feat(lab): add bounded local SLAM surface
This commit is contained in:
@@ -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, /ЛКМ · вращение/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user