Sanish Kumar
← Back to selected work

(From depth frames to a reproducible 3D mesh)

SpatialForge

I built SpatialForge to turn calibrated depth frames and known camera poses into a 3D mesh I can inspect and reproduce. It now streams long scans, saves sparse volumes, resumes interrupted fusion, and measures both surface error and missing geometry. Every faster path has a slower reference to check against.

Role
Independent engineering project
Timeline
2026
Stack
Python · NumPy · RGB-D · Sparse TSDF · TUM RGB-D · ICL-NUIM

See it in action

Reconstructed living room beside the same mesh coloured by surface error, with a 0–5 mm scale

Loads on request · 5.6 MB

A 10 mm reconstruction of the synthetic ICL-NUIM living room, from 440 frames with supplied exact poses. Left: scan colour. Right: distance to the known surface. This is a reconstruction result, not a photograph or a real-sensor accuracy claim.

More screenshots and results
Untextured mesh of a desk and surrounding objects reconstructed from a TUM depth-camera sequence

Geometry from the real TUM freiburg1_xyz scan. The held-out-depth result measures cross-view agreement using supplied poses, not surveyed surface accuracy.

What I wanted to solve

A reconstruction can look convincing and still be wrong. I wanted to know whether a change made the geometry better, merely faster, or quietly different. That meant keeping the scan, volume, mesh, and evaluation tied together, and measuring what the pictures don't show: numerical changes, biased depth, and holes in the surface.

What made it difficult

  • The input contract includes calibrated frames and camera poses supplied by the dataset.
  • Sparse allocation has to save memory without changing what a voxel update means.
  • Reordering floating-point operations can change the final bytes even when the numerical difference looks negligible.
  • Every artifact and validation report needs a traceable link back to its sensor data.
  • Long scans cannot require every decoded depth frame to stay in memory.
  • Stopping and resuming fusion must preserve the order of each voxel's updates.
  • A good error score must not hide missing surfaces or shared bias in the input frames.

How it works

  1. 1

    Session format

    A versioned .vgsession directory keeps RGB, depth, IMU, and supplied pose streams together.

  2. 2

    Deterministic replay

    Replay associates observations by timestamp and hashes the inputs into a digest for the scan.

  3. 3

    Sparse block planner

    A vectorized planner selects the 8x8x8 voxel blocks reached by depth observations and their truncation halo. A scalar fallback preserves the behaviour of irregular inputs.

  4. 4

    Streaming fusion

    One depth frame is decoded, applied to the planned voxels, and released. Stable per-voxel arithmetic keeps this path byte-identical to the reference on the parity fixtures.

  5. 5

    Saved volume and checkpoints

    The .sftvol file records the plan and scan digests with its arrays. A frame-boundary checkpoint saves the accumulators so interrupted fusion can continue without starting over.

  6. 6

    Mesh and colour

    Sparse cells become triangles. Disconnected fans at pinched vertices are split before topology validation; RGB is sampled only from frames that see the vertex.

  7. 7

    Evaluation and observed free space

    Reports check held-out depth or a known surface against the same saved volume and mesh. An optional expanded plan also records space observed along depth rays, keeping unseen space unknown.

Choices I made

Keep a simple correctness reference

I kept the slower dense and scalar paths because they are easy to inspect. They give the sparse and vectorized implementations something trustworthy to compare against.

Preserve arithmetic order during vectorization

My first vectorized path was numerically close, but its bytes differed. I changed it to preserve expression and observation order, then made byte equality part of the test suite.

Optimize planning against its own reference

Block selection now has a vectorized path too. I compare its serialized plan, chosen blocks, and sample counts with the per-pixel implementation. Non-finite coordinates, index-range problems, and oversized spans fall back to the scalar path so error order and messages do not quietly change.

Bind artifacts to their inputs

I use SHA-256 provenance so an artifact cannot be quietly reused after its underlying session changes.

Change the outer loop, not a voxel's history

Fusion doesn't need all the depth images at once. Processing frames in the outer loop frees each image sooner without changing the sequence of additions to any voxel. Saving between frames also gives checkpoint/resume a clean boundary.

Repair pinches, then run the same topology check

The first real-data mesh failed on vertices where disconnected triangle fans touched. I split those fans into separate vertices and kept the reference topology validator. The repair changes the representation; it doesn't make a failed check disappear.

Do not fit the evaluation frame to the answer

ICL-NUIM's trajectory and model use different frames. I align raw depth to the model, then measure the reconstruction in that frame. Fitting the mesh itself could absorb some of the error being measured. A deliberately shifted mesh must score worse.

Measure completeness separately from accuracy

A small patch of wall in exactly the right place could earn a good accuracy score while most of the room is missing. I also ask how much of the ground-truth surface seen by at least three fused frames has mesh nearby.

How I tested it

  • Regression tests compare scalar and vectorized planning, including serialized .sftplan bytes, selected blocks, sample counts, and rejection behaviour.
  • Fusion and sparse meshing are compared with their scalar and dense references, including exact accumulator, vertex, and triangle output.
  • The committed reconstruction fixture has fixed volume and mesh digests, checked in CI on Linux, macOS, and Windows with Python 3.11 and 3.14.
  • Checkpoint/resume is checked against uninterrupted fusion. The documented living-room run was also stopped after 80 frames and resumed to the published volume digest.
  • Held-out evaluation uses frames that contributed no voxels to the reconstructed volume.
  • Ground-truth evaluation uses an alignment fitted to raw depth, with a 25 mm mesh-shift negative control and an exact nearest-neighbour reference check.
  • Tests also check that rejected operations leave the filesystem untouched.

Known failure modes

  • Depth with a consistent offset can agree across views and still reconstruct the wrong surface. The dataset-supplied noisy ICL-NUIM sequence exposes this failure of held-out residuals as an accuracy proxy.
  • Irregular planner inputs use the slower scalar fallback; vectorization does not remove the configured allocation and index limits.
  • Surfaces seen only at glancing angles can disappear between voxel centres. A wider truncation band recovers coverage but increases error near edges.
  • A saved volume or report with mismatched scan, plan, or volume provenance is rejected.
  • A plan larger than 100,000 blocks is refused rather than partially allocated.

What works now

  • The documented TUM freiburg1_xyz run at 15 mm voxels fused 395 frames into a 535,486-triangle mesh and held out another 395. Its median TSDF residual was 7.5 mm; 99.95% of held-out samples fell inside observed voxels. This measures cross-view agreement with supplied poses, not surveyed surface accuracy.
  • A separate 10 mm TUM run reduced the median residual to 6.6 mm and produced 1,269,564 triangles. These are different configurations, not interchangeable figures from one run.
  • On the synthetic ICL-NUIM living room, 440 noise-free frames with exact poses produced a 10 mm mesh with 0.45 mm median and 1.69 mm RMS vertex-to-model tangent-plane distance. This isolates reconstruction error on ideal input, not real-camera performance.
  • With seeded simulated Kinect noise on that scene, the 10 mm mesh measured 0.86 mm median distance to the model's tangent planes. The simulation is distinct from ICL-NUIM's supplied noisy files.
  • At 10 mm, 98.4% of the ground-truth surface seen by at least three fused frames had mesh within 20 mm. This is coverage of the observed subset, not the entire room model.
  • The README reports 79 seconds for fusion and 2.5 for meshing on the 15 mm TUM run, using one laptop CPU core. The older 210-to-2-second room benchmark measures a different, smaller fusion workload.

What it doesn't do yet

  • There is no surveyed real-scene accuracy result. TUM provides real depth and poses; ICL-NUIM provides a known synthetic surface. They answer different questions.
  • Cross-platform byte identity is tested for the committed fixture and CI configurations, not every possible dataset, machine, or numerical-library version.
  • The project does not implement SLAM, visual odometry, bundle adjustment, pose estimation, or relocalization.
  • It reconstructs geometry, not rooms, doors, accessibility, or other semantic map data.
  • Fusion streams frames, but accumulators remain in memory: the 100,000-block ceiling is roughly 614 MB of accumulator storage.
  • A checkpoint can resume an existing plan. Adding frames outside that plan requires replanning and fusing again.
  • Observed free space is not a traversability map: it does not establish floor level, body clearance, obstacle avoidance, or a route.
  • This is a CPU reconstruction project, not a real-time mapper or a new TSDF algorithm.