feat(perception): add mixed-route vegetation review

This commit is contained in:
DCCONSTRUCTIONS
2026-08-28 23:13:48 +03:00
parent e10b96b546
commit af1e530162
12 changed files with 2332 additions and 17 deletions
@@ -1,5 +1,7 @@
import { useEffect, useState } from "react";
import { Icon, IconButton, StatusBadge } from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import {
LaboratoryEvidence,
LaboratoryResultSummary,
@@ -8,6 +10,7 @@ import {
} from "../../components/laboratory/LaboratoryPresentation";
import {
vegetationVideoMaskUrl,
type VegetationMixedRouteReview,
type VegetationShadowResult,
} from "../../core/laboratory/vegetationShadow";
import {
@@ -21,6 +24,128 @@ function decimal(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
const MIXED_ROUTE_MODES = [
{ value: "source", label: "SOURCE" },
{ value: "city", label: "ГОРОД · EoMT" },
{ value: "vegetation", label: "ПРИРОДА · DDRNet" },
{ value: "tgs", label: "TGS" },
] as const;
function MixedRouteReviewEvidence({ review }: { review: VegetationMixedRouteReview }) {
const [index, setIndex] = useState(0);
const [mode, setMode] = useState<typeof MIXED_ROUTE_MODES[number]["value"]>("vegetation");
const [expanded, setExpanded] = useState(false);
const item = review.cases[index]!;
return (
<LaboratoryEvidenceViewer
label="RAVNOVES004TREE mixed route review"
className="m48-atlas-visual"
mode={mode}
modes={MIXED_ROUTE_MODES}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
chromeLayout="stacked"
actions={(
<>
<IconButton label="Предыдущая сцена" onClick={() => setIndex((index - 1 + review.cases.length) % review.cases.length)}>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton label="Следующая сцена" onClick={() => setIndex((index + 1) % review.cases.length)}>
<Icon name="chevron-right" size={16} />
</IconButton>
</>
)}
overlay={(
<div className="m48-atlas-visual__case">
<StatusBadge tone={item.phase === "urban" ? "accent" : item.phase === "transition" ? "warning" : "neutral"}>
{item.phase.toUpperCase()} · {index + 1}/{review.cases.length}
</StatusBadge>
<strong>sequence {item.sourceSequence} · +{decimal(item.sessionSeconds, 2)} s</strong>
<small>
TGS: {item.tgs.groundCells} ground · {item.tgs.occupiedCells} occupied · {item.tgs.unobservedCells} unobserved
</small>
</div>
)}
>
<div className="recorded-evidence-image-scene">
<img src={item.assets[mode]} alt="" draggable={false} />
</div>
</LaboratoryEvidenceViewer>
);
}
function MixedRouteReviewResult({
rigLabel,
review,
}: {
rigLabel: string;
review: VegetationMixedRouteReview;
}) {
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB V1 · RAVNOVES004TREE · село → город"
description="Существующий LAB-шаблон показывает 10 синхронных camera/LiDAR сцен одной записи. EoMT и DDRNet остаются независимыми слоями; TGS показывает отдельную геометрию и не может быть очищен семантической маской."
status="BOUNDED RECORDED REVIEW · truth отсутствует · commands OFF"
statusTone="warning"
facts={[
{ label: "Источник", value: `${review.sourceId} · ${review.frameCount} camera/LiDAR islands` },
{ label: "Переход", value: "5 rural · 1 transition · 4 urban" },
{ label: "Слои", value: "SOURCE · EoMT CITY · DDRNet VEGETATION · causal TGS" },
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
]}
brief={{
question: "Сохраняются ли городская семантика, растительность и геометрия при переходе из сельской среды в город?",
approach: "Выбраны десять соседних с исходными сцен camera-кадров, каждый синхронизирован с LiDAR в пределах 100 мс. Все три вычислительных слоя прогнаны на Worker 006 и запечатаны локально.",
principalResult: "Все 10 сцен обработаны EoMT, DDRNet и causal TGS. Слои можно переключать без наложения цветов и без зависимости LAB от воркера.",
limitation: "Это bounded islands без ручной truth. DDRNet шумит по подтипам растительности; TGS не доказывает обнаружение кювета или отрицательного препятствия.",
}}
method={{
completeness: "complete",
executionClass: "ai-inference",
pipelineId: "ravnoves004tree-eomt-ddrnet-causal-tgs-review/v1",
components: [
{ kind: "model", name: review.models.city.name, version: "sealed Worker run", role: "urban semantic review", identitySha256: null },
{ kind: "model", name: review.models.vegetation.name, version: "GOOSE DDRNet-39", role: "vegetation semantic review", identitySha256: null },
{ kind: "algorithm", name: review.models.tgs.name, version: "TRAVEL compatibility runner", role: "independent local geometry", identitySha256: null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.7 TEMPLATE · RAVNOVES004TREE"
title="SOURCE / ГОРОД / ПРИРОДА / TGS · 10/10 · TRUTH отсутствует"
kind="diagnostic-model"
resizable
>
<MixedRouteReviewEvidence review={review} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Переход село → город воспроизведён; safety gate не закрыт"
status="Review ready · navigation/actuation OFF"
statusTone="warning"
metrics={[
{ label: "Aligned scenes", value: "10/10", hint: "camera + LiDAR + pose · автономный archive" },
{ label: "EoMT end-to-end p95", value: `${decimal(review.models.city.endToEndP95Ms, 2)} ms`, hint: `${decimal(review.models.city.inferenceFps, 2)} fps в изолированном прогоне` },
{ label: "DDRNet inference p95", value: `${decimal(review.models.vegetation.latencyP95Ms, 2)} ms`, hint: "candidate review · не совместный realtime stack" },
{ label: "TGS p95", value: `${decimal(review.models.tgs.latencyP95Ms, 2)} ms`, hint: `${review.models.tgs.cellSizeM} m cells · ${review.models.tgs.radiusM} m radius` },
]}
conclusion={{
proved: "Оба semantic слоя и causal TGS воспроизводимо работают на сельской, переходной и городской части новой записи.",
notProved: "Не доказаны accuracy без truth, временная стабильность по всему видео, детект кюветов и безопасное совместное realtime-управление ровером.",
decision: "Оставить navigation/actuation OFF. Следующий короткий gate — непрерывный realtime-load двух моделей плюс независимый person/vehicle STOP; кюветы проверять отдельной записью.",
}}
/>
)}
/>
);
}
function VegetationRouteEvidence({ result }: { result: VegetationShadowResult }) {
const route = result.routeVideo!;
const [tgs, setTgs] = useState<M49TgsFullShadowResult | null>(null);
@@ -99,6 +224,9 @@ export function VegetationShadowResultView({
rigLabel: string;
result: VegetationShadowResult;
}) {
if (result.routeReview) {
return <MixedRouteReviewResult rigLabel={rigLabel} review={result.routeReview} />;
}
const route = result.routeVideo;
const selected = result.candidates.find(
(candidate) => candidate.candidate === result.selectedCandidate,
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState, type ReactNode } from "react";
import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
import type { L34RightYoloxTruthIslandResult } from "../../../core/laboratory/l34RightYoloxTruthIsland";
import type { L34DResult } from "../../../core/laboratory/l34dCumulativePostprocessing";
@@ -28,19 +28,19 @@ export function useL34AnnotationCapability({
}): ReactNode {
const [open, setOpen] = useState(false);
const openWorkspace = useCallback(() => setOpen(true), []);
const available = selectedWorkId === "l34-right-yolox-truth-island-freeze"
&& l34Result
? { resultId: l34Result.resultId, workflow: "assisted-candidate" as const }
: selectedWorkId === "e46-detector-truth-island" && e46Result
? { resultId: e46Result.resultId, workflow: "independent-blind" as const }
: selectedWorkId === "e46a-ai-engineering-preannotation" && e46aResult
? { resultId: e46aResult.resultId, workflow: "engineering-preannotation" as const }
: selectedWorkId === "l34d-cumulative-postprocessing-candidate"
&& l34dResult
? { resultId: l34dResult.resultId, workflow: "prediction-hidden" as const }
: selectedWorkId === "l34e-self-review-diagnostic" && l34eResult
? { resultId: l34eResult.resultId, workflow: "adjudication" as const }
: null;
const available = useMemo(() => (
selectedWorkId === "l34-right-yolox-truth-island-freeze" && l34Result
? { resultId: l34Result.resultId, workflow: "assisted-candidate" as const }
: selectedWorkId === "e46-detector-truth-island" && e46Result
? { resultId: e46Result.resultId, workflow: "independent-blind" as const }
: selectedWorkId === "e46a-ai-engineering-preannotation" && e46aResult
? { resultId: e46aResult.resultId, workflow: "engineering-preannotation" as const }
: selectedWorkId === "l34d-cumulative-postprocessing-candidate" && l34dResult
? { resultId: l34dResult.resultId, workflow: "prediction-hidden" as const }
: selectedWorkId === "l34e-self-review-diagnostic" && l34eResult
? { resultId: l34eResult.resultId, workflow: "adjudication" as const }
: null
), [e46Result, e46aResult, l34Result, l34dResult, l34eResult, selectedWorkId]);
useEffect(() => {
if (!available) {