Monograph · 04 · Capture loop
Core pipeline · flagship
Capture loop
Owns the capture-rotation state machine and automated multi-position worker. Must not own Sapera transfer objects (ch.02) or folder naming (ch.05). Done when a reader can list valid transitions and the per-position algorithm without reading the UI.
State machine contract
CaptureStateMachine encodes allowed interleavings of camera capture and turntable motion.
States: Idle, Capturing, Rotating, Settling, Error.
Events: StartCapture, CaptureComplete/Failed, StartRotation, RotationComplete/Failed, SettlingComplete, Reset, Stop.
| From | Event | To |
|---|---|---|
| Idle | StartCapture | Capturing |
| Capturing | CaptureComplete | Idle |
| Capturing | StartRotation | Rotating |
| Rotating | RotationComplete | Settling |
| Rotating | RotationFailed | Settling (still settle) |
| Settling | SettlingComplete | Idle |
| Error | Reset / Stop | Idle |
Invalid events are rejected under mutex (ProcessEvent logs REJECTED). If you fire StartCapture while Rotating without a valid edge, nothing happens — better than double-Snap mid-move.
Per-position loop diagram
Worker loop. Move → settle → capture → save → next. Matches AutomatedCaptureController.
Sequence init workflow
- Reject empty positions or missing bluetooth/camera/session pointers
- Require BLE connected and camera count > 0
- Store positions; reset index; is_active=true
- Spawn
worker_thread_ - Worker: set speeds → ReturnToZero/TiltToZero → sleep ~3 s
- While index < N and not stop: honor pause spinlock →
ProcessNextPosition→ index++ - On success: state Completed, notify, zero turntable again
Line-anchored · ProcessNextPosition
1// pseudo-order from AutomatedCaptureController 2UpdateState(MovingTurntable); 3MoveTurntableToPosition(pos); // BLE deltas 4UpdateState(WaitingForSettle); 5Sleep(settle_time_ms_); // default 2000 6Sleep(capture_delay_ms_); // default 500 7UpdateState(Capturing); 8camera->CaptureAllCamerasAsync(path, …); 9Wait for completion / timeout (max_capture_wait_seconds_)
Defaults: \(t_{\mathrm{settle}}=2\,\mathrm{s}\), \(t_{\mathrm{delay}}=0.5\,\mathrm{s}\). Total session \(\approx N \times T_{\mathrm{pos}}\) for \(N\) turntable stops (36 or 72 presets).
Sequence duration is minutes. Blocking the UI thread would freeze the wizard. Progress/log callbacks marshal status back without giving UI Sapera handles.
Honest limits
- Hardware path is Windows + Sapera + specific BLE turntable commands — not portable as-is
- Settle times are empirical, not closed-loop vibration sensing
- Controller and CaptureStateMachine both model state — operators must not drive BLE manually mid-sequence
Reject invalid transitions
Why not “best effort” events
ProcessEvent returns false and logs REJECTED instead of forcing a state. Silent double-Snap mid-rotate is worse than a visible no-op.
Code: CaptureStateMachine::IsValidTransition / ProcessEvent
src/api/CaptureStateMachine.cppIsValidTransition tablesrc/capture/AutomatedCaptureController.cppWorker + ProcessNextPositionsrc/api/CaptureAPI.cppCamMatrix_StartCapture wiring