feat(perception): split vegetation evidence layers
This commit is contained in:
@@ -51,6 +51,7 @@ import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
|
||||
import { M49TgsFailClosedResultView } from "./M49TgsFailClosedResult";
|
||||
import { M49TgsFullShadowResultView } from "./M49TgsFullShadowResult";
|
||||
import { VegetationShadowResultView } from "./VegetationShadowResult";
|
||||
import { VegetationBenchmarkResultView } from "./VegetationBenchmarkResult";
|
||||
|
||||
export { isAdvancedLaboratoryWorkId };
|
||||
export type { AdvancedLaboratoryWorkId };
|
||||
@@ -93,6 +94,9 @@ export function AdvancedLaboratoryResult({
|
||||
failedSessionId: string | null;
|
||||
replayError: string | null;
|
||||
}) {
|
||||
if (workId === "lab-v1-vegetation-benchmark" && results.vegetationBenchmark) {
|
||||
return <VegetationBenchmarkResultView rigLabel={rigLabel} result={results.vegetationBenchmark} />;
|
||||
}
|
||||
if (workId === "lab-v1-vegetation-shadow" && results.vegetationShadow) {
|
||||
return <VegetationShadowResultView rigLabel={rigLabel} result={results.vegetationShadow} />;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ export function M49TgsFullShadowEvidence({
|
||||
const controller = new AbortController();
|
||||
setSemantic(null);
|
||||
setSemanticError(null);
|
||||
if (semanticOverride) return () => controller.abort();
|
||||
void fetchE47SemanticSlamResult({
|
||||
resultId: result.source.linkedSemanticResultId,
|
||||
signal: controller.signal,
|
||||
@@ -87,7 +86,7 @@ export function M49TgsFullShadowEvidence({
|
||||
if (!controller.signal.aborted) setSemanticError(message(caught));
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId, semanticOverride]);
|
||||
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId]);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
@@ -201,15 +200,28 @@ export function M49TgsFullShadowEvidence({
|
||||
const handleSequenceChange = useCallback((sequence: number | null) => {
|
||||
setActiveSequence(sequence);
|
||||
}, []);
|
||||
const semanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(() => [
|
||||
...(semantic ? [{
|
||||
id: "urban",
|
||||
controlLabel: "ГОРОД · EoMT",
|
||||
resultId: semantic.resultId,
|
||||
taxonomy: semantic.taxonomy,
|
||||
label: "EoMT Cityscapes semantic · recorded video",
|
||||
maskAriaLabel: "EoMT urban semantic prediction",
|
||||
}] : []),
|
||||
...(semanticOverride ? [{
|
||||
...semanticOverride,
|
||||
id: semanticOverride.id ?? "vegetation",
|
||||
controlLabel: semanticOverride.controlLabel ?? "ПРИРОДА · DDRNet",
|
||||
}] : []),
|
||||
], [semantic, semanticOverride]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<M4ReplayThreatVisual
|
||||
resultId={result.source.linkedVisualResultId}
|
||||
semantic={semanticOverride ?? (semantic ? {
|
||||
resultId: semantic.resultId,
|
||||
taxonomy: semantic.taxonomy,
|
||||
} : undefined)}
|
||||
semanticLayers={semanticLayers}
|
||||
initialSemanticLayerId={semanticOverride ? "vegetation" : "urban"}
|
||||
showReviewAnchorBoxes={false}
|
||||
reviewLabel="4 489 source-paced TGS frames"
|
||||
evidenceLabel={evidenceLabel}
|
||||
@@ -227,7 +239,7 @@ export function M49TgsFullShadowEvidence({
|
||||
replacePointCloud: false,
|
||||
}}
|
||||
/>
|
||||
{!semanticOverride && semanticError ? (
|
||||
{semanticError ? (
|
||||
<div className="m4-replay-threat-visual__pane-status" role="alert">
|
||||
Semantic overlay недоступен: {semanticError}
|
||||
</div>
|
||||
|
||||
@@ -96,6 +96,8 @@ function SpatialState({ message: text }: { message: string }) {
|
||||
}
|
||||
|
||||
export interface M4ReplayThreatSemanticLayer {
|
||||
id?: string;
|
||||
controlLabel?: string;
|
||||
resultId: string;
|
||||
spatialResultId?: string | null;
|
||||
maskUrl?: (sequence: number) => string;
|
||||
@@ -155,6 +157,8 @@ const EMPTY_REVIEW_ANCHORS: readonly M4ReplayThreatReviewAnchor[] = [];
|
||||
export function M4ReplayThreatVisual({
|
||||
resultId,
|
||||
semantic,
|
||||
semanticLayers,
|
||||
initialSemanticLayerId,
|
||||
reviewAnchors = EMPTY_REVIEW_ANCHORS,
|
||||
showReviewAnchorBoxes = true,
|
||||
reviewLabel = "Контрольные примеры M4.8R1",
|
||||
@@ -168,6 +172,8 @@ export function M4ReplayThreatVisual({
|
||||
}: {
|
||||
resultId: string;
|
||||
semantic?: M4ReplayThreatSemanticLayer;
|
||||
semanticLayers?: readonly M4ReplayThreatSemanticLayer[];
|
||||
initialSemanticLayerId?: string;
|
||||
reviewAnchors?: readonly M4ReplayThreatReviewAnchor[];
|
||||
showReviewAnchorBoxes?: boolean;
|
||||
reviewLabel?: string;
|
||||
@@ -199,6 +205,35 @@ export function M4ReplayThreatVisual({
|
||||
));
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [selectedReviewAnchorIndex, setSelectedReviewAnchorIndex] = useState(0);
|
||||
const availableSemanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(
|
||||
() => semanticLayers?.length ? semanticLayers : semantic ? [semantic] : [],
|
||||
[semantic, semanticLayers],
|
||||
);
|
||||
const semanticLayerIdentity = availableSemanticLayers
|
||||
.map((layer, index) => layer.id ?? `${layer.resultId}:${index}`)
|
||||
.join("|");
|
||||
const [selectedSemanticLayerId, setSelectedSemanticLayerId] = useState(
|
||||
initialSemanticLayerId ?? "",
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!availableSemanticLayers.length) {
|
||||
setSelectedSemanticLayerId("");
|
||||
return;
|
||||
}
|
||||
const selectedStillExists = availableSemanticLayers.some(
|
||||
(layer, index) => (layer.id ?? `${layer.resultId}:${index}`) === selectedSemanticLayerId,
|
||||
);
|
||||
if (selectedStillExists) return;
|
||||
const preferred = initialSemanticLayerId
|
||||
? availableSemanticLayers.find((layer) => layer.id === initialSemanticLayerId)
|
||||
: null;
|
||||
const next = preferred ?? availableSemanticLayers[0]!;
|
||||
const nextIndex = availableSemanticLayers.indexOf(next);
|
||||
setSelectedSemanticLayerId(next.id ?? `${next.resultId}:${nextIndex}`);
|
||||
}, [availableSemanticLayers, initialSemanticLayerId, semanticLayerIdentity, selectedSemanticLayerId]);
|
||||
const activeSemantic = availableSemanticLayers.find(
|
||||
(layer, index) => (layer.id ?? `${layer.resultId}:${index}`) === selectedSemanticLayerId,
|
||||
) ?? availableSemanticLayers[0];
|
||||
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
|
||||
const metadata = useM4ThreatTimelineMetadata(resultId, timelineEndpointRoot);
|
||||
const playbackRange = useMemo(() => metadata.timeline ? ({
|
||||
@@ -298,19 +333,21 @@ export function M4ReplayThreatVisual({
|
||||
sequence: frame?.sequence ?? null,
|
||||
endpointRoot: timelineEndpointRoot,
|
||||
});
|
||||
const semanticSpatialResultId = semantic
|
||||
? semantic.spatialResultId === undefined ? semantic.resultId : semantic.spatialResultId
|
||||
const semanticSpatialResultId = activeSemantic
|
||||
? activeSemantic.spatialResultId === undefined
|
||||
? activeSemantic.resultId
|
||||
: activeSemantic.spatialResultId
|
||||
: null;
|
||||
const spatialSemanticTaxonomy = useMemo<readonly E47SemanticClass[]>(
|
||||
() => semanticSpatialResultId && semantic
|
||||
? semantic.taxonomy.map((item) => ({
|
||||
() => semanticSpatialResultId && activeSemantic
|
||||
? activeSemantic.taxonomy.map((item) => ({
|
||||
classId: item.classId,
|
||||
label: item.label,
|
||||
disposition: item.disposition === "ambiguous" ? "ambiguous" : "labeled",
|
||||
colorRgb: item.colorRgb,
|
||||
}))
|
||||
: [],
|
||||
[semantic, semanticSpatialResultId],
|
||||
[activeSemantic, semanticSpatialResultId],
|
||||
);
|
||||
const semanticTimeline = useE47SemanticTimelineFrame({
|
||||
resultId: semanticSpatialResultId,
|
||||
@@ -386,14 +423,14 @@ export function M4ReplayThreatVisual({
|
||||
[frame, reviewAnchorBoxes, showReferenceMediaLayers, staticObstacleBoxes],
|
||||
);
|
||||
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
|
||||
() => semantic?.taxonomy.map((item) => ({
|
||||
() => activeSemantic?.taxonomy.map((item) => ({
|
||||
id: item.classId,
|
||||
label: `semantic: ${item.label}`,
|
||||
})) ?? [],
|
||||
[semantic?.taxonomy],
|
||||
[activeSemantic?.taxonomy],
|
||||
);
|
||||
const semanticPalette = useMemo<readonly RecordedEvidenceSemanticPaletteEntry[]>(
|
||||
() => semantic?.taxonomy.map((item) => ({
|
||||
() => activeSemantic?.taxonomy.map((item) => ({
|
||||
classId: item.classId,
|
||||
color: item.disposition === "undefined"
|
||||
? { kind: "transparent" as const }
|
||||
@@ -404,7 +441,7 @@ export function M4ReplayThreatVisual({
|
||||
? 0
|
||||
: item.disposition === "ambiguous" ? 0.52 : 0.92,
|
||||
})) ?? [],
|
||||
[semantic?.taxonomy],
|
||||
[activeSemantic?.taxonomy],
|
||||
);
|
||||
const semanticFrame = semanticTimeline.activeFrame?.sequence === frame?.sequence
|
||||
? semanticTimeline.activeFrame
|
||||
@@ -422,7 +459,7 @@ export function M4ReplayThreatVisual({
|
||||
&& lastSpatialSemanticFrameRef.current.frame.sequence === spatialFrame?.sequence
|
||||
? lastSpatialSemanticFrameRef.current.frame
|
||||
: null;
|
||||
const semanticIntegrityError = semantic && spatialFrame && spatialSemanticFrame && (
|
||||
const semanticIntegrityError = activeSemantic && spatialFrame && spatialSemanticFrame && (
|
||||
spatialSemanticFrame.sourcePointCount !== spatialFrame.pointCloudSourceCount
|
||||
|| spatialFrame.pointCloudSampleCount !== spatialFrame.pointCloudSourceCount
|
||||
|| spatialFrame.pointCloudBodyXyzM.length !== spatialFrame.pointCloudSourceCount
|
||||
@@ -431,7 +468,7 @@ export function M4ReplayThreatVisual({
|
||||
: null;
|
||||
const alignedSemanticPointIds = useMemo<readonly (number | null)[] | undefined>(() => {
|
||||
if (
|
||||
!semantic
|
||||
!activeSemantic
|
||||
|| !showSpatialSemantic
|
||||
|| !spatialFrame
|
||||
|| !spatialSemanticFrame
|
||||
@@ -441,7 +478,7 @@ export function M4ReplayThreatVisual({
|
||||
const status = spatialSemanticFrame.statusCodes[index];
|
||||
return status === 2 || status === 3 ? classId : null;
|
||||
});
|
||||
}, [semantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
|
||||
}, [activeSemantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
|
||||
const activeSpatialFrame = spatialFrame?.sequence === timelineFrame.activeSequence
|
||||
? spatialFrame
|
||||
: null;
|
||||
@@ -610,19 +647,19 @@ export function M4ReplayThreatVisual({
|
||||
},
|
||||
), [metadata.timeline, spatialFrame, timelineFrame.availableFrames]);
|
||||
const semanticOverlay: RecordedEvidenceSemanticOverlay | undefined =
|
||||
semantic && showMediaSemantic && frame
|
||||
activeSemantic && showMediaSemantic && frame
|
||||
? {
|
||||
src: semantic.maskUrl?.(frame.sequence)
|
||||
?? e47SemanticMaskUrl(semantic.resultId, frame.sequence),
|
||||
src: activeSemantic.maskUrl?.(frame.sequence)
|
||||
?? e47SemanticMaskUrl(activeSemantic.resultId, frame.sequence),
|
||||
prefetchSrcs: Array.from({ length: 12 }, (_, index) => index + 1)
|
||||
.map((offset) => frame.sequence + offset)
|
||||
.filter((sequence) => sequence < (metadata.timeline?.frameCount ?? 0))
|
||||
.map((sequence) => semantic.maskUrl?.(sequence)
|
||||
?? e47SemanticMaskUrl(semantic.resultId, sequence)),
|
||||
.map((sequence) => activeSemantic.maskUrl?.(sequence)
|
||||
?? e47SemanticMaskUrl(activeSemantic.resultId, sequence)),
|
||||
classes: semanticClasses,
|
||||
palette: semanticPalette,
|
||||
opacity: 0.9,
|
||||
ariaLabel: `${semantic.maskAriaLabel ?? "Semantic prediction"} frame ${frame.sequence + 1}`,
|
||||
ariaLabel: `${activeSemantic.maskAriaLabel ?? "Semantic prediction"} frame ${frame.sequence + 1}`,
|
||||
}
|
||||
: undefined;
|
||||
const accumulatedCameraPoints = cameraPointOverlay.overlay?.sequence === frame?.sequence
|
||||
@@ -692,7 +729,7 @@ export function M4ReplayThreatVisual({
|
||||
</div>
|
||||
);
|
||||
|
||||
const mediaLayerControls = semantic
|
||||
const mediaLayerControls = activeSemantic
|
||||
|| (showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery)
|
||||
|| (showReferenceMediaLayers && metadata.timeline?.cameraObstacleProjectionDelivery) ? (
|
||||
<div
|
||||
@@ -700,7 +737,7 @@ export function M4ReplayThreatVisual({
|
||||
role="group"
|
||||
aria-label="Слои камеры и видео"
|
||||
>
|
||||
{semantic ? (
|
||||
{activeSemantic ? (
|
||||
<Button
|
||||
size="compact"
|
||||
shape="pill"
|
||||
@@ -711,6 +748,20 @@ export function M4ReplayThreatVisual({
|
||||
SEMANTICS
|
||||
</Button>
|
||||
) : null}
|
||||
{availableSemanticLayers.length > 1 ? (
|
||||
<SegmentedControl
|
||||
value={selectedSemanticLayerId}
|
||||
items={availableSemanticLayers.map((layer, index) => ({
|
||||
value: layer.id ?? `${layer.resultId}:${index}`,
|
||||
label: layer.controlLabel ?? layer.label ?? `SEMANTIC ${index + 1}`,
|
||||
}))}
|
||||
label="Источник семантики"
|
||||
onChange={(value) => {
|
||||
setSelectedSemanticLayerId(value);
|
||||
setShowMediaSemantic(true);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{showReferenceMediaLayers && metadata.timeline?.cameraPointDelivery ? (
|
||||
<Button
|
||||
size="compact"
|
||||
@@ -1022,6 +1073,7 @@ export function M4ReplayThreatVisual({
|
||||
<div
|
||||
className="m4-replay-threat-visual__pane-toolbar"
|
||||
data-pane-toolbar="media"
|
||||
data-multi-semantic={availableSemanticLayers.length > 1 ? "true" : undefined}
|
||||
>
|
||||
{mediaLayerControls}
|
||||
{mediaModeControls}
|
||||
@@ -1238,8 +1290,8 @@ export function M4ReplayThreatVisual({
|
||||
return (
|
||||
<div className="l3-visual-audit m4-replay-threat-visual">
|
||||
<LaboratoryEvidenceViewer
|
||||
label={semantic
|
||||
? semantic.label ?? "Semantic diagnostic replay"
|
||||
label={activeSemantic
|
||||
? activeSemantic.label ?? "Semantic diagnostic replay"
|
||||
: `${evidenceLabel} recorded-realtime replay`}
|
||||
className="m4-replay-threat-evidence-viewer"
|
||||
mode={mediaMode ?? "none"}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
LaboratoryEvidence,
|
||||
LaboratoryResultSummary,
|
||||
LaboratorySummary,
|
||||
LaboratoryWorkTemplate,
|
||||
} from "../../components/laboratory/LaboratoryPresentation";
|
||||
import type { VegetationShadowResult } from "../../core/laboratory/vegetationShadow";
|
||||
import {
|
||||
M48MaskComparisonVisual,
|
||||
type M48MaskComparisonCase,
|
||||
} from "./M48FailureAtlasVisual";
|
||||
|
||||
function decimal(value: number, digits = 1): string {
|
||||
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
|
||||
}
|
||||
|
||||
const VEGETATION_LABELS: Readonly<Record<string, string>> = {
|
||||
high_grass: "Высокая трава",
|
||||
low_grass: "Низкая трава",
|
||||
bush: "Куст",
|
||||
tree_trunk: "Ствол дерева",
|
||||
tree_crown: "Крона дерева",
|
||||
hedge: "Живая изгородь",
|
||||
forest: "Лесная растительность",
|
||||
crops: "Посевы",
|
||||
};
|
||||
|
||||
function comparisonCases(result: VegetationShadowResult): readonly M48MaskComparisonCase[] {
|
||||
return result.validationCases.map((item) => {
|
||||
const focus = item.focus!;
|
||||
return {
|
||||
caseId: item.caseId,
|
||||
title: `${VEGETATION_LABELS[focus.className] ?? focus.className} · truth ${decimal(focus.truthFraction * 100, 1)}% кадра`,
|
||||
sourceUrl: item.assets.source,
|
||||
truthUrl: item.assets.truth,
|
||||
predictions: {
|
||||
ddrnet: item.assets.ddrnet,
|
||||
ppliteseg: item.assets.ppliteseg,
|
||||
},
|
||||
errors: {
|
||||
ddrnet: item.assets.ddrnet_error,
|
||||
ppliteseg: item.assets.ppliteseg_error,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function VegetationBenchmarkResultView({
|
||||
rigLabel,
|
||||
result,
|
||||
}: {
|
||||
rigLabel: string;
|
||||
result: VegetationShadowResult;
|
||||
}) {
|
||||
const selected = result.candidates.find(
|
||||
(candidate) => candidate.candidate === result.selectedCandidate,
|
||||
)!;
|
||||
const alternative = result.candidates.find(
|
||||
(candidate) => candidate.candidate !== result.selectedCandidate,
|
||||
)!;
|
||||
|
||||
return (
|
||||
<LaboratoryWorkTemplate
|
||||
summary={(
|
||||
<LaboratorySummary
|
||||
title="M4.8 · архивный benchmark растительности"
|
||||
description="Отдельный truth-backed контур GOOSE для сравнения готовых fine-64 весов. Он не является частью RAVNOVES00 realtime LAB и открывается автономно без Worker 006."
|
||||
status="ARCHIVE ANALYSIS · model qualification only · commands OFF"
|
||||
statusTone="warning"
|
||||
facts={[
|
||||
{ label: "Источник", value: "GOOSE validation · 962 размеченных кадра · 12 hard cases" },
|
||||
{ label: "Сравнение", value: "DDRNet-39 vs PPLiteSeg · official fine-64 weights" },
|
||||
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
|
||||
{ label: "Authority", value: `${rigLabel} · MODEL QUALIFICATION ONLY · commands OFF` },
|
||||
]}
|
||||
brief={{
|
||||
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
|
||||
approach: "Обе модели прогнаны на 962 кадрах, а 12 визуальных кейсов выбраны детерминированно по truth-поддержке восьми растительных классов. Viewer показывает source, ручной truth, prediction и error.",
|
||||
principalResult: `${selected.loadedModelName} лидирует по vegetation IoU: ${decimal(selected.vegetationMeanIouPercent, 2)}% против ${decimal(alternative.vegetationMeanIouPercent, 2)}%.`,
|
||||
limitation: "GOOSE — внешний размеченный домен. Результат выбирает стартовые веса, но не доказывает качество на fisheye RAVNOVES00 и не даёт navigation authority.",
|
||||
}}
|
||||
method={{
|
||||
completeness: "complete",
|
||||
executionClass: "ai-inference",
|
||||
pipelineId: "goose-fine64-ready-weights-benchmark-archive/v1",
|
||||
components: result.candidates.map((candidate) => ({
|
||||
kind: "model" as const,
|
||||
name: candidate.loadedModelName,
|
||||
version: candidate.candidate,
|
||||
role: candidate.candidate === result.selectedCandidate
|
||||
? "selected vegetation candidate"
|
||||
: "comparison candidate",
|
||||
identitySha256: candidate.checkpointSha256,
|
||||
})),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
evidence={(
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
|
||||
title="TRUTH — ручная разметка · PREDICTION — ответ модели · ERROR — расхождение"
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<M48MaskComparisonVisual
|
||||
cases={comparisonCases(result)}
|
||||
initialCandidate={result.selectedCandidate}
|
||||
/>
|
||||
</LaboratoryEvidence>
|
||||
)}
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title="DDRNet выбран как стартовый vegetation candidate"
|
||||
status={`${selected.loadedModelName} · перенос на ровер не доказан`}
|
||||
statusTone="warning"
|
||||
metrics={[
|
||||
{
|
||||
label: "GOOSE mIoU",
|
||||
value: `${decimal(selected.meanIouPercent, 2)}% / ${decimal(alternative.meanIouPercent, 2)}%`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · полный validation split`,
|
||||
},
|
||||
{
|
||||
label: "Vegetation IoU",
|
||||
value: `${decimal(selected.vegetationMeanIouPercent, 2)}% / ${decimal(alternative.vegetationMeanIouPercent, 2)}%`,
|
||||
hint: "grass/vegetation/bush/tree и родственные fine-64 labels",
|
||||
},
|
||||
{
|
||||
label: "Worker shadow p95",
|
||||
value: `${decimal(selected.shadowLatencyP95Ms, 2)} / ${decimal(alternative.shadowLatencyP95Ms, 2)} ms`,
|
||||
hint: "чистый inference · одна тяжёлая модель за раз",
|
||||
},
|
||||
{
|
||||
label: "Peak VRAM",
|
||||
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} / ${decimal(alternative.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · RTX 4090`,
|
||||
},
|
||||
]}
|
||||
conclusion={{
|
||||
proved: "Обе готовые fine-64 модели воспроизводимо запускаются; DDRNet лучше по aggregate vegetation IoU.",
|
||||
notProved: "Не доказаны accuracy на нашем fisheye, temporal stability, collision safety и физическое поведение ровера.",
|
||||
decision: "Хранить как архив квалификации весов. Проверку на RAVNOVES00 вести только в основной многослойной LAB.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -14,10 +14,6 @@ import {
|
||||
fetchM49TgsFullShadowResult,
|
||||
type M49TgsFullShadowResult,
|
||||
} from "../../core/laboratory/m49TgsFullShadow";
|
||||
import {
|
||||
M48MaskComparisonVisual,
|
||||
type M48MaskComparisonCase,
|
||||
} from "./M48FailureAtlasVisual";
|
||||
import { M4ReplayThreatVisual } from "./M4ReplayThreatVisual";
|
||||
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
|
||||
|
||||
@@ -25,37 +21,6 @@ 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,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function VegetationRouteEvidence({ result }: { result: VegetationShadowResult }) {
|
||||
const route = result.routeVideo!;
|
||||
const [tgs, setTgs] = useState<M49TgsFullShadowResult | null>(null);
|
||||
@@ -83,16 +48,14 @@ function VegetationRouteEvidence({ result }: { result: VegetationShadowResult })
|
||||
}, [route.baseM4ResultId, route.linkedTgsResultId]);
|
||||
|
||||
const semantic = {
|
||||
id: "vegetation",
|
||||
controlLabel: "ПРИРОДА · DDRNet",
|
||||
resultId: route.workerResultId,
|
||||
spatialResultId: null,
|
||||
taxonomy: route.taxonomy,
|
||||
maskUrl: (sequence: number) => vegetationVideoMaskUrl(result.resultId, sequence),
|
||||
label: route.viewKind === "coarse-material-policy-review"
|
||||
? "Coarse material evidence · recorded video"
|
||||
: "DDRNet vegetation prediction · recorded video",
|
||||
maskAriaLabel: route.viewKind === "coarse-material-policy-review"
|
||||
? "Coarse material policy evidence"
|
||||
: "DDRNet vegetation prediction",
|
||||
label: "DDRNet coarse vegetation material · recorded video",
|
||||
maskAriaLabel: "DDRNet vegetation material prediction",
|
||||
} as const;
|
||||
|
||||
if (route.linkedTgsResultId && tgs) {
|
||||
@@ -100,19 +63,23 @@ function VegetationRouteEvidence({ result }: { result: VegetationShadowResult })
|
||||
<M49TgsFullShadowEvidence
|
||||
result={tgs}
|
||||
semanticOverride={semantic}
|
||||
evidenceLabel="LAB V1 · MATERIAL + YOLOX + TGS"
|
||||
evidenceLabel="LAB V1 · EoMT + DDRNet + YOLOX + TGS"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (route.linkedTgsResultId && !tgsError) {
|
||||
return <div className="m4-replay-threat-visual__pane-status" role="status">Открываем sealed TGS и coarse material timeline…</div>;
|
||||
return (
|
||||
<div className="m4-replay-threat-visual__pane-status" role="status">
|
||||
Открываем sealed EoMT, TGS и coarse vegetation timeline…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<M4ReplayThreatVisual
|
||||
resultId={route.baseM4ResultId}
|
||||
evidenceLabel="LAB V1 · DDRNet"
|
||||
showReferenceMediaLayers={route.viewKind === "coarse-material-policy-review"}
|
||||
showReferenceMediaLayers
|
||||
showSpatialOverlaySummary={false}
|
||||
semantic={semantic}
|
||||
/>
|
||||
@@ -132,146 +99,117 @@ 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={result.routeVideo
|
||||
? result.routeVideo.viewKind === "coarse-material-policy-review"
|
||||
? "M4.8 сохраняет truth-backed сравнение моделей, а штатный M4.7 синхронно показывает coarse material evidence, frozen YOLOX vetoes и causal TGS на всей записи RAVNOVES00. Все слои запечатаны локально и открываются без Worker 006."
|
||||
: "M4.8 сохраняет truth-backed сравнение моделей, а штатный M4.7 viewer показывает фактический DDRNet prediction на всей записи RAVNOVES00. Все 4489 масок запечатаны локально и открываются без Worker 006."
|
||||
: "Штатный M4.8-инструмент сравнивает две готовые fine-64 модели на полном GOOSE validation split и на 12 truth-backed hard cases, выбранных только по наличию нужной растительности. Sealed evidence открывается локально без Worker 006."}
|
||||
status={result.routeVideo
|
||||
? result.routeVideo.viewKind === "coarse-material-policy-review"
|
||||
? "MULTILAYER POLICY REVIEW · commands OFF · route truth отсутствует"
|
||||
: "DDRNet full-video prediction ready · route truth отсутствует"
|
||||
: "Truth-backed model comparison · route transfer не принят"}
|
||||
title="LAB V1 · карта ровера · город + растительность"
|
||||
description="Один recorded-контур RAVNOVES00 синхронно показывает городской EoMT, природный DDRNet, frozen YOLOX detections и causal TGS. Семантические маски переключаются, чтобы их цвета не скрывали друг друга; геометрическое veto остаётся независимым."
|
||||
status={route
|
||||
? "MULTILAYER RECORDED REVIEW · commands OFF · route truth отсутствует"
|
||||
: "ROUTE EVIDENCE MISSING · commands OFF"}
|
||||
statusTone="warning"
|
||||
facts={[
|
||||
{ label: "Источник", value: "GOOSE validation · 962 размеченных кадра · 12 vegetation hard cases" },
|
||||
{ label: "Сравнение", value: "DDRNet-39 vs PPLiteSeg · official fine-64 weights" },
|
||||
{ label: "Кейсы", value: "трава · куст · ствол · крона · изгородь · лес · посевы" },
|
||||
...(result.routeVideo ? [{
|
||||
label: "Видео",
|
||||
value: result.routeVideo.viewKind === "coarse-material-policy-review"
|
||||
? "RAVNOVES00 · 4489/4489 coarse masks + YOLOX + TGS · exact sequence"
|
||||
: "RAVNOVES00 · 4489/4489 DDRNet masks · exact recorded sequence",
|
||||
}] : []),
|
||||
{ label: "Authority", value: `${rigLabel} · MODEL QUALIFICATION ONLY · commands OFF` },
|
||||
{ label: "Источник", value: "RAVNOVES00 · sensor.camera.right · 4489 recorded frames" },
|
||||
{ label: "Город", value: "EoMT Cityscapes · sealed E47 semantic archive" },
|
||||
{ label: "Растительность", value: "DDRNet-39 fine-64 → coarse mission-neutral materials" },
|
||||
{ label: "Safety", value: "YOLOX object boxes + causal TGS · semantic masks не снимают veto" },
|
||||
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
|
||||
]}
|
||||
brief={{
|
||||
question: "Какие готовые веса лучше различают проезжаемую траву, кусты и стволы на размеченных off-road кадрах?",
|
||||
approach: "Обе модели последовательно прогнаны в одном изолированном CUDA-runtime на 962 кадрах. 12 визуальных кейсов выбраны детерминированно по truth-поддержке восьми растительных классов; один M4.8 viewer показывает source, truth, prediction и material-error для выбранной модели.",
|
||||
principalResult: `${selected.loadedModelName} лидирует по vegetation IoU: ${decimal(selected.vegetationMeanIouPercent, 2)}% против ${decimal(alternative.vegetationMeanIouPercent, 2)}%. ${result.routeVideo?.viewKind === "coarse-material-policy-review" ? "Fine-64 prediction сведён к mission-neutral материалам; YOLOX и TGS сохраняют независимое veto." : result.routeVideo ? "Его фактическая temporal stability теперь видна на всех 4489 кадрах штатного recorded viewer." : "Ошибки по каждому типу проверяются в одном штатном инструменте."}`,
|
||||
limitation: "GOOSE — внешний размеченный домен; RAVNOVES00 — наш fisheye, но без ручной truth-разметки. Материалы — prediction, а не доказательство проходимости. TGS не проецируется в пиксели без отдельной принятой калибровки.",
|
||||
question: "Можно ли одновременно видеть городской и природный semantic stack, не теряя независимую геометрическую защиту?",
|
||||
approach: "EoMT и DDRNet сохранены как два независимых sealed слоя на одной M4 timeline. В штатном M4.7 viewer пользователь переключает только отображаемую маску; YOLOX и TGS остаются активными слоями evidence.",
|
||||
principalResult: route
|
||||
? "Оба semantic archive доступны в одном viewer. Это не пиксельный fusion и не единая новая модель: городской и природный ответы остаются раздельными."
|
||||
: "Route archive для этой immutable identity отсутствует.",
|
||||
limitation: "RAVNOVES00 не имеет ручной truth. DDRNet заметно прыгает между HIGH GRASS, WOODY и UNKNOWN; поэтому subtype нельзя подавать напрямую в planner. Отсутствие класса никогда не означает свободный путь.",
|
||||
}}
|
||||
method={{
|
||||
completeness: "complete",
|
||||
completeness: route ? "complete" : "legacy-partial",
|
||||
executionClass: "ai-inference",
|
||||
pipelineId: "goose-fine64-ready-weights-to-ravnoves-policy-shadow/v1",
|
||||
components: result.candidates.map((candidate) => ({
|
||||
kind: "model" as const,
|
||||
name: candidate.loadedModelName,
|
||||
version: candidate.candidate,
|
||||
role: candidate.candidate === result.selectedCandidate ? "selected policy provider" : "comparison candidate",
|
||||
identitySha256: candidate.checkpointSha256,
|
||||
})),
|
||||
pipelineId: "ravnoves-eomt-ddrnet-yolox-causal-tgs-recorded-review/v1",
|
||||
components: [
|
||||
{
|
||||
kind: "model",
|
||||
name: "EoMT Cityscapes semantic",
|
||||
version: "sealed E47 archive",
|
||||
role: "urban semantic review",
|
||||
identitySha256: null,
|
||||
},
|
||||
{
|
||||
kind: "model",
|
||||
name: selected.loadedModelName,
|
||||
version: selected.candidate,
|
||||
role: "vegetation material candidate",
|
||||
identitySha256: selected.checkpointSha256,
|
||||
},
|
||||
{
|
||||
kind: "algorithm",
|
||||
name: "Frozen YOLOX + causal TGS",
|
||||
version: "linked M4/M4.9 archives",
|
||||
role: "independent object and geometry veto",
|
||||
identitySha256: null,
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
evidence={(
|
||||
<>
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.8 · GOOSE VEGETATION HARD CASES"
|
||||
title="ERROR: красный — пропуск · жёлтый — лишнее · фиолетовый — перепутан тип · зелёный — совпадение"
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<M48MaskComparisonVisual
|
||||
cases={comparisonCases(result)}
|
||||
initialCandidate={result.selectedCandidate}
|
||||
/>
|
||||
</LaboratoryEvidence>
|
||||
{result.routeVideo ? (
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
|
||||
title={result.routeVideo.viewKind === "coarse-material-policy-review"
|
||||
? "COARSE MATERIAL + YOLOX VETO + CAUSAL TGS · 4489/4489 · TRUTH отсутствует"
|
||||
: "DDRNet PREDICTION · 4489/4489 кадров · TRUTH для этой записи отсутствует"}
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<VegetationRouteEvidence result={result} />
|
||||
</LaboratoryEvidence>
|
||||
) : null}
|
||||
</>
|
||||
evidence={route ? (
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
|
||||
title="EoMT CITY / DDRNet VEGETATION + YOLOX + CAUSAL TGS · 4489/4489 · TRUTH отсутствует"
|
||||
kind="diagnostic-model"
|
||||
resizable
|
||||
>
|
||||
<VegetationRouteEvidence result={result} />
|
||||
</LaboratoryEvidence>
|
||||
) : (
|
||||
<LaboratoryEvidence
|
||||
eyebrow="M4.7 · RAVNOVES00 FULL VIDEO"
|
||||
title="ROUTE ARCHIVE отсутствует"
|
||||
kind="diagnostic-model"
|
||||
>
|
||||
<div className="m4-replay-threat-visual__pane-status" role="alert">
|
||||
Для этой immutable identity нет полного route video evidence.
|
||||
</div>
|
||||
</LaboratoryEvidence>
|
||||
)}
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title={result.routeVideo?.viewKind === "coarse-material-policy-review"
|
||||
? "Слои собраны для визуального policy review; управление не авторизовано"
|
||||
: "DDRNet — стартовые веса; перенос на ровер ещё не доказан"}
|
||||
status={result.routeVideo?.viewKind === "coarse-material-policy-review"
|
||||
? "Materials are advisory · YOLOX/TGS veto cannot be cleared"
|
||||
: `${selected.loadedModelName} выбран только как vegetation candidate`}
|
||||
title="Многослойный visual review собран; управление не авторизовано"
|
||||
status="Semantics advisory · YOLOX/TGS veto cannot be cleared"
|
||||
statusTone="warning"
|
||||
metrics={[
|
||||
{
|
||||
label: "GOOSE mIoU",
|
||||
value: `${decimal(selected.meanIouPercent, 2)}% / ${decimal(alternative.meanIouPercent, 2)}%`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · полный validation split`,
|
||||
label: "Route masks",
|
||||
value: route ? `${route.frameCount}/${route.frameCount}` : "0/4489",
|
||||
hint: "sealed local playback · Worker для открытия не нужен",
|
||||
},
|
||||
{
|
||||
label: "Vegetation IoU",
|
||||
value: `${decimal(selected.vegetationMeanIouPercent, 2)}% / ${decimal(alternative.vegetationMeanIouPercent, 2)}%`,
|
||||
hint: "агрегация классов grass/vegetation/bush/tree и родственных fine-64 labels",
|
||||
label: "Semantic sources",
|
||||
value: route ? "2 independent layers" : "0",
|
||||
hint: "EoMT CITY / DDRNet VEGETATION · display switches, evidence does not fuse",
|
||||
},
|
||||
{
|
||||
label: "Worker shadow p95",
|
||||
value: `${decimal(selected.shadowLatencyP95Ms, 2)} / ${decimal(alternative.shadowLatencyP95Ms, 2)} ms`,
|
||||
hint: "чистый inference · одна тяжёлая модель за раз",
|
||||
label: "Vegetation worker p95",
|
||||
value: `${decimal(selected.shadowLatencyP95Ms, 2)} ms`,
|
||||
hint: "изолированный DDRNet inference; не совместный realtime stack",
|
||||
},
|
||||
{
|
||||
label: "Cold prewarm",
|
||||
value: `${decimal(selected.shadowPrewarmLatencyMs, 1)} / ${decimal(alternative.shadowPrewarmLatencyMs, 1)} ms`,
|
||||
hint: "один явный inference до допуска кадров; исключён из steady-state p95",
|
||||
label: "Vegetation peak VRAM",
|
||||
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
|
||||
hint: "DDRNet candidate на Worker 006",
|
||||
},
|
||||
{
|
||||
label: "Worker throughput",
|
||||
value: `${decimal(selected.shadowThroughputFps, 1)} / ${decimal(alternative.shadowThroughputFps, 1)} FPS`,
|
||||
hint: "изолированный Worker 006 · не realtime graph целиком",
|
||||
},
|
||||
{
|
||||
label: "Peak VRAM",
|
||||
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} / ${decimal(alternative.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
|
||||
hint: `${selected.candidate} / ${alternative.candidate} · RTX 4090`,
|
||||
},
|
||||
{
|
||||
label: "Hard-case evidence",
|
||||
value: "12 truth-backed cases",
|
||||
hint: "8 vegetation strata · Worker для открытия не требуется",
|
||||
},
|
||||
...(result.routeVideo ? [{
|
||||
label: "Route video",
|
||||
value: "4489/4489 masks",
|
||||
hint: result.routeVideo.viewKind === "coarse-material-policy-review"
|
||||
? "9 coarse states · YOLOX + causal TGS · Worker-independent playback"
|
||||
: "DDRNet prediction · exact sequence · Worker-independent playback",
|
||||
}] : []),
|
||||
]}
|
||||
conclusion={{
|
||||
proved: "Обе официальные fine-64 модели воспроизводимо запускаются на Worker 006; DDRNet лучше по aggregate vegetation IoU. Truth-backed hard cases прямо показывают траву, кусты и стволы, а не случайные автомобили и здания.",
|
||||
notProved: "Не доказаны accuracy на нашем fisheye-домене, папоротник как отдельный материал, collision safety и physical-live поведение ровера. Видео позволяет увидеть temporal stability, но без truth не превращает её в метрику качества.",
|
||||
decision: result.routeVideo?.viewKind === "coarse-material-policy-review"
|
||||
? "На одном M4.7 проверить ложные LOW GRASS/HIGH GRASS кандидаты против YOLOX и TGS. До truth-кейсов и integrated load этот слой не подключать к planner/actuation."
|
||||
: "Смотреть полный prediction на видео и собирать конкретные temporal/domain failure cases. DDRNet остаётся diagnostic candidate; LiDAR/TGS fail-closed геометрию не ослаблять.",
|
||||
proved: "На одной recorded timeline доступны городской EoMT, природный DDRNet, YOLOX detections и causal TGS; LAB автономна от Worker.",
|
||||
notProved: "Не доказаны совместный live-runtime EoMT+DDRNet, truth accuracy на fisheye, стабильные vegetation subtypes и безопасное управление ровером.",
|
||||
decision: "Использовать маски только для диагностики. Следующий qualification gate — motion-aware temporal vegetation fusion и отдельный совместный realtime load test; до него planner/actuation остаются OFF.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,7 @@ export type LaboratoryProfileId =
|
||||
| "rig-camera-local-surface-v1"
|
||||
| "rig-track-geometry-temporal-v1"
|
||||
| "rig-ravnoves-perception-gate-v1"
|
||||
| "rig-goose-vegetation-benchmark-v1"
|
||||
| "rig-pointpillars-transfer-v1"
|
||||
| "rig-right-yolox-lidar-range-v1"
|
||||
| "rig-nvidia-ready-stack-v1"
|
||||
@@ -63,12 +64,19 @@ interface KnownWorkDefinition {
|
||||
const rig = (rigLabel: string): string => rigLabel.trim() || "Сенсорный риг";
|
||||
|
||||
const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`>, KnownWorkDefinition>> = {
|
||||
"lab-v1-vegetation-benchmark": {
|
||||
profileId: "rig-goose-vegetation-benchmark-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} · GOOSE vegetation archive`,
|
||||
experimentId: "lab-v1-vegetation-benchmark-archive",
|
||||
experimentName: "DDRNet vs PPLiteSeg · truth-backed archival comparison",
|
||||
variantName: "M4.8 · GOOSE truth · архивный анализ моделей",
|
||||
},
|
||||
"lab-v1-vegetation-shadow": {
|
||||
profileId: "rig-ravnoves-perception-gate-v1",
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} · GOOSE vegetation qualification`,
|
||||
profileName: (rigLabel) => `${rig(rigLabel)} · RAVNOVES00 rover perception gate`,
|
||||
experimentId: "lab-v1-vegetation-mission-policy",
|
||||
experimentName: "DDRNet vs PPLiteSeg · truth-backed vegetation hard cases",
|
||||
variantName: "LAB V1 · готовые vegetation weights · GOOSE truth",
|
||||
experimentName: "RAVNOVES00 · city + vegetation + TGS review",
|
||||
variantName: "LAB V1 · EoMT + DDRNet + YOLOX + TGS · commands OFF",
|
||||
},
|
||||
"m48-object-centric-quality": {
|
||||
profileId: "rig-dual-evidence-virtual-corridor-v1",
|
||||
|
||||
@@ -18,6 +18,7 @@ function mergeResults(
|
||||
next: AdvancedLaboratoryResults,
|
||||
): AdvancedLaboratoryResults {
|
||||
return {
|
||||
vegetationBenchmark: next.vegetationBenchmark ?? current.vegetationBenchmark,
|
||||
vegetationShadow: next.vegetationShadow ?? current.vegetationShadow,
|
||||
m47Graph: next.m47Graph ?? current.m47Graph,
|
||||
m48: next.m48 ?? current.m48,
|
||||
@@ -122,6 +123,7 @@ export function useAdvancedLaboratoryCatalog({
|
||||
const indexedResultId = index.find((item) => item.workId === selectedWorkId)?.resultId;
|
||||
if (
|
||||
[
|
||||
"lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow",
|
||||
"m47-reference-graph-shadow",
|
||||
"m48-object-centric-quality",
|
||||
|
||||
Reference in New Issue
Block a user