feat(lab): add bounded local SLAM surface

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 09:41:25 +03:00
parent b8fb4ebcba
commit b7a51e26e6
10 changed files with 369 additions and 8 deletions
@@ -104,6 +104,7 @@ export const LaboratoryMetricEvidenceScene = forwardRef<
LaboratoryMetricEvidenceSceneHandle,
{
pointCloudBodyXyzM: readonly LaboratoryMetricPoint3[];
localSurfaceBodyXyzM: readonly LaboratoryMetricPoint3[];
obstacles: readonly LaboratoryMetricObstacleVisual[];
rig: LaboratoryMetricRigVisual;
corridor: LaboratoryMetricCorridorVisual;
@@ -111,10 +112,12 @@ LaboratoryMetricEvidenceSceneHandle,
mode: LaboratoryMetricSceneMode;
label: string;
showCurrentIncrement: boolean;
showLocalSurface: boolean;
showRollingMap: boolean;
}
>(function LaboratoryMetricEvidenceScene({
pointCloudBodyXyzM,
localSurfaceBodyXyzM,
obstacles,
rig,
corridor,
@@ -122,6 +125,7 @@ LaboratoryMetricEvidenceSceneHandle,
mode,
label,
showCurrentIncrement,
showLocalSurface,
showRollingMap,
}, ref) {
const hostRef = useRef<HTMLDivElement | null>(null);
@@ -211,6 +215,25 @@ LaboratoryMetricEvidenceSceneHandle,
if (!host || !content) return;
clearGroup(content);
if (showLocalSurface) {
const localSurfaceGeometry = new THREE.BufferGeometry();
localSurfaceGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions(localSurfaceBodyXyzM), 3),
);
content.add(new THREE.Points(
localSurfaceGeometry,
new THREE.PointsMaterial({
color: tokenColor(host, "--nodedc-accent-rgb", [247, 248, 244]),
size: 1.3,
sizeAttenuation: false,
transparent: true,
opacity: 0.42,
depthWrite: false,
}),
));
}
if (showCurrentIncrement) {
const contextGeometry = new THREE.BufferGeometry();
contextGeometry.setAttribute(
@@ -296,8 +319,10 @@ LaboratoryMetricEvidenceSceneHandle,
}, [
obstacles,
occupiedVoxelSizeM,
localSurfaceBodyXyzM,
pointCloudBodyXyzM,
showCurrentIncrement,
showLocalSurface,
showRollingMap,
]);
@@ -396,6 +421,7 @@ LaboratoryMetricEvidenceSceneHandle,
<span data-decision="not-threat">Вне коридора</span>
<span data-decision="unknown">Неизвестно</span>
<span data-decision="context">Current increment</span>
<span data-decision="local-surface">Local SLAM surface</span>
<span data-decision="rolling">Rolling-map occupied</span>
</div>
</div>
@@ -0,0 +1,131 @@
import type {
M4Matrix3,
M4Point3,
M4ThreatTimelineFrame,
} from "./m4ReplayThreat";
export interface M4LocalSurfaceProfile {
windowSeconds: number;
voxelSizeM: number;
radiusM: number;
pointLimit: number;
}
export interface M4LocalSurface {
pointsBodyXyzM: readonly M4Point3[];
sourceFrameCount: number;
sourcePointCount: number;
voxelCount: number;
}
function bodyPointToMap(
point: M4Point3,
origin: M4Point3,
basis: M4Matrix3,
): M4Point3 {
return [
origin[0] + point[0] * basis[0][0] + point[1] * basis[0][1] + point[2] * basis[0][2],
origin[1] + point[0] * basis[1][0] + point[1] * basis[1][1] + point[2] * basis[1][2],
origin[2] + point[0] * basis[2][0] + point[1] * basis[2][1] + point[2] * basis[2][2],
];
}
function mapPointToBody(
point: M4Point3,
origin: M4Point3,
basis: M4Matrix3,
): M4Point3 {
const delta: M4Point3 = [
point[0] - origin[0],
point[1] - origin[1],
point[2] - origin[2],
];
return [
delta[0] * basis[0][0] + delta[1] * basis[1][0] + delta[2] * basis[2][0],
delta[0] * basis[0][1] + delta[1] * basis[1][1] + delta[2] * basis[2][1],
delta[0] * basis[0][2] + delta[1] * basis[1][2] + delta[2] * basis[2][2],
];
}
function emptySurface(): M4LocalSurface {
return {
pointsBodyXyzM: [],
sourceFrameCount: 0,
sourcePointCount: 0,
voxelCount: 0,
};
}
export function buildM4LocalSurface(
availableFrames: readonly M4ThreatTimelineFrame[],
activeFrame: M4ThreatTimelineFrame | null,
profile: M4LocalSurfaceProfile,
): M4LocalSurface {
const activeBody = activeFrame?.bodyFrame;
if (
!activeFrame
|| !activeBody
|| profile.windowSeconds <= 0
|| profile.voxelSizeM <= 0
|| profile.radiusM <= 0
|| profile.pointLimit < 1
) {
return emptySurface();
}
const startTimeNs = activeFrame.sourceTimeNs - profile.windowSeconds * 1_000_000_000;
const frames = availableFrames
.filter((frame) => (
frame.bodyFrame
&& frame.sourceTimeNs >= startTimeNs
&& frame.sourceTimeNs <= activeFrame.sourceTimeNs
))
.sort((left, right) => left.sequence - right.sequence);
if (!frames.length) return emptySurface();
const radiusSquared = profile.radiusM * profile.radiusM;
const voxels = new Map<string, M4Point3>();
let sourcePointCount = 0;
for (const frame of frames) {
const sourceBody = frame.bodyFrame;
if (!sourceBody) continue;
sourcePointCount += frame.pointCloudBodyXyzM.length;
for (const sourcePoint of frame.pointCloudBodyXyzM) {
const mapPoint = bodyPointToMap(
sourcePoint,
sourceBody.originMapXyzM,
sourceBody.basisMapFromBody,
);
const activePoint = mapPointToBody(
mapPoint,
activeBody.originMapXyzM,
activeBody.basisMapFromBody,
);
if (
activePoint[0] * activePoint[0]
+ activePoint[1] * activePoint[1]
+ activePoint[2] * activePoint[2]
> radiusSquared
) {
continue;
}
const key = [
Math.floor(mapPoint[0] / profile.voxelSizeM),
Math.floor(mapPoint[1] / profile.voxelSizeM),
Math.floor(mapPoint[2] / profile.voxelSizeM),
].join(":");
if (!voxels.has(key)) voxels.set(key, activePoint);
}
}
const retained = [...voxels.values()];
const stride = Math.max(1, Math.ceil(retained.length / profile.pointLimit));
return {
pointsBodyXyzM: retained
.filter((_, index) => index % stride === 0)
.slice(0, profile.pointLimit),
sourceFrameCount: frames.length,
sourcePointCount,
voxelCount: voxels.size,
};
}
@@ -1,6 +1,7 @@
export type M4ThreatDecision = "threat" | "not-threat" | "unknown";
export type M4ThreatMotion = "moving" | "stationary" | "unknown";
export type M4Point3 = readonly [number, number, number];
export type M4Matrix3 = readonly [M4Point3, M4Point3, M4Point3];
export interface M4ThreatReplayResult {
resultId: string;
@@ -132,6 +133,10 @@ export interface M4ThreatTimelineFrame {
sessionSeconds: number;
sourceAvailable: boolean;
spatialAvailable: boolean;
bodyFrame: {
originMapXyzM: M4Point3;
basisMapFromBody: M4Matrix3;
} | null;
pointCloudBodyXyzM: readonly M4Point3[];
pointCloudSourceCount: number;
pointCloudSampleCount: number;
@@ -159,6 +164,14 @@ export interface M4ThreatTimeline {
maximumSourcePointsPerFrame: number;
pointDelivery: "exact-current-increment";
sourceRepresentationId: "registered-map-increment-v1";
localSurfaceVisualization: {
derivation: "bounded-registered-increment-accumulation";
windowSeconds: number;
voxelSizeM: number;
radiusM: number;
pointLimit: number;
authority: "visual-derived";
};
occupiedVoxelSizeM: number;
rig: M4ThreatVisualFrame["rig"];
corridor: M4ThreatVisualFrame["corridor"];
@@ -219,6 +232,10 @@ const vector = (value: unknown, size: number, label: string): number[] => {
if (parsed.length !== size) throw new M4ThreatContractError(`${label}: неверная размерность.`);
return parsed;
};
const point3 = (value: unknown, label: string): M4Point3 => {
const parsed = vector(value, 3, label);
return [parsed[0]!, parsed[1]!, parsed[2]!];
};
const decision = (value: unknown, label: string): M4ThreatDecision => {
if (value !== "threat" && value !== "not-threat" && value !== "unknown") {
throw new M4ThreatContractError(`${label}: неизвестное решение.`);
@@ -539,6 +556,10 @@ export async function fetchM4ThreatTimeline(
}
const rig = object(payload.rig, "M4.6 timeline rig");
const corridor = object(payload.corridor, "M4.6 timeline corridor");
const localSurface = object(
payload.local_surface_visualization,
"M4.6 local surface profile",
);
return {
resultId: result,
recordedSourceSessionId: "20260720T065719Z_viewer_live",
@@ -565,6 +586,22 @@ export async function fetchM4ThreatTimeline(
"M4.6 point delivery",
),
sourceRepresentationId: "registered-map-increment-v1",
localSurfaceVisualization: {
derivation: exact(
localSurface.derivation,
"bounded-registered-increment-accumulation",
"M4.6 local surface derivation",
),
windowSeconds: number(localSurface.window_seconds, "M4.6 local surface window"),
voxelSizeM: number(localSurface.voxel_size_m, "M4.6 local surface voxel"),
radiusM: number(localSurface.radius_m, "M4.6 local surface radius"),
pointLimit: integer(localSurface.point_limit, "M4.6 local surface point limit"),
authority: exact(
localSurface.authority,
"visual-derived",
"M4.6 local surface authority",
),
},
occupiedVoxelSizeM: number(
corridor.occupied_voxel_size_m,
"M4.6 occupied voxel size",
@@ -647,6 +684,22 @@ function parseTimelineFrame(
throw new M4ThreatContractError("M4.6 timeline frame order: нарушен контракт.");
}
const counts = object(item.decision_counts, "M4.6 timeline decisions");
const spatialAvailable = typeof item.spatial_available === "boolean"
&& item.spatial_available;
const bodyFrame = item.body_frame === null
? null
: object(item.body_frame, "M4.6 timeline body frame");
if (spatialAvailable !== (bodyFrame !== null)) {
throw new M4ThreatContractError("M4.6 timeline body frame: нарушена доступность.");
}
const basis = bodyFrame === null
? null
: array(bodyFrame.basis_map_from_body, "M4.6 timeline body basis").map(
(row) => point3(row, "M4.6 timeline body basis row"),
);
if (basis !== null && basis.length !== 3) {
throw new M4ThreatContractError("M4.6 timeline body basis: нарушен размер.");
}
const cameraUrl = text(item.camera_url, "M4.6 timeline camera URL");
if (!cameraUrl.includes(`/results/${result}/timeline/frames/${sequence}/camera`)) {
throw new M4ThreatContractError("M4.6 timeline camera URL: нарушена идентичность.");
@@ -657,7 +710,16 @@ function parseTimelineFrame(
sourceTimeNs: integer(item.source_time_ns, "M4.6 timeline source time"),
sessionSeconds: number(item.session_seconds, "M4.6 timeline time"),
sourceAvailable: typeof item.source_available === "boolean" && item.source_available,
spatialAvailable: typeof item.spatial_available === "boolean" && item.spatial_available,
spatialAvailable,
bodyFrame: bodyFrame === null || basis === null
? null
: {
originMapXyzM: point3(
bodyFrame.origin_map_xyz_m,
"M4.6 timeline body origin",
),
basisMapFromBody: [basis[0]!, basis[1]!, basis[2]!],
},
pointCloudBodyXyzM: array(item.point_cloud_body_xyz_m, "M4.6 timeline points").map(
(point) => vector(point, 3, "M4.6 timeline point") as [number, number, number],
),
@@ -66,6 +66,10 @@
margin-left: auto;
}
.m4-replay-threat-visual__layer-controls .nodedc-segmented__item {
padding-inline: 0.72rem;
}
.m4-replay-threat-evidence-viewer .laboratory-evidence-viewer__transport {
bottom: 0.3rem;
}
@@ -155,3 +159,8 @@
border: 1px solid rgb(var(--nodedc-accent-rgb));
background: transparent;
}
.laboratory-metric-evidence-scene__legend span[data-decision="local-surface"]::before {
background: rgb(var(--nodedc-accent-rgb));
opacity: 0.72;
}
@@ -18,6 +18,7 @@ import type {
M4ThreatCameraProposal,
M4ThreatTimelineFrame,
} from "../../core/laboratory/m4ReplayThreat";
import { buildM4LocalSurface } from "../../core/laboratory/m4LocalSurface";
import { recordedObservationSources } from "../../core/observation/recordedObservationSources";
import { replayObservationSession } from "../../core/observation/sessionArchive";
import type { ObservationSourceDescriptor } from "../../core/runtime/contracts";
@@ -68,6 +69,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
const [mode, setMode] = useState<M4ThreatViewMode>("video");
const [spatialMode, setSpatialMode] = useState<LaboratoryMetricSceneMode>("3d");
const [showCurrentIncrement, setShowCurrentIncrement] = useState(true);
const [showLocalSurface, setShowLocalSurface] = useState(true);
const [showRollingMap, setShowRollingMap] = useState(true);
const [expanded, setExpanded] = useState(false);
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
@@ -161,6 +163,16 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
.map((item) => item.assessment.closestApproachM)
.filter((value): value is number => value !== null)
.sort((left, right) => left - right)[0] ?? null;
const localSurface = useMemo(() => buildM4LocalSurface(
timelineFrame.availableFrames,
frame,
metadata.timeline?.localSurfaceVisualization ?? {
windowSeconds: 2,
voxelSizeM: 0.1,
radiusM: 12,
pointLimit: 20_000,
},
), [frame, metadata.timeline, timelineFrame.availableFrames]);
const seek = (seconds: number) => playbackController.seek(seconds);
const handleModeChange = (next: M4ThreatViewMode) => {
@@ -206,7 +218,17 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
aria-pressed={showCurrentIncrement}
onClick={() => setShowCurrentIncrement((visible) => !visible)}
>
CURRENT INCREMENT
CURRENT
</button>
<button
type="button"
className="nodedc-segmented__item"
data-active={showLocalSurface ? "true" : undefined}
aria-pressed={showLocalSurface}
title="Bounded local SLAM surface · visual-derived"
onClick={() => setShowLocalSurface((visible) => !visible)}
>
LOCAL SLAM
</button>
<button
type="button"
@@ -215,7 +237,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
aria-pressed={showRollingMap}
onClick={() => setShowRollingMap((visible) => !visible)}
>
ROLLING MAP
ROLLING
</button>
</div>
) : null}
@@ -250,7 +272,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
</strong>
<small>
{frame.spatialAvailable
? `${frame.pointCloudSampleCount}/${frame.pointCloudSourceCount} exact lio_pcl increment`
? `${frame.pointCloudSampleCount}/${frame.pointCloudSourceCount} exact · ${localSurface.pointsBodyXyzM.length} local SLAM / ${localSurface.sourceFrameCount} frames`
: "body frame / current increment unavailable"}
</small>
</div>
@@ -328,13 +350,15 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
<LaboratoryMetricEvidenceScene
ref={metricSceneRef}
pointCloudBodyXyzM={frame.pointCloudBodyXyzM}
localSurfaceBodyXyzM={localSurface.pointsBodyXyzM}
obstacles={sceneObstacles}
rig={timeline.rig}
corridor={timeline.corridor}
occupiedVoxelSizeM={timeline.occupiedVoxelSizeM}
mode={spatialMode}
label="M4.6 recorded-realtime current increment and rolling occupancy"
label="M4.6 exact current increment, bounded local SLAM surface and rolling occupancy"
showCurrentIncrement={showCurrentIncrement}
showLocalSurface={showLocalSurface}
showRollingMap={showRollingMap}
/>
) : null}
@@ -146,10 +146,18 @@ export function useM4ThreatTimelineFrame({
(frame) => frame.sequence === activeSequence,
) ?? null;
}, [activeChunkStart, activeSequence, chunks]);
const availableFrames = useMemo(() => {
const unique = new Map<number, M4ThreatTimelineFrame>();
for (const chunk of chunks.values()) {
for (const frame of chunk.frames) unique.set(frame.sequence, frame);
}
return [...unique.values()].sort((left, right) => left.sequence - right.sequence);
}, [chunks]);
return {
activeSequence,
activeFrame,
availableFrames,
loading: error === null && Boolean(timeline) && !activeFrame,
error,
};
@@ -13,6 +13,7 @@ let selectM4ThreatTimelineFrame;
let selectM4ThreatTimelineSequence;
let advanceRecordedEvidencePlayback;
let m4ThreatChunkWindowStarts;
let buildM4LocalSurface;
const resultId = `m4-threat-replay-${"a".repeat(64)}`;
@@ -36,6 +37,9 @@ before(async () => {
({ m4ThreatChunkWindowStarts } = await server.ssrLoadModule(
"/src/workspaces/laboratory/useM4ThreatTimeline.ts",
));
({ buildM4LocalSurface } = await server.ssrLoadModule(
"/src/core/laboratory/m4LocalSurface.ts",
));
});
after(async () => {
@@ -65,6 +69,10 @@ function timelineFrame(sequence, sessionSeconds, overrides = {}) {
session_seconds: sessionSeconds,
source_available: true,
spatial_available: true,
body_frame: {
origin_map_xyz_m: [sequence, 0, 0],
basis_map_from_body: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
},
point_cloud_body_xyz_m: [[1, 0, 0.1]],
point_cloud_source_count: 1,
point_cloud_sample_count: 1,
@@ -262,6 +270,14 @@ test("M4.6 timeline keeps only a compact index and decodes bounded spatial chunk
point_sample_limit: 4096,
maximum_source_points_per_frame: 3092,
point_delivery: "exact-current-increment",
local_surface_visualization: {
derivation: "bounded-registered-increment-accumulation",
window_seconds: 2,
voxel_size_m: 0.1,
radius_m: 12,
point_limit: 20000,
authority: "visual-derived",
},
rig: { length_m: 1, width_m: 0.6, nominal_sensor_height_m: 1.25 },
corridor: {
forward_length_m: 8,
@@ -277,6 +293,8 @@ test("M4.6 timeline keeps only a compact index and decodes bounded spatial chunk
assert.equal(timeline.pointDelivery, "exact-current-increment");
assert.equal(timeline.maximumSourcePointsPerFrame, 3092);
assert.equal(timeline.occupiedVoxelSizeM, 0.45);
assert.equal(timeline.localSurfaceVisualization.windowSeconds, 2);
assert.equal(timeline.localSurfaceVisualization.authority, "visual-derived");
assert.equal(selectM4ThreatTimelineSequence(timeline.frameTimesNs, 35.50), 1);
const chunk = await fetchM4ThreatTimelineChunk(resultId, 0, 2, {
@@ -300,6 +318,54 @@ test("M4.6 timeline keeps only a compact index and decodes bounded spatial chunk
assert.equal(selectM4ThreatTimelineFrame(chunk.frames, 35.50).sequence, 1);
});
test("M4.6 local SLAM surface reprojects registered increments into the active body frame", () => {
const frames = [
timelineFrame(0, 10, {
body_frame: {
origin_map_xyz_m: [0, 0, 0],
basis_map_from_body: [[0, -1, 0], [1, 0, 0], [0, 0, 1]],
},
point_cloud_body_xyz_m: [[1, 0, 0]],
}),
timelineFrame(1, 10.1, {
body_frame: {
origin_map_xyz_m: [0, 0, 0],
basis_map_from_body: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
},
point_cloud_body_xyz_m: [[1, 0, 0]],
}),
].map((raw, index) => ({
sequence: raw.sequence,
frameId: raw.frame_id,
sourceTimeNs: raw.source_time_ns,
sessionSeconds: raw.session_seconds,
sourceAvailable: true,
spatialAvailable: true,
bodyFrame: {
originMapXyzM: raw.body_frame.origin_map_xyz_m,
basisMapFromBody: raw.body_frame.basis_map_from_body,
},
pointCloudBodyXyzM: raw.point_cloud_body_xyz_m,
pointCloudSourceCount: 1,
pointCloudSampleCount: 1,
pointCloudLayer: "current-increment",
rollingMapComponentCount: 0,
metricObstacles: [],
cameraProposals: [],
decisionCounts: { threat: 0, "not-threat": 0, unknown: 0 },
cameraUrl: raw.camera_url,
}));
const surface = buildM4LocalSurface(frames, frames[1], {
windowSeconds: 2,
voxelSizeM: 0.1,
radiusM: 12,
pointLimit: 20_000,
});
assert.equal(surface.sourceFrameCount, 2);
assert.equal(surface.sourcePointCount, 2);
assert.deepEqual(surface.pointsBodyXyzM, [[0, 1, 0], [1, 0, 0]]);
});
test("M4.6 spatial buffering keeps previous, active and two future chunks", () => {
assert.deepEqual(m4ThreatChunkWindowStarts(48, 24, 4489), [24, 48, 72, 96]);
assert.deepEqual(m4ThreatChunkWindowStarts(0, 24, 4489), [0, 24, 48]);
@@ -345,8 +411,9 @@ test("M4.6 viewer reuses shared camera, video and metric evidence renderers", as
assert.match(videoScene, /<RecordedFmp4Player/);
assert.match(imageScene, /<RecordedEvidenceBoxOverlay/);
assert.match(metricScene, /OrbitControls/);
assert.match(visual, /CURRENT INCREMENT/);
assert.match(visual, /ROLLING MAP/);
assert.match(visual, /LOCAL SLAM/);
assert.match(visual, /showLocalSurface/);
assert.match(metricScene, /Local SLAM surface/);
assert.match(visual, /showJumpToEnd=\{false\}/);
assert.doesNotMatch(metricScene, /ЛКМ · вращение/);
});
+1 -1
View File
@@ -25,7 +25,7 @@ WHEEL_NAME = "nodedc_mission_core-0.1.0-py3-none-any.whl"
RUNNER_NAME = RUNNER.name
PATCH_ID = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
EXPECTED_BASELINE_SHA256 = "ea10359339e6cce31b5780a2710299771cab7cc0c1c2a2b56a1621f786b31fa8"
EXPECTED_WHEEL_SHA256 = "9d203362cebb383fd5394e60dbcaa4cea6d332de9c0aa411cbc23825a4e53868"
EXPECTED_WHEEL_SHA256 = "2fc53bf3c2cd82a33e62b158a455d813bca64b844707e758792b4bac263b2543"
PAYLOAD_FILES = (
RUNNER_NAME,
WHEEL_NAME,
+24
View File
@@ -35,6 +35,10 @@ RECORDED_SPATIAL_CHUNK_SCHEMA: Final = "missioncore.recorded-spatial-evidence-ch
RECORDED_SPATIAL_FRAME_SCHEMA: Final = "missioncore.recorded-spatial-evidence-frame/v1"
RECORDED_SPATIAL_POINT_LIMIT: Final = 4_096
RECORDED_SPATIAL_MAX_CHUNK_FRAMES: Final = 24
RECORDED_LOCAL_SURFACE_WINDOW_SECONDS: Final = 2.0
RECORDED_LOCAL_SURFACE_VOXEL_SIZE_M: Final = 0.10
RECORDED_LOCAL_SURFACE_RADIUS_M: Final = 12.0
RECORDED_LOCAL_SURFACE_POINT_LIMIT: Final = 20_000
_EXPECTED_FRAME_COUNT: Final = 4_489
_SOURCE_TIME = re.compile(rb'"source_time_ns":([0-9]+)')
@@ -117,6 +121,14 @@ class RecordedThreatTimeline:
"point_sample_limit": RECORDED_SPATIAL_POINT_LIMIT,
"point_delivery": "exact-current-increment",
"maximum_source_points_per_frame": self.store.maximum_current_point_count,
"local_surface_visualization": {
"derivation": "bounded-registered-increment-accumulation",
"window_seconds": RECORDED_LOCAL_SURFACE_WINDOW_SECONDS,
"voxel_size_m": RECORDED_LOCAL_SURFACE_VOXEL_SIZE_M,
"radius_m": RECORDED_LOCAL_SURFACE_RADIUS_M,
"point_limit": RECORDED_LOCAL_SURFACE_POINT_LIMIT,
"authority": "visual-derived",
},
"image_width": 800,
"image_height": 600,
"rig": {
@@ -203,6 +215,14 @@ class RecordedThreatTimeline:
"session_seconds": self.index.source_times_ns[sequence] / 1_000_000_000,
"source_available": source_available,
"spatial_available": body_frame is not None,
"body_frame": None
if body_frame is None
else {
"origin_map_xyz_m": list(body_frame.origin_map_xyz_m),
"basis_map_from_body": [
list(row) for row in body_frame.basis_map_from_body
],
},
"point_cloud_body_xyz_m": point_cloud,
"point_cloud_source_count": point_source_count,
"point_cloud_sample_count": len(point_cloud),
@@ -285,6 +305,10 @@ def _decision_counts(assessments: list[dict[str, object]]) -> dict[str, int]:
__all__ = [
"RECORDED_SPATIAL_CHUNK_SCHEMA",
"RECORDED_SPATIAL_FRAME_SCHEMA",
"RECORDED_LOCAL_SURFACE_POINT_LIMIT",
"RECORDED_LOCAL_SURFACE_RADIUS_M",
"RECORDED_LOCAL_SURFACE_VOXEL_SIZE_M",
"RECORDED_LOCAL_SURFACE_WINDOW_SECONDS",
"RECORDED_SPATIAL_MAX_CHUNK_FRAMES",
"RECORDED_SPATIAL_POINT_LIMIT",
"RECORDED_SPATIAL_TIMELINE_SCHEMA",
+10
View File
@@ -133,6 +133,14 @@ def test_m4_6_timeline_is_indexed_and_spatial_evidence_is_chunked() -> None:
assert timeline["recorded_source"]["representation_id"] == "registered-map-increment-v1"
assert timeline["point_delivery"] == "exact-current-increment"
assert timeline["maximum_source_points_per_frame"] == 3092
assert timeline["local_surface_visualization"] == {
"authority": "visual-derived",
"derivation": "bounded-registered-increment-accumulation",
"point_limit": 20000,
"radius_m": 12.0,
"voxel_size_m": 0.1,
"window_seconds": 2.0,
}
assert "frames" not in timeline
chunk = get_chunk(RESULT_ID, start=1880, count=12)
@@ -143,6 +151,8 @@ def test_m4_6_timeline_is_indexed_and_spatial_evidence_is_chunked() -> None:
first = chunk["frames"][0]
assert first["schema_version"] == "missioncore.recorded-spatial-evidence-frame/v1"
assert first["spatial_available"] is True
assert len(first["body_frame"]["origin_map_xyz_m"]) == 3
assert len(first["body_frame"]["basis_map_from_body"]) == 3
assert first["point_cloud_layer"] == "current-increment"
assert first["point_cloud_sample_count"] == first["point_cloud_source_count"]
assert 0 < first["point_cloud_sample_count"] <= 4096