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.
Design view — the one container
Section titled “Design view — the one container”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
On-disk form
Section titled “On-disk form”The same container serializes to three SQL layers:
- Sealed capture core —
capture,camera,imu/imu_meas,board/board_pt,rig,stereo_pair, and the observation hierarchyframe→board_detection→detection(corner arrays as packed float64 blobs). Immutable oncecapture.content_hashis 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 withT,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), andworld_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?”.