Skip to content

SEEDLESS frames: first-N-frames unseeded-TA scan mode (host-side) #146

Open

Description

@boringethan

SDK side of OpenwaterHealth/openmotion-bloodflow-app#361 (SEEDLESS Frames, formerly NO_TA). Experimental engineering-test feature requested by @bahartl: run the first N frames of a scan with the seed off, the TA pulsing 2 ms (deliberately inducing TA self-lasing), and camera exposure at 2295 µs, then revert to normal scan parameters mid-scan.

Approved design spec: https://github.com/OpenwaterHealth/openmotion-sdk/blob/claude/github-issue-link-3ca73e/docs/superpowers/specs/2026-07-15-seedless-frames-design.md (laser-engineer sign-off 2026-07-16).

Everything is host-side register writes — no firmware or FPGA changes (the TA FPGA is an edge-triggered one-shot, so TA_PULSE_WIDTH is writable mid-scan without a trigger restart).

Scope:

  • ScanRequest.seedless_frames: int = 0 (0 = off)
  • omotion/seedless.py — SeedlessController: register snapshot via I2C read-back, pre-trigger apply (seed gains → 0, TA → raw 6250, EE/OPT_PULSE_WIDTH_UL widened to raw 7813, exposure 0xFF), frame-ID-driven restore at frame N with strict ordering (TA width → 50 ms wait → safety limits → seed gains → exposure; reverse order latches TA_shutdown), idempotent force-restore on all teardown/cancel paths
  • ScanWorkflow hooks: pre-trigger apply, on_row frame-N watch, finally/cancel restore; abort pre-trigger if apply fails
  • Trigger config: LaserPulseSkipDelayUsec → 2500 when seedless, so scheduled dark frames stay outside the 2295 µs exposure window
  • FrameClassificationStage: tag frames 1..N seedless, N+1..N+80 seedless_tx (guard band for exposure smear); dtype <U8 → <U12; excluded from BFI/BVI + dark correction like warmup; N recorded in scan metadata
  • Unit tests (no hardware): restore ordering, latch idempotency, snapshot-fallback, classification tags
  • Bench verification per spec §Testing

Deliberate safety note: the 2 ms TA pulse exceeds the 1.000 ms safety-FPGA pulse-width upper limit; widening the UL for the seedless window was explicitly authorized by Ethan for this engineering test. The controller restores baseline limits on every exit path, and a console power-cycle also clears them (volatile registers).

App-side UI (engineering-mode checkbox + N spinner) stays on OpenwaterHealth/openmotion-bloodflow-app#361.

Activity

  1. 19 remaining items

  2. boringethan commented on Jul 18, 2026

    @boringethan
    ContributorAuthor

    Bench session 2026-07-17 (evening): DEBUG_FLAG_CAMERA_CROP × seedless, rebase, two structural fixes

    Branch rebased onto latest next (clean; suite grew to 573 passing) and the seedless experiment re-run with the camera crop (1720×1280, right 200 columns dropped) per Ethan's request. All four regime × crop combinations captured on sensor-fw 1.8.2-dev.1, N=25, mask 0x66.

    Crop effect on the contrast numbers (frames 11..25 seedless vs light, pedestal-subtracted)

    scan (left / right) seedless K light K separation (light K / seedless K)
    boosted, full frame 0.095 / 0.089 0.399 / 0.406 4.19 / 4.57
    boosted, cropped 0.086 / 0.081 0.382 / 0.389 4.44 / 4.82
    stock, full frame 0.120 / 0.128 0.393 / 0.400 3.27 / 3.13
    stock, cropped 0.111 / 0.121 0.389 / 0.396 3.49 / 3.28
    • Cropping lowers measured K in every regime — seeded frames by ~4%, unseeded by ~10% — while intensity rises slightly (e.g. light 243→248): the dropped right-edge columns are dimmer (vignetted), and that intensity nonuniformity was inflating σ/µ beyond true speckle contrast. Unseeded frames benefit more because their K is small enough for the nonuniformity term to matter proportionally more.
    • Intensity congruence is unaffected (boosted/light = 1.037 L / 1.029 R).
    • Net: crop slightly sharpens the seeded/unseeded contrast separation (4.2→4.4 L, 4.6→4.8 R).
    • Crop verified real: every frame's histogram totals 2,201,606 (1720×1280) vs 2,457,606 full-frame.

    Two structural defects this session exposed and fixed (both bench-only discoveries)

    1. Mid-scan rate-LL re-arm was deterministically latching the safety FPGA. The seedless trigger override (LaserPulseSkipDelayUsec=2500) makes each dark frame's return interval 22.5 ms — below the 23.125 ms baseline RATE_LL — so the frame-N restore's re-arm guaranteed a rate_lower_limit_fail latch at the next dark frame of EVERY seedless scan (visible as the trailing safety_se=4 in all previous telemetry; normal scans clear this limit by only ~75 µs). The mid-scan restore now defers the rate-LL re-arm to the teardown restore, which runs after stop_trigger.
    2. The 50 ms fault-clear pulse was marginal on the EE FPGA — it failed twice in a row, and the stale latch starved two scans to 0 frames (TA_shutdown held; fsync also stops ≈2 counts in, mechanism console-side, worth a firmware look). The clear now asserts 300 ms, verifies via STATUS (0x24) read-back, retries 3×, and apply() aborts pre-trigger if the fault won't clear.

    Also found (SDK-side, pre-existing): firmware configure_camera_sensor short-circuits when isConfigured && isPowered, so debug-flag changes (crop/raw) do NOT take effect on already-up cameras — a camera power-cycle before configure is required. Worth documenting on the sensor-fw side.

    Commits: 5e10167 (apply-lock, rebased), 8e0…/latest fix: verify+retry safety-fault clear; defer rate-LL re-arm to teardown, spec updates. Suite: 573 passed, 0 failed. All laser/safety registers verified restored after every run.

  3. boringethan commented on Jul 18, 2026

    @boringethan
    ContributorAuthor

    Per-camera, pipeline-corrected contrast (refines the pooled quick-look numbers above)

    Cameras = mask 0x66 → positions 1, 2, 5, 6 per side (pipeline gains g = 4, 2, 2, 4). Corrections now mirror the real pipeline: pedestal 128 (pedestal_for_fw for FW > 1.5.2), dark correction with variance decomposition (dark.py), and shot-noise subtraction var −= ADC_GAIN·mean·g_cam (shot_noise.py, ADC_GAIN = 0.0815 DN/e⁻). Dark baseline = each scan's 10 seed-off preamble frames — justified because the NRM scan's true terminal dark reads u1 = 128.0 (= pedestal exactly), i.e. seeded CW background ≈ 0 on this bench; 15 s scans contain no usable interior scheduled dark (interval 600), and the BST terminal stop frame caught light (stop-timing artifact — do not use as a dark).

    Crop scans (1720×1280), frames 11..25 seedless vs light, K = σ_corrected/µ_dc:

    scan / side / cam (g) seedless µ_dc / K light µ_dc / K separation
    BST L1(4) 256.6 / 0.0690 246.7 / 0.3261 4.7
    BST L2(2) 239.6 / 0.0895 232.2 / 0.4327 4.8
    BST L5(2) 278.3 / 0.0893 266.0 / 0.4343 4.9
    BST L6(4) 251.2 / 0.0669 244.1 / 0.3280 4.9
    BST R1(4) 174.1 / 0.0645 169.0 / 0.3300 5.1
    BST R2(2) 186.9 / 0.0689 182.9 / 0.4439 6.4
    BST R5(2) 189.4 / 0.0771 183.8 / 0.4420 5.7
    BST R6(4) 175.5 / 0.0662 169.9 / 0.3313 5.0

    (NRM regime table in the analysis script; seedless K per camera matches BST within ~0.01.)

    Observations for the scientists:

    1. Seeded light K splits cleanly by gain family: g=4 cams (1,6) ≈ 0.33; g=2 cams (2,5) ≈ 0.44 — a real optical/speckle-sampling difference the pooled 0.39 was averaging over.
    2. Unseeded K is nearly camera-independent after shot subtraction (0.065–0.090 across all 8 cam-sides) — consistent with a genuine low-coherence contrast floor rather than optics-dependent speckle.
    3. Shot-noise subtraction matters most exactly where it should: seedless frames on g=4 cams drop ~12% (0.079→0.069) vs <1% on bright seeded frames — K²_shot/K² is largest when K is small.
    4. Corrected seedless K is the same in stock and boosted regimes (e.g. L1: 0.0707 vs 0.0690) — K is intensity-independent once shot noise is removed, which is the internal-consistency check on the transfer-function experiment: the boost buys SNR and mean-congruence, not contrast.
    5. Fully-corrected separation: 4.7–6.8× per camera.

    Not applied vs. the full pipeline: dark-interval interpolation (n/a in 15 s scans), calibration C_min/C_max and BFI/BVI conversion, timestamp repair (irrelevant to K).

  4. boringethan commented on Jul 18, 2026

    @boringethan
    ContributorAuthor

    All-8-camera C_min / C_max measurement (Ethan's framing: seedless → C_min, static-phantom light → C_max)

    Per Ethan: the purpose of the seedless frames is to determine C_min, and the light frames — on a static phantom — measure C_max. Full 8-camera coverage per module, boosted seedless regime, crop on, sensor-fw 1.8.2-dev.1, pipeline-corrected (pedestal 128, dark correction from seed-off preamble, shot-noise subtraction with the per-camera gain map). Per-frame K averaged as average-of-ratios (consistent with the C_max convention fix in 8532853).

    Method note — 16-camera scans stall: full 0xFF/0xFF scans stalled the sensor histogram streams (left died at frame 51, right at 89; a rerun at frame 0; uniform across cameras = firmware TX stall, not a camera fault). This is the known parked backpressure limit (ticket #116 / PR #113). All-8 coverage was captured instead as two half-mask scans — 0x66 (cams 1,2,5,6) + 0x99 (cams 0,3,4,7) — same phantom, same session; 4 cams/side streams flawlessly (~3020 chunks, 604/604 frames, RESTORE ALL OK on every run).

    side cam gain C_min (seedless K) C_max (light K) C_max/C_min
    left 0 16 0.0745 0.3271 4.4
    left 1 4 0.0690 0.3261 4.7
    left 2 2 0.0895 0.4327 4.8
    left 3 1 0.0967 0.5189 5.4
    left 4 1 0.1049 0.5233 5.0
    left 5 2 0.0893 0.4343 4.9
    left 6 4 0.0669 0.3280 4.9
    left 7 16 0.0750 0.3325 4.4
    right 0 16 0.0780 0.3362 4.3
    right 1 4 0.0645 0.3300 5.1
    right 2 2 0.0689 0.4439 6.4
    right 3 1 0.1087 0.5171 4.8
    right 4 1 0.0986 0.5240 5.3
    right 5 2 0.0771 0.4420 5.7
    right 6 4 0.0662 0.3313 5.0
    right 7 16 0.0856 0.3452 4.0

    Structure worth the scientists' attention:

    • C_max orders cleanly by gain family: g=1 (cams 3,4) ≈ 0.52 · g=2 (2,5) ≈ 0.43–0.44 · g=4 (1,6) ≈ 0.33 · g=16 (0,7) ≈ 0.33–0.35. The center (low-gain) cameras see the highest speckle contrast; the g=16 edge cams land at the g=4 level, not lower.
    • C_min is much flatter (0.065–0.109) but not featureless: the g=1 cams show the highest C_min (0.097–0.109) — their shot-noise term is smallest, so more residual variance survives the correction; whether the remainder is instrumental or a real coherence floor difference is a question for the team.
    • Separation is 4.0–6.4× on every camera — a usable C_min/C_max span across the whole array.
    • Seedless (C_min) intensity on the g=16 edge cams runs ~97–154 DN — dimmer but still well-resolved above the preamble dark baseline.

    Data: science_out/ scans SEEDLESS_{NRM,BST}_CROP (mask 0x66) and SEEDLESS_{NRM,BST}_CROP_M99 (mask 0x99), with matching telemetry CSVs. Self-lase ≤ ~2 s per scan throughout (watchdog armed, never needed on the half-mask runs).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions