feat(perception): add DDRNet full-video vegetation replay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-28 01:13:18 +03:00
parent 67e6ae98e2
commit 30080c51aa
10 changed files with 729 additions and 64 deletions
@@ -51,6 +51,26 @@ export interface VegetationVisualCase {
assets: Readonly<Record<string, string>>;
}
export interface VegetationVideoSemanticClass {
classId: number;
label: string;
colorRgb: readonly [number, number, number];
disposition: "prediction" | "undefined";
}
export interface VegetationRouteVideo {
workerResultId: string;
m47ReferenceGraphResultId: string;
baseM4ResultId: string;
frameCount: 4489;
width: 800;
height: 600;
centerCropXyxy: readonly [100, 0, 700, 600];
outsideCropState: "undefined";
taxonomy: readonly VegetationVideoSemanticClass[];
aggregatePredictionPixels: readonly number[];
}
export interface VegetationShadowResult {
resultId: string;
createdAtUtc: string;
@@ -59,6 +79,7 @@ export interface VegetationShadowResult {
candidates: readonly VegetationCandidateMetrics[];
routeCases: readonly VegetationVisualCase[];
validationCases: readonly VegetationVisualCase[];
routeVideo: VegetationRouteVideo | null;
limitations: readonly string[];
visualShadowReady: true;
missionPolicyReadyForConfiguration: true;
@@ -227,6 +248,98 @@ function visualCaseValue(
};
}
function routeVideoValue(value: unknown): VegetationRouteVideo | null {
if (value === null || value === undefined) return null;
const row = objectValue(value, "vegetation.route_video");
const workerResultId = textValue(row.worker_result_id, "vegetation.route_video.worker_result_id");
const m47ReferenceGraphResultId = textValue(
row.m47_reference_graph_result_id,
"vegetation.route_video.m47_reference_graph_result_id",
);
const baseM4ResultId = textValue(row.base_m4_result_id, "vegetation.route_video.base_m4_result_id");
if (
!/^lab-v1-ravnoves-video-ddrnet-[a-f0-9]{64}$/.test(workerResultId)
|| !/^m47-reference-graph-lab-[a-f0-9]{64}$/.test(m47ReferenceGraphResultId)
|| !/^m4-threat-replay-[a-f0-9]{64}$/.test(baseM4ResultId)
) {
throw new VegetationShadowContractError("vegetation.route_video: 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");
exact(row.outside_crop_state, "undefined", "vegetation.route_video.outside_crop_state");
exact(
row.sequence_binding,
"sequence-0-to-masks/frame-000001.png",
"vegetation.route_video.sequence_binding",
);
const crop = arrayValue(row.center_crop_xyxy, "vegetation.route_video.center_crop_xyxy")
.map((item, index) => integerValue(item, `vegetation.route_video.crop[${index}]`));
if (crop.join(",") !== "100,0,700,600") {
throw new VegetationShadowContractError("vegetation.route_video: crop contract changed.");
}
const taxonomy = objectValue(row.taxonomy, "vegetation.route_video.taxonomy");
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}]`);
const classId = integerValue(item.class_id, `vegetation.route_video.class_id[${expectedId}]`);
if (classId !== expectedId) {
throw new VegetationShadowContractError("vegetation.route_video: taxonomy order changed.");
}
const color = arrayValue(item.color_rgb, `vegetation.route_video.color[${expectedId}]`)
.map((channel, index) => integerValue(channel, `vegetation.route_video.color[${expectedId}][${index}]`));
if (color.length !== 3 || color.some((channel) => channel > 255)) {
throw new VegetationShadowContractError("vegetation.route_video: taxonomy color invalid.");
}
const disposition: VegetationVideoSemanticClass["disposition"] = expectedId === 0
? "undefined"
: "prediction";
if (item.disposition !== disposition) {
throw new VegetationShadowContractError("vegetation.route_video: taxonomy disposition changed.");
}
return {
classId,
label: textValue(item.label, `vegetation.route_video.label[${expectedId}]`),
colorRgb: color as unknown as readonly [number, number, number],
disposition,
};
});
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 !== 64) {
throw new VegetationShadowContractError("vegetation.route_video: class accounting changed.");
}
const maskArchive = objectValue(row.mask_archive, "vegetation.route_video.mask_archive");
exact(maskArchive.path, "video/ddrnet-semantic-masks.zip", "vegetation.route_video.mask_archive.path");
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");
return {
workerResultId,
m47ReferenceGraphResultId,
baseM4ResultId,
frameCount: 4489,
width: 800,
height: 600,
centerCropXyxy: [100, 0, 700, 600],
outsideCropState: "undefined",
taxonomy: classes,
aggregatePredictionPixels,
};
}
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");
@@ -277,6 +390,7 @@ function parseResult(value: unknown, resultId: string): VegetationShadowResult {
candidates: CANDIDATES.map((candidate) => candidateMetricsValue(candidates[candidate], candidate)),
routeCases,
validationCases,
routeVideo: routeVideoValue(payload.route_video),
limitations: arrayValue(payload.limitations, "vegetation.limitations")
.map((item, index) => textValue(item, `vegetation.limitations[${index}]`)),
visualShadowReady: true,
@@ -290,6 +404,13 @@ function parseResult(value: unknown, resultId: string): VegetationShadowResult {
};
}
export function vegetationVideoMaskUrl(resultId: string, sequence: number): string {
if (!RESULT_ID.test(resultId) || !Number.isInteger(sequence) || sequence < 0 || sequence >= 4489) {
throw new VegetationShadowContractError("Vegetation video mask identity недопустима.");
}
return `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/masks/${sequence}`;
}
export async function fetchVegetationShadowResult(
resultId: string,
{
@@ -97,7 +97,16 @@ function SpatialState({ message: text }: { message: string }) {
export interface M4ReplayThreatSemanticLayer {
resultId: string;
taxonomy: readonly E47SemanticClass[];
spatialResultId?: string | null;
maskUrl?: (sequence: number) => string;
label?: string;
maskAriaLabel?: string;
taxonomy: readonly {
classId: number;
label: string;
disposition: "labeled" | "ambiguous" | "prediction" | "undefined";
colorRgb: readonly [number, number, number];
}[];
}
export interface M4ReplayThreatReviewAnchor {
@@ -153,6 +162,8 @@ export function M4ReplayThreatVisual({
evidenceLabel = "M4.6",
initialSpatialMode = null,
classifiedSpatialLayer,
showReferenceMediaLayers = true,
showSpatialOverlaySummary = true,
onActiveSequenceChange,
}: {
resultId: string;
@@ -164,6 +175,8 @@ export function M4ReplayThreatVisual({
evidenceLabel?: string;
initialSpatialMode?: LaboratoryMetricSceneMode | null;
classifiedSpatialLayer?: M4ReplayClassifiedSpatialLayer;
showReferenceMediaLayers?: boolean;
showSpatialOverlaySummary?: boolean;
onActiveSequenceChange?: (sequence: number | null) => void;
}) {
const [mediaMode, setMediaMode] = useState<M4ThreatMediaMode | null>("video");
@@ -280,16 +293,30 @@ export function M4ReplayThreatVisual({
? lastSpatialFrameRef.current.frame
: null;
const cameraPointOverlay = useM4ThreatCameraPointOverlay({
enabled: showMediaPoints,
enabled: showReferenceMediaLayers && showMediaPoints,
resultId,
sequence: frame?.sequence ?? null,
endpointRoot: timelineEndpointRoot,
});
const semanticSpatialResultId = semantic
? semantic.spatialResultId === undefined ? semantic.resultId : semantic.spatialResultId
: null;
const spatialSemanticTaxonomy = useMemo<readonly E47SemanticClass[]>(
() => semanticSpatialResultId && semantic
? semantic.taxonomy.map((item) => ({
classId: item.classId,
label: item.label,
disposition: item.disposition === "ambiguous" ? "ambiguous" : "labeled",
colorRgb: item.colorRgb,
}))
: [],
[semantic, semanticSpatialResultId],
);
const semanticTimeline = useE47SemanticTimelineFrame({
resultId: semantic?.resultId ?? null,
resultId: semanticSpatialResultId,
activeSequence: frame?.sequence ?? timelineFrame.activeSequence,
frameCount: metadata.timeline?.frameCount ?? 0,
taxonomy: semantic?.taxonomy ?? [],
taxonomy: spatialSemanticTaxonomy,
});
const displayingBufferedFrame = Boolean(
frame
@@ -339,7 +366,8 @@ export function M4ReplayThreatVisual({
const staticObstacleBoxes = useMemo<readonly RecordedEvidenceBox[]>(() => {
const timeline = metadata.timeline;
if (
!frame
!showReferenceMediaLayers
|| !frame
|| !timeline?.cameraObstacleProjectionDelivery
|| !showStaticObstacles
) return [];
@@ -348,14 +376,14 @@ export function M4ReplayThreatVisual({
timeline.imageWidth,
timeline.imageHeight,
);
}, [frame, metadata.timeline, showStaticObstacles]);
}, [frame, metadata.timeline, showReferenceMediaLayers, showStaticObstacles]);
const activeBoxes = useMemo(
() => classifiedSpatialLayer ? [] : [
() => classifiedSpatialLayer || !showReferenceMediaLayers ? [] : [
...boxes(frame?.cameraProposals ?? []),
...staticObstacleBoxes,
...reviewAnchorBoxes,
],
[classifiedSpatialLayer, frame, reviewAnchorBoxes, staticObstacleBoxes],
[classifiedSpatialLayer, frame, reviewAnchorBoxes, showReferenceMediaLayers, staticObstacleBoxes],
);
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
() => semantic?.taxonomy.map((item) => ({
@@ -367,10 +395,14 @@ export function M4ReplayThreatVisual({
const semanticPalette = useMemo<readonly RecordedEvidenceSemanticPaletteEntry[]>(
() => semantic?.taxonomy.map((item) => ({
classId: item.classId,
color: item.disposition === "ambiguous"
color: item.disposition === "undefined"
? { kind: "transparent" as const }
: item.disposition === "ambiguous"
? { kind: "token" as const, token: "--nodedc-warning-rgb" as const }
: { kind: "diagnostic" as const, rgb: item.colorRgb },
opacity: item.disposition === "ambiguous" ? 0.52 : 0.92,
opacity: item.disposition === "undefined"
? 0
: item.disposition === "ambiguous" ? 0.52 : 0.92,
})) ?? [],
[semantic?.taxonomy],
);
@@ -580,15 +612,17 @@ export function M4ReplayThreatVisual({
const semanticOverlay: RecordedEvidenceSemanticOverlay | undefined =
semantic && showMediaSemantic && frame
? {
src: e47SemanticMaskUrl(semantic.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) => e47SemanticMaskUrl(semantic.resultId, sequence)),
.map((sequence) => semantic.maskUrl?.(sequence)
?? e47SemanticMaskUrl(semantic.resultId, sequence)),
classes: semanticClasses,
palette: semanticPalette,
opacity: 0.9,
ariaLabel: `E47 semantic mask frame ${frame.sequence + 1}`,
ariaLabel: `${semantic.maskAriaLabel ?? "Semantic prediction"} frame ${frame.sequence + 1}`,
}
: undefined;
const accumulatedCameraPoints = cameraPointOverlay.overlay?.sequence === frame?.sequence
@@ -659,8 +693,8 @@ export function M4ReplayThreatVisual({
);
const mediaLayerControls = semantic
|| metadata.timeline?.cameraPointDelivery
|| metadata.timeline?.cameraObstacleProjectionDelivery ? (
|| (showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery)
|| (showReferenceMediaLayers && metadata.timeline?.cameraObstacleProjectionDelivery) ? (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
@@ -677,7 +711,7 @@ export function M4ReplayThreatVisual({
SEMANTICS
</Button>
) : null}
{metadata.timeline?.cameraPointDelivery ? (
{showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery ? (
<Button
size="compact"
shape="pill"
@@ -689,7 +723,7 @@ export function M4ReplayThreatVisual({
POINTS
</Button>
) : null}
{metadata.timeline?.cameraObstacleProjectionDelivery ? (
{showReferenceMediaLayers && metadata.timeline?.cameraObstacleProjectionDelivery ? (
<Button
size="compact"
shape="pill"
@@ -738,7 +772,7 @@ export function M4ReplayThreatVisual({
>
{classifiedSpatialLayer.cellLayerLabel}
</Button>
{semantic ? (
{semanticSpatialResultId ? (
<Button
size="compact"
shape="pill"
@@ -796,7 +830,7 @@ export function M4ReplayThreatVisual({
LOW-STEP
</Button>
) : null}
{semantic ? (
{semanticSpatialResultId ? (
<Button
size="compact"
shape="pill"
@@ -901,9 +935,11 @@ export function M4ReplayThreatVisual({
: playbackController.playback.playing ? "воспроизведение" : "пауза / seek"}
</small>
</div>
<div>
<span>Spatial evidence</span>
<strong>{classifiedSpatialLayer
{showSpatialOverlaySummary ? (
<>
<div>
<span>Spatial evidence</span>
<strong>{classifiedSpatialLayer
? classifiedSpatialFrame
? replaceClassifiedPointCloud
? `${classifiedSpatialFrame.pointsMapGravityLocalXyzM.length.toLocaleString("ru-RU")} TGS points · ${classifiedCellCount.toLocaleString("ru-RU")} cells`
@@ -939,15 +975,15 @@ export function M4ReplayThreatVisual({
: showMediaPoints && cameraPointOverlay.error
? " · накопленное camera cloud недоступно"
: ""}
{semantic && spatialSemanticFrame
{semanticSpatialResultId && spatialSemanticFrame
? ` · semantic L ${spatialSemanticFrame.counts.labeled} · A ${spatialSemanticFrame.counts.ambiguous} · U ${spatialSemanticFrame.counts.unprojected} · Ø ${spatialSemanticFrame.counts.absent}`
: semantic ? " · semantic buffer" : ""}
: semanticSpatialResultId ? " · semantic buffer" : ""}
</>
)}</small>
</div>
<div>
<span>{classifiedSpatialLayer ? "TGS fail-closed" : "Virtual corridor"}</span>
<strong>{classifiedSpatialLayer
</div>
<div>
<span>{classifiedSpatialLayer ? "TGS fail-closed" : "Virtual corridor"}</span>
<strong>{classifiedSpatialLayer
? classifiedSpatialFrame
? `${classifiedCellCounts.occupied} occupied · ${classifiedCellCounts.rejected} rejected · ${classifiedCellCounts.unobserved} unobserved`
: classifiedSpatialLayer.loading || displayingBufferedFrame ? "loading" : "unavailable"
@@ -957,7 +993,9 @@ export function M4ReplayThreatVisual({
? `${classifiedCellCounts.ground} ground-support · visual review only · navigation authority OFF`
: "visual review only · navigation authority OFF"
: `${metadata.timeline.corridor.forwardLengthM} м · body ${metadata.timeline.rig.lengthM}×${metadata.timeline.rig.widthM} м · REPLAY-SIMULATED`}</small>
</div>
</div>
</>
) : null}
</div>
) : undefined;
@@ -1153,13 +1191,13 @@ export function M4ReplayThreatVisual({
<span>{timelineFrame.error}</span>
</div>
) : null}
{semantic && semanticTimeline.loading ? (
{semanticSpatialResultId && semanticTimeline.loading ? (
<div className="m4-replay-threat-visual__buffering" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Догружаем semantic-point evidence E47</span>
</div>
) : null}
{semanticTimeline.error ? (
{semanticSpatialResultId && semanticTimeline.error ? (
<div className="m4-replay-threat-visual__buffering" role="alert">
<Icon name="alert" size={16} />
<span>{semanticTimeline.error}</span>
@@ -1201,7 +1239,7 @@ export function M4ReplayThreatVisual({
<div className="l3-visual-audit m4-replay-threat-visual">
<LaboratoryEvidenceViewer
label={semantic
? "E47 semantic + SLAM diagnostic replay"
? semantic.label ?? "Semantic diagnostic replay"
: `${evidenceLabel} recorded-realtime replay`}
className="m4-replay-threat-evidence-viewer"
mode={mediaMode ?? "none"}
@@ -4,11 +4,15 @@ import {
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { VegetationShadowResult } from "../../core/laboratory/vegetationShadow";
import {
vegetationVideoMaskUrl,
type VegetationShadowResult,
} from "../../core/laboratory/vegetationShadow";
import {
M48MaskComparisonVisual,
type M48MaskComparisonCase,
} from "./M48FailureAtlasVisual";
import { M4ReplayThreatVisual } from "./M4ReplayThreatVisual";
function decimal(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
@@ -63,20 +67,28 @@ export function VegetationShadowResultView({
summary={(
<LaboratorySummary
title="LAB V1 · готовые модели растительности"
description="Штатный M4.8-инструмент сравнивает две готовые fine-64 модели на полном GOOSE validation split и на 12 truth-backed hard cases, выбранных только по наличию нужной растительности. Sealed evidence открывается локально без Worker 006."
status="Truth-backed model comparison · route transfer не принят"
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: "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: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных 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)}%. Ошибки по каждому типу теперь проверяются в одном штатном инструменте.`,
limitation: "Это внешний GOOSE-домен, а не наш fisheye/off-road маршрут. Папоротник отдельным классом отсутствует; RAVNOVES00 не содержит truth-backed vegetation island и не используется как главное визуальное доказательство.",
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: "complete",
@@ -93,17 +105,42 @@ export function VegetationShadowResultView({
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
title="ERROR: красный — пропуск · жёлтый — лишнее · фиолетовый — перепутан тип · зелёный — совпадение"
kind="diagnostic-model"
resizable
>
<M48MaskComparisonVisual
cases={comparisonCases(result)}
initialCandidate={result.selectedCandidate}
/>
</LaboratoryEvidence>
<>
<LaboratoryEvidence
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
title="ERROR: красный — пропуск · жёлтый — лишнее · фиолетовый — перепутан тип · зелёный — совпадение"
kind="diagnostic-model"
resizable
>
<M48MaskComparisonVisual
cases={comparisonCases(result)}
initialCandidate={result.selectedCandidate}
/>
</LaboratoryEvidence>
{result.routeVideo ? (
<LaboratoryEvidence
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
title="DDRNet PREDICTION · 4489/4489 кадров · TRUTH для этой записи отсутствует"
kind="diagnostic-model"
resizable
>
<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
@@ -146,11 +183,16 @@ export function VegetationShadowResultView({
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: "Обе официальные fine-64 модели воспроизводимо запускаются на Worker 006; DDRNet лучше по aggregate vegetation IoU. Truth-backed hard cases прямо показывают траву, кусты и стволы, а не случайные автомобили и здания.",
notProved: "Не доказаны accuracy на нашем fisheye-домене, папоротник как отдельный материал, temporal stability, collision safety и physical-live поведение ровера.",
decision: "Сохранить DDRNet как стартовый vegetation candidate. Mission-policy и автоматическое переключение пресетов подключать только после truth-backed island нашего офф-роуда; LiDAR/TGS fail-closed геометрию не ослаблять.",
notProved: "Не доказаны accuracy на нашем fisheye-домене, папоротник как отдельный материал, collision safety и physical-live поведение ровера. Видео позволяет увидеть temporal stability, но без truth не превращает её в метрику качества.",
decision: "Смотреть полный prediction на видео и собирать конкретные temporal/domain failure cases. DDRNet остаётся diagnostic candidate; LiDAR/TGS fail-closed геометрию не ослаблять.",
}}
/>
)}
@@ -75,6 +75,35 @@ function visualCase(sourceKind, index) {
};
}
function routeVideo() {
return {
worker_result_id: `lab-v1-ravnoves-video-ddrnet-${"e".repeat(64)}`,
m47_reference_graph_result_id: `m47-reference-graph-lab-${"f".repeat(64)}`,
base_m4_result_id: `m4-threat-replay-${"1".repeat(64)}`,
frame_count: 4489,
width: 800,
height: 600,
center_crop_xyxy: [100, 0, 700, 600],
outside_crop_state: "undefined",
sequence_binding: "sequence-0-to-masks/frame-000001.png",
taxonomy: {
schema_version: "missioncore.lab-v1-vegetation-taxonomy/v1",
classes: Array.from({ length: 64 }, (_, classId) => ({
class_id: classId,
label: classId === 0 ? "undefined" : `class-${classId}`,
color_rgb: [classId, classId, classId],
disposition: classId === 0 ? "undefined" : "prediction",
})),
},
aggregate_prediction_pixels: Array(64).fill(0),
mask_archive: {
path: "video/ddrnet-semantic-masks.zip",
sha256: "9".repeat(64),
byte_length: 1024,
},
};
}
test("vegetation LAB keeps autonomous assets and fail-closed authority", async () => {
let requestedUrl = "";
const result = await fetchVegetationShadowResult(resultId, {
@@ -111,6 +140,7 @@ test("vegetation LAB keeps autonomous assets and fail-closed authority", async (
goose: Array.from({ length: 12 }, (_, index) => visualCase("goose", index)),
ravnoves: [],
},
route_video: routeVideo(),
access: "read-only",
}), { status: 200, headers: { "Content-Type": "application/json" } });
},
@@ -123,6 +153,8 @@ test("vegetation LAB keeps autonomous assets and fail-closed authority", async (
assert.equal(result.candidates[0].vegetationMeanIouPercent, 64);
assert.equal(result.routeCases.length, 0);
assert.equal(result.validationCases.length, 12);
assert.equal(result.routeVideo.frameCount, 4489);
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\//);
assert.deepEqual(result.authority, {
@@ -133,13 +165,15 @@ test("vegetation LAB keeps autonomous assets and fail-closed authority", async (
});
});
test("vegetation LAB reuses the admitted M4.8 instrument", async () => {
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",
);
assert.match(resultSource, /M48MaskComparisonVisual/);
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 1);
assert.match(resultSource, /M4ReplayThreatVisual/);
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 2);
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)),