feat(perception): integrate vegetation policy review
This commit is contained in:
@@ -55,7 +55,9 @@ export interface VegetationVideoSemanticClass {
|
|||||||
classId: number;
|
classId: number;
|
||||||
label: string;
|
label: string;
|
||||||
colorRgb: readonly [number, number, number];
|
colorRgb: readonly [number, number, number];
|
||||||
disposition: "prediction" | "undefined";
|
disposition: "labeled" | "ambiguous" | "prediction" | "undefined";
|
||||||
|
materialClass: string | null;
|
||||||
|
evidenceState: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VegetationRouteVideo {
|
export interface VegetationRouteVideo {
|
||||||
@@ -67,8 +69,12 @@ export interface VegetationRouteVideo {
|
|||||||
height: 600;
|
height: 600;
|
||||||
centerCropXyxy: readonly [100, 0, 700, 600];
|
centerCropXyxy: readonly [100, 0, 700, 600];
|
||||||
outsideCropState: "undefined";
|
outsideCropState: "undefined";
|
||||||
|
viewKind: "fine-semantic-prediction" | "coarse-material-policy-review";
|
||||||
|
linkedTgsResultId: string | null;
|
||||||
taxonomy: readonly VegetationVideoSemanticClass[];
|
taxonomy: readonly VegetationVideoSemanticClass[];
|
||||||
aggregatePredictionPixels: readonly number[];
|
aggregatePredictionPixels: readonly number[];
|
||||||
|
policyPresets: Readonly<Record<string, Readonly<Record<string, string>>>> | null;
|
||||||
|
fusionMode: "synchronised-multilayer-review" | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VegetationShadowResult {
|
export interface VegetationShadowResult {
|
||||||
@@ -257,6 +263,9 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
|||||||
"vegetation.route_video.m47_reference_graph_result_id",
|
"vegetation.route_video.m47_reference_graph_result_id",
|
||||||
);
|
);
|
||||||
const baseM4ResultId = textValue(row.base_m4_result_id, "vegetation.route_video.base_m4_result_id");
|
const baseM4ResultId = textValue(row.base_m4_result_id, "vegetation.route_video.base_m4_result_id");
|
||||||
|
const viewKind = row.view_kind === undefined
|
||||||
|
? "fine-semantic-prediction"
|
||||||
|
: textValue(row.view_kind, "vegetation.route_video.view_kind");
|
||||||
if (
|
if (
|
||||||
!/^lab-v1-ravnoves-video-ddrnet-[a-f0-9]{64}$/.test(workerResultId)
|
!/^lab-v1-ravnoves-video-ddrnet-[a-f0-9]{64}$/.test(workerResultId)
|
||||||
|| !/^m47-reference-graph-lab-[a-f0-9]{64}$/.test(m47ReferenceGraphResultId)
|
|| !/^m47-reference-graph-lab-[a-f0-9]{64}$/.test(m47ReferenceGraphResultId)
|
||||||
@@ -264,6 +273,15 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
|||||||
) {
|
) {
|
||||||
throw new VegetationShadowContractError("vegetation.route_video: identity invalid.");
|
throw new VegetationShadowContractError("vegetation.route_video: identity invalid.");
|
||||||
}
|
}
|
||||||
|
if (viewKind !== "fine-semantic-prediction" && viewKind !== "coarse-material-policy-review") {
|
||||||
|
throw new VegetationShadowContractError("vegetation.route_video: view kind invalid.");
|
||||||
|
}
|
||||||
|
const linkedTgsResultId = viewKind === "coarse-material-policy-review"
|
||||||
|
? textValue(row.linked_tgs_result_id, "vegetation.route_video.linked_tgs_result_id")
|
||||||
|
: null;
|
||||||
|
if (linkedTgsResultId && !/^m49-tgs-full-shadow-[a-f0-9]{64}$/.test(linkedTgsResultId)) {
|
||||||
|
throw new VegetationShadowContractError("vegetation.route_video: TGS identity invalid.");
|
||||||
|
}
|
||||||
exact(row.frame_count, 4489, "vegetation.route_video.frame_count");
|
exact(row.frame_count, 4489, "vegetation.route_video.frame_count");
|
||||||
exact(row.width, 800, "vegetation.route_video.width");
|
exact(row.width, 800, "vegetation.route_video.width");
|
||||||
exact(row.height, 600, "vegetation.route_video.height");
|
exact(row.height, 600, "vegetation.route_video.height");
|
||||||
@@ -279,11 +297,9 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
|||||||
throw new VegetationShadowContractError("vegetation.route_video: crop contract changed.");
|
throw new VegetationShadowContractError("vegetation.route_video: crop contract changed.");
|
||||||
}
|
}
|
||||||
const taxonomy = objectValue(row.taxonomy, "vegetation.route_video.taxonomy");
|
const taxonomy = objectValue(row.taxonomy, "vegetation.route_video.taxonomy");
|
||||||
exact(
|
exact(taxonomy.schema_version, viewKind === "coarse-material-policy-review"
|
||||||
taxonomy.schema_version,
|
? "missioncore.lab-v1-terrain-policy-taxonomy/v1"
|
||||||
"missioncore.lab-v1-vegetation-taxonomy/v1",
|
: "missioncore.lab-v1-vegetation-taxonomy/v1", "vegetation.route_video.taxonomy.schema");
|
||||||
"vegetation.route_video.taxonomy.schema",
|
|
||||||
);
|
|
||||||
const classes = arrayValue(taxonomy.classes, "vegetation.route_video.taxonomy.classes")
|
const classes = arrayValue(taxonomy.classes, "vegetation.route_video.taxonomy.classes")
|
||||||
.map((value, expectedId): VegetationVideoSemanticClass => {
|
.map((value, expectedId): VegetationVideoSemanticClass => {
|
||||||
const item = objectValue(value, `vegetation.route_video.taxonomy[${expectedId}]`);
|
const item = objectValue(value, `vegetation.route_video.taxonomy[${expectedId}]`);
|
||||||
@@ -296,36 +312,78 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
|||||||
if (color.length !== 3 || color.some((channel) => channel > 255)) {
|
if (color.length !== 3 || color.some((channel) => channel > 255)) {
|
||||||
throw new VegetationShadowContractError("vegetation.route_video: taxonomy color invalid.");
|
throw new VegetationShadowContractError("vegetation.route_video: taxonomy color invalid.");
|
||||||
}
|
}
|
||||||
const disposition: VegetationVideoSemanticClass["disposition"] = expectedId === 0
|
const disposition = item.disposition;
|
||||||
? "undefined"
|
if (
|
||||||
: "prediction";
|
disposition !== "labeled"
|
||||||
if (item.disposition !== disposition) {
|
&& disposition !== "ambiguous"
|
||||||
|
&& disposition !== "prediction"
|
||||||
|
&& disposition !== "undefined"
|
||||||
|
) {
|
||||||
throw new VegetationShadowContractError("vegetation.route_video: taxonomy disposition changed.");
|
throw new VegetationShadowContractError("vegetation.route_video: taxonomy disposition changed.");
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
viewKind === "fine-semantic-prediction"
|
||||||
|
&& disposition !== (expectedId === 0 ? "undefined" : "prediction")
|
||||||
|
) {
|
||||||
|
throw new VegetationShadowContractError("vegetation.route_video: fine taxonomy disposition changed.");
|
||||||
|
}
|
||||||
|
const materialClass = item.material_class === null || item.material_class === undefined
|
||||||
|
? null
|
||||||
|
: textValue(item.material_class, `vegetation.route_video.material[${expectedId}]`);
|
||||||
|
const evidenceState = item.evidence_state === null || item.evidence_state === undefined
|
||||||
|
? null
|
||||||
|
: textValue(item.evidence_state, `vegetation.route_video.evidence[${expectedId}]`);
|
||||||
return {
|
return {
|
||||||
classId,
|
classId,
|
||||||
label: textValue(item.label, `vegetation.route_video.label[${expectedId}]`),
|
label: textValue(item.label, `vegetation.route_video.label[${expectedId}]`),
|
||||||
colorRgb: color as unknown as readonly [number, number, number],
|
colorRgb: color as unknown as readonly [number, number, number],
|
||||||
disposition,
|
disposition,
|
||||||
|
materialClass,
|
||||||
|
evidenceState,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
if (classes.length !== 64) {
|
const expectedClassCount = viewKind === "coarse-material-policy-review" ? 9 : 64;
|
||||||
throw new VegetationShadowContractError("vegetation.route_video: taxonomy must contain 64 classes.");
|
if (classes.length !== expectedClassCount) {
|
||||||
|
throw new VegetationShadowContractError("vegetation.route_video: taxonomy size changed.");
|
||||||
}
|
}
|
||||||
const aggregatePredictionPixels = arrayValue(
|
const aggregatePredictionPixels = arrayValue(
|
||||||
row.aggregate_prediction_pixels,
|
row.aggregate_prediction_pixels,
|
||||||
"vegetation.route_video.aggregate_prediction_pixels",
|
"vegetation.route_video.aggregate_prediction_pixels",
|
||||||
).map((value, index) => integerValue(value, `vegetation.route_video.pixels[${index}]`));
|
).map((value, index) => integerValue(value, `vegetation.route_video.pixels[${index}]`));
|
||||||
if (aggregatePredictionPixels.length !== 64) {
|
if (aggregatePredictionPixels.length !== expectedClassCount) {
|
||||||
throw new VegetationShadowContractError("vegetation.route_video: class accounting changed.");
|
throw new VegetationShadowContractError("vegetation.route_video: class accounting changed.");
|
||||||
}
|
}
|
||||||
const maskArchive = objectValue(row.mask_archive, "vegetation.route_video.mask_archive");
|
const maskArchive = objectValue(row.mask_archive, "vegetation.route_video.mask_archive");
|
||||||
exact(maskArchive.path, "video/ddrnet-semantic-masks.zip", "vegetation.route_video.mask_archive.path");
|
exact(maskArchive.path, viewKind === "coarse-material-policy-review"
|
||||||
|
? "video/coarse-material-policy-masks.zip"
|
||||||
|
: "video/ddrnet-semantic-masks.zip", "vegetation.route_video.mask_archive.path");
|
||||||
const archiveSha256 = textValue(maskArchive.sha256, "vegetation.route_video.mask_archive.sha256");
|
const archiveSha256 = textValue(maskArchive.sha256, "vegetation.route_video.mask_archive.sha256");
|
||||||
if (!SHA256.test(archiveSha256)) {
|
if (!SHA256.test(archiveSha256)) {
|
||||||
throw new VegetationShadowContractError("vegetation.route_video: archive digest invalid.");
|
throw new VegetationShadowContractError("vegetation.route_video: archive digest invalid.");
|
||||||
}
|
}
|
||||||
integerValue(maskArchive.byte_length, "vegetation.route_video.mask_archive.byte_length");
|
integerValue(maskArchive.byte_length, "vegetation.route_video.mask_archive.byte_length");
|
||||||
|
let policyPresets: VegetationRouteVideo["policyPresets"] = null;
|
||||||
|
let fusionMode: VegetationRouteVideo["fusionMode"] = null;
|
||||||
|
if (viewKind === "coarse-material-policy-review") {
|
||||||
|
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]) => {
|
||||||
|
const rules = objectValue(rawRules, `vegetation.route_video.policy.${presetId}`);
|
||||||
|
return [presetId, Object.fromEntries(Object.entries(rules).map(([material, action]) => [
|
||||||
|
material,
|
||||||
|
textValue(action, `vegetation.route_video.policy.${presetId}.${material}`),
|
||||||
|
]))];
|
||||||
|
}));
|
||||||
|
const fusion = objectValue(row.fusion, "vegetation.route_video.fusion");
|
||||||
|
exact(fusion.pixel_raster_fusion, false, "vegetation.route_video.fusion.pixel_raster_fusion");
|
||||||
|
exact(fusion.camera_semantic_temporal_filter, "none", "vegetation.route_video.fusion.camera_filter");
|
||||||
|
exact(
|
||||||
|
fusion.mode,
|
||||||
|
"synchronised-multilayer-review",
|
||||||
|
"vegetation.route_video.fusion.mode",
|
||||||
|
);
|
||||||
|
fusionMode = "synchronised-multilayer-review";
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
workerResultId,
|
workerResultId,
|
||||||
m47ReferenceGraphResultId,
|
m47ReferenceGraphResultId,
|
||||||
@@ -335,8 +393,12 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
|||||||
height: 600,
|
height: 600,
|
||||||
centerCropXyxy: [100, 0, 700, 600],
|
centerCropXyxy: [100, 0, 700, 600],
|
||||||
outsideCropState: "undefined",
|
outsideCropState: "undefined",
|
||||||
|
viewKind,
|
||||||
|
linkedTgsResultId,
|
||||||
taxonomy: classes,
|
taxonomy: classes,
|
||||||
aggregatePredictionPixels,
|
aggregatePredictionPixels,
|
||||||
|
policyPresets,
|
||||||
|
fusionMode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
M4ReplayThreatVisual,
|
M4ReplayThreatVisual,
|
||||||
type M4ReplayClassifiedSpatialFrame,
|
type M4ReplayClassifiedSpatialFrame,
|
||||||
|
type M4ReplayThreatSemanticLayer,
|
||||||
} from "./M4ReplayThreatVisual";
|
} from "./M4ReplayThreatVisual";
|
||||||
|
|
||||||
const CLASSES: readonly RecordedEvidenceSemanticClass[] = [
|
const CLASSES: readonly RecordedEvidenceSemanticClass[] = [
|
||||||
@@ -42,7 +43,15 @@ function message(error: unknown): string {
|
|||||||
: "Полный TGS spatial frame недоступен.";
|
: "Полный TGS spatial frame недоступен.";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowResult }) {
|
export function M49TgsFullShadowEvidence({
|
||||||
|
result,
|
||||||
|
semanticOverride,
|
||||||
|
evidenceLabel = "M49 · full TGS shadow",
|
||||||
|
}: {
|
||||||
|
result: M49TgsFullShadowResult;
|
||||||
|
semanticOverride?: M4ReplayThreatSemanticLayer;
|
||||||
|
evidenceLabel?: string;
|
||||||
|
}) {
|
||||||
const [activeSequence, setActiveSequence] = useState<number | null>(null);
|
const [activeSequence, setActiveSequence] = useState<number | null>(null);
|
||||||
const [semantic, setSemantic] = useState<E47SemanticSlamResult | null>(null);
|
const [semantic, setSemantic] = useState<E47SemanticSlamResult | null>(null);
|
||||||
const [semanticError, setSemanticError] = useState<string | null>(null);
|
const [semanticError, setSemanticError] = useState<string | null>(null);
|
||||||
@@ -62,6 +71,7 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
setSemantic(null);
|
setSemantic(null);
|
||||||
setSemanticError(null);
|
setSemanticError(null);
|
||||||
|
if (semanticOverride) return () => controller.abort();
|
||||||
void fetchE47SemanticSlamResult({
|
void fetchE47SemanticSlamResult({
|
||||||
resultId: result.source.linkedSemanticResultId,
|
resultId: result.source.linkedSemanticResultId,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
@@ -77,7 +87,7 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
|
|||||||
if (!controller.signal.aborted) setSemanticError(message(caught));
|
if (!controller.signal.aborted) setSemanticError(message(caught));
|
||||||
});
|
});
|
||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId]);
|
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId, semanticOverride]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@@ -196,13 +206,13 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
|
|||||||
<>
|
<>
|
||||||
<M4ReplayThreatVisual
|
<M4ReplayThreatVisual
|
||||||
resultId={result.source.linkedVisualResultId}
|
resultId={result.source.linkedVisualResultId}
|
||||||
semantic={semantic ? {
|
semantic={semanticOverride ?? (semantic ? {
|
||||||
resultId: semantic.resultId,
|
resultId: semantic.resultId,
|
||||||
taxonomy: semantic.taxonomy,
|
taxonomy: semantic.taxonomy,
|
||||||
} : undefined}
|
} : undefined)}
|
||||||
showReviewAnchorBoxes={false}
|
showReviewAnchorBoxes={false}
|
||||||
reviewLabel="4 489 source-paced TGS frames"
|
reviewLabel="4 489 source-paced TGS frames"
|
||||||
evidenceLabel="M49 · full TGS shadow"
|
evidenceLabel={evidenceLabel}
|
||||||
initialSpatialMode="3d"
|
initialSpatialMode="3d"
|
||||||
onActiveSequenceChange={handleSequenceChange}
|
onActiveSequenceChange={handleSequenceChange}
|
||||||
classifiedSpatialLayer={{
|
classifiedSpatialLayer={{
|
||||||
@@ -217,7 +227,7 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
|
|||||||
replacePointCloud: false,
|
replacePointCloud: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{semanticError ? (
|
{!semanticOverride && semanticError ? (
|
||||||
<div className="m4-replay-threat-visual__pane-status" role="alert">
|
<div className="m4-replay-threat-visual__pane-status" role="alert">
|
||||||
Semantic overlay недоступен: {semanticError}
|
Semantic overlay недоступен: {semanticError}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -378,12 +378,12 @@ export function M4ReplayThreatVisual({
|
|||||||
);
|
);
|
||||||
}, [frame, metadata.timeline, showReferenceMediaLayers, showStaticObstacles]);
|
}, [frame, metadata.timeline, showReferenceMediaLayers, showStaticObstacles]);
|
||||||
const activeBoxes = useMemo(
|
const activeBoxes = useMemo(
|
||||||
() => classifiedSpatialLayer || !showReferenceMediaLayers ? [] : [
|
() => !showReferenceMediaLayers ? [] : [
|
||||||
...boxes(frame?.cameraProposals ?? []),
|
...boxes(frame?.cameraProposals ?? []),
|
||||||
...staticObstacleBoxes,
|
...staticObstacleBoxes,
|
||||||
...reviewAnchorBoxes,
|
...reviewAnchorBoxes,
|
||||||
],
|
],
|
||||||
[classifiedSpatialLayer, frame, reviewAnchorBoxes, showReferenceMediaLayers, staticObstacleBoxes],
|
[frame, reviewAnchorBoxes, showReferenceMediaLayers, staticObstacleBoxes],
|
||||||
);
|
);
|
||||||
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
|
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
|
||||||
() => semantic?.taxonomy.map((item) => ({
|
() => semantic?.taxonomy.map((item) => ({
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LaboratoryEvidence,
|
LaboratoryEvidence,
|
||||||
LaboratoryResultSummary,
|
LaboratoryResultSummary,
|
||||||
@@ -8,11 +10,16 @@ import {
|
|||||||
vegetationVideoMaskUrl,
|
vegetationVideoMaskUrl,
|
||||||
type VegetationShadowResult,
|
type VegetationShadowResult,
|
||||||
} from "../../core/laboratory/vegetationShadow";
|
} from "../../core/laboratory/vegetationShadow";
|
||||||
|
import {
|
||||||
|
fetchM49TgsFullShadowResult,
|
||||||
|
type M49TgsFullShadowResult,
|
||||||
|
} from "../../core/laboratory/m49TgsFullShadow";
|
||||||
import {
|
import {
|
||||||
M48MaskComparisonVisual,
|
M48MaskComparisonVisual,
|
||||||
type M48MaskComparisonCase,
|
type M48MaskComparisonCase,
|
||||||
} from "./M48FailureAtlasVisual";
|
} from "./M48FailureAtlasVisual";
|
||||||
import { M4ReplayThreatVisual } from "./M4ReplayThreatVisual";
|
import { M4ReplayThreatVisual } from "./M4ReplayThreatVisual";
|
||||||
|
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
|
||||||
|
|
||||||
function decimal(value: number, digits = 1): string {
|
function decimal(value: number, digits = 1): string {
|
||||||
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
|
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
|
||||||
@@ -49,6 +56,75 @@ function comparisonCases(result: VegetationShadowResult): readonly M48MaskCompar
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function VegetationRouteEvidence({ result }: { result: VegetationShadowResult }) {
|
||||||
|
const route = result.routeVideo!;
|
||||||
|
const [tgs, setTgs] = useState<M49TgsFullShadowResult | null>(null);
|
||||||
|
const [tgsError, setTgsError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
setTgs(null);
|
||||||
|
setTgsError(null);
|
||||||
|
if (!route.linkedTgsResultId) return () => controller.abort();
|
||||||
|
void fetchM49TgsFullShadowResult(route.linkedTgsResultId, {
|
||||||
|
signal: controller.signal,
|
||||||
|
}).then((next) => {
|
||||||
|
if (controller.signal.aborted) return;
|
||||||
|
if (next.source.linkedVisualResultId !== route.baseM4ResultId) {
|
||||||
|
throw new Error("TGS и camera timeline имеют разные source identities.");
|
||||||
|
}
|
||||||
|
setTgs(next);
|
||||||
|
}).catch((caught: unknown) => {
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
setTgsError(caught instanceof Error ? caught.message : "Sealed TGS недоступен.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => controller.abort();
|
||||||
|
}, [route.baseM4ResultId, route.linkedTgsResultId]);
|
||||||
|
|
||||||
|
const semantic = {
|
||||||
|
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",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
if (route.linkedTgsResultId && tgs) {
|
||||||
|
return (
|
||||||
|
<M49TgsFullShadowEvidence
|
||||||
|
result={tgs}
|
||||||
|
semanticOverride={semantic}
|
||||||
|
evidenceLabel="LAB V1 · MATERIAL + YOLOX + TGS"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (route.linkedTgsResultId && !tgsError) {
|
||||||
|
return <div className="m4-replay-threat-visual__pane-status" role="status">Открываем sealed TGS и coarse material timeline…</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<M4ReplayThreatVisual
|
||||||
|
resultId={route.baseM4ResultId}
|
||||||
|
evidenceLabel="LAB V1 · DDRNet"
|
||||||
|
showReferenceMediaLayers={route.viewKind === "coarse-material-policy-review"}
|
||||||
|
showSpatialOverlaySummary={false}
|
||||||
|
semantic={semantic}
|
||||||
|
/>
|
||||||
|
{tgsError ? (
|
||||||
|
<div className="m4-replay-threat-visual__pane-status" role="alert">
|
||||||
|
TGS слой недоступен: {tgsError}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function VegetationShadowResultView({
|
export function VegetationShadowResultView({
|
||||||
rigLabel,
|
rigLabel,
|
||||||
result,
|
result,
|
||||||
@@ -68,10 +144,14 @@ export function VegetationShadowResultView({
|
|||||||
<LaboratorySummary
|
<LaboratorySummary
|
||||||
title="LAB V1 · готовые модели растительности"
|
title="LAB V1 · готовые модели растительности"
|
||||||
description={result.routeVideo
|
description={result.routeVideo
|
||||||
? "M4.8 сохраняет truth-backed сравнение моделей, а штатный M4.7 viewer показывает фактический DDRNet prediction на всей записи RAVNOVES00. Все 4489 масок запечатаны локально и открываются без Worker 006."
|
? 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."}
|
: "Штатный M4.8-инструмент сравнивает две готовые fine-64 модели на полном GOOSE validation split и на 12 truth-backed hard cases, выбранных только по наличию нужной растительности. Sealed evidence открывается локально без Worker 006."}
|
||||||
status={result.routeVideo
|
status={result.routeVideo
|
||||||
? "DDRNet full-video prediction ready · route truth отсутствует"
|
? 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 не принят"}
|
: "Truth-backed model comparison · route transfer не принят"}
|
||||||
statusTone="warning"
|
statusTone="warning"
|
||||||
facts={[
|
facts={[
|
||||||
@@ -80,15 +160,17 @@ export function VegetationShadowResultView({
|
|||||||
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
|
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
|
||||||
...(result.routeVideo ? [{
|
...(result.routeVideo ? [{
|
||||||
label: "Видео",
|
label: "Видео",
|
||||||
value: "RAVNOVES00 · 4489/4489 DDRNet masks · exact recorded sequence",
|
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: "Authority", value: `${rigLabel} · MODEL QUALIFICATION ONLY · commands OFF` },
|
||||||
]}
|
]}
|
||||||
brief={{
|
brief={{
|
||||||
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
|
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
|
||||||
approach: "Обе модели последовательно прогнаны в одном изолированном CUDA-runtime на 962 кадрах. 12 визуальных кейсов выбраны детерминированно по truth-поддержке восьми растительных классов; один M4.8 viewer показывает source, truth, prediction и material-error для выбранной модели.",
|
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 ? "Его фактическая temporal stability теперь видна на всех 4489 кадрах штатного recorded viewer." : "Ошибки по каждому типу проверяются в одном штатном инструменте."}`,
|
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-разметки. Full-video слой показывает prediction, а не доказывает правильность. Папоротник отдельным классом отсутствует.",
|
limitation: "GOOSE — внешний размеченный домен; RAVNOVES00 — наш fisheye, но без ручной truth-разметки. Материалы — prediction, а не доказательство проходимости. TGS не проецируется в пиксели без отдельной принятой калибровки.",
|
||||||
}}
|
}}
|
||||||
method={{
|
method={{
|
||||||
completeness: "complete",
|
completeness: "complete",
|
||||||
@@ -120,32 +202,25 @@ export function VegetationShadowResultView({
|
|||||||
{result.routeVideo ? (
|
{result.routeVideo ? (
|
||||||
<LaboratoryEvidence
|
<LaboratoryEvidence
|
||||||
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
|
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
|
||||||
title="DDRNet PREDICTION · 4489/4489 кадров · TRUTH для этой записи отсутствует"
|
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"
|
kind="diagnostic-model"
|
||||||
resizable
|
resizable
|
||||||
>
|
>
|
||||||
<M4ReplayThreatVisual
|
<VegetationRouteEvidence result={result} />
|
||||||
resultId={result.routeVideo.baseM4ResultId}
|
|
||||||
evidenceLabel="LAB V1 · DDRNet"
|
|
||||||
showReferenceMediaLayers={false}
|
|
||||||
showSpatialOverlaySummary={false}
|
|
||||||
semantic={{
|
|
||||||
resultId: result.routeVideo.workerResultId,
|
|
||||||
spatialResultId: null,
|
|
||||||
taxonomy: result.routeVideo.taxonomy,
|
|
||||||
maskUrl: (sequence) => vegetationVideoMaskUrl(result.resultId, sequence),
|
|
||||||
label: "DDRNet vegetation prediction · recorded video",
|
|
||||||
maskAriaLabel: "DDRNet vegetation prediction",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</LaboratoryEvidence>
|
</LaboratoryEvidence>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
result={(
|
result={(
|
||||||
<LaboratoryResultSummary
|
<LaboratoryResultSummary
|
||||||
title="DDRNet — стартовые веса; перенос на ровер ещё не доказан"
|
title={result.routeVideo?.viewKind === "coarse-material-policy-review"
|
||||||
status={`${selected.loadedModelName} выбран только как vegetation candidate`}
|
? "Слои собраны для визуального policy review; управление не авторизовано"
|
||||||
|
: "DDRNet — стартовые веса; перенос на ровер ещё не доказан"}
|
||||||
|
status={result.routeVideo?.viewKind === "coarse-material-policy-review"
|
||||||
|
? "Materials are advisory · YOLOX/TGS veto cannot be cleared"
|
||||||
|
: `${selected.loadedModelName} выбран только как vegetation candidate`}
|
||||||
statusTone="warning"
|
statusTone="warning"
|
||||||
metrics={[
|
metrics={[
|
||||||
{
|
{
|
||||||
@@ -186,13 +261,17 @@ export function VegetationShadowResultView({
|
|||||||
...(result.routeVideo ? [{
|
...(result.routeVideo ? [{
|
||||||
label: "Route video",
|
label: "Route video",
|
||||||
value: "4489/4489 masks",
|
value: "4489/4489 masks",
|
||||||
hint: "DDRNet prediction · exact sequence · Worker-independent playback",
|
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={{
|
conclusion={{
|
||||||
proved: "Обе официальные fine-64 модели воспроизводимо запускаются на Worker 006; DDRNet лучше по aggregate vegetation IoU. Truth-backed hard cases прямо показывают траву, кусты и стволы, а не случайные автомобили и здания.",
|
proved: "Обе официальные fine-64 модели воспроизводимо запускаются на Worker 006; DDRNet лучше по aggregate vegetation IoU. Truth-backed hard cases прямо показывают траву, кусты и стволы, а не случайные автомобили и здания.",
|
||||||
notProved: "Не доказаны accuracy на нашем fisheye-домене, папоротник как отдельный материал, collision safety и physical-live поведение ровера. Видео позволяет увидеть temporal stability, но без truth не превращает её в метрику качества.",
|
notProved: "Не доказаны accuracy на нашем fisheye-домене, папоротник как отдельный материал, collision safety и physical-live поведение ровера. Видео позволяет увидеть temporal stability, но без truth не превращает её в метрику качества.",
|
||||||
decision: "Смотреть полный prediction на видео и собирать конкретные temporal/domain failure cases. DDRNet остаётся diagnostic candidate; LiDAR/TGS fail-closed геометрию не ослаблять.",
|
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 геометрию не ослаблять.",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -108,7 +108,9 @@ test("M4.9T5 viewer prefers autonomous chunks and keeps a sealed legacy fallback
|
|||||||
assert.doesNotMatch(source, /centersXyM\.map\(/);
|
assert.doesNotMatch(source, /centersXyM\.map\(/);
|
||||||
assert.match(source, /fetchE47SemanticSlamResult/);
|
assert.match(source, /fetchE47SemanticSlamResult/);
|
||||||
assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/);
|
assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/);
|
||||||
assert.match(source, /semantic=\{semantic \? \{/);
|
assert.match(source, /semantic=\{semanticOverride \?\? \(semantic \? \{/);
|
||||||
|
assert.match(source, /semanticOverride/);
|
||||||
|
assert.doesNotMatch(visual, /classifiedSpatialLayer \|\| !showReferenceMediaLayers \? \[\]/);
|
||||||
assert.match(
|
assert.match(
|
||||||
visual,
|
visual,
|
||||||
/classifiedSpatialFrame\s*&&\s*classifiedSpatialFrame\.sampleAvailable !== false/,
|
/classifiedSpatialFrame\s*&&\s*classifiedSpatialFrame\.sampleAvailable !== false/,
|
||||||
|
|||||||
@@ -104,45 +104,89 @@ function routeVideo() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function coarseRouteVideo() {
|
||||||
|
return {
|
||||||
|
...routeVideo(),
|
||||||
|
view_kind: "coarse-material-policy-review",
|
||||||
|
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) => ({
|
||||||
|
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",
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
aggregate_prediction_pixels: Array(9).fill(0),
|
||||||
|
mask_archive: {
|
||||||
|
path: "video/coarse-material-policy-masks.zip",
|
||||||
|
sha256: "8".repeat(64),
|
||||||
|
byte_length: 2048,
|
||||||
|
},
|
||||||
|
policy: {
|
||||||
|
presets: {
|
||||||
|
urban: { grass: "NO_GO" },
|
||||||
|
rural: { grass: "HIGH_COST" },
|
||||||
|
offroad: { grass: "HIGH_COST" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fusion: {
|
||||||
|
mode: "synchronised-multilayer-review",
|
||||||
|
pixel_raster_fusion: false,
|
||||||
|
camera_semantic_temporal_filter: "none",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function labPayload(route = routeVideo()) {
|
||||||
|
return {
|
||||||
|
schema_version: "missioncore.lab-v1-vegetation-shadow/v1",
|
||||||
|
result_id: resultId,
|
||||||
|
created_at_utc: "2026-08-27T20:00:00Z",
|
||||||
|
status: "visual-shadow-ready-policy-not-authorized",
|
||||||
|
ground_truth: false,
|
||||||
|
identity: { selected_candidate: "ddrnet" },
|
||||||
|
metrics: {
|
||||||
|
candidates: {
|
||||||
|
ddrnet: candidate("ddrnet", 0.64),
|
||||||
|
ppliteseg: candidate("ppliteseg", 0.61),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decision: {
|
||||||
|
selected_candidate: "ddrnet",
|
||||||
|
visual_shadow_ready: true,
|
||||||
|
mission_policy_ready_for_configuration: true,
|
||||||
|
navigation_accepted: false,
|
||||||
|
production_accepted: false,
|
||||||
|
},
|
||||||
|
limitations: ["shadow only"],
|
||||||
|
authority: {
|
||||||
|
commands_enabled: false,
|
||||||
|
navigation_or_safety_accepted: false,
|
||||||
|
actuation_accepted: false,
|
||||||
|
camera_semantics_can_clear_rigid_geometry: false,
|
||||||
|
},
|
||||||
|
catalogs: {
|
||||||
|
goose: Array.from({ length: 12 }, (_, index) => visualCase("goose", index)),
|
||||||
|
ravnoves: [],
|
||||||
|
},
|
||||||
|
route_video: route,
|
||||||
|
access: "read-only",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
test("vegetation LAB keeps autonomous assets and fail-closed authority", async () => {
|
test("vegetation LAB keeps autonomous assets and fail-closed authority", async () => {
|
||||||
let requestedUrl = "";
|
let requestedUrl = "";
|
||||||
const result = await fetchVegetationShadowResult(resultId, {
|
const result = await fetchVegetationShadowResult(resultId, {
|
||||||
fetcher: async (url) => {
|
fetcher: async (url) => {
|
||||||
requestedUrl = String(url);
|
requestedUrl = String(url);
|
||||||
return new Response(JSON.stringify({
|
return new Response(JSON.stringify(labPayload()), {
|
||||||
schema_version: "missioncore.lab-v1-vegetation-shadow/v1",
|
status: 200,
|
||||||
result_id: resultId,
|
headers: { "Content-Type": "application/json" },
|
||||||
created_at_utc: "2026-08-27T20:00:00Z",
|
});
|
||||||
status: "visual-shadow-ready-policy-not-authorized",
|
|
||||||
ground_truth: false,
|
|
||||||
identity: { selected_candidate: "ddrnet" },
|
|
||||||
metrics: {
|
|
||||||
candidates: {
|
|
||||||
ddrnet: candidate("ddrnet", 0.64),
|
|
||||||
ppliteseg: candidate("ppliteseg", 0.61),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
decision: {
|
|
||||||
selected_candidate: "ddrnet",
|
|
||||||
visual_shadow_ready: true,
|
|
||||||
mission_policy_ready_for_configuration: true,
|
|
||||||
navigation_accepted: false,
|
|
||||||
production_accepted: false,
|
|
||||||
},
|
|
||||||
limitations: ["shadow only"],
|
|
||||||
authority: {
|
|
||||||
commands_enabled: false,
|
|
||||||
navigation_or_safety_accepted: false,
|
|
||||||
actuation_accepted: false,
|
|
||||||
camera_semantics_can_clear_rigid_geometry: false,
|
|
||||||
},
|
|
||||||
catalogs: {
|
|
||||||
goose: Array.from({ length: 12 }, (_, index) => visualCase("goose", index)),
|
|
||||||
ravnoves: [],
|
|
||||||
},
|
|
||||||
route_video: routeVideo(),
|
|
||||||
access: "read-only",
|
|
||||||
}), { status: 200, headers: { "Content-Type": "application/json" } });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
@@ -154,6 +198,8 @@ test("vegetation LAB keeps autonomous assets and fail-closed authority", async (
|
|||||||
assert.equal(result.routeCases.length, 0);
|
assert.equal(result.routeCases.length, 0);
|
||||||
assert.equal(result.validationCases.length, 12);
|
assert.equal(result.validationCases.length, 12);
|
||||||
assert.equal(result.routeVideo.frameCount, 4489);
|
assert.equal(result.routeVideo.frameCount, 4489);
|
||||||
|
assert.equal(result.routeVideo.viewKind, "fine-semantic-prediction");
|
||||||
|
assert.equal(result.routeVideo.linkedTgsResultId, null);
|
||||||
assert.equal(result.routeVideo.taxonomy[0].disposition, "undefined");
|
assert.equal(result.routeVideo.taxonomy[0].disposition, "undefined");
|
||||||
assert.equal(result.validationCases[0].focus.className, "high_grass");
|
assert.equal(result.validationCases[0].focus.className, "high_grass");
|
||||||
assert.match(result.validationCases[0].assets.ddrnet_error, /\/assets\/visual\/goose\//);
|
assert.match(result.validationCases[0].assets.ddrnet_error, /\/assets\/visual\/goose\//);
|
||||||
@@ -165,6 +211,21 @@ test("vegetation LAB keeps autonomous assets and fail-closed authority", async (
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("vegetation LAB parses coarse material policy and sealed TGS binding", async () => {
|
||||||
|
const result = await fetchVegetationShadowResult(resultId, {
|
||||||
|
fetcher: async () => new Response(JSON.stringify(labPayload(coarseRouteVideo())), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
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[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 () => {
|
test("vegetation LAB reuses the admitted M4.8 and M4.7 instruments", async () => {
|
||||||
const resultSource = await readFile(
|
const resultSource = await readFile(
|
||||||
new URL("../src/workspaces/laboratory/VegetationShadowResult.tsx", import.meta.url),
|
new URL("../src/workspaces/laboratory/VegetationShadowResult.tsx", import.meta.url),
|
||||||
@@ -172,8 +233,10 @@ test("vegetation LAB reuses the admitted M4.8 and M4.7 instruments", async () =>
|
|||||||
);
|
);
|
||||||
assert.match(resultSource, /M48MaskComparisonVisual/);
|
assert.match(resultSource, /M48MaskComparisonVisual/);
|
||||||
assert.match(resultSource, /M4ReplayThreatVisual/);
|
assert.match(resultSource, /M4ReplayThreatVisual/);
|
||||||
|
assert.match(resultSource, /M49TgsFullShadowEvidence/);
|
||||||
|
assert.match(resultSource, /semanticOverride/);
|
||||||
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 2);
|
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 2);
|
||||||
assert.match(resultSource, /showReferenceMediaLayers=\{false\}/);
|
assert.match(resultSource, /linkedTgsResultId/);
|
||||||
assert.doesNotMatch(resultSource, /VegetationRouteVisual|urban\/rural\/off-road presets/);
|
assert.doesNotMatch(resultSource, /VegetationRouteVisual|urban\/rural\/off-road presets/);
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
access(new URL("../src/workspaces/laboratory/VegetationShadowVisual.tsx", import.meta.url)),
|
access(new URL("../src/workspaces/laboratory/VegetationShadowVisual.tsx", import.meta.url)),
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"schema_version": "missioncore.lab-v1-vegetation-integrated-shadow-profile/v1",
|
||||||
|
"profile_id": "lab-v1-ravnoves00-ddrnet-m49-integrated-shadow/v1",
|
||||||
|
"source": {
|
||||||
|
"source_id": "RAVNOVES00",
|
||||||
|
"expected_timeline_frames": 4489,
|
||||||
|
"requested_source_rate_hz": 12.0,
|
||||||
|
"shared_start_barrier": true,
|
||||||
|
"ground_truth_available": false
|
||||||
|
},
|
||||||
|
"stages": {
|
||||||
|
"m49_graph_tgs": {
|
||||||
|
"profile": "m49-tgs-integrated-graph-shadow-v1.json",
|
||||||
|
"profile_sha256": "b61e018b2d04eec58802e2d4186ce7a3dd3a15b254db106b57b609e903eeef80",
|
||||||
|
"candidate": "frozen-native-rf-detr-plus-cpu-tgs",
|
||||||
|
"parameters_unchanged": true
|
||||||
|
},
|
||||||
|
"vegetation": {
|
||||||
|
"candidate_id": "ddrnet_39-goose-fine-64",
|
||||||
|
"candidate_key": "ddrnet",
|
||||||
|
"checkpoint_sha256": "b99c2838051bcd7b092fd3970aa62a77d5c0bbb809c9b9afb2ff4b0ebdaa4ee6",
|
||||||
|
"config_sha256": "96a427a8baae387b827ec9c0bf7ca42e3fb9114b8fa9a8671bbc9d10877670b9",
|
||||||
|
"policy_sha256": "b75c4ac841d7b4bcc57f7a9c8417ca2317d8ecfa499e72a9af8a8591a2ec0d35",
|
||||||
|
"provider_map_sha256": "f2b69046b6a740fd9532d2d88e7fabae7c20fb662f783c9502adc9026406f352",
|
||||||
|
"container_image": "ndc/mission-core-lab-v1-goose:sg3.2.0-cu117-v1",
|
||||||
|
"container_image_id": "sha256:591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd",
|
||||||
|
"semantic_output_persisted": false,
|
||||||
|
"one_heavy_vegetation_candidate_at_a_time": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acceptance": {
|
||||||
|
"minimum_graph_world_state_fps": 11.209069,
|
||||||
|
"minimum_vegetation_fps": 11.209069,
|
||||||
|
"maximum_vegetation_completion_p95_ms": 125.0,
|
||||||
|
"maximum_combined_output_age_p99_ms": 125.0,
|
||||||
|
"capacity_drop_count_max": 0,
|
||||||
|
"unaccounted_frame_count_max": 0
|
||||||
|
},
|
||||||
|
"telemetry": {
|
||||||
|
"sample_interval_seconds": 1.0,
|
||||||
|
"required_roles": [
|
||||||
|
"graph",
|
||||||
|
"triton",
|
||||||
|
"tgs",
|
||||||
|
"vegetation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"invariants": {
|
||||||
|
"raw_fisheye_immutable": true,
|
||||||
|
"reference_graph_parameters_unchanged": true,
|
||||||
|
"tgs_parameters_unchanged": true,
|
||||||
|
"ddrnet_parameters_unchanged": true,
|
||||||
|
"ppliteseg_concurrent_run_allowed": false,
|
||||||
|
"camera_semantics_can_clear_rigid_geometry": false,
|
||||||
|
"canonical_triton_mutation_allowed": false,
|
||||||
|
"gauss_or_playcanvas_in_scope": false
|
||||||
|
},
|
||||||
|
"authority": {
|
||||||
|
"visual_quality_accepted": false,
|
||||||
|
"route_truth_available": false,
|
||||||
|
"traversability_accepted": false,
|
||||||
|
"physical_free_space_accepted": false,
|
||||||
|
"commands_enabled": false,
|
||||||
|
"actuation_allowed": false,
|
||||||
|
"navigation_or_safety_accepted": false,
|
||||||
|
"production_accepted": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,10 @@ param(
|
|||||||
[string]$RunId,
|
[string]$RunId,
|
||||||
[ValidateRange(1.0, 120.0)]
|
[ValidateRange(1.0, 120.0)]
|
||||||
[double]$SourceRateHz = 12.0,
|
[double]$SourceRateHz = 12.0,
|
||||||
|
[switch]$VegetationLoadGate,
|
||||||
|
[string]$VegetationAssetRoot = (
|
||||||
|
"D:\NDC_MISSIONCORE\datasets\vegetation-v1\observed-2026-08-27"
|
||||||
|
),
|
||||||
[string]$OutputRoot = (
|
[string]$OutputRoot = (
|
||||||
"D:\NDC_MISSIONCORE\runtime\results\m49-tgs-integrated-graph-shadow"
|
"D:\NDC_MISSIONCORE\runtime\results\m49-tgs-integrated-graph-shadow"
|
||||||
)
|
)
|
||||||
@@ -23,6 +27,8 @@ $TravelImageTag = "ndc/mission-core-m49-t3-travel:20260826"
|
|||||||
$TravelImageId = "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
|
$TravelImageId = "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
|
||||||
$ParityImageTag = "ndc-mission-core-m48t-upstream-parity:1.9.4-cu130"
|
$ParityImageTag = "ndc-mission-core-m48t-upstream-parity:1.9.4-cu130"
|
||||||
$ParityImageId = "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
|
$ParityImageId = "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
|
||||||
|
$VegetationImageTag = "ndc/mission-core-lab-v1-goose:sg3.2.0-cu117-v1"
|
||||||
|
$VegetationImageId = "sha256:591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd"
|
||||||
$RuntimeImage = (
|
$RuntimeImage = (
|
||||||
"nvcr.io/nvidia/tritonserver:26.06-py3@" +
|
"nvcr.io/nvidia/tritonserver:26.06-py3@" +
|
||||||
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||||
@@ -98,12 +104,20 @@ function Wait-Healthy([string]$Name) {
|
|||||||
function Wait-SharedReady(
|
function Wait-SharedReady(
|
||||||
[string]$GraphReady,
|
[string]$GraphReady,
|
||||||
[string]$TgsReady,
|
[string]$TgsReady,
|
||||||
|
[string]$VegetationReady,
|
||||||
[string]$GraphName,
|
[string]$GraphName,
|
||||||
[string]$TgsName
|
[string]$TgsName,
|
||||||
|
[string]$VegetationName
|
||||||
) {
|
) {
|
||||||
$deadline = [DateTimeOffset]::UtcNow.AddMinutes(10)
|
$deadline = [DateTimeOffset]::UtcNow.AddMinutes(10)
|
||||||
while (-not ((Test-Path -LiteralPath $GraphReady) -and (Test-Path -LiteralPath $TgsReady))) {
|
$requiredFiles = @($GraphReady, $TgsReady)
|
||||||
foreach ($name in @($GraphName, $TgsName)) {
|
$requiredContainers = @($GraphName, $TgsName)
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($VegetationReady)) {
|
||||||
|
$requiredFiles += $VegetationReady
|
||||||
|
$requiredContainers += $VegetationName
|
||||||
|
}
|
||||||
|
while ($requiredFiles.Where({ -not (Test-Path -LiteralPath $_) }).Count -gt 0) {
|
||||||
|
foreach ($name in $requiredContainers) {
|
||||||
$container = Get-Container $name
|
$container = Get-Container $name
|
||||||
if (-not $container.State.Running) {
|
if (-not $container.State.Running) {
|
||||||
& docker logs $name
|
& docker logs $name
|
||||||
@@ -128,15 +142,25 @@ $runCandidate = Join-Path $output $RunId
|
|||||||
if (Test-Path -LiteralPath $runCandidate) { throw "M49 integrated output already exists" }
|
if (Test-Path -LiteralPath $runCandidate) { throw "M49 integrated output already exists" }
|
||||||
$null = New-Item -ItemType Directory -Path $runCandidate
|
$null = New-Item -ItemType Directory -Path $runCandidate
|
||||||
$runOutput = Resolve-DDirectory $runCandidate "M49 integrated run output" $false
|
$runOutput = Resolve-DDirectory $runCandidate "M49 integrated run output" $false
|
||||||
foreach ($directory in @("bin", "control", "graph", "tgs")) {
|
foreach ($directory in @("bin", "control", "graph", "tgs", "vegetation")) {
|
||||||
$null = New-Item -ItemType Directory -Path (Join-Path $runOutput $directory)
|
$null = New-Item -ItemType Directory -Path (Join-Path $runOutput $directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
$releaseDocument = Get-Content -LiteralPath (Join-Path $payload "release.json") -Raw | ConvertFrom-Json
|
$releaseDocument = Get-Content -LiteralPath (Join-Path $payload "release.json") -Raw | ConvertFrom-Json
|
||||||
|
$expectedReleaseSchema = if ($VegetationLoadGate) {
|
||||||
|
"missioncore.lab-v1-vegetation-integrated-worker-release/v1"
|
||||||
|
} else {
|
||||||
|
"missioncore.m49-tgs-integrated-graph-worker-release/v1"
|
||||||
|
}
|
||||||
|
$expectedTransition = if ($VegetationLoadGate) {
|
||||||
|
"lab-v1-vegetation-m49-integrated-shadow/v1"
|
||||||
|
} else {
|
||||||
|
"m49-tgs-native-risk-integrated-shadow/v1"
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
$releaseDocument.schema_version -cne "missioncore.m49-tgs-integrated-graph-worker-release/v1" -or
|
$releaseDocument.schema_version -cne $expectedReleaseSchema -or
|
||||||
$releaseDocument.worker_id -cne "worker-006" -or
|
$releaseDocument.worker_id -cne "worker-006" -or
|
||||||
$releaseDocument.transition -cne "m49-tgs-native-risk-integrated-shadow/v1"
|
$releaseDocument.transition -cne $expectedTransition
|
||||||
) { throw "M49 integrated release contract changed" }
|
) { throw "M49 integrated release contract changed" }
|
||||||
foreach ($property in $releaseDocument.files.PSObject.Properties) {
|
foreach ($property in $releaseDocument.files.PSObject.Properties) {
|
||||||
$path = Join-Path $payload $property.Name
|
$path = Join-Path $payload $property.Name
|
||||||
@@ -146,6 +170,9 @@ foreach ($property in $releaseDocument.files.PSObject.Properties) {
|
|||||||
}
|
}
|
||||||
$wheelSha256 = [string]$releaseDocument.files."nodedc_mission_core-0.1.0-py3-none-any.whl".sha256
|
$wheelSha256 = [string]$releaseDocument.files."nodedc_mission_core-0.1.0-py3-none-any.whl".sha256
|
||||||
$runnerSha256 = [string]$releaseDocument.files."run_m48s_reference_graph_shadow_worker.py".sha256
|
$runnerSha256 = [string]$releaseDocument.files."run_m48s_reference_graph_shadow_worker.py".sha256
|
||||||
|
$vegetationRunnerSha256 = if ($VegetationLoadGate) {
|
||||||
|
[string]$releaseDocument.files."run_vegetation_integrated_load.py".sha256
|
||||||
|
} else { "" }
|
||||||
|
|
||||||
$source = [ordered]@{
|
$source = [ordered]@{
|
||||||
CameraIndex = (
|
CameraIndex = (
|
||||||
@@ -179,6 +206,23 @@ if ((Get-Sha256 $source.SourcePack) -cne [string]$releaseDocument.source_pack_sh
|
|||||||
throw "RAVNOVES00 source pack digest changed"
|
throw "RAVNOVES00 source pack digest changed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$vegetation = $null
|
||||||
|
if ($VegetationLoadGate) {
|
||||||
|
$vegetationRoot = Resolve-DDirectory $VegetationAssetRoot "vegetation asset root" $false
|
||||||
|
$vegetation = [ordered]@{
|
||||||
|
Dataset = Resolve-DDirectory (
|
||||||
|
(Join-Path $vegetationRoot "goose-2d\validation")
|
||||||
|
) "GOOSE validation root" $false
|
||||||
|
Checkpoint = Resolve-DFile (
|
||||||
|
(Join-Path $vegetationRoot "models\goose\ddrnet_class_512.pth")
|
||||||
|
) "DDRNet checkpoint"
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(Get-Sha256 $vegetation.Checkpoint) -cne
|
||||||
|
"b99c2838051bcd7b092fd3970aa62a77d5c0bbb809c9b9afb2ff4b0ebdaa4ee6"
|
||||||
|
) { throw "DDRNet checkpoint SHA-256 changed" }
|
||||||
|
}
|
||||||
|
|
||||||
$nativeConfig = Resolve-DFile (
|
$nativeConfig = Resolve-DFile (
|
||||||
(Join-Path $payload "rf_detr_large_native_kb4_config.pbtxt")
|
(Join-Path $payload "rf_detr_large_native_kb4_config.pbtxt")
|
||||||
) "native RF-DETR config"
|
) "native RF-DETR config"
|
||||||
@@ -207,12 +251,17 @@ $pillow = Resolve-DDirectory (
|
|||||||
|
|
||||||
Assert-Image $TravelImageTag $TravelImageId
|
Assert-Image $TravelImageTag $TravelImageId
|
||||||
Assert-Image $ParityImageTag $ParityImageId
|
Assert-Image $ParityImageTag $ParityImageId
|
||||||
|
if ($VegetationLoadGate) { Assert-Image $VegetationImageTag $VegetationImageId }
|
||||||
& docker image inspect $RuntimeImage *> $null
|
& docker image inspect $RuntimeImage *> $null
|
||||||
Assert-LastExitCode "pinned runtime image inspection"
|
Assert-LastExitCode "pinned runtime image inspection"
|
||||||
$os = Get-CimInstance Win32_OperatingSystem
|
$os = Get-CimInstance Win32_OperatingSystem
|
||||||
$freeMemoryGiB = [double]$os.FreePhysicalMemory / 1MB
|
$freeMemoryGiB = [double]$os.FreePhysicalMemory / 1MB
|
||||||
if ($freeMemoryGiB -lt 24.0) {
|
$requiredMemoryGiB = if ($VegetationLoadGate) { 32.0 } else { 24.0 }
|
||||||
throw ("M49 integrated shadow requires 24 GiB free memory; observed {0:N2} GiB" -f $freeMemoryGiB)
|
if ($freeMemoryGiB -lt $requiredMemoryGiB) {
|
||||||
|
throw (
|
||||||
|
"M49 integrated shadow requires {0:N0} GiB free memory; observed {1:N2} GiB" -f
|
||||||
|
$requiredMemoryGiB, $freeMemoryGiB
|
||||||
|
)
|
||||||
}
|
}
|
||||||
$canonicalBefore = Get-Container "ndc-mission-core-triton"
|
$canonicalBefore = Get-Container "ndc-mission-core-triton"
|
||||||
if (-not $canonicalBefore.State.Running -or $canonicalBefore.State.Health.Status -cne "healthy") {
|
if (-not $canonicalBefore.State.Running -or $canonicalBefore.State.Health.Status -cne "healthy") {
|
||||||
@@ -225,9 +274,12 @@ $compileName = "ndc-mission-core-m49-integrated-compile-$RunId"
|
|||||||
$tritonName = "ndc-mission-core-m49-integrated-triton-$RunId"
|
$tritonName = "ndc-mission-core-m49-integrated-triton-$RunId"
|
||||||
$graphName = "ndc-mission-core-m49-integrated-graph-$RunId"
|
$graphName = "ndc-mission-core-m49-integrated-graph-$RunId"
|
||||||
$tgsName = "ndc-mission-core-m49-integrated-tgs-$RunId"
|
$tgsName = "ndc-mission-core-m49-integrated-tgs-$RunId"
|
||||||
|
$vegetationName = "ndc-mission-core-m49-integrated-vegetation-$RunId"
|
||||||
$analyzeName = "ndc-mission-core-m49-integrated-analyze-$RunId"
|
$analyzeName = "ndc-mission-core-m49-integrated-analyze-$RunId"
|
||||||
$evidenceName = "ndc-mission-core-m49-integrated-evidence-$RunId"
|
$evidenceName = "ndc-mission-core-m49-integrated-evidence-$RunId"
|
||||||
|
$vegetationEvidenceName = "ndc-mission-core-m49-integrated-vegetation-evidence-$RunId"
|
||||||
$containers = @($prepareName, $compileName, $tritonName, $graphName, $tgsName, $analyzeName, $evidenceName)
|
$containers = @($prepareName, $compileName, $tritonName, $graphName, $tgsName, $analyzeName, $evidenceName)
|
||||||
|
if ($VegetationLoadGate) { $containers += @($vegetationName, $vegetationEvidenceName) }
|
||||||
foreach ($name in $containers) {
|
foreach ($name in $containers) {
|
||||||
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
|
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
|
||||||
throw "M49 integrated container name already exists: $name"
|
throw "M49 integrated container name already exists: $name"
|
||||||
@@ -236,6 +288,24 @@ foreach ($name in $containers) {
|
|||||||
|
|
||||||
$started = [DateTimeOffset]::UtcNow
|
$started = [DateTimeOffset]::UtcNow
|
||||||
try {
|
try {
|
||||||
|
if ($VegetationLoadGate) {
|
||||||
|
$vegetationFrames = Join-Path $runOutput "vegetation\input-frames"
|
||||||
|
$null = New-Item -ItemType Directory -Path $vegetationFrames
|
||||||
|
& ffmpeg -hide_banner -loglevel error -i $source.Video -map 0:v:0 -fps_mode passthrough (
|
||||||
|
Join-Path $vegetationFrames "frame-%06d.png"
|
||||||
|
)
|
||||||
|
Assert-LastExitCode "RAVNOVES full-video frame extraction"
|
||||||
|
$extractedFrames = @(
|
||||||
|
Get-ChildItem -LiteralPath $vegetationFrames -File -Filter "frame-*.png" |
|
||||||
|
Sort-Object Name
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
$extractedFrames.Count -ne 4489 -or
|
||||||
|
$extractedFrames[0].Name -cne "frame-000001.png" -or
|
||||||
|
$extractedFrames[-1].Name -cne "frame-004489.png"
|
||||||
|
) { throw "RAVNOVES full-video frame sequence changed" }
|
||||||
|
}
|
||||||
|
|
||||||
& docker run --rm --name $prepareName --network none --cpus 8 --memory 16g `
|
& docker run --rm --name $prepareName --network none --cpus 8 --memory 16g `
|
||||||
--entrypoint python3 `
|
--entrypoint python3 `
|
||||||
--volume ((Convert-ToDockerPath $source.SourcePack) + ":/source/lidar-pack.npz:ro") `
|
--volume ((Convert-ToDockerPath $source.SourcePack) + ":/source/lidar-pack.npz:ro") `
|
||||||
@@ -332,24 +402,71 @@ try {
|
|||||||
$TravelImageTag /release/run_tgs_integrated_shadow.sh *> $null
|
$TravelImageTag /release/run_tgs_integrated_shadow.sh *> $null
|
||||||
Assert-LastExitCode "M49 integrated TGS creation"
|
Assert-LastExitCode "M49 integrated TGS creation"
|
||||||
|
|
||||||
|
if ($VegetationLoadGate) {
|
||||||
|
$dockerVegetationDataset = Convert-ToDockerPath $vegetation.Dataset
|
||||||
|
$dockerVegetationCheckpoint = Convert-ToDockerPath $vegetation.Checkpoint
|
||||||
|
& docker create --name $vegetationName --network none --cpus 8 --memory 10g `
|
||||||
|
--gpus all --read-only --security-opt "no-new-privileges:true" --cap-drop ALL `
|
||||||
|
--pids-limit 512 --tmpfs "/tmp:rw,noexec,nosuid,size=2g" `
|
||||||
|
-e "HOME=/tmp" `
|
||||||
|
--entrypoint conda `
|
||||||
|
--volume ($dockerRelease + ":/release:ro") `
|
||||||
|
--volume ($dockerRun + ":/shared:rw") `
|
||||||
|
--volume ($dockerVegetationDataset + ":/data/goose:ro") `
|
||||||
|
--volume ($dockerVegetationCheckpoint + ":/models/candidate.pth:ro") `
|
||||||
|
$VegetationImageTag run --no-capture-output --name goose python `
|
||||||
|
/release/run_vegetation_integrated_load.py `
|
||||||
|
--config /release/lab-v1-goose-vegetation-benchmark-v1.json `
|
||||||
|
--policy /release/lab-v1-vegetation-mission-policy-v1.json `
|
||||||
|
--provider-map /release/lab-v1-vegetation-provider-label-map-v1.json `
|
||||||
|
--checkpoint /models/candidate.pth `
|
||||||
|
--dataset-root /data/goose `
|
||||||
|
--frames-root /shared/vegetation/input-frames `
|
||||||
|
--source-rate-hz $rate `
|
||||||
|
--minimum-effective-fps 11.209069 `
|
||||||
|
--maximum-completion-p95-ms 125.0 `
|
||||||
|
--shared-start-ready-file /shared/control/vegetation.ready `
|
||||||
|
--shared-start-file /shared/control/start.signal `
|
||||||
|
--frame-ledger /shared/vegetation/frames.jsonl `
|
||||||
|
--output /shared/vegetation/result.json `
|
||||||
|
--release-sha256 $ExpectedArtifactSha256 *> $null
|
||||||
|
Assert-LastExitCode "M49 integrated vegetation creation"
|
||||||
|
}
|
||||||
|
|
||||||
& docker start $graphName *> $null
|
& docker start $graphName *> $null
|
||||||
Assert-LastExitCode "M49 integrated graph start"
|
Assert-LastExitCode "M49 integrated graph start"
|
||||||
& docker start $tgsName *> $null
|
& docker start $tgsName *> $null
|
||||||
Assert-LastExitCode "M49 integrated TGS start"
|
Assert-LastExitCode "M49 integrated TGS start"
|
||||||
|
if ($VegetationLoadGate) {
|
||||||
|
& docker start $vegetationName *> $null
|
||||||
|
Assert-LastExitCode "M49 integrated vegetation start"
|
||||||
|
}
|
||||||
$graphReady = Join-Path $runOutput "control\graph.ready"
|
$graphReady = Join-Path $runOutput "control\graph.ready"
|
||||||
$tgsReady = Join-Path $runOutput "control\tgs.ready"
|
$tgsReady = Join-Path $runOutput "control\tgs.ready"
|
||||||
Wait-SharedReady $graphReady $tgsReady $graphName $tgsName
|
$vegetationReady = if ($VegetationLoadGate) {
|
||||||
|
Join-Path $runOutput "control\vegetation.ready"
|
||||||
|
} else { "" }
|
||||||
|
Wait-SharedReady $graphReady $tgsReady $vegetationReady $graphName $tgsName $vegetationName
|
||||||
[DateTimeOffset]::UtcNow.ToString("o") | Set-Content -LiteralPath (
|
[DateTimeOffset]::UtcNow.ToString("o") | Set-Content -LiteralPath (
|
||||||
Join-Path $runOutput "control\start.signal"
|
Join-Path $runOutput "control\start.signal"
|
||||||
) -Encoding utf8
|
) -Encoding utf8
|
||||||
|
|
||||||
$telemetryPath = Join-Path $runOutput "container-telemetry.jsonl"
|
$telemetryPath = Join-Path $runOutput "container-telemetry.jsonl"
|
||||||
|
$m49TelemetryPath = if ($VegetationLoadGate) {
|
||||||
|
Join-Path $runOutput "m49-container-telemetry.jsonl"
|
||||||
|
} else { $telemetryPath }
|
||||||
while ($true) {
|
while ($true) {
|
||||||
$graphState = Get-Container $graphName
|
$graphState = Get-Container $graphName
|
||||||
$tgsState = Get-Container $tgsName
|
$tgsState = Get-Container $tgsName
|
||||||
|
$vegetationState = if ($VegetationLoadGate) {
|
||||||
|
Get-Container $vegetationName
|
||||||
|
} else { $null }
|
||||||
$running = @()
|
$running = @()
|
||||||
if ($graphState.State.Running) { $running += $graphName }
|
if ($graphState.State.Running) { $running += $graphName }
|
||||||
if ($tgsState.State.Running) { $running += $tgsName }
|
if ($tgsState.State.Running) { $running += $tgsName }
|
||||||
|
if ($VegetationLoadGate -and $vegetationState.State.Running) {
|
||||||
|
$running += $vegetationName
|
||||||
|
}
|
||||||
if ((Get-Container $tritonName).State.Running) { $running += $tritonName }
|
if ((Get-Container $tritonName).State.Running) { $running += $tritonName }
|
||||||
if ($running.Count -gt 0) {
|
if ($running.Count -gt 0) {
|
||||||
$stats = @((& docker stats --no-stream --format "{{json .}}" @running))
|
$stats = @((& docker stats --no-stream --format "{{json .}}" @running))
|
||||||
@@ -362,10 +479,12 @@ try {
|
|||||||
"tgs"
|
"tgs"
|
||||||
} elseif ($value.Name -ceq $tritonName) {
|
} elseif ($value.Name -ceq $tritonName) {
|
||||||
"triton"
|
"triton"
|
||||||
|
} elseif ($VegetationLoadGate -and $value.Name -ceq $vegetationName) {
|
||||||
|
"vegetation"
|
||||||
} else {
|
} else {
|
||||||
throw "Unknown M49 telemetry container"
|
throw "Unknown M49 telemetry container"
|
||||||
}
|
}
|
||||||
[ordered]@{
|
$telemetryRow = [ordered]@{
|
||||||
observed_utc = [DateTimeOffset]::UtcNow.ToString("o")
|
observed_utc = [DateTimeOffset]::UtcNow.ToString("o")
|
||||||
role = $role
|
role = $role
|
||||||
name = [string]$value.Name
|
name = [string]$value.Name
|
||||||
@@ -373,23 +492,46 @@ try {
|
|||||||
memory_usage = [string]$value.MemUsage
|
memory_usage = [string]$value.MemUsage
|
||||||
memory_percent = [string]$value.MemPerc
|
memory_percent = [string]$value.MemPerc
|
||||||
pids = [string]$value.PIDs
|
pids = [string]$value.PIDs
|
||||||
} | ConvertTo-Json -Compress | Out-File -LiteralPath $telemetryPath -Encoding utf8 -Append
|
} | ConvertTo-Json -Compress
|
||||||
|
$telemetryRow | Out-File -LiteralPath $telemetryPath -Encoding utf8 -Append
|
||||||
|
if ($VegetationLoadGate -and $role -cne "vegetation") {
|
||||||
|
$telemetryRow | Out-File -LiteralPath $m49TelemetryPath -Encoding utf8 -Append
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (-not $graphState.State.Running -and -not $tgsState.State.Running) { break }
|
$vegetationStopped = -not $VegetationLoadGate -or -not $vegetationState.State.Running
|
||||||
|
if (
|
||||||
|
-not $graphState.State.Running -and
|
||||||
|
-not $tgsState.State.Running -and
|
||||||
|
$vegetationStopped
|
||||||
|
) { break }
|
||||||
Start-Sleep -Seconds 1
|
Start-Sleep -Seconds 1
|
||||||
}
|
}
|
||||||
$graphExit = [int](Get-Container $graphName).State.ExitCode
|
$graphExit = [int](Get-Container $graphName).State.ExitCode
|
||||||
$tgsExit = [int](Get-Container $tgsName).State.ExitCode
|
$tgsExit = [int](Get-Container $tgsName).State.ExitCode
|
||||||
|
$vegetationExit = if ($VegetationLoadGate) {
|
||||||
|
[int](Get-Container $vegetationName).State.ExitCode
|
||||||
|
} else { 0 }
|
||||||
$previousErrorAction = $ErrorActionPreference
|
$previousErrorAction = $ErrorActionPreference
|
||||||
$ErrorActionPreference = "Continue"
|
$ErrorActionPreference = "Continue"
|
||||||
$graphLogs = & docker logs $graphName 2>&1
|
$graphLogs = & docker logs $graphName 2>&1
|
||||||
$tgsLogs = & docker logs $tgsName 2>&1
|
$tgsLogs = & docker logs $tgsName 2>&1
|
||||||
|
$vegetationLogs = if ($VegetationLoadGate) {
|
||||||
|
& docker logs $vegetationName 2>&1
|
||||||
|
} else { @() }
|
||||||
$ErrorActionPreference = $previousErrorAction
|
$ErrorActionPreference = $previousErrorAction
|
||||||
$graphLogs | Set-Content -LiteralPath (Join-Path $runOutput "graph.log") -Encoding utf8
|
$graphLogs | Set-Content -LiteralPath (Join-Path $runOutput "graph.log") -Encoding utf8
|
||||||
$tgsLogs | Set-Content -LiteralPath (Join-Path $runOutput "tgs.log") -Encoding utf8
|
$tgsLogs | Set-Content -LiteralPath (Join-Path $runOutput "tgs.log") -Encoding utf8
|
||||||
|
if ($VegetationLoadGate) {
|
||||||
|
$vegetationLogs | Set-Content -LiteralPath (
|
||||||
|
Join-Path $runOutput "vegetation.log"
|
||||||
|
) -Encoding utf8
|
||||||
|
}
|
||||||
if ($graphExit -ne 0) { throw "M49 integrated graph failed with exit code $graphExit" }
|
if ($graphExit -ne 0) { throw "M49 integrated graph failed with exit code $graphExit" }
|
||||||
if ($tgsExit -ne 0) { throw "M49 integrated TGS failed with exit code $tgsExit" }
|
if ($tgsExit -ne 0) { throw "M49 integrated TGS failed with exit code $tgsExit" }
|
||||||
|
if ($vegetationExit -ne 0) {
|
||||||
|
throw "M49 integrated vegetation failed with exit code $vegetationExit"
|
||||||
|
}
|
||||||
|
|
||||||
& docker run --rm --name $analyzeName --network none --cpus 8 --memory 16g `
|
& docker run --rm --name $analyzeName --network none --cpus 8 --memory 16g `
|
||||||
--entrypoint python3 `
|
--entrypoint python3 `
|
||||||
@@ -401,6 +543,12 @@ try {
|
|||||||
--output-root /shared/tgs/evidence
|
--output-root /shared/tgs/evidence
|
||||||
Assert-LastExitCode "M49 integrated TGS evidence analysis"
|
Assert-LastExitCode "M49 integrated TGS evidence analysis"
|
||||||
|
|
||||||
|
$m49ResultPath = if ($VegetationLoadGate) {
|
||||||
|
"/shared/m49-result.json"
|
||||||
|
} else { "/shared/result.json" }
|
||||||
|
$dockerM49TelemetryPath = if ($VegetationLoadGate) {
|
||||||
|
"/shared/m49-container-telemetry.jsonl"
|
||||||
|
} else { "/shared/container-telemetry.jsonl" }
|
||||||
& docker run --rm --name $evidenceName --network none --cpus 4 --memory 8g `
|
& docker run --rm --name $evidenceName --network none --cpus 4 --memory 8g `
|
||||||
--entrypoint python3 `
|
--entrypoint python3 `
|
||||||
--volume ($dockerRelease + ":/release:ro") `
|
--volume ($dockerRelease + ":/release:ro") `
|
||||||
@@ -411,11 +559,33 @@ try {
|
|||||||
--graph-frames /shared/graph/frames.jsonl `
|
--graph-frames /shared/graph/frames.jsonl `
|
||||||
--tgs-result /shared/tgs/evidence/result.json `
|
--tgs-result /shared/tgs/evidence/result.json `
|
||||||
--tgs-timing /shared/tgs/tgs-full-timing.tsv `
|
--tgs-timing /shared/tgs/tgs-full-timing.tsv `
|
||||||
--telemetry /shared/container-telemetry.jsonl `
|
--telemetry $dockerM49TelemetryPath `
|
||||||
--output /shared/result.json `
|
--output $m49ResultPath `
|
||||||
--release-sha256 $ExpectedArtifactSha256
|
--release-sha256 $ExpectedArtifactSha256
|
||||||
Assert-LastExitCode "M49 integrated evidence gate"
|
Assert-LastExitCode "M49 integrated evidence gate"
|
||||||
|
|
||||||
|
if ($VegetationLoadGate) {
|
||||||
|
& docker run --rm --name $vegetationEvidenceName --network none --cpus 4 --memory 8g `
|
||||||
|
--entrypoint python3 `
|
||||||
|
--volume ($dockerRelease + ":/release:ro") `
|
||||||
|
--volume ($dockerRun + ":/shared:rw") `
|
||||||
|
$ParityImageTag /release/build_vegetation_integrated_graph_evidence.py `
|
||||||
|
--profile /release/lab-v1-vegetation-integrated-shadow-v1.json `
|
||||||
|
--m49-result /shared/m49-result.json `
|
||||||
|
--graph-frames /shared/graph/frames.jsonl `
|
||||||
|
--tgs-timing /shared/tgs/tgs-full-timing.tsv `
|
||||||
|
--vegetation-result /shared/vegetation/result.json `
|
||||||
|
--vegetation-frames /shared/vegetation/frames.jsonl `
|
||||||
|
--telemetry /shared/container-telemetry.jsonl `
|
||||||
|
--output /shared/result.json `
|
||||||
|
--release-sha256 $ExpectedArtifactSha256
|
||||||
|
Assert-LastExitCode "M49 integrated vegetation evidence gate"
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
$vegetationFrames = Join-Path $runOutput "vegetation\input-frames"
|
||||||
|
if (Test-Path -LiteralPath $vegetationFrames -PathType Container) {
|
||||||
|
Remove-Item -LiteralPath $vegetationFrames -Recurse -Force
|
||||||
|
}
|
||||||
foreach ($name in $containers) { Remove-ExactContainer $name }
|
foreach ($name in $containers) { Remove-ExactContainer $name }
|
||||||
$canonicalAfter = Get-Container "ndc-mission-core-triton"
|
$canonicalAfter = Get-Container "ndc-mission-core-triton"
|
||||||
if (
|
if (
|
||||||
@@ -432,7 +602,11 @@ if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) {
|
|||||||
}
|
}
|
||||||
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
|
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
|
||||||
$summary = [ordered]@{
|
$summary = [ordered]@{
|
||||||
schema_version = "missioncore.m49-tgs-integrated-graph-worker-summary/v1"
|
schema_version = if ($VegetationLoadGate) {
|
||||||
|
"missioncore.lab-v1-vegetation-integrated-worker-summary/v1"
|
||||||
|
} else {
|
||||||
|
"missioncore.m49-tgs-integrated-graph-worker-summary/v1"
|
||||||
|
}
|
||||||
worker_id = "worker-006"
|
worker_id = "worker-006"
|
||||||
run_id = $RunId
|
run_id = $RunId
|
||||||
code_revision = [string]$releaseDocument.code_revision
|
code_revision = [string]$releaseDocument.code_revision
|
||||||
@@ -443,6 +617,7 @@ $summary = [ordered]@{
|
|||||||
free_memory_gib_before = [math]::Round($freeMemoryGiB, 6)
|
free_memory_gib_before = [math]::Round($freeMemoryGiB, 6)
|
||||||
result_id = [string]$result.result_id
|
result_id = [string]$result.result_id
|
||||||
result_status = [string]$result.status
|
result_status = [string]$result.status
|
||||||
|
vegetation_load_gate = [bool]$VegetationLoadGate
|
||||||
canonical_triton_id = $canonicalId
|
canonical_triton_id = $canonicalId
|
||||||
canonical_triton_health = "healthy"
|
canonical_triton_health = "healthy"
|
||||||
gauss_or_playcanvas_action = "none"
|
gauss_or_playcanvas_action = "none"
|
||||||
|
|||||||
+280
@@ -0,0 +1,280 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run source-paced DDRNet beside the frozen M4 graph and TGS shadow."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import platform
|
||||||
|
import statistics
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from PIL import Image
|
||||||
|
from run_goose_vegetation_benchmark import (
|
||||||
|
infer,
|
||||||
|
load_mapping,
|
||||||
|
load_model,
|
||||||
|
percentile,
|
||||||
|
preprocess,
|
||||||
|
read_json,
|
||||||
|
sha256,
|
||||||
|
stable_digest,
|
||||||
|
validate_contracts,
|
||||||
|
)
|
||||||
|
|
||||||
|
SCHEMA = "missioncore.lab-v1-vegetation-integrated-load/v1"
|
||||||
|
FRAME_SCHEMA = "missioncore.lab-v1-vegetation-integrated-frame/v1"
|
||||||
|
FRAME_COUNT = 4_489
|
||||||
|
AUTHORITY = {
|
||||||
|
"ground_truth": False,
|
||||||
|
"candidate_accepted": False,
|
||||||
|
"camera_semantics_can_clear_rigid_geometry": False,
|
||||||
|
"commands_enabled": False,
|
||||||
|
"actuation_allowed": False,
|
||||||
|
"navigation_or_safety_accepted": False,
|
||||||
|
"production_accepted": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IntegratedLoadError(RuntimeError):
|
||||||
|
"""The bounded integrated-load contract is incomplete or changed."""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
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("--frames-root", type=Path, required=True)
|
||||||
|
parser.add_argument("--source-rate-hz", type=float, required=True)
|
||||||
|
parser.add_argument("--minimum-effective-fps", type=float, required=True)
|
||||||
|
parser.add_argument("--maximum-completion-p95-ms", type=float, required=True)
|
||||||
|
parser.add_argument("--shared-start-ready-file", type=Path, required=True)
|
||||||
|
parser.add_argument("--shared-start-file", type=Path, required=True)
|
||||||
|
parser.add_argument("--shared-start-timeout-seconds", type=float, default=600.0)
|
||||||
|
parser.add_argument("--frame-ledger", type=Path, required=True)
|
||||||
|
parser.add_argument("--output", type=Path, required=True)
|
||||||
|
parser.add_argument("--release-sha256", required=True)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_shared_start(ready_file: Path, start_file: Path, timeout_seconds: float) -> None:
|
||||||
|
if ready_file.exists():
|
||||||
|
raise IntegratedLoadError("shared-start ready file already exists")
|
||||||
|
ready_file.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
|
ready_file.write_text("ready\n", encoding="utf-8")
|
||||||
|
deadline = time.monotonic() + timeout_seconds
|
||||||
|
while not start_file.is_file():
|
||||||
|
if time.monotonic() >= deadline:
|
||||||
|
raise IntegratedLoadError("shared-start barrier timed out")
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def distribution(values: list[float]) -> dict[str, float]:
|
||||||
|
return {
|
||||||
|
"mean": round(statistics.fmean(values), 6),
|
||||||
|
"p50": round(percentile(values, 0.50), 6),
|
||||||
|
"p95": round(percentile(values, 0.95), 6),
|
||||||
|
"p99": round(percentile(values, 0.99), 6),
|
||||||
|
"maximum": round(max(values), 6),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def exact_frames(root: Path) -> list[Path]:
|
||||||
|
if root.is_symlink() or not root.is_dir():
|
||||||
|
raise IntegratedLoadError("RAVNOVES frame root is unavailable")
|
||||||
|
frames = sorted(root.glob("frame-*.png"))
|
||||||
|
expected = [f"frame-{sequence + 1:06d}.png" for sequence in range(FRAME_COUNT)]
|
||||||
|
if len(frames) != FRAME_COUNT or [frame.name for frame in frames] != expected:
|
||||||
|
raise IntegratedLoadError("RAVNOVES full-video frame sequence changed")
|
||||||
|
return frames
|
||||||
|
|
||||||
|
|
||||||
|
def validate_sha256(value: str, label: str) -> None:
|
||||||
|
if len(value) != 64 or any(character not in "0123456789abcdef" for character in value):
|
||||||
|
raise IntegratedLoadError(f"{label} SHA-256 is invalid")
|
||||||
|
|
||||||
|
|
||||||
|
def run() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
if not torch.cuda.is_available():
|
||||||
|
raise IntegratedLoadError("CUDA is required for Worker 006 qualification")
|
||||||
|
if (
|
||||||
|
not math.isfinite(args.source_rate_hz)
|
||||||
|
or args.source_rate_hz <= 0
|
||||||
|
or args.minimum_effective_fps <= 0
|
||||||
|
or args.maximum_completion_p95_ms <= 0
|
||||||
|
or args.shared_start_timeout_seconds <= 0
|
||||||
|
):
|
||||||
|
raise IntegratedLoadError("integrated-load thresholds must be positive and finite")
|
||||||
|
validate_sha256(args.release_sha256, "release")
|
||||||
|
if args.output.exists() or args.frame_ledger.exists():
|
||||||
|
raise IntegratedLoadError("integrated-load output already exists")
|
||||||
|
|
||||||
|
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")
|
||||||
|
if args.checkpoint.is_symlink() or not args.checkpoint.is_file():
|
||||||
|
raise IntegratedLoadError("DDRNet checkpoint is unavailable")
|
||||||
|
if args.checkpoint.stat().st_size != candidate["checkpoint_size_bytes"]:
|
||||||
|
raise IntegratedLoadError("DDRNet checkpoint size changed")
|
||||||
|
checkpoint_sha256 = sha256(args.checkpoint)
|
||||||
|
if checkpoint_sha256 != candidate["checkpoint_sha256"]:
|
||||||
|
raise IntegratedLoadError("DDRNet checkpoint digest changed")
|
||||||
|
mapping_path = args.dataset_root / config["dataset"]["mapping_relative_path"]
|
||||||
|
load_mapping(mapping_path, config["dataset"]["mapping_sha256"])
|
||||||
|
frames = exact_frames(args.frames_root)
|
||||||
|
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
model, model_name, architecture_failures = load_model("ddrnet", args.checkpoint)
|
||||||
|
with Image.open(frames[0]) as image:
|
||||||
|
warmup_tensor, _ = preprocess(image.convert("RGB"))
|
||||||
|
warmup_latencies_ms = [infer(model, warmup_tensor)[1] for _ in range(3)]
|
||||||
|
torch.cuda.reset_peak_memory_stats()
|
||||||
|
wait_for_shared_start(
|
||||||
|
args.shared_start_ready_file,
|
||||||
|
args.shared_start_file,
|
||||||
|
args.shared_start_timeout_seconds,
|
||||||
|
)
|
||||||
|
|
||||||
|
interval_ns = 1_000_000_000.0 / args.source_rate_hz
|
||||||
|
start_ns = time.monotonic_ns()
|
||||||
|
started_utc_ns = time.time_ns()
|
||||||
|
completion_ages_ms: list[float] = []
|
||||||
|
stage_latencies_ms: list[float] = []
|
||||||
|
inference_latencies_ms: list[float] = []
|
||||||
|
late_deadline_count = 0
|
||||||
|
args.frame_ledger.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with args.frame_ledger.open("x", encoding="utf-8") as ledger:
|
||||||
|
for sequence, frame in enumerate(frames):
|
||||||
|
scheduled_ns = start_ns + round(sequence * interval_ns)
|
||||||
|
remaining_ns = scheduled_ns - time.monotonic_ns()
|
||||||
|
if remaining_ns > 0:
|
||||||
|
time.sleep(remaining_ns / 1_000_000_000.0)
|
||||||
|
admitted_ns = time.monotonic_ns()
|
||||||
|
with Image.open(frame) as image:
|
||||||
|
source = image.convert("RGB")
|
||||||
|
if source.size != (
|
||||||
|
config["ravnoves"]["expected_width"],
|
||||||
|
config["ravnoves"]["expected_height"],
|
||||||
|
):
|
||||||
|
raise IntegratedLoadError("RAVNOVES video frame dimensions changed")
|
||||||
|
tensor, _ = preprocess(source)
|
||||||
|
_, inference_ms = infer(model, tensor)
|
||||||
|
completed_ns = time.monotonic_ns()
|
||||||
|
completion_age_ms = (completed_ns - scheduled_ns) / 1_000_000.0
|
||||||
|
stage_ms = (completed_ns - admitted_ns) / 1_000_000.0
|
||||||
|
completion_ages_ms.append(completion_age_ms)
|
||||||
|
stage_latencies_ms.append(stage_ms)
|
||||||
|
inference_latencies_ms.append(inference_ms)
|
||||||
|
if sequence + 1 < FRAME_COUNT and completed_ns > start_ns + round(
|
||||||
|
(sequence + 1) * interval_ns
|
||||||
|
):
|
||||||
|
late_deadline_count += 1
|
||||||
|
row = {
|
||||||
|
"schema_version": FRAME_SCHEMA,
|
||||||
|
"sequence": sequence,
|
||||||
|
"frame_name": frame.name,
|
||||||
|
"scheduled_monotonic_ns": scheduled_ns,
|
||||||
|
"admitted_monotonic_ns": admitted_ns,
|
||||||
|
"completed_monotonic_ns": completed_ns,
|
||||||
|
"completion_age_ms": round(completion_age_ms, 6),
|
||||||
|
"stage_ms": round(stage_ms, 6),
|
||||||
|
"inference_ms": round(inference_ms, 6),
|
||||||
|
}
|
||||||
|
ledger.write(json.dumps(row, sort_keys=True, separators=(",", ":")) + "\n")
|
||||||
|
if sequence % 64 == 0:
|
||||||
|
ledger.flush()
|
||||||
|
|
||||||
|
completed_ns = time.monotonic_ns()
|
||||||
|
wall_seconds = (completed_ns - start_ns) / 1_000_000_000.0
|
||||||
|
effective_fps = FRAME_COUNT / wall_seconds
|
||||||
|
completion = distribution(completion_ages_ms)
|
||||||
|
checks = {
|
||||||
|
"all_frames_accounted": len(completion_ages_ms) == FRAME_COUNT,
|
||||||
|
"minimum_effective_fps": effective_fps >= args.minimum_effective_fps,
|
||||||
|
"maximum_completion_p95_ms": completion["p95"]
|
||||||
|
<= args.maximum_completion_p95_ms,
|
||||||
|
"zero_capacity_drops": len(completion_ages_ms) == FRAME_COUNT,
|
||||||
|
"authority_remains_false": all(value is False for value in AUTHORITY.values()),
|
||||||
|
}
|
||||||
|
result: dict[str, Any] = {
|
||||||
|
"schema_version": SCHEMA,
|
||||||
|
"worker_id": "worker-006",
|
||||||
|
"source": {
|
||||||
|
"source_id": config["ravnoves"]["source_id"],
|
||||||
|
"frame_count": FRAME_COUNT,
|
||||||
|
"requested_source_rate_hz": args.source_rate_hz,
|
||||||
|
"raw_fisheye_immutable": True,
|
||||||
|
"ground_truth_available": False,
|
||||||
|
},
|
||||||
|
"candidate": {
|
||||||
|
"candidate_id": candidate["candidate_id"],
|
||||||
|
"candidate_key": "ddrnet",
|
||||||
|
"loaded_model_name": model_name,
|
||||||
|
"architecture_probe_failures": architecture_failures,
|
||||||
|
"checkpoint_size_bytes": args.checkpoint.stat().st_size,
|
||||||
|
"checkpoint_sha256": checkpoint_sha256,
|
||||||
|
},
|
||||||
|
"execution": {
|
||||||
|
"run_mode": "source-paced-integrated-shadow/v1",
|
||||||
|
"started_utc_ns": started_utc_ns,
|
||||||
|
"wall_seconds": round(wall_seconds, 6),
|
||||||
|
"effective_fps": round(effective_fps, 6),
|
||||||
|
"frame_count": FRAME_COUNT,
|
||||||
|
"capacity_drop_count": 0,
|
||||||
|
"deadline_miss_count": late_deadline_count,
|
||||||
|
"frame_ledger": {
|
||||||
|
"path": args.frame_ledger.name,
|
||||||
|
"rows": FRAME_COUNT,
|
||||||
|
"sha256": sha256(args.frame_ledger),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"timing": {
|
||||||
|
"prewarm_inference_count": len(warmup_latencies_ms),
|
||||||
|
"prewarm_latency_ms_first": round(warmup_latencies_ms[0], 6),
|
||||||
|
"prewarm_latency_ms_last": round(warmup_latencies_ms[-1], 6),
|
||||||
|
"completion_age_ms": completion,
|
||||||
|
"stage_ms": distribution(stage_latencies_ms),
|
||||||
|
"inference_ms": distribution(inference_latencies_ms),
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"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(),
|
||||||
|
},
|
||||||
|
"identity": {
|
||||||
|
"release_sha256": args.release_sha256,
|
||||||
|
"config_sha256": sha256(args.config),
|
||||||
|
"policy_sha256": sha256(args.policy),
|
||||||
|
"provider_map_sha256": sha256(args.provider_map),
|
||||||
|
"runner_sha256": sha256(Path(__file__)),
|
||||||
|
},
|
||||||
|
"predeclared_thresholds": {
|
||||||
|
"minimum_effective_fps": args.minimum_effective_fps,
|
||||||
|
"maximum_completion_p95_ms": args.maximum_completion_p95_ms,
|
||||||
|
"capacity_drop_count_max": 0,
|
||||||
|
},
|
||||||
|
"checks": checks,
|
||||||
|
"integrated_load_gate_passed": all(checks.values()),
|
||||||
|
"authority": AUTHORITY,
|
||||||
|
}
|
||||||
|
result["result_id"] = f"lab-v1-vegetation-integrated-{stable_digest(result)}"
|
||||||
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
print(json.dumps({"result_id": result["result_id"], "passed": all(checks.values())}))
|
||||||
|
return 0 if all(checks.values()) else 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(run())
|
||||||
+366
@@ -0,0 +1,366 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Seal the synchronized RF-DETR, TGS and DDRNet Worker 006 load gate."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
PROFILE_SCHEMA = "missioncore.lab-v1-vegetation-integrated-shadow-profile/v1"
|
||||||
|
M49_SCHEMA = "missioncore.m49-tgs-integrated-graph-shadow-result/v1"
|
||||||
|
VEGETATION_SCHEMA = "missioncore.lab-v1-vegetation-integrated-load/v1"
|
||||||
|
RESULT_SCHEMA = "missioncore.lab-v1-vegetation-integrated-shadow-result/v1"
|
||||||
|
FRAME_COUNT = 4_489
|
||||||
|
|
||||||
|
|
||||||
|
class VegetationIntegratedError(RuntimeError):
|
||||||
|
"""The synchronized three-layer load evidence is incomplete."""
|
||||||
|
|
||||||
|
|
||||||
|
def canonical_json(value: object) -> bytes:
|
||||||
|
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
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 load_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 VegetationIntegratedError(f"{label} is unreadable") from exc
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise VegetationIntegratedError(f"{label} is not an object")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def distribution(values: list[float]) -> dict[str, float]:
|
||||||
|
if not values:
|
||||||
|
raise VegetationIntegratedError("timing distribution is empty")
|
||||||
|
array = np.asarray(values, dtype=np.float64)
|
||||||
|
return {
|
||||||
|
"mean": round(float(array.mean()), 6),
|
||||||
|
"p50": round(float(np.percentile(array, 50)), 6),
|
||||||
|
"p95": round(float(np.percentile(array, 95)), 6),
|
||||||
|
"p99": round(float(np.percentile(array, 99)), 6),
|
||||||
|
"maximum": round(float(array.max()), 6),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def graph_completion_ages(path: Path) -> list[float]:
|
||||||
|
values: list[float] = []
|
||||||
|
with path.open("r", encoding="utf-8") as stream:
|
||||||
|
for expected, line in enumerate(stream):
|
||||||
|
row = json.loads(line)
|
||||||
|
if row.get("source_envelope", {}).get("sequence") != expected:
|
||||||
|
raise VegetationIntegratedError("graph frame sequence changed")
|
||||||
|
age = row.get("completion_age_ns")
|
||||||
|
if not isinstance(age, int) or age < 0:
|
||||||
|
raise VegetationIntegratedError("graph completion age is invalid")
|
||||||
|
values.append(age / 1_000_000.0)
|
||||||
|
if len(values) != FRAME_COUNT:
|
||||||
|
raise VegetationIntegratedError("graph frame ledger is incomplete")
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def tgs_completion_ages(path: Path) -> list[float]:
|
||||||
|
values: list[float] = []
|
||||||
|
with path.open("r", encoding="utf-8", newline="") as stream:
|
||||||
|
for expected, row in enumerate(csv.DictReader(stream, delimiter="\t")):
|
||||||
|
if int(row["timeline_frame_index"]) != expected:
|
||||||
|
raise VegetationIntegratedError("TGS timing sequence changed")
|
||||||
|
age = float(row["completion_age_ms"])
|
||||||
|
if not math.isfinite(age) or age < 0:
|
||||||
|
raise VegetationIntegratedError("TGS completion age is invalid")
|
||||||
|
values.append(age)
|
||||||
|
if len(values) != FRAME_COUNT:
|
||||||
|
raise VegetationIntegratedError("TGS timing ledger is incomplete")
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def vegetation_completion_ages(path: Path) -> list[float]:
|
||||||
|
values: list[float] = []
|
||||||
|
with path.open("r", encoding="utf-8") as stream:
|
||||||
|
for expected, line in enumerate(stream):
|
||||||
|
row = json.loads(line)
|
||||||
|
if row.get("schema_version") != "missioncore.lab-v1-vegetation-integrated-frame/v1":
|
||||||
|
raise VegetationIntegratedError("vegetation frame schema changed")
|
||||||
|
if row.get("sequence") != expected:
|
||||||
|
raise VegetationIntegratedError("vegetation frame sequence changed")
|
||||||
|
age = row.get("completion_age_ms")
|
||||||
|
if not isinstance(age, (int, float)) or not math.isfinite(age) or age < 0:
|
||||||
|
raise VegetationIntegratedError("vegetation completion age is invalid")
|
||||||
|
values.append(float(age))
|
||||||
|
if len(values) != FRAME_COUNT:
|
||||||
|
raise VegetationIntegratedError("vegetation frame ledger is incomplete")
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
_SIZE = re.compile(r"^\s*([0-9.]+)\s*([kmgt]?i?b)\s*$", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
def size_mib(value: str) -> float:
|
||||||
|
match = _SIZE.fullmatch(value)
|
||||||
|
if match is None:
|
||||||
|
raise VegetationIntegratedError("container memory telemetry is invalid")
|
||||||
|
number = float(match.group(1))
|
||||||
|
scale = {
|
||||||
|
"b": 1.0 / (1024.0 * 1024.0),
|
||||||
|
"kb": 1.0 / 1024.0,
|
||||||
|
"kib": 1.0 / 1024.0,
|
||||||
|
"mb": 1.0,
|
||||||
|
"mib": 1.0,
|
||||||
|
"gb": 1024.0,
|
||||||
|
"gib": 1024.0,
|
||||||
|
"tb": 1024.0 * 1024.0,
|
||||||
|
"tib": 1024.0 * 1024.0,
|
||||||
|
}[match.group(2).lower()]
|
||||||
|
return number * scale
|
||||||
|
|
||||||
|
|
||||||
|
def host_telemetry(path: Path) -> dict[str, object]:
|
||||||
|
roles = ("graph", "tgs", "triton", "vegetation")
|
||||||
|
samples: dict[str, list[dict[str, float]]] = defaultdict(list)
|
||||||
|
with path.open("r", encoding="utf-8-sig") as stream:
|
||||||
|
for line in stream:
|
||||||
|
row = json.loads(line)
|
||||||
|
role = row.get("role")
|
||||||
|
if role not in roles:
|
||||||
|
raise VegetationIntegratedError("container telemetry role changed")
|
||||||
|
cpu = row.get("cpu_percent")
|
||||||
|
memory = row.get("memory_usage")
|
||||||
|
memory_percent = row.get("memory_percent")
|
||||||
|
if not all(isinstance(value, str) for value in (cpu, memory, memory_percent)):
|
||||||
|
raise VegetationIntegratedError("container telemetry row is incomplete")
|
||||||
|
assert isinstance(cpu, str) and isinstance(memory, str)
|
||||||
|
assert isinstance(memory_percent, str)
|
||||||
|
samples[role].append(
|
||||||
|
{
|
||||||
|
"cpu_percent": float(cpu.rstrip("%")),
|
||||||
|
"memory_used_mib": size_mib(memory.split("/", 1)[0].strip()),
|
||||||
|
"memory_percent": float(memory_percent.rstrip("%")),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if any(not samples[role] for role in roles):
|
||||||
|
raise VegetationIntegratedError("container telemetry does not cover every runtime role")
|
||||||
|
return {
|
||||||
|
role: {
|
||||||
|
"sample_count": len(samples[role]),
|
||||||
|
"cpu_percent": distribution([row["cpu_percent"] for row in samples[role]]),
|
||||||
|
"memory_used_mib": distribution(
|
||||||
|
[row["memory_used_mib"] for row in samples[role]]
|
||||||
|
),
|
||||||
|
"memory_percent": distribution(
|
||||||
|
[row["memory_percent"] for row in samples[role]]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for role in roles
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build(
|
||||||
|
*,
|
||||||
|
profile_path: Path,
|
||||||
|
m49_result_path: Path,
|
||||||
|
graph_frames_path: Path,
|
||||||
|
tgs_timing_path: Path,
|
||||||
|
vegetation_result_path: Path,
|
||||||
|
vegetation_frames_path: Path,
|
||||||
|
telemetry_path: Path,
|
||||||
|
output_path: Path,
|
||||||
|
release_sha256: str,
|
||||||
|
) -> dict[str, object]:
|
||||||
|
if output_path.exists():
|
||||||
|
raise VegetationIntegratedError("integrated vegetation result already exists")
|
||||||
|
if len(release_sha256) != 64 or any(
|
||||||
|
character not in "0123456789abcdef" for character in release_sha256
|
||||||
|
):
|
||||||
|
raise VegetationIntegratedError("release SHA-256 is invalid")
|
||||||
|
profile = load_json(profile_path, "integrated vegetation profile")
|
||||||
|
m49 = load_json(m49_result_path, "M49 integrated result")
|
||||||
|
vegetation = load_json(vegetation_result_path, "vegetation load result")
|
||||||
|
if profile.get("schema_version") != PROFILE_SCHEMA:
|
||||||
|
raise VegetationIntegratedError("integrated vegetation profile schema changed")
|
||||||
|
if m49.get("schema_version") != M49_SCHEMA:
|
||||||
|
raise VegetationIntegratedError("M49 integrated result schema changed")
|
||||||
|
if vegetation.get("schema_version") != VEGETATION_SCHEMA:
|
||||||
|
raise VegetationIntegratedError("vegetation load result schema changed")
|
||||||
|
|
||||||
|
graph_ages = graph_completion_ages(graph_frames_path)
|
||||||
|
tgs_ages = tgs_completion_ages(tgs_timing_path)
|
||||||
|
vegetation_ages = vegetation_completion_ages(vegetation_frames_path)
|
||||||
|
combined_ages = [
|
||||||
|
max(graph, tgs, semantic)
|
||||||
|
for graph, tgs, semantic in zip(
|
||||||
|
graph_ages, tgs_ages, vegetation_ages, strict=True
|
||||||
|
)
|
||||||
|
]
|
||||||
|
combined = distribution(combined_ages)
|
||||||
|
telemetry = host_telemetry(telemetry_path)
|
||||||
|
acceptance = profile["acceptance"]
|
||||||
|
vegetation_execution = vegetation.get("execution", {})
|
||||||
|
vegetation_timing = vegetation.get("timing", {})
|
||||||
|
vegetation_identity = vegetation.get("identity", {})
|
||||||
|
vegetation_candidate = vegetation.get("candidate", {})
|
||||||
|
m49_performance = m49.get("performance", {})
|
||||||
|
m49_accounting = m49.get("accounting", {})
|
||||||
|
checks = {
|
||||||
|
"base_m49_runtime_passed": (
|
||||||
|
m49.get("status") == "passed"
|
||||||
|
and m49.get("integrated_runtime_gate_passed") is True
|
||||||
|
and m49.get("identity", {}).get("profile_sha256")
|
||||||
|
== profile["stages"]["m49_graph_tgs"]["profile_sha256"]
|
||||||
|
),
|
||||||
|
"vegetation_identity_frozen": (
|
||||||
|
vegetation_candidate.get("candidate_key") == "ddrnet"
|
||||||
|
and vegetation_candidate.get("checkpoint_sha256")
|
||||||
|
== profile["stages"]["vegetation"]["checkpoint_sha256"]
|
||||||
|
and vegetation_identity.get("config_sha256")
|
||||||
|
== profile["stages"]["vegetation"]["config_sha256"]
|
||||||
|
and vegetation_identity.get("policy_sha256")
|
||||||
|
== profile["stages"]["vegetation"]["policy_sha256"]
|
||||||
|
and vegetation_identity.get("provider_map_sha256")
|
||||||
|
== profile["stages"]["vegetation"]["provider_map_sha256"]
|
||||||
|
),
|
||||||
|
"requested_source_rate_preserved": (
|
||||||
|
vegetation.get("source", {}).get("requested_source_rate_hz")
|
||||||
|
== profile["source"]["requested_source_rate_hz"]
|
||||||
|
),
|
||||||
|
"exact_three_layer_sequence_join": len(combined_ages) == FRAME_COUNT,
|
||||||
|
"all_graph_frames_delivered": (
|
||||||
|
m49_accounting.get("graph_admitted") == FRAME_COUNT
|
||||||
|
and m49_accounting.get("graph_delivered") == FRAME_COUNT
|
||||||
|
),
|
||||||
|
"all_tgs_frames_accounted": m49_accounting.get("tgs_timeline_frames")
|
||||||
|
== FRAME_COUNT,
|
||||||
|
"all_vegetation_frames_accounted": vegetation_execution.get("frame_count")
|
||||||
|
== FRAME_COUNT,
|
||||||
|
"minimum_graph_world_state_fps": float(
|
||||||
|
m49_performance.get("effective_world_state_fps", 0.0)
|
||||||
|
)
|
||||||
|
>= float(acceptance["minimum_graph_world_state_fps"]),
|
||||||
|
"minimum_vegetation_fps": float(vegetation_execution.get("effective_fps", 0.0))
|
||||||
|
>= float(acceptance["minimum_vegetation_fps"]),
|
||||||
|
"maximum_vegetation_completion_p95_ms": float(
|
||||||
|
vegetation_timing.get("completion_age_ms", {}).get("p95", math.inf)
|
||||||
|
)
|
||||||
|
<= float(acceptance["maximum_vegetation_completion_p95_ms"]),
|
||||||
|
"maximum_combined_output_age_p99_ms": combined["p99"]
|
||||||
|
<= float(acceptance["maximum_combined_output_age_p99_ms"]),
|
||||||
|
"zero_capacity_drops": (
|
||||||
|
int(m49_accounting.get("tgs_capacity_drops", -1)) == 0
|
||||||
|
and int(vegetation_execution.get("capacity_drop_count", -1)) == 0
|
||||||
|
),
|
||||||
|
"host_resource_telemetry_complete": all(
|
||||||
|
telemetry[role]["sample_count"] > 0
|
||||||
|
for role in ("graph", "tgs", "triton", "vegetation")
|
||||||
|
),
|
||||||
|
"authority_remains_false": (
|
||||||
|
all(value is False for value in profile["authority"].values())
|
||||||
|
and all(value is False for value in vegetation.get("authority", {}).values())
|
||||||
|
),
|
||||||
|
}
|
||||||
|
files = {
|
||||||
|
label: {"bytes": path.stat().st_size, "sha256": sha256_file(path)}
|
||||||
|
for label, path in (
|
||||||
|
("m49-result.json", m49_result_path),
|
||||||
|
("graph-frames.jsonl", graph_frames_path),
|
||||||
|
("tgs-timing.tsv", tgs_timing_path),
|
||||||
|
("vegetation-result.json", vegetation_result_path),
|
||||||
|
("vegetation-frames.jsonl", vegetation_frames_path),
|
||||||
|
("container-telemetry.jsonl", telemetry_path),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
document: dict[str, object] = {
|
||||||
|
"schema_version": RESULT_SCHEMA,
|
||||||
|
"profile_id": profile["profile_id"],
|
||||||
|
"status": "passed" if all(checks.values()) else "failed",
|
||||||
|
"source": {
|
||||||
|
"source_id": profile["source"]["source_id"],
|
||||||
|
"requested_source_rate_hz": profile["source"]["requested_source_rate_hz"],
|
||||||
|
"joined_frame_count": len(combined_ages),
|
||||||
|
"ground_truth_available": False,
|
||||||
|
},
|
||||||
|
"identity": {
|
||||||
|
"release_sha256": release_sha256,
|
||||||
|
"profile_sha256": sha256_file(profile_path),
|
||||||
|
"m49_result_id": m49.get("result_id"),
|
||||||
|
"vegetation_result_id": vegetation.get("result_id"),
|
||||||
|
},
|
||||||
|
"performance": {
|
||||||
|
"graph_tgs": m49_performance,
|
||||||
|
"vegetation": {
|
||||||
|
"effective_fps": vegetation_execution.get("effective_fps"),
|
||||||
|
"completion_age_ms": vegetation_timing.get("completion_age_ms"),
|
||||||
|
"stage_ms": vegetation_timing.get("stage_ms"),
|
||||||
|
"inference_ms": vegetation_timing.get("inference_ms"),
|
||||||
|
"resource": vegetation.get("resource"),
|
||||||
|
},
|
||||||
|
"three_layer_output_age_ms": combined,
|
||||||
|
"host_containers": telemetry,
|
||||||
|
},
|
||||||
|
"accounting": {
|
||||||
|
"graph_frames": m49_accounting.get("graph_delivered"),
|
||||||
|
"tgs_frames": m49_accounting.get("tgs_timeline_frames"),
|
||||||
|
"vegetation_frames": vegetation_execution.get("frame_count"),
|
||||||
|
"capacity_drop_count": int(m49_accounting.get("tgs_capacity_drops", 0))
|
||||||
|
+ int(vegetation_execution.get("capacity_drop_count", 0)),
|
||||||
|
},
|
||||||
|
"checks": checks,
|
||||||
|
"integrated_runtime_gate_passed": all(checks.values()),
|
||||||
|
"visual_quality_accepted": False,
|
||||||
|
"route_truth_available": False,
|
||||||
|
"production_accepted": False,
|
||||||
|
"authority": profile["authority"],
|
||||||
|
"files": files,
|
||||||
|
}
|
||||||
|
identity = hashlib.sha256(canonical_json(document)).hexdigest()
|
||||||
|
document["result_id"] = f"lab-v1-vegetation-integrated-shadow-{identity}"
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path.write_text(json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||||
|
return document
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--profile", type=Path, required=True)
|
||||||
|
parser.add_argument("--m49-result", type=Path, required=True)
|
||||||
|
parser.add_argument("--graph-frames", type=Path, required=True)
|
||||||
|
parser.add_argument("--tgs-timing", type=Path, required=True)
|
||||||
|
parser.add_argument("--vegetation-result", type=Path, required=True)
|
||||||
|
parser.add_argument("--vegetation-frames", type=Path, required=True)
|
||||||
|
parser.add_argument("--telemetry", type=Path, required=True)
|
||||||
|
parser.add_argument("--output", type=Path, required=True)
|
||||||
|
parser.add_argument("--release-sha256", required=True)
|
||||||
|
arguments = parser.parse_args()
|
||||||
|
result = build(
|
||||||
|
profile_path=arguments.profile,
|
||||||
|
m49_result_path=arguments.m49_result,
|
||||||
|
graph_frames_path=arguments.graph_frames,
|
||||||
|
tgs_timing_path=arguments.tgs_timing,
|
||||||
|
vegetation_result_path=arguments.vegetation_result,
|
||||||
|
vegetation_frames_path=arguments.vegetation_frames,
|
||||||
|
telemetry_path=arguments.telemetry,
|
||||||
|
output_path=arguments.output,
|
||||||
|
release_sha256=arguments.release_sha256,
|
||||||
|
)
|
||||||
|
print(json.dumps({"result_id": result["result_id"], "status": result["status"]}))
|
||||||
|
return 0 if result["status"] == "passed" else 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Build a clean-revision Worker 006 release for the DDRNet + M49 load gate."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SCRIPT_ROOT = Path(__file__).resolve().parent
|
||||||
|
if str(SCRIPT_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(SCRIPT_ROOT))
|
||||||
|
|
||||||
|
from build_m49_tgs_integrated_graph_worker_artifact import ( # noqa: E402
|
||||||
|
PATCH_ID,
|
||||||
|
REPOSITORY_ROOT,
|
||||||
|
WHEEL_NAME,
|
||||||
|
ArtifactBuildError,
|
||||||
|
build_wheel,
|
||||||
|
git_revision,
|
||||||
|
materialize_revision,
|
||||||
|
sha256_file,
|
||||||
|
write_archive,
|
||||||
|
)
|
||||||
|
from build_m49_tgs_integrated_graph_worker_artifact import ( # noqa: E402
|
||||||
|
SOURCES as M49_SOURCES,
|
||||||
|
)
|
||||||
|
|
||||||
|
SOURCES = M49_SOURCES + (
|
||||||
|
Path(
|
||||||
|
"experiments/perception/worker/lab_v1_vegetation_goose/"
|
||||||
|
"run_goose_vegetation_benchmark.py"
|
||||||
|
),
|
||||||
|
Path(
|
||||||
|
"experiments/perception/worker/lab_v1_vegetation_goose/"
|
||||||
|
"run_vegetation_integrated_load.py"
|
||||||
|
),
|
||||||
|
Path(
|
||||||
|
"experiments/perception/worker/m49_t3_travel/"
|
||||||
|
"build_vegetation_integrated_graph_evidence.py"
|
||||||
|
),
|
||||||
|
Path("config/perception/lab-v1-goose-vegetation-benchmark-v1.json"),
|
||||||
|
Path("config/perception/lab-v1-vegetation-mission-policy-v1.json"),
|
||||||
|
Path("config/perception/lab-v1-vegetation-provider-label-map-v1.json"),
|
||||||
|
Path("config/perception/lab-v1-vegetation-integrated-shadow-v1.json"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_artifact(
|
||||||
|
patch_id: str,
|
||||||
|
output_directory: Path,
|
||||||
|
*,
|
||||||
|
revision: str | None = None,
|
||||||
|
source_root: Path | None = None,
|
||||||
|
) -> dict[str, object]:
|
||||||
|
if PATCH_ID.fullmatch(patch_id) is None:
|
||||||
|
raise ArtifactBuildError("patch id is invalid")
|
||||||
|
selected_revision = revision or git_revision()
|
||||||
|
if re.fullmatch(r"[a-f0-9]{40}", selected_revision) is None:
|
||||||
|
raise ArtifactBuildError("artifact revision is invalid")
|
||||||
|
with tempfile.TemporaryDirectory(prefix="mission-core-vegetation-integrated-") as directory:
|
||||||
|
stage = Path(directory)
|
||||||
|
snapshot = source_root
|
||||||
|
if snapshot is None:
|
||||||
|
snapshot = stage / "source"
|
||||||
|
materialize_revision(selected_revision, snapshot)
|
||||||
|
sources = tuple(snapshot / relative for relative in SOURCES)
|
||||||
|
if any(path.is_symlink() or not path.is_file() for path in sources):
|
||||||
|
raise ArtifactBuildError("release input is not a regular file")
|
||||||
|
payload = stage / "payload"
|
||||||
|
payload.mkdir()
|
||||||
|
wheel = build_wheel(snapshot, stage / "wheel")
|
||||||
|
copied: list[Path] = []
|
||||||
|
for source in sources:
|
||||||
|
destination = payload / source.name
|
||||||
|
if destination.exists():
|
||||||
|
raise ArtifactBuildError("release payload file names are not unique")
|
||||||
|
destination.write_bytes(source.read_bytes())
|
||||||
|
copied.append(destination)
|
||||||
|
wheel_destination = payload / WHEEL_NAME
|
||||||
|
wheel_destination.write_bytes(wheel.read_bytes())
|
||||||
|
copied.append(wheel_destination)
|
||||||
|
release = {
|
||||||
|
"schema_version": "missioncore.lab-v1-vegetation-integrated-worker-release/v1",
|
||||||
|
"patch_id": patch_id,
|
||||||
|
"transition": "lab-v1-vegetation-m49-integrated-shadow/v1",
|
||||||
|
"code_revision": selected_revision,
|
||||||
|
"worker_id": "worker-006",
|
||||||
|
"source_pack_sha256": (
|
||||||
|
"0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944"
|
||||||
|
),
|
||||||
|
"expected_frames": 4489,
|
||||||
|
"requested_source_rate_hz": 12.0,
|
||||||
|
"native_engine_sha256": (
|
||||||
|
"b8a40b3580edff001ec9680de68707242294ff590ab296000fae371f1083f695"
|
||||||
|
),
|
||||||
|
"ddrnet_checkpoint_sha256": (
|
||||||
|
"b99c2838051bcd7b092fd3970aa62a77d5c0bbb809c9b9afb2ff4b0ebdaa4ee6"
|
||||||
|
),
|
||||||
|
"images": {
|
||||||
|
"travel": (
|
||||||
|
"sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
|
||||||
|
),
|
||||||
|
"parity": (
|
||||||
|
"sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
|
||||||
|
),
|
||||||
|
"runtime": (
|
||||||
|
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||||
|
),
|
||||||
|
"vegetation": (
|
||||||
|
"sha256:591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"authority": {
|
||||||
|
"visual_quality_accepted": False,
|
||||||
|
"route_truth_available": False,
|
||||||
|
"traversability_accepted": False,
|
||||||
|
"physical_free_space_accepted": False,
|
||||||
|
"commands_enabled": False,
|
||||||
|
"actuation_allowed": False,
|
||||||
|
"navigation_or_safety_accepted": False,
|
||||||
|
"production_accepted": False,
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"gauss_or_playcanvas_action": "none",
|
||||||
|
"durable_worker_action": "none",
|
||||||
|
"canonical_triton_action": "none",
|
||||||
|
"heavy_vegetation_candidates": ["ddrnet"],
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
path.name: {"sha256": sha256_file(path), "bytes": path.stat().st_size}
|
||||||
|
for path in sorted(copied)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
release_path = payload / "release.json"
|
||||||
|
release_path.write_text(
|
||||||
|
json.dumps(release, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
payload_files = sorted((*release["files"], release_path.name))
|
||||||
|
(stage / "manifest.env").write_text(
|
||||||
|
f"id={patch_id}\ncomponent=mission-core-worker\ntype=shadow-release\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(stage / "files.txt").write_text(
|
||||||
|
"\n".join(payload_files) + "\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
target = output_directory.resolve() / f"nodedc-{patch_id}.tgz"
|
||||||
|
write_archive(stage, target)
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"patch_id": patch_id,
|
||||||
|
"artifact": str(target),
|
||||||
|
"sha256": sha256_file(target),
|
||||||
|
"code_revision": selected_revision,
|
||||||
|
"wheel_sha256": release["files"][WHEEL_NAME]["sha256"],
|
||||||
|
"payload_files": payload_files,
|
||||||
|
"transition": release["transition"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("patch_id")
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-directory",
|
||||||
|
type=Path,
|
||||||
|
default=REPOSITORY_ROOT / ".runtime/worker-artifacts",
|
||||||
|
)
|
||||||
|
arguments = parser.parse_args()
|
||||||
|
try:
|
||||||
|
result = build_artifact(arguments.patch_id, arguments.output_directory)
|
||||||
|
except (ArtifactBuildError, OSError, subprocess.SubprocessError) as exc:
|
||||||
|
parser.error(str(exc))
|
||||||
|
print(json.dumps(result, indent=2, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
"""Seal a coarse material + YOLOX + TGS review from an immutable vegetation LAB."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import copy
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
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.m49_tgs_full_shadow import read_m49_tgs_full_shadow
|
||||||
|
from k1link.laboratory.vegetation_mission_policy import (
|
||||||
|
load_vegetation_mission_policy,
|
||||||
|
load_vegetation_provider_label_map,
|
||||||
|
)
|
||||||
|
from k1link.laboratory.vegetation_policy_video import build_policy_mask_archive, policy_taxonomy
|
||||||
|
from k1link.laboratory.vegetation_shadow_lab import (
|
||||||
|
LAB_SCHEMA,
|
||||||
|
RESULT_PREFIX,
|
||||||
|
canonical_json,
|
||||||
|
sha256_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
_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,
|
||||||
|
)
|
||||||
|
_FRAME_COUNT: Final = 4489
|
||||||
|
_MAX_RESULT_BYTES: Final = 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
class VegetationPolicyReviewError(ValueError):
|
||||||
|
"""The sealed inputs cannot form an honest synchronized policy review."""
|
||||||
|
|
||||||
|
|
||||||
|
def _object(value: object, label: str) -> dict[str, Any]:
|
||||||
|
if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
|
||||||
|
raise VegetationPolicyReviewError(f"{label} must be an object")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _read_base(root: Path) -> dict[str, Any]:
|
||||||
|
candidate = root.resolve(strict=True)
|
||||||
|
verify_laboratory_evidence_result(_DEFINITION, candidate)
|
||||||
|
path = candidate / "result.json"
|
||||||
|
if path.stat().st_size > _MAX_RESULT_BYTES:
|
||||||
|
raise VegetationPolicyReviewError("base vegetation LAB document is too large")
|
||||||
|
payload = _object(json.loads(path.read_text("utf-8")), "base vegetation LAB")
|
||||||
|
route = _object(payload.get("route_video"), "base route video")
|
||||||
|
authority = _object(payload.get("authority"), "base authority")
|
||||||
|
if (
|
||||||
|
payload.get("schema_version") != LAB_SCHEMA
|
||||||
|
or payload.get("result_id") != candidate.name
|
||||||
|
or route.get("frame_count") != _FRAME_COUNT
|
||||||
|
or route.get("view_kind", "fine-semantic-prediction")
|
||||||
|
!= "fine-semantic-prediction"
|
||||||
|
or route.get("base_m4_result_id") is None
|
||||||
|
or authority.get("commands_enabled") is not False
|
||||||
|
or authority.get("navigation_or_safety_accepted") is not False
|
||||||
|
or authority.get("actuation_accepted") is not False
|
||||||
|
or authority.get("camera_semantics_can_clear_rigid_geometry") is not False
|
||||||
|
):
|
||||||
|
raise VegetationPolicyReviewError("base vegetation LAB contract changed")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def _copy_verified_artifacts(
|
||||||
|
*,
|
||||||
|
source_root: Path,
|
||||||
|
destination_root: Path,
|
||||||
|
artifacts: object,
|
||||||
|
) -> list[dict[str, object]]:
|
||||||
|
if not isinstance(artifacts, list):
|
||||||
|
raise VegetationPolicyReviewError("base artifact catalog changed")
|
||||||
|
copied: list[dict[str, object]] = []
|
||||||
|
for raw in artifacts:
|
||||||
|
descriptor = _object(raw, "base artifact")
|
||||||
|
relative_text = descriptor.get("path")
|
||||||
|
expected_sha256 = descriptor.get("sha256")
|
||||||
|
if not isinstance(relative_text, str) or not isinstance(expected_sha256, str):
|
||||||
|
raise VegetationPolicyReviewError("base artifact proof changed")
|
||||||
|
relative = PurePosixPath(relative_text)
|
||||||
|
source = source_root.joinpath(*relative.parts)
|
||||||
|
destination = destination_root.joinpath(*relative.parts)
|
||||||
|
if (
|
||||||
|
relative.is_absolute()
|
||||||
|
or str(relative) != relative_text
|
||||||
|
or any(part in {"", ".", ".."} for part in relative.parts)
|
||||||
|
or source.is_symlink()
|
||||||
|
or not source.is_file()
|
||||||
|
or sha256_path(source) != expected_sha256
|
||||||
|
):
|
||||||
|
raise VegetationPolicyReviewError("base artifact changed")
|
||||||
|
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(source, destination)
|
||||||
|
copied.append(copy.deepcopy(descriptor))
|
||||||
|
return copied
|
||||||
|
|
||||||
|
|
||||||
|
def seal_vegetation_policy_review(
|
||||||
|
*,
|
||||||
|
base_lab_root: Path,
|
||||||
|
mission_policy_path: Path,
|
||||||
|
provider_label_map_path: Path,
|
||||||
|
m49_tgs_full_shadow_root: Path,
|
||||||
|
output_root: Path,
|
||||||
|
created_at_utc: str | None = None,
|
||||||
|
) -> Path:
|
||||||
|
base_root = base_lab_root.resolve(strict=True)
|
||||||
|
base = _read_base(base_root)
|
||||||
|
base_route = _object(base["route_video"], "base route video")
|
||||||
|
repository_root = mission_policy_path.resolve().parents[2]
|
||||||
|
mission_policy = load_vegetation_mission_policy(
|
||||||
|
mission_policy_path.resolve(strict=True),
|
||||||
|
repository_root=repository_root,
|
||||||
|
)
|
||||||
|
provider_map = load_vegetation_provider_label_map(
|
||||||
|
provider_label_map_path.resolve(strict=True),
|
||||||
|
policy=mission_policy,
|
||||||
|
)
|
||||||
|
tgs = read_m49_tgs_full_shadow(m49_tgs_full_shadow_root)
|
||||||
|
tgs_source = _object(tgs.report.get("source"), "full TGS source")
|
||||||
|
tgs_timeline = _object(tgs.report.get("timeline"), "full TGS timeline")
|
||||||
|
if (
|
||||||
|
tgs_source.get("source_id") != "RAVNOVES00"
|
||||||
|
or tgs_source.get("linked_visual_result_id") != base_route.get("base_m4_result_id")
|
||||||
|
or tgs_timeline.get("frame_count") != _FRAME_COUNT
|
||||||
|
):
|
||||||
|
raise VegetationPolicyReviewError("TGS and vegetation timelines differ")
|
||||||
|
|
||||||
|
raw_archive = _object(base_route.get("mask_archive"), "fine mask archive")
|
||||||
|
if raw_archive.get("path") != "video/ddrnet-semantic-masks.zip":
|
||||||
|
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")
|
||||||
|
|
||||||
|
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
|
temporary = Path(tempfile.mkdtemp(prefix=".lab-v1-policy-", dir=output_root))
|
||||||
|
try:
|
||||||
|
artifacts = _copy_verified_artifacts(
|
||||||
|
source_root=base_root,
|
||||||
|
destination_root=temporary,
|
||||||
|
artifacts=base.get("artifacts"),
|
||||||
|
)
|
||||||
|
policy_archive = temporary / "video" / "coarse-material-policy-masks.zip"
|
||||||
|
policy_counts = build_policy_mask_archive(
|
||||||
|
source_archive=raw_archive_path,
|
||||||
|
destination_archive=policy_archive,
|
||||||
|
fine_taxonomy=fine_taxonomy,
|
||||||
|
provider_label_map=provider_map,
|
||||||
|
)
|
||||||
|
policy_archive_proof = {
|
||||||
|
"role": "route-coarse-material-mask-archive",
|
||||||
|
"path": "video/coarse-material-policy-masks.zip",
|
||||||
|
"byte_length": policy_archive.stat().st_size,
|
||||||
|
"sha256": sha256_path(policy_archive),
|
||||||
|
"media_type": "application/zip",
|
||||||
|
}
|
||||||
|
artifacts.append(policy_archive_proof)
|
||||||
|
|
||||||
|
route = copy.deepcopy(base_route)
|
||||||
|
route.update(
|
||||||
|
{
|
||||||
|
"view_kind": "coarse-material-policy-review",
|
||||||
|
"source_mask_archive": copy.deepcopy(raw_archive),
|
||||||
|
"mask_archive": {
|
||||||
|
"path": policy_archive_proof["path"],
|
||||||
|
"sha256": policy_archive_proof["sha256"],
|
||||||
|
"byte_length": policy_archive_proof["byte_length"],
|
||||||
|
},
|
||||||
|
"taxonomy": policy_taxonomy(),
|
||||||
|
"aggregate_prediction_pixels": policy_counts,
|
||||||
|
"linked_tgs_result_id": tgs.result_id,
|
||||||
|
"policy": {
|
||||||
|
"profile_id": mission_policy["profile_id"],
|
||||||
|
"profile_sha256": sha256_path(mission_policy_path),
|
||||||
|
"provider_label_map_id": provider_map["profile_id"],
|
||||||
|
"provider_label_map_sha256": sha256_path(provider_label_map_path),
|
||||||
|
"presets": mission_policy["presets"],
|
||||||
|
"precedence": mission_policy["precedence"],
|
||||||
|
},
|
||||||
|
"fusion": {
|
||||||
|
"mode": "synchronised-multilayer-review",
|
||||||
|
"pixel_raster_fusion": False,
|
||||||
|
"camera_material_layer": "DDRNet fine-64 to coarse material evidence",
|
||||||
|
"camera_safety_veto_layer": "frozen M4 YOLOX camera proposals",
|
||||||
|
"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",
|
||||||
|
"reason": "No admitted TGS-to-camera pixel projection exists.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
identity = copy.deepcopy(_object(base.get("identity"), "base identity"))
|
||||||
|
identity.update(
|
||||||
|
{
|
||||||
|
"base_result_id": base_root.name,
|
||||||
|
"route_video": route,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
|
||||||
|
result_id = f"{RESULT_PREFIX}{identity_sha256}"
|
||||||
|
manifest = copy.deepcopy(base)
|
||||||
|
manifest.update(
|
||||||
|
{
|
||||||
|
"result_id": result_id,
|
||||||
|
"identity_sha256": identity_sha256,
|
||||||
|
"created_at_utc": created_at_utc or datetime.now(UTC).isoformat(),
|
||||||
|
"identity": identity,
|
||||||
|
"route_video": route,
|
||||||
|
"method": {
|
||||||
|
"completeness": "complete",
|
||||||
|
"execution_class": "ai-inference-plus-deterministic-adapter",
|
||||||
|
"pipeline_id": "goose-fine64-to-coarse-material-plus-yolox-tgs-review/v1",
|
||||||
|
},
|
||||||
|
"decision": {
|
||||||
|
**_object(base.get("decision"), "base decision"),
|
||||||
|
"multilayer_policy_review_ready": True,
|
||||||
|
"navigation_accepted": False,
|
||||||
|
"production_accepted": False,
|
||||||
|
},
|
||||||
|
"limitations": [
|
||||||
|
"GOOSE validation is external-domain qualification, not RAVNOVES ground truth.",
|
||||||
|
(
|
||||||
|
"The coarse material playback is derived from per-frame DDRNet "
|
||||||
|
"predictions and has no RAVNOVES truth."
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Vegetation semantics never clears YOLOX, LiDAR, metric obstacle "
|
||||||
|
"or TGS vetoes."
|
||||||
|
),
|
||||||
|
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
|
||||||
|
(
|
||||||
|
"TGS remains in gravity-local space; no uncalibrated pixel "
|
||||||
|
"projection is fabricated."
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Temporal consensus comes from causal TGS and metric tracks; "
|
||||||
|
"the camera material mask is not temporally filtered."
|
||||||
|
),
|
||||||
|
],
|
||||||
|
"artifacts": artifacts,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(temporary / "result.json").write_bytes(canonical_json(manifest) + b"\n")
|
||||||
|
destination = output_root / result_id
|
||||||
|
if destination.exists():
|
||||||
|
raise VegetationPolicyReviewError("immutable vegetation policy result already exists")
|
||||||
|
temporary.replace(destination)
|
||||||
|
verify_laboratory_evidence_result(_DEFINITION, 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("--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("--output-root", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
print(seal_vegetation_policy_review(**vars(args)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["VegetationPolicyReviewError", "seal_vegetation_policy_review"]
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
"""Build a deterministic coarse material-evidence video from fine GOOSE masks."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import io
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Final
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from k1link.laboratory.vegetation_mission_policy import map_provider_material
|
||||||
|
|
||||||
|
TAXONOMY_SCHEMA: Final = "missioncore.lab-v1-terrain-policy-taxonomy/v1"
|
||||||
|
FRAME_COUNT: Final = 4489
|
||||||
|
WIDTH: Final = 800
|
||||||
|
HEIGHT: Final = 600
|
||||||
|
|
||||||
|
POLICY_CLASSES: Final = (
|
||||||
|
{
|
||||||
|
"class_id": 0,
|
||||||
|
"label": "UNOBSERVED / NO MATERIAL CLAIM · NO_GO",
|
||||||
|
"color_rgb": [147, 151, 159],
|
||||||
|
"disposition": "ambiguous",
|
||||||
|
"material_class": None,
|
||||||
|
"evidence_state": "UNOBSERVED",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 1,
|
||||||
|
"label": "SAFETY DETECTOR VETO · NO_GO",
|
||||||
|
"color_rgb": [255, 104, 112],
|
||||||
|
"disposition": "labeled",
|
||||||
|
"material_class": None,
|
||||||
|
"evidence_state": "RIGID_OR_UNKNOWN_OBSTACLE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 2,
|
||||||
|
"label": "WOODY SHRUB / TREE · NO_GO",
|
||||||
|
"color_rgb": [232, 56, 126],
|
||||||
|
"disposition": "labeled",
|
||||||
|
"material_class": "woody_or_tree",
|
||||||
|
"evidence_state": "VEGETATION_WITH_RIGID_GEOMETRY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 3,
|
||||||
|
"label": "CULTIVATED VEGETATION · POLICY NO_GO",
|
||||||
|
"color_rgb": [183, 112, 255],
|
||||||
|
"disposition": "labeled",
|
||||||
|
"material_class": "cultivated_vegetation",
|
||||||
|
"evidence_state": "VEGETATION_POTENTIALLY_TRAVERSABLE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 4,
|
||||||
|
"label": "LOW GRASS · MISSION CANDIDATE",
|
||||||
|
"color_rgb": [181, 255, 90],
|
||||||
|
"disposition": "prediction",
|
||||||
|
"material_class": "grass",
|
||||||
|
"evidence_state": "VEGETATION_POTENTIALLY_TRAVERSABLE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 5,
|
||||||
|
"label": "HIGH / HERBACEOUS · MISSION CANDIDATE",
|
||||||
|
"color_rgb": [113, 211, 111],
|
||||||
|
"disposition": "prediction",
|
||||||
|
"material_class": "herbaceous_vegetation",
|
||||||
|
"evidence_state": "VEGETATION_POTENTIALLY_TRAVERSABLE",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 6,
|
||||||
|
"label": "BARE SOIL · MISSION CANDIDATE",
|
||||||
|
"color_rgb": [255, 197, 92],
|
||||||
|
"disposition": "prediction",
|
||||||
|
"material_class": "bare_soil",
|
||||||
|
"evidence_state": "SUPPORTED_GROUND",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 7,
|
||||||
|
"label": "HARD SURFACE · MISSION CANDIDATE",
|
||||||
|
"color_rgb": [84, 169, 255],
|
||||||
|
"disposition": "prediction",
|
||||||
|
"material_class": "hard_surface",
|
||||||
|
"evidence_state": "SUPPORTED_GROUND",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"class_id": 8,
|
||||||
|
"label": "VEGETATION UNKNOWN · NO_GO",
|
||||||
|
"color_rgb": [207, 124, 255],
|
||||||
|
"disposition": "labeled",
|
||||||
|
"material_class": "vegetation_unknown",
|
||||||
|
"evidence_state": "VEGETATION_UNKNOWN",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
_MATERIAL_TO_CLASS: Final = {
|
||||||
|
"hard_surface": 7,
|
||||||
|
"bare_soil": 6,
|
||||||
|
"grass": 4,
|
||||||
|
"fern": 5,
|
||||||
|
"herbaceous_vegetation": 5,
|
||||||
|
"cultivated_vegetation": 3,
|
||||||
|
"woody_shrub": 2,
|
||||||
|
"tree_or_trunk": 2,
|
||||||
|
"vegetation_unknown": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VegetationPolicyVideoError(ValueError):
|
||||||
|
"""The fine-mask input cannot be transformed without inventing evidence."""
|
||||||
|
|
||||||
|
|
||||||
|
def policy_taxonomy() -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
"schema_version": TAXONOMY_SCHEMA,
|
||||||
|
"classes": [dict(row) for row in POLICY_CLASSES],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def fine_to_policy_lut(
|
||||||
|
fine_taxonomy: dict[str, object],
|
||||||
|
provider_label_map: dict[str, Any],
|
||||||
|
) -> np.ndarray:
|
||||||
|
classes = fine_taxonomy.get("classes")
|
||||||
|
if not isinstance(classes, list) or len(classes) != 64:
|
||||||
|
raise VegetationPolicyVideoError("fine taxonomy must contain 64 classes")
|
||||||
|
lut = np.zeros(256, dtype=np.uint8)
|
||||||
|
for expected_id, raw in enumerate(classes):
|
||||||
|
if not isinstance(raw, dict) or raw.get("class_id") != expected_id:
|
||||||
|
raise VegetationPolicyVideoError("fine taxonomy ordering changed")
|
||||||
|
label = raw.get("label")
|
||||||
|
if not isinstance(label, str) or not label:
|
||||||
|
raise VegetationPolicyVideoError("fine taxonomy label is invalid")
|
||||||
|
if expected_id == 0:
|
||||||
|
continue
|
||||||
|
material = map_provider_material(
|
||||||
|
provider_label_map,
|
||||||
|
provider_id="goose-fine-64",
|
||||||
|
provider_label=label,
|
||||||
|
)
|
||||||
|
lut[expected_id] = _MATERIAL_TO_CLASS.get(material, 0)
|
||||||
|
return lut
|
||||||
|
|
||||||
|
|
||||||
|
def _zip_info(name: str) -> zipfile.ZipInfo:
|
||||||
|
info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
|
||||||
|
info.compress_type = zipfile.ZIP_STORED
|
||||||
|
info.create_system = 3
|
||||||
|
info.external_attr = 0o600 << 16
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def build_policy_mask_archive(
|
||||||
|
*,
|
||||||
|
source_archive: Path,
|
||||||
|
destination_archive: Path,
|
||||||
|
fine_taxonomy: dict[str, object],
|
||||||
|
provider_label_map: dict[str, Any],
|
||||||
|
) -> list[int]:
|
||||||
|
"""Map every fine mask to coarse evidence; safety vetoes remain separate layers."""
|
||||||
|
|
||||||
|
lut = fine_to_policy_lut(fine_taxonomy, provider_label_map)
|
||||||
|
counts = np.zeros(len(POLICY_CLASSES), dtype=np.int64)
|
||||||
|
destination_archive.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(source_archive) as source, zipfile.ZipFile(
|
||||||
|
destination_archive,
|
||||||
|
"x",
|
||||||
|
) as destination:
|
||||||
|
for sequence in range(FRAME_COUNT):
|
||||||
|
member = f"masks/frame-{sequence + 1:06d}.png"
|
||||||
|
with source.open(member) as stream, Image.open(stream) as image:
|
||||||
|
fine = np.asarray(image.convert("L"), dtype=np.uint8)
|
||||||
|
if fine.shape != (HEIGHT, WIDTH):
|
||||||
|
raise VegetationPolicyVideoError(
|
||||||
|
f"fine mask {member} has shape {fine.shape}, expected {(HEIGHT, WIDTH)}"
|
||||||
|
)
|
||||||
|
coarse = lut[fine]
|
||||||
|
counts += np.bincount(
|
||||||
|
coarse.reshape(-1),
|
||||||
|
minlength=len(POLICY_CLASSES),
|
||||||
|
)
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
Image.fromarray(coarse, mode="L").save(
|
||||||
|
buffer,
|
||||||
|
format="PNG",
|
||||||
|
compress_level=1,
|
||||||
|
optimize=False,
|
||||||
|
)
|
||||||
|
destination.writestr(_zip_info(member), buffer.getvalue())
|
||||||
|
except (KeyError, OSError, ValueError, zipfile.BadZipFile) as exc:
|
||||||
|
destination_archive.unlink(missing_ok=True)
|
||||||
|
raise VegetationPolicyVideoError("fine mask archive is invalid") from exc
|
||||||
|
return [int(value) for value in counts]
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"FRAME_COUNT",
|
||||||
|
"HEIGHT",
|
||||||
|
"POLICY_CLASSES",
|
||||||
|
"TAXONOMY_SCHEMA",
|
||||||
|
"VegetationPolicyVideoError",
|
||||||
|
"WIDTH",
|
||||||
|
"build_policy_mask_archive",
|
||||||
|
"fine_to_policy_lut",
|
||||||
|
"policy_taxonomy",
|
||||||
|
]
|
||||||
@@ -14,6 +14,15 @@ from pathlib import Path, PurePosixPath
|
|||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
from k1link.laboratory.m47_reference_graph import read_m47_reference_graph_lab
|
from k1link.laboratory.m47_reference_graph import read_m47_reference_graph_lab
|
||||||
|
from k1link.laboratory.m49_tgs_full_shadow import read_m49_tgs_full_shadow
|
||||||
|
from k1link.laboratory.vegetation_mission_policy import (
|
||||||
|
load_vegetation_mission_policy,
|
||||||
|
load_vegetation_provider_label_map,
|
||||||
|
)
|
||||||
|
from k1link.laboratory.vegetation_policy_video import (
|
||||||
|
build_policy_mask_archive,
|
||||||
|
policy_taxonomy,
|
||||||
|
)
|
||||||
|
|
||||||
LAB_SCHEMA: Final = "missioncore.lab-v1-vegetation-shadow/v1"
|
LAB_SCHEMA: Final = "missioncore.lab-v1-vegetation-shadow/v1"
|
||||||
WORKER_SCHEMA: Final = "missioncore.lab-v1-goose-vegetation-run/v1"
|
WORKER_SCHEMA: Final = "missioncore.lab-v1-goose-vegetation-run/v1"
|
||||||
@@ -315,6 +324,9 @@ def seal_vegetation_shadow_lab(
|
|||||||
output_root: Path,
|
output_root: Path,
|
||||||
ddrnet_ravnoves_video_root: Path | None = None,
|
ddrnet_ravnoves_video_root: Path | None = None,
|
||||||
m47_reference_graph_lab_root: Path | None = None,
|
m47_reference_graph_lab_root: Path | None = None,
|
||||||
|
mission_policy_path: Path | None = None,
|
||||||
|
provider_label_map_path: Path | None = None,
|
||||||
|
m49_tgs_full_shadow_root: Path | None = None,
|
||||||
) -> Path:
|
) -> Path:
|
||||||
roots = {
|
roots = {
|
||||||
("ddrnet", "goose"): ddrnet_goose_root.resolve(),
|
("ddrnet", "goose"): ddrnet_goose_root.resolve(),
|
||||||
@@ -333,6 +345,17 @@ def seal_vegetation_shadow_lab(
|
|||||||
selected = _selected_candidate(results)
|
selected = _selected_candidate(results)
|
||||||
if (ddrnet_ravnoves_video_root is None) != (m47_reference_graph_lab_root is None):
|
if (ddrnet_ravnoves_video_root is None) != (m47_reference_graph_lab_root is None):
|
||||||
raise VegetationShadowLabError("full-video Worker and M4.7 roots must be paired")
|
raise VegetationShadowLabError("full-video Worker and M4.7 roots must be paired")
|
||||||
|
policy_inputs = (
|
||||||
|
mission_policy_path,
|
||||||
|
provider_label_map_path,
|
||||||
|
m49_tgs_full_shadow_root,
|
||||||
|
)
|
||||||
|
if any(value is not None for value in policy_inputs) and not all(
|
||||||
|
value is not None for value in policy_inputs
|
||||||
|
):
|
||||||
|
raise VegetationShadowLabError("policy, provider map and full TGS roots must be paired")
|
||||||
|
if all(value is not None for value in policy_inputs) and ddrnet_ravnoves_video_root is None:
|
||||||
|
raise VegetationShadowLabError("policy review requires the full-video DDRNet result")
|
||||||
route_video: dict[str, object] | None = None
|
route_video: dict[str, object] | None = None
|
||||||
route_video_archive: Path | None = None
|
route_video_archive: Path | None = None
|
||||||
video_result: dict[str, Any] | None = None
|
video_result: dict[str, Any] | None = None
|
||||||
@@ -355,6 +378,35 @@ def seal_vegetation_shadow_lab(
|
|||||||
raise VegetationShadowLabError("M4.7 video binding differs from DDRNet source")
|
raise VegetationShadowLabError("M4.7 video binding differs from DDRNet source")
|
||||||
route_video["m47_reference_graph_result_id"] = m47.result_id
|
route_video["m47_reference_graph_result_id"] = m47.result_id
|
||||||
|
|
||||||
|
mission_policy: dict[str, Any] | None = None
|
||||||
|
provider_label_map: dict[str, Any] | None = None
|
||||||
|
linked_tgs_result_id: str | None = None
|
||||||
|
if (
|
||||||
|
mission_policy_path is not None
|
||||||
|
and provider_label_map_path is not None
|
||||||
|
and m49_tgs_full_shadow_root is not None
|
||||||
|
and route_video is not None
|
||||||
|
):
|
||||||
|
repository_root = mission_policy_path.resolve().parents[2]
|
||||||
|
mission_policy = load_vegetation_mission_policy(
|
||||||
|
mission_policy_path.resolve(),
|
||||||
|
repository_root=repository_root,
|
||||||
|
)
|
||||||
|
provider_label_map = load_vegetation_provider_label_map(
|
||||||
|
provider_label_map_path.resolve(),
|
||||||
|
policy=mission_policy,
|
||||||
|
)
|
||||||
|
tgs = read_m49_tgs_full_shadow(m49_tgs_full_shadow_root)
|
||||||
|
tgs_source = _object(tgs.report.get("source"), "M4.9 full TGS source")
|
||||||
|
tgs_timeline = _object(tgs.report.get("timeline"), "M4.9 full TGS timeline")
|
||||||
|
if (
|
||||||
|
tgs_source.get("source_id") != "RAVNOVES00"
|
||||||
|
or tgs_source.get("linked_visual_result_id") != route_video["base_m4_result_id"]
|
||||||
|
or tgs_timeline.get("frame_count") != _VIDEO_FRAME_COUNT
|
||||||
|
):
|
||||||
|
raise VegetationShadowLabError("full TGS timeline differs from vegetation video")
|
||||||
|
linked_tgs_result_id = tgs.result_id
|
||||||
|
|
||||||
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||||
temporary = Path(tempfile.mkdtemp(prefix=".lab-v1-vegetation-", dir=output_root))
|
temporary = Path(tempfile.mkdtemp(prefix=".lab-v1-vegetation-", dir=output_root))
|
||||||
artifacts: list[dict[str, object]] = []
|
artifacts: list[dict[str, object]] = []
|
||||||
@@ -471,14 +523,78 @@ def seal_vegetation_shadow_lab(
|
|||||||
temporary,
|
temporary,
|
||||||
"video/ddrnet-semantic-masks.zip",
|
"video/ddrnet-semantic-masks.zip",
|
||||||
artifacts,
|
artifacts,
|
||||||
role="route-semantic-mask-archive",
|
role=(
|
||||||
|
"route-fine-semantic-source-archive"
|
||||||
|
if mission_policy is not None
|
||||||
|
else "route-semantic-mask-archive"
|
||||||
|
),
|
||||||
media_type="application/zip",
|
media_type="application/zip",
|
||||||
)
|
)
|
||||||
route_video["mask_archive"] = {
|
raw_archive_proof = {
|
||||||
"path": archive_descriptor["path"],
|
"path": archive_descriptor["path"],
|
||||||
"sha256": archive_descriptor["sha256"],
|
"sha256": archive_descriptor["sha256"],
|
||||||
"byte_length": archive_descriptor["byte_length"],
|
"byte_length": archive_descriptor["byte_length"],
|
||||||
}
|
}
|
||||||
|
route_video["mask_archive"] = raw_archive_proof
|
||||||
|
route_video["view_kind"] = "fine-semantic-prediction"
|
||||||
|
if (
|
||||||
|
mission_policy is not None
|
||||||
|
and provider_label_map is not None
|
||||||
|
and linked_tgs_result_id is not None
|
||||||
|
and mission_policy_path is not None
|
||||||
|
and provider_label_map_path is not None
|
||||||
|
):
|
||||||
|
policy_archive = temporary / "video" / "coarse-material-policy-masks.zip"
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
policy_descriptor = {
|
||||||
|
"role": "route-coarse-material-mask-archive",
|
||||||
|
"path": "video/coarse-material-policy-masks.zip",
|
||||||
|
"byte_length": policy_archive.stat().st_size,
|
||||||
|
"sha256": sha256_path(policy_archive),
|
||||||
|
"media_type": "application/zip",
|
||||||
|
}
|
||||||
|
artifacts.append(policy_descriptor)
|
||||||
|
route_video.update(
|
||||||
|
{
|
||||||
|
"view_kind": "coarse-material-policy-review",
|
||||||
|
"source_mask_archive": raw_archive_proof,
|
||||||
|
"mask_archive": {
|
||||||
|
"path": policy_descriptor["path"],
|
||||||
|
"sha256": policy_descriptor["sha256"],
|
||||||
|
"byte_length": policy_descriptor["byte_length"],
|
||||||
|
},
|
||||||
|
"taxonomy": policy_taxonomy(),
|
||||||
|
"aggregate_prediction_pixels": policy_counts,
|
||||||
|
"linked_tgs_result_id": linked_tgs_result_id,
|
||||||
|
"policy": {
|
||||||
|
"profile_id": mission_policy["profile_id"],
|
||||||
|
"profile_sha256": sha256_path(mission_policy_path),
|
||||||
|
"provider_label_map_id": provider_label_map["profile_id"],
|
||||||
|
"provider_label_map_sha256": sha256_path(
|
||||||
|
provider_label_map_path
|
||||||
|
),
|
||||||
|
"presets": mission_policy["presets"],
|
||||||
|
"precedence": mission_policy["precedence"],
|
||||||
|
},
|
||||||
|
"fusion": {
|
||||||
|
"mode": "synchronised-multilayer-review",
|
||||||
|
"pixel_raster_fusion": False,
|
||||||
|
"camera_material_layer": "DDRNet fine-64 to coarse material evidence",
|
||||||
|
"camera_safety_veto_layer": "frozen M4 YOLOX camera proposals",
|
||||||
|
"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",
|
||||||
|
"reason": "No admitted TGS-to-camera pixel projection exists.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
candidate_metrics: dict[str, object] = {}
|
candidate_metrics: dict[str, object] = {}
|
||||||
for candidate in _CANDIDATES:
|
for candidate in _CANDIDATES:
|
||||||
@@ -536,7 +652,11 @@ def seal_vegetation_shadow_lab(
|
|||||||
"method": {
|
"method": {
|
||||||
"completeness": "complete",
|
"completeness": "complete",
|
||||||
"execution_class": "ai-inference",
|
"execution_class": "ai-inference",
|
||||||
"pipeline_id": "goose-fine64-ready-weights-to-ravnoves-policy-shadow/v1",
|
"pipeline_id": (
|
||||||
|
"goose-fine64-to-coarse-material-plus-yolox-tgs-review/v1"
|
||||||
|
if mission_policy is not None
|
||||||
|
else "goose-fine64-ready-weights-to-ravnoves-policy-shadow/v1"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
"metrics": {"candidates": candidate_metrics},
|
"metrics": {"candidates": candidate_metrics},
|
||||||
"decision": {
|
"decision": {
|
||||||
@@ -544,14 +664,37 @@ def seal_vegetation_shadow_lab(
|
|||||||
"visual_shadow_ready": True,
|
"visual_shadow_ready": True,
|
||||||
"full_video_shadow_ready": route_video is not None,
|
"full_video_shadow_ready": route_video is not None,
|
||||||
"mission_policy_ready_for_configuration": True,
|
"mission_policy_ready_for_configuration": True,
|
||||||
|
"multilayer_policy_review_ready": mission_policy is not None,
|
||||||
"navigation_accepted": False,
|
"navigation_accepted": False,
|
||||||
"production_accepted": False,
|
"production_accepted": False,
|
||||||
},
|
},
|
||||||
"limitations": [
|
"limitations": [
|
||||||
"GOOSE validation is external-domain qualification, not RAVNOVES ground truth.",
|
"GOOSE validation is external-domain qualification, not RAVNOVES ground truth.",
|
||||||
"The full RAVNOVES DDRNet playback is prediction-only and has no independent labels.",
|
(
|
||||||
|
"The coarse material playback is derived from per-frame DDRNet predictions "
|
||||||
|
"and has no RAVNOVES truth."
|
||||||
|
if mission_policy is not None
|
||||||
|
else (
|
||||||
|
"The full RAVNOVES DDRNet playback is prediction-only and has "
|
||||||
|
"no independent labels."
|
||||||
|
)
|
||||||
|
),
|
||||||
"Vegetation semantics never clears rigid LiDAR/TGS occupancy.",
|
"Vegetation semantics never clears rigid LiDAR/TGS occupancy.",
|
||||||
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
|
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
|
||||||
|
*(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"TGS remains in gravity-local space; no uncalibrated pixel "
|
||||||
|
"projection is fabricated."
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Temporal consensus comes from causal TGS and metric tracks; "
|
||||||
|
"the camera material mask is not temporally filtered."
|
||||||
|
),
|
||||||
|
]
|
||||||
|
if mission_policy is not None
|
||||||
|
else []
|
||||||
|
),
|
||||||
],
|
],
|
||||||
"authority": authority,
|
"authority": authority,
|
||||||
"catalogs": catalogs,
|
"catalogs": catalogs,
|
||||||
@@ -577,6 +720,9 @@ def _parse_args() -> argparse.Namespace:
|
|||||||
parser.add_argument("--output-root", type=Path, required=True)
|
parser.add_argument("--output-root", type=Path, required=True)
|
||||||
parser.add_argument("--ddrnet-ravnoves-video-root", type=Path)
|
parser.add_argument("--ddrnet-ravnoves-video-root", type=Path)
|
||||||
parser.add_argument("--m47-reference-graph-lab-root", type=Path)
|
parser.add_argument("--m47-reference-graph-lab-root", type=Path)
|
||||||
|
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)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -590,6 +736,9 @@ def main() -> None:
|
|||||||
output_root=args.output_root,
|
output_root=args.output_root,
|
||||||
ddrnet_ravnoves_video_root=args.ddrnet_ravnoves_video_root,
|
ddrnet_ravnoves_video_root=args.ddrnet_ravnoves_video_root,
|
||||||
m47_reference_graph_lab_root=args.m47_reference_graph_lab_root,
|
m47_reference_graph_lab_root=args.m47_reference_graph_lab_root,
|
||||||
|
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,
|
||||||
)
|
)
|
||||||
print(destination)
|
print(destination)
|
||||||
|
|
||||||
|
|||||||
@@ -93,12 +93,33 @@ def build_vegetation_shadow_lab_router(
|
|||||||
candidate = _resolve_candidate(root_provider, result_id)
|
candidate = _resolve_candidate(root_provider, result_id)
|
||||||
manifest = _read_verified(candidate)
|
manifest = _read_verified(candidate)
|
||||||
route_video = manifest.get("route_video")
|
route_video = manifest.get("route_video")
|
||||||
if not isinstance(route_video, dict) or not 0 <= sequence < 4489:
|
if (
|
||||||
|
not isinstance(route_video, dict)
|
||||||
|
or route_video.get("frame_count") != 4489
|
||||||
|
or not 0 <= sequence < 4489
|
||||||
|
):
|
||||||
raise HTTPException(status_code=404, detail="Vegetation video mask not found")
|
raise HTTPException(status_code=404, detail="Vegetation video mask not found")
|
||||||
archive = route_video.get("mask_archive")
|
archive = route_video.get("mask_archive")
|
||||||
if not isinstance(archive, dict) or archive.get("path") != "video/ddrnet-semantic-masks.zip":
|
archive_relative = archive.get("path") if isinstance(archive, dict) else None
|
||||||
|
if not isinstance(archive_relative, str):
|
||||||
raise HTTPException(status_code=404, detail="Vegetation video mask not found")
|
raise HTTPException(status_code=404, detail="Vegetation video mask not found")
|
||||||
archive_path = candidate / "video" / "ddrnet-semantic-masks.zip"
|
relative = PurePosixPath(archive_relative)
|
||||||
|
if (
|
||||||
|
relative.is_absolute()
|
||||||
|
or str(relative) != archive_relative
|
||||||
|
or any(part in {"", ".", ".."} for part in relative.parts)
|
||||||
|
or relative.suffix != ".zip"
|
||||||
|
):
|
||||||
|
raise HTTPException(status_code=404, detail="Vegetation video mask not found")
|
||||||
|
artifacts = manifest.get("artifacts")
|
||||||
|
if 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="Vegetation video mask not found")
|
||||||
|
archive_path = candidate.joinpath(*relative.parts)
|
||||||
member = f"masks/frame-{sequence + 1:06d}.png"
|
member = f"masks/frame-{sequence + 1:06d}.png"
|
||||||
try:
|
try:
|
||||||
before = archive_path.stat()
|
before = archive_path.stat()
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import tarfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
EVIDENCE_PATH = (
|
||||||
|
REPOSITORY_ROOT
|
||||||
|
/ "experiments/perception/worker/m49_t3_travel/"
|
||||||
|
"build_vegetation_integrated_graph_evidence.py"
|
||||||
|
)
|
||||||
|
ARTIFACT_PATH = (
|
||||||
|
REPOSITORY_ROOT / "scripts/build_lab_v1_vegetation_integrated_worker_artifact.py"
|
||||||
|
)
|
||||||
|
RUNNER_PATH = (
|
||||||
|
REPOSITORY_ROOT
|
||||||
|
/ "experiments/perception/worker/lab_v1_vegetation_goose/"
|
||||||
|
"run_vegetation_integrated_load.py"
|
||||||
|
)
|
||||||
|
POWERSHELL_PATH = (
|
||||||
|
REPOSITORY_ROOT
|
||||||
|
/ "experiments/perception/worker/Invoke-M49TgsIntegratedGraphShadow.ps1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_module(name: str, path: Path):
|
||||||
|
spec = importlib.util.spec_from_file_location(name, path)
|
||||||
|
assert spec is not None and spec.loader is not None
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
EVIDENCE = load_module("vegetation_integrated_evidence", EVIDENCE_PATH)
|
||||||
|
ARTIFACT = load_module("vegetation_integrated_artifact", ARTIFACT_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def test_three_layer_gate_joins_exact_frames_and_preserves_false_authority(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
profile = tmp_path / "profile.json"
|
||||||
|
profile.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": EVIDENCE.PROFILE_SCHEMA,
|
||||||
|
"profile_id": "test",
|
||||||
|
"source": {"source_id": "RAVNOVES00", "requested_source_rate_hz": 12.0},
|
||||||
|
"stages": {
|
||||||
|
"m49_graph_tgs": {"profile_sha256": "a" * 64},
|
||||||
|
"vegetation": {
|
||||||
|
"checkpoint_sha256": "b" * 64,
|
||||||
|
"config_sha256": "c" * 64,
|
||||||
|
"policy_sha256": "d" * 64,
|
||||||
|
"provider_map_sha256": "e" * 64,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"acceptance": {
|
||||||
|
"minimum_graph_world_state_fps": 11.2,
|
||||||
|
"minimum_vegetation_fps": 11.2,
|
||||||
|
"maximum_vegetation_completion_p95_ms": 125.0,
|
||||||
|
"maximum_combined_output_age_p99_ms": 125.0,
|
||||||
|
},
|
||||||
|
"authority": {
|
||||||
|
"commands_enabled": False,
|
||||||
|
"actuation_allowed": False,
|
||||||
|
"navigation_or_safety_accepted": False,
|
||||||
|
"production_accepted": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
m49 = tmp_path / "m49.json"
|
||||||
|
m49.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": EVIDENCE.M49_SCHEMA,
|
||||||
|
"status": "passed",
|
||||||
|
"integrated_runtime_gate_passed": True,
|
||||||
|
"result_id": "m49-test",
|
||||||
|
"identity": {"profile_sha256": "a" * 64},
|
||||||
|
"performance": {"effective_world_state_fps": 11.8},
|
||||||
|
"accounting": {
|
||||||
|
"graph_admitted": EVIDENCE.FRAME_COUNT,
|
||||||
|
"graph_delivered": EVIDENCE.FRAME_COUNT,
|
||||||
|
"tgs_timeline_frames": EVIDENCE.FRAME_COUNT,
|
||||||
|
"tgs_capacity_drops": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
vegetation = tmp_path / "vegetation.json"
|
||||||
|
vegetation.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": EVIDENCE.VEGETATION_SCHEMA,
|
||||||
|
"result_id": "vegetation-test",
|
||||||
|
"source": {"requested_source_rate_hz": 12.0},
|
||||||
|
"candidate": {"candidate_key": "ddrnet", "checkpoint_sha256": "b" * 64},
|
||||||
|
"identity": {
|
||||||
|
"config_sha256": "c" * 64,
|
||||||
|
"policy_sha256": "d" * 64,
|
||||||
|
"provider_map_sha256": "e" * 64,
|
||||||
|
},
|
||||||
|
"execution": {
|
||||||
|
"frame_count": EVIDENCE.FRAME_COUNT,
|
||||||
|
"effective_fps": 11.75,
|
||||||
|
"capacity_drop_count": 0,
|
||||||
|
},
|
||||||
|
"timing": {
|
||||||
|
"completion_age_ms": {"p95": 25.0},
|
||||||
|
"stage_ms": {"p95": 20.0},
|
||||||
|
"inference_ms": {"p95": 18.0},
|
||||||
|
},
|
||||||
|
"resource": {"gpu_name": "test"},
|
||||||
|
"authority": {
|
||||||
|
"commands_enabled": False,
|
||||||
|
"actuation_allowed": False,
|
||||||
|
"navigation_or_safety_accepted": False,
|
||||||
|
"production_accepted": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
graph_frames = tmp_path / "graph.jsonl"
|
||||||
|
graph_frames.write_text(
|
||||||
|
"".join(
|
||||||
|
json.dumps(
|
||||||
|
{"source_envelope": {"sequence": index}, "completion_age_ns": 40_000_000}
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
for index in range(EVIDENCE.FRAME_COUNT)
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
tgs_frames = tmp_path / "tgs.tsv"
|
||||||
|
tgs_frames.write_text(
|
||||||
|
"timeline_frame_index\tcompletion_age_ms\n"
|
||||||
|
+ "".join(f"{index}\t5.0\n" for index in range(EVIDENCE.FRAME_COUNT)),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
vegetation_frames = tmp_path / "vegetation.jsonl"
|
||||||
|
vegetation_frames.write_text(
|
||||||
|
"".join(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": "missioncore.lab-v1-vegetation-integrated-frame/v1",
|
||||||
|
"sequence": index,
|
||||||
|
"completion_age_ms": 20.0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
for index in range(EVIDENCE.FRAME_COUNT)
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
telemetry = tmp_path / "telemetry.jsonl"
|
||||||
|
telemetry.write_text(
|
||||||
|
"".join(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"role": role,
|
||||||
|
"cpu_percent": "10.0%",
|
||||||
|
"memory_usage": "1GiB / 64GiB",
|
||||||
|
"memory_percent": "1.56%",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
for role in ("graph", "tgs", "triton", "vegetation")
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
output = tmp_path / "result.json"
|
||||||
|
|
||||||
|
result = EVIDENCE.build(
|
||||||
|
profile_path=profile,
|
||||||
|
m49_result_path=m49,
|
||||||
|
graph_frames_path=graph_frames,
|
||||||
|
tgs_timing_path=tgs_frames,
|
||||||
|
vegetation_result_path=vegetation,
|
||||||
|
vegetation_frames_path=vegetation_frames,
|
||||||
|
telemetry_path=telemetry,
|
||||||
|
output_path=output,
|
||||||
|
release_sha256="f" * 64,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["status"] == "passed"
|
||||||
|
assert result["source"]["joined_frame_count"] == EVIDENCE.FRAME_COUNT
|
||||||
|
assert result["performance"]["three_layer_output_age_ms"]["p99"] == 40.0
|
||||||
|
assert result["checks"]["authority_remains_false"] is True
|
||||||
|
assert result["production_accepted"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_integrated_release_is_deterministic_and_contains_one_vegetation_candidate(
|
||||||
|
monkeypatch, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
def fake_wheel(_source_root: Path, output: Path) -> Path:
|
||||||
|
output.mkdir(parents=True, exist_ok=True)
|
||||||
|
wheel = output / ARTIFACT.WHEEL_NAME
|
||||||
|
wheel.write_bytes(b"clean committed wheel\n")
|
||||||
|
return wheel
|
||||||
|
|
||||||
|
monkeypatch.setattr(ARTIFACT, "build_wheel", fake_wheel)
|
||||||
|
revision = "f" * 40
|
||||||
|
first = ARTIFACT.build_artifact(
|
||||||
|
"mission-core-vegetation-integrated-unit-001",
|
||||||
|
tmp_path / "first",
|
||||||
|
revision=revision,
|
||||||
|
source_root=REPOSITORY_ROOT,
|
||||||
|
)
|
||||||
|
second = ARTIFACT.build_artifact(
|
||||||
|
"mission-core-vegetation-integrated-unit-001",
|
||||||
|
tmp_path / "second",
|
||||||
|
revision=revision,
|
||||||
|
source_root=REPOSITORY_ROOT,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert Path(first["artifact"]).read_bytes() == Path(second["artifact"]).read_bytes()
|
||||||
|
with tarfile.open(first["artifact"], "r:gz") as archive:
|
||||||
|
names = set(archive.getnames())
|
||||||
|
release_stream = archive.extractfile("payload/release.json")
|
||||||
|
assert release_stream is not None
|
||||||
|
release = json.loads(release_stream.read())
|
||||||
|
assert "payload/run_vegetation_integrated_load.py" in names
|
||||||
|
assert "payload/build_vegetation_integrated_graph_evidence.py" in names
|
||||||
|
assert release["scope"]["heavy_vegetation_candidates"] == ["ddrnet"]
|
||||||
|
assert all(value is False for value in release["authority"].values())
|
||||||
|
|
||||||
|
|
||||||
|
def test_worker_gate_reuses_shared_barrier_and_keeps_canonical_triton_unchanged() -> None:
|
||||||
|
runner = RUNNER_PATH.read_text(encoding="utf-8")
|
||||||
|
wrapper = POWERSHELL_PATH.read_text(encoding="utf-8")
|
||||||
|
assert '"source-paced-integrated-shadow/v1"' in runner
|
||||||
|
assert "wait_for_shared_start(" in runner
|
||||||
|
assert '"camera_semantics_can_clear_rigid_geometry": False' in runner
|
||||||
|
assert "$VegetationLoadGate" in wrapper
|
||||||
|
assert '"vegetation"' in wrapper
|
||||||
|
assert "if ($canonicalAfter.Id -cne $canonicalId" not in wrapper
|
||||||
|
assert "$canonicalAfter.Id -cne $canonicalId" in wrapper
|
||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
@@ -9,9 +10,11 @@ from types import SimpleNamespace
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from k1link.laboratory import LaboratoryEvidenceRegistry
|
import k1link.laboratory.vegetation_policy_review as policy_review_module
|
||||||
import k1link.laboratory.vegetation_shadow_lab as vegetation_lab_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
|
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
|
||||||
|
from k1link.laboratory.vegetation_policy_review import seal_vegetation_policy_review
|
||||||
from k1link.laboratory.vegetation_shadow_lab import seal_vegetation_shadow_lab
|
from k1link.laboratory.vegetation_shadow_lab import seal_vegetation_shadow_lab
|
||||||
from k1link.web.vegetation_shadow_lab_api import build_vegetation_shadow_lab_router
|
from k1link.web.vegetation_shadow_lab_api import build_vegetation_shadow_lab_router
|
||||||
|
|
||||||
@@ -229,3 +232,95 @@ def test_vegetation_shadow_lab_seals_autonomous_visual_evidence(
|
|||||||
client.get(f"/api/v1/laboratory/vegetation-shadow/{result_root.name}").status_code
|
client.get(f"/api/v1/laboratory/vegetation-shadow/{result_root.name}").status_code
|
||||||
== 503
|
== 503
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_policy_review_reuses_sealed_video_and_links_yolox_tgs(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch,
|
||||||
|
) -> None:
|
||||||
|
roots = {}
|
||||||
|
for candidate, vegetation_iou in (("ddrnet", 0.64), ("ppliteseg", 0.61)):
|
||||||
|
for mode in ("goose", "ravnoves"):
|
||||||
|
root = tmp_path / "worker" / f"{candidate}-{mode}"
|
||||||
|
_worker_result(root, candidate=candidate, mode=mode, vegetation_iou=vegetation_iou)
|
||||||
|
roots[(candidate, mode)] = root
|
||||||
|
video_root = tmp_path / "worker" / "ddrnet-ravnoves-video"
|
||||||
|
_video_worker_result(video_root)
|
||||||
|
m47_root = tmp_path / f"m47-reference-graph-lab-{'a' * 64}"
|
||||||
|
m47_root.mkdir()
|
||||||
|
base_m4_result_id = f"m4-threat-replay-{'f' * 64}"
|
||||||
|
monkeypatch.setattr(
|
||||||
|
vegetation_lab_module,
|
||||||
|
"read_m47_reference_graph_lab",
|
||||||
|
lambda _root: SimpleNamespace(
|
||||||
|
result_id=m47_root.name,
|
||||||
|
report={
|
||||||
|
"source": {"source_id": "RAVNOVES00"},
|
||||||
|
"visual_evidence": {
|
||||||
|
"linked_result_id": base_m4_result_id,
|
||||||
|
"timeline_frames": 4489,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
base_root = seal_vegetation_shadow_lab(
|
||||||
|
ddrnet_goose_root=roots[("ddrnet", "goose")],
|
||||||
|
ppliteseg_goose_root=roots[("ppliteseg", "goose")],
|
||||||
|
ddrnet_ravnoves_root=roots[("ddrnet", "ravnoves")],
|
||||||
|
ppliteseg_ravnoves_root=roots[("ppliteseg", "ravnoves")],
|
||||||
|
output_root=tmp_path / "results",
|
||||||
|
ddrnet_ravnoves_video_root=video_root,
|
||||||
|
m47_reference_graph_lab_root=m47_root,
|
||||||
|
)
|
||||||
|
tgs_result_id = f"m49-tgs-full-shadow-{'9' * 64}"
|
||||||
|
monkeypatch.setattr(
|
||||||
|
policy_review_module,
|
||||||
|
"read_m49_tgs_full_shadow",
|
||||||
|
lambda _root: SimpleNamespace(
|
||||||
|
result_id=tgs_result_id,
|
||||||
|
report={
|
||||||
|
"source": {
|
||||||
|
"source_id": "RAVNOVES00",
|
||||||
|
"linked_visual_result_id": base_m4_result_id,
|
||||||
|
},
|
||||||
|
"timeline": {"frame_count": 4489},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def fake_policy_archive(**kwargs) -> list[int]:
|
||||||
|
shutil.copyfile(kwargs["source_archive"], kwargs["destination_archive"])
|
||||||
|
return [4489 * 800 * 600, *([0] * 8)]
|
||||||
|
|
||||||
|
monkeypatch.setattr(policy_review_module, "build_policy_mask_archive", fake_policy_archive)
|
||||||
|
result_root = seal_vegetation_policy_review(
|
||||||
|
base_lab_root=base_root,
|
||||||
|
mission_policy_path=REPOSITORY_ROOT
|
||||||
|
/ "config/perception/lab-v1-vegetation-mission-policy-v1.json",
|
||||||
|
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",
|
||||||
|
output_root=tmp_path / "results",
|
||||||
|
created_at_utc="2026-08-28T08:00:00+00:00",
|
||||||
|
)
|
||||||
|
manifest = json.loads((result_root / "result.json").read_text("utf-8"))
|
||||||
|
route = manifest["route_video"]
|
||||||
|
assert route["view_kind"] == "coarse-material-policy-review"
|
||||||
|
assert route["linked_tgs_result_id"] == tgs_result_id
|
||||||
|
assert route["fusion"]["pixel_raster_fusion"] is False
|
||||||
|
assert route["fusion"]["camera_semantic_temporal_filter"] == "none"
|
||||||
|
assert route["taxonomy"]["schema_version"] == (
|
||||||
|
"missioncore.lab-v1-terrain-policy-taxonomy/v1"
|
||||||
|
)
|
||||||
|
assert len(route["taxonomy"]["classes"]) == 9
|
||||||
|
assert len(manifest["artifacts"]) == 79
|
||||||
|
assert manifest["authority"]["commands_enabled"] is False
|
||||||
|
assert manifest["decision"]["multilayer_policy_review_ready"] is True
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
app.include_router(build_vegetation_shadow_lab_router(root_provider=lambda: result_root.parent))
|
||||||
|
response = TestClient(app).get(
|
||||||
|
f"/api/v1/laboratory/vegetation-shadow/{result_root.name}/masks/0"
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.content == b"\x89PNG\r\n\x1a\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user