Skip to content

The capture database

Every MVIS run works out of a single SQLite file per capture — the .mvisdb. A sealed, content-hashed capture core holds everything expensive (board detections, full IMU streams); each solve writes a disposable per-run_id overlay recording every decision (which frames, boards and corners were used, and why the rest were excluded) and every estimate. Deleting a run cascades its whole story away; the core is never touched after sealing, so detection work is paid once per recording.

The in-memory object model the pipeline actually touches: one MVISDatabase per capture, with one run’s overlay folded into inline flags. Annotations on the right; ⟨derived⟩ fields are computed, never stored; D-numbers reference internal design decisions.

MVISDatabase                            the one container (D55) — live run or loaded via capture.load(run_id) · flags INLINE (D53) · overlay tables = its on-disk form
├─ streams: list[CameraStream]              PK camera_id — the number you wrote in the YAML
   ├─ camera_id · topic · source_type · source_path
   ├─ info: CameraInfo
│   │     name · shutter · readout_s · rolling_mode
│   │     intrinsics · distortion · camera_model · resolution · calib_provenance
│   │     ⟨derived⟩ stereo_pair · projection · distortion_kind · is_fisheye
│   └─ frames: list[FrameData]              PK frame_id · UNIQUE (camera_id, timestamp)
       ├─ frame_id · camera_id · timestamp
       ├─ is_motion_still · is_selected_to_use
       ├─ ⟨derived⟩ is_valid_to_use = OR over every group below
│       ├─ board_detections: list[BoardDetection]   ONE PER BOARD SEEN · PK (frame_id, board_id)
│       │   ├─ board_id
│       │   ├─ ⟨derived⟩ n_detections · n_tags · detect_status
│       │   ├─ pose_cam_to_board: PoseCamToBoard{T, source} · is_valid_to_use
│       │   ├─ ⟨derived⟩ is_selected_to_use = frame.selected AND own selection
│       │   └─ detections: list[TagDetection]       PK (frame_id, board_id, point_id)
│       │       ├─ point_id · uv · detector_emission_order
│       │       ├─ ⟨derived⟩ tag_id · corner_id · row = int(uv[1])
│       │       ├─ is_valid · is_selected · exclude_reason
│       │       └─ residual: Residual{residual_px, ⟨derived⟩ residual_source}
│       ├─ tag_feature_detections                       TODO
│       └─ free_feature_detections                      TODO
├─ boards: dict[int, Board]                 Board = {BoardInfo, [BoardPt]}
│   ├─ info: BoardInfo   board_type · rows · cols · cell_size_m · gap_m · tag_family · start_id · corners_per_tag · config_hash
│   │                     ⟨derived⟩ is_kalibr_tag · num_tags · num_points
│   └─ board_pts: list[BoardPt]   board_pt_id · board_pt_3d · info: BoardPtInfo{tag_id, corner_id}
├─ imus: list[ImuStream]                    PK imu_id — the position in imu_topics
│   ├─ info: ImuInfo     name · topic · frequency_hz · gyro_unit · accel_unit · gyro_only
│   │                     gyro_noise · gyro_bias_noise · accel_noise · accel_bias_noise
│   │                     intrinsics: ImuIntrinsics{xi24, model} · td_gyro_to_accel_s · calib_provenance   ⟨derived⟩ role
│   └─ meas: ImuMeasurements   t_gyro · gyro · t_accel · accel   ⟨derived⟩ synchronized
├─ rig: Rig   base_camera_id · base_imu_id · sync_kind · sync_tolerance_s
                ⟨derived, per run⟩ reference_stream = (imu, base_imu_id) if cam_imu else (cam, base_camera_id)
├─ estimate_states: EstimateStates | None   the estimate side of THIS run — a projection, zero new tables
reference_stream: (cam, base_camera_id) | (imu, base_imu_id)   keyed on run.calib_choice
state_hz                                     the reference stream’s knot rate
     base_state: CamOnly{t, T_cam_to_board (F,7)} | CamImu{t, T_imu_to_world (K,7), v, b_g, b_a}
sensor_states[]: SensorState                 one per sensor — reference included (its factor_hz, relative = None)
        sensor · factor_hz · relative{T_x_to_ref · td_x_to_ref_s · provenance}
        · intrinsics: Camera{fxfycxcy · dist · model · readout · provenance} | Imu{xi24 · model · provenance}
boards[board_id]: T_board_to_world (7,)      one per board, from state_var — cam-IMU only
world_frame                                  the yaw gauge; None in cam-only — the board IS the reference
└─ run_id: int | None                        every run field above resolves against this

The same container serializes to three SQL layers:

  • Sealed capture corecapture, camera, imu/imu_meas, board/board_pt, rig, stereo_pair, and the observation hierarchy frameboard_detectiondetection (corner arrays as packed float64 blobs). Immutable once capture.content_hash is set.
  • Run overlay (per run_id, ON DELETE CASCADE) — run, the sparse selection story (frame_status, board_detection_status, detection_status: only rows that differ from selected/valid are written), and the results (result_camera, result_imu, extrinsic — every cam↔cam, cam↔IMU and IMU↔IMU edge with T, td, and sigmas in one table).
  • Trajectory (camera-IMU runs) — estimate_meta, state_knot/state_var (the state timeline and typed variables), frame_bracket (which knots bracket each frame and at what interpolation factor), and world_frame (the gravity-aligned convention).

The status tables mirror the core hierarchy one-for-one, which is what makes the write-through selection ledger — and the consistency checks between the working set and the stored flags — cheap. Loading folds one run’s overlay back onto the objects above, so pipeline code never sees SQL.

The selection story is also what powers the run audit: every excluded frame, board pose and corner carries its exclude_reason and stage, so a calibration result can always answer “why was this observation not used?”.