Skip to content

Feature/contact quality#36

Open
boringethan wants to merge 22 commits intonextfrom
feature/contact-quality
Open

Feature/contact quality#36
boringethan wants to merge 22 commits intonextfrom
feature/contact-quality

Conversation

@boringethan
Copy link
Copy Markdown
Contributor

No description provided.

boringethan and others added 22 commits April 15, 2026 14:16
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ntics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a convenience method that runs a brief (default 1 s) scan with camera
mask 0xFF on both modules, collects contact-quality warnings via a
callback, and returns a ContactQualityResult. Thread the new
contact_quality_callback kwarg through ScanWorkflow.start_scan into the
create_science_pipeline call so the monitor's warnings reach the caller.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ill alive

If _stream_loop is blocked in dev.read when stop_streaming's 2s join
times out, the old teardown unconditionally nulled expected_size and
data_queue. When dev.read then returned, the next loop iteration passed
expected_size=None into dev.read and pyusb raised TypeError from
array.array('B', length * b"\x00"). This was first hit by the 1 s
run_contact_quality_check quick-check, where LEFT histo never receives
a chunk before scan-end and the read loop is stuck.

Only null the buffers after confirming the thread has exited; if it is
still alive, log a warning and leave them intact so the leaked thread
exits cleanly once dev.read errors out. Also add an explicit
stop_event check at the top of _stream_loop so teardown is deterministic
and does not depend on a dev.read timeout firing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds an ``error`` field to ``ContactQualityResult`` and populates it from
``run_contact_quality_check``:

- ``start_scan`` returning False -> ok=False, error="Failed to start scan"
- All connected sides received 0 USB chunks -> ok=False, error notes the
  likely cabling/FPGA cause
- One side received 0 chunks -> ok=True with a per-side note (single-side
  scenarios remain valid)

Also raises the quick-check minimum duration from 1 s to 3 s; the previous
default couldn't survive scan-teardown overhead + camera-enable + warmup-
discard and frequently produced empty acquisitions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ed contention

Observed worst-case response latency was ~1.08 s when streaming on IF1 has
just been armed — the MCU's IF0 command handler takes noticeably longer to
service the request under that contention. The previous 0.3 s timeout
caused ``_send`` to return as "no response", and the eventual stale
response then poisoned the next packet ID, breaking subsequent commands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously run_contact_quality_check skipped the camera-configure step that
the play-button path performs via startConfigureCameraSensors, which could
leave cameras in a state that caused flaky enable_camera timeouts during
the quick-check scan. Now always run a ConfigureRequest(0xFF, 0xFF) first,
block on the configure worker via on_complete_fn + _config_thread.join(),
and abort with a descriptive error if configuration fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
the app auto-flashes at startup; reconfiguring mid-session fails with
not-READY errors. Quick-check should follow the same contract as normal
scans — cameras must already be configured.
ContactQualityMonitor now emits an INFO log line whenever a warning
latches, including the raw value, the absolute threshold compared
against, the pedestal + named constant that make up that threshold,
and the streak counter (for POOR_CONTACT) or "immediate" marker (for
AMBIENT_LIGHT). Latch clears are logged at DEBUG.

run_contact_quality_check now logs a summary block after the scan
thread joins with elapsed time, requested duration, absolute
thresholds, per-side chunk counts, a bullet list of each warning, and
the final ok/error decision.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contact-quality INFO log lines now show L1..L8 / R1..R8 instead of
L0..L7 / R0..R7 to match how cameras are numbered in clinical docs and
the app UI. The raw camera_id field on ContactQualityWarning stays
0-indexed; only the formatted string label is shifted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…o 1s; log per-camera status

Replaces per-frame streak-based POOR_CONTACT logic with an average-based
evaluation. ContactQualityMonitor.update_light now accumulates each
light-frame raw_light_mean; a new finalize() method computes the
per-camera average and emits one POOR_CONTACT warning per camera whose
average falls below pedestal + LIGHT_MEAN_THRESHOLD_DN. Ambient-light
per-frame latching behavior is unchanged.

run_contact_quality_check now constructs the monitor explicitly and
plumbs it through ScanWorkflow.start_scan and create_science_pipeline
via a new contact_quality_monitor kwarg, so it can call finalize() and
per_camera_summary() after the scan thread joins. The returned
warnings list merges live ambient warnings (from the callback) with
averaged poor-contact warnings (from finalize), de-duped by
(camera_id, warning_type). An INFO line is logged for each camera
with light_frames, avg_light_mean, ambient_latched, and a status of
OK / AMBIENT_LIGHT / POOR_CONTACT / AMBIENT_LIGHT, POOR_CONTACT /
NO DATA.

Default duration_s drops back to 1.0 and the floor to 1 s; the app
now pushes trigger config before invoking the check so 1 s acquisitions
are viable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…w=10)

ContactQualityMonitor.update_light now maintains a per-camera rolling
window of the last LIVE_LIGHT_WINDOW_FRAMES (=10) raw_light_mean values
in addition to the existing cumulative light_sum/light_count. Once the
window is full, the rolling average is compared against
pedestal + LIGHT_MEAN_THRESHOLD_DN on every new frame. A single latching
POOR_CONTACT warning is returned when the rolling average dips below
threshold; the latch clears when the average recovers, so subsequent
dips can re-fire. The cumulative finalize() path is untouched, keeping
the quick-check behavior exactly as before, and the dedup-by-
(camera_id, warning_type) in Interface.run_contact_quality_check
continues to suppress duplicate POOR_CONTACT between the live callback
and finalize() paths.

per_camera_summary() now additionally reports rolling_avg_light_mean
and contact_latched for end-of-check diagnostics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… don't collide

Previously _state_for(camera_id) keyed state by cam_id alone, which
caused left cam 0 and right cam 0 to share a single _CameraState.
Whichever side updated last overwrote the other in per_camera_summary()
and ambient-latch suppressed the second side's warnings. State is now
keyed by (side, cam_id); finalize() and per_camera_summary() iterate
over the tuple keys, and reset() accepts optional side/camera_id to
clear all / a side / a specific entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Accumulate dark_sum/dark_count per (side, cam_id) in update_dark() and
surface dark_frames/dark_mean_avg in per_camera_summary(). The end-of-
check INFO log in run_contact_quality_check now reports dark-frame count
and average alongside light-frame stats, and a camera seen only via dark
frames still produces a summary row.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…g window

The rolling-window check is for live continuous scans where transient dips
matter. For the 1s quick-check, drop live POOR_CONTACT warnings and use
finalize()'s cumulative average so the warning value matches the
per-camera summary log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
….py, gitignore superpowers docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant