Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e44e27967 | ||
|
|
2ccf172319 |
@@ -48,13 +48,9 @@ import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
import { fetchM49TgsFailClosedResult } from "./m49TgsFailClosed";
|
||||
import { fetchM49TgsFullShadowResult } from "./m49TgsFullShadow";
|
||||
import {
|
||||
fetchVegetationBenchmarkResult,
|
||||
fetchVegetationShadowResult,
|
||||
} from "./vegetationShadow";
|
||||
import { fetchVegetationShadowResult } from "./vegetationShadow";
|
||||
|
||||
export type AdvancedLaboratoryWorkId =
|
||||
| "lab-v1-vegetation-benchmark"
|
||||
| "lab-v1-vegetation-shadow"
|
||||
| "m48-object-centric-quality"
|
||||
| "m48-small-static-passage-regression"
|
||||
@@ -106,7 +102,6 @@ export interface AdvancedLaboratoryIndexItem {
|
||||
}
|
||||
|
||||
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
"lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow",
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
@@ -153,7 +148,6 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
];
|
||||
|
||||
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
|
||||
"lab-v1-vegetation-benchmark": "lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow": "lab-v1-vegetation-shadow",
|
||||
"m48-object-centric-quality": "m48-object-quality-(?:pack|result)",
|
||||
"m48-small-static-passage-regression": "m48-small-static-passage-regression",
|
||||
@@ -207,7 +201,6 @@ export function isAdvancedLaboratoryWorkId(
|
||||
|
||||
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
|
||||
return {
|
||||
vegetationBenchmark: null,
|
||||
vegetationShadow: null,
|
||||
m47Graph: null,
|
||||
m48: null,
|
||||
@@ -342,8 +335,7 @@ export function advancedLaboratoryResultAvailable(
|
||||
workId: AdvancedLaboratoryWorkId,
|
||||
results: AdvancedLaboratoryResults,
|
||||
): boolean {
|
||||
return workId === "lab-v1-vegetation-benchmark" ? results.vegetationBenchmark !== null
|
||||
: workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
|
||||
return workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
|
||||
: workId === "m48-object-centric-quality" ? results.m48 !== null
|
||||
: workId === "m48-small-static-passage-regression" ? results.m48SmallStatic !== null
|
||||
: workId === "m48-static-occupancy-qualification" ? results.m48StaticOccupancy !== null
|
||||
@@ -401,10 +393,7 @@ export async function fetchAdvancedLaboratoryResult(
|
||||
} = {},
|
||||
): Promise<AdvancedLaboratoryResults> {
|
||||
const results = emptyAdvancedLaboratoryResults();
|
||||
if (workId === "lab-v1-vegetation-benchmark") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation benchmark identity не выбрана.");
|
||||
results.vegetationBenchmark = await fetchVegetationBenchmarkResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "lab-v1-vegetation-shadow") {
|
||||
if (workId === "lab-v1-vegetation-shadow") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation LAB identity не выбрана.");
|
||||
results.vegetationShadow = await fetchVegetationShadowResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48-object-centric-quality") {
|
||||
|
||||
@@ -45,7 +45,6 @@ import type { M49TgsFullShadowResult } from "./m49TgsFullShadow";
|
||||
import type { VegetationShadowResult } from "./vegetationShadow";
|
||||
|
||||
export interface AdvancedLaboratoryResults {
|
||||
vegetationBenchmark: VegetationShadowResult | null;
|
||||
vegetationShadow: VegetationShadowResult | null;
|
||||
m47Graph: M47ReferenceGraphLabResult | null;
|
||||
m48: M48AdvancedResult | null;
|
||||
|
||||
@@ -967,7 +967,6 @@ export async function fetchAdvancedLaboratoryResults({
|
||||
const e39 = settledCatalogValue(settled[7]);
|
||||
const e40 = settledCatalogValue(settled[8]);
|
||||
return {
|
||||
vegetationBenchmark: null,
|
||||
vegetationShadow: null,
|
||||
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
|
||||
m48r3StaticOccupancy: null,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { LaboratoryFetch } from "./advancedResults";
|
||||
|
||||
const RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
|
||||
const BENCHMARK_RESULT_ID = /^lab-v1-vegetation-benchmark-[a-f0-9]{64}$/;
|
||||
const SHA256 = /^[a-f0-9]{64}$/;
|
||||
const CANDIDATES = ["ddrnet", "ppliteseg"] as const;
|
||||
const ROUTE_MODES = ["source", "ddrnet", "ppliteseg", "urban", "rural", "offroad"] as const;
|
||||
@@ -56,9 +55,7 @@ export interface VegetationVideoSemanticClass {
|
||||
classId: number;
|
||||
label: string;
|
||||
colorRgb: readonly [number, number, number];
|
||||
disposition: "labeled" | "ambiguous" | "prediction" | "undefined";
|
||||
materialClass: string | null;
|
||||
evidenceState: string | null;
|
||||
disposition: "prediction" | "undefined";
|
||||
}
|
||||
|
||||
export interface VegetationRouteVideo {
|
||||
@@ -70,13 +67,8 @@ export interface VegetationRouteVideo {
|
||||
height: 600;
|
||||
centerCropXyxy: readonly [100, 0, 700, 600];
|
||||
outsideCropState: "undefined";
|
||||
viewKind: "fine-semantic-prediction" | "coarse-material-policy-review";
|
||||
linkedTgsResultId: string | null;
|
||||
taxonomy: readonly VegetationVideoSemanticClass[];
|
||||
aggregatePredictionPixels: readonly number[];
|
||||
policyPresets: Readonly<Record<string, Readonly<Record<string, string>>>> | null;
|
||||
fusionMode: "synchronised-multilayer-review" | null;
|
||||
validFovMaskSha256: string | null;
|
||||
}
|
||||
|
||||
export interface VegetationShadowResult {
|
||||
@@ -194,7 +186,6 @@ function visualCaseValue(
|
||||
value: unknown,
|
||||
resultId: string,
|
||||
expectedKind: "goose" | "ravnoves",
|
||||
endpointRoot: string,
|
||||
): VegetationVisualCase {
|
||||
const row = objectValue(value, `vegetation.${expectedKind}.case`);
|
||||
exact(row.source_kind, expectedKind, "vegetation.case.source_kind");
|
||||
@@ -213,7 +204,7 @@ function visualCaseValue(
|
||||
if (!SHA256.test(sha256) || !path.startsWith(`visual/${expectedKind}/${caseId}/`)) {
|
||||
throw new VegetationShadowContractError(`vegetation.case.assets.${key}: proof invalid.`);
|
||||
}
|
||||
projected[key] = `${endpointRoot}/${encodeURIComponent(resultId)}/assets/${path
|
||||
projected[key] = `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/assets/${path
|
||||
.split("/")
|
||||
.map(encodeURIComponent)
|
||||
.join("/")}`;
|
||||
@@ -266,9 +257,6 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
"vegetation.route_video.m47_reference_graph_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 (
|
||||
!/^lab-v1-ravnoves-video-ddrnet-[a-f0-9]{64}$/.test(workerResultId)
|
||||
|| !/^m47-reference-graph-lab-[a-f0-9]{64}$/.test(m47ReferenceGraphResultId)
|
||||
@@ -276,15 +264,6 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
) {
|
||||
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.width, 800, "vegetation.route_video.width");
|
||||
exact(row.height, 600, "vegetation.route_video.height");
|
||||
@@ -300,9 +279,11 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: crop contract changed.");
|
||||
}
|
||||
const taxonomy = objectValue(row.taxonomy, "vegetation.route_video.taxonomy");
|
||||
exact(taxonomy.schema_version, viewKind === "coarse-material-policy-review"
|
||||
? "missioncore.lab-v1-terrain-policy-taxonomy/v1"
|
||||
: "missioncore.lab-v1-vegetation-taxonomy/v1", "vegetation.route_video.taxonomy.schema");
|
||||
exact(
|
||||
taxonomy.schema_version,
|
||||
"missioncore.lab-v1-vegetation-taxonomy/v1",
|
||||
"vegetation.route_video.taxonomy.schema",
|
||||
);
|
||||
const classes = arrayValue(taxonomy.classes, "vegetation.route_video.taxonomy.classes")
|
||||
.map((value, expectedId): VegetationVideoSemanticClass => {
|
||||
const item = objectValue(value, `vegetation.route_video.taxonomy[${expectedId}]`);
|
||||
@@ -315,95 +296,36 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
if (color.length !== 3 || color.some((channel) => channel > 255)) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: taxonomy color invalid.");
|
||||
}
|
||||
const disposition = item.disposition;
|
||||
if (
|
||||
disposition !== "labeled"
|
||||
&& disposition !== "ambiguous"
|
||||
&& disposition !== "prediction"
|
||||
&& disposition !== "undefined"
|
||||
) {
|
||||
const disposition: VegetationVideoSemanticClass["disposition"] = expectedId === 0
|
||||
? "undefined"
|
||||
: "prediction";
|
||||
if (item.disposition !== disposition) {
|
||||
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 {
|
||||
classId,
|
||||
label: textValue(item.label, `vegetation.route_video.label[${expectedId}]`),
|
||||
colorRgb: color as unknown as readonly [number, number, number],
|
||||
disposition,
|
||||
materialClass,
|
||||
evidenceState,
|
||||
};
|
||||
});
|
||||
const expectedClassCount = viewKind === "coarse-material-policy-review" ? 10 : 64;
|
||||
if (classes.length !== expectedClassCount) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: taxonomy size changed.");
|
||||
}
|
||||
if (
|
||||
viewKind === "coarse-material-policy-review"
|
||||
&& (classes[9]?.disposition !== "undefined" || classes[9]?.evidenceState !== "UNOBSERVED")
|
||||
) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: valid-FOV class changed.");
|
||||
if (classes.length !== 64) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: taxonomy must contain 64 classes.");
|
||||
}
|
||||
const aggregatePredictionPixels = arrayValue(
|
||||
row.aggregate_prediction_pixels,
|
||||
"vegetation.route_video.aggregate_prediction_pixels",
|
||||
).map((value, index) => integerValue(value, `vegetation.route_video.pixels[${index}]`));
|
||||
if (aggregatePredictionPixels.length !== expectedClassCount) {
|
||||
if (aggregatePredictionPixels.length !== 64) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: class accounting changed.");
|
||||
}
|
||||
const maskArchive = objectValue(row.mask_archive, "vegetation.route_video.mask_archive");
|
||||
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");
|
||||
exact(maskArchive.path, "video/ddrnet-semantic-masks.zip", "vegetation.route_video.mask_archive.path");
|
||||
const archiveSha256 = textValue(maskArchive.sha256, "vegetation.route_video.mask_archive.sha256");
|
||||
if (!SHA256.test(archiveSha256)) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: archive digest invalid.");
|
||||
}
|
||||
integerValue(maskArchive.byte_length, "vegetation.route_video.mask_archive.byte_length");
|
||||
let policyPresets: VegetationRouteVideo["policyPresets"] = null;
|
||||
let fusionMode: VegetationRouteVideo["fusionMode"] = null;
|
||||
let validFovMaskSha256: string | null = null;
|
||||
if (viewKind === "coarse-material-policy-review") {
|
||||
const validFov = objectValue(row.valid_fov, "vegetation.route_video.valid_fov");
|
||||
exact(validFov.mask_path, "video/valid-fov-mask.png", "vegetation.route_video.valid_fov.path");
|
||||
validFovMaskSha256 = textValue(
|
||||
validFov.mask_sha256,
|
||||
"vegetation.route_video.valid_fov.sha256",
|
||||
);
|
||||
if (!SHA256.test(validFovMaskSha256)) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: valid-FOV digest invalid.");
|
||||
}
|
||||
exact(validFov.outside_valid_fov_class_id, 9, "vegetation.route_video.valid_fov.class_id");
|
||||
const policy = objectValue(row.policy, "vegetation.route_video.policy");
|
||||
const presets = objectValue(policy.presets, "vegetation.route_video.policy.presets");
|
||||
policyPresets = Object.fromEntries(Object.entries(presets).map(([presetId, rawRules]) => {
|
||||
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 {
|
||||
workerResultId,
|
||||
m47ReferenceGraphResultId,
|
||||
@@ -413,21 +335,12 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
height: 600,
|
||||
centerCropXyxy: [100, 0, 700, 600],
|
||||
outsideCropState: "undefined",
|
||||
viewKind,
|
||||
linkedTgsResultId,
|
||||
taxonomy: classes,
|
||||
aggregatePredictionPixels,
|
||||
policyPresets,
|
||||
fusionMode,
|
||||
validFovMaskSha256,
|
||||
};
|
||||
}
|
||||
|
||||
function parseResult(
|
||||
value: unknown,
|
||||
resultId: string,
|
||||
endpointRoot: string,
|
||||
): VegetationShadowResult {
|
||||
function parseResult(value: unknown, resultId: string): VegetationShadowResult {
|
||||
const payload = objectValue(value, "Vegetation LAB");
|
||||
exact(payload.schema_version, "missioncore.lab-v1-vegetation-shadow/v1", "vegetation.schema");
|
||||
exact(payload.result_id, resultId, "vegetation.result_id");
|
||||
@@ -463,9 +376,9 @@ function parseResult(
|
||||
"vegetation.authority.camera_semantics_can_clear_rigid_geometry",
|
||||
);
|
||||
const routeCases = arrayValue(catalogs.ravnoves, "vegetation.catalogs.ravnoves")
|
||||
.map((item) => visualCaseValue(item, resultId, "ravnoves", endpointRoot));
|
||||
.map((item) => visualCaseValue(item, resultId, "ravnoves"));
|
||||
const validationCases = arrayValue(catalogs.goose, "vegetation.catalogs.goose")
|
||||
.map((item) => visualCaseValue(item, resultId, "goose", endpointRoot));
|
||||
.map((item) => visualCaseValue(item, resultId, "goose"));
|
||||
if (routeCases.length !== 0 || validationCases.length !== 12) {
|
||||
throw new VegetationShadowContractError("vegetation.catalogs: ожидалось 12 truth-backed GOOSE случаев без route viewer.");
|
||||
}
|
||||
@@ -515,36 +428,5 @@ export async function fetchVegetationShadowResult(
|
||||
if (!response.ok) {
|
||||
throw new VegetationShadowContractError(`Vegetation LAB недоступна: HTTP ${response.status}.`);
|
||||
}
|
||||
return parseResult(
|
||||
await response.json(),
|
||||
resultId,
|
||||
"/api/v1/laboratory/vegetation-shadow",
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchVegetationBenchmarkResult(
|
||||
resultId: string,
|
||||
{
|
||||
fetcher = fetch,
|
||||
signal,
|
||||
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<VegetationShadowResult> {
|
||||
if (!BENCHMARK_RESULT_ID.test(resultId)) {
|
||||
throw new VegetationShadowContractError("Vegetation benchmark identity недопустима.");
|
||||
}
|
||||
const endpointRoot = "/api/v1/laboratory/vegetation-benchmark";
|
||||
const response = await fetcher(
|
||||
`${endpointRoot}/${encodeURIComponent(resultId)}`,
|
||||
{ method: "GET", headers: { Accept: "application/json" }, signal },
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new VegetationShadowContractError(
|
||||
`Vegetation benchmark недоступен: HTTP ${response.status}.`,
|
||||
);
|
||||
}
|
||||
const result = parseResult(await response.json(), resultId, endpointRoot);
|
||||
if (result.routeVideo) {
|
||||
throw new VegetationShadowContractError("Vegetation benchmark содержит route video.");
|
||||
}
|
||||
return result;
|
||||
return parseResult(await response.json(), resultId);
|
||||
}
|
||||
|
||||
@@ -81,21 +81,6 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"][data-multi-semantic="true"] {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"][data-multi-semantic="true"]
|
||||
> .m4-replay-threat-visual__pane-layer-controls {
|
||||
flex: 1 0 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"][data-multi-semantic="true"]
|
||||
> .m4-replay-threat-visual__pane-mode-controls {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.m4-replay-threat-evidence-viewer[data-mode-controls="content"]:has(
|
||||
.m4-replay-threat-visual__review-controls
|
||||
) .m4-replay-threat-visual__pane-toolbar[data-pane-toolbar="media"] {
|
||||
|
||||
@@ -51,7 +51,6 @@ import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
|
||||
import { M49TgsFailClosedResultView } from "./M49TgsFailClosedResult";
|
||||
import { M49TgsFullShadowResultView } from "./M49TgsFullShadowResult";
|
||||
import { VegetationShadowResultView } from "./VegetationShadowResult";
|
||||
import { VegetationBenchmarkResultView } from "./VegetationBenchmarkResult";
|
||||
|
||||
export { isAdvancedLaboratoryWorkId };
|
||||
export type { AdvancedLaboratoryWorkId };
|
||||
@@ -94,9 +93,6 @@ export function AdvancedLaboratoryResult({
|
||||
failedSessionId: string | null;
|
||||
replayError: string | null;
|
||||
}) {
|
||||
if (workId === "lab-v1-vegetation-benchmark" && results.vegetationBenchmark) {
|
||||
return <VegetationBenchmarkResultView rigLabel={rigLabel} result={results.vegetationBenchmark} />;
|
||||
}
|
||||
if (workId === "lab-v1-vegetation-shadow" && results.vegetationShadow) {
|
||||
return <VegetationShadowResultView rigLabel={rigLabel} result={results.vegetationShadow} />;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
import {
|
||||
M4ReplayThreatVisual,
|
||||
type M4ReplayClassifiedSpatialFrame,
|
||||
type M4ReplayThreatSemanticLayer,
|
||||
} from "./M4ReplayThreatVisual";
|
||||
|
||||
const CLASSES: readonly RecordedEvidenceSemanticClass[] = [
|
||||
@@ -43,15 +42,7 @@ function message(error: unknown): string {
|
||||
: "Полный TGS spatial frame недоступен.";
|
||||
}
|
||||
|
||||
export function M49TgsFullShadowEvidence({
|
||||
result,
|
||||
semanticOverride,
|
||||
evidenceLabel = "M49 · full TGS shadow",
|
||||
}: {
|
||||
result: M49TgsFullShadowResult;
|
||||
semanticOverride?: M4ReplayThreatSemanticLayer;
|
||||
evidenceLabel?: string;
|
||||
}) {
|
||||
export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowResult }) {
|
||||
const [activeSequence, setActiveSequence] = useState<number | null>(null);
|
||||
const [semantic, setSemantic] = useState<E47SemanticSlamResult | null>(null);
|
||||
const [semanticError, setSemanticError] = useState<string | null>(null);
|
||||
@@ -200,31 +191,18 @@ export function M49TgsFullShadowEvidence({
|
||||
const handleSequenceChange = useCallback((sequence: number | null) => {
|
||||
setActiveSequence(sequence);
|
||||
}, []);
|
||||
const semanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(() => [
|
||||
...(semantic ? [{
|
||||
id: "urban",
|
||||
controlLabel: "ГОРОД · EoMT",
|
||||
resultId: semantic.resultId,
|
||||
taxonomy: semantic.taxonomy,
|
||||
label: "EoMT Cityscapes semantic · recorded video",
|
||||
maskAriaLabel: "EoMT urban semantic prediction",
|
||||
}] : []),
|
||||
...(semanticOverride ? [{
|
||||
...semanticOverride,
|
||||
id: semanticOverride.id ?? "vegetation",
|
||||
controlLabel: semanticOverride.controlLabel ?? "ПРИРОДА · DDRNet",
|
||||
}] : []),
|
||||
], [semantic, semanticOverride]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<M4ReplayThreatVisual
|
||||
resultId={result.source.linkedVisualResultId}
|
||||
semanticLayers={semanticLayers}
|
||||
initialSemanticLayerId={semanticOverride ? "vegetation" : "urban"}
|
||||
semantic={semantic ? {
|
||||
resultId: semantic.resultId,
|
||||
taxonomy: semantic.taxonomy,
|
||||
} : undefined}
|
||||
showReviewAnchorBoxes={false}
|
||||
reviewLabel="4 489 source-paced TGS frames"
|
||||
evidenceLabel={evidenceLabel}
|
||||
evidenceLabel="M49 · full TGS shadow"
|
||||
initialSpatialMode="3d"
|
||||
onActiveSequenceChange={handleSequenceChange}
|
||||
classifiedSpatialLayer={{
|
||||
|
||||
@@ -96,8 +96,6 @@ function SpatialState({ message: text }: { message: string }) {
|
||||
}
|
||||
|
||||
export interface M4ReplayThreatSemanticLayer {
|
||||
id?: string;
|
||||
controlLabel?: string;
|
||||
resultId: string;
|
||||
spatialResultId?: string | null;
|
||||
maskUrl?: (sequence: number) => string;
|
||||
@@ -157,8 +155,6 @@ const EMPTY_REVIEW_ANCHORS: readonly M4ReplayThreatReviewAnchor[] = [];
|
||||
export function M4ReplayThreatVisual({
|
||||
resultId,
|
||||
semantic,
|
||||
semanticLayers,
|
||||
initialSemanticLayerId,
|
||||
reviewAnchors = EMPTY_REVIEW_ANCHORS,
|
||||
showReviewAnchorBoxes = true,
|
||||
reviewLabel = "Контрольные примеры M4.8R1",
|
||||
@@ -172,8 +168,6 @@ export function M4ReplayThreatVisual({
|
||||
}: {
|
||||
resultId: string;
|
||||
semantic?: M4ReplayThreatSemanticLayer;
|
||||
semanticLayers?: readonly M4ReplayThreatSemanticLayer[];
|
||||
initialSemanticLayerId?: string;
|
||||
reviewAnchors?: readonly M4ReplayThreatReviewAnchor[];
|
||||
showReviewAnchorBoxes?: boolean;
|
||||
reviewLabel?: string;
|
||||
@@ -205,35 +199,6 @@ export function M4ReplayThreatVisual({
|
||||
));
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [selectedReviewAnchorIndex, setSelectedReviewAnchorIndex] = useState(0);
|
||||
const availableSemanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(
|
||||
() => semanticLayers?.length ? semanticLayers : semantic ? [semantic] : [],
|
||||
[semantic, semanticLayers],
|
||||
);
|
||||
const semanticLayerIdentity = availableSemanticLayers
|
||||
.map((layer, index) => layer.id ?? `${layer.resultId}:${index}`)
|
||||
.join("|");
|
||||
const [selectedSemanticLayerId, setSelectedSemanticLayerId] = useState(
|
||||
initialSemanticLayerId ?? "",
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!availableSemanticLayers.length) {
|
||||
setSelectedSemanticLayerId("");
|
||||
return;
|
||||
}
|
||||
const selectedStillExists = availableSemanticLayers.some(
|
||||
(layer, index) => (layer.id ?? `${layer.resultId}:${index}`) === selectedSemanticLayerId,
|
||||
);
|
||||
if (selectedStillExists) return;
|
||||
const preferred = initialSemanticLayerId
|
||||
? availableSemanticLayers.find((layer) => layer.id === initialSemanticLayerId)
|
||||
: null;
|
||||
const next = preferred ?? availableSemanticLayers[0]!;
|
||||
const nextIndex = availableSemanticLayers.indexOf(next);
|
||||
setSelectedSemanticLayerId(next.id ?? `${next.resultId}:${nextIndex}`);
|
||||
}, [availableSemanticLayers, initialSemanticLayerId, semanticLayerIdentity, selectedSemanticLayerId]);
|
||||
const activeSemantic = availableSemanticLayers.find(
|
||||
(layer, index) => (layer.id ?? `${layer.resultId}:${index}`) === selectedSemanticLayerId,
|
||||
) ?? availableSemanticLayers[0];
|
||||
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
|
||||
const metadata = useM4ThreatTimelineMetadata(resultId, timelineEndpointRoot);
|
||||
const playbackRange = useMemo(() => metadata.timeline ? ({
|
||||
@@ -333,21 +298,19 @@ export function M4ReplayThreatVisual({
|
||||
sequence: frame?.sequence ?? null,
|
||||
endpointRoot: timelineEndpointRoot,
|
||||
});
|
||||
const semanticSpatialResultId = activeSemantic
|
||||
? activeSemantic.spatialResultId === undefined
|
||||
? activeSemantic.resultId
|
||||
: activeSemantic.spatialResultId
|
||||
const semanticSpatialResultId = semantic
|
||||
? semantic.spatialResultId === undefined ? semantic.resultId : semantic.spatialResultId
|
||||
: null;
|
||||
const spatialSemanticTaxonomy = useMemo<readonly E47SemanticClass[]>(
|
||||
() => semanticSpatialResultId && activeSemantic
|
||||
? activeSemantic.taxonomy.map((item) => ({
|
||||
() => semanticSpatialResultId && semantic
|
||||
? semantic.taxonomy.map((item) => ({
|
||||
classId: item.classId,
|
||||
label: item.label,
|
||||
disposition: item.disposition === "ambiguous" ? "ambiguous" : "labeled",
|
||||
colorRgb: item.colorRgb,
|
||||
}))
|
||||
: [],
|
||||
[activeSemantic, semanticSpatialResultId],
|
||||
[semantic, semanticSpatialResultId],
|
||||
);
|
||||
const semanticTimeline = useE47SemanticTimelineFrame({
|
||||
resultId: semanticSpatialResultId,
|
||||
@@ -415,22 +378,22 @@ export function M4ReplayThreatVisual({
|
||||
);
|
||||
}, [frame, metadata.timeline, showReferenceMediaLayers, showStaticObstacles]);
|
||||
const activeBoxes = useMemo(
|
||||
() => !showReferenceMediaLayers ? [] : [
|
||||
() => classifiedSpatialLayer || !showReferenceMediaLayers ? [] : [
|
||||
...boxes(frame?.cameraProposals ?? []),
|
||||
...staticObstacleBoxes,
|
||||
...reviewAnchorBoxes,
|
||||
],
|
||||
[frame, reviewAnchorBoxes, showReferenceMediaLayers, staticObstacleBoxes],
|
||||
[classifiedSpatialLayer, frame, reviewAnchorBoxes, showReferenceMediaLayers, staticObstacleBoxes],
|
||||
);
|
||||
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
|
||||
() => activeSemantic?.taxonomy.map((item) => ({
|
||||
() => semantic?.taxonomy.map((item) => ({
|
||||
id: item.classId,
|
||||
label: `semantic: ${item.label}`,
|
||||
})) ?? [],
|
||||
[activeSemantic?.taxonomy],
|
||||
[semantic?.taxonomy],
|
||||
);
|
||||
const semanticPalette = useMemo<readonly RecordedEvidenceSemanticPaletteEntry[]>(
|
||||
() => activeSemantic?.taxonomy.map((item) => ({
|
||||
() => semantic?.taxonomy.map((item) => ({
|
||||
classId: item.classId,
|
||||
color: item.disposition === "undefined"
|
||||
? { kind: "transparent" as const }
|
||||
@@ -441,7 +404,7 @@ export function M4ReplayThreatVisual({
|
||||
? 0
|
||||
: item.disposition === "ambiguous" ? 0.52 : 0.92,
|
||||
})) ?? [],
|
||||
[activeSemantic?.taxonomy],
|
||||
[semantic?.taxonomy],
|
||||
);
|
||||
const semanticFrame = semanticTimeline.activeFrame?.sequence === frame?.sequence
|
||||
? semanticTimeline.activeFrame
|
||||
@@ -459,7 +422,7 @@ export function M4ReplayThreatVisual({
|
||||
&& lastSpatialSemanticFrameRef.current.frame.sequence === spatialFrame?.sequence
|
||||
? lastSpatialSemanticFrameRef.current.frame
|
||||
: null;
|
||||
const semanticIntegrityError = activeSemantic && spatialFrame && spatialSemanticFrame && (
|
||||
const semanticIntegrityError = semantic && spatialFrame && spatialSemanticFrame && (
|
||||
spatialSemanticFrame.sourcePointCount !== spatialFrame.pointCloudSourceCount
|
||||
|| spatialFrame.pointCloudSampleCount !== spatialFrame.pointCloudSourceCount
|
||||
|| spatialFrame.pointCloudBodyXyzM.length !== spatialFrame.pointCloudSourceCount
|
||||
@@ -468,7 +431,7 @@ export function M4ReplayThreatVisual({
|
||||
: null;
|
||||
const alignedSemanticPointIds = useMemo<readonly (number | null)[] | undefined>(() => {
|
||||
if (
|
||||
!activeSemantic
|
||||
!semantic
|
||||
|| !showSpatialSemantic
|
||||
|| !spatialFrame
|
||||
|| !spatialSemanticFrame
|
||||
@@ -478,7 +441,7 @@ export function M4ReplayThreatVisual({
|
||||
const status = spatialSemanticFrame.statusCodes[index];
|
||||
return status === 2 || status === 3 ? classId : null;
|
||||
});
|
||||
}, [activeSemantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
|
||||
}, [semantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
|
||||
const activeSpatialFrame = spatialFrame?.sequence === timelineFrame.activeSequence
|
||||
? spatialFrame
|
||||
: null;
|
||||
@@ -647,19 +610,19 @@ export function M4ReplayThreatVisual({
|
||||
},
|
||||
), [metadata.timeline, spatialFrame, timelineFrame.availableFrames]);
|
||||
const semanticOverlay: RecordedEvidenceSemanticOverlay | undefined =
|
||||
activeSemantic && showMediaSemantic && frame
|
||||
semantic && showMediaSemantic && frame
|
||||
? {
|
||||
src: activeSemantic.maskUrl?.(frame.sequence)
|
||||
?? e47SemanticMaskUrl(activeSemantic.resultId, frame.sequence),
|
||||
src: semantic.maskUrl?.(frame.sequence)
|
||||
?? e47SemanticMaskUrl(semantic.resultId, frame.sequence),
|
||||
prefetchSrcs: Array.from({ length: 12 }, (_, index) => index + 1)
|
||||
.map((offset) => frame.sequence + offset)
|
||||
.filter((sequence) => sequence < (metadata.timeline?.frameCount ?? 0))
|
||||
.map((sequence) => activeSemantic.maskUrl?.(sequence)
|
||||
?? e47SemanticMaskUrl(activeSemantic.resultId, sequence)),
|
||||
.map((sequence) => semantic.maskUrl?.(sequence)
|
||||
?? e47SemanticMaskUrl(semantic.resultId, sequence)),
|
||||
classes: semanticClasses,
|
||||
palette: semanticPalette,
|
||||
opacity: 0.9,
|
||||
ariaLabel: `${activeSemantic.maskAriaLabel ?? "Semantic prediction"} frame ${frame.sequence + 1}`,
|
||||
ariaLabel: `${semantic.maskAriaLabel ?? "Semantic prediction"} frame ${frame.sequence + 1}`,
|
||||
}
|
||||
: undefined;
|
||||
const accumulatedCameraPoints = cameraPointOverlay.overlay?.sequence === frame?.sequence
|
||||
@@ -729,7 +692,7 @@ export function M4ReplayThreatVisual({
|
||||
</div>
|
||||
);
|
||||
|
||||
const mediaLayerControls = activeSemantic
|
||||
const mediaLayerControls = semantic
|
||||
|| (showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery)
|
||||
|| (showReferenceMediaLayers && metadata.timeline?.cameraObstacleProjectionDelivery) ? (
|
||||
<div
|
||||
@@ -737,7 +700,7 @@ export function M4ReplayThreatVisual({
|
||||
role="group"
|
||||
aria-label="Слои камеры и видео"
|
||||
>
|
||||
{activeSemantic ? (
|
||||
{semantic ? (
|
||||
<Button
|
||||
size="compact"
|
||||
shape="pill"
|
||||
@@ -748,20 +711,6 @@ export function M4ReplayThreatVisual({
|
||||
SEMANTICS
|
||||
</Button>
|
||||
) : null}
|
||||
{availableSemanticLayers.length > 1 ? (
|
||||
<SegmentedControl
|
||||
value={selectedSemanticLayerId}
|
||||
items={availableSemanticLayers.map((layer, index) => ({
|
||||
value: layer.id ?? `${layer.resultId}:${index}`,
|
||||
label: layer.controlLabel ?? layer.label ?? `SEMANTIC ${index + 1}`,
|
||||
}))}
|
||||
label="Источник семантики"
|
||||
onChange={(value) => {
|
||||
setSelectedSemanticLayerId(value);
|
||||
setShowMediaSemantic(true);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery ? (
|
||||
<Button
|
||||
size="compact"
|
||||
@@ -1073,7 +1022,6 @@ export function M4ReplayThreatVisual({
|
||||
<div
|
||||
className="m4-replay-threat-visual__pane-toolbar"
|
||||
data-pane-toolbar="media"
|
||||
data-multi-semantic={availableSemanticLayers.length > 1 ? "true" : undefined}
|
||||
>
|
||||
{mediaLayerControls}
|
||||
{mediaModeControls}
|
||||
@@ -1290,8 +1238,8 @@ export function M4ReplayThreatVisual({
|
||||
return (
|
||||
<div className="l3-visual-audit m4-replay-threat-visual">
|
||||
<LaboratoryEvidenceViewer
|
||||
label={activeSemantic
|
||||
? activeSemantic.label ?? "Semantic diagnostic replay"
|
||||
label={semantic
|
||||
? semantic.label ?? "Semantic diagnostic replay"
|
||||
: `${evidenceLabel} recorded-realtime replay`}
|
||||
className="m4-replay-threat-evidence-viewer"
|
||||
mode={mediaMode ?? "none"}
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import {
|
||||
LaboratoryEvidence,
|
||||
LaboratoryResultSummary,
|
||||
LaboratorySummary,
|
||||
LaboratoryWorkTemplate,
|
||||
} from "../../components/laboratory/LaboratoryPresentation";
|
||||
import type { VegetationShadowResult } from "../../core/laboratory/vegetationShadow";
|
||||
import {
|
||||
M48MaskComparisonVisual,
|
||||
type M48MaskComparisonCase,
|
||||
} from "./M48FailureAtlasVisual";
|
||||
|
||||
function decimal(value: number, digits = 1): string {
|
||||
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
|
||||
}
|
||||
|
||||
const VEGETATION_LABELS: Readonly<Record<string, string>> = {
|
||||
high_grass: "Высокая трава",
|
||||
low_grass: "Низкая трава",
|
||||
bush: "Куст",
|
||||
tree_trunk: "Ствол дерева",
|
||||
tree_crown: "Крона дерева",
|
||||
hedge: "Живая изгородь",
|
||||
forest: "Лесная растительность",
|
||||
crops: "Посевы",
|
||||
};
|
||||
|
||||
function comparisonCases(result: VegetationShadowResult): readonly M48MaskComparisonCase[] {
|
||||
return result.validationCases.map((item) => {
|
||||
const focus = item.focus!;
|
||||
return {
|
||||
caseId: item.caseId,
|
||||
title: `${VEGETATION_LABELS[focus.className] ?? focus.className} · truth ${decimal(focus.truthFraction * 100, 1)}% кадра`,
|
||||
sourceUrl: item.assets.source,
|
||||
truthUrl: item.assets.truth,
|
||||
predictions: {
|
||||
ddrnet: item.assets.ddrnet,
|
||||
ppliteseg: item.assets.ppliteseg,
|
||||
},
|
||||
errors: {
|
||||
ddrnet: item.assets.ddrnet_error,
|
||||
ppliteseg: item.assets.ppliteseg_error,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function VegetationBenchmarkResultView({
|
||||
rigLabel,
|
||||
result,
|
||||
}: {
|
||||
rigLabel: string;
|
||||
result: VegetationShadowResult;
|
||||
}) {
|
||||
const selected = result.candidates.find(
|
||||
(candidate) => candidate.candidate === result.selectedCandidate,
|
||||
)!;
|
||||
const alternative = result.candidates.find(
|
||||
(candidate) => candidate.candidate !== result.selectedCandidate,
|
||||
)!;
|
||||
|
||||
return (
|
||||
<LaboratoryWorkTemplate
|
||||
summary={(
|
||||
<LaboratorySummary
|
||||
title="M4.8 · архивный benchmark растительности"
|
||||
description="Отдельный truth-backed контур GOOSE для сравнения готовых fine-64 весов. Он не является частью RAVNOVES00 realtime LAB и открывается автономно без Worker 006."
|
||||
status="ARCHIVE ANALYSIS · model qualification only · commands OFF"
|
||||
statusTone="warning"
|
||||
facts={[
|
||||
{ label: "Источник", value: "GOOSE validation · 962 размеченных кадра · 12 hard cases" },
|
||||
{ label: "Сравнение", value: "DDRNet-39 vs PPLiteSeg · official fine-64 weights" },
|
||||
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
|
||||
{ label: "Authority", value: `${rigLabel} · MODEL QUALIFICATION ONLY · commands OFF` },
|
||||
]}
|
||||
brief={{
|
||||
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
|
||||
approach: "Обе модели прогнаны на 962 кадрах, а 12 визуальных кейсов выбраны детерминированно по truth-поддержке восьми растительных классов. Viewer показывает source, ручной truth, prediction и error.",
|
||||
principalResult: `${selected.loadedModelName} лидирует по vegetation IoU: ${decimal(selected.vegetationMeanIouPercent, 2)}% против ${decimal(alternative.vegetationMeanIouPercent, 2)}%.`,
|
||||
limitation: "GOOSE — внешний размеченный домен. Результат выбирает стартовые веса, но не доказывает качество на fisheye RAVNOVES00 и не даёт navigation authority.",
|
||||
}}
|
||||
method={{
|
||||
completeness: "complete",
|
||||
executionClass: "ai-inference",
|
||||
pipelineId: "goose-fine64-ready-weights-benchmark-archive/v1",
|
||||
components: result.candidates.map((candidate) => ({
|
||||
kind: "model" as const,
|
||||
name: candidate.loadedModelName,
|
||||
version: candidate.candidate,
|
||||
role: candidate.candidate === result.selectedCandidate
|
||||
? "selected vegetation candidate"
|
||||
: "comparison candidate",
|
||||
identitySha256: candidate.checkpointSha256,
|
||||
})),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
evidence={(
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
|
||||
title="TRUTH — ручная разметка · PREDICTION — ответ модели · ERROR — расхождение"
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<M48MaskComparisonVisual
|
||||
cases={comparisonCases(result)}
|
||||
initialCandidate={result.selectedCandidate}
|
||||
/>
|
||||
</LaboratoryEvidence>
|
||||
)}
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title="DDRNet выбран как стартовый vegetation candidate"
|
||||
status={`${selected.loadedModelName} · перенос на ровер не доказан`}
|
||||
statusTone="warning"
|
||||
metrics={[
|
||||
{
|
||||
label: "GOOSE mIoU",
|
||||
value: `${decimal(selected.meanIouPercent, 2)}% / ${decimal(alternative.meanIouPercent, 2)}%`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · полный validation split`,
|
||||
},
|
||||
{
|
||||
label: "Vegetation IoU",
|
||||
value: `${decimal(selected.vegetationMeanIouPercent, 2)}% / ${decimal(alternative.vegetationMeanIouPercent, 2)}%`,
|
||||
hint: "grass/vegetation/bush/tree и родственные fine-64 labels",
|
||||
},
|
||||
{
|
||||
label: "Worker shadow p95",
|
||||
value: `${decimal(selected.shadowLatencyP95Ms, 2)} / ${decimal(alternative.shadowLatencyP95Ms, 2)} ms`,
|
||||
hint: "чистый inference · одна тяжёлая модель за раз",
|
||||
},
|
||||
{
|
||||
label: "Peak VRAM",
|
||||
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} / ${decimal(alternative.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · RTX 4090`,
|
||||
},
|
||||
]}
|
||||
conclusion={{
|
||||
proved: "Обе готовые fine-64 модели воспроизводимо запускаются; DDRNet лучше по aggregate vegetation IoU.",
|
||||
notProved: "Не доказаны accuracy на нашем fisheye, temporal stability, collision safety и физическое поведение ровера.",
|
||||
decision: "Хранить как архив квалификации весов. Проверку на RAVNOVES00 вести только в основной многослойной LAB.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
LaboratoryEvidence,
|
||||
LaboratoryResultSummary,
|
||||
@@ -11,85 +9,44 @@ import {
|
||||
type VegetationShadowResult,
|
||||
} from "../../core/laboratory/vegetationShadow";
|
||||
import {
|
||||
fetchM49TgsFullShadowResult,
|
||||
type M49TgsFullShadowResult,
|
||||
} from "../../core/laboratory/m49TgsFullShadow";
|
||||
M48MaskComparisonVisual,
|
||||
type M48MaskComparisonCase,
|
||||
} from "./M48FailureAtlasVisual";
|
||||
import { M4ReplayThreatVisual } from "./M4ReplayThreatVisual";
|
||||
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
|
||||
|
||||
function decimal(value: number, digits = 1): string {
|
||||
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
|
||||
}
|
||||
|
||||
function VegetationRouteEvidence({ result }: { result: VegetationShadowResult }) {
|
||||
const route = result.routeVideo!;
|
||||
const [tgs, setTgs] = useState<M49TgsFullShadowResult | null>(null);
|
||||
const [tgsError, setTgsError] = useState<string | null>(null);
|
||||
const VEGETATION_LABELS: Readonly<Record<string, string>> = {
|
||||
high_grass: "Высокая трава",
|
||||
low_grass: "Низкая трава",
|
||||
bush: "Куст",
|
||||
tree_trunk: "Ствол дерева",
|
||||
tree_crown: "Крона дерева",
|
||||
hedge: "Живая изгородь",
|
||||
forest: "Лесная растительность",
|
||||
crops: "Посевы",
|
||||
};
|
||||
|
||||
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 недоступен.");
|
||||
}
|
||||
function comparisonCases(result: VegetationShadowResult): readonly M48MaskComparisonCase[] {
|
||||
return result.validationCases.map((item) => {
|
||||
const focus = item.focus!;
|
||||
return {
|
||||
caseId: item.caseId,
|
||||
title: `${VEGETATION_LABELS[focus.className] ?? focus.className} · truth ${decimal(focus.truthFraction * 100, 1)}% кадра`,
|
||||
sourceUrl: item.assets.source,
|
||||
truthUrl: item.assets.truth,
|
||||
predictions: {
|
||||
ddrnet: item.assets.ddrnet,
|
||||
ppliteseg: item.assets.ppliteseg,
|
||||
},
|
||||
errors: {
|
||||
ddrnet: item.assets.ddrnet_error,
|
||||
ppliteseg: item.assets.ppliteseg_error,
|
||||
},
|
||||
};
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [route.baseM4ResultId, route.linkedTgsResultId]);
|
||||
|
||||
const semantic = {
|
||||
id: "vegetation",
|
||||
controlLabel: "ПРИРОДА · DDRNet",
|
||||
resultId: route.workerResultId,
|
||||
spatialResultId: null,
|
||||
taxonomy: route.taxonomy,
|
||||
maskUrl: (sequence: number) => vegetationVideoMaskUrl(result.resultId, sequence),
|
||||
label: "DDRNet coarse vegetation material · recorded video",
|
||||
maskAriaLabel: "DDRNet vegetation material prediction",
|
||||
} as const;
|
||||
|
||||
if (route.linkedTgsResultId && tgs) {
|
||||
return (
|
||||
<M49TgsFullShadowEvidence
|
||||
result={tgs}
|
||||
semanticOverride={semantic}
|
||||
evidenceLabel="LAB V1 · EoMT + DDRNet + YOLOX + TGS"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (route.linkedTgsResultId && !tgsError) {
|
||||
return (
|
||||
<div className="m4-replay-threat-visual__pane-status" role="status">
|
||||
Открываем sealed EoMT, TGS и coarse vegetation timeline…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<M4ReplayThreatVisual
|
||||
resultId={route.baseM4ResultId}
|
||||
evidenceLabel="LAB V1 · DDRNet"
|
||||
showReferenceMediaLayers
|
||||
showSpatialOverlaySummary={false}
|
||||
semantic={semantic}
|
||||
/>
|
||||
{tgsError ? (
|
||||
<div className="m4-replay-threat-visual__pane-status" role="alert">
|
||||
TGS слой недоступен: {tgsError}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function VegetationShadowResultView({
|
||||
@@ -99,117 +56,143 @@ export function VegetationShadowResultView({
|
||||
rigLabel: string;
|
||||
result: VegetationShadowResult;
|
||||
}) {
|
||||
const route = result.routeVideo;
|
||||
const selected = result.candidates.find(
|
||||
(candidate) => candidate.candidate === result.selectedCandidate,
|
||||
)!;
|
||||
|
||||
const alternative = result.candidates.find(
|
||||
(candidate) => candidate.candidate !== result.selectedCandidate,
|
||||
)!;
|
||||
return (
|
||||
<LaboratoryWorkTemplate
|
||||
summary={(
|
||||
<LaboratorySummary
|
||||
title="LAB V1 · карта ровера · город + растительность"
|
||||
description="Один recorded-контур RAVNOVES00 синхронно показывает городской EoMT, природный DDRNet, frozen YOLOX detections и causal TGS. Семантические маски переключаются, чтобы их цвета не скрывали друг друга; геометрическое veto остаётся независимым."
|
||||
status={route
|
||||
? "MULTILAYER RECORDED REVIEW · commands OFF · route truth отсутствует"
|
||||
: "ROUTE EVIDENCE MISSING · commands OFF"}
|
||||
title="LAB V1 · готовые модели растительности"
|
||||
description={result.routeVideo
|
||||
? "M4.8 сохраняет truth-backed сравнение моделей, а штатный M4.7 viewer показывает фактический DDRNet prediction на всей записи RAVNOVES00. Все 4489 масок запечатаны локально и открываются без Worker 006."
|
||||
: "Штатный M4.8-инструмент сравнивает две готовые fine-64 модели на полном GOOSE validation split и на 12 truth-backed hard cases, выбранных только по наличию нужной растительности. Sealed evidence открывается локально без Worker 006."}
|
||||
status={result.routeVideo
|
||||
? "DDRNet full-video prediction ready · route truth отсутствует"
|
||||
: "Truth-backed model comparison · route transfer не принят"}
|
||||
statusTone="warning"
|
||||
facts={[
|
||||
{ label: "Источник", value: "RAVNOVES00 · sensor.camera.right · 4489 recorded frames" },
|
||||
{ label: "Город", value: "EoMT Cityscapes · sealed E47 semantic archive" },
|
||||
{ label: "Растительность", value: "DDRNet-39 fine-64 → coarse mission-neutral materials" },
|
||||
{ label: "Safety", value: "YOLOX object boxes + causal TGS · semantic masks не снимают veto" },
|
||||
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
|
||||
{ label: "Источник", value: "GOOSE validation · 962 размеченных кадра · 12 vegetation hard cases" },
|
||||
{ label: "Сравнение", value: "DDRNet-39 vs PPLiteSeg · official fine-64 weights" },
|
||||
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
|
||||
...(result.routeVideo ? [{
|
||||
label: "Видео",
|
||||
value: "RAVNOVES00 · 4489/4489 DDRNet masks · exact recorded sequence",
|
||||
}] : []),
|
||||
{ label: "Authority", value: `${rigLabel} · MODEL QUALIFICATION ONLY · commands OFF` },
|
||||
]}
|
||||
brief={{
|
||||
question: "Можно ли одновременно видеть городской и природный semantic stack, не теряя независимую геометрическую защиту?",
|
||||
approach: "EoMT и DDRNet сохранены как два независимых sealed слоя на одной M4 timeline. В штатном M4.7 viewer пользователь переключает только отображаемую маску; YOLOX и TGS остаются активными слоями evidence.",
|
||||
principalResult: route
|
||||
? "Оба semantic archive доступны в одном viewer. Это не пиксельный fusion и не единая новая модель: городской и природный ответы остаются раздельными."
|
||||
: "Route archive для этой immutable identity отсутствует.",
|
||||
limitation: "RAVNOVES00 не имеет ручной truth. DDRNet заметно прыгает между HIGH GRASS, WOODY и UNKNOWN; поэтому subtype нельзя подавать напрямую в planner. Отсутствие класса никогда не означает свободный путь.",
|
||||
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
|
||||
approach: "Обе модели последовательно прогнаны в одном изолированном CUDA-runtime на 962 кадрах. 12 визуальных кейсов выбраны детерминированно по truth-поддержке восьми растительных классов; один M4.8 viewer показывает source, truth, prediction и material-error для выбранной модели.",
|
||||
principalResult: `${selected.loadedModelName} лидирует по vegetation IoU: ${decimal(selected.vegetationMeanIouPercent, 2)}% против ${decimal(alternative.vegetationMeanIouPercent, 2)}%. ${result.routeVideo ? "Его фактическая temporal stability теперь видна на всех 4489 кадрах штатного recorded viewer." : "Ошибки по каждому типу проверяются в одном штатном инструменте."}`,
|
||||
limitation: "GOOSE — внешний размеченный домен; RAVNOVES00 — наш fisheye, но без ручной truth-разметки. Full-video слой показывает prediction, а не доказывает правильность. Папоротник отдельным классом отсутствует.",
|
||||
}}
|
||||
method={{
|
||||
completeness: route ? "complete" : "legacy-partial",
|
||||
completeness: "complete",
|
||||
executionClass: "ai-inference",
|
||||
pipelineId: "ravnoves-eomt-ddrnet-yolox-causal-tgs-recorded-review/v1",
|
||||
components: [
|
||||
{
|
||||
kind: "model",
|
||||
name: "EoMT Cityscapes semantic",
|
||||
version: "sealed E47 archive",
|
||||
role: "urban semantic review",
|
||||
identitySha256: null,
|
||||
},
|
||||
{
|
||||
kind: "model",
|
||||
name: selected.loadedModelName,
|
||||
version: selected.candidate,
|
||||
role: "vegetation material candidate",
|
||||
identitySha256: selected.checkpointSha256,
|
||||
},
|
||||
{
|
||||
kind: "algorithm",
|
||||
name: "Frozen YOLOX + causal TGS",
|
||||
version: "linked M4/M4.9 archives",
|
||||
role: "independent object and geometry veto",
|
||||
identitySha256: null,
|
||||
},
|
||||
],
|
||||
pipelineId: "goose-fine64-ready-weights-to-ravnoves-policy-shadow/v1",
|
||||
components: result.candidates.map((candidate) => ({
|
||||
kind: "model" as const,
|
||||
name: candidate.loadedModelName,
|
||||
version: candidate.candidate,
|
||||
role: candidate.candidate === result.selectedCandidate ? "selected policy provider" : "comparison candidate",
|
||||
identitySha256: candidate.checkpointSha256,
|
||||
})),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
evidence={route ? (
|
||||
evidence={(
|
||||
<>
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
|
||||
title="EoMT CITY / DDRNet VEGETATION + YOLOX + CAUSAL TGS · 4489/4489 · TRUTH отсутствует"
|
||||
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
|
||||
title="ERROR: красный — пропуск · жёлтый — лишнее · фиолетовый — перепутан тип · зелёный — совпадение"
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<VegetationRouteEvidence result={result} />
|
||||
<M48MaskComparisonVisual
|
||||
cases={comparisonCases(result)}
|
||||
initialCandidate={result.selectedCandidate}
|
||||
/>
|
||||
</LaboratoryEvidence>
|
||||
) : (
|
||||
{result.routeVideo ? (
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
|
||||
title="ROUTE ARCHIVE отсутствует"
|
||||
title="DDRNet PREDICTION · 4489/4489 кадров · TRUTH для этой записи отсутствует"
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<div className="m4-replay-threat-visual__pane-status" role="alert">
|
||||
Для этой immutable identity нет полного route video evidence.
|
||||
</div>
|
||||
<M4ReplayThreatVisual
|
||||
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>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title="Многослойный visual review собран; управление не авторизовано"
|
||||
status="Semantics advisory · YOLOX/TGS veto cannot be cleared"
|
||||
title="DDRNet — стартовые веса; перенос на ровер ещё не доказан"
|
||||
status={`${selected.loadedModelName} выбран только как vegetation candidate`}
|
||||
statusTone="warning"
|
||||
metrics={[
|
||||
{
|
||||
label: "Route masks",
|
||||
value: route ? `${route.frameCount}/${route.frameCount}` : "0/4489",
|
||||
hint: "sealed local playback · Worker для открытия не нужен",
|
||||
label: "GOOSE mIoU",
|
||||
value: `${decimal(selected.meanIouPercent, 2)}% / ${decimal(alternative.meanIouPercent, 2)}%`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · полный validation split`,
|
||||
},
|
||||
{
|
||||
label: "Semantic sources",
|
||||
value: route ? "2 independent layers" : "0",
|
||||
hint: "EoMT CITY / DDRNet VEGETATION · display switches, evidence does not fuse",
|
||||
label: "Vegetation IoU",
|
||||
value: `${decimal(selected.vegetationMeanIouPercent, 2)}% / ${decimal(alternative.vegetationMeanIouPercent, 2)}%`,
|
||||
hint: "агрегация классов grass/vegetation/bush/tree и родственных fine-64 labels",
|
||||
},
|
||||
{
|
||||
label: "Vegetation worker p95",
|
||||
value: `${decimal(selected.shadowLatencyP95Ms, 2)} ms`,
|
||||
hint: "изолированный DDRNet inference; не совместный realtime stack",
|
||||
label: "Worker shadow p95",
|
||||
value: `${decimal(selected.shadowLatencyP95Ms, 2)} / ${decimal(alternative.shadowLatencyP95Ms, 2)} ms`,
|
||||
hint: "чистый inference · одна тяжёлая модель за раз",
|
||||
},
|
||||
{
|
||||
label: "Vegetation peak VRAM",
|
||||
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
|
||||
hint: "DDRNet candidate на Worker 006",
|
||||
label: "Cold prewarm",
|
||||
value: `${decimal(selected.shadowPrewarmLatencyMs, 1)} / ${decimal(alternative.shadowPrewarmLatencyMs, 1)} ms`,
|
||||
hint: "один явный inference до допуска кадров; исключён из steady-state p95",
|
||||
},
|
||||
{
|
||||
label: "Worker throughput",
|
||||
value: `${decimal(selected.shadowThroughputFps, 1)} / ${decimal(alternative.shadowThroughputFps, 1)} FPS`,
|
||||
hint: "изолированный Worker 006 · не realtime graph целиком",
|
||||
},
|
||||
{
|
||||
label: "Peak VRAM",
|
||||
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} / ${decimal(alternative.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · RTX 4090`,
|
||||
},
|
||||
{
|
||||
label: "Hard-case evidence",
|
||||
value: "12 truth-backed cases",
|
||||
hint: "8 vegetation strata · Worker для открытия не требуется",
|
||||
},
|
||||
...(result.routeVideo ? [{
|
||||
label: "Route video",
|
||||
value: "4489/4489 masks",
|
||||
hint: "DDRNet prediction · exact sequence · Worker-independent playback",
|
||||
}] : []),
|
||||
]}
|
||||
conclusion={{
|
||||
proved: "На одной recorded timeline доступны городской EoMT, природный DDRNet, YOLOX detections и causal TGS; LAB автономна от Worker.",
|
||||
notProved: "Не доказаны совместный live-runtime EoMT+DDRNet, truth accuracy на fisheye, стабильные vegetation subtypes и безопасное управление ровером.",
|
||||
decision: "Использовать маски только для диагностики. Следующий qualification gate — motion-aware temporal vegetation fusion и отдельный совместный realtime load test; до него planner/actuation остаются OFF.",
|
||||
proved: "Обе официальные fine-64 модели воспроизводимо запускаются на Worker 006; DDRNet лучше по aggregate vegetation IoU. Truth-backed hard cases прямо показывают траву, кусты и стволы, а не случайные автомобили и здания.",
|
||||
notProved: "Не доказаны accuracy на нашем fisheye-домене, папоротник как отдельный материал, collision safety и physical-live поведение ровера. Видео позволяет увидеть temporal stability, но без truth не превращает её в метрику качества.",
|
||||
decision: "Смотреть полный prediction на видео и собирать конкретные temporal/domain failure cases. DDRNet остаётся diagnostic candidate; LiDAR/TGS fail-closed геометрию не ослаблять.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,6 @@ export type LaboratoryProfileId =
|
||||
| "rig-camera-local-surface-v1"
|
||||
| "rig-track-geometry-temporal-v1"
|
||||
| "rig-ravnoves-perception-gate-v1"
|
||||
| "rig-goose-vegetation-benchmark-v1"
|
||||
| "rig-pointpillars-transfer-v1"
|
||||
| "rig-right-yolox-lidar-range-v1"
|
||||
| "rig-nvidia-ready-stack-v1"
|
||||
@@ -64,19 +63,12 @@ interface KnownWorkDefinition {
|
||||
const rig = (rigLabel: string): string => rigLabel.trim() || "Сенсорный риг";
|
||||
|
||||
const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`>, KnownWorkDefinition>> = {
|
||||
"lab-v1-vegetation-benchmark": {
|
||||
profileId: "rig-goose-vegetation-benchmark-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} · GOOSE vegetation archive`,
|
||||
experimentId: "lab-v1-vegetation-benchmark-archive",
|
||||
experimentName: "DDRNet vs PPLiteSeg · truth-backed archival comparison",
|
||||
variantName: "M4.8 · GOOSE truth · архивный анализ моделей",
|
||||
},
|
||||
"lab-v1-vegetation-shadow": {
|
||||
profileId: "rig-ravnoves-perception-gate-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} · RAVNOVES00 rover perception gate`,
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} · GOOSE vegetation qualification`,
|
||||
experimentId: "lab-v1-vegetation-mission-policy",
|
||||
experimentName: "RAVNOVES00 · city + vegetation + TGS review",
|
||||
variantName: "LAB V1 · EoMT + DDRNet + YOLOX + TGS · commands OFF",
|
||||
experimentName: "DDRNet vs PPLiteSeg · truth-backed vegetation hard cases",
|
||||
variantName: "LAB V1 · готовые vegetation weights · GOOSE truth",
|
||||
},
|
||||
"m48-object-centric-quality": {
|
||||
profileId: "rig-dual-evidence-virtual-corridor-v1",
|
||||
|
||||
@@ -18,7 +18,6 @@ function mergeResults(
|
||||
next: AdvancedLaboratoryResults,
|
||||
): AdvancedLaboratoryResults {
|
||||
return {
|
||||
vegetationBenchmark: next.vegetationBenchmark ?? current.vegetationBenchmark,
|
||||
vegetationShadow: next.vegetationShadow ?? current.vegetationShadow,
|
||||
m47Graph: next.m47Graph ?? current.m47Graph,
|
||||
m48: next.m48 ?? current.m48,
|
||||
@@ -123,7 +122,6 @@ export function useAdvancedLaboratoryCatalog({
|
||||
const indexedResultId = index.find((item) => item.workId === selectedWorkId)?.resultId;
|
||||
if (
|
||||
[
|
||||
"lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow",
|
||||
"m47-reference-graph-shadow",
|
||||
"m48-object-centric-quality",
|
||||
|
||||
@@ -108,14 +108,7 @@ test("M4.9T5 viewer prefers autonomous chunks and keeps a sealed legacy fallback
|
||||
assert.doesNotMatch(source, /centersXyM\.map\(/);
|
||||
assert.match(source, /fetchE47SemanticSlamResult/);
|
||||
assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/);
|
||||
assert.match(source, /semanticLayers=\{semanticLayers\}/);
|
||||
assert.match(source, /ГОРОД · EoMT/);
|
||||
assert.match(source, /ПРИРОДА · DDRNet/);
|
||||
assert.match(source, /semanticOverride/);
|
||||
assert.doesNotMatch(source, /if \(semanticOverride\) return/);
|
||||
assert.match(visual, /label="Источник семантики"/);
|
||||
assert.match(visual, /availableSemanticLayers\.length > 1/);
|
||||
assert.doesNotMatch(visual, /classifiedSpatialLayer \|\| !showReferenceMediaLayers \? \[\]/);
|
||||
assert.match(source, /semantic=\{semantic \? \{/);
|
||||
assert.match(
|
||||
visual,
|
||||
/classifiedSpatialFrame\s*&&\s*classifiedSpatialFrame\.sampleAvailable !== false/,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { after, before, test } from "node:test";
|
||||
import { createServer } from "vite";
|
||||
|
||||
let server;
|
||||
let fetchVegetationBenchmarkResult;
|
||||
let fetchVegetationShadowResult;
|
||||
|
||||
before(async () => {
|
||||
@@ -14,7 +13,7 @@ before(async () => {
|
||||
logLevel: "silent",
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
({ fetchVegetationBenchmarkResult, fetchVegetationShadowResult } = await server.ssrLoadModule(
|
||||
({ fetchVegetationShadowResult } = await server.ssrLoadModule(
|
||||
"/src/core/laboratory/vegetationShadow.ts",
|
||||
));
|
||||
});
|
||||
@@ -24,7 +23,6 @@ after(async () => {
|
||||
});
|
||||
|
||||
const resultId = `lab-v1-vegetation-shadow-${"a".repeat(64)}`;
|
||||
const benchmarkResultId = `lab-v1-vegetation-benchmark-${"d".repeat(64)}`;
|
||||
|
||||
function candidate(candidateKey, vegetationIou) {
|
||||
return {
|
||||
@@ -106,50 +104,12 @@ 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: 10 }, (_, classId) => ({
|
||||
class_id: classId,
|
||||
label: `policy-${classId}`,
|
||||
color_rgb: [classId, classId, classId],
|
||||
disposition: classId === 0 ? "ambiguous" : classId === 9 ? "undefined" : "prediction",
|
||||
material_class: classId === 0 || classId === 9 ? null : "grass",
|
||||
evidence_state: classId === 0 || classId === 9 ? "UNOBSERVED" : "SUPPORTED_GROUND",
|
||||
})),
|
||||
},
|
||||
aggregate_prediction_pixels: Array(10).fill(0),
|
||||
mask_archive: {
|
||||
path: "video/coarse-material-policy-masks.zip",
|
||||
sha256: "8".repeat(64),
|
||||
byte_length: 2048,
|
||||
},
|
||||
valid_fov: {
|
||||
mask_path: "video/valid-fov-mask.png",
|
||||
mask_sha256: "7".repeat(64),
|
||||
outside_valid_fov_class_id: 9,
|
||||
},
|
||||
policy: {
|
||||
presets: {
|
||||
urban: { grass: "NO_GO" },
|
||||
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 {
|
||||
test("vegetation LAB keeps autonomous assets and fail-closed authority", async () => {
|
||||
let requestedUrl = "";
|
||||
const result = await fetchVegetationShadowResult(resultId, {
|
||||
fetcher: async (url) => {
|
||||
requestedUrl = String(url);
|
||||
return new Response(JSON.stringify({
|
||||
schema_version: "missioncore.lab-v1-vegetation-shadow/v1",
|
||||
result_id: resultId,
|
||||
created_at_utc: "2026-08-27T20:00:00Z",
|
||||
@@ -180,20 +140,9 @@ function labPayload(route = routeVideo()) {
|
||||
goose: Array.from({ length: 12 }, (_, index) => visualCase("goose", index)),
|
||||
ravnoves: [],
|
||||
},
|
||||
route_video: route,
|
||||
route_video: routeVideo(),
|
||||
access: "read-only",
|
||||
};
|
||||
}
|
||||
|
||||
test("vegetation LAB keeps autonomous assets and fail-closed authority", async () => {
|
||||
let requestedUrl = "";
|
||||
const result = await fetchVegetationShadowResult(resultId, {
|
||||
fetcher: async (url) => {
|
||||
requestedUrl = String(url);
|
||||
return new Response(JSON.stringify(labPayload()), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}), { status: 200, headers: { "Content-Type": "application/json" } });
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
@@ -205,8 +154,6 @@ test("vegetation LAB keeps autonomous assets and fail-closed authority", async (
|
||||
assert.equal(result.routeCases.length, 0);
|
||||
assert.equal(result.validationCases.length, 12);
|
||||
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.validationCases[0].focus.className, "high_grass");
|
||||
assert.match(result.validationCases[0].assets.ddrnet_error, /\/assets\/visual\/goose\//);
|
||||
@@ -218,64 +165,15 @@ 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, 10);
|
||||
assert.equal(result.routeVideo.taxonomy[0].evidenceState, "UNOBSERVED");
|
||||
assert.equal(result.routeVideo.policyPresets.urban.grass, "NO_GO");
|
||||
assert.equal(result.routeVideo.fusionMode, "synchronised-multilayer-review");
|
||||
});
|
||||
|
||||
test("vegetation GOOSE benchmark opens through its separate archival endpoint", async () => {
|
||||
let requestedUrl = "";
|
||||
const result = await fetchVegetationBenchmarkResult(benchmarkResultId, {
|
||||
fetcher: async (url) => {
|
||||
requestedUrl = String(url);
|
||||
return new Response(JSON.stringify({
|
||||
...labPayload(null),
|
||||
result_id: benchmarkResultId,
|
||||
}), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
requestedUrl,
|
||||
`/api/v1/laboratory/vegetation-benchmark/${benchmarkResultId}`,
|
||||
);
|
||||
assert.equal(result.routeVideo, null);
|
||||
assert.equal(result.validationCases.length, 12);
|
||||
});
|
||||
|
||||
test("vegetation realtime LAB and archival benchmark use separate admitted instruments", async () => {
|
||||
const [resultSource, benchmarkSource] = await Promise.all([
|
||||
readFile(
|
||||
test("vegetation LAB reuses the admitted M4.8 and M4.7 instruments", async () => {
|
||||
const resultSource = await readFile(
|
||||
new URL("../src/workspaces/laboratory/VegetationShadowResult.tsx", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
readFile(
|
||||
new URL("../src/workspaces/laboratory/VegetationBenchmarkResult.tsx", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
]);
|
||||
assert.doesNotMatch(resultSource, /M48MaskComparisonVisual/);
|
||||
);
|
||||
assert.match(resultSource, /M48MaskComparisonVisual/);
|
||||
assert.match(resultSource, /M4ReplayThreatVisual/);
|
||||
assert.match(resultSource, /M49TgsFullShadowEvidence/);
|
||||
assert.match(resultSource, /semanticOverride/);
|
||||
assert.match(resultSource, /EoMT CITY \/ DDRNet VEGETATION/);
|
||||
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 2);
|
||||
assert.match(resultSource, /linkedTgsResultId/);
|
||||
assert.match(benchmarkSource, /M48MaskComparisonVisual/);
|
||||
assert.doesNotMatch(benchmarkSource, /M49TgsFullShadowEvidence/);
|
||||
assert.equal(benchmarkSource.match(/<LaboratoryEvidence\b/g)?.length, 1);
|
||||
assert.match(resultSource, /showReferenceMediaLayers=\{false\}/);
|
||||
assert.doesNotMatch(resultSource, /VegetationRouteVisual|urban\/rural\/off-road presets/);
|
||||
await assert.rejects(
|
||||
access(new URL("../src/workspaces/laboratory/VegetationShadowVisual.tsx", import.meta.url)),
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"schema_version": "missioncore.laboratory-evidence-definition/v1",
|
||||
"work_id": "lab-v1-vegetation-benchmark",
|
||||
"evidence": {
|
||||
"runtime_relative_root": "lab-v1-vegetation-benchmark/results",
|
||||
"result_id_prefix": "lab-v1-vegetation-benchmark",
|
||||
"document_name": "result.json",
|
||||
"schema_version": "missioncore.lab-v1-vegetation-shadow/v1"
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,6 @@
|
||||
}
|
||||
],
|
||||
"legacy_work_ids": [
|
||||
"lab-v1-vegetation-benchmark",
|
||||
"m48r3-static-occupancy-shadow",
|
||||
"m47-reference-graph-shadow",
|
||||
"e31-source-binding",
|
||||
|
||||
@@ -282,17 +282,10 @@
|
||||
"lifecycle": "current",
|
||||
"visual_evidence": "available"
|
||||
},
|
||||
{
|
||||
"catalog_id": "lab-v1-vegetation-benchmark",
|
||||
"evidence_id": "lab-v1-vegetation-benchmark-a8944d6c2d1102d81da78bcb4963760c9288db0421d9f2686afcbdd14b610d3d",
|
||||
"signal": "progress",
|
||||
"lifecycle": "current",
|
||||
"visual_evidence": "available"
|
||||
},
|
||||
{
|
||||
"catalog_id": "lab-v1-vegetation-shadow",
|
||||
"evidence_id": "lab-v1-vegetation-shadow-394a5bca860e49a619a90c0f267259fd17e550a0abc4b51f821b1f482040c00e",
|
||||
"signal": "progress",
|
||||
"evidence_id": "lab-v1-vegetation-shadow-ad4d9fbbb21ff8a270b77f559b4e78dcdaf0455afd61afb5033009623984e554",
|
||||
"signal": "failed",
|
||||
"lifecycle": "current",
|
||||
"visual_evidence": "available"
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"schema_version": "missioncore.lab-v1-vegetation-integrated-shadow-profile/v3",
|
||||
"profile_id": "lab-v1-ravnoves00-ddrnet-m49-integrated-multirate-phased-shadow/v3",
|
||||
"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,
|
||||
"timeline_rate_hz": 12.0
|
||||
},
|
||||
"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",
|
||||
"timeline_rate_hz": 12.0,
|
||||
"inference_rate_hz": 6.0,
|
||||
"inference_stride": 2,
|
||||
"inference_phase_offset_ms": 40.0,
|
||||
"held_evidence_fail_closed": true,
|
||||
"semantic_output_persisted": false,
|
||||
"one_heavy_vegetation_candidate_at_a_time": true
|
||||
}
|
||||
},
|
||||
"acceptance": {
|
||||
"minimum_graph_world_state_fps": 11.209069,
|
||||
"minimum_vegetation_timeline_fps": 11.209069,
|
||||
"minimum_vegetation_inference_fps": 5.604534,
|
||||
"maximum_vegetation_inference_completion_p95_ms": 125.0,
|
||||
"maximum_semantic_evidence_source_age_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,
|
||||
"safety_layers_remain_12hz": true,
|
||||
"vegetation_gpu_phase_follows_safety_detector": true,
|
||||
"held_semantic_evidence_is_advisory_only": true,
|
||||
"vegetation_source_buffer_bounded": true,
|
||||
"vegetation_full_route_rgb_prefetch_allowed": false,
|
||||
"ppliteseg_concurrent_run_allowed": false,
|
||||
"camera_semantics_can_clear_rigid_geometry": false,
|
||||
"canonical_triton_mutation_allowed": false,
|
||||
"runtime_shared_source_frame_target": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"schema_version": "missioncore.lab-v1-vegetation-integrated-shadow-profile/v2",
|
||||
"profile_id": "lab-v1-ravnoves00-ddrnet-m49-integrated-multirate-shadow/v2",
|
||||
"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,
|
||||
"timeline_rate_hz": 12.0
|
||||
},
|
||||
"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",
|
||||
"timeline_rate_hz": 12.0,
|
||||
"inference_rate_hz": 6.0,
|
||||
"inference_stride": 2,
|
||||
"held_evidence_fail_closed": true,
|
||||
"semantic_output_persisted": false,
|
||||
"one_heavy_vegetation_candidate_at_a_time": true
|
||||
}
|
||||
},
|
||||
"acceptance": {
|
||||
"minimum_graph_world_state_fps": 11.209069,
|
||||
"minimum_vegetation_timeline_fps": 11.209069,
|
||||
"minimum_vegetation_inference_fps": 5.604534,
|
||||
"maximum_vegetation_inference_completion_p95_ms": 125.0,
|
||||
"maximum_semantic_evidence_source_age_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,
|
||||
"safety_layers_remain_12hz": true,
|
||||
"held_semantic_evidence_is_advisory_only": true,
|
||||
"vegetation_source_buffer_bounded": true,
|
||||
"vegetation_full_route_rgb_prefetch_allowed": false,
|
||||
"ppliteseg_concurrent_run_allowed": false,
|
||||
"camera_semantics_can_clear_rigid_geometry": false,
|
||||
"canonical_triton_mutation_allowed": false,
|
||||
"runtime_shared_source_frame_target": true,
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
{
|
||||
"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,
|
||||
"vegetation_source_buffer_bounded": true,
|
||||
"vegetation_full_route_rgb_prefetch_allowed": false,
|
||||
"ppliteseg_concurrent_run_allowed": false,
|
||||
"camera_semantics_can_clear_rigid_geometry": false,
|
||||
"canonical_triton_mutation_allowed": false,
|
||||
"runtime_shared_source_frame_target": true,
|
||||
"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,10 +12,6 @@ param(
|
||||
[string]$RunId,
|
||||
[ValidateRange(1.0, 120.0)]
|
||||
[double]$SourceRateHz = 12.0,
|
||||
[switch]$VegetationLoadGate,
|
||||
[string]$VegetationAssetRoot = (
|
||||
"D:\NDC_MISSIONCORE\datasets\vegetation-v1\observed-2026-08-27"
|
||||
),
|
||||
[string]$OutputRoot = (
|
||||
"D:\NDC_MISSIONCORE\runtime\results\m49-tgs-integrated-graph-shadow"
|
||||
)
|
||||
@@ -27,8 +23,6 @@ $TravelImageTag = "ndc/mission-core-m49-t3-travel:20260826"
|
||||
$TravelImageId = "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
|
||||
$ParityImageTag = "ndc-mission-core-m48t-upstream-parity:1.9.4-cu130"
|
||||
$ParityImageId = "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
|
||||
$VegetationImageTag = "ndc/mission-core-lab-v1-goose:sg3.2.0-cu117-v1"
|
||||
$VegetationImageId = "sha256:591cb382c099eeb05e7ec16e2371e0b2da54d2bb5c49ec0f4ac88dbf72b0f0cd"
|
||||
$RuntimeImage = (
|
||||
"nvcr.io/nvidia/tritonserver:26.06-py3@" +
|
||||
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
||||
@@ -104,20 +98,12 @@ function Wait-Healthy([string]$Name) {
|
||||
function Wait-SharedReady(
|
||||
[string]$GraphReady,
|
||||
[string]$TgsReady,
|
||||
[string]$VegetationReady,
|
||||
[string]$GraphName,
|
||||
[string]$TgsName,
|
||||
[string]$VegetationName
|
||||
[string]$TgsName
|
||||
) {
|
||||
$deadline = [DateTimeOffset]::UtcNow.AddMinutes(10)
|
||||
$requiredFiles = @($GraphReady, $TgsReady)
|
||||
$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) {
|
||||
while (-not ((Test-Path -LiteralPath $GraphReady) -and (Test-Path -LiteralPath $TgsReady))) {
|
||||
foreach ($name in @($GraphName, $TgsName)) {
|
||||
$container = Get-Container $name
|
||||
if (-not $container.State.Running) {
|
||||
& docker logs $name
|
||||
@@ -142,25 +128,15 @@ $runCandidate = Join-Path $output $RunId
|
||||
if (Test-Path -LiteralPath $runCandidate) { throw "M49 integrated output already exists" }
|
||||
$null = New-Item -ItemType Directory -Path $runCandidate
|
||||
$runOutput = Resolve-DDirectory $runCandidate "M49 integrated run output" $false
|
||||
foreach ($directory in @("bin", "control", "graph", "tgs", "vegetation")) {
|
||||
foreach ($directory in @("bin", "control", "graph", "tgs")) {
|
||||
$null = New-Item -ItemType Directory -Path (Join-Path $runOutput $directory)
|
||||
}
|
||||
|
||||
$releaseDocument = Get-Content -LiteralPath (Join-Path $payload "release.json") -Raw | ConvertFrom-Json
|
||||
$expectedReleaseSchema = if ($VegetationLoadGate) {
|
||||
"missioncore.lab-v1-vegetation-integrated-worker-release/v3"
|
||||
} else {
|
||||
"missioncore.m49-tgs-integrated-graph-worker-release/v1"
|
||||
}
|
||||
$expectedTransition = if ($VegetationLoadGate) {
|
||||
"lab-v1-vegetation-m49-integrated-multirate-phased-shadow/v3"
|
||||
} else {
|
||||
"m49-tgs-native-risk-integrated-shadow/v1"
|
||||
}
|
||||
if (
|
||||
$releaseDocument.schema_version -cne $expectedReleaseSchema -or
|
||||
$releaseDocument.schema_version -cne "missioncore.m49-tgs-integrated-graph-worker-release/v1" -or
|
||||
$releaseDocument.worker_id -cne "worker-006" -or
|
||||
$releaseDocument.transition -cne $expectedTransition
|
||||
$releaseDocument.transition -cne "m49-tgs-native-risk-integrated-shadow/v1"
|
||||
) { throw "M49 integrated release contract changed" }
|
||||
foreach ($property in $releaseDocument.files.PSObject.Properties) {
|
||||
$path = Join-Path $payload $property.Name
|
||||
@@ -170,9 +146,6 @@ foreach ($property in $releaseDocument.files.PSObject.Properties) {
|
||||
}
|
||||
$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
|
||||
$vegetationRunnerSha256 = if ($VegetationLoadGate) {
|
||||
[string]$releaseDocument.files."run_vegetation_integrated_load.py".sha256
|
||||
} else { "" }
|
||||
|
||||
$source = [ordered]@{
|
||||
CameraIndex = (
|
||||
@@ -205,27 +178,6 @@ foreach ($entry in $source.GetEnumerator()) {
|
||||
if ((Get-Sha256 $source.SourcePack) -cne [string]$releaseDocument.source_pack_sha256) {
|
||||
throw "RAVNOVES00 source pack digest changed"
|
||||
}
|
||||
$videoSha256 = [string]$releaseDocument.video_sha256
|
||||
if ((Get-Sha256 $source.Video) -cne $videoSha256) {
|
||||
throw "RAVNOVES00 video 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 (
|
||||
(Join-Path $payload "rf_detr_large_native_kb4_config.pbtxt")
|
||||
@@ -255,17 +207,12 @@ $pillow = Resolve-DDirectory (
|
||||
|
||||
Assert-Image $TravelImageTag $TravelImageId
|
||||
Assert-Image $ParityImageTag $ParityImageId
|
||||
if ($VegetationLoadGate) { Assert-Image $VegetationImageTag $VegetationImageId }
|
||||
& docker image inspect $RuntimeImage *> $null
|
||||
Assert-LastExitCode "pinned runtime image inspection"
|
||||
$os = Get-CimInstance Win32_OperatingSystem
|
||||
$freeMemoryGiB = [double]$os.FreePhysicalMemory / 1MB
|
||||
$requiredMemoryGiB = if ($VegetationLoadGate) { 32.0 } else { 24.0 }
|
||||
if ($freeMemoryGiB -lt $requiredMemoryGiB) {
|
||||
throw (
|
||||
"M49 integrated shadow requires {0:N0} GiB free memory; observed {1:N2} GiB" -f
|
||||
$requiredMemoryGiB, $freeMemoryGiB
|
||||
)
|
||||
if ($freeMemoryGiB -lt 24.0) {
|
||||
throw ("M49 integrated shadow requires 24 GiB free memory; observed {0:N2} GiB" -f $freeMemoryGiB)
|
||||
}
|
||||
$canonicalBefore = Get-Container "ndc-mission-core-triton"
|
||||
if (-not $canonicalBefore.State.Running -or $canonicalBefore.State.Health.Status -cne "healthy") {
|
||||
@@ -278,12 +225,9 @@ $compileName = "ndc-mission-core-m49-integrated-compile-$RunId"
|
||||
$tritonName = "ndc-mission-core-m49-integrated-triton-$RunId"
|
||||
$graphName = "ndc-mission-core-m49-integrated-graph-$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"
|
||||
$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)
|
||||
if ($VegetationLoadGate) { $containers += @($vegetationName, $vegetationEvidenceName) }
|
||||
foreach ($name in $containers) {
|
||||
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
|
||||
throw "M49 integrated container name already exists: $name"
|
||||
@@ -380,7 +324,7 @@ try {
|
||||
|
||||
& docker create --name $tgsName --network none --cpus 16 --memory 24g `
|
||||
--read-only --security-opt "no-new-privileges:true" --cap-drop ALL `
|
||||
--pids-limit 256 --tmpfs "/tmp:rw,noexec,nosuid,size=2g" `
|
||||
--pids-limit 256 --tmpfs "/tmp:rw,noexec,nosuid,size=1g" `
|
||||
-e ("M49_SOURCE_RATE_HZ={0}" -f $rate) `
|
||||
--entrypoint /bin/bash `
|
||||
--volume ($dockerRelease + ":/release:ro") `
|
||||
@@ -388,78 +332,24 @@ try {
|
||||
$TravelImageTag /release/run_tgs_integrated_shadow.sh *> $null
|
||||
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") `
|
||||
--volume ((Convert-ToDockerPath $source.Video) + ":/source/right.mp4: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 `
|
||||
--video /source/right.mp4 `
|
||||
--video-sha256 $videoSha256 `
|
||||
--runtime-video-cache /tmp/vegetation-right.mp4 `
|
||||
--source-rate-hz $rate `
|
||||
--inference-stride 2 `
|
||||
--inference-phase-offset-ms 40.0 `
|
||||
--minimum-effective-timeline-fps 11.209069 `
|
||||
--minimum-effective-inference-fps 5.604534 `
|
||||
--maximum-inference-completion-p95-ms 125.0 `
|
||||
--maximum-evidence-source-age-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
|
||||
Assert-LastExitCode "M49 integrated graph start"
|
||||
& docker start $tgsName *> $null
|
||||
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"
|
||||
$tgsReady = Join-Path $runOutput "control\tgs.ready"
|
||||
$vegetationReady = if ($VegetationLoadGate) {
|
||||
Join-Path $runOutput "control\vegetation.ready"
|
||||
} else { "" }
|
||||
Wait-SharedReady $graphReady $tgsReady $vegetationReady $graphName $tgsName $vegetationName
|
||||
Wait-SharedReady $graphReady $tgsReady $graphName $tgsName
|
||||
[DateTimeOffset]::UtcNow.ToString("o") | Set-Content -LiteralPath (
|
||||
Join-Path $runOutput "control\start.signal"
|
||||
) -Encoding utf8
|
||||
|
||||
$telemetryPath = Join-Path $runOutput "container-telemetry.jsonl"
|
||||
$m49TelemetryPath = if ($VegetationLoadGate) {
|
||||
Join-Path $runOutput "m49-container-telemetry.jsonl"
|
||||
} else { $telemetryPath }
|
||||
while ($true) {
|
||||
$graphState = Get-Container $graphName
|
||||
$tgsState = Get-Container $tgsName
|
||||
$vegetationState = if ($VegetationLoadGate) {
|
||||
Get-Container $vegetationName
|
||||
} else { $null }
|
||||
$running = @()
|
||||
if ($graphState.State.Running) { $running += $graphName }
|
||||
if ($tgsState.State.Running) { $running += $tgsName }
|
||||
if ($VegetationLoadGate -and $vegetationState.State.Running) {
|
||||
$running += $vegetationName
|
||||
}
|
||||
if ((Get-Container $tritonName).State.Running) { $running += $tritonName }
|
||||
if ($running.Count -gt 0) {
|
||||
$stats = @((& docker stats --no-stream --format "{{json .}}" @running))
|
||||
@@ -472,12 +362,10 @@ try {
|
||||
"tgs"
|
||||
} elseif ($value.Name -ceq $tritonName) {
|
||||
"triton"
|
||||
} elseif ($VegetationLoadGate -and $value.Name -ceq $vegetationName) {
|
||||
"vegetation"
|
||||
} else {
|
||||
throw "Unknown M49 telemetry container"
|
||||
}
|
||||
$telemetryRow = [ordered]@{
|
||||
[ordered]@{
|
||||
observed_utc = [DateTimeOffset]::UtcNow.ToString("o")
|
||||
role = $role
|
||||
name = [string]$value.Name
|
||||
@@ -485,46 +373,23 @@ try {
|
||||
memory_usage = [string]$value.MemUsage
|
||||
memory_percent = [string]$value.MemPerc
|
||||
pids = [string]$value.PIDs
|
||||
} | 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
|
||||
} | ConvertTo-Json -Compress | Out-File -LiteralPath $telemetryPath -Encoding utf8 -Append
|
||||
}
|
||||
}
|
||||
}
|
||||
$vegetationStopped = -not $VegetationLoadGate -or -not $vegetationState.State.Running
|
||||
if (
|
||||
-not $graphState.State.Running -and
|
||||
-not $tgsState.State.Running -and
|
||||
$vegetationStopped
|
||||
) { break }
|
||||
if (-not $graphState.State.Running -and -not $tgsState.State.Running) { break }
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
$graphExit = [int](Get-Container $graphName).State.ExitCode
|
||||
$tgsExit = [int](Get-Container $tgsName).State.ExitCode
|
||||
$vegetationExit = if ($VegetationLoadGate) {
|
||||
[int](Get-Container $vegetationName).State.ExitCode
|
||||
} else { 0 }
|
||||
$previousErrorAction = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
$graphLogs = & docker logs $graphName 2>&1
|
||||
$tgsLogs = & docker logs $tgsName 2>&1
|
||||
$vegetationLogs = if ($VegetationLoadGate) {
|
||||
& docker logs $vegetationName 2>&1
|
||||
} else { @() }
|
||||
$ErrorActionPreference = $previousErrorAction
|
||||
$graphLogs | Set-Content -LiteralPath (Join-Path $runOutput "graph.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 ($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 `
|
||||
--entrypoint python3 `
|
||||
@@ -536,12 +401,6 @@ try {
|
||||
--output-root /shared/tgs/evidence
|
||||
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 `
|
||||
--entrypoint python3 `
|
||||
--volume ($dockerRelease + ":/release:ro") `
|
||||
@@ -552,28 +411,10 @@ try {
|
||||
--graph-frames /shared/graph/frames.jsonl `
|
||||
--tgs-result /shared/tgs/evidence/result.json `
|
||||
--tgs-timing /shared/tgs/tgs-full-timing.tsv `
|
||||
--telemetry $dockerM49TelemetryPath `
|
||||
--output $m49ResultPath `
|
||||
--release-sha256 $ExpectedArtifactSha256
|
||||
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-multirate-phased-shadow-v3.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"
|
||||
}
|
||||
Assert-LastExitCode "M49 integrated evidence gate"
|
||||
} finally {
|
||||
foreach ($name in $containers) { Remove-ExactContainer $name }
|
||||
$canonicalAfter = Get-Container "ndc-mission-core-triton"
|
||||
@@ -591,11 +432,7 @@ if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) {
|
||||
}
|
||||
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
|
||||
$summary = [ordered]@{
|
||||
schema_version = if ($VegetationLoadGate) {
|
||||
"missioncore.lab-v1-vegetation-integrated-worker-summary/v3"
|
||||
} else {
|
||||
"missioncore.m49-tgs-integrated-graph-worker-summary/v1"
|
||||
}
|
||||
schema_version = "missioncore.m49-tgs-integrated-graph-worker-summary/v1"
|
||||
worker_id = "worker-006"
|
||||
run_id = $RunId
|
||||
code_revision = [string]$releaseDocument.code_revision
|
||||
@@ -606,7 +443,6 @@ $summary = [ordered]@{
|
||||
free_memory_gib_before = [math]::Round($freeMemoryGiB, 6)
|
||||
result_id = [string]$result.result_id
|
||||
result_status = [string]$result.status
|
||||
vegetation_load_gate = [bool]$VegetationLoadGate
|
||||
canonical_triton_id = $canonicalId
|
||||
canonical_triton_health = "healthy"
|
||||
gauss_or_playcanvas_action = "none"
|
||||
|
||||
-398
@@ -1,398 +0,0 @@
|
||||
#!/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 shutil
|
||||
import statistics
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
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/v3"
|
||||
FRAME_SCHEMA = "missioncore.lab-v1-vegetation-integrated-frame/v2"
|
||||
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("--video", type=Path, required=True)
|
||||
parser.add_argument("--video-sha256", required=True)
|
||||
parser.add_argument("--runtime-video-cache", type=Path, required=True)
|
||||
parser.add_argument("--source-rate-hz", type=float, required=True)
|
||||
parser.add_argument("--inference-stride", type=int, required=True)
|
||||
parser.add_argument("--inference-phase-offset-ms", type=float, required=True)
|
||||
parser.add_argument("--minimum-effective-timeline-fps", type=float, required=True)
|
||||
parser.add_argument("--minimum-effective-inference-fps", type=float, required=True)
|
||||
parser.add_argument("--maximum-inference-completion-p95-ms", type=float, required=True)
|
||||
parser.add_argument("--maximum-evidence-source-age-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 open_video(path: Path) -> cv2.VideoCapture:
|
||||
if path.is_symlink() or not path.is_file():
|
||||
raise IntegratedLoadError("RAVNOVES video is unavailable")
|
||||
capture = cv2.VideoCapture(str(path))
|
||||
if not capture.isOpened():
|
||||
raise IntegratedLoadError("RAVNOVES video decoder did not open")
|
||||
return capture
|
||||
|
||||
|
||||
def decode_source(capture: cv2.VideoCapture, expected_size: tuple[int, int]) -> Image.Image:
|
||||
available, bgr = capture.read()
|
||||
if not available or bgr is None:
|
||||
raise IntegratedLoadError("RAVNOVES video ended before the frozen frame count")
|
||||
if (bgr.shape[1], bgr.shape[0]) != expected_size:
|
||||
raise IntegratedLoadError("RAVNOVES decoded frame dimensions changed")
|
||||
return Image.fromarray(cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB), mode="RGB")
|
||||
|
||||
|
||||
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 buffer_compressed_video(source: Path, target: Path, expected_sha256: str) -> dict[str, Any]:
|
||||
if source.is_symlink() or not source.is_file():
|
||||
raise IntegratedLoadError("RAVNOVES video is unavailable")
|
||||
if target.exists() or target.is_symlink():
|
||||
raise IntegratedLoadError("RAVNOVES runtime video cache already exists")
|
||||
if not target.parent.is_dir():
|
||||
raise IntegratedLoadError("RAVNOVES runtime video cache parent is unavailable")
|
||||
started = time.monotonic_ns()
|
||||
shutil.copyfile(source, target)
|
||||
copied_bytes = target.stat().st_size
|
||||
if copied_bytes != source.stat().st_size:
|
||||
raise IntegratedLoadError("RAVNOVES runtime video cache size changed")
|
||||
copied_sha256 = sha256(target)
|
||||
if copied_sha256 != expected_sha256:
|
||||
raise IntegratedLoadError("RAVNOVES runtime video cache digest changed")
|
||||
return {
|
||||
"bytes": copied_bytes,
|
||||
"sha256": copied_sha256,
|
||||
"seconds": round((time.monotonic_ns() - started) / 1_000_000_000.0, 6),
|
||||
}
|
||||
|
||||
|
||||
def run() -> int:
|
||||
args = parse_args()
|
||||
if not torch.cuda.is_available():
|
||||
raise IntegratedLoadError("CUDA is required for Worker 006 qualification")
|
||||
positive_finite_values = (
|
||||
args.source_rate_hz,
|
||||
args.minimum_effective_timeline_fps,
|
||||
args.minimum_effective_inference_fps,
|
||||
args.maximum_inference_completion_p95_ms,
|
||||
args.maximum_evidence_source_age_ms,
|
||||
args.shared_start_timeout_seconds,
|
||||
)
|
||||
if args.inference_stride <= 0 or any(
|
||||
not math.isfinite(value) or value <= 0 for value in positive_finite_values
|
||||
):
|
||||
raise IntegratedLoadError("integrated-load thresholds must be positive and finite")
|
||||
if (
|
||||
not math.isfinite(args.inference_phase_offset_ms)
|
||||
or args.inference_phase_offset_ms < 0
|
||||
or args.inference_phase_offset_ms >= 1000.0 / args.source_rate_hz
|
||||
):
|
||||
raise IntegratedLoadError("inference phase offset must fit inside one source interval")
|
||||
validate_sha256(args.release_sha256, "release")
|
||||
validate_sha256(args.video_sha256, "video")
|
||||
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"])
|
||||
expected_size = (
|
||||
config["ravnoves"]["expected_width"],
|
||||
config["ravnoves"]["expected_height"],
|
||||
)
|
||||
compressed_video_buffer = buffer_compressed_video(
|
||||
args.video, args.runtime_video_cache, args.video_sha256
|
||||
)
|
||||
warmup_capture = open_video(args.runtime_video_cache)
|
||||
warmup_source = decode_source(warmup_capture, expected_size)
|
||||
warmup_capture.release()
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
model, model_name, architecture_failures = load_model("ddrnet", args.checkpoint)
|
||||
warmup_tensor, _ = preprocess(warmup_source)
|
||||
warmup_latencies_ms = [infer(model, warmup_tensor)[1] for _ in range(3)]
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
source_capture = open_video(args.runtime_video_cache)
|
||||
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] = []
|
||||
inference_completion_ages_ms: list[float] = []
|
||||
evidence_source_ages_ms: list[float] = []
|
||||
stage_latencies_ms: list[float] = []
|
||||
inference_latencies_ms: list[float] = []
|
||||
late_deadline_count = 0
|
||||
inference_frame_count = 0
|
||||
last_inference_sequence = -1
|
||||
args.frame_ledger.parent.mkdir(parents=True, exist_ok=True)
|
||||
with args.frame_ledger.open("x", encoding="utf-8") as ledger:
|
||||
for sequence in range(FRAME_COUNT):
|
||||
scheduled_ns = start_ns + round(sequence * interval_ns)
|
||||
inference_executed = sequence % args.inference_stride == 0
|
||||
execution_target_ns = scheduled_ns
|
||||
if inference_executed:
|
||||
execution_target_ns += round(args.inference_phase_offset_ms * 1_000_000.0)
|
||||
remaining_ns = execution_target_ns - time.monotonic_ns()
|
||||
if remaining_ns > 0:
|
||||
time.sleep(remaining_ns / 1_000_000_000.0)
|
||||
admitted_ns = time.monotonic_ns()
|
||||
source = decode_source(source_capture, expected_size)
|
||||
inference_ms: float | None = None
|
||||
if inference_executed:
|
||||
tensor, _ = preprocess(source)
|
||||
_, inference_ms = infer(model, tensor)
|
||||
last_inference_sequence = sequence
|
||||
inference_frame_count += 1
|
||||
if last_inference_sequence < 0:
|
||||
raise IntegratedLoadError("semantic evidence is unavailable for the timeline")
|
||||
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
|
||||
semantic_source_scheduled_ns = start_ns + round(
|
||||
last_inference_sequence * interval_ns
|
||||
)
|
||||
evidence_source_age_ms = (
|
||||
completed_ns - semantic_source_scheduled_ns
|
||||
) / 1_000_000.0
|
||||
completion_ages_ms.append(completion_age_ms)
|
||||
evidence_source_ages_ms.append(evidence_source_age_ms)
|
||||
stage_latencies_ms.append(stage_ms)
|
||||
if inference_ms is not None:
|
||||
inference_latencies_ms.append(inference_ms)
|
||||
inference_completion_ages_ms.append(completion_age_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": f"frame-{sequence + 1:06d}",
|
||||
"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_executed": inference_executed,
|
||||
"inference_phase_offset_ms": args.inference_phase_offset_ms
|
||||
if inference_executed
|
||||
else 0.0,
|
||||
"inference_ms": round(inference_ms, 6) if inference_ms is not None else None,
|
||||
"semantic_source_sequence": last_inference_sequence,
|
||||
"semantic_evidence_source_age_ms": round(evidence_source_age_ms, 6),
|
||||
}
|
||||
ledger.write(json.dumps(row, sort_keys=True, separators=(",", ":")) + "\n")
|
||||
if sequence % 64 == 0:
|
||||
ledger.flush()
|
||||
extra_available, _ = source_capture.read()
|
||||
source_capture.release()
|
||||
if extra_available:
|
||||
raise IntegratedLoadError("RAVNOVES video contains frames beyond the frozen timeline")
|
||||
|
||||
completed_ns = time.monotonic_ns()
|
||||
wall_seconds = (completed_ns - start_ns) / 1_000_000_000.0
|
||||
effective_timeline_fps = FRAME_COUNT / wall_seconds
|
||||
effective_inference_fps = inference_frame_count / wall_seconds
|
||||
completion = distribution(completion_ages_ms)
|
||||
inference_completion = distribution(inference_completion_ages_ms)
|
||||
evidence_source_age = distribution(evidence_source_ages_ms)
|
||||
expected_inference_frames = (FRAME_COUNT + args.inference_stride - 1) // args.inference_stride
|
||||
checks = {
|
||||
"all_frames_accounted": len(completion_ages_ms) == FRAME_COUNT,
|
||||
"exact_multirate_schedule": inference_frame_count == expected_inference_frames,
|
||||
"inference_phase_offset_preserved": args.inference_phase_offset_ms
|
||||
< 1000.0 / args.source_rate_hz,
|
||||
"minimum_effective_timeline_fps": effective_timeline_fps
|
||||
>= args.minimum_effective_timeline_fps,
|
||||
"minimum_effective_inference_fps": effective_inference_fps
|
||||
>= args.minimum_effective_inference_fps,
|
||||
"maximum_inference_completion_p95_ms": inference_completion["p95"]
|
||||
<= args.maximum_inference_completion_p95_ms,
|
||||
"maximum_evidence_source_age_ms": evidence_source_age["maximum"]
|
||||
<= args.maximum_evidence_source_age_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-multirate-integrated-shadow/v2",
|
||||
"started_utc_ns": started_utc_ns,
|
||||
"wall_seconds": round(wall_seconds, 6),
|
||||
"effective_fps": round(effective_timeline_fps, 6),
|
||||
"effective_timeline_fps": round(effective_timeline_fps, 6),
|
||||
"effective_inference_fps": round(effective_inference_fps, 6),
|
||||
"inference_stride": args.inference_stride,
|
||||
"inference_phase_offset_ms": args.inference_phase_offset_ms,
|
||||
"inference_frame_count": inference_frame_count,
|
||||
"held_evidence_frame_count": FRAME_COUNT - inference_frame_count,
|
||||
"frame_count": FRAME_COUNT,
|
||||
"capacity_drop_count": 0,
|
||||
"deadline_miss_count": late_deadline_count,
|
||||
"source_decode": {
|
||||
"mode": "bounded-compressed-scene-buffer/v1",
|
||||
"compressed_scene_prefetch": True,
|
||||
"compressed_scene_buffer": compressed_video_buffer,
|
||||
"full_route_rgb_prefetch": False,
|
||||
"candidate_local_decoder": True,
|
||||
"runtime_target": "shared-source-frame",
|
||||
},
|
||||
"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,
|
||||
"inference_completion_age_ms": inference_completion,
|
||||
"semantic_evidence_source_age_ms": evidence_source_age,
|
||||
"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_timeline_fps": args.minimum_effective_timeline_fps,
|
||||
"minimum_effective_inference_fps": args.minimum_effective_inference_fps,
|
||||
"inference_phase_offset_ms": args.inference_phase_offset_ms,
|
||||
"maximum_inference_completion_p95_ms": (
|
||||
args.maximum_inference_completion_p95_ms
|
||||
),
|
||||
"maximum_evidence_source_age_ms": args.maximum_evidence_source_age_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())
|
||||
-461
@@ -1,461 +0,0 @@
|
||||
#!/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/v3"
|
||||
M49_SCHEMA = "missioncore.m49-tgs-integrated-graph-shadow-result/v1"
|
||||
VEGETATION_SCHEMA = "missioncore.lab-v1-vegetation-integrated-load/v3"
|
||||
RESULT_SCHEMA = "missioncore.lab-v1-vegetation-integrated-shadow-result/v3"
|
||||
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_frame_metrics(
|
||||
path: Path,
|
||||
*,
|
||||
inference_stride: int,
|
||||
inference_phase_offset_ms: float,
|
||||
source_rate_hz: float,
|
||||
) -> dict[str, object]:
|
||||
completion_ages: list[float] = []
|
||||
evidence_source_ages: list[float] = []
|
||||
inference_count = 0
|
||||
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/v2":
|
||||
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")
|
||||
inference_executed = row.get("inference_executed")
|
||||
expected_inference = expected % inference_stride == 0
|
||||
if inference_executed is not expected_inference:
|
||||
raise VegetationIntegratedError("vegetation inference schedule changed")
|
||||
expected_phase = inference_phase_offset_ms if expected_inference else 0.0
|
||||
phase = row.get("inference_phase_offset_ms")
|
||||
if not isinstance(phase, (int, float)) or float(phase) != expected_phase:
|
||||
raise VegetationIntegratedError("vegetation inference phase changed")
|
||||
if expected_inference and float(age) + 0.001 < expected_phase:
|
||||
raise VegetationIntegratedError("vegetation inference phase attribution changed")
|
||||
expected_source = expected - (expected % inference_stride)
|
||||
if row.get("semantic_source_sequence") != expected_source:
|
||||
raise VegetationIntegratedError("vegetation evidence source changed")
|
||||
evidence_age = row.get("semantic_evidence_source_age_ms")
|
||||
expected_evidence_age = float(age) + (
|
||||
(expected - expected_source) * 1000.0 / source_rate_hz
|
||||
)
|
||||
if (
|
||||
not isinstance(evidence_age, (int, float))
|
||||
or not math.isfinite(evidence_age)
|
||||
or evidence_age < 0
|
||||
or abs(float(evidence_age) - expected_evidence_age) > 0.001
|
||||
):
|
||||
raise VegetationIntegratedError("vegetation evidence source age changed")
|
||||
completion_ages.append(float(age))
|
||||
evidence_source_ages.append(float(evidence_age))
|
||||
inference_count += int(expected_inference)
|
||||
if len(completion_ages) != FRAME_COUNT:
|
||||
raise VegetationIntegratedError("vegetation frame ledger is incomplete")
|
||||
return {
|
||||
"completion_ages": completion_ages,
|
||||
"evidence_source_ages": evidence_source_ages,
|
||||
"inference_count": inference_count,
|
||||
"held_count": FRAME_COUNT - inference_count,
|
||||
}
|
||||
|
||||
|
||||
_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")
|
||||
|
||||
source_rate_hz = float(profile["source"]["requested_source_rate_hz"])
|
||||
inference_stride = int(profile["stages"]["vegetation"]["inference_stride"])
|
||||
inference_phase_offset_ms = float(
|
||||
profile["stages"]["vegetation"]["inference_phase_offset_ms"]
|
||||
)
|
||||
if (
|
||||
not math.isfinite(source_rate_hz)
|
||||
or source_rate_hz <= 0
|
||||
or inference_stride <= 0
|
||||
or not math.isfinite(inference_phase_offset_ms)
|
||||
or inference_phase_offset_ms < 0
|
||||
or inference_phase_offset_ms >= 1000.0 / source_rate_hz
|
||||
):
|
||||
raise VegetationIntegratedError("vegetation multirate schedule is invalid")
|
||||
graph_ages = graph_completion_ages(graph_frames_path)
|
||||
tgs_ages = tgs_completion_ages(tgs_timing_path)
|
||||
vegetation_frames = vegetation_frame_metrics(
|
||||
vegetation_frames_path,
|
||||
inference_stride=inference_stride,
|
||||
inference_phase_offset_ms=inference_phase_offset_ms,
|
||||
source_rate_hz=source_rate_hz,
|
||||
)
|
||||
vegetation_ages = vegetation_frames["completion_ages"]
|
||||
assert isinstance(vegetation_ages, list)
|
||||
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"]
|
||||
),
|
||||
"vegetation_load_gate_passed": vegetation.get("integrated_load_gate_passed") is True,
|
||||
"vegetation_multirate_schedule_frozen": (
|
||||
vegetation_execution.get("inference_stride") == inference_stride
|
||||
and vegetation_execution.get("inference_phase_offset_ms")
|
||||
== inference_phase_offset_ms
|
||||
and vegetation_execution.get("inference_frame_count")
|
||||
== vegetation_frames["inference_count"]
|
||||
and vegetation_execution.get("held_evidence_frame_count")
|
||||
== vegetation_frames["held_count"]
|
||||
),
|
||||
"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_timeline_fps": float(
|
||||
vegetation_execution.get("effective_timeline_fps", 0.0)
|
||||
)
|
||||
>= float(acceptance["minimum_vegetation_timeline_fps"]),
|
||||
"minimum_vegetation_inference_fps": float(
|
||||
vegetation_execution.get("effective_inference_fps", 0.0)
|
||||
)
|
||||
>= float(acceptance["minimum_vegetation_inference_fps"]),
|
||||
"maximum_vegetation_inference_completion_p95_ms": float(
|
||||
vegetation_timing.get("inference_completion_age_ms", {}).get(
|
||||
"p95", math.inf
|
||||
)
|
||||
)
|
||||
<= float(acceptance["maximum_vegetation_inference_completion_p95_ms"]),
|
||||
"maximum_semantic_evidence_source_age_ms": max(
|
||||
vegetation_frames["evidence_source_ages"]
|
||||
)
|
||||
<= float(acceptance["maximum_semantic_evidence_source_age_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_timeline_fps": vegetation_execution.get(
|
||||
"effective_timeline_fps"
|
||||
),
|
||||
"effective_inference_fps": vegetation_execution.get(
|
||||
"effective_inference_fps"
|
||||
),
|
||||
"completion_age_ms": vegetation_timing.get("completion_age_ms"),
|
||||
"inference_completion_age_ms": vegetation_timing.get(
|
||||
"inference_completion_age_ms"
|
||||
),
|
||||
"semantic_evidence_source_age_ms": distribution(
|
||||
vegetation_frames["evidence_source_ages"]
|
||||
),
|
||||
"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"),
|
||||
"vegetation_inference_frames": vegetation_frames["inference_count"],
|
||||
"vegetation_held_evidence_frames": vegetation_frames["held_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())
|
||||
@@ -5,9 +5,6 @@ readonly BINARY=/shared/bin/run_tgs_full_shadow
|
||||
readonly INPUT_ROOT=/shared/tgs/inputs
|
||||
readonly OUTPUT_ROOT=/shared/tgs/outputs/causal_rolling_1s
|
||||
readonly TIMING_PATH=/shared/tgs/tgs-full-timing.tsv
|
||||
readonly RUNTIME_ROOT=/tmp/m49-tgs-runtime
|
||||
readonly RUNTIME_OUTPUT_ROOT=${RUNTIME_ROOT}/outputs
|
||||
readonly RUNTIME_TIMING_PATH=${RUNTIME_ROOT}/tgs-full-timing.tsv
|
||||
readonly READY_FILE=/shared/control/tgs.ready
|
||||
readonly START_FILE=/shared/control/start.signal
|
||||
readonly SOURCE_RATE_HZ=${M49_SOURCE_RATE_HZ:-12.0}
|
||||
@@ -18,20 +15,12 @@ test -f "${INPUT_ROOT}/schedule.tsv"
|
||||
test ! -e /shared/tgs/outputs
|
||||
test ! -e "${TIMING_PATH}"
|
||||
test ! -e "${READY_FILE}"
|
||||
test ! -e "${RUNTIME_ROOT}"
|
||||
mkdir -p "${RUNTIME_OUTPUT_ROOT}"
|
||||
/usr/bin/time -v "${BINARY}" \
|
||||
mkdir -p "${OUTPUT_ROOT}"
|
||||
exec /usr/bin/time -v "${BINARY}" \
|
||||
"${INPUT_ROOT}/profiles/causal_rolling_1s" \
|
||||
"${INPUT_ROOT}/schedule.tsv" \
|
||||
"${RUNTIME_OUTPUT_ROOT}" \
|
||||
"${RUNTIME_TIMING_PATH}" \
|
||||
"${OUTPUT_ROOT}" \
|
||||
"${TIMING_PATH}" \
|
||||
"${SOURCE_RATE_HZ}" \
|
||||
"${READY_FILE}" \
|
||||
"${START_FILE}"
|
||||
test -f "${RUNTIME_TIMING_PATH}"
|
||||
mkdir -p "${OUTPUT_ROOT}"
|
||||
copy_started=$(date +%s%N)
|
||||
cp -R "${RUNTIME_OUTPUT_ROOT}/." "${OUTPUT_ROOT}/"
|
||||
cp "${RUNTIME_TIMING_PATH}" "${TIMING_PATH}"
|
||||
copy_completed=$(date +%s%N)
|
||||
echo "[TGS-FULL] evidence_copy_ms=$(((copy_completed - copy_started) / 1000000))"
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
#!/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-multirate-phased-shadow-v3.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/v3",
|
||||
"patch_id": patch_id,
|
||||
"transition": "lab-v1-vegetation-m49-integrated-multirate-phased-shadow/v3",
|
||||
"code_revision": selected_revision,
|
||||
"worker_id": "worker-006",
|
||||
"source_pack_sha256": (
|
||||
"0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944"
|
||||
),
|
||||
"video_sha256": (
|
||||
"cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8"
|
||||
),
|
||||
"expected_frames": 4489,
|
||||
"requested_source_rate_hz": 12.0,
|
||||
"semantic_inference_rate_hz": 6.0,
|
||||
"semantic_inference_stride": 2,
|
||||
"semantic_inference_phase_offset_ms": 40.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())
|
||||
@@ -1,139 +0,0 @@
|
||||
"""Seal a benchmark-only vegetation result into its archival LAB namespace."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.laboratory.evidence_registry import LaboratoryEvidenceDefinition
|
||||
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
|
||||
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA
|
||||
|
||||
_SOURCE_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="lab-v1-vegetation-shadow",
|
||||
runtime_relative_root=PurePosixPath("lab-v1-vegetation/results"),
|
||||
result_id_prefix="lab-v1-vegetation-shadow",
|
||||
document_name="result.json",
|
||||
result_schema_version=LAB_SCHEMA,
|
||||
)
|
||||
_ARCHIVE_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="lab-v1-vegetation-benchmark",
|
||||
runtime_relative_root=PurePosixPath("lab-v1-vegetation-benchmark/results"),
|
||||
result_id_prefix="lab-v1-vegetation-benchmark",
|
||||
document_name="result.json",
|
||||
result_schema_version=LAB_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
class VegetationBenchmarkArchiveError(ValueError):
|
||||
"""The source result is not a valid benchmark-only immutable result."""
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True,
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _object(value: object, label: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise VegetationBenchmarkArchiveError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def seal_vegetation_benchmark_archive(
|
||||
*,
|
||||
source_result_root: Path,
|
||||
output_root: Path,
|
||||
) -> Path:
|
||||
source = source_result_root.resolve(strict=True)
|
||||
verify_laboratory_evidence_result(_SOURCE_DEFINITION, source)
|
||||
manifest = _object(
|
||||
json.loads((source / "result.json").read_text("utf-8")),
|
||||
"source result",
|
||||
)
|
||||
if manifest.get("route_video") is not None:
|
||||
raise VegetationBenchmarkArchiveError("benchmark archive source contains route video")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list):
|
||||
raise VegetationBenchmarkArchiveError("source artifacts are invalid")
|
||||
|
||||
identity = copy.deepcopy(_object(manifest.get("identity"), "source identity"))
|
||||
identity.update(
|
||||
{
|
||||
"lab_id": "lab-v1-vegetation-benchmark-archive",
|
||||
"archived_from_result_id": source.name,
|
||||
}
|
||||
)
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"lab-v1-vegetation-benchmark-{identity_sha256}"
|
||||
|
||||
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
destination = output_root / result_id
|
||||
if destination.exists():
|
||||
verify_laboratory_evidence_result(_ARCHIVE_DEFINITION, destination)
|
||||
return destination
|
||||
|
||||
temporary = Path(tempfile.mkdtemp(prefix=".vegetation-benchmark-", dir=output_root))
|
||||
try:
|
||||
for raw in artifacts:
|
||||
descriptor = _object(raw, "artifact descriptor")
|
||||
relative_text = descriptor.get("path")
|
||||
if not isinstance(relative_text, str):
|
||||
raise VegetationBenchmarkArchiveError("artifact path is invalid")
|
||||
relative = PurePosixPath(relative_text)
|
||||
if relative.is_absolute() or any(part in {"", ".", ".."} for part in relative.parts):
|
||||
raise VegetationBenchmarkArchiveError("artifact path is unsafe")
|
||||
source_path = source.joinpath(*relative.parts)
|
||||
destination_path = temporary.joinpath(*relative.parts)
|
||||
destination_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
shutil.copyfile(source_path, destination_path)
|
||||
|
||||
archived = copy.deepcopy(manifest)
|
||||
archived.update(
|
||||
{
|
||||
"result_id": result_id,
|
||||
"identity": identity,
|
||||
"identity_sha256": identity_sha256,
|
||||
"archived_from_result_id": source.name,
|
||||
}
|
||||
)
|
||||
(temporary / "result.json").write_bytes(_canonical_json(archived) + b"\n")
|
||||
temporary.rename(destination)
|
||||
verify_laboratory_evidence_result(_ARCHIVE_DEFINITION, destination)
|
||||
return destination
|
||||
except Exception:
|
||||
shutil.rmtree(temporary, ignore_errors=True)
|
||||
raise
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source-result-root", type=Path, required=True)
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
print(
|
||||
seal_vegetation_benchmark_archive(
|
||||
source_result_root=args.source_result_root,
|
||||
output_root=args.output_root,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"VegetationBenchmarkArchiveError",
|
||||
"seal_vegetation_benchmark_archive",
|
||||
]
|
||||
@@ -1,302 +0,0 @@
|
||||
"""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,
|
||||
valid_fov_mask_path: 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")
|
||||
valid_fov_source = valid_fov_mask_path.resolve(strict=True)
|
||||
|
||||
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
temporary = Path(tempfile.mkdtemp(prefix=".lab-v1-policy-", dir=output_root))
|
||||
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"
|
||||
valid_fov_destination = temporary / "video" / "valid-fov-mask.png"
|
||||
valid_fov_destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
shutil.copyfile(valid_fov_source, valid_fov_destination)
|
||||
valid_fov_proof = {
|
||||
"role": "route-camera-valid-fov-mask",
|
||||
"path": "video/valid-fov-mask.png",
|
||||
"byte_length": valid_fov_destination.stat().st_size,
|
||||
"sha256": sha256_path(valid_fov_destination),
|
||||
"media_type": "image/png",
|
||||
}
|
||||
artifacts.append(valid_fov_proof)
|
||||
policy_counts = build_policy_mask_archive(
|
||||
source_archive=raw_archive_path,
|
||||
destination_archive=policy_archive,
|
||||
fine_taxonomy=fine_taxonomy,
|
||||
provider_label_map=provider_map,
|
||||
valid_fov_mask=valid_fov_destination,
|
||||
)
|
||||
policy_archive_proof = {
|
||||
"role": "route-coarse-material-mask-archive",
|
||||
"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,
|
||||
"valid_fov": {
|
||||
"mask_path": valid_fov_proof["path"],
|
||||
"mask_sha256": valid_fov_proof["sha256"],
|
||||
"outside_valid_fov_class_id": 9,
|
||||
},
|
||||
"policy": {
|
||||
"profile_id": mission_policy["profile_id"],
|
||||
"profile_sha256": sha256_path(mission_policy_path),
|
||||
"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",
|
||||
"camera_valid_fov_filter": "sealed exact KB4 valid-FOV mask",
|
||||
"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."
|
||||
),
|
||||
"Pixels outside the exact KB4 valid FOV are transparent UNOBSERVED evidence.",
|
||||
(
|
||||
"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("--valid-fov-mask-path", type=Path, required=True)
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
print(seal_vegetation_policy_review(**vars(args)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
__all__ = ["VegetationPolicyReviewError", "seal_vegetation_policy_review"]
|
||||
@@ -1,223 +0,0 @@
|
||||
"""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",
|
||||
},
|
||||
{
|
||||
"class_id": 9,
|
||||
"label": "OUTSIDE VALID FOV · NO SENSOR EVIDENCE",
|
||||
"color_rgb": [0, 0, 0],
|
||||
"disposition": "undefined",
|
||||
"material_class": None,
|
||||
"evidence_state": "UNOBSERVED",
|
||||
},
|
||||
)
|
||||
|
||||
_MATERIAL_TO_CLASS: Final = {
|
||||
"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],
|
||||
valid_fov_mask: Path,
|
||||
) -> list[int]:
|
||||
"""Map every fine mask to coarse evidence; safety vetoes remain separate layers."""
|
||||
|
||||
lut = fine_to_policy_lut(fine_taxonomy, provider_label_map)
|
||||
try:
|
||||
with Image.open(valid_fov_mask) as image:
|
||||
valid_fov = np.asarray(image.convert("L"), dtype=np.uint8) > 0
|
||||
except OSError as exc:
|
||||
raise VegetationPolicyVideoError("valid-FOV mask is unreadable") from exc
|
||||
if valid_fov.shape != (HEIGHT, WIDTH) or not np.any(valid_fov) or np.all(valid_fov):
|
||||
raise VegetationPolicyVideoError("valid-FOV mask geometry is invalid")
|
||||
counts = np.zeros(len(POLICY_CLASSES), dtype=np.int64)
|
||||
destination_archive.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
try:
|
||||
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]
|
||||
coarse[~valid_fov] = 9
|
||||
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,15 +14,6 @@ from pathlib import Path, PurePosixPath
|
||||
from typing import Any, Final
|
||||
|
||||
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"
|
||||
WORKER_SCHEMA: Final = "missioncore.lab-v1-goose-vegetation-run/v1"
|
||||
@@ -324,10 +315,6 @@ def seal_vegetation_shadow_lab(
|
||||
output_root: Path,
|
||||
ddrnet_ravnoves_video_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,
|
||||
valid_fov_mask_path: Path | None = None,
|
||||
) -> Path:
|
||||
roots = {
|
||||
("ddrnet", "goose"): ddrnet_goose_root.resolve(),
|
||||
@@ -346,18 +333,6 @@ def seal_vegetation_shadow_lab(
|
||||
selected = _selected_candidate(results)
|
||||
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")
|
||||
policy_inputs = (
|
||||
mission_policy_path,
|
||||
provider_label_map_path,
|
||||
m49_tgs_full_shadow_root,
|
||||
valid_fov_mask_path,
|
||||
)
|
||||
if any(value is not None for value in policy_inputs) and not all(
|
||||
value is not None for value in policy_inputs
|
||||
):
|
||||
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_archive: Path | None = None
|
||||
video_result: dict[str, Any] | None = None
|
||||
@@ -380,36 +355,6 @@ def seal_vegetation_shadow_lab(
|
||||
raise VegetationShadowLabError("M4.7 video binding differs from DDRNet source")
|
||||
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 valid_fov_mask_path 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)
|
||||
temporary = Path(tempfile.mkdtemp(prefix=".lab-v1-vegetation-", dir=output_root))
|
||||
artifacts: list[dict[str, object]] = []
|
||||
@@ -526,96 +471,14 @@ def seal_vegetation_shadow_lab(
|
||||
temporary,
|
||||
"video/ddrnet-semantic-masks.zip",
|
||||
artifacts,
|
||||
role=(
|
||||
"route-fine-semantic-source-archive"
|
||||
if mission_policy is not None
|
||||
else "route-semantic-mask-archive"
|
||||
),
|
||||
role="route-semantic-mask-archive",
|
||||
media_type="application/zip",
|
||||
)
|
||||
raw_archive_proof = {
|
||||
route_video["mask_archive"] = {
|
||||
"path": archive_descriptor["path"],
|
||||
"sha256": archive_descriptor["sha256"],
|
||||
"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
|
||||
and valid_fov_mask_path is not None
|
||||
):
|
||||
policy_archive = temporary / "video" / "coarse-material-policy-masks.zip"
|
||||
valid_fov_destination = temporary / "video" / "valid-fov-mask.png"
|
||||
shutil.copyfile(valid_fov_mask_path.resolve(strict=True), valid_fov_destination)
|
||||
valid_fov_descriptor = {
|
||||
"role": "route-camera-valid-fov-mask",
|
||||
"path": "video/valid-fov-mask.png",
|
||||
"byte_length": valid_fov_destination.stat().st_size,
|
||||
"sha256": sha256_path(valid_fov_destination),
|
||||
"media_type": "image/png",
|
||||
}
|
||||
artifacts.append(valid_fov_descriptor)
|
||||
policy_counts = build_policy_mask_archive(
|
||||
source_archive=route_video_archive,
|
||||
destination_archive=policy_archive,
|
||||
fine_taxonomy=_object(route_video["taxonomy"], "fine video taxonomy"),
|
||||
provider_label_map=provider_label_map,
|
||||
valid_fov_mask=valid_fov_destination,
|
||||
)
|
||||
policy_descriptor = {
|
||||
"role": "route-coarse-material-mask-archive",
|
||||
"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,
|
||||
"valid_fov": {
|
||||
"mask_path": valid_fov_descriptor["path"],
|
||||
"mask_sha256": valid_fov_descriptor["sha256"],
|
||||
"outside_valid_fov_class_id": 9,
|
||||
},
|
||||
"policy": {
|
||||
"profile_id": mission_policy["profile_id"],
|
||||
"profile_sha256": sha256_path(mission_policy_path),
|
||||
"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",
|
||||
"camera_valid_fov_filter": "sealed exact KB4 valid-FOV mask",
|
||||
"reason": "No admitted TGS-to-camera pixel projection exists.",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
candidate_metrics: dict[str, object] = {}
|
||||
for candidate in _CANDIDATES:
|
||||
@@ -673,11 +536,7 @@ def seal_vegetation_shadow_lab(
|
||||
"method": {
|
||||
"completeness": "complete",
|
||||
"execution_class": "ai-inference",
|
||||
"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"
|
||||
),
|
||||
"pipeline_id": "goose-fine64-ready-weights-to-ravnoves-policy-shadow/v1",
|
||||
},
|
||||
"metrics": {"candidates": candidate_metrics},
|
||||
"decision": {
|
||||
@@ -685,41 +544,14 @@ def seal_vegetation_shadow_lab(
|
||||
"visual_shadow_ready": True,
|
||||
"full_video_shadow_ready": route_video is not None,
|
||||
"mission_policy_ready_for_configuration": True,
|
||||
"multilayer_policy_review_ready": mission_policy is not None,
|
||||
"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."
|
||||
if mission_policy is not None
|
||||
else (
|
||||
"The full RAVNOVES DDRNet playback is prediction-only and has "
|
||||
"no independent labels."
|
||||
)
|
||||
),
|
||||
"The full RAVNOVES DDRNet playback is prediction-only and has no independent labels.",
|
||||
"Vegetation semantics never clears rigid LiDAR/TGS occupancy.",
|
||||
(
|
||||
"Pixels outside the exact KB4 valid FOV are transparent UNOBSERVED evidence."
|
||||
if mission_policy is not None
|
||||
else "Undefined pixels outside the 600x600 center crop remain fail-closed."
|
||||
),
|
||||
*(
|
||||
[
|
||||
(
|
||||
"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 []
|
||||
),
|
||||
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
|
||||
],
|
||||
"authority": authority,
|
||||
"catalogs": catalogs,
|
||||
@@ -745,10 +577,6 @@ def _parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
parser.add_argument("--ddrnet-ravnoves-video-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)
|
||||
parser.add_argument("--valid-fov-mask-path", type=Path)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -762,10 +590,6 @@ def main() -> None:
|
||||
output_root=args.output_root,
|
||||
ddrnet_ravnoves_video_root=args.ddrnet_ravnoves_video_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,
|
||||
valid_fov_mask_path=args.valid_fov_mask_path,
|
||||
)
|
||||
print(destination)
|
||||
|
||||
|
||||
+1
-15
@@ -138,6 +138,7 @@ from k1link.web.m49_physical_safety_playback_api import (
|
||||
)
|
||||
from k1link.web.m49_tgs_fail_closed_api import build_m49_tgs_fail_closed_router
|
||||
from k1link.web.m49_tgs_full_shadow_api import build_m49_tgs_full_shadow_router
|
||||
from k1link.web.vegetation_shadow_lab_api import build_vegetation_shadow_lab_router
|
||||
from k1link.web.map_api import (
|
||||
MapGatewayConfiguration,
|
||||
MapGatewayProxy,
|
||||
@@ -164,10 +165,6 @@ from k1link.web.session_api import build_session_router
|
||||
from k1link.web.simulation_projects_api import build_simulation_projects_router
|
||||
from k1link.web.simulation_world_provider_api import build_simulation_world_provider_router
|
||||
from k1link.web.system_telemetry_api import build_system_telemetry_router
|
||||
from k1link.web.vegetation_shadow_lab_api import (
|
||||
build_vegetation_benchmark_lab_router,
|
||||
build_vegetation_shadow_lab_router,
|
||||
)
|
||||
from k1link.web.viewer_diagnostics_api import build_viewer_diagnostics_router
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[3]
|
||||
@@ -1034,17 +1031,6 @@ app.include_router(
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_vegetation_benchmark_lab_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "lab-v1-vegetation-benchmark"
|
||||
/ "results"
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_m49_physical_safety_playback_router(
|
||||
root_provider=lambda: (
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import zipfile
|
||||
from collections.abc import Callable
|
||||
from functools import lru_cache
|
||||
@@ -19,9 +20,10 @@ from k1link.laboratory.evidence_report import (
|
||||
LaboratoryEvidenceReportError,
|
||||
verify_laboratory_evidence_result,
|
||||
)
|
||||
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA
|
||||
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA, RESULT_PREFIX
|
||||
|
||||
RootProvider = Callable[[], Path | None]
|
||||
RESULT_ID: Final = re.compile(rf"^{re.escape(RESULT_PREFIX)}[a-f0-9]{{64}}$")
|
||||
_MAX_DOCUMENT_BYTES: Final = 1024 * 1024
|
||||
_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="lab-v1-vegetation-shadow",
|
||||
@@ -30,55 +32,25 @@ _DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
document_name="result.json",
|
||||
result_schema_version=LAB_SCHEMA,
|
||||
)
|
||||
_BENCHMARK_DEFINITION: Final = LaboratoryEvidenceDefinition(
|
||||
work_id="lab-v1-vegetation-benchmark",
|
||||
runtime_relative_root=PurePosixPath("lab-v1-vegetation-benchmark/results"),
|
||||
result_id_prefix="lab-v1-vegetation-benchmark",
|
||||
document_name="result.json",
|
||||
result_schema_version=LAB_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
def build_vegetation_shadow_lab_router(
|
||||
*, root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
return _build_vegetation_lab_router(
|
||||
prefix="/api/v1/laboratory/vegetation-shadow",
|
||||
definition=_DEFINITION,
|
||||
root_provider=root_provider,
|
||||
)
|
||||
|
||||
|
||||
def build_vegetation_benchmark_lab_router(
|
||||
*, root_provider: RootProvider = lambda: None,
|
||||
) -> APIRouter:
|
||||
return _build_vegetation_lab_router(
|
||||
prefix="/api/v1/laboratory/vegetation-benchmark",
|
||||
definition=_BENCHMARK_DEFINITION,
|
||||
root_provider=root_provider,
|
||||
)
|
||||
|
||||
|
||||
def _build_vegetation_lab_router(
|
||||
*,
|
||||
prefix: str,
|
||||
definition: LaboratoryEvidenceDefinition,
|
||||
root_provider: RootProvider,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(
|
||||
prefix=prefix,
|
||||
prefix="/api/v1/laboratory/vegetation-shadow",
|
||||
tags=["laboratory"],
|
||||
)
|
||||
|
||||
@router.get("/{result_id}")
|
||||
def get_result(result_id: str) -> dict[str, object]:
|
||||
candidate = _resolve_candidate(root_provider, definition, result_id)
|
||||
return {**copy.deepcopy(_read_verified(candidate, definition)), "access": "read-only"}
|
||||
candidate = _resolve_candidate(root_provider, result_id)
|
||||
return {**copy.deepcopy(_read_verified(candidate)), "access": "read-only"}
|
||||
|
||||
@router.get("/{result_id}/assets/{asset_path:path}")
|
||||
def get_asset(result_id: str, asset_path: str) -> FileResponse:
|
||||
candidate = _resolve_candidate(root_provider, definition, result_id)
|
||||
manifest = _read_verified(candidate, definition)
|
||||
candidate = _resolve_candidate(root_provider, result_id)
|
||||
manifest = _read_verified(candidate)
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list):
|
||||
raise HTTPException(status_code=404, detail="Vegetation LAB asset not found")
|
||||
@@ -118,36 +90,15 @@ def _build_vegetation_lab_router(
|
||||
|
||||
@router.get("/{result_id}/masks/{sequence}")
|
||||
def get_video_mask(result_id: str, sequence: int) -> Response:
|
||||
candidate = _resolve_candidate(root_provider, definition, result_id)
|
||||
manifest = _read_verified(candidate, definition)
|
||||
candidate = _resolve_candidate(root_provider, result_id)
|
||||
manifest = _read_verified(candidate)
|
||||
route_video = manifest.get("route_video")
|
||||
if (
|
||||
not isinstance(route_video, dict)
|
||||
or route_video.get("frame_count") != 4489
|
||||
or not 0 <= sequence < 4489
|
||||
):
|
||||
if not isinstance(route_video, dict) or not 0 <= sequence < 4489:
|
||||
raise HTTPException(status_code=404, detail="Vegetation video mask not found")
|
||||
archive = route_video.get("mask_archive")
|
||||
archive_relative = archive.get("path") if isinstance(archive, dict) else None
|
||||
if not isinstance(archive_relative, str):
|
||||
if not isinstance(archive, dict) or archive.get("path") != "video/ddrnet-semantic-masks.zip":
|
||||
raise HTTPException(status_code=404, detail="Vegetation video mask not found")
|
||||
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)
|
||||
archive_path = candidate / "video" / "ddrnet-semantic-masks.zip"
|
||||
member = f"masks/frame-{sequence + 1:06d}.png"
|
||||
try:
|
||||
before = archive_path.stat()
|
||||
@@ -196,13 +147,9 @@ def _configured_root(provider: RootProvider) -> Path | None:
|
||||
return root if root.is_dir() else None
|
||||
|
||||
|
||||
def _resolve_candidate(
|
||||
provider: RootProvider,
|
||||
definition: LaboratoryEvidenceDefinition,
|
||||
result_id: str,
|
||||
) -> Path:
|
||||
def _resolve_candidate(provider: RootProvider, result_id: str) -> Path:
|
||||
root = _configured_root(provider)
|
||||
if root is None or definition.result_id_pattern.fullmatch(result_id) is None:
|
||||
if root is None or RESULT_ID.fullmatch(result_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Vegetation LAB result not found")
|
||||
candidate = root / result_id
|
||||
if candidate.is_symlink():
|
||||
@@ -216,10 +163,7 @@ def _resolve_candidate(
|
||||
return resolved
|
||||
|
||||
|
||||
def _read_verified(
|
||||
candidate: Path,
|
||||
definition: LaboratoryEvidenceDefinition,
|
||||
) -> dict[str, Any]:
|
||||
def _read_verified(candidate: Path) -> dict[str, Any]:
|
||||
try:
|
||||
rows: list[tuple[str, int, int, int, int]] = []
|
||||
for path in sorted(candidate.rglob("*"), key=lambda item: item.as_posix()):
|
||||
@@ -237,39 +181,19 @@ def _read_verified(
|
||||
status_code=503,
|
||||
detail="Vegetation LAB evidence failed verification",
|
||||
) from None
|
||||
return _read_verified_cached(
|
||||
str(candidate),
|
||||
signature,
|
||||
definition.work_id,
|
||||
str(definition.runtime_relative_root),
|
||||
definition.result_id_prefix,
|
||||
definition.document_name,
|
||||
definition.result_schema_version,
|
||||
)
|
||||
return _read_verified_cached(str(candidate), signature)
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _read_verified_cached(
|
||||
candidate_text: str,
|
||||
signature: tuple[tuple[str, int, int, int, int], ...],
|
||||
work_id: str,
|
||||
runtime_relative_root: str,
|
||||
result_id_prefix: str,
|
||||
document_name: str,
|
||||
result_schema_version: str,
|
||||
) -> dict[str, Any]:
|
||||
del signature
|
||||
candidate = Path(candidate_text)
|
||||
definition = LaboratoryEvidenceDefinition(
|
||||
work_id=work_id,
|
||||
runtime_relative_root=PurePosixPath(runtime_relative_root),
|
||||
result_id_prefix=result_id_prefix,
|
||||
document_name=document_name,
|
||||
result_schema_version=result_schema_version,
|
||||
)
|
||||
try:
|
||||
verify_laboratory_evidence_result(definition, candidate)
|
||||
path = candidate / definition.document_name
|
||||
verify_laboratory_evidence_result(_DEFINITION, candidate)
|
||||
path = candidate / "result.json"
|
||||
if path.stat().st_size > _MAX_DOCUMENT_BYTES:
|
||||
raise LaboratoryEvidenceReportError("Vegetation LAB document is too large")
|
||||
payload = json.loads(path.read_text("utf-8"))
|
||||
@@ -283,7 +207,4 @@ def _read_verified_cached(
|
||||
return payload
|
||||
|
||||
|
||||
__all__ = [
|
||||
"build_vegetation_benchmark_lab_router",
|
||||
"build_vegetation_shadow_lab_router",
|
||||
]
|
||||
__all__ = ["build_vegetation_shadow_lab_router"]
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
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,
|
||||
"inference_stride": 2,
|
||||
"inference_phase_offset_ms": 40.0,
|
||||
},
|
||||
},
|
||||
"acceptance": {
|
||||
"minimum_graph_world_state_fps": 11.2,
|
||||
"minimum_vegetation_timeline_fps": 11.2,
|
||||
"minimum_vegetation_inference_fps": 5.6,
|
||||
"maximum_vegetation_inference_completion_p95_ms": 125.0,
|
||||
"maximum_semantic_evidence_source_age_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",
|
||||
"integrated_load_gate_passed": True,
|
||||
"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,
|
||||
"effective_timeline_fps": 11.75,
|
||||
"effective_inference_fps": 5.875,
|
||||
"inference_stride": 2,
|
||||
"inference_phase_offset_ms": 40.0,
|
||||
"inference_frame_count": 2245,
|
||||
"held_evidence_frame_count": 2244,
|
||||
"capacity_drop_count": 0,
|
||||
},
|
||||
"timing": {
|
||||
"completion_age_ms": {"p95": 25.0},
|
||||
"inference_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/v2",
|
||||
"sequence": index,
|
||||
"completion_age_ms": 60.0 if index % 2 == 0 else 20.0,
|
||||
"inference_executed": index % 2 == 0,
|
||||
"inference_phase_offset_ms": 40.0 if index % 2 == 0 else 0.0,
|
||||
"semantic_source_sequence": index - (index % 2),
|
||||
"semantic_evidence_source_age_ms": 60.0
|
||||
if index % 2 == 0
|
||||
else 103.333333,
|
||||
}
|
||||
)
|
||||
+ "\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"] == 60.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 (
|
||||
"payload/lab-v1-vegetation-integrated-multirate-phased-shadow-v3.json"
|
||||
in names
|
||||
)
|
||||
assert release["semantic_inference_rate_hz"] == 6.0
|
||||
assert release["semantic_inference_phase_offset_ms"] == 40.0
|
||||
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-multirate-integrated-shadow/v2"' in runner
|
||||
assert "wait_for_shared_start(" in runner
|
||||
assert '"bounded-compressed-scene-buffer/v1"' in runner
|
||||
assert "buffer_compressed_video(" in runner
|
||||
assert '"compressed_scene_prefetch": True' in runner
|
||||
assert '"full_route_rgb_prefetch": False' in runner
|
||||
assert "decode_source(source_capture, expected_size)" in runner
|
||||
assert '"camera_semantics_can_clear_rigid_geometry": False' in runner
|
||||
assert "--runtime-video-cache /tmp/vegetation-right.mp4" in wrapper
|
||||
assert "--inference-stride 2" in wrapper
|
||||
assert "--inference-phase-offset-ms 40.0" in wrapper
|
||||
assert '--tmpfs "/tmp:rw,noexec,nosuid,size=2g"' in wrapper
|
||||
assert "$VegetationLoadGate" in wrapper
|
||||
assert '"vegetation"' in wrapper
|
||||
assert "if ($canonicalAfter.Id -cne $canonicalId" not in wrapper
|
||||
assert "$canonicalAfter.Id -cne $canonicalId" in wrapper
|
||||
@@ -127,10 +127,9 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
|
||||
repository_root / "config" / "laboratories"
|
||||
)
|
||||
|
||||
assert len(registry.definitions) == 44
|
||||
assert len(registry.definitions) == 43
|
||||
assert {item.work_id for item in registry.definitions} >= {
|
||||
"lab-v1-vegetation-shadow",
|
||||
"lab-v1-vegetation-benchmark",
|
||||
"e31-source-binding",
|
||||
"e46j-raw-fisheye-realtime",
|
||||
"e47-semantic-slam-shadow",
|
||||
|
||||
@@ -80,7 +80,7 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
|
||||
root / "config" / "laboratory-value-review.json"
|
||||
)
|
||||
|
||||
assert len(registry.entries) == 42
|
||||
assert len(registry.entries) == 41
|
||||
assert {entry.catalog_id for entry in registry.entries} >= {
|
||||
"e28-local-surface",
|
||||
"e46d-temporal-failure-audit",
|
||||
@@ -94,5 +94,4 @@ def test_product_value_review_registry_covers_reviewed_laboratory_families() ->
|
||||
"m49-tgs-fail-closed-evidence",
|
||||
"m49-tgs-full-shadow",
|
||||
"lab-v1-vegetation-shadow",
|
||||
"lab-v1-vegetation-benchmark",
|
||||
}
|
||||
|
||||
@@ -1,69 +1,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import shutil
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from PIL import Image
|
||||
|
||||
import k1link.laboratory.vegetation_policy_review as policy_review_module
|
||||
import k1link.laboratory.vegetation_policy_video as policy_video_module
|
||||
import k1link.laboratory.vegetation_shadow_lab as vegetation_lab_module
|
||||
from k1link.laboratory import LaboratoryEvidenceRegistry
|
||||
import k1link.laboratory.vegetation_shadow_lab as vegetation_lab_module
|
||||
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.web.vegetation_shadow_lab_api import build_vegetation_shadow_lab_router
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_coarse_policy_masks_mark_every_outside_fov_pixel_undefined(
|
||||
tmp_path: Path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(policy_video_module, "FRAME_COUNT", 1)
|
||||
monkeypatch.setattr(
|
||||
policy_video_module,
|
||||
"fine_to_policy_lut",
|
||||
lambda _taxonomy, _provider_map: np.full(256, 4, dtype=np.uint8),
|
||||
)
|
||||
source = tmp_path / "fine.zip"
|
||||
fine_buffer = io.BytesIO()
|
||||
Image.new("L", (800, 600), color=1).save(fine_buffer, format="PNG")
|
||||
with zipfile.ZipFile(source, "w") as archive:
|
||||
archive.writestr("masks/frame-000001.png", fine_buffer.getvalue())
|
||||
|
||||
valid_fov = np.zeros((600, 800), dtype=np.uint8)
|
||||
valid_fov[:, :400] = 255
|
||||
valid_fov_path = tmp_path / "valid-fov.png"
|
||||
Image.fromarray(valid_fov, mode="L").save(valid_fov_path)
|
||||
destination = tmp_path / "coarse.zip"
|
||||
counts = policy_video_module.build_policy_mask_archive(
|
||||
source_archive=source,
|
||||
destination_archive=destination,
|
||||
fine_taxonomy={},
|
||||
provider_label_map={},
|
||||
valid_fov_mask=valid_fov_path,
|
||||
)
|
||||
with (
|
||||
zipfile.ZipFile(destination) as archive,
|
||||
Image.open(io.BytesIO(archive.read("masks/frame-000001.png"))) as image,
|
||||
):
|
||||
coarse = np.asarray(image.convert("L"))
|
||||
assert np.all(coarse[:, :400] == 4)
|
||||
assert np.all(coarse[:, 400:] == 9)
|
||||
assert counts[4] == 600 * 400
|
||||
assert counts[9] == 600 * 400
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
@@ -275,100 +229,3 @@ def test_vegetation_shadow_lab_seals_autonomous_visual_evidence(
|
||||
client.get(f"/api/v1/laboratory/vegetation-shadow/{result_root.name}").status_code
|
||||
== 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"])
|
||||
assert kwargs["valid_fov_mask"].is_file()
|
||||
return [4489 * 800 * 600, *([0] * 9)]
|
||||
|
||||
monkeypatch.setattr(policy_review_module, "build_policy_mask_archive", fake_policy_archive)
|
||||
valid_fov_mask = tmp_path / "valid-fov-mask.png"
|
||||
Image.new("L", (800, 600), color=255).save(valid_fov_mask)
|
||||
result_root = seal_vegetation_policy_review(
|
||||
base_lab_root=base_root,
|
||||
mission_policy_path=REPOSITORY_ROOT
|
||||
/ "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",
|
||||
valid_fov_mask_path=valid_fov_mask,
|
||||
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"]) == 10
|
||||
assert route["valid_fov"]["outside_valid_fov_class_id"] == 9
|
||||
assert len(manifest["artifacts"]) == 80
|
||||
assert manifest["authority"]["commands_enabled"] is False
|
||||
assert manifest["decision"]["multilayer_policy_review_ready"] is True
|
||||
|
||||
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