45 changed files with 5047 additions and 316 deletions
@@ -48,9 +48,13 @@ import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
import { fetchM49TgsFailClosedResult } from "./m49TgsFailClosed";
import { fetchM49TgsFullShadowResult } from "./m49TgsFullShadow";
import { fetchVegetationShadowResult } from "./vegetationShadow";
import {
fetchVegetationBenchmarkResult,
fetchVegetationShadowResult,
} from "./vegetationShadow";
export type AdvancedLaboratoryWorkId =
| "lab-v1-vegetation-benchmark"
| "lab-v1-vegetation-shadow"
| "m48-object-centric-quality"
| "m48-small-static-passage-regression"
@@ -102,6 +106,7 @@ export interface AdvancedLaboratoryIndexItem {
}
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"lab-v1-vegetation-benchmark",
"lab-v1-vegetation-shadow",
"m48-object-centric-quality",
"m48-small-static-passage-regression",
@@ -148,6 +153,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
];
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"lab-v1-vegetation-benchmark": "lab-v1-vegetation-benchmark",
"lab-v1-vegetation-shadow": "lab-v1-vegetation-shadow",
"m48-object-centric-quality": "m48-object-quality-(?:pack|result)",
"m48-small-static-passage-regression": "m48-small-static-passage-regression",
@@ -201,6 +207,7 @@ export function isAdvancedLaboratoryWorkId(
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
return {
vegetationBenchmark: null,
vegetationShadow: null,
m47Graph: null,
m48: null,
@@ -335,7 +342,8 @@ export function advancedLaboratoryResultAvailable(
workId: AdvancedLaboratoryWorkId,
results: AdvancedLaboratoryResults,
): boolean {
return workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
return workId === "lab-v1-vegetation-benchmark" ? results.vegetationBenchmark !== null
: workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
: workId === "m48-object-centric-quality" ? results.m48 !== null
: workId === "m48-small-static-passage-regression" ? results.m48SmallStatic !== null
: workId === "m48-static-occupancy-qualification" ? results.m48StaticOccupancy !== null
@@ -393,7 +401,10 @@ export async function fetchAdvancedLaboratoryResult(
} = {},
): Promise<AdvancedLaboratoryResults> {
const results = emptyAdvancedLaboratoryResults();
if (workId === "lab-v1-vegetation-shadow") {
if (workId === "lab-v1-vegetation-benchmark") {
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation benchmark identity не выбрана.");
results.vegetationBenchmark = await fetchVegetationBenchmarkResult(resultId, { fetcher, signal });
} else if (workId === "lab-v1-vegetation-shadow") {
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation LAB identity не выбрана.");
results.vegetationShadow = await fetchVegetationShadowResult(resultId, { fetcher, signal });
} else if (workId === "m48-object-centric-quality") {
@@ -45,6 +45,7 @@ import type { M49TgsFullShadowResult } from "./m49TgsFullShadow";
import type { VegetationShadowResult } from "./vegetationShadow";
export interface AdvancedLaboratoryResults {
vegetationBenchmark: VegetationShadowResult | null;
vegetationShadow: VegetationShadowResult | null;
m47Graph: M47ReferenceGraphLabResult | null;
m48: M48AdvancedResult | null;
@@ -967,6 +967,7 @@ export async function fetchAdvancedLaboratoryResults({
const e39 = settledCatalogValue(settled[7]);
const e40 = settledCatalogValue(settled[8]);
return {
vegetationBenchmark: null,
vegetationShadow: null,
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
m48r3StaticOccupancy: null,
@@ -1,6 +1,7 @@
import type { LaboratoryFetch } from "./advancedResults";
const RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
const BENCHMARK_RESULT_ID = /^lab-v1-vegetation-benchmark-[a-f0-9]{64}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const CANDIDATES = ["ddrnet", "ppliteseg"] as const;
const ROUTE_MODES = ["source", "ddrnet", "ppliteseg", "urban", "rural", "offroad"] as const;
@@ -75,6 +76,71 @@ export interface VegetationRouteVideo {
aggregatePredictionPixels: readonly number[];
policyPresets: Readonly<Record<string, Readonly<Record<string, string>>>> | null;
fusionMode: "synchronised-multilayer-review" | null;
validFovMaskSha256: string | null;
}
export interface VegetationMixedRouteCase {
caseId: string;
phase: "rural" | "transition" | "urban";
sourceSequence: number;
sessionSeconds: number;
assets: Readonly<Record<"source" | "city" | "vegetation" | "tgs", string>>;
tgs: {
groundCells: number;
occupiedCells: number;
rejectedCells: number;
unobservedCells: number;
};
}
export interface VegetationMixedRouteReview {
sourceId: "RAVNOVES004TREE";
sessionId: string;
packId: string;
frameCount: 10;
models: {
city: { name: string; inferenceFps: number; endToEndP95Ms: number };
vegetation: { name: string; latencyP95Ms: number };
tgs: { name: string; latencyP95Ms: number; cellSizeM: number; radiusM: number };
};
cases: readonly VegetationMixedRouteCase[];
}
export interface VegetationFullRouteLayer {
name: string;
resultId: string;
frameCount: 6830;
taxonomy: readonly VegetationVideoSemanticClass[];
inferenceFps: number;
latencyP95Ms: number;
peakReservedVramBytes: number;
}
export interface VegetationFullRouteReview {
sourceId: "RAVNOVES004TREE";
sessionId: "20260828T130511Z_viewer_live";
sourceJobId: "recorded-camera-eb2783c5480d56bda07c8af0";
sourceJobInputSha256: string;
sourceStreamSha256: string;
recordedMediaSourceId: "recorded.camera.6a3945242828a038";
recordedMediaGenerationSha256: string;
frameCount: 6830;
width: 800;
height: 600;
timelineStartSeconds: number;
timelineEndSeconds: number;
timelineArtifact: {
sha256: string;
byteLength: number;
};
frameSourceTimesNs: readonly number[];
decodeRepair: {
repairedFrameCount: 1;
sequence: 6092;
method: "duplicate-previous-decoded-frame";
};
city: VegetationFullRouteLayer;
vegetation: VegetationFullRouteLayer;
}
export interface VegetationShadowResult {
@@ -86,6 +152,8 @@ export interface VegetationShadowResult {
routeCases: readonly VegetationVisualCase[];
validationCases: readonly VegetationVisualCase[];
routeVideo: VegetationRouteVideo | null;
routeReview: VegetationMixedRouteReview | null;
routeFullReview: VegetationFullRouteReview | null;
limitations: readonly string[];
visualShadowReady: true;
missionPolicyReadyForConfiguration: true;
@@ -192,6 +260,7 @@ function visualCaseValue(
value: unknown,
resultId: string,
expectedKind: "goose" | "ravnoves",
endpointRoot: string,
): VegetationVisualCase {
const row = objectValue(value, `vegetation.${expectedKind}.case`);
exact(row.source_kind, expectedKind, "vegetation.case.source_kind");
@@ -210,7 +279,7 @@ function visualCaseValue(
if (!SHA256.test(sha256) || !path.startsWith(`visual/${expectedKind}/${caseId}/`)) {
throw new VegetationShadowContractError(`vegetation.case.assets.${key}: proof invalid.`);
}
projected[key] = `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/assets/${path
projected[key] = `${endpointRoot}/${encodeURIComponent(resultId)}/assets/${path
.split("/")
.map(encodeURIComponent)
.join("/")}`;
@@ -342,10 +411,16 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
evidenceState,
};
});
const expectedClassCount = viewKind === "coarse-material-policy-review" ? 9 : 64;
const expectedClassCount = viewKind === "coarse-material-policy-review" ? 10 : 64;
if (classes.length !== expectedClassCount) {
throw new VegetationShadowContractError("vegetation.route_video: taxonomy size changed.");
}
if (
viewKind === "coarse-material-policy-review"
&& (classes[9]?.disposition !== "undefined" || classes[9]?.evidenceState !== "UNOBSERVED")
) {
throw new VegetationShadowContractError("vegetation.route_video: valid-FOV class changed.");
}
const aggregatePredictionPixels = arrayValue(
row.aggregate_prediction_pixels,
"vegetation.route_video.aggregate_prediction_pixels",
@@ -364,7 +439,18 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
integerValue(maskArchive.byte_length, "vegetation.route_video.mask_archive.byte_length");
let policyPresets: VegetationRouteVideo["policyPresets"] = null;
let fusionMode: VegetationRouteVideo["fusionMode"] = null;
let validFovMaskSha256: string | null = null;
if (viewKind === "coarse-material-policy-review") {
const validFov = objectValue(row.valid_fov, "vegetation.route_video.valid_fov");
exact(validFov.mask_path, "video/valid-fov-mask.png", "vegetation.route_video.valid_fov.path");
validFovMaskSha256 = textValue(
validFov.mask_sha256,
"vegetation.route_video.valid_fov.sha256",
);
if (!SHA256.test(validFovMaskSha256)) {
throw new VegetationShadowContractError("vegetation.route_video: valid-FOV digest invalid.");
}
exact(validFov.outside_valid_fov_class_id, 9, "vegetation.route_video.valid_fov.class_id");
const policy = objectValue(row.policy, "vegetation.route_video.policy");
const presets = objectValue(policy.presets, "vegetation.route_video.policy.presets");
policyPresets = Object.fromEntries(Object.entries(presets).map(([presetId, rawRules]) => {
@@ -399,10 +485,358 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
aggregatePredictionPixels,
policyPresets,
fusionMode,
validFovMaskSha256,
};
}
function parseResult(value: unknown, resultId: string): VegetationShadowResult {
function mixedRouteReviewValue(
value: unknown,
resultId: string,
endpointRoot: string,
): VegetationMixedRouteReview | null {
if (value === null || value === undefined) return null;
const row = objectValue(value, "vegetation.route_review");
exact(row.source_id, "RAVNOVES004TREE", "vegetation.route_review.source_id");
exact(row.frame_count, 10, "vegetation.route_review.frame_count");
exact(row.ground_truth, false, "vegetation.route_review.ground_truth");
exact(
row.selection_policy,
"same-scene-camera-lidar-aligned-review-islands/v1",
"vegetation.route_review.selection_policy",
);
const packId = textValue(row.pack_id, "vegetation.route_review.pack_id");
if (!/^mixed-route-review-pack-[a-f0-9]{64}$/.test(packId)) {
throw new VegetationShadowContractError("vegetation.route_review.pack_id: identity invalid.");
}
const models = objectValue(row.models, "vegetation.route_review.models");
const city = objectValue(models.city, "vegetation.route_review.models.city");
const vegetation = objectValue(models.vegetation, "vegetation.route_review.models.vegetation");
const tgsModel = objectValue(models.tgs, "vegetation.route_review.models.tgs");
exact(city.frames, 10, "vegetation.route_review.models.city.frames");
exact(vegetation.frames, 10, "vegetation.route_review.models.vegetation.frames");
exact(tgsModel.frames, 10, "vegetation.route_review.models.tgs.frames");
const cases = arrayValue(row.cases, "vegetation.route_review.cases").map((raw, index) => {
const item = objectValue(raw, `vegetation.route_review.cases[${index}]`);
const caseId = textValue(item.case_id, `vegetation.route_review.cases[${index}].case_id`);
if (caseId !== `route-${String(index + 1).padStart(2, "0")}`) {
throw new VegetationShadowContractError("vegetation.route_review.case order changed.");
}
const phaseValue = item.phase;
if (phaseValue !== "rural" && phaseValue !== "transition" && phaseValue !== "urban") {
throw new VegetationShadowContractError("vegetation.route_review.phase changed.");
}
const phase: VegetationMixedRouteCase["phase"] = phaseValue;
const assets = objectValue(item.assets, `vegetation.route_review.cases[${index}].assets`);
const projected = Object.fromEntries(["source", "city", "vegetation", "tgs"].map((key) => {
const descriptor = objectValue(assets[key], `vegetation.route_review.assets.${key}`);
const path = textValue(descriptor.path, `vegetation.route_review.assets.${key}.path`);
const digest = textValue(descriptor.sha256, `vegetation.route_review.assets.${key}.sha256`);
if (!SHA256.test(digest) || !path.startsWith(`route-review/${caseId}/`)) {
throw new VegetationShadowContractError(`vegetation.route_review.assets.${key}: proof invalid.`);
}
return [key, `${endpointRoot}/${encodeURIComponent(resultId)}/assets/${path
.split("/").map(encodeURIComponent).join("/")}`];
})) as Record<"source" | "city" | "vegetation" | "tgs", string>;
const tgs = objectValue(item.tgs, `vegetation.route_review.cases[${index}].tgs`);
const groundCells = integerValue(tgs.ground_cells, "vegetation.route_review.tgs.ground");
const occupiedCells = integerValue(tgs.occupied_cells, "vegetation.route_review.tgs.occupied");
const rejectedCells = integerValue(tgs.rejected_cells, "vegetation.route_review.tgs.rejected");
const unobservedCells = integerValue(tgs.unobserved_cells, "vegetation.route_review.tgs.unobserved");
if (groundCells + occupiedCells + rejectedCells + unobservedCells !== 2244) {
throw new VegetationShadowContractError("vegetation.route_review.tgs cell accounting changed.");
}
return {
caseId,
phase,
sourceSequence: integerValue(item.source_sequence, "vegetation.route_review.source_sequence"),
sessionSeconds: numberValue(item.session_seconds, "vegetation.route_review.session_seconds"),
assets: projected,
tgs: { groundCells, occupiedCells, rejectedCells, unobservedCells },
};
});
if (cases.length !== 10) {
throw new VegetationShadowContractError("vegetation.route_review.cases: expected 10 aligned islands.");
}
return {
sourceId: "RAVNOVES004TREE",
sessionId: textValue(row.session_id, "vegetation.route_review.session_id"),
packId,
frameCount: 10,
models: {
city: {
name: textValue(city.name, "vegetation.route_review.models.city.name"),
inferenceFps: numberValue(city.inference_fps, "vegetation.route_review.models.city.fps"),
endToEndP95Ms: numberValue(city.end_to_end_p95_ms, "vegetation.route_review.models.city.p95"),
},
vegetation: {
name: textValue(vegetation.name, "vegetation.route_review.models.vegetation.name"),
latencyP95Ms: numberValue(vegetation.latency_p95_ms, "vegetation.route_review.models.vegetation.p95"),
},
tgs: {
name: textValue(tgsModel.name, "vegetation.route_review.models.tgs.name"),
latencyP95Ms: numberValue(tgsModel.latency_p95_ms, "vegetation.route_review.models.tgs.p95"),
cellSizeM: numberValue(tgsModel.cell_size_m, "vegetation.route_review.models.tgs.cell"),
radiusM: numberValue(tgsModel.radius_m, "vegetation.route_review.models.tgs.radius"),
},
},
cases,
};
}
function fullRouteTaxonomyValue(
value: unknown,
label: string,
schema: string,
classCount: number,
): readonly VegetationVideoSemanticClass[] {
const taxonomy = objectValue(value, `${label}.taxonomy`);
exact(taxonomy.schema_version, schema, `${label}.taxonomy.schema`);
const classes = arrayValue(taxonomy.classes, `${label}.taxonomy.classes`).map(
(raw, expectedId): VegetationVideoSemanticClass => {
const item = objectValue(raw, `${label}.taxonomy[${expectedId}]`);
const classId = integerValue(item.class_id, `${label}.class_id[${expectedId}]`);
if (classId !== expectedId) {
throw new VegetationShadowContractError(`${label}: taxonomy order changed.`);
}
const color = arrayValue(item.color_rgb, `${label}.color[${expectedId}]`)
.map((channel, index) => integerValue(channel, `${label}.color[${expectedId}][${index}]`));
if (color.length !== 3 || color.some((channel) => channel > 255)) {
throw new VegetationShadowContractError(`${label}: taxonomy color invalid.`);
}
const disposition = item.disposition;
if (
disposition !== "labeled"
&& disposition !== "ambiguous"
&& disposition !== "prediction"
&& disposition !== "undefined"
) {
throw new VegetationShadowContractError(`${label}: taxonomy disposition changed.`);
}
return {
classId,
label: textValue(item.label, `${label}.label[${expectedId}]`),
colorRgb: color as unknown as readonly [number, number, number],
disposition,
materialClass: item.material_class === null || item.material_class === undefined
? null
: textValue(item.material_class, `${label}.material[${expectedId}]`),
evidenceState: item.evidence_state === null || item.evidence_state === undefined
? null
: textValue(item.evidence_state, `${label}.evidence[${expectedId}]`),
};
},
);
if (classes.length !== classCount) {
throw new VegetationShadowContractError(`${label}: taxonomy size changed.`);
}
return classes;
}
function fullRouteLayerValue(
value: unknown,
layer: "city" | "vegetation",
): VegetationFullRouteLayer {
const label = `vegetation.route_full_review.layers.${layer}`;
const row = objectValue(value, label);
const resultId = textValue(row.result_id, `${label}.result_id`);
const identity = layer === "city"
? /^result-[a-f0-9]{64}$/
: /^lab-v1-ravnoves-video-ddrnet-[a-f0-9]{64}$/;
if (!identity.test(resultId)) {
throw new VegetationShadowContractError(`${label}: identity invalid.`);
}
exact(row.frame_count, 6830, `${label}.frame_count`);
const archive = objectValue(row.mask_archive, `${label}.mask_archive`);
exact(
archive.path,
layer === "city" ? "video/eomt-semantic-masks.zip" : "video/ddrnet-semantic-masks.zip",
`${label}.mask_archive.path`,
);
const digest = textValue(archive.sha256, `${label}.mask_archive.sha256`);
if (!SHA256.test(digest)) {
throw new VegetationShadowContractError(`${label}: archive digest invalid.`);
}
integerValue(archive.byte_length, `${label}.mask_archive.byte_length`);
return {
name: textValue(row.name, `${label}.name`),
resultId,
frameCount: 6830,
taxonomy: fullRouteTaxonomyValue(
row.taxonomy,
label,
layer === "city"
? "missioncore.recorded-eomt-taxonomy/v1"
: "missioncore.lab-v1-vegetation-taxonomy/v1",
layer === "city" ? 16 : 64,
),
inferenceFps: numberValue(row.inference_fps, `${label}.inference_fps`),
latencyP95Ms: numberValue(row.latency_p95_ms, `${label}.latency_p95_ms`),
peakReservedVramBytes: integerValue(
row.peak_reserved_vram_bytes,
`${label}.peak_reserved_vram_bytes`,
),
};
}
function fullRouteReviewValue(value: unknown): VegetationFullRouteReview | null {
if (value === null || value === undefined) return null;
const row = objectValue(value, "vegetation.route_full_review");
exact(row.source_id, "RAVNOVES004TREE", "vegetation.route_full_review.source_id");
exact(
row.session_id,
"20260828T130511Z_viewer_live",
"vegetation.route_full_review.session_id",
);
exact(
row.source_job_id,
"recorded-camera-eb2783c5480d56bda07c8af0",
"vegetation.route_full_review.source_job_id",
);
exact(row.frame_count, 6830, "vegetation.route_full_review.frame_count");
exact(row.width, 800, "vegetation.route_full_review.width");
exact(row.height, 600, "vegetation.route_full_review.height");
exact(row.ground_truth, false, "vegetation.route_full_review.ground_truth");
const sourceJobInputSha256 = textValue(
row.source_job_input_sha256,
"vegetation.route_full_review.source_job_input_sha256",
);
const sourceStreamSha256 = textValue(
row.source_stream_sha256,
"vegetation.route_full_review.source_stream_sha256",
);
exact(
sourceJobInputSha256,
"eb2783c5480d56bda07c8af008dff5344d19dc550ef70fe2075d6f098f7cc715",
"vegetation.route_full_review.source_job_input_sha256",
);
exact(
sourceStreamSha256,
"e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac",
"vegetation.route_full_review.source_stream_sha256",
);
exact(
row.recorded_media_source_id,
"recorded.camera.6a3945242828a038",
"vegetation.route_full_review.recorded_media_source_id",
);
const recordedMediaGenerationSha256 = textValue(
row.recorded_media_generation_sha256,
"vegetation.route_full_review.recorded_media_generation_sha256",
);
exact(
recordedMediaGenerationSha256,
"b073ea1e7babf1c77a664e1a5b95e3702d0e05b0e34c1e85a7c67a6f8b392ded",
"vegetation.route_full_review.recorded_media_generation_sha256",
);
if (
!SHA256.test(sourceJobInputSha256)
|| !SHA256.test(sourceStreamSha256)
|| !SHA256.test(recordedMediaGenerationSha256)
) {
throw new VegetationShadowContractError("vegetation.route_full_review: source digest invalid.");
}
const timelineStartSeconds = numberValue(
row.timeline_start_seconds,
"vegetation.route_full_review.timeline_start_seconds",
);
const timelineEndSeconds = numberValue(
row.timeline_end_seconds,
"vegetation.route_full_review.timeline_end_seconds",
);
if (timelineEndSeconds <= timelineStartSeconds) {
throw new VegetationShadowContractError("vegetation.route_full_review: timeline invalid.");
}
const timeline = objectValue(row.timeline, "vegetation.route_full_review.timeline");
exact(
timeline.path,
"video/frame-source-times-ns.bin",
"vegetation.route_full_review.timeline.path",
);
exact(
timeline.encoding,
"uint64-le-nanoseconds",
"vegetation.route_full_review.timeline.encoding",
);
exact(timeline.frame_count, 6830, "vegetation.route_full_review.timeline.frame_count");
const timelineSha256 = textValue(
timeline.sha256,
"vegetation.route_full_review.timeline.sha256",
);
if (!SHA256.test(timelineSha256)) {
throw new VegetationShadowContractError("vegetation.route_full_review: timeline digest invalid.");
}
const timelineByteLength = integerValue(
timeline.byte_length,
"vegetation.route_full_review.timeline.byte_length",
);
exact(timelineByteLength, 6830 * 8, "vegetation.route_full_review.timeline.byte_length");
const decodeRepair = objectValue(
row.decode_repair,
"vegetation.route_full_review.decode_repair",
);
exact(decodeRepair.repaired_frame_count, 1, "vegetation.route_full_review.decode_repair.count");
exact(decodeRepair.sequence, 6092, "vegetation.route_full_review.decode_repair.sequence");
exact(
decodeRepair.method,
"duplicate-previous-decoded-frame",
"vegetation.route_full_review.decode_repair.method",
);
const repairProofs = objectValue(
decodeRepair.proofs,
"vegetation.route_full_review.decode_repair.proofs",
);
for (const [key, expectedPath] of Object.entries({
eomt: "proofs/decode_repair.json",
ddrnet: "proofs/ddrnet_decode_repair.json",
})) {
const proof = objectValue(
repairProofs[key],
`vegetation.route_full_review.decode_repair.proofs.${key}`,
);
exact(
proof.path,
expectedPath,
`vegetation.route_full_review.decode_repair.proofs.${key}.path`,
);
const digest = textValue(
proof.sha256,
`vegetation.route_full_review.decode_repair.proofs.${key}.sha256`,
);
if (!SHA256.test(digest)) {
throw new VegetationShadowContractError("vegetation.route_full_review: repair proof invalid.");
}
}
const layers = objectValue(row.layers, "vegetation.route_full_review.layers");
return {
sourceId: "RAVNOVES004TREE",
sessionId: "20260828T130511Z_viewer_live",
sourceJobId: "recorded-camera-eb2783c5480d56bda07c8af0",
sourceJobInputSha256,
sourceStreamSha256,
recordedMediaSourceId: "recorded.camera.6a3945242828a038",
recordedMediaGenerationSha256,
frameCount: 6830,
width: 800,
height: 600,
timelineStartSeconds,
timelineEndSeconds,
timelineArtifact: { sha256: timelineSha256, byteLength: timelineByteLength },
frameSourceTimesNs: [],
decodeRepair: {
repairedFrameCount: 1,
sequence: 6092,
method: "duplicate-previous-decoded-frame",
},
city: fullRouteLayerValue(layers.city, "city"),
vegetation: fullRouteLayerValue(layers.vegetation, "vegetation"),
};
}
function parseResult(
value: unknown,
resultId: string,
endpointRoot: string,
): VegetationShadowResult {
const payload = objectValue(value, "Vegetation LAB");
exact(payload.schema_version, "missioncore.lab-v1-vegetation-shadow/v1", "vegetation.schema");
exact(payload.result_id, resultId, "vegetation.result_id");
@@ -438,10 +872,15 @@ function parseResult(value: unknown, resultId: string): VegetationShadowResult {
"vegetation.authority.camera_semantics_can_clear_rigid_geometry",
);
const routeCases = arrayValue(catalogs.ravnoves, "vegetation.catalogs.ravnoves")
.map((item) => visualCaseValue(item, resultId, "ravnoves"));
.map((item) => visualCaseValue(item, resultId, "ravnoves", endpointRoot));
const validationCases = arrayValue(catalogs.goose, "vegetation.catalogs.goose")
.map((item) => visualCaseValue(item, resultId, "goose"));
if (routeCases.length !== 0 || validationCases.length !== 12) {
.map((item) => visualCaseValue(item, resultId, "goose", endpointRoot));
const routeReview = mixedRouteReviewValue(payload.route_review, resultId, endpointRoot);
const routeFullReview = fullRouteReviewValue(payload.route_full_review);
if (
routeCases.length !== 0
|| (routeReview || routeFullReview ? validationCases.length !== 0 : validationCases.length !== 12)
) {
throw new VegetationShadowContractError("vegetation.catalogs: ожидалось 12 truth-backed GOOSE случаев без route viewer.");
}
return {
@@ -453,6 +892,8 @@ function parseResult(value: unknown, resultId: string): VegetationShadowResult {
routeCases,
validationCases,
routeVideo: routeVideoValue(payload.route_video),
routeReview,
routeFullReview,
limitations: arrayValue(payload.limitations, "vegetation.limitations")
.map((item, index) => textValue(item, `vegetation.limitations[${index}]`)),
visualShadowReady: true,
@@ -473,6 +914,23 @@ export function vegetationVideoMaskUrl(resultId: string, sequence: number): stri
return `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/masks/${sequence}`;
}
export function vegetationFullRouteMaskUrl(
resultId: string,
layer: "city" | "vegetation",
sequence: number,
): string {
if (
!RESULT_ID.test(resultId)
|| (layer !== "city" && layer !== "vegetation")
|| !Number.isInteger(sequence)
|| sequence < 0
|| sequence >= 6830
) {
throw new VegetationShadowContractError("Vegetation full-route mask identity недопустима.");
}
return `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/route-masks/${layer}/${sequence}`;
}
export async function fetchVegetationShadowResult(
resultId: string,
{
@@ -490,5 +948,74 @@ export async function fetchVegetationShadowResult(
if (!response.ok) {
throw new VegetationShadowContractError(`Vegetation LAB недоступна: HTTP ${response.status}.`);
}
return parseResult(await response.json(), resultId);
const result = parseResult(
await response.json(),
resultId,
"/api/v1/laboratory/vegetation-shadow",
);
if (!result.routeFullReview) return result;
const timelineResponse = await fetcher(
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/route-timeline`,
{ method: "GET", headers: { Accept: "application/octet-stream" }, signal },
);
if (!timelineResponse.ok) {
throw new VegetationShadowContractError(
`Vegetation LAB timeline недоступна: HTTP ${timelineResponse.status}.`,
);
}
if (
timelineResponse.headers.get("etag")
!== `"${result.routeFullReview.timelineArtifact.sha256}"`
) {
throw new VegetationShadowContractError("Vegetation LAB timeline digest изменён.");
}
const timelinePayload = await timelineResponse.arrayBuffer();
if (timelinePayload.byteLength !== result.routeFullReview.timelineArtifact.byteLength) {
throw new VegetationShadowContractError("Vegetation LAB timeline size изменён.");
}
const timelineView = new DataView(timelinePayload);
const frameSourceTimesNs = Array.from({ length: result.routeFullReview.frameCount }, (_, index) => {
const value = Number(timelineView.getBigUint64(index * 8, true));
if (!Number.isSafeInteger(value)) {
throw new VegetationShadowContractError("Vegetation LAB timeline содержит unsafe time.");
}
return value;
});
if (
frameSourceTimesNs[0] !== Math.round(result.routeFullReview.timelineStartSeconds * 1_000_000_000)
|| frameSourceTimesNs.some((time, index) => index > 0 && time <= frameSourceTimesNs[index - 1]!)
) {
throw new VegetationShadowContractError("Vegetation LAB timeline нарушена.");
}
return {
...result,
routeFullReview: { ...result.routeFullReview, frameSourceTimesNs },
};
}
export async function fetchVegetationBenchmarkResult(
resultId: string,
{
fetcher = fetch,
signal,
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<VegetationShadowResult> {
if (!BENCHMARK_RESULT_ID.test(resultId)) {
throw new VegetationShadowContractError("Vegetation benchmark identity недопустима.");
}
const endpointRoot = "/api/v1/laboratory/vegetation-benchmark";
const response = await fetcher(
`${endpointRoot}/${encodeURIComponent(resultId)}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new VegetationShadowContractError(
`Vegetation benchmark недоступен: HTTP ${response.status}.`,
);
}
const result = parseResult(await response.json(), resultId, endpointRoot);
if (result.routeVideo) {
throw new VegetationShadowContractError("Vegetation benchmark содержит route video.");
}
return result;
}
@@ -81,6 +81,21 @@
justify-content: flex-end;
}
.m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"][data-multi-semantic="true"] {
flex-wrap: wrap;
}
.m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"][data-multi-semantic="true"]
> .m4-replay-threat-visual__pane-layer-controls {
flex: 1 0 100%;
justify-content: flex-start;
}
.m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"][data-multi-semantic="true"]
> .m4-replay-threat-visual__pane-mode-controls {
margin-left: auto;
}
.m4-replay-threat-evidence-viewer[data-mode-controls="content"]:has(
.m4-replay-threat-visual__review-controls
) .m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"] {
@@ -51,6 +51,7 @@ import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
import { M49TgsFailClosedResultView } from "./M49TgsFailClosedResult";
import { M49TgsFullShadowResultView } from "./M49TgsFullShadowResult";
import { VegetationShadowResultView } from "./VegetationShadowResult";
import { VegetationBenchmarkResultView } from "./VegetationBenchmarkResult";
export { isAdvancedLaboratoryWorkId };
export type { AdvancedLaboratoryWorkId };
@@ -93,6 +94,9 @@ export function AdvancedLaboratoryResult({
failedSessionId: string | null;
replayError: string | null;
}) {
if (workId === "lab-v1-vegetation-benchmark" && results.vegetationBenchmark) {
return <VegetationBenchmarkResultView rigLabel={rigLabel} result={results.vegetationBenchmark} />;
}
if (workId === "lab-v1-vegetation-shadow" && results.vegetationShadow) {
return <VegetationShadowResultView rigLabel={rigLabel} result={results.vegetationShadow} />;
}
@@ -71,7 +71,6 @@ export function M49TgsFullShadowEvidence({
const controller = new AbortController();
setSemantic(null);
setSemanticError(null);
if (semanticOverride) return () => controller.abort();
void fetchE47SemanticSlamResult({
resultId: result.source.linkedSemanticResultId,
signal: controller.signal,
@@ -87,7 +86,7 @@ export function M49TgsFullShadowEvidence({
if (!controller.signal.aborted) setSemanticError(message(caught));
});
return () => controller.abort();
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId, semanticOverride]);
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId]);
useEffect(() => {
const controller = new AbortController();
@@ -201,15 +200,28 @@ export function M49TgsFullShadowEvidence({
const handleSequenceChange = useCallback((sequence: number | null) => {
setActiveSequence(sequence);
}, []);
const semanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(() => [
...(semantic ? [{
id: "urban",
controlLabel: "ГОРОД · EoMT",
resultId: semantic.resultId,
taxonomy: semantic.taxonomy,
label: "EoMT Cityscapes semantic · recorded video",
maskAriaLabel: "EoMT urban semantic prediction",
}] : []),
...(semanticOverride ? [{
...semanticOverride,
id: semanticOverride.id ?? "vegetation",
controlLabel: semanticOverride.controlLabel ?? "ПРИРОДА · DDRNet",
}] : []),
], [semantic, semanticOverride]);
return (
<>
<M4ReplayThreatVisual
resultId={result.source.linkedVisualResultId}
semantic={semanticOverride ?? (semantic ? {
resultId: semantic.resultId,
taxonomy: semantic.taxonomy,
} : undefined)}
semanticLayers={semanticLayers}
initialSemanticLayerId={semanticOverride ? "vegetation" : "urban"}
showReviewAnchorBoxes={false}
reviewLabel="4 489 source-paced TGS frames"
evidenceLabel={evidenceLabel}
@@ -227,7 +239,7 @@ export function M49TgsFullShadowEvidence({
replacePointCloud: false,
}}
/>
{!semanticOverride && semanticError ? (
{semanticError ? (
<div className="m4-replay-threat-visual__pane-status" role="alert">
Semantic overlay недоступен: {semanticError}
</div>
@@ -96,6 +96,8 @@ function SpatialState({ message: text }: { message: string }) {
}
export interface M4ReplayThreatSemanticLayer {
id?: string;
controlLabel?: string;
resultId: string;
spatialResultId?: string | null;
maskUrl?: (sequence: number) => string;
@@ -155,6 +157,8 @@ const EMPTY_REVIEW_ANCHORS: readonly M4ReplayThreatReviewAnchor[] = [];
export function M4ReplayThreatVisual({
resultId,
semantic,
semanticLayers,
initialSemanticLayerId,
reviewAnchors = EMPTY_REVIEW_ANCHORS,
showReviewAnchorBoxes = true,
reviewLabel = "Контрольные примеры M4.8R1",
@@ -168,6 +172,8 @@ export function M4ReplayThreatVisual({
}: {
resultId: string;
semantic?: M4ReplayThreatSemanticLayer;
semanticLayers?: readonly M4ReplayThreatSemanticLayer[];
initialSemanticLayerId?: string;
reviewAnchors?: readonly M4ReplayThreatReviewAnchor[];
showReviewAnchorBoxes?: boolean;
reviewLabel?: string;
@@ -199,6 +205,35 @@ export function M4ReplayThreatVisual({
));
const [expanded, setExpanded] = useState(false);
const [selectedReviewAnchorIndex, setSelectedReviewAnchorIndex] = useState(0);
const availableSemanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(
() => semanticLayers?.length ? semanticLayers : semantic ? [semantic] : [],
[semantic, semanticLayers],
);
const semanticLayerIdentity = availableSemanticLayers
.map((layer, index) => layer.id ?? `${layer.resultId}:${index}`)
.join("|");
const [selectedSemanticLayerId, setSelectedSemanticLayerId] = useState(
initialSemanticLayerId ?? "",
);
useEffect(() => {
if (!availableSemanticLayers.length) {
setSelectedSemanticLayerId("");
return;
}
const selectedStillExists = availableSemanticLayers.some(
(layer, index) => (layer.id ?? `${layer.resultId}:${index}`) === selectedSemanticLayerId,
);
if (selectedStillExists) return;
const preferred = initialSemanticLayerId
? availableSemanticLayers.find((layer) => layer.id === initialSemanticLayerId)
: null;
const next = preferred ?? availableSemanticLayers[0]!;
const nextIndex = availableSemanticLayers.indexOf(next);
setSelectedSemanticLayerId(next.id ?? `${next.resultId}:${nextIndex}`);
}, [availableSemanticLayers, initialSemanticLayerId, semanticLayerIdentity, selectedSemanticLayerId]);
const activeSemantic = availableSemanticLayers.find(
(layer, index) => (layer.id ?? `${layer.resultId}:${index}`) === selectedSemanticLayerId,
) ?? availableSemanticLayers[0];
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
const metadata = useM4ThreatTimelineMetadata(resultId, timelineEndpointRoot);
const playbackRange = useMemo(() => metadata.timeline ? ({
@@ -298,19 +333,21 @@ export function M4ReplayThreatVisual({
sequence: frame?.sequence ?? null,
endpointRoot: timelineEndpointRoot,
});
const semanticSpatialResultId = semantic
? semantic.spatialResultId === undefined ? semantic.resultId : semantic.spatialResultId
const semanticSpatialResultId = activeSemantic
? activeSemantic.spatialResultId === undefined
? activeSemantic.resultId
: activeSemantic.spatialResultId
: null;
const spatialSemanticTaxonomy = useMemo<readonly E47SemanticClass[]>(
() => semanticSpatialResultId && semantic
? semantic.taxonomy.map((item) => ({
() => semanticSpatialResultId && activeSemantic
? activeSemantic.taxonomy.map((item) => ({
classId: item.classId,
label: item.label,
disposition: item.disposition === "ambiguous" ? "ambiguous" : "labeled",
colorRgb: item.colorRgb,
}))
: [],
[semantic, semanticSpatialResultId],
[activeSemantic, semanticSpatialResultId],
);
const semanticTimeline = useE47SemanticTimelineFrame({
resultId: semanticSpatialResultId,
@@ -386,14 +423,14 @@ export function M4ReplayThreatVisual({
[frame, reviewAnchorBoxes, showReferenceMediaLayers, staticObstacleBoxes],
);
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
() => semantic?.taxonomy.map((item) => ({
() => activeSemantic?.taxonomy.map((item) => ({
id: item.classId,
label: `semantic: ${item.label}`,
})) ?? [],
[semantic?.taxonomy],
[activeSemantic?.taxonomy],
);
const semanticPalette = useMemo<readonly RecordedEvidenceSemanticPaletteEntry[]>(
() => semantic?.taxonomy.map((item) => ({
() => activeSemantic?.taxonomy.map((item) => ({
classId: item.classId,
color: item.disposition === "undefined"
? { kind: "transparent" as const }
@@ -404,7 +441,7 @@ export function M4ReplayThreatVisual({
? 0
: item.disposition === "ambiguous" ? 0.52 : 0.92,
})) ?? [],
[semantic?.taxonomy],
[activeSemantic?.taxonomy],
);
const semanticFrame = semanticTimeline.activeFrame?.sequence === frame?.sequence
? semanticTimeline.activeFrame
@@ -422,7 +459,7 @@ export function M4ReplayThreatVisual({
&& lastSpatialSemanticFrameRef.current.frame.sequence === spatialFrame?.sequence
? lastSpatialSemanticFrameRef.current.frame
: null;
const semanticIntegrityError = semantic && spatialFrame && spatialSemanticFrame && (
const semanticIntegrityError = activeSemantic && spatialFrame && spatialSemanticFrame && (
spatialSemanticFrame.sourcePointCount !== spatialFrame.pointCloudSourceCount
|| spatialFrame.pointCloudSampleCount !== spatialFrame.pointCloudSourceCount
|| spatialFrame.pointCloudBodyXyzM.length !== spatialFrame.pointCloudSourceCount
@@ -431,7 +468,7 @@ export function M4ReplayThreatVisual({
: null;
const alignedSemanticPointIds = useMemo<readonly (number | null)[] | undefined>(() => {
if (
!semantic
!activeSemantic
|| !showSpatialSemantic
|| !spatialFrame
|| !spatialSemanticFrame
@@ -441,7 +478,7 @@ export function M4ReplayThreatVisual({
const status = spatialSemanticFrame.statusCodes[index];
return status === 2 || status === 3 ? classId : null;
});
}, [semantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
}, [activeSemantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
const activeSpatialFrame = spatialFrame?.sequence === timelineFrame.activeSequence
? spatialFrame
: null;
@@ -610,19 +647,19 @@ export function M4ReplayThreatVisual({
},
), [metadata.timeline, spatialFrame, timelineFrame.availableFrames]);
const semanticOverlay: RecordedEvidenceSemanticOverlay | undefined =
semantic && showMediaSemantic && frame
activeSemantic && showMediaSemantic && frame
? {
src: semantic.maskUrl?.(frame.sequence)
?? e47SemanticMaskUrl(semantic.resultId, frame.sequence),
src: activeSemantic.maskUrl?.(frame.sequence)
?? e47SemanticMaskUrl(activeSemantic.resultId, frame.sequence),
prefetchSrcs: Array.from({ length: 12 }, (_, index) => index + 1)
.map((offset) => frame.sequence + offset)
.filter((sequence) => sequence < (metadata.timeline?.frameCount ?? 0))
.map((sequence) => semantic.maskUrl?.(sequence)
?? e47SemanticMaskUrl(semantic.resultId, sequence)),
.map((sequence) => activeSemantic.maskUrl?.(sequence)
?? e47SemanticMaskUrl(activeSemantic.resultId, sequence)),
classes: semanticClasses,
palette: semanticPalette,
opacity: 0.9,
ariaLabel: `${semantic.maskAriaLabel ?? "Semantic prediction"} frame ${frame.sequence + 1}`,
ariaLabel: `${activeSemantic.maskAriaLabel ?? "Semantic prediction"} frame ${frame.sequence + 1}`,
}
: undefined;
const accumulatedCameraPoints = cameraPointOverlay.overlay?.sequence === frame?.sequence
@@ -692,7 +729,7 @@ export function M4ReplayThreatVisual({
</div>
);
const mediaLayerControls = semantic
const mediaLayerControls = activeSemantic
|| (showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery)
|| (showReferenceMediaLayers && metadata.timeline?.cameraObstacleProjectionDelivery) ? (
<div
@@ -700,7 +737,7 @@ export function M4ReplayThreatVisual({
role="group"
aria-label="Слои камеры и видео"
>
{semantic ? (
{activeSemantic ? (
<Button
size="compact"
shape="pill"
@@ -711,6 +748,20 @@ export function M4ReplayThreatVisual({
SEMANTICS
</Button>
) : null}
{availableSemanticLayers.length > 1 ? (
<SegmentedControl
value={selectedSemanticLayerId}
items={availableSemanticLayers.map((layer, index) => ({
value: layer.id ?? `${layer.resultId}:${index}`,
label: layer.controlLabel ?? layer.label ?? `SEMANTIC ${index + 1}`,
}))}
label="Источник семантики"
onChange={(value) => {
setSelectedSemanticLayerId(value);
setShowMediaSemantic(true);
}}
/>
) : null}
{showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery ? (
<Button
size="compact"
@@ -1022,6 +1073,7 @@ export function M4ReplayThreatVisual({
<div
className="m4-replay-threat-visual__pane-toolbar"
data-pane-toolbar="media"
data-multi-semantic={availableSemanticLayers.length > 1 ? "true" : undefined}
>
{mediaLayerControls}
{mediaModeControls}
@@ -1238,8 +1290,8 @@ export function M4ReplayThreatVisual({
return (
<div className="l3-visual-audit m4-replay-threat-visual">
<LaboratoryEvidenceViewer
label={semantic
? semantic.label ?? "Semantic diagnostic replay"
label={activeSemantic
? activeSemantic.label ?? "Semantic diagnostic replay"
: `${evidenceLabel} recorded-realtime replay`}
className="m4-replay-threat-evidence-viewer"
mode={mediaMode ?? "none"}
@@ -0,0 +1,147 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { VegetationShadowResult } from "../../core/laboratory/vegetationShadow";
import {
M48MaskComparisonVisual,
type M48MaskComparisonCase,
} from "./M48FailureAtlasVisual";
function decimal(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
const VEGETATION_LABELS: Readonly<Record<string, string>> = {
high_grass: "Высокая трава",
low_grass: "Низкая трава",
bush: "Куст",
tree_trunk: "Ствол дерева",
tree_crown: "Крона дерева",
hedge: "Живая изгородь",
forest: "Лесная растительность",
crops: "Посевы",
};
function comparisonCases(result: VegetationShadowResult): readonly M48MaskComparisonCase[] {
return result.validationCases.map((item) => {
const focus = item.focus!;
return {
caseId: item.caseId,
title: `${VEGETATION_LABELS[focus.className] ?? focus.className} · truth ${decimal(focus.truthFraction * 100, 1)}% кадра`,
sourceUrl: item.assets.source,
truthUrl: item.assets.truth,
predictions: {
ddrnet: item.assets.ddrnet,
ppliteseg: item.assets.ppliteseg,
},
errors: {
ddrnet: item.assets.ddrnet_error,
ppliteseg: item.assets.ppliteseg_error,
},
};
});
}
export function VegetationBenchmarkResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: VegetationShadowResult;
}) {
const selected = result.candidates.find(
(candidate) => candidate.candidate === result.selectedCandidate,
)!;
const alternative = result.candidates.find(
(candidate) => candidate.candidate !== result.selectedCandidate,
)!;
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="M4.8 · архивный benchmark растительности"
description="Отдельный truth-backed контур GOOSE для сравнения готовых fine-64 весов. Он не является частью RAVNOVES00 realtime LAB и открывается автономно без Worker 006."
status="ARCHIVE ANALYSIS · model qualification only · commands OFF"
statusTone="warning"
facts={[
{ label: "Источник", value: "GOOSE validation · 962 размеченных кадра · 12 hard cases" },
{ label: "Сравнение", value: "DDRNet-39 vs PPLiteSeg · official fine-64 weights" },
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
{ label: "Authority", value: `${rigLabel} · MODEL QUALIFICATION ONLY · commands OFF` },
]}
brief={{
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
approach: "Обе модели прогнаны на 962 кадрах, а 12 визуальных кейсов выбраны детерминированно по truth-поддержке восьми растительных классов. Viewer показывает source, ручной truth, prediction и error.",
principalResult: `${selected.loadedModelName} лидирует по vegetation IoU: ${decimal(selected.vegetationMeanIouPercent, 2)}% против ${decimal(alternative.vegetationMeanIouPercent, 2)}%.`,
limitation: "GOOSE — внешний размеченный домен. Результат выбирает стартовые веса, но не доказывает качество на fisheye RAVNOVES00 и не даёт navigation authority.",
}}
method={{
completeness: "complete",
executionClass: "ai-inference",
pipelineId: "goose-fine64-ready-weights-benchmark-archive/v1",
components: result.candidates.map((candidate) => ({
kind: "model" as const,
name: candidate.loadedModelName,
version: candidate.candidate,
role: candidate.candidate === result.selectedCandidate
? "selected vegetation candidate"
: "comparison candidate",
identitySha256: candidate.checkpointSha256,
})),
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
title="TRUTH — ручная разметка · PREDICTION — ответ модели · ERROR — расхождение"
kind="diagnostic-model"
resizable
>
<M48MaskComparisonVisual
cases={comparisonCases(result)}
initialCandidate={result.selectedCandidate}
/>
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="DDRNet выбран как стартовый vegetation candidate"
status={`${selected.loadedModelName} · перенос на ровер не доказан`}
statusTone="warning"
metrics={[
{
label: "GOOSE mIoU",
value: `${decimal(selected.meanIouPercent, 2)}% / ${decimal(alternative.meanIouPercent, 2)}%`,
hint: `${selected.candidate} / ${alternative.candidate} · полный validation split`,
},
{
label: "Vegetation IoU",
value: `${decimal(selected.vegetationMeanIouPercent, 2)}% / ${decimal(alternative.vegetationMeanIouPercent, 2)}%`,
hint: "grass/vegetation/bush/tree и родственные fine-64 labels",
},
{
label: "Worker shadow p95",
value: `${decimal(selected.shadowLatencyP95Ms, 2)} / ${decimal(alternative.shadowLatencyP95Ms, 2)} ms`,
hint: "чистый inference · одна тяжёлая модель за раз",
},
{
label: "Peak VRAM",
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} / ${decimal(alternative.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
hint: `${selected.candidate} / ${alternative.candidate} · RTX 4090`,
},
]}
conclusion={{
proved: "Обе готовые fine-64 модели воспроизводимо запускаются; DDRNet лучше по aggregate vegetation IoU.",
notProved: "Не доказаны accuracy на нашем fisheye, temporal stability, collision safety и физическое поведение ровера.",
decision: "Хранить как архив квалификации весов. Проверку на RAVNOVES00 вести только в основной многослойной LAB.",
}}
/>
)}
/>
);
}
@@ -1,5 +1,8 @@
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { Icon, IconButton, StatusBadge } from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import { LaboratoryRecordedClipPlayer } from "../../components/laboratory/LaboratoryRecordedClipPlayer";
import {
LaboratoryEvidence,
LaboratoryResultSummary,
@@ -7,17 +10,25 @@ import {
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import {
RecordedEvidenceSemanticMaskOverlay,
type RecordedEvidenceSemanticClass,
type RecordedEvidenceSemanticPaletteEntry,
} from "../../components/laboratory/RecordedEvidenceSemanticMaskOverlay";
import {
vegetationFullRouteMaskUrl,
vegetationVideoMaskUrl,
type VegetationFullRouteLayer,
type VegetationFullRouteReview,
type VegetationMixedRouteReview,
type VegetationShadowResult,
} from "../../core/laboratory/vegetationShadow";
import { recordedObservationSources } from "../../core/observation/recordedObservationSources";
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
import type { ObservationSourceDescriptor } from "../../core/runtime/contracts";
import {
fetchM49TgsFullShadowResult,
type M49TgsFullShadowResult,
} from "../../core/laboratory/m49TgsFullShadow";
import {
M48MaskComparisonVisual,
type M48MaskComparisonCase,
} from "./M48FailureAtlasVisual";
import { M4ReplayThreatVisual } from "./M4ReplayThreatVisual";
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
@@ -25,35 +36,339 @@ function decimal(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
const VEGETATION_LABELS: Readonly<Record<string, string>> = {
high_grass: "Высокая трава",
low_grass: "Низкая трава",
bush: "Куст",
tree_trunk: "Ствол дерева",
tree_crown: "Крона дерева",
hedge: "Живая изгородь",
forest: "Лесная растительность",
crops: "Посевы",
};
const MIXED_ROUTE_MODES = [
{ value: "source", label: "SOURCE" },
{ value: "city", label: "ГОРОД · EoMT" },
{ value: "vegetation", label: "ПРИРОДА · DDRNet" },
{ value: "tgs", label: "TGS" },
] as const;
function comparisonCases(result: VegetationShadowResult): readonly M48MaskComparisonCase[] {
return result.validationCases.map((item) => {
const focus = item.focus!;
return {
caseId: item.caseId,
title: `${VEGETATION_LABELS[focus.className] ?? focus.className} · truth ${decimal(focus.truthFraction * 100, 1)}% кадра`,
sourceUrl: item.assets.source,
truthUrl: item.assets.truth,
predictions: {
ddrnet: item.assets.ddrnet,
ppliteseg: item.assets.ppliteseg,
},
errors: {
ddrnet: item.assets.ddrnet_error,
ppliteseg: item.assets.ppliteseg_error,
},
};
});
const FULL_ROUTE_MODES = [
{ value: "source", label: "SOURCE" },
{ value: "city", label: "ГОРОД · EoMT" },
{ value: "vegetation", label: "ПРИРОДА · DDRNet" },
] as const;
function semanticPresentation(layer: VegetationFullRouteLayer): {
classes: readonly RecordedEvidenceSemanticClass[];
palette: readonly RecordedEvidenceSemanticPaletteEntry[];
} {
return {
classes: layer.taxonomy.map((item) => ({ id: item.classId, label: item.label })),
palette: layer.taxonomy.map((item) => ({
classId: item.classId,
color: item.classId === 0
? { kind: "transparent" as const }
: { kind: "diagnostic" as const, rgb: item.colorRgb },
})),
};
}
function FullRouteReviewEvidence({
resultId,
review,
}: {
resultId: string;
review: VegetationFullRouteReview;
}) {
const [sequence, setSequence] = useState(1);
const [playing, setPlaying] = useState(false);
const [playbackRate, setPlaybackRate] = useState(1);
const [mode, setMode] = useState<typeof FULL_ROUTE_MODES[number]["value"]>("vegetation");
const [expanded, setExpanded] = useState(false);
const [videoSource, setVideoSource] = useState<ObservationSourceDescriptor | null>(null);
const [videoError, setVideoError] = useState<string | null>(null);
const frames = useMemo(
() => review.frameSourceTimesNs.map((sourceTimeNs, index) => ({
sequence: index + 1,
sourceTimeNs,
})),
[review.frameSourceTimesNs],
);
const layer = mode === "source" ? null : review[mode];
const semantic = useMemo(() => layer ? semanticPresentation(layer) : null, [layer]);
const maskSequence = sequence - 1;
const prefetchSrcs = useMemo(() => layer
? Array.from({ length: 8 }, (_, offset) => maskSequence + offset + 1)
.filter((candidate) => candidate < review.frameCount)
.map((candidate) => vegetationFullRouteMaskUrl(resultId, mode as "city" | "vegetation", candidate))
: [], [layer, maskSequence, mode, resultId, review.frameCount]);
useEffect(() => {
const controller = new AbortController();
setVideoSource(null);
setVideoError(null);
void resolveObservationSessionReplay(review.sessionId, { signal: controller.signal })
.then((launch) => {
const source = recordedObservationSources(launch).find((candidate) => (
candidate.id === review.recordedMediaSourceId
&& candidate.modality === "video"
&& candidate.semanticChannelId === "camera.video.recorded"
&& candidate.delivery?.kind === "recorded-fmp4-manifest"
&& candidate.delivery.manifestGenerationSha256 === review.recordedMediaGenerationSha256
&& candidate.delivery.timelineStartSeconds === review.timelineStartSeconds
&& candidate.delivery.timelineEndSeconds >= review.timelineEndSeconds
));
if (!source) {
throw new Error("RIGHT-видео не совпало с sealed RAVNOVES004TREE timeline.");
}
if (!controller.signal.aborted) setVideoSource(source);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) {
setVideoError(caught instanceof Error ? caught.message : "Записанное видео недоступно.");
}
});
return () => controller.abort();
}, [
review.recordedMediaGenerationSha256,
review.recordedMediaSourceId,
review.sessionId,
review.timelineEndSeconds,
review.timelineStartSeconds,
]);
return (
<LaboratoryEvidenceViewer
label="RAVNOVES004TREE full recorded review"
className="m48-atlas-visual"
mode={mode}
modes={FULL_ROUTE_MODES}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
chromeLayout="stacked"
>
{videoSource ? (
<LaboratoryRecordedClipPlayer
source={videoSource}
segmentCount={review.frameCount}
frames={frames}
sequence={sequence}
playing={playing}
playbackRate={playbackRate}
cameraPresentation="primary"
continuousPlayback
sourceCount={1}
onSequenceChange={setSequence}
onPlayingChange={setPlaying}
onPlaybackRateChange={setPlaybackRate}
cameraOverlay={(
<>
<div className="m48-clip-player__pane-label" data-pane="camera">
{mode === "source" ? "SOURCE" : `${mode === "city" ? "EoMT CITY" : "DDRNet NATURE"} · КАДР ${sequence}/${review.frameCount}`}
</div>
{layer && semantic ? (
<div className="m48-clip-player__overlay">
<RecordedEvidenceSemanticMaskOverlay
src={vegetationFullRouteMaskUrl(resultId, mode as "city" | "vegetation", maskSequence)}
prefetchSrcs={prefetchSrcs}
imageWidth={review.width}
imageHeight={review.height}
classes={semantic.classes}
palette={semantic.palette}
opacity={0.76}
ariaLabel={`${layer.name} semantic prediction`}
/>
</div>
) : null}
</>
)}
/>
) : (
<div className="m4-replay-threat-visual__pane-status" role={videoError ? "alert" : "status"}>
{videoError ?? "Открываем автономный recorded source…"}
</div>
)}
</LaboratoryEvidenceViewer>
);
}
function FullRouteReviewResult({
rigLabel,
resultId,
review,
}: {
rigLabel: string;
resultId: string;
review: VegetationFullRouteReview;
}) {
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB V1 · RAVNOVES004TREE · полный маршрут"
description="Существующий M4.7-шаблон воспроизводит всю запись и переключает два независимых sealed semantic-слоя: городской EoMT и природный DDRNet. Worker для открытия результата не нужен."
status="FULL RECORDED REVIEW · truth отсутствует · commands OFF"
statusTone="warning"
facts={[
{ label: "Источник", value: `${review.sourceId} · ${review.frameCount}/${review.frameCount} frames` },
{ label: "Город", value: `${review.city.name} · ${decimal(review.city.inferenceFps, 2)} fps` },
{ label: "Природа", value: `${review.vegetation.name} · ${decimal(review.vegetation.inferenceFps, 2)} fps` },
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
]}
brief={{
question: "Как оба semantic-кандидата ведут себя на полном переходе от сельской среды к городской?",
approach: "Все 6830 позиции одной recorded timeline последовательно прогнаны на Worker 006 и сохранены двумя независимыми архивами масок. В M4.7 переключается только видимый слой.",
principalResult: "Полная временная шкала доступна локально в SOURCE / EoMT CITY / DDRNet NATURE без обращения к Worker.",
limitation: "Ручной truth отсутствует. Один повреждённый H.264-пакет на позиции 6092 представлен предыдущим декодированным кадром и явно зафиксирован в proof. Полный TGS и кюветы этим прогоном не проверялись.",
}}
method={{
completeness: "complete",
executionClass: "ai-inference",
pipelineId: "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
components: [
{ kind: "model", name: review.city.name, version: "sealed Worker 006 run", role: "urban semantic review", identitySha256: null },
{ kind: "model", name: review.vegetation.name, version: "GOOSE DDRNet-39", role: "vegetation semantic review", identitySha256: null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.7 TEMPLATE · RAVNOVES004TREE FULL VIDEO"
title="SOURCE / EoMT CITY / DDRNet NATURE · 6830/6830 · TRUTH отсутствует"
kind="diagnostic-model"
resizable
>
<FullRouteReviewEvidence resultId={resultId} review={review} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Полный двухслойный visual review собран; управление не авторизовано"
status="Recorded evidence ready · navigation/actuation OFF"
statusTone="warning"
metrics={[
{ label: "Route masks", value: "6830/6830 × 2", hint: "sealed local archives · Worker не требуется" },
{ label: "EoMT p95", value: `${decimal(review.city.latencyP95Ms, 2)} ms`, hint: "последовательный изолированный прогон" },
{ label: "DDRNet p95", value: `${decimal(review.vegetation.latencyP95Ms, 2)} ms`, hint: "последовательный изолированный прогон" },
{ label: "Decode repair", value: "1/6830", hint: "sequence 6092 · previous frame · sealed proof" },
]}
conclusion={{
proved: "Городской EoMT и природный DDRNet воспроизводимо обработали полную запись и доступны в одном существующем M4.7 viewer.",
notProved: "Не доказаны truth accuracy, одновременный realtime-load, полный TGS, отрицательные препятствия и безопасное управление ровером.",
decision: "Использовать результат только как визуальную диагностику. Navigation/actuation оставить OFF; следующий gate — оценка временной стабильности и независимый person/vehicle STOP.",
}}
/>
)}
/>
);
}
function MixedRouteReviewEvidence({ review }: { review: VegetationMixedRouteReview }) {
const [index, setIndex] = useState(0);
const [mode, setMode] = useState<typeof MIXED_ROUTE_MODES[number]["value"]>("vegetation");
const [expanded, setExpanded] = useState(false);
const item = review.cases[index]!;
return (
<LaboratoryEvidenceViewer
label="RAVNOVES004TREE mixed route review"
className="m48-atlas-visual"
mode={mode}
modes={MIXED_ROUTE_MODES}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
chromeLayout="stacked"
actions={(
<>
<IconButton label="Предыдущая сцена" onClick={() => setIndex((index - 1 + review.cases.length) % review.cases.length)}>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton label="Следующая сцена" onClick={() => setIndex((index + 1) % review.cases.length)}>
<Icon name="chevron-right" size={16} />
</IconButton>
</>
)}
overlay={(
<div className="m48-atlas-visual__case">
<StatusBadge tone={item.phase === "urban" ? "accent" : item.phase === "transition" ? "warning" : "neutral"}>
{item.phase.toUpperCase()} · {index + 1}/{review.cases.length}
</StatusBadge>
<strong>sequence {item.sourceSequence} · +{decimal(item.sessionSeconds, 2)} s</strong>
<small>
TGS: {item.tgs.groundCells} ground · {item.tgs.occupiedCells} occupied · {item.tgs.unobservedCells} unobserved
</small>
</div>
)}
>
<div className="recorded-evidence-image-scene">
<img src={item.assets[mode]} alt="" draggable={false} />
</div>
</LaboratoryEvidenceViewer>
);
}
function MixedRouteReviewResult({
rigLabel,
review,
}: {
rigLabel: string;
review: VegetationMixedRouteReview;
}) {
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB V1 · RAVNOVES004TREE · село → город"
description="Существующий LAB-шаблон показывает 10 синхронных camera/LiDAR сцен одной записи. EoMT и DDRNet остаются независимыми слоями; TGS показывает отдельную геометрию и не может быть очищен семантической маской."
status="BOUNDED RECORDED REVIEW · truth отсутствует · commands OFF"
statusTone="warning"
facts={[
{ label: "Источник", value: `${review.sourceId} · ${review.frameCount} camera/LiDAR islands` },
{ label: "Переход", value: "5 rural · 1 transition · 4 urban" },
{ label: "Слои", value: "SOURCE · EoMT CITY · DDRNet VEGETATION · causal TGS" },
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
]}
brief={{
question: "Сохраняются ли городская семантика, растительность и геометрия при переходе из сельской среды в город?",
approach: "Выбраны десять соседних с исходными сцен camera-кадров, каждый синхронизирован с LiDAR в пределах 100 мс. Все три вычислительных слоя прогнаны на Worker 006 и запечатаны локально.",
principalResult: "Все 10 сцен обработаны EoMT, DDRNet и causal TGS. Слои можно переключать без наложения цветов и без зависимости LAB от воркера.",
limitation: "Это bounded islands без ручной truth. DDRNet шумит по подтипам растительности; TGS не доказывает обнаружение кювета или отрицательного препятствия.",
}}
method={{
completeness: "complete",
executionClass: "ai-inference",
pipelineId: "ravnoves004tree-eomt-ddrnet-causal-tgs-review/v1",
components: [
{ kind: "model", name: review.models.city.name, version: "sealed Worker run", role: "urban semantic review", identitySha256: null },
{ kind: "model", name: review.models.vegetation.name, version: "GOOSE DDRNet-39", role: "vegetation semantic review", identitySha256: null },
{ kind: "algorithm", name: review.models.tgs.name, version: "TRAVEL compatibility runner", role: "independent local geometry", identitySha256: null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.7 TEMPLATE · RAVNOVES004TREE"
title="SOURCE / ГОРОД / ПРИРОДА / TGS · 10/10 · TRUTH отсутствует"
kind="diagnostic-model"
resizable
>
<MixedRouteReviewEvidence review={review} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Переход село → город воспроизведён; safety gate не закрыт"
status="Review ready · navigation/actuation OFF"
statusTone="warning"
metrics={[
{ label: "Aligned scenes", value: "10/10", hint: "camera + LiDAR + pose · автономный archive" },
{ label: "EoMT end-to-end p95", value: `${decimal(review.models.city.endToEndP95Ms, 2)} ms`, hint: `${decimal(review.models.city.inferenceFps, 2)} fps в изолированном прогоне` },
{ label: "DDRNet inference p95", value: `${decimal(review.models.vegetation.latencyP95Ms, 2)} ms`, hint: "candidate review · не совместный realtime stack" },
{ label: "TGS p95", value: `${decimal(review.models.tgs.latencyP95Ms, 2)} ms`, hint: `${review.models.tgs.cellSizeM} m cells · ${review.models.tgs.radiusM} m radius` },
]}
conclusion={{
proved: "Оба semantic слоя и causal TGS воспроизводимо работают на сельской, переходной и городской части новой записи.",
notProved: "Не доказаны accuracy без truth, временная стабильность по всему видео, детект кюветов и безопасное совместное realtime-управление ровером.",
decision: "Оставить navigation/actuation OFF. Следующий короткий gate — непрерывный realtime-load двух моделей плюс независимый person/vehicle STOP; кюветы проверять отдельной записью.",
}}
/>
)}
/>
);
}
function VegetationRouteEvidence({ result }: { result: VegetationShadowResult }) {
@@ -83,16 +398,14 @@ function VegetationRouteEvidence({ result }: { result: VegetationShadowResult })
}, [route.baseM4ResultId, route.linkedTgsResultId]);
const semantic = {
id: "vegetation",
controlLabel: "ПРИРОДА · DDRNet",
resultId: route.workerResultId,
spatialResultId: null,
taxonomy: route.taxonomy,
maskUrl: (sequence: number) => vegetationVideoMaskUrl(result.resultId, sequence),
label: route.viewKind === "coarse-material-policy-review"
? "Coarse material evidence · recorded video"
: "DDRNet vegetation prediction · recorded video",
maskAriaLabel: route.viewKind === "coarse-material-policy-review"
? "Coarse material policy evidence"
: "DDRNet vegetation prediction",
label: "DDRNet coarse vegetation material · recorded video",
maskAriaLabel: "DDRNet vegetation material prediction",
} as const;
if (route.linkedTgsResultId && tgs) {
@@ -100,19 +413,23 @@ function VegetationRouteEvidence({ result }: { result: VegetationShadowResult })
<M49TgsFullShadowEvidence
result={tgs}
semanticOverride={semantic}
evidenceLabel="LAB V1 · MATERIAL + YOLOX + TGS"
evidenceLabel="LAB V1 · EoMT + DDRNet + YOLOX + TGS"
/>
);
}
if (route.linkedTgsResultId && !tgsError) {
return <div className="m4-replay-threat-visual__pane-status" role="status">Открываем sealed TGS и coarse material timeline</div>;
return (
<div className="m4-replay-threat-visual__pane-status" role="status">
Открываем sealed EoMT, TGS и coarse vegetation timeline
</div>
);
}
return (
<>
<M4ReplayThreatVisual
resultId={route.baseM4ResultId}
evidenceLabel="LAB V1 · DDRNet"
showReferenceMediaLayers={route.viewKind === "coarse-material-policy-review"}
showReferenceMediaLayers
showSpatialOverlaySummary={false}
semantic={semantic}
/>
@@ -132,146 +449,129 @@ export function VegetationShadowResultView({
rigLabel: string;
result: VegetationShadowResult;
}) {
if (result.routeFullReview) {
return (
<FullRouteReviewResult
rigLabel={rigLabel}
resultId={result.resultId}
review={result.routeFullReview}
/>
);
}
if (result.routeReview) {
return <MixedRouteReviewResult rigLabel={rigLabel} review={result.routeReview} />;
}
const route = result.routeVideo;
const selected = result.candidates.find(
(candidate) => candidate.candidate === result.selectedCandidate,
)!;
const alternative = result.candidates.find(
(candidate) => candidate.candidate !== result.selectedCandidate,
)!;
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB V1 · готовые модели растительности"
description={result.routeVideo
? result.routeVideo.viewKind === "coarse-material-policy-review"
? "M4.8 сохраняет truth-backed сравнение моделей, а штатный M4.7 синхронно показывает coarse material evidence, frozen YOLOX vetoes и causal TGS на всей записи RAVNOVES00. Все слои запечатаны локально и открываются без Worker 006."
: "M4.8 сохраняет truth-backed сравнение моделей, а штатный M4.7 viewer показывает фактический DDRNet prediction на всей записи RAVNOVES00. Все 4489 масок запечатаны локально и открываются без Worker 006."
: "Штатный M4.8-инструмент сравнивает две готовые fine-64 модели на полном GOOSE validation split и на 12 truth-backed hard cases, выбранных только по наличию нужной растительности. Sealed evidence открывается локально без Worker 006."}
status={result.routeVideo
? result.routeVideo.viewKind === "coarse-material-policy-review"
? "MULTILAYER POLICY REVIEW · commands OFF · route truth отсутствует"
: "DDRNet full-video prediction ready · route truth отсутствует"
: "Truth-backed model comparison · route transfer не принят"}
title="LAB V1 · карта ровера · город + растительность"
description="Один recorded-контур RAVNOVES00 синхронно показывает городской EoMT, природный DDRNet, frozen YOLOX detections и causal TGS. Семантические маски переключаются, чтобы их цвета не скрывали друг друга; геометрическое veto остаётся независимым."
status={route
? "MULTILAYER RECORDED REVIEW · commands OFF · route truth отсутствует"
: "ROUTE EVIDENCE MISSING · commands OFF"}
statusTone="warning"
facts={[
{ label: "Источник", value: "GOOSE validation · 962 размеченных кадра · 12 vegetation hard cases" },
{ label: "Сравнение", value: "DDRNet-39 vs PPLiteSeg · official fine-64 weights" },
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
...(result.routeVideo ? [{
label: "Видео",
value: result.routeVideo.viewKind === "coarse-material-policy-review"
? "RAVNOVES00 · 4489/4489 coarse masks + YOLOX + TGS · exact sequence"
: "RAVNOVES00 · 4489/4489 DDRNet masks · exact recorded sequence",
}] : []),
{ label: "Authority", value: `${rigLabel} · MODEL QUALIFICATION ONLY · commands OFF` },
{ label: "Источник", value: "RAVNOVES00 · sensor.camera.right · 4489 recorded frames" },
{ label: "Город", value: "EoMT Cityscapes · sealed E47 semantic archive" },
{ label: "Растительность", value: "DDRNet-39 fine-64 → coarse mission-neutral materials" },
{ label: "Safety", value: "YOLOX object boxes + causal TGS · semantic masks не снимают veto" },
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
]}
brief={{
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
approach: "Обе модели последовательно прогнаны в одном изолированном CUDA-runtime на 962 кадрах. 12 визуальных кейсов выбраны детерминированно по truth-поддержке восьми растительных классов; один M4.8 viewer показывает source, truth, prediction и material-error для выбранной модели.",
principalResult: `${selected.loadedModelName} лидирует по vegetation IoU: ${decimal(selected.vegetationMeanIouPercent, 2)}% против ${decimal(alternative.vegetationMeanIouPercent, 2)}%. ${result.routeVideo?.viewKind === "coarse-material-policy-review" ? "Fine-64 prediction сведён к mission-neutral материалам; YOLOX и TGS сохраняют независимое veto." : result.routeVideo ? "Его фактическая temporal stability теперь видна на всех 4489 кадрах штатного recorded viewer." : "Ошибки по каждому типу проверяются в одном штатном инструменте."}`,
limitation: "GOOSE — внешний размеченный домен; RAVNOVES00 — наш fisheye, но без ручной truth-разметки. Материалы — prediction, а не доказательство проходимости. TGS не проецируется в пиксели без отдельной принятой калибровки.",
question: "Можно ли одновременно видеть городской и природный semantic stack, не теряя независимую геометрическую защиту?",
approach: "EoMT и DDRNet сохранены как два независимых sealed слоя на одной M4 timeline. В штатном M4.7 viewer пользователь переключает только отображаемую маску; YOLOX и TGS остаются активными слоями evidence.",
principalResult: route
? "Оба semantic archive доступны в одном viewer. Это не пиксельный fusion и не единая новая модель: городской и природный ответы остаются раздельными."
: "Route archive для этой immutable identity отсутствует.",
limitation: "RAVNOVES00 не имеет ручной truth. DDRNet заметно прыгает между HIGH GRASS, WOODY и UNKNOWN; поэтому subtype нельзя подавать напрямую в planner. Отсутствие класса никогда не означает свободный путь.",
}}
method={{
completeness: "complete",
completeness: route ? "complete" : "legacy-partial",
executionClass: "ai-inference",
pipelineId: "goose-fine64-ready-weights-to-ravnoves-policy-shadow/v1",
components: result.candidates.map((candidate) => ({
kind: "model" as const,
name: candidate.loadedModelName,
version: candidate.candidate,
role: candidate.candidate === result.selectedCandidate ? "selected policy provider" : "comparison candidate",
identitySha256: candidate.checkpointSha256,
})),
pipelineId: "ravnoves-eomt-ddrnet-yolox-causal-tgs-recorded-review/v1",
components: [
{
kind: "model",
name: "EoMT Cityscapes semantic",
version: "sealed E47 archive",
role: "urban semantic review",
identitySha256: null,
},
{
kind: "model",
name: selected.loadedModelName,
version: selected.candidate,
role: "vegetation material candidate",
identitySha256: selected.checkpointSha256,
},
{
kind: "algorithm",
name: "Frozen YOLOX + causal TGS",
version: "linked M4/M4.9 archives",
role: "independent object and geometry veto",
identitySha256: null,
},
],
}}
/>
)}
evidence={(
<>
<LaboratoryEvidence
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
title="ERROR: красный — пропуск · жёлтый — лишнее · фиолетовый — перепутан тип · зелёный — совпадение"
kind="diagnostic-model"
resizable
>
<M48MaskComparisonVisual
cases={comparisonCases(result)}
initialCandidate={result.selectedCandidate}
/>
</LaboratoryEvidence>
{result.routeVideo ? (
<LaboratoryEvidence
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
title={result.routeVideo.viewKind === "coarse-material-policy-review"
? "COARSE MATERIAL + YOLOX VETO + CAUSAL TGS · 4489/4489 · TRUTH отсутствует"
: "DDRNet PREDICTION · 4489/4489 кадров · TRUTH для этой записи отсутствует"}
kind="diagnostic-model"
resizable
>
<VegetationRouteEvidence result={result} />
</LaboratoryEvidence>
) : null}
</>
evidence={route ? (
<LaboratoryEvidence
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
title="EoMT CITY / DDRNet VEGETATION + YOLOX + CAUSAL TGS · 4489/4489 · TRUTH отсутствует"
kind="diagnostic-model"
resizable
>
<VegetationRouteEvidence result={result} />
</LaboratoryEvidence>
) : (
<LaboratoryEvidence
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
title="ROUTE ARCHIVE отсутствует"
kind="diagnostic-model"
>
<div className="m4-replay-threat-visual__pane-status" role="alert">
Для этой immutable identity нет полного route video evidence.
</div>
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title={result.routeVideo?.viewKind === "coarse-material-policy-review"
? "Слои собраны для визуального policy review; управление не авторизовано"
: "DDRNet — стартовые веса; перенос на ровер ещё не доказан"}
status={result.routeVideo?.viewKind === "coarse-material-policy-review"
? "Materials are advisory · YOLOX/TGS veto cannot be cleared"
: `${selected.loadedModelName} выбран только как vegetation candidate`}
title="Многослойный visual review собран; управление не авторизовано"
status="Semantics advisory · YOLOX/TGS veto cannot be cleared"
statusTone="warning"
metrics={[
{
label: "GOOSE mIoU",
value: `${decimal(selected.meanIouPercent, 2)}% / ${decimal(alternative.meanIouPercent, 2)}%`,
hint: `${selected.candidate} / ${alternative.candidate} · полный validation split`,
label: "Route masks",
value: route ? `${route.frameCount}/${route.frameCount}` : "0/4489",
hint: "sealed local playback · Worker для открытия не нужен",
},
{
label: "Vegetation IoU",
value: `${decimal(selected.vegetationMeanIouPercent, 2)}% / ${decimal(alternative.vegetationMeanIouPercent, 2)}%`,
hint: "агрегация классов grass/vegetation/bush/tree и родственных fine-64 labels",
label: "Semantic sources",
value: route ? "2 independent layers" : "0",
hint: "EoMT CITY / DDRNet VEGETATION · display switches, evidence does not fuse",
},
{
label: "Worker shadow p95",
value: `${decimal(selected.shadowLatencyP95Ms, 2)} / ${decimal(alternative.shadowLatencyP95Ms, 2)} ms`,
hint: "чистый inference · одна тяжёлая модель за раз",
label: "Vegetation worker p95",
value: `${decimal(selected.shadowLatencyP95Ms, 2)} ms`,
hint: "изолированный DDRNet inference; не совместный realtime stack",
},
{
label: "Cold prewarm",
value: `${decimal(selected.shadowPrewarmLatencyMs, 1)} / ${decimal(alternative.shadowPrewarmLatencyMs, 1)} ms`,
hint: "один явный inference до допуска кадров; исключён из steady-state p95",
label: "Vegetation peak VRAM",
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
hint: "DDRNet candidate на Worker 006",
},
{
label: "Worker throughput",
value: `${decimal(selected.shadowThroughputFps, 1)} / ${decimal(alternative.shadowThroughputFps, 1)} FPS`,
hint: "изолированный Worker 006 · не realtime graph целиком",
},
{
label: "Peak VRAM",
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} / ${decimal(alternative.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
hint: `${selected.candidate} / ${alternative.candidate} · RTX 4090`,
},
{
label: "Hard-case evidence",
value: "12 truth-backed cases",
hint: "8 vegetation strata · Worker для открытия не требуется",
},
...(result.routeVideo ? [{
label: "Route video",
value: "4489/4489 masks",
hint: result.routeVideo.viewKind === "coarse-material-policy-review"
? "9 coarse states · YOLOX + causal TGS · Worker-independent playback"
: "DDRNet prediction · exact sequence · Worker-independent playback",
}] : []),
]}
conclusion={{
proved: "Обе официальные fine-64 модели воспроизводимо запускаются на Worker 006; DDRNet лучше по aggregate vegetation IoU. Truth-backed hard cases прямо показывают траву, кусты и стволы, а не случайные автомобили и здания.",
notProved: "Не доказаны accuracy на нашем fisheye-домене, папоротник как отдельный материал, collision safety и physical-live поведение ровера. Видео позволяет увидеть temporal stability, но без truth не превращает её в метрику качества.",
decision: result.routeVideo?.viewKind === "coarse-material-policy-review"
? "На одном M4.7 проверить ложные LOW GRASS/HIGH GRASS кандидаты против YOLOX и TGS. До truth-кейсов и integrated load этот слой не подключать к planner/actuation."
: "Смотреть полный prediction на видео и собирать конкретные temporal/domain failure cases. DDRNet остаётся diagnostic candidate; LiDAR/TGS fail-closed геометрию не ослаблять.",
proved: "На одной recorded timeline доступны городской EoMT, природный DDRNet, YOLOX detections и causal TGS; LAB автономна от Worker.",
notProved: "Не доказаны совместный live-runtime EoMT+DDRNet, truth accuracy на fisheye, стабильные vegetation subtypes и безопасное управление ровером.",
decision: "Использовать маски только для диагностики. Следующий qualification gate — motion-aware temporal vegetation fusion и отдельный совместный realtime load test; до него planner/actuation остаются OFF.",
}}
/>
)}
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState, type ReactNode } from "react";
import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
import type { L34RightYoloxTruthIslandResult } from "../../../core/laboratory/l34RightYoloxTruthIsland";
import type { L34DResult } from "../../../core/laboratory/l34dCumulativePostprocessing";
@@ -28,19 +28,19 @@ export function useL34AnnotationCapability({
}): ReactNode {
const [open, setOpen] = useState(false);
const openWorkspace = useCallback(() => setOpen(true), []);
const available = selectedWorkId === "l34-right-yolox-truth-island-freeze"
&& l34Result
? { resultId: l34Result.resultId, workflow: "assisted-candidate" as const }
: selectedWorkId === "e46-detector-truth-island" && e46Result
? { resultId: e46Result.resultId, workflow: "independent-blind" as const }
: selectedWorkId === "e46a-ai-engineering-preannotation" && e46aResult
? { resultId: e46aResult.resultId, workflow: "engineering-preannotation" as const }
: selectedWorkId === "l34d-cumulative-postprocessing-candidate"
&& l34dResult
? { resultId: l34dResult.resultId, workflow: "prediction-hidden" as const }
: selectedWorkId === "l34e-self-review-diagnostic" && l34eResult
? { resultId: l34eResult.resultId, workflow: "adjudication" as const }
: null;
const available = useMemo(() => (
selectedWorkId === "l34-right-yolox-truth-island-freeze" && l34Result
? { resultId: l34Result.resultId, workflow: "assisted-candidate" as const }
: selectedWorkId === "e46-detector-truth-island" && e46Result
? { resultId: e46Result.resultId, workflow: "independent-blind" as const }
: selectedWorkId === "e46a-ai-engineering-preannotation" && e46aResult
? { resultId: e46aResult.resultId, workflow: "engineering-preannotation" as const }
: selectedWorkId === "l34d-cumulative-postprocessing-candidate" && l34dResult
? { resultId: l34dResult.resultId, workflow: "prediction-hidden" as const }
: selectedWorkId === "l34e-self-review-diagnostic" && l34eResult
? { resultId: l34eResult.resultId, workflow: "adjudication" as const }
: null
), [e46Result, e46aResult, l34Result, l34dResult, l34eResult, selectedWorkId]);
useEffect(() => {
if (!available) {
@@ -10,6 +10,7 @@ export type LaboratoryProfileId =
| "rig-camera-local-surface-v1"
| "rig-track-geometry-temporal-v1"
| "rig-ravnoves-perception-gate-v1"
| "rig-goose-vegetation-benchmark-v1"
| "rig-pointpillars-transfer-v1"
| "rig-right-yolox-lidar-range-v1"
| "rig-nvidia-ready-stack-v1"
@@ -63,12 +64,19 @@ interface KnownWorkDefinition {
const rig = (rigLabel: string): string => rigLabel.trim() || "Сенсорный риг";
const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`>, KnownWorkDefinition>> = {
"lab-v1-vegetation-benchmark": {
profileId: "rig-goose-vegetation-benchmark-v1",
profileName: (rigLabel) => `${rig(rigLabel)} · GOOSE vegetation archive`,
experimentId: "lab-v1-vegetation-benchmark-archive",
experimentName: "DDRNet vs PPLiteSeg · truth-backed archival comparison",
variantName: "M4.8 · GOOSE truth · архивный анализ моделей",
},
"lab-v1-vegetation-shadow": {
profileId: "rig-ravnoves-perception-gate-v1",
profileName: (rigLabel) => `${rig(rigLabel)} · GOOSE vegetation qualification`,
profileName: (rigLabel) => `${rig(rigLabel)} · RAVNOVES00 rover perception gate`,
experimentId: "lab-v1-vegetation-mission-policy",
experimentName: "DDRNet vs PPLiteSeg · truth-backed vegetation hard cases",
variantName: "LAB V1 · готовые vegetation weights · GOOSE truth",
experimentName: "RAVNOVES00 · city + vegetation + TGS review",
variantName: "LAB V1 · EoMT + DDRNet + YOLOX + TGS · commands OFF",
},
"m48-object-centric-quality": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
@@ -18,6 +18,7 @@ function mergeResults(
next: AdvancedLaboratoryResults,
): AdvancedLaboratoryResults {
return {
vegetationBenchmark: next.vegetationBenchmark ?? current.vegetationBenchmark,
vegetationShadow: next.vegetationShadow ?? current.vegetationShadow,
m47Graph: next.m47Graph ?? current.m47Graph,
m48: next.m48 ?? current.m48,
@@ -122,6 +123,7 @@ export function useAdvancedLaboratoryCatalog({
const indexedResultId = index.find((item) => item.workId === selectedWorkId)?.resultId;
if (
[
"lab-v1-vegetation-benchmark",
"lab-v1-vegetation-shadow",
"m47-reference-graph-shadow",
"m48-object-centric-quality",
@@ -108,8 +108,13 @@ test("M4.9T5 viewer prefers autonomous chunks and keeps a sealed legacy fallback
assert.doesNotMatch(source, /centersXyM\.map\(/);
assert.match(source, /fetchE47SemanticSlamResult/);
assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/);
assert.match(source, /semantic=\{semanticOverride \?\? \(semantic \? \{/);
assert.match(source, /semanticLayers=\{semanticLayers\}/);
assert.match(source, /ГОРОД · EoMT/);
assert.match(source, /ПРИРОДА · DDRNet/);
assert.match(source, /semanticOverride/);
assert.doesNotMatch(source, /if \(semanticOverride\) return/);
assert.match(visual, /label="Источник семантики"/);
assert.match(visual, /availableSemanticLayers\.length > 1/);
assert.doesNotMatch(visual, /classifiedSpatialLayer \|\| !showReferenceMediaLayers \? \[\]/);
assert.match(
visual,
@@ -5,7 +5,9 @@ import { after, before, test } from "node:test";
import { createServer } from "vite";
let server;
let fetchVegetationBenchmarkResult;
let fetchVegetationShadowResult;
let vegetationFullRouteMaskUrl;
before(async () => {
server = await createServer({
@@ -13,7 +15,11 @@ before(async () => {
logLevel: "silent",
server: { middlewareMode: true },
});
({ fetchVegetationShadowResult } = await server.ssrLoadModule(
({
fetchVegetationBenchmarkResult,
fetchVegetationShadowResult,
vegetationFullRouteMaskUrl,
} = await server.ssrLoadModule(
"/src/core/laboratory/vegetationShadow.ts",
));
});
@@ -23,6 +29,7 @@ after(async () => {
});
const resultId = `lab-v1-vegetation-shadow-${"a".repeat(64)}`;
const benchmarkResultId = `lab-v1-vegetation-benchmark-${"d".repeat(64)}`;
function candidate(candidateKey, vegetationIou) {
return {
@@ -111,21 +118,26 @@ function coarseRouteVideo() {
linked_tgs_result_id: `m49-tgs-full-shadow-${"2".repeat(64)}`,
taxonomy: {
schema_version: "missioncore.lab-v1-terrain-policy-taxonomy/v1",
classes: Array.from({ length: 9 }, (_, classId) => ({
classes: Array.from({ length: 10 }, (_, classId) => ({
class_id: classId,
label: `policy-${classId}`,
color_rgb: [classId, classId, classId],
disposition: classId === 0 ? "ambiguous" : "prediction",
material_class: classId === 0 ? null : "grass",
evidence_state: classId === 0 ? "UNOBSERVED" : "SUPPORTED_GROUND",
disposition: classId === 0 ? "ambiguous" : classId === 9 ? "undefined" : "prediction",
material_class: classId === 0 || classId === 9 ? null : "grass",
evidence_state: classId === 0 || classId === 9 ? "UNOBSERVED" : "SUPPORTED_GROUND",
})),
},
aggregate_prediction_pixels: Array(9).fill(0),
aggregate_prediction_pixels: Array(10).fill(0),
mask_archive: {
path: "video/coarse-material-policy-masks.zip",
sha256: "8".repeat(64),
byte_length: 2048,
},
valid_fov: {
mask_path: "video/valid-fov-mask.png",
mask_sha256: "7".repeat(64),
outside_valid_fov_class_id: 9,
},
policy: {
presets: {
urban: { grass: "NO_GO" },
@@ -141,6 +153,69 @@ function coarseRouteVideo() {
};
}
function fullRouteReview() {
const layer = (kind) => ({
name: kind === "city" ? "EoMT Cityscapes" : "ddrnet_39",
result_id: kind === "city"
? `result-${"2".repeat(64)}`
: `lab-v1-ravnoves-video-ddrnet-${"3".repeat(64)}`,
frame_count: 6830,
taxonomy: {
schema_version: kind === "city"
? "missioncore.recorded-eomt-taxonomy/v1"
: "missioncore.lab-v1-vegetation-taxonomy/v1",
classes: Array.from({ length: kind === "city" ? 16 : 64 }, (_, classId) => ({
class_id: classId,
label: classId === 0 ? "undefined" : `${kind}-${classId}`,
color_rgb: [classId, classId, classId],
disposition: classId === 0 ? "undefined" : "prediction",
})),
},
mask_archive: {
path: kind === "city"
? "video/eomt-semantic-masks.zip"
: "video/ddrnet-semantic-masks.zip",
sha256: "4".repeat(64),
byte_length: 4096,
},
inference_fps: 9.5,
latency_p95_ms: 101.2,
peak_reserved_vram_bytes: 3_000_000_000,
});
return {
source_id: "RAVNOVES004TREE",
session_id: "20260828T130511Z_viewer_live",
source_job_id: "recorded-camera-eb2783c5480d56bda07c8af0",
source_job_input_sha256: "eb2783c5480d56bda07c8af008dff5344d19dc550ef70fe2075d6f098f7cc715",
source_stream_sha256: "e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac",
recorded_media_source_id: "recorded.camera.6a3945242828a038",
recorded_media_generation_sha256: "b073ea1e7babf1c77a664e1a5b95e3702d0e05b0e34c1e85a7c67a6f8b392ded",
frame_count: 6830,
width: 800,
height: 600,
timeline_start_seconds: 39.215263458,
timeline_end_seconds: 757.260263458,
timeline: {
path: "video/frame-source-times-ns.bin",
sha256: "5".repeat(64),
byte_length: 6830 * 8,
encoding: "uint64-le-nanoseconds",
frame_count: 6830,
},
ground_truth: false,
decode_repair: {
repaired_frame_count: 1,
sequence: 6092,
method: "duplicate-previous-decoded-frame",
proofs: {
eomt: { path: "proofs/decode_repair.json", sha256: "7".repeat(64) },
ddrnet: { path: "proofs/ddrnet_decode_repair.json", sha256: "8".repeat(64) },
},
},
layers: { city: layer("city"), vegetation: layer("vegetation") },
};
}
function labPayload(route = routeVideo()) {
return {
schema_version: "missioncore.lab-v1-vegetation-shadow/v1",
@@ -220,23 +295,100 @@ test("vegetation LAB parses coarse material policy and sealed TGS binding", asyn
});
assert.equal(result.routeVideo.viewKind, "coarse-material-policy-review");
assert.match(result.routeVideo.linkedTgsResultId, /^m49-tgs-full-shadow-/);
assert.equal(result.routeVideo.taxonomy.length, 9);
assert.equal(result.routeVideo.taxonomy.length, 10);
assert.equal(result.routeVideo.taxonomy[0].evidenceState, "UNOBSERVED");
assert.equal(result.routeVideo.policyPresets.urban.grass, "NO_GO");
assert.equal(result.routeVideo.fusionMode, "synchronised-multilayer-review");
});
test("vegetation LAB reuses the admitted M4.8 and M4.7 instruments", async () => {
const resultSource = await readFile(
new URL("../src/workspaces/laboratory/VegetationShadowResult.tsx", import.meta.url),
"utf8",
test("vegetation LAB parses the full 004 pass inside the existing result contract", async () => {
const payload = {
...labPayload(null),
catalogs: { goose: [], ravnoves: [] },
route_full_review: fullRouteReview(),
};
const timeline = new ArrayBuffer(6830 * 8);
const timelineView = new DataView(timeline);
for (let index = 0; index < 6830; index += 1) {
timelineView.setBigUint64(
index * 8,
BigInt(39_215_263_458 + index * 100_000_000),
true,
);
}
const result = await fetchVegetationShadowResult(resultId, {
fetcher: async (url) => String(url).endsWith("/route-timeline")
? new Response(timeline, {
status: 200,
headers: {
"Content-Type": "application/octet-stream",
ETag: `"${"5".repeat(64)}"`,
},
})
: new Response(JSON.stringify(payload), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
});
assert.equal(result.routeVideo, null);
assert.equal(result.routeFullReview.frameCount, 6830);
assert.equal(result.routeFullReview.city.taxonomy.length, 16);
assert.equal(result.routeFullReview.vegetation.taxonomy.length, 64);
assert.equal(result.routeFullReview.decodeRepair.sequence, 6092);
assert.equal(result.routeFullReview.frameSourceTimesNs.length, 6830);
assert.equal(
vegetationFullRouteMaskUrl(resultId, "vegetation", 6829),
`/api/v1/laboratory/vegetation-shadow/${resultId}/route-masks/vegetation/6829`,
);
assert.match(resultSource, /M48MaskComparisonVisual/);
});
test("vegetation GOOSE benchmark opens through its separate archival endpoint", async () => {
let requestedUrl = "";
const result = await fetchVegetationBenchmarkResult(benchmarkResultId, {
fetcher: async (url) => {
requestedUrl = String(url);
return new Response(JSON.stringify({
...labPayload(null),
result_id: benchmarkResultId,
}), {
status: 200,
headers: { "Content-Type": "application/json" },
});
},
});
assert.equal(
requestedUrl,
`/api/v1/laboratory/vegetation-benchmark/${benchmarkResultId}`,
);
assert.equal(result.routeVideo, null);
assert.equal(result.validationCases.length, 12);
});
test("vegetation realtime LAB and archival benchmark use separate admitted instruments", async () => {
const [resultSource, benchmarkSource] = await Promise.all([
readFile(
new URL("../src/workspaces/laboratory/VegetationShadowResult.tsx", import.meta.url),
"utf8",
),
readFile(
new URL("../src/workspaces/laboratory/VegetationBenchmarkResult.tsx", import.meta.url),
"utf8",
),
]);
assert.doesNotMatch(resultSource, /M48MaskComparisonVisual/);
assert.match(resultSource, /M4ReplayThreatVisual/);
assert.match(resultSource, /M49TgsFullShadowEvidence/);
assert.match(resultSource, /semanticOverride/);
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 2);
assert.match(resultSource, /EoMT CITY \/ DDRNet VEGETATION/);
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 4);
assert.match(resultSource, /RAVNOVES004TREE mixed route review/);
assert.match(resultSource, /RAVNOVES004TREE full recorded review/);
assert.match(resultSource, /LaboratoryRecordedClipPlayer/);
assert.match(resultSource, /className="m48-clip-player__overlay"/);
assert.match(resultSource, /linkedTgsResultId/);
assert.match(benchmarkSource, /M48MaskComparisonVisual/);
assert.doesNotMatch(benchmarkSource, /M49TgsFullShadowEvidence/);
assert.equal(benchmarkSource.match(/<LaboratoryEvidence\b/g)?.length, 1);
assert.doesNotMatch(resultSource, /VegetationRouteVisual|urban\/rural\/off-road presets/);
await assert.rejects(
access(new URL("../src/workspaces/laboratory/VegetationShadowVisual.tsx", import.meta.url)),
@@ -0,0 +1,10 @@
{
"schema_version": "missioncore.laboratory-evidence-definition/v1",
"work_id": "lab-v1-vegetation-benchmark",
"evidence": {
"runtime_relative_root": "lab-v1-vegetation-benchmark/results",
"result_id_prefix": "lab-v1-vegetation-benchmark",
"document_name": "result.json",
"schema_version": "missioncore.lab-v1-vegetation-shadow/v1"
}
}
+1
View File
@@ -212,6 +212,7 @@
}
],
"legacy_work_ids": [
"lab-v1-vegetation-benchmark",
"m48r3-static-occupancy-shadow",
"m47-reference-graph-shadow",
"e31-source-binding",
+9 -2
View File
@@ -282,10 +282,17 @@
"lifecycle": "current",
"visual_evidence": "available"
},
{
"catalog_id": "lab-v1-vegetation-benchmark",
"evidence_id": "lab-v1-vegetation-benchmark-a8944d6c2d1102d81da78bcb4963760c9288db0421d9f2686afcbdd14b610d3d",
"signal": "progress",
"lifecycle": "current",
"visual_evidence": "available"
},
{
"catalog_id": "lab-v1-vegetation-shadow",
"evidence_id": "lab-v1-vegetation-shadow-ad4d9fbbb21ff8a270b77f559b4e78dcdaf0455afd61afb5033009623984e554",
"signal": "failed",
"evidence_id": "lab-v1-vegetation-shadow-d179462134967ace1c5ebd6fbdbdd8659905d390484b9c01ea7930f083bb74d1",
"signal": "progress",
"lifecycle": "current",
"visual_evidence": "available"
}
@@ -0,0 +1,19 @@
{
"schema_version": "missioncore.lab-v1-ravnoves-source/v1",
"profile_id": "ravnoves004tree-full-video-source/v1",
"source": {
"source_id": "RAVNOVES004TREE/right-e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac",
"source_sha256": "e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac",
"source_job_id": "recorded-camera-eb2783c5480d56bda07c8af0",
"source_job_input_sha256": "eb2783c5480d56bda07c8af008dff5344d19dc550ef70fe2075d6f098f7cc715",
"session_id": "20260828T130511Z_viewer_live",
"base_m4_result_id": null,
"expected_width": 800,
"expected_height": 600,
"expected_frame_count": 6830,
"timeline_start_seconds": 39.215263458,
"timeline_end_seconds": 757.260263458,
"frame_indices": [],
"crop_contract": "center-600-square-to-512; outside-crop-is-undefined"
}
}
@@ -0,0 +1,68 @@
{
"schema_version": "missioncore.mixed-route-tgs-review-profile/v1",
"profile_id": "ravnoves004tree-mixed-route-tgs-review/v1",
"source": {
"source_id": "RAVNOVES004TREE",
"session_id": "20260828T130511Z_viewer_live",
"review_pack_id": "mixed-route-review-pack-a8d245eb08a9581a994c4ae5ad242fec20f02c7c512c5ca5d3a6dd9464012753",
"source_pack_id": "mixed-route-lidar-pack-e3fe195588cc4a2ec17e15af6f46582ed71c9bed643943779c4ed5e565a3c839",
"source_pack_sha256": "10c759463da7711fbbe67e70df931597d85ab21325f7f8026e2c945b677e1bc6",
"input_coordinate_frame": "map-gravity-local-translation-only"
},
"tgs": {
"max_range_m": 80.0,
"min_range_m": 1.0,
"resolution_m": 8.0,
"num_iterations": 3,
"num_lowest_representative_points": 5,
"minimum_points": 10,
"seed_threshold_m": 0.5,
"distance_threshold_m": 0.125,
"outlier_threshold_m": 0.3,
"normal_threshold": 0.94,
"weight_threshold": 200.0,
"lcc_normal_similarity": 0.03,
"lcc_planar_distance_m": 0.1,
"obstacle_height_m": 1.0,
"refine_mode": true
},
"profiles": {
"current_increment": {
"role": "diagnostic-current-evidence"
},
"causal_rolling_1s": {
"role": "primary-local-evidence",
"history_seconds": 1.0,
"local_radius_m": 12.0
}
},
"costmap": {
"coordinate_frame": "map-gravity-local",
"cell_size_m": 0.45,
"radius_m": 12.0,
"state_priority": [
"NONGROUND_OCCUPIED",
"UNKNOWN_REJECTED",
"GROUND_SUPPORT",
"UNOBSERVED"
]
},
"state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3
},
"invariants": {
"all_eligible_input_points_accounted": true,
"aos_allowed": false,
"lidar_orientation_applied_to_tgs_input": false,
"map_gravity_axis_preserved": true,
"missing_support_means_free": false,
"unobserved_cells_are_emitted": true,
"camera_projection_is_authoritative": false,
"future_frames_used": false,
"gpu_allowed": false,
"navigation_or_actuation_allowed": false
}
}
+19 -20
View File
@@ -58,12 +58,10 @@ Mission Core backend
DC Gaussian Pipeline
├─ TUS bundle or archive admission
├─ secure ZIP/RAR/7z normalization
─ native Vulkan SplatTransform visual build on Worker 006
Optional physical-mesh pipeline (separate job; experimental)
├─ source-mesh discovery or explicit mesh generation
├─ physics-oriented cleanup and geometry budget
└─ compressed, digest-bound publication
─ native Vulkan SplatTransform visual build on Worker 006
└─ optional Mesh_Files/*.ply source-collision path
├─ conservative small-hole repair and topology audit
└─ mandatory Draco, digest-bound publication
```
The browser never receives the Worker token. Mission Core does not embed archive-format behavior
@@ -81,18 +79,18 @@ SplatTransform GPU command through a confined filesystem spool to the pinned nat
on the RTX 4090. It does not install into or share the Python, CUDA, Triton or computer-vision
environments on the host.
Project processing is split at a durable product boundary. The mandatory first job builds only the
preview and streamed Gaussian assets required for visual inspection. It never generates collision
geometry, so a location can reach `ready` without paying the time, GPU-memory and storage cost of a
physical mesh.
Project processing never generates collision geometry from Gaussian data by default. When the
normalized source contains exactly one PLY below `Mesh_Files`, the same queued build automatically
selects the provider's `source` collision profile. The original indexed mesh is retained; only
strictly admitted small internal boundary loops on approximately planar Z-up surfaces receive new
triangles. Vertices and existing faces are never moved, welded, smoothed, simplified or remeshed.
The derived GLB then passes the mandatory Draco publication gate and carries a separate repair
report. A source with no admitted PLY remains visual-only and still reaches `ready` normally.
Physical geometry is an optional second job started only after the visual world is ready. Its first
candidate source is a mesh already present in the uploaded export (for example a PLY in
`Mesh_Files`); generation from the Gaussian cloud is a fallback experiment, not the default path.
The second-stage contract, cleanup method and acceptance gates are intentionally separate from the
visual build. When that stage publishes a GLB, simplification still controls decoded physics cost
and Draco controls transfer/storage bytes; compression is not treated as a replacement for a
physics mesh budget.
The conservative repair rejects outer borders, branched boundaries, large or non-planar loops,
vertical openings, mixed orientation and failed/self-intersecting triangulations. Ambiguous holes
remain open and are counted in the report instead of being silently capped. Explicit mesh
generation from Gaussian data remains a later experiment, not a fallback in this ingestion path.
Visual and collision layers retain independent X/Y/Z correction settings, while PlayCanvas world,
camera, navigation and future physics stay in the canonical Y-up coordinate system. Quality, both
@@ -115,9 +113,10 @@ Primary implementation references:
- encrypted, linked, traversing, duplicate and over-limit archive entries fail closed;
- project status is durable and reflects provider state without fabricated percentages;
- ready artifacts are imported digest-bound and served from Mission Core same-origin URLs;
- the mandatory build requests preview and streamed Gaussian outputs with collision disabled;
- a visual project reaches ready state without a collision artifact;
- physical mesh preparation is a separate explicit job and never blocks visual inspection;
- the build always requests preview and streamed Gaussian outputs;
- exactly one `Mesh_Files/*.ply` automatically selects repaired source-mesh collision plus Draco;
- archives without that mesh remain visual-only and reach ready without a collision artifact;
- generated Gaussian/voxel collision is never used as an implicit fallback;
- edit changes project metadata; delete removes both the Mission Core project and terminal provider
job;
- a ready project mounts direct PlayCanvas Engine and loads Streamed SOG with preview fallback;
@@ -0,0 +1,418 @@
#!/usr/bin/env python3
"""Publish LiDAR/pose evidence aligned to an immutable mixed-route review pack."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import tempfile
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
from fuse_e6_tracking_lidar import CameraAnchor, _lidar_samples
from k1link.compute.jobs import validate_camera_compute_job
from k1link.device_plugins.xgrids_k1.analyze.calibrated_overlay import (
_load_calibration_snapshot,
)
from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
Kb4ProjectionProfile,
)
from k1link.device_plugins.xgrids_k1.mqtt.capture import read_capture_clock_origin
from k1link.device_plugins.xgrids_k1.protocol.streams import decode_lio_pcl
from k1link.device_plugins.xgrids_k1.viewer.replay import iter_replay_messages
SCHEMA = "missioncore.mixed-route-lidar-pack/v1"
REVIEW_SCHEMA = "missioncore.mixed-route-review-pack/v1"
MAXIMUM_LIDAR_CAMERA_DELTA_MS = 100.0
MAXIMUM_POSE_POINT_DELTA_MS = 100.0
CAUSAL_HISTORY_SECONDS = 1.0
class MixedRouteLidarPackError(RuntimeError):
"""The recorded route cannot satisfy the selected LiDAR evidence contract."""
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--session", type=Path, required=True)
parser.add_argument("--review-pack", type=Path, required=True)
parser.add_argument("--calibration", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
return parser.parse_args()
def _read_review_pack(root: Path) -> tuple[dict[str, Any], list[dict[str, Any]]]:
resolved = root.resolve(strict=True)
manifest_path = resolved / "manifest.json"
try:
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise MixedRouteLidarPackError("mixed-route review manifest is invalid") from exc
identity = manifest.get("identity") if isinstance(manifest, dict) else None
timeline = manifest.get("timeline") if isinstance(manifest, dict) else None
frames = manifest.get("frames") if isinstance(manifest, dict) else None
if (
manifest.get("schema_version") != REVIEW_SCHEMA
or not isinstance(identity, dict)
or identity.get("schema_version") != REVIEW_SCHEMA
or identity.get("ground_truth") is not False
or not isinstance(timeline, dict)
or not isinstance(frames, list)
or manifest.get("frame_count") != len(frames)
or not frames
):
raise MixedRouteLidarPackError("mixed-route review contract changed")
timeline_path = resolved / str(timeline.get("path"))
if (
not timeline_path.is_file()
or timeline.get("sha256") != _sha256(timeline_path)
or timeline.get("byte_length") != timeline_path.stat().st_size
):
raise MixedRouteLidarPackError("mixed-route review timeline changed")
rows: list[dict[str, Any]] = []
previous_seconds = -1.0
with timeline_path.open(encoding="utf-8") as stream:
for expected, line in enumerate(stream):
try:
row = json.loads(line)
except json.JSONDecodeError as exc:
raise MixedRouteLidarPackError("mixed-route timeline JSON is invalid") from exc
seconds = row.get("session_seconds") if isinstance(row, dict) else None
if (
not isinstance(row, dict)
or row.get("frame_index") != expected
or row.get("sequence") != expected + 1
or row.get("source_sequence") != row.get("source_frame_index") + 1
or not isinstance(seconds, (int, float))
or isinstance(seconds, bool)
or float(seconds) <= previous_seconds
):
raise MixedRouteLidarPackError("mixed-route timeline row changed")
rows.append(row)
previous_seconds = float(seconds)
if len(rows) != len(frames):
raise MixedRouteLidarPackError("mixed-route timeline is incomplete")
for frame in frames:
path = resolved / str(frame.get("path"))
if (
not path.is_file()
or frame.get("byte_length") != path.stat().st_size
or frame.get("sha256") != _sha256(path)
):
raise MixedRouteLidarPackError("mixed-route source frame changed")
return manifest, rows
def _causal_history_clouds(
raw_path: Path,
*,
origin_monotonic_ns: int,
sample_seconds: list[float],
) -> list[np.ndarray]:
grouped: list[list[np.ndarray]] = [[] for _ in sample_seconds]
last = sample_seconds[-1]
for message in iter_replay_messages(raw_path):
monotonic_ns = message.received_monotonic_ns
if not isinstance(monotonic_ns, int) or monotonic_ns < origin_monotonic_ns:
raise MixedRouteLidarPackError("MQTT replay message has no compatible clock")
seconds = (monotonic_ns - origin_monotonic_ns) / 1e9
if seconds > last:
break
if not message.topic.endswith("/lio_pcl"):
continue
matching = [
index
for index, sample_time in enumerate(sample_seconds)
if sample_time - CAUSAL_HISTORY_SECONDS <= seconds <= sample_time
]
if not matching:
continue
frame = decode_lio_pcl(message.payload)
cloud = np.asarray(
[point.scaled_xyz(frame.header.scaler) for point in frame.points],
dtype=np.float32,
).reshape((-1, 3))
if cloud.shape[0] == 0 or not np.isfinite(cloud).all():
raise MixedRouteLidarPackError("causal LiDAR history is empty or non-finite")
for index in matching:
grouped[index].append(cloud)
result: list[np.ndarray] = []
for clouds in grouped:
if not clouds:
raise MixedRouteLidarPackError("selected frame has no causal LiDAR history")
result.append(np.concatenate(clouds))
return result
def prepare(
*,
job_root: Path,
session_root: Path,
review_pack_root: Path,
calibration_root: Path,
output_root: Path,
) -> Path:
job = validate_camera_compute_job(job_root)
session = session_root.resolve(strict=True)
if not session.is_dir() or session.name != job.session_id:
raise MixedRouteLidarPackError("camera job and observation session differ")
review, timeline = _read_review_pack(review_pack_root)
review_identity = review["identity"]
if (
review_identity.get("job_id") != job.job_id
or review_identity.get("input_sha256") != job.input_sha256
or review_identity.get("session_id") != job.session_id
or review_identity.get("source_id") != job.source_id
or review_identity.get("codec_epoch") != job.codec_epoch
):
raise MixedRouteLidarPackError("review pack and camera job differ")
calibration, calibration_sha256 = _load_calibration_snapshot(
calibration_root.resolve(strict=True)
)
projection = Kb4ProjectionProfile.from_factory_calibration(calibration, job.source_id)
capture_root = session / "captures" / "mqtt_live"
origin_path = capture_root / "mqtt.timeline.origin.json"
origin = read_capture_clock_origin(origin_path)
anchors = [
CameraAnchor(
frame_index=int(row["frame_index"]),
source_frame_index=int(row["source_frame_index"]),
host_session_seconds=(
int(row["host_monotonic_ns"]) - origin.started_monotonic_ns
)
/ 1e9,
video_session_seconds=float(row["session_seconds"]),
)
for row in timeline
]
if any(
anchor.host_session_seconds != anchor.video_session_seconds
for anchor in anchors
):
raise MixedRouteLidarPackError("review timeline does not use host arrival time")
samples = list(
_lidar_samples(
capture_root / "mqtt.raw.k1mqtt",
anchors,
origin_monotonic_ns=origin.started_monotonic_ns,
maximum_lidar_camera_delta_s=MAXIMUM_LIDAR_CAMERA_DELTA_MS / 1000.0,
maximum_pose_point_delta_s=MAXIMUM_POSE_POINT_DELTA_MS / 1000.0,
)
)
if len(samples) != len(anchors):
raise MixedRouteLidarPackError("LiDAR sampler did not account for every anchor")
count = len(anchors)
available = np.zeros((count,), dtype=np.bool_)
offsets = [0]
clouds: list[np.ndarray] = []
positions = np.full((count, 3), np.nan, dtype=np.float64)
quaternions = np.full((count, 4), np.nan, dtype=np.float64)
lidar_delta = np.full((count,), np.nan, dtype=np.float64)
pose_delta = np.full((count,), np.nan, dtype=np.float64)
sample_seconds: list[float] = []
for index, (anchor, sample) in enumerate(zip(anchors, samples, strict=True)):
if sample is None:
offsets.append(offsets[-1])
sample_seconds.append(float("nan"))
continue
cloud = np.asarray(
[
point.scaled_xyz(sample.point_frame.header.scaler)
for point in sample.point_frame.points
],
dtype=np.float32,
).reshape((-1, 3))
if cloud.shape[0] == 0 or not np.isfinite(cloud).all():
raise MixedRouteLidarPackError("selected LiDAR sample is empty or non-finite")
available[index] = True
clouds.append(cloud)
offsets.append(offsets[-1] + cloud.shape[0])
positions[index] = sample.pose_frame.position_xyz
quaternions[index] = sample.pose_frame.orientation_xyzw
lidar_delta[index] = (
sample.point_session_seconds - anchor.host_session_seconds
) * 1000.0
pose_delta[index] = (
sample.pose_session_seconds - sample.point_session_seconds
) * 1000.0
sample_seconds.append(sample.point_session_seconds)
if not available.all() or not np.isfinite(np.asarray(sample_seconds)).all():
raise MixedRouteLidarPackError(
"every mixed-route review island must have a temporally admissible LiDAR sample"
)
history_clouds = _causal_history_clouds(
capture_root / "mqtt.raw.k1mqtt",
origin_monotonic_ns=origin.started_monotonic_ns,
sample_seconds=sample_seconds,
)
history_offsets = [0]
for cloud in history_clouds:
history_offsets.append(history_offsets[-1] + cloud.shape[0])
identity = {
"schema_version": SCHEMA,
"job_id": job.job_id,
"input_sha256": job.input_sha256,
"session_id": job.session_id,
"source_id": job.source_id,
"camera_slot": "camera_1",
"calibration_sha256": calibration_sha256,
"review_pack_id": review["pack_id"],
"review_pack_identity_sha256": review["identity_sha256"],
"selected_source_frame_indices": [
int(row["source_frame_index"]) for row in timeline
],
"frame_count": count,
"available_lidar_frames": int(available.sum()),
"point_count": int(offsets[-1]),
"causal_history_seconds": CAUSAL_HISTORY_SECONDS,
"causal_history_point_count": int(history_offsets[-1]),
"temporal_policy": {
"binding": "nearest-host-arrival-best-effort",
"maximum_lidar_camera_delta_ms": MAXIMUM_LIDAR_CAMERA_DELTA_MS,
"maximum_pose_point_delta_ms": MAXIMUM_POSE_POINT_DELTA_MS,
"clock_source": "recorded-host-monotonic-arrival",
},
"projection": {
"model": "kb4",
"width": projection.width,
"height": projection.height,
"source_coordinates": "k1-map",
"target_camera": job.source_id,
},
"ground_truth": False,
"authority": {
"navigation_or_safety_accepted": False,
"actuation_allowed": False,
},
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
pack_id = f"mixed-route-lidar-pack-{identity_sha256}"
parent = output_root.resolve()
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
final = parent / pack_id
if final.exists():
return final
staging = Path(tempfile.mkdtemp(prefix=f".{pack_id}.", dir=parent))
published = False
try:
arrays_path = staging / "lidar-pack.npz"
np.savez_compressed(
arrays_path,
frame_indices=np.arange(count, dtype=np.int64),
source_frame_indices=np.asarray(
[row["source_frame_index"] for row in timeline], dtype=np.int64
),
session_seconds=np.asarray(
[anchor.video_session_seconds for anchor in anchors], dtype=np.float64
),
host_session_seconds=np.asarray(
[anchor.host_session_seconds for anchor in anchors], dtype=np.float64
),
lidar_session_seconds=np.asarray(sample_seconds, dtype=np.float64),
sample_available=available,
cloud_offsets=np.asarray(offsets, dtype=np.int64),
cloud_points_map=(
np.concatenate(clouds) if clouds else np.empty((0, 3), dtype=np.float32)
),
pose_positions_map=positions,
pose_quaternions_map_from_lidar=quaternions,
lidar_camera_delta_ms=lidar_delta,
pose_point_delta_ms=pose_delta,
causal_history_seconds=np.asarray(
[CAUSAL_HISTORY_SECONDS], dtype=np.float64
),
causal_history_offsets=np.asarray(history_offsets, dtype=np.int64),
causal_history_points_map=np.concatenate(history_clouds),
intrinsic_fx_fy_cx_cy=np.asarray(
projection.intrinsic_fx_fy_cx_cy, dtype=np.float64
),
distortion_kb4=np.asarray(projection.distortion_kb4, dtype=np.float64),
t_camera_from_lidar=np.asarray(projection.t_camera_from_lidar, dtype=np.float64),
)
manifest = {
"schema_version": SCHEMA,
"pack_id": pack_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"classification": "private-recorded-sensor-review-input",
"ground_truth": False,
"artifact": {
"path": arrays_path.name,
"media_type": "application/x-npz",
"byte_length": arrays_path.stat().st_size,
"sha256": _sha256(arrays_path),
},
}
(staging / "manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
os.replace(staging, final)
published = True
finally:
if not published:
shutil.rmtree(staging, ignore_errors=True)
return final
def main() -> int:
args = _arguments()
output = prepare(
job_root=args.job,
session_root=args.session,
review_pack_root=args.review_pack,
calibration_root=args.calibration,
output_root=args.output_root,
)
manifest = json.loads((output / "manifest.json").read_text(encoding="utf-8"))
print(
json.dumps(
{
"pack_id": manifest["pack_id"],
"output": str(output),
"frames": manifest["identity"]["frame_count"],
"lidar_frames": manifest["identity"]["available_lidar_frames"],
"points": manifest["identity"]["point_count"],
"artifact_sha256": manifest["artifact"]["sha256"],
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -187,12 +187,15 @@ Write-Output "PHASE=e4-preflight-complete"
$runToken = [Guid]::NewGuid().ToString("N")
$workRoot = Join-Path $tmpRoot ("{0}-e4-{1}" -f $job.job_id, $runToken)
$framesRoot = Join-Path $workRoot "frames"
$decodedFramesRoot = Join-Path $workRoot "decoded-by-pts"
$streamPath = Join-Path $workRoot "camera.mp4"
$ptsPath = Join-Path $workRoot "pts.json"
$packetsPath = Join-Path $workRoot "packets.csv"
$decodeRepairPath = Join-Path $workRoot "decode-repair.json"
$timelinePath = Join-Path $workRoot "timeline.jsonl"
$publishRoot = Join-Path $derivedRoot (".{0}-e4-{1}.publish" -f $job.job_id, $runToken)
$stagingRoot = Join-Path $publishRoot "output"
$null = New-Item -ItemType Directory -Path $framesRoot
$null = New-Item -ItemType Directory -Path $decodedFramesRoot
$null = New-Item -ItemType Directory -Path $publishRoot
$totalWatch = [Diagnostics.Stopwatch]::StartNew()
$completed = $false
@@ -230,29 +233,69 @@ try {
$extractWatch = [Diagnostics.Stopwatch]::StartNew()
Write-Output "PHASE=e4-frame-extraction-start"
& ffmpeg -hide_banner -loglevel fatal -i $streamPath -map 0:v:0 -fps_mode passthrough -frames:v $activeFrameCount (Join-Path $framesRoot "frame-%06d.png")
& ffprobe -v error -select_streams v:0 -show_packets -show_entries packet=pts,flags -of csv=p=0 -o $packetsPath $streamPath
Assert-LastExitCode "LAB E4 packet timestamp probe"
$packetRows = @(Get-Content -LiteralPath $packetsPath | Select-Object -First $activeFrameCount)
if ($packetRows.Count -ne $activeFrameCount) {
throw "LAB E4 packet count differs from the requested camera epoch"
}
& ffmpeg -hide_banner -loglevel error `
-hwaccel cuda -hwaccel_output_format cuda -c:v h264_cuvid `
-err_detect ignore_err -flags +output_corrupt -copyts `
-i $streamPath -map 0:v:0 -vf "hwdownload,format=nv12" `
-fps_mode passthrough -enc_time_base demux -frames:v $activeFrameCount `
-frame_pts 1 (Join-Path $decodedFramesRoot "frame-%d.png")
Assert-LastExitCode "LAB E4 camera extraction"
& ffprobe -v error -select_streams v:0 -show_entries frame=best_effort_timestamp_time -of json $streamPath | Set-Content -LiteralPath $ptsPath -Encoding utf8
Assert-LastExitCode "LAB E4 camera timestamp probe"
$decodedCount = @(Get-ChildItem -LiteralPath $decodedFramesRoot -File -Filter "frame-*.png").Count
$repairs = @()
$packetPts = @()
for ($index = 0; $index -lt $activeFrameCount; $index++) {
$columns = ([string]$packetRows[$index]).Split(",")
if ($columns.Count -lt 2) {
throw "LAB E4 packet timestamp row is malformed"
}
$pts = [int64]::Parse($columns[0].Trim(), [Globalization.CultureInfo]::InvariantCulture)
$packetPts += $pts
$decodedPath = Join-Path $decodedFramesRoot ("frame-{0}.png" -f $pts)
$canonicalPath = Join-Path $framesRoot ("frame-{0:D6}.png" -f ($index + 1))
if (Test-Path -LiteralPath $decodedPath -PathType Leaf) {
Move-Item -LiteralPath $decodedPath -Destination $canonicalPath
continue
}
if ($index -eq 0 -or $repairs.Count -ge 1) {
throw "LAB E4 source contains more than one recoverable decoder gap"
}
$previousPath = Join-Path $framesRoot ("frame-{0:D6}.png" -f $index)
Copy-Item -LiteralPath $previousPath -Destination $canonicalPath
$repairs += [ordered]@{
sequence = $index + 1
packet_pts = $pts
method = "duplicate-previous-decoded-frame"
}
}
$decodedFrames = @(Get-ChildItem -LiteralPath $framesRoot -File -Filter "frame-*.png")
$ptsDocument = Get-Content -LiteralPath $ptsPath -Raw | ConvertFrom-Json
$pts = @($ptsDocument.frames)
if ($decodedFrames.Count -ne $activeFrameCount -or $pts.Count -lt $activeFrameCount) {
if ($decodedFrames.Count -ne $activeFrameCount) {
throw "Decoded LAB E4 frame count differs from the requested camera epoch"
}
$firstEpochSeconds = [double]::Parse(
([string]$pts[0].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
)
$decodeRepair = [ordered]@{
schema_version = "missioncore.recorded-video-decode-repair/v1"
decoder = "ffmpeg-h264_cuvid-output-corrupt"
packets_requested = $activeFrameCount
frames_decoded = $decodedCount
repaired_frame_count = $repairs.Count
repairs = $repairs
}
$decodeRepair | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $decodeRepairPath -Encoding utf8
$firstPacketPts = [int64]$packetPts[0]
$previousEpochSeconds = -1.0
$timelineWriter = [IO.StreamWriter]::new($timelinePath, $false, [Text.UTF8Encoding]::new($false))
try {
for ($index = 0; $index -lt $activeFrameCount; $index++) {
$epochSeconds = [double]::Parse(
([string]$pts[$index].best_effort_timestamp_time).Trim(),
[Globalization.CultureInfo]::InvariantCulture
) - $firstEpochSeconds
$epochSeconds = ([int64]$packetPts[$index] - $firstPacketPts) / 90000.0
if ($epochSeconds -le $previousEpochSeconds -or $epochSeconds -gt ($timelineDuration + 0.001)) {
throw "Decoded LAB E4 timestamps are not strictly monotonic inside the camera timeline"
}
@@ -313,6 +356,7 @@ try {
Write-Output ("PHASE=e4-inference-start FRAMES={0}" -f $activeFrameCount)
& docker @runArgs
Assert-LastExitCode "LAB E4 semantic inference"
Copy-Item -LiteralPath $decodeRepairPath -Destination (Join-Path $stagingRoot "decode-repair.json")
$freeBytesPostInference = Assert-FreeSpace "post-inference"
Write-Output "PHASE=e4-inference-complete"
@@ -12,7 +12,18 @@ param(
[string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\experiments\lab-v1-vegetation",
[string]$RavnovesVideo = "D:\NDC_MISSIONCORE\runtime\experiments\e46e\inputs\right-cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8.mp4"
[string]$RavnovesVideo = "D:\NDC_MISSIONCORE\runtime\experiments\e46e\inputs\right-cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8.mp4",
[string]$RavnovesSourceId = "RAVNOVES00/right-cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8",
[string]$RavnovesSha256 = "cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8",
[ValidateRange(1, 1000000)]
[int]$RavnovesExpectedFrameCount = 4489,
[string]$RavnovesBaseM4ResultId = "m4-threat-replay-2a953c5f27f2a5b1dddc5c658c1de2c323d7796084a099c024987a1da03aa324",
[string]$RavnovesSourceProfile = ""
)
Set-StrictMode -Version Latest
@@ -38,6 +49,31 @@ $configRoot = Join-Path $ToolRoot "config"
$benchmarkConfig = Join-Path $configRoot "lab-v1-goose-vegetation-benchmark-v1.json"
$policyConfig = Join-Path $configRoot "lab-v1-vegetation-mission-policy-v1.json"
$providerMapConfig = Join-Path $configRoot "lab-v1-vegetation-provider-label-map-v1.json"
$ravnovesProfileDocument = $null
if (-not [string]::IsNullOrWhiteSpace($RavnovesSourceProfile)) {
$resolvedProfile = (Resolve-Path -LiteralPath $RavnovesSourceProfile).Path
if (-not $resolvedProfile.StartsWith($ToolRoot, [StringComparison]::OrdinalIgnoreCase)) {
throw "RAVNOVES source profile must stay under ToolRoot"
}
$ravnovesProfileDocument = Get-Content -LiteralPath $resolvedProfile -Raw | ConvertFrom-Json
$source = $ravnovesProfileDocument.source
if (
$ravnovesProfileDocument.schema_version -ne "missioncore.lab-v1-ravnoves-source/v1" -or
$null -eq $source -or
[string]::IsNullOrWhiteSpace([string]$source.source_id) -or
[string]$source.source_sha256 -notmatch "^[a-f0-9]{64}$" -or
[int]$source.expected_width -ne 800 -or
[int]$source.expected_height -ne 600 -or
[int]$source.expected_frame_count -lt 1 -or
[string]$source.crop_contract -ne "center-600-square-to-512; outside-crop-is-undefined"
) {
throw "RAVNOVES source profile is incompatible"
}
$RavnovesSourceId = [string]$source.source_id
$RavnovesSha256 = [string]$source.source_sha256
$RavnovesExpectedFrameCount = [int]$source.expected_frame_count
$RavnovesBaseM4ResultId = [string]$source.base_m4_result_id
}
$datasetRoot = Join-Path $AssetRoot "goose-2d\validation"
$checkpointRelative = if ($candidateKey -eq "ddrnet") {
"models\goose\ddrnet_class_512.pth"
@@ -51,7 +87,6 @@ $expectedCheckpointSha256 = if ($candidateKey -eq "ddrnet") {
"6dd412c0c99115e359896c4cab43a8e6bce9e09b843e7fa885fe597b0a6121cd"
}
$expectedCheckpointBytes = if ($candidateKey -eq "ddrnet") { 259419077 } else { 98208249 }
$ravnovesSha256 = "cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8"
$frameIndices = @(0, 253, 512, 768, 1024, 1536, 2048, 2560, 3072, 3584, 4096, 4488)
$dockerConfig = "D:\NDC_MISSIONCORE\datasets\state\lab-v1-vegetation\docker-config"
@@ -110,6 +145,18 @@ function Invoke-IsolatedRun {
[string]$FramesRoot = ""
)
$containerName = "ndc-lab-v1-goose-$candidateKey-$([Guid]::NewGuid().ToString('N').Substring(0, 10))"
$activeConfigRoot = $configRoot
if ($RunMode -eq "ravnoves-video" -and $null -ne $ravnovesProfileDocument) {
$activeConfigRoot = Join-Path $RunRoot "effective-config"
New-Item -ItemType Directory -Path $activeConfigRoot | Out-Null
Copy-Item -LiteralPath $policyConfig -Destination $activeConfigRoot
Copy-Item -LiteralPath $providerMapConfig -Destination $activeConfigRoot
$benchmark = Get-Content -LiteralPath $benchmarkConfig -Raw | ConvertFrom-Json
$benchmark.ravnoves = $ravnovesProfileDocument.source
$benchmark | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath (
Join-Path $activeConfigRoot "lab-v1-goose-vegetation-benchmark-v1.json"
) -Encoding utf8
}
$visualCount = if ($RunMode -eq "ravnoves-video") { 0 } else { 12 }
$arguments = @(
"run", "--rm", "--name", $containerName,
@@ -125,7 +172,7 @@ function Invoke-IsolatedRun {
"--env", "HOME=/tmp",
"--mount", "type=bind,src=$datasetRoot,dst=/data/goose,readonly",
"--mount", "type=bind,src=$checkpoint,dst=/models/candidate.pth,readonly",
"--mount", "type=bind,src=$configRoot,dst=/config,readonly",
"--mount", "type=bind,src=$activeConfigRoot,dst=/config,readonly",
"--mount", "type=bind,src=$RunRoot,dst=/output",
$image,
"--mode", $RunMode,
@@ -147,15 +194,27 @@ function Invoke-IsolatedRun {
$tail = @($arguments[$mountIndex..($arguments.Count - 1)])
$arguments = $head + @("--mount", "type=bind,src=$FramesRoot,dst=/input,readonly") + $tail
}
& docker @arguments
if ($LASTEXITCODE -ne 0) {
throw "LAB V1 container failed with exit code $LASTEXITCODE"
$dockerExitCode = -1
$previousErrorActionPreference = $ErrorActionPreference
try {
# Windows PowerShell exposes native stderr as ErrorRecord objects. Model
# libraries legitimately emit warnings there, so merge the stream and
# fail only on the native process exit code.
$ErrorActionPreference = "Continue"
& docker @arguments 2>&1 | ForEach-Object { Write-Output $_ }
$dockerExitCode = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $previousErrorActionPreference
}
if ($dockerExitCode -ne 0) {
throw "LAB V1 container failed with exit code $dockerExitCode"
}
}
function Export-RavnovesFrames {
param([string]$Destination)
Assert-FileIdentity -Path $RavnovesVideo -ExpectedBytes (Get-Item -LiteralPath $RavnovesVideo).Length -ExpectedSha256 $ravnovesSha256
Assert-FileIdentity -Path $RavnovesVideo -ExpectedBytes (Get-Item -LiteralPath $RavnovesVideo).Length -ExpectedSha256 $RavnovesSha256
New-Item -ItemType Directory -Path $Destination | Out-Null
$expression = ($frameIndices | ForEach-Object { "eq(n\,$_ )" }) -join "+"
$temporaryPattern = Join-Path $Destination "selected-%03d.png"
@@ -175,14 +234,68 @@ function Export-RavnovesFrames {
function Export-RavnovesVideoFrames {
param([string]$Destination)
Assert-FileIdentity -Path $RavnovesVideo -ExpectedBytes (Get-Item -LiteralPath $RavnovesVideo).Length -ExpectedSha256 $ravnovesSha256
Assert-FileIdentity -Path $RavnovesVideo -ExpectedBytes (Get-Item -LiteralPath $RavnovesVideo).Length -ExpectedSha256 $RavnovesSha256
New-Item -ItemType Directory -Path $Destination | Out-Null
& ffmpeg -hide_banner -loglevel error -i $RavnovesVideo -map 0:v:0 -fps_mode passthrough (Join-Path $Destination "frame-%06d.png")
$decodedRoot = "{0}-decoded-by-pts" -f $Destination
$packetsPath = "{0}-packets.csv" -f $Destination
New-Item -ItemType Directory -Path $decodedRoot | Out-Null
& ffprobe -v error -select_streams v:0 -show_packets -show_entries packet=pts,flags -of csv=p=0 -o $packetsPath $RavnovesVideo
if ($LASTEXITCODE -ne 0) {
throw "RAVNOVES full-video packet probe failed"
}
$packetRows = @(Get-Content -LiteralPath $packetsPath | Select-Object -First $RavnovesExpectedFrameCount)
if ($packetRows.Count -ne $RavnovesExpectedFrameCount) {
throw "RAVNOVES full-video packet sequence changed"
}
& ffmpeg -hide_banner -loglevel error `
-hwaccel cuda -hwaccel_output_format cuda -c:v h264_cuvid `
-err_detect ignore_err -flags +output_corrupt -copyts `
-i $RavnovesVideo -map 0:v:0 -vf "hwdownload,format=nv12" `
-fps_mode passthrough -enc_time_base demux -frames:v $RavnovesExpectedFrameCount `
-frame_pts 1 (Join-Path $decodedRoot "frame-%d.png")
if ($LASTEXITCODE -ne 0) {
throw "RAVNOVES full-video frame extraction failed"
}
$decodedCount = @(Get-ChildItem -LiteralPath $decodedRoot -File -Filter "frame-*.png").Count
$repairs = @()
for ($index = 0; $index -lt $RavnovesExpectedFrameCount; $index++) {
$columns = ([string]$packetRows[$index]).Split(",")
if ($columns.Count -lt 2) {
throw "RAVNOVES full-video packet row is malformed"
}
$pts = [int64]::Parse($columns[0].Trim(), [Globalization.CultureInfo]::InvariantCulture)
$decodedPath = Join-Path $decodedRoot ("frame-{0}.png" -f $pts)
$canonicalPath = Join-Path $Destination ("frame-{0:D6}.png" -f ($index + 1))
if (Test-Path -LiteralPath $decodedPath -PathType Leaf) {
Move-Item -LiteralPath $decodedPath -Destination $canonicalPath
continue
}
if ($index -eq 0 -or $repairs.Count -ge 1) {
throw "RAVNOVES source contains more than one recoverable decoder gap"
}
$previousPath = Join-Path $Destination ("frame-{0:D6}.png" -f $index)
Copy-Item -LiteralPath $previousPath -Destination $canonicalPath
$repairs += [ordered]@{
sequence = $index + 1
packet_pts = $pts
method = "duplicate-previous-decoded-frame"
}
}
Remove-Item -LiteralPath $decodedRoot -Recurse -Force
Remove-Item -LiteralPath $packetsPath -Force
[ordered]@{
schema_version = "missioncore.recorded-video-decode-repair/v1"
decoder = "ffmpeg-h264_cuvid-output-corrupt"
packets_requested = $RavnovesExpectedFrameCount
frames_decoded = $decodedCount
repaired_frame_count = $repairs.Count
repairs = $repairs
} | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (
Join-Path (Split-Path $Destination -Parent) "decode-repair.json"
) -Encoding utf8
$frames = @(Get-ChildItem -LiteralPath $Destination -File -Filter "frame-*.png" | Sort-Object Name)
if ($frames.Count -ne 4489 -or $frames[0].Name -ne "frame-000001.png" -or $frames[-1].Name -ne "frame-004489.png") {
$lastFrameName = "frame-{0:D6}.png" -f $RavnovesExpectedFrameCount
if ($frames.Count -ne $RavnovesExpectedFrameCount -or $frames[0].Name -ne "frame-000001.png" -or $frames[-1].Name -ne $lastFrameName) {
throw "RAVNOVES full-video frame sequence changed"
}
}
@@ -244,6 +357,9 @@ try {
$framesRoot = Join-Path $runRoot "input-frames"
Export-RavnovesVideoFrames -Destination $framesRoot
Invoke-IsolatedRun -RunMode "ravnoves-video" -RunRoot $runRoot -Limit 0 -FramesRoot $framesRoot
Copy-Item -LiteralPath (Join-Path $runRoot "decode-repair.json") -Destination (
Join-Path $runRoot "result\decode-repair.json"
)
Remove-Item -LiteralPath $framesRoot -Recurse -Force
}
}
@@ -0,0 +1,340 @@
#!/usr/bin/env python3
"""Run DDRNet on an immutable mixed-route camera review pack."""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import platform
import statistics
import time
from pathlib import Path, PurePosixPath
from typing import Any
import numpy as np
import torch
from PIL import Image
from run_goose_vegetation_benchmark import (
CLASS_COUNT,
expand_mask,
infer,
load_mapping,
load_model,
percentile,
preprocess,
read_json,
save_image,
sha256,
stable_digest,
validate_contracts,
)
SCHEMA = "missioncore.mixed-route-ddrnet-islands/v1"
PACK_SCHEMA = "missioncore.mixed-route-review-pack/v1"
AUTHORITY = {
"ground_truth": False,
"candidate_accepted": False,
"navigation_or_safety_accepted": False,
"camera_semantics_can_clear_rigid_geometry": False,
"actuation_allowed": False,
}
class MixedRouteDdrnetError(RuntimeError):
"""The route pack or DDRNet evidence changed or is incomplete."""
def arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--pack", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--policy", type=Path, required=True)
parser.add_argument("--provider-map", type=Path, required=True)
parser.add_argument("--checkpoint", type=Path, required=True)
parser.add_argument("--dataset-root", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
return parser.parse_args()
def canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def object_value(value: object, label: str) -> dict[str, Any]:
if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
raise MixedRouteDdrnetError(f"{label} must be an object")
return value
def load_pack(root: Path) -> tuple[dict[str, Any], list[dict[str, Any]]]:
pack = root.resolve(strict=True)
if not pack.is_dir() or pack.is_symlink():
raise MixedRouteDdrnetError("mixed-route review pack is unavailable")
manifest_path = pack / "manifest.json"
manifest = object_value(
json.loads(manifest_path.read_text(encoding="utf-8")),
"mixed-route manifest",
)
identity = object_value(manifest.get("identity"), "mixed-route identity")
identity_sha256 = manifest.get("identity_sha256")
frames = manifest.get("frames")
frame_count = manifest.get("frame_count")
if (
manifest.get("schema_version") != PACK_SCHEMA
or identity.get("schema_version") != PACK_SCHEMA
or not isinstance(identity_sha256, str)
or hashlib.sha256(canonical_json(identity)).hexdigest() != identity_sha256
or manifest.get("pack_id") != f"mixed-route-review-pack-{identity_sha256}"
or identity.get("ground_truth") is not False
or object_value(identity.get("authority"), "mixed-route authority").get(
"navigation_or_safety_accepted"
)
is not False
or not isinstance(frame_count, int)
or isinstance(frame_count, bool)
or not 1 <= frame_count <= 64
or not isinstance(frames, list)
or len(frames) != frame_count
):
raise MixedRouteDdrnetError("mixed-route review pack identity changed")
timeline_descriptor = object_value(manifest.get("timeline"), "mixed-route timeline")
timeline_path = pack / "timeline.jsonl"
if (
timeline_descriptor.get("path") != timeline_path.name
or timeline_path.stat().st_size != timeline_descriptor.get("byte_length")
or sha256(timeline_path) != timeline_descriptor.get("sha256")
):
raise MixedRouteDdrnetError("mixed-route timeline proof changed")
rows: list[dict[str, Any]] = []
with timeline_path.open(encoding="utf-8") as stream:
for expected, line in enumerate(stream):
row = object_value(json.loads(line), "mixed-route timeline row")
seconds = row.get("session_seconds")
if (
row.get("frame_index") != expected
or row.get("sequence") != expected + 1
or not isinstance(row.get("source_sequence"), int)
or row.get("source_frame_index") != row["source_sequence"] - 1
or not isinstance(seconds, (int, float))
or isinstance(seconds, bool)
or (rows and float(seconds) <= float(rows[-1]["session_seconds"]))
):
raise MixedRouteDdrnetError("mixed-route timeline order changed")
rows.append(row)
if len(rows) != frame_count:
raise MixedRouteDdrnetError("mixed-route timeline is incomplete")
for expected, (descriptor_raw, row) in enumerate(zip(frames, rows)): # noqa: B905
descriptor = object_value(descriptor_raw, "mixed-route frame descriptor")
relative = descriptor.get("path")
if relative != f"frames/frame-{expected + 1:06d}.png":
raise MixedRouteDdrnetError("mixed-route frame path changed")
pure = PurePosixPath(relative)
path = pack.joinpath(*pure.parts)
if (
path.is_symlink()
or not path.is_file()
or not path.resolve().is_relative_to(pack)
or path.stat().st_size != descriptor.get("byte_length")
or sha256(path) != descriptor.get("sha256")
or not isinstance(descriptor.get("source_segment_sha256"), str)
or row.get("source_sequence")
!= identity["selected_sequences"][expected]
):
raise MixedRouteDdrnetError("mixed-route frame proof changed")
return manifest, rows
def overlay(source: Image.Image, semantic: np.ndarray, palette: np.ndarray) -> Image.Image:
if semantic.shape != (600, 800):
raise MixedRouteDdrnetError("expanded semantic mask shape changed")
base = source.convert("RGBA")
colors = Image.fromarray(palette[semantic], mode="RGBA")
return Image.alpha_composite(base, colors)
def run() -> int:
args = arguments()
if not torch.cuda.is_available():
raise MixedRouteDdrnetError("CUDA is required for DDRNet islands")
if args.output.exists():
raise MixedRouteDdrnetError("DDRNet islands output already exists")
manifest, timeline = load_pack(args.pack)
config = read_json(args.config, "benchmark config")
policy = read_json(args.policy, "mission policy")
provider_map = read_json(args.provider_map, "provider map")
candidate = validate_contracts(config, policy, provider_map, "ddrnet")
checkpoint = args.checkpoint.resolve(strict=True)
if (
checkpoint.is_symlink()
or checkpoint.stat().st_size != candidate["checkpoint_size_bytes"]
or sha256(checkpoint) != candidate["checkpoint_sha256"]
):
raise MixedRouteDdrnetError("DDRNet checkpoint identity changed")
dataset_root = args.dataset_root.resolve(strict=True)
mapping_path = dataset_root / config["dataset"]["mapping_relative_path"]
names, palette = load_mapping(mapping_path, config["dataset"]["mapping_sha256"])
args.output.mkdir(mode=0o700, parents=True, exist_ok=False)
mask_root = args.output / "semantic-masks"
overlay_root = args.output / "overlay-frames"
mask_root.mkdir(mode=0o700)
overlay_root.mkdir(mode=0o700)
torch.cuda.empty_cache()
model, model_name, architecture_failures = load_model("ddrnet", checkpoint)
first_path = args.pack / manifest["frames"][0]["path"]
with Image.open(first_path) as opened:
warm_source = opened.convert("RGB")
warm_tensor, _ = preprocess(warm_source)
warmup_ms = [infer(model, warm_tensor)[1] for _ in range(3)]
torch.cuda.reset_peak_memory_stats()
latencies_ms: list[float] = []
aggregate = np.zeros(CLASS_COUNT, dtype=np.int64)
frame_results: list[dict[str, Any]] = []
started = time.perf_counter()
for index, (descriptor, timeline_row) in enumerate(
zip(manifest["frames"], timeline) # noqa: B905 - Worker image uses Python 3.9.
):
source_path = args.pack / descriptor["path"]
with Image.open(source_path) as opened:
source = opened.convert("RGB")
if source.size != (800, 600):
raise MixedRouteDdrnetError("mixed-route source resolution changed")
tensor, crop_box = preprocess(source)
prediction, latency_ms = infer(model, tensor)
expanded = expand_mask(prediction, source.size, crop_box)
latencies_ms.append(latency_ms)
aggregate += np.bincount(expanded.reshape(-1), minlength=CLASS_COUNT)
mask_path = mask_root / f"frame-{index + 1:06d}.png"
overlay_path = overlay_root / f"frame-{index + 1:06d}.png"
mask_sha256 = save_image(mask_path, expanded, "L")
overlay_sha256 = save_image(overlay_path, overlay(source, expanded, palette))
present = np.flatnonzero(np.bincount(expanded.reshape(-1), minlength=CLASS_COUNT))
frame_results.append(
{
"frame_index": index,
"source_sequence": timeline_row["source_sequence"],
"source_frame_index": timeline_row["source_frame_index"],
"session_seconds": timeline_row["session_seconds"],
"latency_ms": round(latency_ms, 6),
"present_classes": [
{"class_id": int(class_id), "label": names[int(class_id)]}
for class_id in present
],
"mask": {
"path": mask_path.relative_to(args.output).as_posix(),
"byte_length": mask_path.stat().st_size,
"sha256": mask_sha256,
},
"overlay": {
"path": overlay_path.relative_to(args.output).as_posix(),
"byte_length": overlay_path.stat().st_size,
"sha256": overlay_sha256,
},
}
)
wall_seconds = time.perf_counter() - started
if len(frame_results) != manifest["frame_count"]:
raise MixedRouteDdrnetError("DDRNet island accounting changed")
timing = {
"prewarm_inference_count": len(warmup_ms),
"prewarm_latency_ms_first": round(warmup_ms[0], 6),
"prewarm_latency_ms_last": round(warmup_ms[-1], 6),
"inference_wall_seconds": round(wall_seconds, 6),
"latency_ms_mean": round(statistics.fmean(latencies_ms), 6),
"latency_ms_p50": round(percentile(latencies_ms, 0.5), 6),
"latency_ms_p95": round(percentile(latencies_ms, 0.95), 6),
"throughput_fps_from_mean_inference": round(
1000.0 / statistics.fmean(latencies_ms), 6
),
}
if any(not math.isfinite(float(value)) for value in timing.values()):
raise MixedRouteDdrnetError("DDRNet timing is non-finite")
result: dict[str, Any] = {
"schema_version": SCHEMA,
"status": "review-islands-ready-not-accepted",
"worker_id": "worker-006",
"source": {
"pack_id": manifest["pack_id"],
"pack_identity_sha256": manifest["identity_sha256"],
"job_id": manifest["identity"]["job_id"],
"input_sha256": manifest["identity"]["input_sha256"],
"session_id": manifest["identity"]["session_id"],
"source_id": manifest["identity"]["source_id"],
"frame_count": manifest["frame_count"],
"ground_truth_available": False,
},
"candidate": {
"candidate_key": "ddrnet",
"candidate_id": candidate["candidate_id"],
"loaded_model_name": model_name,
"architecture_probe_failures": architecture_failures,
"checkpoint_size_bytes": checkpoint.stat().st_size,
"checkpoint_sha256": sha256(checkpoint),
},
"taxonomy": {
"schema_version": "missioncore.lab-v1-vegetation-taxonomy/v1",
"classes": [
{
"class_id": class_id,
"label": names[class_id],
"color_rgb": palette[class_id, :3].astype(int).tolist(),
"disposition": "undefined" if class_id == 0 else "prediction",
}
for class_id in range(CLASS_COUNT)
],
},
"aggregate_prediction_pixels": aggregate.tolist(),
"frames": frame_results,
"timing": timing,
"resource": {
"hostname": platform.node(),
"gpu_name": torch.cuda.get_device_name(0),
"peak_allocated_vram_bytes": int(torch.cuda.max_memory_allocated()),
"peak_reserved_vram_bytes": int(torch.cuda.max_memory_reserved()),
"torch_version": torch.__version__,
"cuda_runtime_version": torch.version.cuda,
"python_version": platform.python_version(),
},
"provenance": {
"pack_manifest_sha256": sha256(args.pack / "manifest.json"),
"config_sha256": sha256(args.config),
"policy_sha256": sha256(args.policy),
"provider_map_sha256": sha256(args.provider_map),
"runner_sha256": sha256(Path(__file__)),
},
"limitations": [
"Selected independently decodable islands are not a complete route timeline.",
"RAVNOVES004TREE has no route truth; class colors are model predictions.",
"DDRNet evidence cannot clear rigid geometry, person or vehicle vetoes.",
],
"authority": AUTHORITY,
}
result["result_id"] = f"mixed-route-ddrnet-islands-{stable_digest(result)}"
(args.output / "result.json").write_text(
json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
print(
json.dumps(
{
"result_id": result["result_id"],
"frames": len(frame_results),
"latency_p95_ms": timing["latency_ms_p95"],
},
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(run())
@@ -0,0 +1,277 @@
#!/usr/bin/env python3
"""Seal fail-closed TRAVEL/TGS evidence for mixed-route review islands."""
from __future__ import annotations
import argparse
import csv
import json
from pathlib import Path
import numpy as np
from build_tgs_fail_closed_evidence import (
TgsEvidenceError,
_load_float32,
classify_exact_input,
costmap_grid,
rasterize_costmap,
sha256_file,
write_deterministic_npz,
)
CONFIG_SCHEMA = "missioncore.mixed-route-tgs-review-profile/v1"
INPUT_SCHEMA = "missioncore.mixed-route-tgs-input/v1"
RESULT_SCHEMA = "missioncore.mixed-route-tgs-result/v1"
FRAME_COUNT = 10
def _timing(path: Path) -> dict[str, object]:
rows: list[dict[str, object]] = []
with path.open(encoding="utf-8", newline="") as stream:
for raw in csv.DictReader(stream, delimiter="\t"):
try:
row = {
"profile_id": str(raw["profile"]),
"slot": int(raw["slot"]),
"wall_seconds": float(raw["wall_seconds"]),
"max_rss_kib": int(raw["max_rss_kib"]),
}
except (KeyError, TypeError, ValueError) as exc:
raise TgsEvidenceError("TGS timing row is invalid") from exc
if (
row["profile_id"] not in {"current_increment", "causal_rolling_1s"}
or not 0 <= row["slot"] < FRAME_COUNT
or not 0 <= row["wall_seconds"] < 60
or not 0 < row["max_rss_kib"] < 16 * 1024 * 1024
):
raise TgsEvidenceError("TGS timing value is invalid")
rows.append(row)
if len(rows) != FRAME_COUNT * 2:
raise TgsEvidenceError("TGS timing is incomplete")
seconds = np.asarray([row["wall_seconds"] for row in rows], dtype=np.float64)
return {
"runs": rows,
"wall_seconds_mean": round(float(seconds.mean()), 6),
"wall_seconds_p95": round(float(np.percentile(seconds, 95)), 6),
"max_rss_kib": max(int(row["max_rss_kib"]) for row in rows),
}
def build(run_root: Path, config_path: Path, output_root: Path) -> dict[str, object]:
if output_root.exists():
raise TgsEvidenceError("mixed-route TGS evidence already exists")
config = json.loads(config_path.read_text(encoding="utf-8"))
source = config.get("source") if isinstance(config, dict) else None
invariants = config.get("invariants") if isinstance(config, dict) else None
if (
config.get("schema_version") != CONFIG_SCHEMA
or not isinstance(source, dict)
or not isinstance(invariants, dict)
or invariants.get("aos_allowed") is not False
or invariants.get("missing_support_means_free") is not False
or invariants.get("future_frames_used") is not False
or invariants.get("navigation_or_actuation_allowed") is not False
or config.get("state_codes")
!= {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3,
}
):
raise TgsEvidenceError("mixed-route TGS profile changed")
input_manifest_path = run_root / "inputs" / "input-manifest.json"
input_manifest = json.loads(input_manifest_path.read_text(encoding="utf-8"))
if (
input_manifest.get("schema_version") != INPUT_SCHEMA
or input_manifest.get("source_pack_id") != source.get("source_pack_id")
or input_manifest.get("source_pack_sha256")
!= source.get("source_pack_sha256")
or input_manifest.get("config_sha256") != sha256_file(config_path)
or input_manifest.get("coordinate_frame") != "map-gravity-local"
or input_manifest.get("future_frames_used") is not False
or input_manifest.get("frame_count") != FRAME_COUNT
or len(input_manifest.get("records", [])) != FRAME_COUNT * 2
):
raise TgsEvidenceError("mixed-route TGS input manifest changed")
records = {
(str(row["profile_id"]), int(row["slot"])): row
for row in input_manifest["records"]
}
if len(records) != FRAME_COUNT * 2:
raise TgsEvidenceError("mixed-route TGS input records are not unique")
cell_size = float(config["costmap"]["cell_size_m"])
radius = float(config["costmap"]["radius_m"])
grid = costmap_grid(radius, cell_size)
arrays: dict[str, np.ndarray] = {
"costmap_cell_indices_xy": grid[:, :2].astype(np.int32),
"costmap_cell_centers_xy_m": grid[:, 2:].astype(np.float32),
"source_frame_indices": np.asarray(
[
records[("current_increment", slot)]["source_frame_index"]
for slot in range(FRAME_COUNT)
],
dtype=np.int64,
),
"session_seconds": np.asarray(
[
records[("current_increment", slot)]["session_seconds"]
for slot in range(FRAME_COUNT)
],
dtype=np.float64,
),
}
summaries: list[dict[str, object]] = []
for profile_id in ("current_increment", "causal_rolling_1s"):
all_points: list[np.ndarray] = []
all_states: list[np.ndarray] = []
offsets = [0]
grid_states: list[np.ndarray] = []
ground_counts: list[np.ndarray] = []
nonground_counts: list[np.ndarray] = []
rejected_counts: list[np.ndarray] = []
z_bounds_rows: list[np.ndarray] = []
for slot in range(FRAME_COUNT):
record = records[(profile_id, slot)]
native_path = run_root / "inputs" / str(record["relative_path"])
if (
not native_path.is_file()
or native_path.stat().st_size != record["bytes"]
or sha256_file(native_path) != record["sha256"]
):
raise TgsEvidenceError("sealed mixed-route TGS input changed")
output = run_root / "outputs" / profile_id
points, states = classify_exact_input(
_load_float32(native_path, 4),
_load_float32(output / f"{slot}_ground.bin", 4),
_load_float32(output / f"{slot}_nonground.bin", 4),
min_range_m=float(config["tgs"]["min_range_m"]),
max_range_m=float(config["tgs"]["max_range_m"]),
)
grid_state, ground, nonground, rejected, z_bounds = rasterize_costmap(
points,
states,
grid,
cell_size_m=cell_size,
)
all_points.append(points.astype(np.float32, copy=False))
all_states.append(states)
offsets.append(offsets[-1] + points.shape[0])
grid_states.append(grid_state)
ground_counts.append(ground)
nonground_counts.append(nonground)
rejected_counts.append(rejected)
z_bounds_rows.append(z_bounds)
accounted = (
np.count_nonzero(states == 1)
+ np.count_nonzero(states == 2)
+ np.count_nonzero(states == 3)
== points.shape[0]
)
summaries.append(
{
"profile_id": profile_id,
"slot": slot,
"frame_index": int(record["frame_index"]),
"source_frame_index": int(record["source_frame_index"]),
"source_sequence": int(record["source_sequence"]),
"session_seconds": float(record["session_seconds"]),
"point_count": int(points.shape[0]),
"ground_point_count": int(np.count_nonzero(states == 1)),
"nonground_point_count": int(np.count_nonzero(states == 2)),
"rejected_point_count": int(np.count_nonzero(states == 3)),
"ground_cell_count": int(np.count_nonzero(grid_state == 1)),
"nonground_cell_count": int(np.count_nonzero(grid_state == 2)),
"rejected_cell_count": int(np.count_nonzero(grid_state == 3)),
"unobserved_cell_count": int(np.count_nonzero(grid_state == 0)),
"all_points_accounted": bool(accounted),
}
)
arrays[f"{profile_id}_points_xyz_m"] = np.concatenate(all_points)
arrays[f"{profile_id}_point_states"] = np.concatenate(all_states)
arrays[f"{profile_id}_point_offsets"] = np.asarray(offsets, dtype=np.int64)
arrays[f"{profile_id}_costmap_states"] = np.stack(grid_states)
arrays[f"{profile_id}_costmap_ground_point_counts"] = np.stack(ground_counts)
arrays[f"{profile_id}_costmap_nonground_point_counts"] = np.stack(
nonground_counts
)
arrays[f"{profile_id}_costmap_rejected_point_counts"] = np.stack(
rejected_counts
)
arrays[f"{profile_id}_costmap_z_bounds_m"] = np.stack(z_bounds_rows)
if not all(bool(row["all_points_accounted"]) for row in summaries):
raise TgsEvidenceError("mixed-route TGS lost an eligible point")
output_root.mkdir(parents=True)
evidence_path = output_root / "evidence.npz"
write_deterministic_npz(evidence_path, arrays)
timing = _timing(run_root / "tgs-timing.tsv")
result = {
"schema_version": RESULT_SCHEMA,
"status": "passed-review-only",
"source": {
"source_id": source["source_id"],
"session_id": source["session_id"],
"review_pack_id": source["review_pack_id"],
"source_pack_id": source["source_pack_id"],
"source_pack_sha256": source["source_pack_sha256"],
},
"config_sha256": sha256_file(config_path),
"input_manifest_sha256": sha256_file(input_manifest_path),
"evidence": {
"path": "evidence.npz",
"bytes": evidence_path.stat().st_size,
"sha256": sha256_file(evidence_path),
},
"costmap": {
"coordinate_frame": "map-gravity-local",
"cell_size_m": cell_size,
"radius_m": radius,
"cell_count": int(grid.shape[0]),
},
"anchors": summaries,
"timing": timing,
"summary": {
"frame_count": FRAME_COUNT,
"anchor_profile_count": len(summaries),
"all_eligible_points_accounted": True,
"aos_used": False,
"primary_profile": "causal_rolling_1s",
},
"limitations": [
"Selected review islands are not a complete route timeline.",
(
"TGS separates local ground support from non-ground evidence; it does not "
"prove ditch or negative-obstacle detection."
),
"Camera projection is visual evidence only and cannot clear rigid geometry.",
],
"authority": {
"visual_quality_accepted": False,
"traversability_accepted": False,
"realtime_accepted": False,
"navigation_or_safety_accepted": False,
"actuation_allowed": False,
},
}
(output_root / "result.json").write_text(
json.dumps(result, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return result
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--run-root", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
result = build(args.run_root, args.config, args.output_root)
print(json.dumps(result["summary"], sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,243 @@
#!/usr/bin/env python3
"""Prepare exact mixed-route LiDAR islands for isolated TRAVEL/TGS review."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import numpy as np
from prepare_tgs_fail_closed_inputs import TgsInputError, gravity_local_xyzi
CONFIG_SCHEMA = "missioncore.mixed-route-tgs-review-profile/v1"
PACK_SCHEMA = "missioncore.mixed-route-lidar-pack/v1"
INPUT_SCHEMA = "missioncore.mixed-route-tgs-input/v1"
FRAME_COUNT = 10
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _slice(points: np.ndarray, offsets: np.ndarray, index: int) -> np.ndarray:
return points[int(offsets[index]) : int(offsets[index + 1])]
def _validate_offsets(offsets: np.ndarray, point_count: int) -> bool:
return bool(
offsets.shape == (FRAME_COUNT + 1,)
and offsets.dtype == np.int64
and int(offsets[0]) == 0
and int(offsets[-1]) == point_count
and np.all(np.diff(offsets) > 0)
)
def prepare(source_root: Path, config_path: Path, output_root: Path) -> dict[str, object]:
if output_root.exists():
raise TgsInputError("mixed-route TGS output already exists")
source = source_root.resolve(strict=True)
config = json.loads(config_path.read_text(encoding="utf-8"))
manifest = json.loads((source / "manifest.json").read_text(encoding="utf-8"))
identity = manifest.get("identity") if isinstance(manifest, dict) else None
artifact = manifest.get("artifact") if isinstance(manifest, dict) else None
source_config = config.get("source") if isinstance(config, dict) else None
invariants = config.get("invariants") if isinstance(config, dict) else None
profiles = config.get("profiles") if isinstance(config, dict) else None
if (
config.get("schema_version") != CONFIG_SCHEMA
or not isinstance(source_config, dict)
or not isinstance(invariants, dict)
or not isinstance(profiles, dict)
or set(profiles) != {"current_increment", "causal_rolling_1s"}
or source_config.get("input_coordinate_frame")
!= "map-gravity-local-translation-only"
or invariants.get("lidar_orientation_applied_to_tgs_input") is not False
or invariants.get("future_frames_used") is not False
or invariants.get("navigation_or_actuation_allowed") is not False
or manifest.get("schema_version") != PACK_SCHEMA
or not isinstance(identity, dict)
or identity.get("schema_version") != PACK_SCHEMA
or identity.get("session_id") != source_config.get("session_id")
or identity.get("review_pack_id") != source_config.get("review_pack_id")
or manifest.get("pack_id") != source_config.get("source_pack_id")
or not isinstance(artifact, dict)
or artifact.get("path") != "lidar-pack.npz"
or artifact.get("sha256") != source_config.get("source_pack_sha256")
or identity.get("frame_count") != FRAME_COUNT
or identity.get("available_lidar_frames") != FRAME_COUNT
or identity.get("causal_history_seconds")
!= float(profiles["causal_rolling_1s"]["history_seconds"])
or identity.get("ground_truth") is not False
):
raise TgsInputError("mixed-route TGS source contract changed")
pack_path = source / "lidar-pack.npz"
if (
not pack_path.is_file()
or pack_path.stat().st_size != artifact.get("byte_length")
or sha256_file(pack_path) != artifact.get("sha256")
):
raise TgsInputError("mixed-route LiDAR pack changed")
required = {
"frame_indices",
"source_frame_indices",
"session_seconds",
"lidar_session_seconds",
"sample_available",
"cloud_offsets",
"cloud_points_map",
"pose_positions_map",
"lidar_camera_delta_ms",
"pose_point_delta_ms",
"causal_history_seconds",
"causal_history_offsets",
"causal_history_points_map",
}
with np.load(pack_path, allow_pickle=False) as archive:
if not required.issubset(archive.files):
raise TgsInputError("mixed-route LiDAR pack members changed")
arrays = {name: archive[name] for name in required}
current_points = arrays["cloud_points_map"]
history_points = arrays["causal_history_points_map"]
if (
arrays["frame_indices"].shape != (FRAME_COUNT,)
or arrays["frame_indices"].dtype != np.int64
or not np.array_equal(arrays["frame_indices"], np.arange(FRAME_COUNT))
or arrays["source_frame_indices"].shape != (FRAME_COUNT,)
or arrays["source_frame_indices"].dtype != np.int64
or np.any(np.diff(arrays["source_frame_indices"]) <= 0)
or arrays["session_seconds"].shape != (FRAME_COUNT,)
or arrays["session_seconds"].dtype != np.float64
or np.any(np.diff(arrays["session_seconds"]) <= 0)
or arrays["lidar_session_seconds"].shape != (FRAME_COUNT,)
or arrays["lidar_session_seconds"].dtype != np.float64
or arrays["sample_available"].shape != (FRAME_COUNT,)
or arrays["sample_available"].dtype != np.bool_
or not arrays["sample_available"].all()
or current_points.ndim != 2
or current_points.shape[1:] != (3,)
or current_points.dtype != np.float32
or history_points.ndim != 2
or history_points.shape[1:] != (3,)
or history_points.dtype != np.float32
or not np.isfinite(current_points).all()
or not np.isfinite(history_points).all()
or not _validate_offsets(arrays["cloud_offsets"], current_points.shape[0])
or not _validate_offsets(
arrays["causal_history_offsets"], history_points.shape[0]
)
or arrays["pose_positions_map"].shape != (FRAME_COUNT, 3)
or arrays["pose_positions_map"].dtype != np.float64
or not np.isfinite(arrays["pose_positions_map"]).all()
or arrays["causal_history_seconds"].shape != (1,)
or float(arrays["causal_history_seconds"][0])
!= float(profiles["causal_rolling_1s"]["history_seconds"])
or np.any(np.abs(arrays["lidar_camera_delta_ms"]) > 100.0)
or np.any(np.abs(arrays["pose_point_delta_ms"]) > 100.0)
):
raise TgsInputError("mixed-route LiDAR arrays changed")
records: list[dict[str, object]] = []
for profile_id in ("current_increment", "causal_rolling_1s"):
for slot in range(FRAME_COUNT):
if profile_id == "current_increment":
points_map = _slice(
current_points, arrays["cloud_offsets"], slot
)
else:
points_map = _slice(
history_points, arrays["causal_history_offsets"], slot
)
radius = float(profiles[profile_id]["local_radius_m"])
relative_xy = (
points_map[:, :2].astype(np.float64)
- arrays["pose_positions_map"][slot, :2]
)
points_map = points_map[np.linalg.norm(relative_xy, axis=1) <= radius]
native = gravity_local_xyzi(
points_map, arrays["pose_positions_map"][slot]
)
if native.shape[0] == 0:
raise TgsInputError("mixed-route TGS profile produced an empty cloud")
target = (
output_root
/ "profiles"
/ profile_id
/ "velodyne"
/ f"{slot:06d}.bin"
)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(np.ascontiguousarray(native).tobytes())
records.append(
{
"profile_id": profile_id,
"slot": slot,
"frame_index": slot,
"source_frame_index": int(
arrays["source_frame_indices"][slot]
),
"source_sequence": int(
arrays["source_frame_indices"][slot]
)
+ 1,
"session_seconds": float(arrays["session_seconds"][slot]),
"lidar_session_seconds": float(
arrays["lidar_session_seconds"][slot]
),
"lidar_camera_delta_ms": float(
arrays["lidar_camera_delta_ms"][slot]
),
"pose_point_delta_ms": float(
arrays["pose_point_delta_ms"][slot]
),
"point_count": int(native.shape[0]),
"relative_path": target.relative_to(output_root).as_posix(),
"bytes": target.stat().st_size,
"sha256": sha256_file(target),
}
)
manifest_out = {
"schema_version": INPUT_SCHEMA,
"source_pack_id": manifest["pack_id"],
"source_pack_sha256": artifact["sha256"],
"config_sha256": sha256_file(config_path),
"coordinate_frame": "map-gravity-local",
"transform": "translation-only-preserve-map-gravity-axis",
"intensity_policy": "zero-filled-algorithm-compatibility-only",
"future_frames_used": False,
"frame_count": FRAME_COUNT,
"profile_count": 2,
"records": records,
"authority": {
"navigation_or_safety_accepted": False,
"actuation_allowed": False,
},
}
manifest_path = output_root / "input-manifest.json"
manifest_path.write_text(
json.dumps(manifest_out, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return manifest_out
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-root", type=Path, required=True)
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
manifest = prepare(args.source_root, args.config, args.output_root)
print(json.dumps({"ok": True, "records": len(manifest["records"])}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+296
View File
@@ -0,0 +1,296 @@
#!/usr/bin/env python3
"""Publish exact, independently decodable camera islands for mixed-route review."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import tempfile
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
from PIL import Image
from k1link.compute.jobs import validate_camera_compute_job
from k1link.device_plugins.xgrids_k1.mqtt.capture import read_capture_clock_origin
SCHEMA = "missioncore.mixed-route-review-pack/v1"
MAX_INDEX_LINE_BYTES = 64 * 1024
class MixedRouteReviewPackError(RuntimeError):
"""The selected camera evidence cannot be published without ambiguity."""
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _sequences(value: str) -> tuple[int, ...]:
try:
sequences = tuple(int(item) for item in value.split(","))
except ValueError as exc:
raise argparse.ArgumentTypeError("sequences must be comma-separated integers") from exc
if not sequences or any(item < 1 for item in sequences):
raise argparse.ArgumentTypeError("sequences must be positive")
if len(set(sequences)) != len(sequences) or tuple(sorted(sequences)) != sequences:
raise argparse.ArgumentTypeError("sequences must be unique and increasing")
return sequences
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--session", type=Path, required=True)
parser.add_argument("--sequences", type=_sequences, required=True)
parser.add_argument("--output-root", type=Path, required=True)
parser.add_argument("--ffmpeg", type=Path, required=True)
return parser.parse_args()
def _read_selected_index(
path: Path,
sequences: tuple[int, ...],
) -> list[dict[str, Any]]:
wanted = set(sequences)
selected: dict[int, dict[str, Any]] = {}
with path.open("rb") as stream:
for expected_sequence, line in enumerate(stream, start=1):
if len(line) > MAX_INDEX_LINE_BYTES or not line.endswith(b"\n"):
raise MixedRouteReviewPackError("camera index line is invalid")
if expected_sequence not in wanted:
continue
try:
value = json.loads(line)
except json.JSONDecodeError as exc:
raise MixedRouteReviewPackError("camera index JSON is invalid") from exc
if (
not isinstance(value, dict)
or value.get("schema_version")
!= "missioncore.camera-recording-index/v1"
or value.get("kind") != "media"
or value.get("sequence") != expected_sequence
or value.get("path") != f"segments/{expected_sequence}.m4s"
or not isinstance(value.get("session_monotonic_ns"), int)
or not isinstance(value.get("host_monotonic_ns"), int)
or not isinstance(value.get("host_epoch_ns"), int)
):
raise MixedRouteReviewPackError("selected camera index row changed")
selected[expected_sequence] = value
if tuple(sorted(selected)) != sequences:
raise MixedRouteReviewPackError("selected camera sequence is incomplete")
return [selected[sequence] for sequence in sequences]
def _decode_exact_fragment(
*,
ffmpeg: Path,
init_path: Path,
segment_path: Path,
output_path: Path,
) -> None:
input_value = f"concat:{init_path}|{segment_path}"
completed = subprocess.run(
[
os.fspath(ffmpeg),
"-hide_banner",
"-loglevel",
"error",
"-nostdin",
"-y",
"-i",
input_value,
"-frames:v",
"1",
os.fspath(output_path),
],
capture_output=True,
text=True,
timeout=30,
check=False,
)
if completed.returncode != 0 or not output_path.is_file():
detail = completed.stderr.strip().splitlines()[-1:] or ["no decoded frame"]
raise MixedRouteReviewPackError(
f"selected fragment is not independently decodable: {segment_path.name}: {detail[0]}"
)
with Image.open(output_path) as image:
if image.mode != "RGB" or image.size != (800, 600):
raise MixedRouteReviewPackError("selected camera frame shape changed")
def prepare(
*,
job_root: Path,
session_root: Path,
sequences: tuple[int, ...],
output_root: Path,
ffmpeg_path: Path,
) -> Path:
job = validate_camera_compute_job(job_root)
session = session_root.resolve(strict=True)
if not session.is_dir() or session.name != job.session_id:
raise MixedRouteReviewPackError("camera job and observation session differ")
capture_root = session / "captures" / "mqtt_live"
origin_path = capture_root / "mqtt.timeline.origin.json"
origin = read_capture_clock_origin(origin_path)
if sequences[-1] > job.segment_count:
raise MixedRouteReviewPackError("selected sequence escapes the camera epoch")
ffmpeg = ffmpeg_path.resolve(strict=True)
if not ffmpeg.is_file():
raise MixedRouteReviewPackError("ffmpeg is unavailable")
epoch_root = (
job.job_root
/ "input"
/ "camera"
/ job.source_id
/ f"epoch-{job.codec_epoch}"
)
selected = _read_selected_index(epoch_root / "index.jsonl", sequences)
identity = {
"schema_version": SCHEMA,
"job_id": job.job_id,
"input_sha256": job.input_sha256,
"session_id": job.session_id,
"source_id": job.source_id,
"codec_epoch": job.codec_epoch,
"clock_origin": {
"artifact_sha256": _sha256(origin_path),
"started_epoch_ns": origin.started_at_epoch_ns,
"started_monotonic_ns": origin.started_monotonic_ns,
},
"selected_sequences": list(sequences),
"selection_policy": "exact-independently-decodable-fragments/v1",
"ground_truth": False,
"authority": {
"navigation_or_safety_accepted": False,
"actuation_allowed": False,
},
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
pack_id = f"mixed-route-review-pack-{identity_sha256}"
parent = output_root.resolve()
parent.mkdir(mode=0o700, parents=True, exist_ok=True)
final = parent / pack_id
if final.exists():
return final
staging = Path(tempfile.mkdtemp(prefix=f".{pack_id}.", dir=parent))
published = False
try:
frames_root = staging / "frames"
frames_root.mkdir(mode=0o700)
timeline_rows: list[dict[str, Any]] = []
artifacts: list[dict[str, Any]] = []
for frame_index, (sequence, row) in enumerate(
zip(sequences, selected, strict=True)
):
output_path = frames_root / f"frame-{frame_index + 1:06d}.png"
segment_path = epoch_root / "segments" / f"{sequence}.m4s"
_decode_exact_fragment(
ffmpeg=ffmpeg,
init_path=epoch_root / "init.mp4",
segment_path=segment_path,
output_path=output_path,
)
host_monotonic_ns = int(row["host_monotonic_ns"])
if host_monotonic_ns < origin.started_monotonic_ns:
raise MixedRouteReviewPackError("selected frame predates the session clock origin")
session_seconds = (
host_monotonic_ns - origin.started_monotonic_ns
) / 1e9
timeline_rows.append(
{
"frame_index": frame_index,
"sequence": frame_index + 1,
"source_frame_index": sequence - 1,
"source_sequence": sequence,
"session_seconds": session_seconds,
"host_monotonic_ns": row["host_monotonic_ns"],
"host_epoch_ns": row["host_epoch_ns"],
}
)
artifacts.append(
{
"path": output_path.relative_to(staging).as_posix(),
"byte_length": output_path.stat().st_size,
"sha256": _sha256(output_path),
"source_segment_sha256": row["sha256"],
}
)
timeline_path = staging / "timeline.jsonl"
timeline_path.write_text(
"".join(
json.dumps(
row,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
+ "\n"
for row in timeline_rows
),
encoding="utf-8",
)
manifest = {
"schema_version": SCHEMA,
"pack_id": pack_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"frame_count": len(sequences),
"timeline": {
"path": timeline_path.name,
"byte_length": timeline_path.stat().st_size,
"sha256": _sha256(timeline_path),
},
"frames": artifacts,
}
(staging / "manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
os.replace(staging, final)
published = True
finally:
if not published:
shutil.rmtree(staging, ignore_errors=True)
return final
def main() -> int:
args = _arguments()
result = prepare(
job_root=args.job,
session_root=args.session,
sequences=args.sequences,
output_root=args.output_root,
ffmpeg_path=args.ffmpeg,
)
print(json.dumps({"pack_id": result.name, "output": os.fspath(result)}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Seal the complete RAVNOVES004TREE semantic pass into existing LAB V1."""
from __future__ import annotations
import argparse
from pathlib import Path
from k1link.laboratory.mixed_route_vegetation_review import (
seal_mixed_route_full_video_review,
)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-lab-root", type=Path, required=True)
parser.add_argument("--job-root", type=Path, required=True)
parser.add_argument("--recorded-media-preparation", type=Path, required=True)
parser.add_argument("--eomt-root", type=Path, required=True)
parser.add_argument("--eomt-profile", type=Path, required=True)
parser.add_argument("--ddrnet-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
print(
seal_mixed_route_full_video_review(
base_lab_root=args.base_lab_root,
job_root=args.job_root,
recorded_media_preparation_path=args.recorded_media_preparation,
eomt_root=args.eomt_root,
eomt_profile_path=args.eomt_profile,
ddrnet_root=args.ddrnet_root,
output_root=args.output_root,
)
)
if __name__ == "__main__":
main()
@@ -0,0 +1,901 @@
"""Seal RAVNOVES004TREE mixed-route review into the existing vegetation LAB."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import shutil
import struct
import tarfile
import tempfile
import zipfile
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
from PIL import Image, ImageDraw
from k1link.compute.jobs import validate_camera_compute_job
from k1link.laboratory.vegetation_shadow_lab import (
LAB_SCHEMA,
RESULT_PREFIX,
VegetationShadowLabError,
canonical_json,
sha256_path,
)
REVIEW_SCHEMA = "missioncore.mixed-route-review-pack/v1"
DDRNET_SCHEMA = "missioncore.mixed-route-ddrnet-islands/v1"
TGS_SCHEMA = "missioncore.mixed-route-tgs-result/v1"
FRAME_COUNT = 10
PHASES = (
"rural",
"rural",
"rural",
"rural",
"rural",
"transition",
"urban",
"urban",
"urban",
"urban",
)
TGS_COLORS = {
0: (5, 7, 9),
1: (132, 188, 86),
2: (235, 112, 122),
3: (150, 154, 163),
}
FULL_ROUTE_SOURCE_ID = "RAVNOVES004TREE"
FULL_ROUTE_FRAME_COUNT = 6830
FULL_ROUTE_JOB_ID = "recorded-camera-eb2783c5480d56bda07c8af0"
FULL_ROUTE_INPUT_SHA256 = (
"eb2783c5480d56bda07c8af008dff5344d19dc550ef70fe2075d6f098f7cc715"
)
FULL_ROUTE_STREAM_SHA256 = (
"e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac"
)
def _read_json(path: Path, label: str) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8-sig"))
except (OSError, json.JSONDecodeError) as exc:
raise VegetationShadowLabError(f"{label} is invalid") from exc
if not isinstance(value, dict):
raise VegetationShadowLabError(f"{label} must be an object")
return value
def _artifact(
source: Path,
staging: Path,
relative: str,
artifacts: list[dict[str, object]],
*,
role: str,
media_type: str,
) -> dict[str, object]:
if source.is_symlink() or not source.is_file():
raise VegetationShadowLabError(f"mixed-route artifact is unavailable: {relative}")
target = staging / relative
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
shutil.copyfile(source, target)
descriptor = {
"role": role,
"path": relative,
"byte_length": target.stat().st_size,
"sha256": sha256_path(target),
"media_type": media_type,
}
artifacts.append(descriptor)
return descriptor
def _image_proof(descriptor: dict[str, object]) -> dict[str, object]:
return {"path": descriptor["path"], "sha256": descriptor["sha256"]}
def _mask_archive_descriptor(
path: Path,
relative: str,
artifacts: list[dict[str, object]],
*,
role: str,
) -> dict[str, object]:
descriptor = {
"role": role,
"path": relative,
"byte_length": path.stat().st_size,
"sha256": sha256_path(path),
"media_type": "application/zip",
}
artifacts.append(descriptor)
return descriptor
def _repack_eomt_masks(source: Path, destination: Path, frame_count: int) -> None:
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
expected = [f"semantic-masks/frame-{sequence + 1:06d}.png" for sequence in range(frame_count)]
try:
with (
tarfile.open(source, mode="r:gz") as archive,
zipfile.ZipFile(
destination,
mode="x",
compression=zipfile.ZIP_STORED,
allowZip64=True,
) as output,
):
members = [member for member in archive.getmembers() if member.isfile()]
if [member.name.removeprefix("./") for member in members] != expected:
raise VegetationShadowLabError("full-route EoMT mask sequence changed")
for member, expected_name in zip(members, expected, strict=True):
if member.size < 8 or member.size > 1024 * 1024:
raise VegetationShadowLabError("full-route EoMT mask size changed")
stream = archive.extractfile(member)
if stream is None:
raise VegetationShadowLabError("full-route EoMT mask is unavailable")
output.writestr(
f"masks/{Path(expected_name).name}",
stream.read(),
)
except (OSError, tarfile.TarError, zipfile.BadZipFile) as exc:
destination.unlink(missing_ok=True)
raise VegetationShadowLabError("full-route EoMT archive is invalid") from exc
def _validate_zip_masks(path: Path, frame_count: int) -> None:
expected = [f"masks/frame-{sequence + 1:06d}.png" for sequence in range(frame_count)]
try:
with zipfile.ZipFile(path) as archive:
members = archive.infolist()
if (
[member.filename for member in members] != expected
or any(
member.is_dir() or member.file_size < 8 or member.file_size > 1024 * 1024
for member in members
)
):
raise VegetationShadowLabError("full-route semantic mask sequence changed")
except (OSError, zipfile.BadZipFile) as exc:
raise VegetationShadowLabError("full-route semantic archive is invalid") from exc
def _full_route_frame_times(media: dict[str, Any], frame_count: int) -> list[int]:
epochs = media.get("epochs")
start = media.get("timeline_start_seconds")
end = media.get("timeline_end_seconds")
if (
not isinstance(epochs, list)
or len(epochs) != 1
or not isinstance(start, (int, float))
or not isinstance(end, (int, float))
):
raise VegetationShadowLabError("recorded media timeline changed")
epoch = epochs[0]
segments = epoch.get("segments") if isinstance(epoch, dict) else None
if not isinstance(segments, list) or len(segments) != frame_count:
raise VegetationShadowLabError("recorded media segment count changed")
starts = [float(start)]
previous_end = 0.0
for sequence, raw in enumerate(segments, start=1):
if (
not isinstance(raw, dict)
or raw.get("sequence") != sequence
or not isinstance(raw.get("end_time_seconds"), (int, float))
or float(raw["end_time_seconds"]) <= previous_end
):
raise VegetationShadowLabError("recorded media segment timeline changed")
if sequence < frame_count:
starts.append(float(start) + float(raw["end_time_seconds"]))
previous_end = float(raw["end_time_seconds"])
if abs((float(start) + previous_end) - float(end)) > 0.001:
raise VegetationShadowLabError("recorded media duration changed")
return [round(value * 1_000_000_000) for value in starts]
def _eomt_taxonomy(profile: dict[str, Any]) -> dict[str, object]:
taxonomy = profile.get("target_taxonomy")
if not isinstance(taxonomy, dict) or set(taxonomy) != {str(index) for index in range(16)}:
raise VegetationShadowLabError("EoMT target taxonomy changed")
classes = []
for class_id in range(16):
digest = hashlib.sha256(f"mission-core-segment-{class_id}".encode()).digest()
classes.append(
{
"class_id": class_id,
"label": taxonomy[str(class_id)],
"color_rgb": [64 + digest[index] % 176 for index in range(3)],
"disposition": "undefined" if class_id == 0 else "prediction",
}
)
return {
"schema_version": "missioncore.recorded-eomt-taxonomy/v1",
"classes": classes,
}
def _render_tgs_costmaps(tgs_root: Path, destination: Path) -> list[Path]:
result = _read_json(tgs_root / "result.json", "mixed-route TGS result")
evidence = result.get("evidence")
costmap = result.get("costmap")
if (
result.get("schema_version") != TGS_SCHEMA
or result.get("status") != "passed-review-only"
or not isinstance(evidence, dict)
or not isinstance(costmap, dict)
or result.get("summary", {}).get("frame_count") != FRAME_COUNT
or result.get("authority", {}).get("actuation_allowed") is not False
):
raise VegetationShadowLabError("mixed-route TGS contract changed")
evidence_path = tgs_root / str(evidence.get("path"))
if (
not evidence_path.is_file()
or evidence.get("bytes") != evidence_path.stat().st_size
or evidence.get("sha256") != sha256_path(evidence_path)
):
raise VegetationShadowLabError("mixed-route TGS evidence changed")
with np.load(evidence_path, allow_pickle=False) as archive:
centers = archive["costmap_cell_centers_xy_m"]
states = archive["causal_rolling_1s_costmap_states"]
if centers.shape != (2244, 2) or states.shape != (FRAME_COUNT, 2244):
raise VegetationShadowLabError("mixed-route TGS costmap shape changed")
radius = float(costmap["radius_m"])
cell_size = float(costmap["cell_size_m"])
size = 600
scale = size / (radius * 2.0)
outputs: list[Path] = []
destination.mkdir(mode=0o700, parents=True, exist_ok=True)
for slot in range(FRAME_COUNT):
image = Image.new("RGB", (size, size), TGS_COLORS[0])
draw = ImageDraw.Draw(image)
half = cell_size * scale / 2.0
for center, state in zip(centers, states[slot], strict=True):
x = (float(center[0]) + radius) * scale
y = (radius - float(center[1])) * scale
draw.rectangle((x - half, y - half, x + half, y + half), fill=TGS_COLORS[int(state)])
rover_w = 0.8 * scale
rover_l = 1.0 * scale
cx = size / 2.0
cy = size / 2.0
draw.rectangle(
(cx - rover_w / 2, cy - rover_l / 2, cx + rover_w / 2, cy + rover_l / 2),
outline=(255, 255, 255),
width=3,
)
path = destination / f"frame-{slot + 1:06d}.png"
image.save(path, format="PNG", optimize=True)
outputs.append(path)
return outputs
def seal_mixed_route_vegetation_review(
*,
base_lab_root: Path,
review_pack_root: Path,
eomt_root: Path,
ddrnet_root: Path,
tgs_root: Path,
output_root: Path,
) -> Path:
base_root = base_lab_root.resolve(strict=True)
base = _read_json(base_root / "result.json", "base vegetation LAB")
base_identity = base.get("identity")
if (
base.get("schema_version") != LAB_SCHEMA
or not isinstance(base_identity, dict)
or hashlib.sha256(canonical_json(base_identity)).hexdigest()
!= base.get("identity_sha256")
or base.get("result_id") != base_root.name
or not base_root.name.startswith(RESULT_PREFIX)
or base.get("authority", {}).get("commands_enabled") is not False
):
raise VegetationShadowLabError("base vegetation LAB proof changed")
pack_root = review_pack_root.resolve(strict=True)
pack = _read_json(pack_root / "manifest.json", "mixed-route review pack")
timeline_path = pack_root / str(pack.get("timeline", {}).get("path"))
if (
pack.get("schema_version") != REVIEW_SCHEMA
or pack.get("frame_count") != FRAME_COUNT
or pack.get("identity", {}).get("session_id") != "20260828T130511Z_viewer_live"
or pack.get("identity", {}).get("ground_truth") is not False
or not timeline_path.is_file()
or pack.get("timeline", {}).get("sha256") != sha256_path(timeline_path)
):
raise VegetationShadowLabError("mixed-route review pack changed")
timeline = [json.loads(line) for line in timeline_path.read_text(encoding="utf-8").splitlines()]
if len(timeline) != FRAME_COUNT:
raise VegetationShadowLabError("mixed-route timeline is incomplete")
eomt = _read_json(eomt_root / "run-report.partial.json", "mixed-route EoMT result")
ddrnet = _read_json(ddrnet_root / "result.json", "mixed-route DDRNet result")
tgs = _read_json(tgs_root / "result.json", "mixed-route TGS result")
if (
eomt.get("input", {}).get("frames_admitted") != FRAME_COUNT
or eomt.get("metrics", {}).get("frames_processed") != FRAME_COUNT
or eomt.get("ground_truth") is not False
or ddrnet.get("schema_version") != DDRNET_SCHEMA
or ddrnet.get("source", {}).get("pack_id") != pack["pack_id"]
or len(ddrnet.get("frames", [])) != FRAME_COUNT
or ddrnet.get("authority", {}).get("candidate_accepted") is not False
or tgs.get("schema_version") != TGS_SCHEMA
or tgs.get("source", {}).get("review_pack_id") != pack["pack_id"]
or tgs.get("summary", {}).get("frame_count") != FRAME_COUNT
):
raise VegetationShadowLabError("mixed-route model identities differ")
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = Path(tempfile.mkdtemp(prefix=".mixed-route-vegetation-", dir=output_root))
artifacts: list[dict[str, object]] = []
try:
tgs_images = _render_tgs_costmaps(tgs_root, temporary / ".tgs-render")
cases: list[dict[str, object]] = []
tgs_anchors = {
int(row["slot"]): row
for row in tgs["anchors"]
if row.get("profile_id") == "causal_rolling_1s"
}
for slot, row in enumerate(timeline):
case_id = f"route-{slot + 1:02d}"
relative_root = f"route-review/{case_id}"
source_descriptor = _artifact(
pack_root / "frames" / f"frame-{slot + 1:06d}.png",
temporary,
f"{relative_root}/source.png",
artifacts,
role="mixed-route-source-frame",
media_type="image/png",
)
city_descriptor = _artifact(
eomt_root / "overlay-frames" / f"frame-{slot + 1:06d}.png",
temporary,
f"{relative_root}/city.png",
artifacts,
role="mixed-route-eomt-overlay",
media_type="image/png",
)
vegetation_descriptor = _artifact(
ddrnet_root / "overlay-frames" / f"frame-{slot + 1:06d}.png",
temporary,
f"{relative_root}/vegetation.png",
artifacts,
role="mixed-route-ddrnet-overlay",
media_type="image/png",
)
tgs_descriptor = _artifact(
tgs_images[slot],
temporary,
f"{relative_root}/tgs.png",
artifacts,
role="mixed-route-tgs-costmap",
media_type="image/png",
)
anchor = tgs_anchors[slot]
cases.append(
{
"case_id": case_id,
"phase": PHASES[slot],
"source_sequence": int(row["source_sequence"]),
"session_seconds": float(row["session_seconds"]),
"assets": {
"source": _image_proof(source_descriptor),
"city": _image_proof(city_descriptor),
"vegetation": _image_proof(vegetation_descriptor),
"tgs": _image_proof(tgs_descriptor),
},
"tgs": {
"ground_cells": int(anchor["ground_cell_count"]),
"occupied_cells": int(anchor["nonground_cell_count"]),
"rejected_cells": int(anchor["rejected_cell_count"]),
"unobserved_cells": int(anchor["unobserved_cell_count"]),
},
}
)
shutil.rmtree(temporary / ".tgs-render")
proofs = {}
for key, path in (
("base", base_root / "result.json"),
("eomt", eomt_root / "run-report.partial.json"),
("ddrnet", ddrnet_root / "result.json"),
("tgs", tgs_root / "result.json"),
):
descriptor = _artifact(
path,
temporary,
f"proofs/{key}.json",
artifacts,
role="mixed-route-proof",
media_type="application/json",
)
proofs[key] = _image_proof(descriptor)
_artifact(
tgs_root / str(tgs["evidence"]["path"]),
temporary,
"proofs/tgs-evidence.npz",
artifacts,
role="mixed-route-tgs-evidence",
media_type="application/x-npz",
)
route_review = {
"source_id": "RAVNOVES004TREE",
"session_id": "20260828T130511Z_viewer_live",
"pack_id": pack["pack_id"],
"frame_count": FRAME_COUNT,
"ground_truth": False,
"selection_policy": "same-scene-camera-lidar-aligned-review-islands/v1",
"models": {
"city": {
"name": "EoMT Cityscapes",
"frames": FRAME_COUNT,
"inference_fps": eomt["metrics"]["inference_frames_per_second"],
"end_to_end_p95_ms": eomt["metrics"]["latency_ms"]["end_to_end_ms"]["p95"],
},
"vegetation": {
"name": ddrnet["candidate"]["loaded_model_name"],
"result_id": ddrnet["result_id"],
"frames": FRAME_COUNT,
"latency_p95_ms": ddrnet["timing"]["latency_ms_p95"],
},
"tgs": {
"name": "TRAVEL/TGS causal rolling 1 s",
"frames": FRAME_COUNT,
"latency_p95_ms": tgs["timing"]["wall_seconds_p95"] * 1000.0,
"cell_size_m": tgs["costmap"]["cell_size_m"],
"radius_m": tgs["costmap"]["radius_m"],
},
},
"cases": cases,
"proofs": proofs,
"limitations": [
"Ten aligned review islands are not a complete route timeline.",
"RAVNOVES004TREE has no manual truth.",
"DDRNet vegetation subtypes remain visually noisy and are not planner authority.",
"TGS does not prove ditch or negative-obstacle detection.",
"People and vehicles require an independent fail-safe detector and STOP path.",
],
}
authority = {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
"camera_semantics_can_clear_rigid_geometry": False,
}
identity = {
"lab_id": "lab-v1-vegetation-mission-policy",
"base_result_id": base["result_id"],
"selected_candidate": base_identity["selected_candidate"],
"candidate_metrics": base_identity["candidate_metrics"],
"source": {
"shadow_session": "RAVNOVES004TREE",
"shadow_camera": "sensor.camera.right",
"shadow_frame_count": FRAME_COUNT,
"video_shadow_frame_count": 0,
},
"route_review": route_review,
"authority": authority,
}
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
result_id = f"{RESULT_PREFIX}{identity_sha256}"
manifest = {
"schema_version": LAB_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"created_at_utc": datetime.now(UTC).isoformat(),
"ground_truth": False,
"status": "visual-shadow-ready-policy-not-authorized",
"identity": identity,
"source": identity["source"],
"route_video": None,
"route_review": route_review,
"method": {
"completeness": "bounded-review-islands",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-eomt-ddrnet-causal-tgs-review/v1",
},
"metrics": {"candidates": base["metrics"]["candidates"]},
"decision": {
"selected_candidate": base_identity["selected_candidate"],
"visual_shadow_ready": True,
"full_video_shadow_ready": False,
"mission_policy_ready_for_configuration": True,
"multilayer_policy_review_ready": True,
"navigation_accepted": False,
"production_accepted": False,
},
"limitations": route_review["limitations"],
"authority": authority,
"catalogs": {"goose": [], "ravnoves": []},
"artifacts": artifacts,
}
(temporary / "result.json").write_bytes(canonical_json(manifest) + b"\n")
destination = output_root / result_id
if destination.exists():
raise VegetationShadowLabError("immutable mixed-route LAB result already exists")
os.replace(temporary, destination)
return destination
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
def seal_mixed_route_full_video_review(
*,
base_lab_root: Path,
job_root: Path,
recorded_media_preparation_path: Path,
eomt_root: Path,
eomt_profile_path: Path,
ddrnet_root: Path,
output_root: Path,
) -> Path:
"""Publish the complete 004 city/nature pass in the existing M4.7 LAB."""
base_root = base_lab_root.resolve(strict=True)
base = _read_json(base_root / "result.json", "base vegetation LAB")
base_identity = base.get("identity")
if (
base.get("schema_version") != LAB_SCHEMA
or not isinstance(base_identity, dict)
or hashlib.sha256(canonical_json(base_identity)).hexdigest()
!= base.get("identity_sha256")
or base.get("result_id") != base_root.name
or not base_root.name.startswith(RESULT_PREFIX)
or base.get("authority", {}).get("commands_enabled") is not False
):
raise VegetationShadowLabError("base vegetation LAB proof changed")
job = validate_camera_compute_job(job_root)
if (
job.job_id != FULL_ROUTE_JOB_ID
or job.input_sha256 != FULL_ROUTE_INPUT_SHA256
or job.session_id != "20260828T130511Z_viewer_live"
or job.source_id != "sensor.camera.right"
or job.segment_count != FULL_ROUTE_FRAME_COUNT
):
raise VegetationShadowLabError("full-route camera job changed")
eomt = _read_json(eomt_root / "result.json", "full-route EoMT result")
eomt_report = _read_json(eomt_root / "run-report.json", "full-route EoMT report")
decode_repair = _read_json(
eomt_root / "decode-repair.json",
"full-route video decode repair",
)
eomt_input = eomt_report.get("input")
eomt_metrics = eomt_report.get("metrics")
if (
eomt.get("schema_version") != "missioncore.recorded-perception-result/v2"
or eomt.get("ground_truth") is not False
or eomt.get("frames_processed") != FULL_ROUTE_FRAME_COUNT
or not isinstance(eomt_input, dict)
or eomt_input.get("job_id") != job.job_id
or eomt_input.get("input_sha256") != job.input_sha256
or eomt_input.get("frames_admitted") != FULL_ROUTE_FRAME_COUNT
or not isinstance(eomt_metrics, dict)
or eomt_metrics.get("frames_processed") != FULL_ROUTE_FRAME_COUNT
):
raise VegetationShadowLabError("full-route EoMT contract changed")
if (
decode_repair.get("schema_version")
!= "missioncore.recorded-video-decode-repair/v1"
or decode_repair.get("decoder") != "ffmpeg-h264_cuvid-output-corrupt"
or decode_repair.get("packets_requested") != FULL_ROUTE_FRAME_COUNT
or decode_repair.get("frames_decoded") != FULL_ROUTE_FRAME_COUNT - 1
or decode_repair.get("repaired_frame_count") != 1
or decode_repair.get("repairs")
!= [
{
"sequence": 6092,
"packet_pts": 55656450,
"method": "duplicate-previous-decoded-frame",
}
]
):
raise VegetationShadowLabError("full-route video decode repair changed")
eomt_artifacts = {
item.get("kind"): item
for item in eomt.get("artifacts", [])
if isinstance(item, dict)
}
eomt_archive_proof = eomt_artifacts.get("panoptic-mask-archive")
if not isinstance(eomt_archive_proof, dict):
raise VegetationShadowLabError("full-route EoMT mask proof is missing")
eomt_archive = eomt_root / str(eomt_archive_proof.get("path"))
if (
not eomt_archive.is_file()
or eomt_archive.stat().st_size != eomt_archive_proof.get("byte_length")
or sha256_path(eomt_archive) != eomt_archive_proof.get("sha256")
):
raise VegetationShadowLabError("full-route EoMT mask proof changed")
ddrnet = _read_json(ddrnet_root / "result.json", "full-route DDRNet result")
ddrnet_decode_repair = _read_json(
ddrnet_root / "decode-repair.json",
"full-route DDRNet video decode repair",
)
ddrnet_source = ddrnet.get("source")
ddrnet_video = ddrnet.get("video_semantics")
if (
ddrnet.get("schema_version") != "missioncore.lab-v1-goose-vegetation-run/v1"
or ddrnet.get("mode") != "ravnoves-video"
or ddrnet.get("candidate", {}).get("candidate_key") != "ddrnet"
or not isinstance(ddrnet_source, dict)
or ddrnet_source.get("source_id")
!= f"{FULL_ROUTE_SOURCE_ID}/right-{FULL_ROUTE_STREAM_SHA256}"
or ddrnet_source.get("input_count") != FULL_ROUTE_FRAME_COUNT
or ddrnet_source.get("ground_truth_available") is not False
or not isinstance(ddrnet_video, dict)
or ddrnet_video.get("base_m4_result_id") is not None
or ddrnet.get("authority", {}).get("navigation_accepted") is not False
or ddrnet.get("authority", {}).get("actuation_accepted") is not False
):
raise VegetationShadowLabError("full-route DDRNet contract changed")
if ddrnet_decode_repair != decode_repair:
raise VegetationShadowLabError("full-route model decoders disagree")
ddrnet_archive_proof = ddrnet_video.get("mask_archive")
ddrnet_taxonomy = ddrnet_video.get("taxonomy")
if (
not isinstance(ddrnet_archive_proof, dict)
or ddrnet_archive_proof.get("frame_count") != FULL_ROUTE_FRAME_COUNT
or not isinstance(ddrnet_taxonomy, dict)
):
raise VegetationShadowLabError("full-route DDRNet mask proof changed")
ddrnet_archive = ddrnet_root / str(ddrnet_archive_proof.get("path"))
if (
not ddrnet_archive.is_file()
or ddrnet_archive.stat().st_size != ddrnet_archive_proof.get("byte_length")
or sha256_path(ddrnet_archive) != ddrnet_archive_proof.get("sha256")
):
raise VegetationShadowLabError("full-route DDRNet archive changed")
_validate_zip_masks(ddrnet_archive, FULL_ROUTE_FRAME_COUNT)
media_document = _read_json(
recorded_media_preparation_path.resolve(strict=True),
"recorded media preparation",
)
media = media_document.get("manifest")
if (
media_document.get("schema_version") != "missioncore.recorded-media-preparation/v3"
or media_document.get("session_id") != job.session_id
or media_document.get("artifact_id") != "recorded-video-6a3945242828a038"
or media_document.get("checksum_sha256")
!= "557e61f2839140dc9f97b5aea855c576b0616573080dff5d2852ab1df0558665"
or not isinstance(media, dict)
or media.get("source_id") != "recorded.camera.6a3945242828a038"
or media.get("generation_sha256")
!= "b073ea1e7babf1c77a664e1a5b95e3702d0e05b0e34c1e85a7c67a6f8b392ded"
or media.get("byte_length") != 551674491
or media.get("timeline_start_seconds") != job.timeline_start_seconds
or media.get("timeline_end_seconds") != job.timeline_end_seconds
or media.get("synchronization") != "host-arrival-best-effort"
):
raise VegetationShadowLabError("recorded media preparation changed")
frame_times_ns = _full_route_frame_times(media, FULL_ROUTE_FRAME_COUNT)
eomt_profile = _read_json(eomt_profile_path.resolve(strict=True), "EoMT profile")
eomt_taxonomy = _eomt_taxonomy(eomt_profile)
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = Path(tempfile.mkdtemp(prefix=".mixed-route-full-video-", dir=output_root))
artifacts: list[dict[str, object]] = []
try:
eomt_destination = temporary / "video" / "eomt-semantic-masks.zip"
_repack_eomt_masks(eomt_archive, eomt_destination, FULL_ROUTE_FRAME_COUNT)
_validate_zip_masks(eomt_destination, FULL_ROUTE_FRAME_COUNT)
eomt_descriptor = _mask_archive_descriptor(
eomt_destination,
"video/eomt-semantic-masks.zip",
artifacts,
role="full-route-eomt-semantic-mask-archive",
)
ddrnet_descriptor = _artifact(
ddrnet_archive,
temporary,
"video/ddrnet-semantic-masks.zip",
artifacts,
role="full-route-ddrnet-semantic-mask-archive",
media_type="application/zip",
)
_validate_zip_masks(
temporary / "video" / "ddrnet-semantic-masks.zip",
FULL_ROUTE_FRAME_COUNT,
)
timeline_destination = temporary / "video" / "frame-source-times-ns.bin"
timeline_destination.write_bytes(
struct.pack(f"<{FULL_ROUTE_FRAME_COUNT}Q", *frame_times_ns)
)
timeline_descriptor = {
"role": "full-route-frame-timeline",
"path": "video/frame-source-times-ns.bin",
"byte_length": timeline_destination.stat().st_size,
"sha256": sha256_path(timeline_destination),
"media_type": "application/octet-stream",
}
artifacts.append(timeline_descriptor)
proof_descriptors: dict[str, dict[str, object]] = {}
for key, path in (
("base", base_root / "result.json"),
("job", job.manifest_path),
("media", recorded_media_preparation_path.resolve(strict=True)),
("eomt", eomt_root / "result.json"),
("eomt_report", eomt_root / "run-report.json"),
("decode_repair", eomt_root / "decode-repair.json"),
("ddrnet", ddrnet_root / "result.json"),
("ddrnet_decode_repair", ddrnet_root / "decode-repair.json"),
):
descriptor = _artifact(
path,
temporary,
f"proofs/{key}.json",
artifacts,
role="full-route-proof",
media_type="application/json",
)
proof_descriptors[key] = _image_proof(descriptor)
full_route = {
"source_id": FULL_ROUTE_SOURCE_ID,
"session_id": job.session_id,
"source_job_id": job.job_id,
"source_job_input_sha256": job.input_sha256,
"source_stream_sha256": FULL_ROUTE_STREAM_SHA256,
"recorded_media_source_id": media["source_id"],
"recorded_media_generation_sha256": media["generation_sha256"],
"frame_count": FULL_ROUTE_FRAME_COUNT,
"width": 800,
"height": 600,
"timeline_start_seconds": job.timeline_start_seconds,
"timeline_end_seconds": job.timeline_end_seconds,
"timeline": {
"path": timeline_descriptor["path"],
"sha256": timeline_descriptor["sha256"],
"byte_length": timeline_descriptor["byte_length"],
"encoding": "uint64-le-nanoseconds",
"frame_count": FULL_ROUTE_FRAME_COUNT,
},
"ground_truth": False,
"decode_repair": {
"repaired_frame_count": 1,
"sequence": 6092,
"method": "duplicate-previous-decoded-frame",
"proofs": {
"eomt": proof_descriptors["decode_repair"],
"ddrnet": proof_descriptors["ddrnet_decode_repair"],
},
},
"layers": {
"city": {
"name": "EoMT Cityscapes",
"result_id": eomt["result_id"],
"frame_count": FULL_ROUTE_FRAME_COUNT,
"taxonomy": eomt_taxonomy,
"mask_archive": {
"path": eomt_descriptor["path"],
"sha256": eomt_descriptor["sha256"],
"byte_length": eomt_descriptor["byte_length"],
},
"inference_fps": eomt_metrics["inference_frames_per_second"],
"latency_p95_ms": eomt_metrics["latency_ms"]["end_to_end_ms"]["p95"],
"peak_reserved_vram_bytes": int(
float(eomt_metrics["cuda_peak_memory_reserved_mib"]) * 1024 * 1024
),
},
"vegetation": {
"name": ddrnet["candidate"]["loaded_model_name"],
"result_id": ddrnet["result_id"],
"frame_count": FULL_ROUTE_FRAME_COUNT,
"taxonomy": ddrnet_taxonomy,
"mask_archive": {
"path": ddrnet_descriptor["path"],
"sha256": ddrnet_descriptor["sha256"],
"byte_length": ddrnet_descriptor["byte_length"],
},
"inference_fps": ddrnet["timing"]["throughput_fps_from_mean_inference"],
"latency_p95_ms": ddrnet["timing"]["latency_ms_p95"],
"peak_reserved_vram_bytes": ddrnet["resource"]["peak_reserved_vram_bytes"],
},
},
"proofs": proof_descriptors,
"limitations": [
"RAVNOVES004TREE has no manual route truth.",
"One corrupt H.264 packet at sequence 6092 was represented by the previous decoded frame; the repair is sealed as evidence.",
"EoMT and DDRNet were executed sequentially, not as a concurrent realtime stack.",
"DDRNet vegetation subtypes remain prediction-only and are not planner authority.",
"This full-video pass does not add full-route TGS, ditch or negative-obstacle proof.",
"People and vehicles still require an independent fail-safe detector and STOP path.",
],
}
authority = {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"actuation_accepted": False,
"camera_semantics_can_clear_rigid_geometry": False,
}
identity = {
"lab_id": "lab-v1-vegetation-mission-policy",
"base_result_id": base["result_id"],
"selected_candidate": base_identity["selected_candidate"],
"candidate_metrics": base_identity["candidate_metrics"],
"source": {
"shadow_session": FULL_ROUTE_SOURCE_ID,
"shadow_camera": job.source_id,
"shadow_frame_count": FULL_ROUTE_FRAME_COUNT,
"video_shadow_frame_count": FULL_ROUTE_FRAME_COUNT,
},
"route_full_review": full_route,
"authority": authority,
}
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
result_id = f"{RESULT_PREFIX}{identity_sha256}"
manifest = {
"schema_version": LAB_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"created_at_utc": datetime.now(UTC).isoformat(),
"ground_truth": False,
"status": "visual-shadow-ready-policy-not-authorized",
"identity": identity,
"source": identity["source"],
"route_video": None,
"route_review": None,
"route_full_review": full_route,
"method": {
"completeness": "complete",
"execution_class": "ai-inference",
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
},
"metrics": {"candidates": base["metrics"]["candidates"]},
"decision": {
"selected_candidate": base_identity["selected_candidate"],
"visual_shadow_ready": True,
"full_video_shadow_ready": True,
"mission_policy_ready_for_configuration": True,
"multilayer_policy_review_ready": True,
"navigation_accepted": False,
"production_accepted": False,
},
"limitations": full_route["limitations"],
"authority": authority,
"catalogs": {"goose": [], "ravnoves": []},
"artifacts": artifacts,
}
(temporary / "result.json").write_bytes(canonical_json(manifest) + b"\n")
destination = output_root / result_id
if destination.exists():
raise VegetationShadowLabError("immutable full-route LAB result already exists")
os.replace(temporary, destination)
return destination
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-lab-root", type=Path, required=True)
parser.add_argument("--review-pack-root", type=Path, required=True)
parser.add_argument("--eomt-root", type=Path, required=True)
parser.add_argument("--ddrnet-root", type=Path, required=True)
parser.add_argument("--tgs-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
print(
seal_mixed_route_vegetation_review(
base_lab_root=args.base_lab_root,
review_pack_root=args.review_pack_root,
eomt_root=args.eomt_root,
ddrnet_root=args.ddrnet_root,
tgs_root=args.tgs_root,
output_root=args.output_root,
)
)
if __name__ == "__main__":
main()
@@ -0,0 +1,139 @@
"""Seal a benchmark-only vegetation result into its archival LAB namespace."""
from __future__ import annotations
import argparse
import copy
import hashlib
import json
import shutil
import tempfile
from pathlib import Path, PurePosixPath
from typing import Any, Final
from k1link.laboratory.evidence_registry import LaboratoryEvidenceDefinition
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA
_SOURCE_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-shadow",
runtime_relative_root=PurePosixPath("lab-v1-vegetation/results"),
result_id_prefix="lab-v1-vegetation-shadow",
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
_ARCHIVE_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-benchmark",
runtime_relative_root=PurePosixPath("lab-v1-vegetation-benchmark/results"),
result_id_prefix="lab-v1-vegetation-benchmark",
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
class VegetationBenchmarkArchiveError(ValueError):
"""The source result is not a valid benchmark-only immutable result."""
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
def _object(value: object, label: str) -> dict[str, Any]:
if not isinstance(value, dict):
raise VegetationBenchmarkArchiveError(f"{label} is invalid")
return value
def seal_vegetation_benchmark_archive(
*,
source_result_root: Path,
output_root: Path,
) -> Path:
source = source_result_root.resolve(strict=True)
verify_laboratory_evidence_result(_SOURCE_DEFINITION, source)
manifest = _object(
json.loads((source / "result.json").read_text("utf-8")),
"source result",
)
if manifest.get("route_video") is not None:
raise VegetationBenchmarkArchiveError("benchmark archive source contains route video")
artifacts = manifest.get("artifacts")
if not isinstance(artifacts, list):
raise VegetationBenchmarkArchiveError("source artifacts are invalid")
identity = copy.deepcopy(_object(manifest.get("identity"), "source identity"))
identity.update(
{
"lab_id": "lab-v1-vegetation-benchmark-archive",
"archived_from_result_id": source.name,
}
)
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"lab-v1-vegetation-benchmark-{identity_sha256}"
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
destination = output_root / result_id
if destination.exists():
verify_laboratory_evidence_result(_ARCHIVE_DEFINITION, destination)
return destination
temporary = Path(tempfile.mkdtemp(prefix=".vegetation-benchmark-", dir=output_root))
try:
for raw in artifacts:
descriptor = _object(raw, "artifact descriptor")
relative_text = descriptor.get("path")
if not isinstance(relative_text, str):
raise VegetationBenchmarkArchiveError("artifact path is invalid")
relative = PurePosixPath(relative_text)
if relative.is_absolute() or any(part in {"", ".", ".."} for part in relative.parts):
raise VegetationBenchmarkArchiveError("artifact path is unsafe")
source_path = source.joinpath(*relative.parts)
destination_path = temporary.joinpath(*relative.parts)
destination_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
shutil.copyfile(source_path, destination_path)
archived = copy.deepcopy(manifest)
archived.update(
{
"result_id": result_id,
"identity": identity,
"identity_sha256": identity_sha256,
"archived_from_result_id": source.name,
}
)
(temporary / "result.json").write_bytes(_canonical_json(archived) + b"\n")
temporary.rename(destination)
verify_laboratory_evidence_result(_ARCHIVE_DEFINITION, destination)
return destination
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--source-result-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
print(
seal_vegetation_benchmark_archive(
source_result_root=args.source_result_root,
output_root=args.output_root,
)
)
if __name__ == "__main__":
main()
__all__ = [
"VegetationBenchmarkArchiveError",
"seal_vegetation_benchmark_archive",
]
@@ -112,6 +112,7 @@ def seal_vegetation_policy_review(
mission_policy_path: Path,
provider_label_map_path: Path,
m49_tgs_full_shadow_root: Path,
valid_fov_mask_path: Path,
output_root: Path,
created_at_utc: str | None = None,
) -> Path:
@@ -142,6 +143,7 @@ def seal_vegetation_policy_review(
raise VegetationPolicyReviewError("fine mask archive identity changed")
raw_archive_path = base_root / "video" / "ddrnet-semantic-masks.zip"
fine_taxonomy = _object(base_route.get("taxonomy"), "fine taxonomy")
valid_fov_source = valid_fov_mask_path.resolve(strict=True)
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = Path(tempfile.mkdtemp(prefix=".lab-v1-policy-", dir=output_root))
@@ -152,11 +154,23 @@ def seal_vegetation_policy_review(
artifacts=base.get("artifacts"),
)
policy_archive = temporary / "video" / "coarse-material-policy-masks.zip"
valid_fov_destination = temporary / "video" / "valid-fov-mask.png"
valid_fov_destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
shutil.copyfile(valid_fov_source, valid_fov_destination)
valid_fov_proof = {
"role": "route-camera-valid-fov-mask",
"path": "video/valid-fov-mask.png",
"byte_length": valid_fov_destination.stat().st_size,
"sha256": sha256_path(valid_fov_destination),
"media_type": "image/png",
}
artifacts.append(valid_fov_proof)
policy_counts = build_policy_mask_archive(
source_archive=raw_archive_path,
destination_archive=policy_archive,
fine_taxonomy=fine_taxonomy,
provider_label_map=provider_map,
valid_fov_mask=valid_fov_destination,
)
policy_archive_proof = {
"role": "route-coarse-material-mask-archive",
@@ -180,6 +194,11 @@ def seal_vegetation_policy_review(
"taxonomy": policy_taxonomy(),
"aggregate_prediction_pixels": policy_counts,
"linked_tgs_result_id": tgs.result_id,
"valid_fov": {
"mask_path": valid_fov_proof["path"],
"mask_sha256": valid_fov_proof["sha256"],
"outside_valid_fov_class_id": 9,
},
"policy": {
"profile_id": mission_policy["profile_id"],
"profile_sha256": sha256_path(mission_policy_path),
@@ -196,6 +215,7 @@ def seal_vegetation_policy_review(
"spatial_safety_veto_layer": "M4.9 full TGS gravity-local costmap",
"temporal_consensus_owner": "TGS causal rolling 1 s and metric obstacle tracks",
"camera_semantic_temporal_filter": "none",
"camera_valid_fov_filter": "sealed exact KB4 valid-FOV mask",
"reason": "No admitted TGS-to-camera pixel projection exists.",
},
}
@@ -238,7 +258,7 @@ def seal_vegetation_policy_review(
"Vegetation semantics never clears YOLOX, LiDAR, metric obstacle "
"or TGS vetoes."
),
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
"Pixels outside the exact KB4 valid FOV are transparent UNOBSERVED evidence.",
(
"TGS remains in gravity-local space; no uncalibrated pixel "
"projection is fabricated."
@@ -269,6 +289,7 @@ def main() -> None:
parser.add_argument("--mission-policy-path", type=Path, required=True)
parser.add_argument("--provider-label-map-path", type=Path, required=True)
parser.add_argument("--m49-tgs-full-shadow-root", type=Path, required=True)
parser.add_argument("--valid-fov-mask-path", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
print(seal_vegetation_policy_review(**vars(args)))
@@ -90,6 +90,14 @@ POLICY_CLASSES: Final = (
"material_class": "vegetation_unknown",
"evidence_state": "VEGETATION_UNKNOWN",
},
{
"class_id": 9,
"label": "OUTSIDE VALID FOV · NO SENSOR EVIDENCE",
"color_rgb": [0, 0, 0],
"disposition": "undefined",
"material_class": None,
"evidence_state": "UNOBSERVED",
},
)
_MATERIAL_TO_CLASS: Final = {
@@ -155,10 +163,18 @@ def build_policy_mask_archive(
destination_archive: Path,
fine_taxonomy: dict[str, object],
provider_label_map: dict[str, Any],
valid_fov_mask: Path,
) -> list[int]:
"""Map every fine mask to coarse evidence; safety vetoes remain separate layers."""
lut = fine_to_policy_lut(fine_taxonomy, provider_label_map)
try:
with Image.open(valid_fov_mask) as image:
valid_fov = np.asarray(image.convert("L"), dtype=np.uint8) > 0
except OSError as exc:
raise VegetationPolicyVideoError("valid-FOV mask is unreadable") from exc
if valid_fov.shape != (HEIGHT, WIDTH) or not np.any(valid_fov) or np.all(valid_fov):
raise VegetationPolicyVideoError("valid-FOV mask geometry is invalid")
counts = np.zeros(len(POLICY_CLASSES), dtype=np.int64)
destination_archive.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
try:
@@ -175,6 +191,7 @@ def build_policy_mask_archive(
f"fine mask {member} has shape {fine.shape}, expected {(HEIGHT, WIDTH)}"
)
coarse = lut[fine]
coarse[~valid_fov] = 9
counts += np.bincount(
coarse.reshape(-1),
minlength=len(POLICY_CLASSES),
+28 -1
View File
@@ -327,6 +327,7 @@ def seal_vegetation_shadow_lab(
mission_policy_path: Path | None = None,
provider_label_map_path: Path | None = None,
m49_tgs_full_shadow_root: Path | None = None,
valid_fov_mask_path: Path | None = None,
) -> Path:
roots = {
("ddrnet", "goose"): ddrnet_goose_root.resolve(),
@@ -349,6 +350,7 @@ def seal_vegetation_shadow_lab(
mission_policy_path,
provider_label_map_path,
m49_tgs_full_shadow_root,
valid_fov_mask_path,
)
if any(value is not None for value in policy_inputs) and not all(
value is not None for value in policy_inputs
@@ -385,6 +387,7 @@ def seal_vegetation_shadow_lab(
mission_policy_path is not None
and provider_label_map_path is not None
and m49_tgs_full_shadow_root is not None
and valid_fov_mask_path is not None
and route_video is not None
):
repository_root = mission_policy_path.resolve().parents[2]
@@ -543,13 +546,25 @@ def seal_vegetation_shadow_lab(
and linked_tgs_result_id is not None
and mission_policy_path is not None
and provider_label_map_path is not None
and valid_fov_mask_path is not None
):
policy_archive = temporary / "video" / "coarse-material-policy-masks.zip"
valid_fov_destination = temporary / "video" / "valid-fov-mask.png"
shutil.copyfile(valid_fov_mask_path.resolve(strict=True), valid_fov_destination)
valid_fov_descriptor = {
"role": "route-camera-valid-fov-mask",
"path": "video/valid-fov-mask.png",
"byte_length": valid_fov_destination.stat().st_size,
"sha256": sha256_path(valid_fov_destination),
"media_type": "image/png",
}
artifacts.append(valid_fov_descriptor)
policy_counts = build_policy_mask_archive(
source_archive=route_video_archive,
destination_archive=policy_archive,
fine_taxonomy=_object(route_video["taxonomy"], "fine video taxonomy"),
provider_label_map=provider_label_map,
valid_fov_mask=valid_fov_destination,
)
policy_descriptor = {
"role": "route-coarse-material-mask-archive",
@@ -571,6 +586,11 @@ def seal_vegetation_shadow_lab(
"taxonomy": policy_taxonomy(),
"aggregate_prediction_pixels": policy_counts,
"linked_tgs_result_id": linked_tgs_result_id,
"valid_fov": {
"mask_path": valid_fov_descriptor["path"],
"mask_sha256": valid_fov_descriptor["sha256"],
"outside_valid_fov_class_id": 9,
},
"policy": {
"profile_id": mission_policy["profile_id"],
"profile_sha256": sha256_path(mission_policy_path),
@@ -591,6 +611,7 @@ def seal_vegetation_shadow_lab(
"TGS causal rolling 1 s and metric obstacle tracks"
),
"camera_semantic_temporal_filter": "none",
"camera_valid_fov_filter": "sealed exact KB4 valid-FOV mask",
"reason": "No admitted TGS-to-camera pixel projection exists.",
},
}
@@ -680,7 +701,11 @@ def seal_vegetation_shadow_lab(
)
),
"Vegetation semantics never clears rigid LiDAR/TGS occupancy.",
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
(
"Pixels outside the exact KB4 valid FOV are transparent UNOBSERVED evidence."
if mission_policy is not None
else "Undefined pixels outside the 600x600 center crop remain fail-closed."
),
*(
[
(
@@ -723,6 +748,7 @@ def _parse_args() -> argparse.Namespace:
parser.add_argument("--mission-policy-path", type=Path)
parser.add_argument("--provider-label-map-path", type=Path)
parser.add_argument("--m49-tgs-full-shadow-root", type=Path)
parser.add_argument("--valid-fov-mask-path", type=Path)
return parser.parse_args()
@@ -739,6 +765,7 @@ def main() -> None:
mission_policy_path=args.mission_policy_path,
provider_label_map_path=args.provider_label_map_path,
m49_tgs_full_shadow_root=args.m49_tgs_full_shadow_root,
valid_fov_mask_path=args.valid_fov_mask_path,
)
print(destination)
@@ -767,6 +767,24 @@ def _discover_bundle_members(root: Path, entrypoint: str, source_format: str) ->
return _logical_path(logical, "descriptor member")
members = {entrypoint}
source_meshes: list[str] = []
for candidate in root.rglob("*"):
if candidate.is_symlink():
raise GaussianPipelineIntegrityError(
"Gaussian source bundle contains a symlink"
)
if not candidate.is_file():
continue
logical_path = candidate.relative_to(root).as_posix()
if is_xgrids_source_mesh_path(logical_path):
source_meshes.append(logical_path)
source_meshes.sort(key=lambda value: value.encode("utf-8"))
if len(source_meshes) > 1:
raise GaussianPipelineIntegrityError(
"Gaussian source bundle must contain at most one PLY mesh inside Mesh_Files"
)
if source_meshes:
members.add(source_meshes[0])
if source_format == "lcc":
members.update({related("index.bin"), related("data.bin")})
file_type = document.get("fileType")
@@ -810,6 +828,11 @@ def _discover_bundle_members(root: Path, entrypoint: str, source_format: str) ->
return members
def is_xgrids_source_mesh_path(logical_path: str) -> bool:
parts = logical_path.lower().replace("\\", "/").split("/")
return "mesh_files" in parts and parts[-1].endswith(".ply")
def _logical_path(value: str, label: str) -> str:
if (
not value
+19 -2
View File
@@ -26,6 +26,7 @@ from k1link.simulation.gaussian_pipeline_gateway import (
GaussianPipelineUnavailableError,
configured_gaussian_pipeline_gateway,
discover_gaussian_source_bundle,
is_xgrids_source_mesh_path,
)
PROJECT_SCHEMA: Final = "missioncore.simulation-project/v1"
@@ -610,6 +611,22 @@ class SimulationProjectService:
entrypoint=entrypoint,
source_format=source_format,
)
source_mesh_available = any(
is_xgrids_source_mesh_path(member.logical_path)
for member in source.members
)
collision_profile = (
{
"scene_type": project["scene_type"],
"seed_position": [0.0, 0.0, 0.0],
"capsule_height": 0.4,
"capsule_radius": 0.4,
"voxel_size": 0.05,
"mesh_shape": "source",
}
if source_mesh_available
else None
)
request = {
"schema_version": BUILD_REQUEST_SCHEMA,
"idempotency_key": f"missioncore-{project_id}",
@@ -617,10 +634,10 @@ class SimulationProjectService:
"outputs": {
"preview_sog": True,
"streamed_sog": True,
"collision": False,
"collision": source_mesh_available,
},
"preview_lod": "coarsest",
"collision_profile": None,
"collision_profile": collision_profile,
}
submitted = provider.submit_build(request)
job_id = submitted.get("job_id")
+15 -1
View File
@@ -138,7 +138,6 @@ from k1link.web.m49_physical_safety_playback_api import (
)
from k1link.web.m49_tgs_fail_closed_api import build_m49_tgs_fail_closed_router
from k1link.web.m49_tgs_full_shadow_api import build_m49_tgs_full_shadow_router
from k1link.web.vegetation_shadow_lab_api import build_vegetation_shadow_lab_router
from k1link.web.map_api import (
MapGatewayConfiguration,
MapGatewayProxy,
@@ -165,6 +164,10 @@ from k1link.web.session_api import build_session_router
from k1link.web.simulation_projects_api import build_simulation_projects_router
from k1link.web.simulation_world_provider_api import build_simulation_world_provider_router
from k1link.web.system_telemetry_api import build_system_telemetry_router
from k1link.web.vegetation_shadow_lab_api import (
build_vegetation_benchmark_lab_router,
build_vegetation_shadow_lab_router,
)
from k1link.web.viewer_diagnostics_api import build_viewer_diagnostics_router
REPOSITORY_ROOT = Path(__file__).resolve().parents[3]
@@ -1031,6 +1034,17 @@ app.include_router(
),
)
)
app.include_router(
build_vegetation_benchmark_lab_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "lab-v1-vegetation-benchmark"
/ "results"
),
)
)
app.include_router(
build_m49_physical_safety_playback_router(
root_provider=lambda: (
+191 -17
View File
@@ -5,7 +5,6 @@ from __future__ import annotations
import copy
import hashlib
import json
import re
import zipfile
from collections.abc import Callable
from functools import lru_cache
@@ -20,10 +19,9 @@ from k1link.laboratory.evidence_report import (
LaboratoryEvidenceReportError,
verify_laboratory_evidence_result,
)
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA, RESULT_PREFIX
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA
RootProvider = Callable[[], Path | None]
RESULT_ID: Final = re.compile(rf"^{re.escape(RESULT_PREFIX)}[a-f0-9]{{64}}$")
_MAX_DOCUMENT_BYTES: Final = 1024 * 1024
_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-shadow",
@@ -32,25 +30,55 @@ _DEFINITION: Final = LaboratoryEvidenceDefinition(
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
_BENCHMARK_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-benchmark",
runtime_relative_root=PurePosixPath("lab-v1-vegetation-benchmark/results"),
result_id_prefix="lab-v1-vegetation-benchmark",
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
def build_vegetation_shadow_lab_router(
*, root_provider: RootProvider = lambda: None,
) -> APIRouter:
router = APIRouter(
return _build_vegetation_lab_router(
prefix="/api/v1/laboratory/vegetation-shadow",
definition=_DEFINITION,
root_provider=root_provider,
)
def build_vegetation_benchmark_lab_router(
*, root_provider: RootProvider = lambda: None,
) -> APIRouter:
return _build_vegetation_lab_router(
prefix="/api/v1/laboratory/vegetation-benchmark",
definition=_BENCHMARK_DEFINITION,
root_provider=root_provider,
)
def _build_vegetation_lab_router(
*,
prefix: str,
definition: LaboratoryEvidenceDefinition,
root_provider: RootProvider,
) -> APIRouter:
router = APIRouter(
prefix=prefix,
tags=["laboratory"],
)
@router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]:
candidate = _resolve_candidate(root_provider, result_id)
return {**copy.deepcopy(_read_verified(candidate)), "access": "read-only"}
candidate = _resolve_candidate(root_provider, definition, result_id)
return {**copy.deepcopy(_read_verified(candidate, definition)), "access": "read-only"}
@router.get("/{result_id}/assets/{asset_path:path}")
def get_asset(result_id: str, asset_path: str) -> FileResponse:
candidate = _resolve_candidate(root_provider, result_id)
manifest = _read_verified(candidate)
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
artifacts = manifest.get("artifacts")
if not isinstance(artifacts, list):
raise HTTPException(status_code=404, detail="Vegetation LAB asset not found")
@@ -90,8 +118,8 @@ def build_vegetation_shadow_lab_router(
@router.get("/{result_id}/masks/{sequence}")
def get_video_mask(result_id: str, sequence: int) -> Response:
candidate = _resolve_candidate(root_provider, result_id)
manifest = _read_verified(candidate)
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
route_video = manifest.get("route_video")
if (
not isinstance(route_video, dict)
@@ -151,9 +179,125 @@ def build_vegetation_shadow_lab_router(
},
)
@router.get("/{result_id}/route-masks/{layer}/{sequence}")
def get_full_route_mask(result_id: str, layer: str, sequence: int) -> Response:
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
route = manifest.get("route_full_review")
layers = route.get("layers") if isinstance(route, dict) else None
frame_count = route.get("frame_count") if isinstance(route, dict) else None
selected = layers.get(layer) if isinstance(layers, dict) else None
archive = selected.get("mask_archive") if isinstance(selected, dict) else None
archive_relative = archive.get("path") if isinstance(archive, dict) else None
if (
layer not in {"city", "vegetation"}
or not isinstance(frame_count, int)
or not 0 <= sequence < frame_count
or not isinstance(archive_relative, str)
):
raise HTTPException(status_code=404, detail="Full-route semantic mask not found")
relative = PurePosixPath(archive_relative)
artifacts = manifest.get("artifacts")
if (
relative.is_absolute()
or str(relative) != archive_relative
or any(part in {"", ".", ".."} for part in relative.parts)
or relative.suffix != ".zip"
or not isinstance(artifacts, list)
or not any(
isinstance(item, dict)
and item.get("path") == archive_relative
and item.get("media_type") == "application/zip"
for item in artifacts
)
):
raise HTTPException(status_code=404, detail="Full-route semantic mask not found")
return _zip_mask_response(candidate.joinpath(*relative.parts), sequence)
@router.get("/{result_id}/route-timeline")
def get_full_route_timeline(result_id: str) -> FileResponse:
candidate = _resolve_candidate(root_provider, definition, result_id)
manifest = _read_verified(candidate, definition)
route = manifest.get("route_full_review")
timeline = route.get("timeline") if isinstance(route, dict) else None
relative_text = timeline.get("path") if isinstance(timeline, dict) else None
frame_count = timeline.get("frame_count") if isinstance(timeline, dict) else None
byte_length = timeline.get("byte_length") if isinstance(timeline, dict) else None
sha256 = timeline.get("sha256") if isinstance(timeline, dict) else None
if (
not isinstance(relative_text, str)
or frame_count != route.get("frame_count")
or byte_length != frame_count * 8
or not isinstance(sha256, str)
or len(sha256) != 64
):
raise HTTPException(status_code=404, detail="Full-route timeline not found")
relative = PurePosixPath(relative_text)
artifacts = manifest.get("artifacts")
if (
relative.is_absolute()
or str(relative) != relative_text
or any(part in {"", ".", ".."} for part in relative.parts)
or not isinstance(artifacts, list)
or not any(
isinstance(item, dict)
and item.get("path") == relative_text
and item.get("byte_length") == byte_length
and item.get("sha256") == sha256
and item.get("media_type") == "application/octet-stream"
for item in artifacts
)
):
raise HTTPException(status_code=404, detail="Full-route timeline not found")
path = candidate.joinpath(*relative.parts)
if not path.is_file() or path.is_symlink() or path.stat().st_size != byte_length:
raise HTTPException(status_code=404, detail="Full-route timeline not found")
return FileResponse(
path,
media_type="application/octet-stream",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"ETag": f'"{sha256}"',
"X-Content-Type-Options": "nosniff",
},
)
return router
def _zip_mask_response(archive_path: Path, sequence: int) -> Response:
member = f"masks/frame-{sequence + 1:06d}.png"
try:
before = archive_path.stat()
with zipfile.ZipFile(archive_path) as frozen:
info = frozen.getinfo(member)
if info.is_dir() or info.file_size < 8 or info.file_size > 1024 * 1024:
raise ValueError("Semantic mask member is invalid")
payload = frozen.read(info)
after = archive_path.stat()
if (
before.st_size != after.st_size
or before.st_mtime_ns != after.st_mtime_ns
or len(payload) != info.file_size
):
raise ValueError("Semantic mask archive changed during read")
except (KeyError, OSError, ValueError, zipfile.BadZipFile):
raise HTTPException(
status_code=503,
detail="Semantic mask failed verification",
) from None
digest = hashlib.sha256(payload).hexdigest()
return Response(
content=payload,
media_type="image/png",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"ETag": f'"{digest}"',
"X-Content-Type-Options": "nosniff",
},
)
def _configured_root(provider: RootProvider) -> Path | None:
candidate = provider()
if candidate is None:
@@ -168,9 +312,13 @@ def _configured_root(provider: RootProvider) -> Path | None:
return root if root.is_dir() else None
def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
def _resolve_candidate(
provider: RootProvider,
definition: LaboratoryEvidenceDefinition,
result_id: str,
) -> Path:
root = _configured_root(provider)
if root is None or RESULT_ID.fullmatch(result_id) is None:
if root is None or definition.result_id_pattern.fullmatch(result_id) is None:
raise HTTPException(status_code=404, detail="Vegetation LAB result not found")
candidate = root / result_id
if candidate.is_symlink():
@@ -184,7 +332,10 @@ def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
return resolved
def _read_verified(candidate: Path) -> dict[str, Any]:
def _read_verified(
candidate: Path,
definition: LaboratoryEvidenceDefinition,
) -> dict[str, Any]:
try:
rows: list[tuple[str, int, int, int, int]] = []
for path in sorted(candidate.rglob("*"), key=lambda item: item.as_posix()):
@@ -202,19 +353,39 @@ def _read_verified(candidate: Path) -> dict[str, Any]:
status_code=503,
detail="Vegetation LAB evidence failed verification",
) from None
return _read_verified_cached(str(candidate), signature)
return _read_verified_cached(
str(candidate),
signature,
definition.work_id,
str(definition.runtime_relative_root),
definition.result_id_prefix,
definition.document_name,
definition.result_schema_version,
)
@lru_cache(maxsize=16)
def _read_verified_cached(
candidate_text: str,
signature: tuple[tuple[str, int, int, int, int], ...],
work_id: str,
runtime_relative_root: str,
result_id_prefix: str,
document_name: str,
result_schema_version: str,
) -> dict[str, Any]:
del signature
candidate = Path(candidate_text)
definition = LaboratoryEvidenceDefinition(
work_id=work_id,
runtime_relative_root=PurePosixPath(runtime_relative_root),
result_id_prefix=result_id_prefix,
document_name=document_name,
result_schema_version=result_schema_version,
)
try:
verify_laboratory_evidence_result(_DEFINITION, candidate)
path = candidate / "result.json"
verify_laboratory_evidence_result(definition, candidate)
path = candidate / definition.document_name
if path.stat().st_size > _MAX_DOCUMENT_BYTES:
raise LaboratoryEvidenceReportError("Vegetation LAB document is too large")
payload = json.loads(path.read_text("utf-8"))
@@ -228,4 +399,7 @@ def _read_verified_cached(
return payload
__all__ = ["build_vegetation_shadow_lab_router"]
__all__ = [
"build_vegetation_benchmark_lab_router",
"build_vegetation_shadow_lab_router",
]
@@ -104,3 +104,20 @@ def test_e4_class_fractions_use_only_valid_fov_pixels() -> None:
assert {item["id"]: item["pixels"] for item in classes} == {1: 1, 4: 2, 7: 1}
assert sum(float(item["fraction_of_valid_fov"]) for item in classes) == 1.0
def test_e4_orchestrator_seals_a_single_decoder_gap_without_frame_shift() -> None:
path = (
Path(__file__).parents[1]
/ "experiments"
/ "perception"
/ "worker"
/ "Invoke-E4FullSessionSegmentation.ps1"
)
source = path.read_text(encoding="utf-8")
assert "-c:v h264_cuvid" in source
assert "-frame_pts 1" in source
assert '$decodedPath = Join-Path $decodedFramesRoot ("frame-{0}.png" -f $pts)' in source
assert "$repairs.Count -ge 1" in source
assert 'method = "duplicate-previous-decoded-frame"' in source
assert 'schema_version = "missioncore.recorded-video-decode-repair/v1"' in source
+22
View File
@@ -117,6 +117,28 @@ def test_gateway_uploads_lcc_bundle_with_tus_and_reads_provider_contract(tmp_pat
assert descriptor.total_byte_length == sum(member.byte_length for member in descriptor.members)
def test_folder_discovery_retains_one_nested_xgrids_source_mesh(tmp_path: Path) -> None:
root = tmp_path / "export"
scene = root / "LCC_Results"
mesh = root / "Mesh_Files"
scene.mkdir(parents=True)
mesh.mkdir()
(scene / "scan.lcc").write_text(
json.dumps({"fileType": "Portable"}),
encoding="utf-8",
)
(scene / "index.bin").write_bytes(b"index")
(scene / "data.bin").write_bytes(b"data")
(mesh / "scan.ply").write_bytes(b"ply")
members = _discover_bundle_members(root, "LCC_Results/scan.lcc", "lcc")
assert "Mesh_Files/scan.ply" in members
(mesh / "duplicate.ply").write_bytes(b"ply")
with pytest.raises(GaussianPipelineIntegrityError, match="at most one"):
_discover_bundle_members(root, "LCC_Results/scan.lcc", "lcc")
def test_gateway_uploads_and_normalizes_archive_with_tus(tmp_path: Path) -> None:
archive_bytes = b"portable-archive"
archive_sha = hashlib.sha256(archive_bytes).hexdigest()
@@ -25,6 +25,12 @@ POWERSHELL_PATH = (
/ "worker"
/ "Invoke-LabV1VegetationGooseBenchmark.ps1"
)
RAV004_SOURCE_PATH = (
REPOSITORY_ROOT
/ "config"
/ "perception"
/ "lab-v1-ravnoves004tree-full-video-source-v1.json"
)
def test_benchmark_contract_is_bounded_and_fail_closed() -> None:
@@ -93,3 +99,21 @@ def test_worker_wrapper_is_isolated_from_canonical_triton() -> None:
assert '"--cap-drop", "ALL"' in source
assert '"--security-opt", "no-new-privileges"' in source
assert "if ($canonicalAfter -ne $canonicalBefore)" in source
def test_rav004_full_video_profile_and_decoder_gap_are_explicit() -> None:
profile = json.loads(RAV004_SOURCE_PATH.read_text(encoding="utf-8"))
source_profile = profile["source"]
assert profile["schema_version"] == "missioncore.lab-v1-ravnoves-source/v1"
assert source_profile["source_job_id"] == (
"recorded-camera-eb2783c5480d56bda07c8af0"
)
assert source_profile["expected_frame_count"] == 6830
assert source_profile["base_m4_result_id"] is None
source = POWERSHELL_PATH.read_text(encoding="utf-8")
assert "-c:v h264_cuvid" in source
assert 'schema_version = "missioncore.recorded-video-decode-repair/v1"' in source
assert 'method = "duplicate-previous-decoded-frame"' in source
assert "$repairs.Count -ge 1" in source
assert '& docker @arguments 2>&1 | ForEach-Object { Write-Output $_ }' in source
assert 'if ($dockerExitCode -ne 0)' in source
+2 -1
View File
@@ -127,9 +127,10 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
repository_root / "config" / "laboratories"
)
assert len(registry.definitions) == 43
assert len(registry.definitions) == 44
assert {item.work_id for item in registry.definitions} >= {
"lab-v1-vegetation-shadow",
"lab-v1-vegetation-benchmark",
"e31-source-binding",
"e46j-raw-fisheye-realtime",
"e47-semantic-slam-shadow",
@@ -80,7 +80,7 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
root / "config" / "laboratory-value-review.json"
)
assert len(registry.entries) == 41
assert len(registry.entries) == 42
assert {entry.catalog_id for entry in registry.entries} >= {
"e28-local-surface",
"e46d-temporal-failure-audit",
@@ -94,4 +94,5 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
"lab-v1-vegetation-shadow",
"lab-v1-vegetation-benchmark",
}
+109 -22
View File
@@ -156,11 +156,12 @@ def test_store_preserves_provider_job_and_stage_timing(tmp_path: Path) -> None:
class _ReadyProvider:
def __init__(self) -> None:
def __init__(self, *, source_mesh: bool = False) -> None:
self.deleted: list[str] = []
self.upload_calls = 0
self.submit_calls = 0
self.submitted_document: dict[str, object] | None = None
self.source_mesh = source_mesh
def capabilities(self) -> dict[str, object]:
return {"outputs": ["preview.sog", "streamed-sog"]}
@@ -173,17 +174,26 @@ class _ReadyProvider:
source_format: str,
) -> GaussianSourceBundleUpload:
self.upload_calls += 1
members = (
members = [
GaussianSourceMemberUpload("upload-1", entrypoint, "a" * 64, 23),
GaussianSourceMemberUpload("upload-2", "export/data.bin", "b" * 64, 4),
GaussianSourceMemberUpload("upload-3", "export/index.bin", "c" * 64, 5),
)
]
if self.source_mesh:
members.append(
GaussianSourceMemberUpload(
"upload-4",
"export/Mesh_Files/scene.ply",
"e" * 64,
3,
)
)
return GaussianSourceBundleUpload(
format=source_format,
entrypoint=entrypoint,
bundle_sha256="d" * 64,
total_byte_length=32,
members=members,
total_byte_length=sum(member.byte_length for member in members),
members=tuple(members),
)
def submit_build(self, document: dict[str, object]) -> dict[str, object]:
@@ -208,6 +218,39 @@ class _ReadyProvider:
}
def get_result(self, _job_id: str) -> dict[str, object]:
artifacts = [
{
"role": "preview",
"logical_path": "preview.sog",
"media_type": "application/octet-stream",
"sha256": "1" * 64,
"byte_length": 7,
},
{
"role": "stream-manifest",
"logical_path": "streamed/lod-meta.json",
"media_type": "application/json",
"sha256": "2" * 64,
"byte_length": 2,
},
]
if self.source_mesh:
artifacts.extend([
{
"role": "collision-mesh",
"logical_path": "collision/scene.collision.glb",
"media_type": "model/gltf-binary",
"sha256": "3" * 64,
"byte_length": 3,
},
{
"role": "collision-repair-report",
"logical_path": "collision/scene.repair.json",
"media_type": "application/json",
"sha256": "4" * 64,
"byte_length": 2,
},
])
return {
"schema_version": "gaussian-pipeline.build-result/v1",
"job_id": "gsp-20260826000000-deadbeef",
@@ -215,22 +258,7 @@ class _ReadyProvider:
"source_revision": "e" * 40,
"image_digest": f"sha256:{'f' * 64}",
},
"artifacts": [
{
"role": "preview",
"logical_path": "preview.sog",
"media_type": "application/octet-stream",
"sha256": "1" * 64,
"byte_length": 7,
},
{
"role": "stream-manifest",
"logical_path": "streamed/lod-meta.json",
"media_type": "application/json",
"sha256": "2" * 64,
"byte_length": 2,
},
],
"artifacts": artifacts,
}
def download_artifact(
@@ -240,7 +268,13 @@ class _ReadyProvider:
destination: Path,
) -> Path:
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(b"preview" if descriptor["role"] == "preview" else b"{}")
payload = {
"preview": b"preview",
"stream-manifest": b"{}",
"collision-mesh": b"glb",
"collision-repair-report": b"{}",
}[str(descriptor["role"])]
destination.write_bytes(payload)
return destination
def delete_job(self, job_id: str) -> None:
@@ -318,6 +352,59 @@ def test_service_builds_visual_world_without_automatic_collision(tmp_path: Path)
assert provider.deleted == ["gsp-20260826000000-deadbeef"]
def test_service_automatically_builds_repaired_source_mesh_collision(tmp_path: Path) -> None:
store = SimulationProjectStore(tmp_path)
files = [
*_folder_files(),
{"logical_path": "export/Mesh_Files/scene.ply", "byte_length": 3},
]
project = store.create(
name="Source mesh scene",
scene_type="outdoor",
source_kind="folder",
files=files,
)
payloads = {
"export/scene.lcc": b'{"fileType":"Portable"}',
"export/index.bin": b"index",
"export/data.bin": b"data",
"export/Mesh_Files/scene.ply": b"ply",
}
for source_file in project["source"]["files"]:
store.append_upload(
project["project_id"],
source_file["file_id"],
offset=0,
payload=payloads[source_file["logical_path"]],
)
store.begin_build(project["project_id"])
provider = _ReadyProvider(source_mesh=True)
service = SimulationProjectService(store, provider_factory=lambda: provider) # type: ignore[arg-type]
service.process(project["project_id"])
ready = store.get(project["project_id"])
assert ready["status"] == "ready"
assert ready["world_manifest"]["collision"]["available"] is True
assert ready["world_manifest"]["collision"]["mesh_url"].endswith(
"/collision/scene.collision.glb"
)
assert provider.submitted_document is not None
assert provider.submitted_document["outputs"] == {
"preview_sog": True,
"streamed_sog": True,
"collision": True,
}
assert provider.submitted_document["collision_profile"] == {
"scene_type": "outdoor",
"seed_position": [0.0, 0.0, 0.0],
"capsule_height": 0.4,
"capsule_radius": 0.4,
"voxel_size": 0.05,
"mesh_shape": "source",
}
def test_service_queue_processes_projects_strictly_one_at_a_time(tmp_path: Path) -> None:
store = SimulationProjectStore(tmp_path)
projects: list[dict[str, Any]] = []
+136 -3
View File
@@ -1,16 +1,21 @@
from __future__ import annotations
import hashlib
import io
import json
import shutil
import struct
import zipfile
from pathlib import Path
from types import SimpleNamespace
import numpy as np
from fastapi import FastAPI
from fastapi.testclient import TestClient
from PIL import Image
import k1link.laboratory.vegetation_policy_review as policy_review_module
import k1link.laboratory.vegetation_policy_video as policy_video_module
import k1link.laboratory.vegetation_shadow_lab as vegetation_lab_module
from k1link.laboratory import LaboratoryEvidenceRegistry
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
@@ -21,6 +26,45 @@ from k1link.web.vegetation_shadow_lab_api import build_vegetation_shadow_lab_rou
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
def test_coarse_policy_masks_mark_every_outside_fov_pixel_undefined(
tmp_path: Path,
monkeypatch,
) -> None:
monkeypatch.setattr(policy_video_module, "FRAME_COUNT", 1)
monkeypatch.setattr(
policy_video_module,
"fine_to_policy_lut",
lambda _taxonomy, _provider_map: np.full(256, 4, dtype=np.uint8),
)
source = tmp_path / "fine.zip"
fine_buffer = io.BytesIO()
Image.new("L", (800, 600), color=1).save(fine_buffer, format="PNG")
with zipfile.ZipFile(source, "w") as archive:
archive.writestr("masks/frame-000001.png", fine_buffer.getvalue())
valid_fov = np.zeros((600, 800), dtype=np.uint8)
valid_fov[:, :400] = 255
valid_fov_path = tmp_path / "valid-fov.png"
Image.fromarray(valid_fov, mode="L").save(valid_fov_path)
destination = tmp_path / "coarse.zip"
counts = policy_video_module.build_policy_mask_archive(
source_archive=source,
destination_archive=destination,
fine_taxonomy={},
provider_label_map={},
valid_fov_mask=valid_fov_path,
)
with (
zipfile.ZipFile(destination) as archive,
Image.open(io.BytesIO(archive.read("masks/frame-000001.png"))) as image,
):
coarse = np.asarray(image.convert("L"))
assert np.all(coarse[:, :400] == 4)
assert np.all(coarse[:, 400:] == 9)
assert counts[4] == 600 * 400
assert counts[9] == 600 * 400
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
@@ -227,6 +271,90 @@ def test_vegetation_shadow_lab_seals_autonomous_visual_evidence(
assert mask.content == b"\x89PNG\r\n\x1a\n"
assert mask.headers["cache-control"].endswith("immutable")
full_archive_payloads = (b"\x89PNG\r\n\x1a\ncity", b"\x89PNG\r\n\x1a\nvegetation")
full_timeline_payload = struct.pack("<2Q", 1_000_000_000, 1_100_000_000)
full_identity = dict(manifest["identity"])
full_route = {
"frame_count": 2,
"timeline": {
"path": "video/frame-source-times-ns.bin",
"sha256": hashlib.sha256(full_timeline_payload).hexdigest(),
"byte_length": len(full_timeline_payload),
"encoding": "uint64-le-nanoseconds",
"frame_count": 2,
},
"layers": {
layer: {"mask_archive": {"path": "video/full-route-masks.zip"}}
for layer in ("city", "vegetation")
},
}
full_identity["route_full_review"] = full_route
full_identity_sha = hashlib.sha256(
json.dumps(
full_identity,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
).hexdigest()
full_result_id = f"lab-v1-vegetation-shadow-{full_identity_sha}"
full_root = result_root.parent / full_result_id
shutil.copytree(result_root, full_root)
full_archive = full_root / "video" / "full-route-masks.zip"
full_archive.parent.mkdir(exist_ok=True)
with zipfile.ZipFile(full_archive, "x", compression=zipfile.ZIP_STORED) as frozen:
for sequence, payload in enumerate(full_archive_payloads, start=1):
frozen.writestr(f"masks/frame-{sequence:06d}.png", payload)
full_timeline = full_root / "video" / "frame-source-times-ns.bin"
full_timeline.write_bytes(full_timeline_payload)
full_manifest = dict(manifest)
full_manifest["result_id"] = full_result_id
full_manifest["identity"] = full_identity
full_manifest["identity_sha256"] = full_identity_sha
full_manifest["route_full_review"] = full_route
full_manifest["artifacts"] = [
*manifest["artifacts"],
{
"role": "full-route-mask-fixture",
"path": "video/full-route-masks.zip",
"byte_length": full_archive.stat().st_size,
"sha256": _sha256(full_archive),
"media_type": "application/zip",
},
{
"role": "full-route-frame-timeline",
"path": "video/frame-source-times-ns.bin",
"byte_length": full_timeline.stat().st_size,
"sha256": _sha256(full_timeline),
"media_type": "application/octet-stream",
},
]
(full_root / "result.json").write_text(
json.dumps(full_manifest, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
+ "\n",
encoding="utf-8",
)
for layer, sequence, expected in (
("city", 0, full_archive_payloads[0]),
("vegetation", 1, full_archive_payloads[1]),
):
response = client.get(
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}"
f"/route-masks/{layer}/{sequence}"
)
assert response.status_code == 200
assert response.content == expected
assert response.headers["cache-control"].endswith("immutable")
assert client.get(
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}/route-masks/city/2"
).status_code == 404
timeline = client.get(
f"/api/v1/laboratory/vegetation-shadow/{full_result_id}/route-timeline"
)
assert timeline.status_code == 200
assert timeline.content == full_timeline_payload
assert timeline.headers["cache-control"].endswith("immutable")
(result_root / asset_path).write_bytes(b"tampered")
assert (
client.get(f"/api/v1/laboratory/vegetation-shadow/{result_root.name}").status_code
@@ -290,9 +418,12 @@ def test_policy_review_reuses_sealed_video_and_links_yolox_tgs(
def fake_policy_archive(**kwargs) -> list[int]:
shutil.copyfile(kwargs["source_archive"], kwargs["destination_archive"])
return [4489 * 800 * 600, *([0] * 8)]
assert kwargs["valid_fov_mask"].is_file()
return [4489 * 800 * 600, *([0] * 9)]
monkeypatch.setattr(policy_review_module, "build_policy_mask_archive", fake_policy_archive)
valid_fov_mask = tmp_path / "valid-fov-mask.png"
Image.new("L", (800, 600), color=255).save(valid_fov_mask)
result_root = seal_vegetation_policy_review(
base_lab_root=base_root,
mission_policy_path=REPOSITORY_ROOT
@@ -300,6 +431,7 @@ def test_policy_review_reuses_sealed_video_and_links_yolox_tgs(
provider_label_map_path=REPOSITORY_ROOT
/ "config/perception/lab-v1-vegetation-provider-label-map-v1.json",
m49_tgs_full_shadow_root=tmp_path / "sealed-tgs",
valid_fov_mask_path=valid_fov_mask,
output_root=tmp_path / "results",
created_at_utc="2026-08-28T08:00:00+00:00",
)
@@ -312,8 +444,9 @@ def test_policy_review_reuses_sealed_video_and_links_yolox_tgs(
assert route["taxonomy"]["schema_version"] == (
"missioncore.lab-v1-terrain-policy-taxonomy/v1"
)
assert len(route["taxonomy"]["classes"]) == 9
assert len(manifest["artifacts"]) == 79
assert len(route["taxonomy"]["classes"]) == 10
assert route["valid_fov"]["outside_valid_fov_class_id"] == 9
assert len(manifest["artifacts"]) == 80
assert manifest["authority"]["commands_enabled"] is False
assert manifest["decision"]["multilayer_policy_review_ready"] is True