Neural Capture · Demo: open Demo tour

Monograph · 04 · Capture loop

04

Core pipeline · flagship

Capture loop

Chapter contract

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.

FromEventTo
IdleStartCaptureCapturing
CapturingCaptureCompleteIdle
CapturingStartRotationRotating
RotatingRotationCompleteSettling
RotatingRotationFailedSettling (still settle)
SettlingSettlingCompleteIdle
ErrorReset / StopIdle
Invariant

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

Capture loop diagram
Fig. CAP-1

Worker loop. Move → settle → capture → save → next. Matches AutomatedCaptureController.

Sequence init workflow

StartSequence
  1. Reject empty positions or missing bluetooth/camera/session pointers
  2. Require BLE connected and camera count > 0
  3. Store positions; reset index; is_active=true
  4. Spawn worker_thread_
  5. Worker: set speeds → ReturnToZero/TiltToZero → sleep ~3 s
  6. While index < N and not stop: honor pause spinlock → ProcessNextPosition → index++
  7. On success: state Completed, notify, zero turntable again

Line-anchored · ProcessNextPosition

Behavior (as coded)
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_)
Timing defaults
\[ T_{\mathrm{pos}} \approx T_{\mathrm{move}} + t_{\mathrm{settle}} + t_{\mathrm{delay}} + T_{\mathrm{snap}} + T_{\mathrm{save}} \]

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).

Why a dedicated worker thread

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
Decision

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 table
src/capture/AutomatedCaptureController.cppWorker + ProcessNextPosition
src/api/CaptureAPI.cppCamMatrix_StartCapture wiring