Challenge entry for the Presage SmartSpectra SDK Challenge. Code: https://github.com/474443481/slow-breath-coach
π‘ The idea
Most breathing apps count seconds and assume you followed along. Slow-Breath Coach does not assume anything: it watches your chest through a plain webcam with the Presage SmartSpectra SDK, measures your actual breathing waveform, and scores how well you tracked the pacer after each session.
The target is 5.5 breaths per minute β the resonance pace where six seconds in and six seconds out line up with heart-rate rhythms. The pacer circle expands as you inhale, contracts as you exhale, and the score tells you how close your real chest motion came to that rhythm.
π¬ Demo
βΆ Watch the 30-second demo: demo.webm β 10 MB, webm, direct play
(Also committed at github.com/474443481/slow-breath-coach β pacer circle, live waveform and the follow score, recorded straight from the app's own /demo canvas.)
30 seconds, canvas-rendered, self-recorded by the page itself (/demo?record=1). The demo replays a synthetic breathing signal through the exact same scoring function used on live camera captures, so the UI is honest even without a face in frame.
π§ How it works
Three metric channels from the SDK (requestedMetrics: [0, 2, 15]): the chest breathing trace, breathing rate, and pulse. Frames go from the browser (JPEG, 15 fps) to a local Node server, which decodes them and feeds the SDK through a forked worker process over IPC.
Two SDK quirks ate most of my debugging time β worth documenting for the next person:
-
Timestamps must be epoch microseconds, and they must be gap-free. I first passed process uptime. The SDK silently treated the frames as days old, stalled the pipeline, and the session died at exactly 151 frames β every single time. Then real-world browser capture jitter (a dropped frame here, a 40 ms stall there) trips the SDK's gap detector:
SmartSpectra detected a gap between camera frame timestamps.The fix: a virtual 15 fps clock, +16 667 Β΅s per frame, re-anchored toDate.now() * 1000on every new worker. -
IPC must use
serialization: "advanced". A plainchild_process.forkturnsUint8Arrayframe buffers into plain objects on the other side, andsendFramerejects them with a confusing type error.
The score. Every second the server takes the last 30 s of the smoothed upper-chest trace and computes a normalized cross-correlation against the pacer's cosine reference, maximized over lags of Β±half a period (a steady lag is fine β you may breathe slightly behind the circle β but the rhythm must match). That blends with a rate gate from zero-crossings: score = 100 Γ (0.6Β·corr + 0.4Β·rateGate). A noise-only trace can fake a high correlation, but the rate gate kills it. Validated on synthetic signals from perfect following (1.00) down to pure noise (0.12).
Survivability. The native SDK pipeline wedges in long real sessions, so the HTTP server never loads the SDK itself. The worker is a disposable fork that self-recycles (no face for 140 frames, no metrics for 400, 9 s of silence) and a tiny supervisor respawns it β the dashboard websocket never dies with the SDK.
π Run it
git clone https://github.com/474443481/slow-breath-coach
cd slow-breath-coach
npm install @smartspectra/node-sdk jpeg-js
PRESAGE_API_KEY="<your key>" node coach_server.mjs
-
http://localhost:8815/β camera capture page (consent gate, then keep the tab in the foreground) -
http://localhost:8815/dashboardβ pacer circle + live waveform + score -
http://127.0.0.1:8815/demo?record=1&secs=30β the canvas demo above, and it records itself
Free API key: physiology.presagetech.com (100 credits on the free tier).
π Consent and Data Handling
- Consent first. The capture page shows a blocking consent notice explaining what is captured and where it goes; nothing starts until you press I agree.
- What leaves your machine. JPEG frames go to the local Node process and are forwarded to the Presage cloud API for physiological inference β that is the SDK's architecture (see Presage's Telemetry and Privacy documentation). The SDK also reports anonymous usage telemetry to Presage.
- What is stored. Nothing, anywhere. The server keeps a bounded in-memory ring buffer for the live chart and discards it on restart. No frames, no video, no waveforms are written to disk. Closing the capture page stops the stream.
- No medical claims. This is a breathing-pacing toy, not a diagnostic device. Its numbers are not medical measurements and it makes no health claims.
π― What I learned
Camera pipelines are 20% vision and 80% plumbing. This week a webcam was held hostage by a QQ video call, then by the Windows Camera app, then by a quietly stopped Frame Server service, then by zombie device handles inside Edge β and underneath all of that, the SDK wanted a frame clock more regular than any real camera delivers. The virtual clock fixed the SDK; the process firewall fixed everything the SDK still throws at you.
The nicest surprise: once the timestamps were honest, the correlation between "am I following the circle" and "what the SDK sees in my chest" was immediately visible on the waveform β no calibration, no tuning.
Top comments (0)