feat(perception): add DDRNet full-video vegetation replay
This commit is contained in:
@@ -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 геометрию не ослаблять.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user