feat(perception): integrate calibrated operator pipeline
Add calibrated K1 projection, recorded and near-live perception qualification, unified Rerun operator layers, bounded replay admission, audited viewer controls, worker experiments, and lab evidence.
This commit is contained in:
@@ -45,7 +45,6 @@ import {
|
||||
type RerunPlaybackState,
|
||||
type RerunSelection,
|
||||
type RerunViewportStatus,
|
||||
type RecordedRerunView,
|
||||
} from "../components/RerunViewport";
|
||||
import {
|
||||
capabilityStatusLabel,
|
||||
@@ -124,6 +123,16 @@ export interface WorkspaceRendererProps {
|
||||
accumulationSeconds: number;
|
||||
onAccumulationChange: (value: number) => void;
|
||||
onAccumulationCommit: () => void;
|
||||
livePerceptionLayers: {
|
||||
detections2d: boolean;
|
||||
segmentation: boolean;
|
||||
cuboids3d: boolean;
|
||||
};
|
||||
onLivePerceptionLayersChange: (next: {
|
||||
detections2d: boolean;
|
||||
segmentation: boolean;
|
||||
cuboids3d: boolean;
|
||||
}) => void;
|
||||
observationLayout: ObservationLayoutController;
|
||||
navigation: WorkspaceNavigation;
|
||||
spatialControls: {
|
||||
@@ -286,6 +295,8 @@ function SpatialWorkspace({
|
||||
accumulationSeconds,
|
||||
onAccumulationChange,
|
||||
onAccumulationCommit,
|
||||
livePerceptionLayers,
|
||||
onLivePerceptionLayersChange,
|
||||
observationLayout,
|
||||
navigation,
|
||||
spatialControls,
|
||||
@@ -295,8 +306,13 @@ function SpatialWorkspace({
|
||||
const [selection, setSelection] = useState<RerunSelection | null>(null);
|
||||
const [playbackState, setPlaybackState] = useState<RerunPlaybackState | null>(null);
|
||||
const [playbackController, setPlaybackController] = useState<RerunPlaybackController | null>(null);
|
||||
const [recordedRerunView, setRecordedRerunView] = useState<RecordedRerunView>("spatial");
|
||||
const [perceptionAvailable, setPerceptionAvailable] = useState(false);
|
||||
const [recordedViewResetGeneration, setRecordedViewResetGeneration] = useState<0 | 1>(0);
|
||||
const [perceptionAvailability, setPerceptionAvailability] = useState<
|
||||
"unknown" | "available" | "unavailable"
|
||||
>("unknown");
|
||||
const [showDetections2d, setShowDetections2d] = useState(false);
|
||||
const [showSegmentation, setShowSegmentation] = useState(false);
|
||||
const [showCuboids3d, setShowCuboids3d] = useState(false);
|
||||
const recordedSource = state?.sourceMode === "replay" || /\.rrd(?:$|[?#])/i.test(sourceUrl);
|
||||
const recordedSessionGate: RecordedAdmissionPhase = recordedSource
|
||||
? recordedSessionAdmission?.phase ?? "loading"
|
||||
@@ -309,6 +325,8 @@ function SpatialWorkspace({
|
||||
const latency = pipelineLatency(metrics);
|
||||
const frameRate = finiteMetric(metrics?.frameRateHz);
|
||||
const points = finiteMetric(metrics?.pointCount);
|
||||
const aiLatency = finiteMetric(metrics?.aiLatencyMs);
|
||||
const aiFrameRate = finiteMetric(metrics?.aiFrameRateHz);
|
||||
const observationSources = state?.observationSources ?? [];
|
||||
const pointCloudSource = observationSources.find((source) => source.modality === "point-cloud");
|
||||
const pointCloudVisible = pointCloudSource
|
||||
@@ -317,13 +335,29 @@ function SpatialWorkspace({
|
||||
const mediaSources = observationSources.filter(
|
||||
(source) => source.capabilities.overlay && source.modality !== "point-cloud",
|
||||
);
|
||||
const recordedPerceptionSupported = recordedSource && perceptionAvailability !== "unavailable";
|
||||
const recordedPerceptionEnabled = showDetections2d || showSegmentation || showCuboids3d;
|
||||
const unifiedPerception = recordedPerceptionSupported && recordedPerceptionEnabled;
|
||||
const livePerceptionAvailable = !recordedSource && streamActive;
|
||||
const detections2dActive = recordedSource
|
||||
? showDetections2d
|
||||
: livePerceptionLayers.detections2d;
|
||||
const segmentationActive = recordedSource
|
||||
? showSegmentation
|
||||
: livePerceptionLayers.segmentation;
|
||||
const cuboids3dActive = recordedSource
|
||||
? showCuboids3d
|
||||
: livePerceptionLayers.cuboids3d;
|
||||
const visibleMediaSources = mediaSources.filter((source) =>
|
||||
observationLayout.visibleSourceIds.has(source.id),
|
||||
observationLayout.visibleSourceIds.has(source.id) &&
|
||||
!source.id.startsWith("recorded.perception."),
|
||||
);
|
||||
const presentedMediaSourceCount = unifiedPerception ? 0 : visibleMediaSources.length;
|
||||
const pointCloudFocused = Boolean(
|
||||
pointCloudSource && observationLayout.focusedSourceId === pointCloudSource.id,
|
||||
);
|
||||
const floatingSourceMaximized = Boolean(observationLayout.maximizedFloatingSourceId);
|
||||
const floatingSourceMaximized = !unifiedPerception &&
|
||||
Boolean(observationLayout.maximizedFloatingSourceId);
|
||||
const timeline = state?.observationTimeline;
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const intentionalSourceEnd = !recordedSource && [
|
||||
@@ -373,13 +407,20 @@ function SpatialWorkspace({
|
||||
[],
|
||||
);
|
||||
const onPerceptionAvailabilityChange = useCallback((available: boolean) => {
|
||||
setPerceptionAvailable(available);
|
||||
if (!available) setRecordedRerunView("spatial");
|
||||
setPerceptionAvailability(available ? "available" : "unavailable");
|
||||
if (!available) {
|
||||
setShowDetections2d(false);
|
||||
setShowSegmentation(false);
|
||||
setShowCuboids3d(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setPerceptionAvailable(false);
|
||||
setRecordedRerunView("spatial");
|
||||
setPerceptionAvailability("unknown");
|
||||
setShowDetections2d(false);
|
||||
setShowSegmentation(false);
|
||||
setShowCuboids3d(false);
|
||||
setRecordedViewResetGeneration(0);
|
||||
}, [sourceUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -389,8 +430,10 @@ function SpatialWorkspace({
|
||||
setSelection(null);
|
||||
setPlaybackState(null);
|
||||
setPlaybackController(null);
|
||||
setPerceptionAvailable(false);
|
||||
setRecordedRerunView("spatial");
|
||||
setPerceptionAvailability("unknown");
|
||||
setShowDetections2d(false);
|
||||
setShowSegmentation(false);
|
||||
setShowCuboids3d(false);
|
||||
}, [pointCloudVisible, sourceUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -428,20 +471,74 @@ function SpatialWorkspace({
|
||||
className="spatial-workspace"
|
||||
data-focused={pointCloudFocused || floatingSourceMaximized ? "true" : undefined}
|
||||
>
|
||||
<div className="spatial-toolbar">
|
||||
<div className="spatial-toolbar" data-viewer-controls="v1">
|
||||
<div className="spatial-toolbar__mode">
|
||||
<span className="section-eyebrow">СЦЕНА 3D · RERUN</span>
|
||||
</div>
|
||||
<div className="spatial-toolbar__actions">
|
||||
{recordedSource && perceptionAvailable ? (
|
||||
{recordedPerceptionSupported || livePerceptionAvailable ? (
|
||||
<div className="spatial-toolbar__view-switch" role="group" aria-label="Слои распознавания сцены">
|
||||
<Button
|
||||
size="compact"
|
||||
variant="primary"
|
||||
icon={<Icon name="video" />}
|
||||
aria-pressed="true"
|
||||
disabled
|
||||
>
|
||||
Оригинал
|
||||
</Button>
|
||||
<Button
|
||||
size="compact"
|
||||
variant={detections2dActive ? "primary" : "secondary"}
|
||||
icon={<Icon name="target" />}
|
||||
aria-pressed={detections2dActive}
|
||||
onClick={() => recordedSource
|
||||
? setShowDetections2d((current) => !current)
|
||||
: onLivePerceptionLayersChange({
|
||||
...livePerceptionLayers,
|
||||
detections2d: !livePerceptionLayers.detections2d,
|
||||
})}
|
||||
>
|
||||
Объекты 2D
|
||||
</Button>
|
||||
<Button
|
||||
size="compact"
|
||||
variant={segmentationActive ? "primary" : "secondary"}
|
||||
icon={<Icon name="image" />}
|
||||
aria-pressed={segmentationActive}
|
||||
onClick={() => recordedSource
|
||||
? setShowSegmentation((current) => !current)
|
||||
: onLivePerceptionLayersChange({
|
||||
...livePerceptionLayers,
|
||||
segmentation: !livePerceptionLayers.segmentation,
|
||||
})}
|
||||
>
|
||||
Сегментация
|
||||
</Button>
|
||||
<Button
|
||||
size="compact"
|
||||
variant={cuboids3dActive ? "primary" : "secondary"}
|
||||
icon={<Icon name="apps" />}
|
||||
aria-pressed={cuboids3dActive}
|
||||
onClick={() => recordedSource
|
||||
? setShowCuboids3d((current) => !current)
|
||||
: onLivePerceptionLayersChange({
|
||||
...livePerceptionLayers,
|
||||
cuboids3d: !livePerceptionLayers.cuboids3d,
|
||||
})}
|
||||
>
|
||||
Кубы 3D
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{recordedSource && presentedViewerStatus === "ready" ? (
|
||||
<Button
|
||||
size="compact"
|
||||
variant={recordedRerunView === "perception" ? "primary" : "secondary"}
|
||||
icon={<Icon name={recordedRerunView === "perception" ? "globe" : "image"} />}
|
||||
onClick={() => setRecordedRerunView((current) =>
|
||||
current === "perception" ? "spatial" : "perception")}
|
||||
variant="secondary"
|
||||
icon={<Icon name="refresh" />}
|
||||
onClick={() => setRecordedViewResetGeneration((current) => current === 0 ? 1 : 0)}
|
||||
>
|
||||
{recordedRerunView === "perception" ? "Облако точек" : "Распознавание"}
|
||||
Сброс вида
|
||||
</Button>
|
||||
) : null}
|
||||
<Button size="compact" variant="secondary" icon={<Icon name="network" />} onClick={navigation.openSource}>
|
||||
@@ -476,7 +573,13 @@ function SpatialWorkspace({
|
||||
? state?.observationTimeline?.range?.endSeconds
|
||||
: undefined}
|
||||
sceneSettings={sceneSettings}
|
||||
recordedView={recordedRerunView}
|
||||
recordedViewResetGeneration={recordedViewResetGeneration}
|
||||
recordedPerceptionLayers={{
|
||||
enabled: unifiedPerception,
|
||||
detections2d: showDetections2d,
|
||||
segmentation: showSegmentation,
|
||||
cuboids3d: showCuboids3d,
|
||||
}}
|
||||
onPerceptionAvailabilityChange={onPerceptionAvailabilityChange}
|
||||
onStatusChange={onStatusChange}
|
||||
onSelectionChange={onSelectionChange}
|
||||
@@ -511,7 +614,9 @@ function SpatialWorkspace({
|
||||
) : !floatingSourceMaximized ? (
|
||||
<div className="scene-source-controls">
|
||||
<ObservationSourcePicker
|
||||
sources={observationSources}
|
||||
sources={unifiedPerception
|
||||
? observationSources.filter((source) => source.modality === "point-cloud")
|
||||
: observationSources}
|
||||
visibleSourceIds={observationLayout.visibleSourceIds}
|
||||
pendingSourceIds={observationLayout.pendingSourceIds}
|
||||
onToggle={observationLayout.toggleSource}
|
||||
@@ -555,6 +660,16 @@ function SpatialWorkspace({
|
||||
<span>До публикации</span>
|
||||
<strong>{formatNumber(latency)}<small> мс</small></strong>
|
||||
</div>
|
||||
{streamActive ? (
|
||||
<div>
|
||||
<span>AI</span>
|
||||
<strong>
|
||||
{aiLatency === null ? "—" : formatNumber(aiLatency)}
|
||||
<small>{aiLatency === null ? "" : " мс"}</small>
|
||||
{aiFrameRate === null ? null : <small> · {formatNumber(aiFrameRate)} Гц</small>}
|
||||
</strong>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{!pointCloudFocused && !floatingSourceMaximized && state?.sourceMode && state.sourceMode !== "idle" && !sourceUrl.trim() ? (
|
||||
@@ -575,10 +690,17 @@ function SpatialWorkspace({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{presentedViewerStatus === "ready" && !floatingSourceMaximized ? (
|
||||
<div className="scene-navigation-hint" aria-label="Навигация по 3D-сцене">
|
||||
<span>Колесо · зум к курсору</span>
|
||||
<span>WASD · свободный проход</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!pointCloudFocused && !floatingSourceMaximized && recordedPlaybackReady ? (
|
||||
<ObservationTimeline
|
||||
active={presentedViewerStatus === "ready"}
|
||||
sourceCount={Math.max(1, 1 + visibleMediaSources.length)}
|
||||
sourceCount={Math.max(1, (unifiedPerception ? 2 : 1) + presentedMediaSourceCount)}
|
||||
mode={recordedSource && playbackState?.rangeNs
|
||||
? "recorded"
|
||||
: timeline?.mode}
|
||||
@@ -599,7 +721,7 @@ function SpatialWorkspace({
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!pointCloudFocused ? visibleMediaSources.map((source, index) => (
|
||||
{visibleMediaSources.map((source, index) => (
|
||||
<FloatingObservationWindow
|
||||
key={source.id}
|
||||
source={source}
|
||||
@@ -609,6 +731,7 @@ function SpatialWorkspace({
|
||||
rect={observationLayout.windowRects[source.id]}
|
||||
maximized={observationLayout.maximizedFloatingSourceId === source.id}
|
||||
active={observationLayout.activeFloatingSourceId === source.id}
|
||||
hidden={pointCloudFocused || unifiedPerception}
|
||||
onRectChange={(rect) => observationLayout.setWindowRect(source.id, rect)}
|
||||
onMaximizedChange={(maximized) =>
|
||||
observationLayout.setFloatingMaximized(source.id, maximized)}
|
||||
@@ -627,12 +750,12 @@ function SpatialWorkspace({
|
||||
void observationLayout.hideSource(source.id);
|
||||
}}
|
||||
/>
|
||||
)) : null}
|
||||
))}
|
||||
{recordedSource ? (
|
||||
<div className="recorded-session-preloaders" aria-hidden="true">
|
||||
{mediaSources.filter((source) => (
|
||||
source.delivery?.kind === "recorded-fmp4-manifest" &&
|
||||
(pointCloudFocused || !observationLayout.visibleSourceIds.has(source.id)) &&
|
||||
!observationLayout.visibleSourceIds.has(source.id) &&
|
||||
shouldPrepareRecordedSource(source.id)
|
||||
)).map((source) => (
|
||||
<ObservationMedia
|
||||
@@ -657,7 +780,9 @@ function SpatialWorkspace({
|
||||
<span><i data-state="ready" />Траектория</span>
|
||||
<span><i data-state="ready" />Преобразования</span>
|
||||
<span><i data-state="contract" />Камеры в 3D</span>
|
||||
<span><i data-state={perceptionAvailable ? "ready" : "contract"} />Объекты / рамки</span>
|
||||
<span><i data-state={detections2dActive ? "ready" : "contract"} />Объекты 2D</span>
|
||||
<span><i data-state={segmentationActive ? "ready" : "contract"} />Сегментация</span>
|
||||
<span><i data-state={cuboids3dActive ? "ready" : "contract"} />Кубы 3D</span>
|
||||
<span><i data-state="contract" />Компоновка</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user