Terminating eyetracking --calib left the DSP eye camera stuck streaming on the
Frame. Capture now lets the bounded run end by itself, deleting images
meanwhile, and only kills as a last resort. Also hardens Ctrl-C cleanup and
heart-check error handling.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Watch for a second capture directory on every poll; stop and remove every
directory that appeared, and fail loudly if an eye image can't be deleted.
- Start the worker pool inside the cleanup block.
- Flat patches no longer rank first (zero-power SNR is 0, not infinity).
- Align eyes to the truly nearest frame.
- Keep patch grids as float arrays (a 300 s run no longer needs ~0.4 GB).
- heart-check: tolerate unusual flags, close the Health archive, give a clear
error when export.xml is missing, and build the lookup index once.
Found by a SWE-2 Max read-only review. Part of #27
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The capture tool's stdout is block-buffered, so waiting for its directory name
left every image on disk until the capture ended (3,554 PNGs in a 20 s run on
the Frame). Detect the new capture directory instead, decode in three worker
processes, and stop the capture if more than 900 images wait. On the Frame a
30 s run now peaks at 7 images on disk.
Part of #27
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds 'pulse', which captures the IR eye cameras through SteamVR's own
eyetracking --calib mode, reduces each image to patch averages and deletes it
immediately, then estimates pulse from skin brightness. Adds heart-check.py to
show OSC readings live and compare recordings with an Apple Health export or
CSV, and a synthetic Mac BLE strap for relay tests.
Part of #27
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>