refactor(lab): перевести RAV004 на канонический Rerun pipeline

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 12:59:16 +03:00
parent e9ffb829c9
commit f5ee42751d
30 changed files with 1425 additions and 288 deletions
@@ -0,0 +1,260 @@
import { useEffect, useMemo, useState } from "react";
import { Button, Icon, SegmentedControl } from "@nodedc/ui-react";
import { ObservationTimeline } from "../../components/ObservationTimeline";
import {
RerunViewport,
type RecordedPerceptionLoadState,
type RerunPlaybackController,
type RerunPlaybackState,
} from "../../components/RerunViewport";
import {
CanonicalRecordedLabReplay,
useCanonicalRecordedLabReplayState,
} from "../../components/laboratory/CanonicalRecordedLabReplay";
import type { VegetationFullRouteReview } from "../../core/laboratory/vegetationShadow";
import { recordedSessionRerunProfile } from "../../core/observation/viewerProfile";
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
import { defaultSceneSettings } from "../../sceneSettings";
type MediaMode = "video" | "camera";
type SpatialMode = "3d" | "plan";
type SpatialLayer = "source" | "local" | "tgs" | "semantic";
type SemanticLayer = "city" | "vegetation";
const EMPTY_PERCEPTION_LOAD: RecordedPerceptionLoadState = {
phase: "idle",
receivedBytes: 0,
totalBytes: null,
progress: null,
message: "",
};
export function CanonicalVegetationRerunReplay({
resultId,
review,
}: {
resultId: string;
review: VegetationFullRouteReview;
}) {
const {
mediaMode,
spatialMode,
splitPrimarySize,
splitOrientation,
expanded,
onMediaModeChange,
onSpatialModeChange,
onSplitPrimarySizeChange,
onExpandedChange,
} = useCanonicalRecordedLabReplayState<MediaMode, SpatialMode>({
initialMediaMode: "video",
initialSpatialMode: "3d",
});
const [semanticLayer, setSemanticLayer] = useState<SemanticLayer>("vegetation");
const [showSemantics, setShowSemantics] = useState(true);
const [spatialLayer, setSpatialLayer] = useState<SpatialLayer>("source");
const [viewResetGeneration, setViewResetGeneration] = useState<0 | 1>(0);
const [playback, setPlayback] = useState<RerunPlaybackState | null>(null);
const [playbackController, setPlaybackController] =
useState<RerunPlaybackController | null>(null);
const [perceptionLoad, setPerceptionLoad] =
useState<RecordedPerceptionLoadState>(EMPTY_PERCEPTION_LOAD);
const [launch, setLaunch] = useState<Awaited<ReturnType<typeof resolveObservationSessionReplay>> | null>(null);
const [launchError, setLaunchError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLaunch(null);
setLaunchError(null);
void resolveObservationSessionReplay(review.sessionId, {
signal: controller.signal,
maximumWaitMs: 30 * 60 * 1000,
onUpdate: () => undefined,
}).then((value) => {
if (!controller.signal.aborted) setLaunch(value);
}).catch((caught: unknown) => {
if (!controller.signal.aborted) {
setLaunchError(
caught instanceof Error ? caught.message : "Каноническая запись RAV004 недоступна.",
);
}
});
return () => controller.abort();
}, [review.sessionId]);
const splitView = mediaMode !== null && spatialMode !== null;
const sceneSettings = useMemo(() => ({
...defaultSceneSettings,
accumulationSeconds: spatialLayer === "local" ? 5 : 0,
showPoints: spatialMode !== null,
showTrajectory: spatialMode !== null,
showGrid: spatialMode !== null,
pointSize: 2.2,
}), [spatialLayer, spatialMode]);
const profile = launch ? recordedSessionRerunProfile({
sourceUrl: launch.sourceUrl,
artifact: {
sourceUrl: launch.sourceUrl,
viewerSourceUrl: launch.viewerSourceUrl,
byteLength: launch.byteLength,
sha256: launch.sha256,
},
autoplayWhenReady: false,
presentationGate: "ready",
expectedTimelineStartSeconds: launch.timelineStartSeconds,
expectedTimelineEndSeconds: launch.timelineEndSeconds,
initialPlaybackStartSeconds: review.timelineStartSeconds,
view: mediaMode !== null ? "perception" : "spatial",
viewResetGeneration,
followTrajectory: false,
perceptionSourceUrl:
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}` +
"/canonical-overlay.rrd",
semanticLayer,
unifiedPerception: splitView,
planView: spatialMode === "plan",
perceptionLayers: {
enabled: mediaMode !== null,
detections2d: mediaMode === "video",
segmentation: mediaMode === "video" && showSemantics,
cuboids3d: false,
},
perceptionRetryGeneration: 0,
lockPerceptionCameraInteraction: false,
}) : null;
const mediaLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Слои камеры и видео"
>
<Button
size="compact"
shape="pill"
variant={showSemantics ? "primary" : "secondary"}
aria-pressed={showSemantics}
onClick={() => setShowSemantics((visible) => !visible)}
>
SEMANTICS
</Button>
<SegmentedControl
value={semanticLayer}
items={[
{ value: "city", label: "ГОРОД · EoMT" },
{ value: "vegetation", label: "ПРИРОДА · DDRNet" },
]}
label="Источник семантики"
onChange={(value) => {
setSemanticLayer(value);
setShowSemantics(true);
}}
/>
</div>
);
const spatialLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Пространственные слои RAV004"
>
<SegmentedControl
value={spatialLayer}
items={[
{ value: "source", label: "SOURCE POINTS" },
{ value: "local", label: "LOCAL SLAM" },
{ value: "tgs", label: "TGS COSTMAP", disabled: true },
{ value: "semantic", label: "SEMANTICS", disabled: true },
]}
label="Пространственные слои"
onChange={setSpatialLayer}
/>
</div>
);
const resetSpatialView = (
<Button
size="compact"
variant="ghost"
icon={<Icon name="refresh" size={14} />}
aria-label="Сбросить положение 3D камеры"
title="Сбросить положение 3D камеры"
onClick={() => setViewResetGeneration((value) => value === 0 ? 1 : 0)}
>
</Button>
);
const transport = playback && playbackController ? (
<ObservationTimeline
className="m4-replay-threat-visual__timeline"
active
sourceCount={3}
mode="recorded"
seekable
synchronization="shared-clock"
rangeNs={playback.rangeNs}
currentNs={playback.currentNs}
playing={playback.playing}
onSeek={playbackController.seek}
onPlayingChange={playbackController.setPlaying}
showJumpToEnd={false}
/>
) : undefined;
const overlayMessage = launchError
?? (perceptionLoad.phase === "loading" ? perceptionLoad.message : null)
?? (perceptionLoad.phase === "error" ? perceptionLoad.message : null);
return (
<CanonicalRecordedLabReplay
label="RAVNOVES004TREE · upstream Rerun recorded replay"
mediaMode={mediaMode ?? "none"}
mediaModes={[
{ value: "video", label: "VIDEO" },
{ value: "camera", label: "CAMERA" },
]}
spatialMode={spatialMode ?? "none"}
spatialModes={[
{ value: "3d", label: "3D" },
{ value: "plan", label: "PLAN" },
]}
expanded={expanded}
splitPrimarySize={splitPrimarySize}
splitOrientation={splitOrientation}
mediaAriaLabel={mediaMode === "camera" ? "Камера" : "Видео и семантика"}
spatialAriaLabel={spatialMode === "plan" ? "Вид сверху" : "Трёхмерная сцена"}
mediaLayerControls={mediaLayerControls}
spatialLayerControls={spatialLayerControls}
spatialLeadingControl={resetSpatialView}
mediaMultiLayer
unifiedContent={profile ? (
<RerunViewport
profile={profile}
sceneSettings={sceneSettings}
onPlaybackChange={setPlayback}
onPlaybackControllerChange={setPlaybackController}
onPerceptionLoadChange={setPerceptionLoad}
/>
) : (
<div className="l3-visual-audit__state" role={launchError ? "alert" : "status"}>
{launchError ?? "Открываем каноническую запись RAV004…"}
</div>
)}
emptyMessage="Выберите VIDEO/CAMERA или 3D/PLAN. Общий Rerun-clock останется на месте."
deckOverlays={overlayMessage ? (
<div className="m4-replay-threat-visual__buffering" role="status">
{perceptionLoad.phase === "loading" ? (
<span className="busy-indicator" aria-hidden="true" />
) : (
<Icon name="alert" size={14} />
)}
<span>{overlayMessage}</span>
</div>
) : undefined}
transport={transport}
onMediaModeChange={onMediaModeChange}
onSpatialModeChange={onSpatialModeChange}
onExpandedChange={onExpandedChange}
onSplitPrimarySizeChange={onSplitPrimarySizeChange}
/>
);
}
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import {
LaboratoryEvidence,
@@ -7,7 +7,6 @@ import {
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import {
vegetationFullRouteMaskUrl,
vegetationVideoMaskUrl,
type VegetationFullRouteReview,
type VegetationShadowResult,
@@ -17,13 +16,7 @@ import {
type M49TgsFullShadowResult,
} from "../../core/laboratory/m49TgsFullShadow";
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
import {
M4ReplayThreatVisual,
type M4ReplayClassifiedSpatialLayer,
type M4ReplayThreatSemanticLayer,
} from "./M4ReplayThreatVisual";
const VEGETATION_TIMELINE_ENDPOINT = "/api/v1/laboratory/vegetation-shadow";
import { CanonicalVegetationRerunReplay } from "./CanonicalVegetationRerunReplay";
function decimal(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
@@ -36,56 +29,7 @@ function FullRouteReviewEvidence({
resultId: string;
review: VegetationFullRouteReview;
}) {
const semanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(() => ([
{
id: "city",
controlLabel: "ГОРОД · EoMT",
resultId,
spatialResultId: null,
taxonomy: review.city.taxonomy,
maskUrl: (sequence) => vegetationFullRouteMaskUrl(resultId, "city", sequence),
label: review.city.name,
maskAriaLabel: "EoMT city semantic prediction",
},
{
id: "vegetation",
controlLabel: "ПРИРОДА · DDRNet",
resultId,
spatialResultId: null,
taxonomy: review.vegetation.taxonomy,
maskUrl: (sequence) => vegetationFullRouteMaskUrl(resultId, "vegetation", sequence),
label: review.vegetation.name,
maskAriaLabel: "DDRNet nature semantic prediction",
},
]), [resultId, review.city, review.vegetation]);
const sealedSpatialGap = useMemo<M4ReplayClassifiedSpatialLayer>(() => ({
label: "RAVNOVES004TREE",
pointLayerLabel: "SOURCE POINTS",
cellLayerLabel: "TGS COSTMAP",
cellLayerAvailable: false,
expectedAtSequence: false,
frame: null,
loading: false,
error: null,
replacePointCloud: false,
}), []);
return (
<M4ReplayThreatVisual
resultId={resultId}
timelineEndpointRoot={VEGETATION_TIMELINE_ENDPOINT}
semanticLayers={semanticLayers}
initialSemanticLayerId="vegetation"
initialSpatialMode="3d"
classifiedSpatialLayer={sealedSpatialGap}
evidenceLabel="RAVNOVES004TREE"
playbackTransport="segmented"
spatialPlaybackTransport="sealed-binary"
recoverTimestampStalls
showReferenceMediaLayers
showSpatialOverlaySummary
/>
);
return <CanonicalVegetationRerunReplay resultId={resultId} review={review} />;
}
function FullRouteReviewResult({