Inspiration
Every 43 seconds, a child under five dies of pneumonia. That was about 740,000 children in 2019 (WHO).
The first test a health worker uses is simple: count the child's breaths for one full minute and compare the count with a cut-off for their age. People are bad at it. In a four-country trial, only 8 to 20% of health-worker counts on infants under two months were within two breaths of the true rate (Baker et al., 2019).
Every breath-counting app I found still asks you to tap the screen once per breath. Automated counters are separate hardware. Meanwhile the parent and the health worker already carry a camera.
So I asked: what if the phone could just watch the chest and count?
What it does
Breathwise turns any phone camera into a contactless breath counter that follows the WHO method.
- 📷 Point, don't touch. Aim the phone at a sleeping or calm child. Breathwise finds the breathing by itself and highlights the chest in green tiles as it locks on.
- 🫁 Every breath, live. A dot lands on the breathing trace and the phone gives a small vibration for each breath.
- ⏱️ The WHO minute. It counts 60 seconds of clean signal. If the child moves, the clock pauses instead of counting noise.
- 🩺 A clear answer. It applies the WHO IMCI fast-breathing cut-off for the child's age (≥60, ≥50 or ≥40 per minute) and walks through the IMCI danger signs. Any danger sign turns the result into "Seek care now."
- ✋ Human backup. A tap-count mode (the manual WHO method), plus a "tap along" check against the camera.
- 🌍 Built for the field. Spoken guidance, five languages (English, Français, Español, Kiswahili, አማርኛ) and a dark screen that won't wake the child.
- 🔒 Private by design. No account. Video never leaves the phone and is never saved.
How I built it
Stack: Expo SDK 57 · React Native 0.86 · VisionCamera 5 (frame output + worklets) · Skia · Reanimated 4 · RevenueCat
The breathing detector is classic signal processing running on the phone in real time. There is no ML model, no server and no training data of children.
- Pixels → 192 numbers. On the camera thread, each frame is shrunk to a 12×16 grid of brightness averages and then released. The video never reaches JavaScript.
- Keep only breathing. Slow exposure drift is divided out, whole-scene movement is detected, and every cell is band-pass filtered to 8–108 breaths/min.
- Cells vote. Every second, each cell's spectrum is computed. Rhythmic cells vote on a rate, with a check against counting a harmonic of the real rhythm.
- One clean trace. The cells that agree are merged with principal component analysis, which also lines up cells that brighten and darken in opposite directions.
- Count like a clinician. A peak tracker counts breaths. The final result checks the count against the spectral rate and the breath-to-breath rate to report a confidence level.
Validation: 13 end-to-end tests render synthetic scenes pixel by pixel and run them through the exact app pipeline.
- ✅ 14 to 75 breaths/min, all within ±2
- ✅ Chest motion under one pixel
- ✅ Heavy low-light sensor noise
- ✅ Bumped mid-count: the clock pauses and recovers
- ✅ No breathing at all: never locks on
Monetization with RevenueCat
Counting is free. Forever. The core breath count is never behind a paywall, and health-worker mode is free for life.
Breathwise Family (one entitlement, monthly or yearly, with a 7-day free trial) is for parents on a sick night:
- Unlimited children
- Breathing trends over time
- A PDF report for the doctor
- Sick-night recheck reminders
Every Family plan keeps Breathwise free for community health workers. The people who can pay fund the people who can't.
Built with react-native-purchases: a custom paywall built from Offerings, entitlement-gated features, restore, and Customer Center for self-service management. Tested end to end with RevenueCat Test Store.
Challenges I ran into
The core problem is finding a chest that moves one or two pixels under sensor noise, auto-exposure and hand shake. Four fixes made it work:
- Lock the camera. Exposure, focus and white balance are frozen once the shot is framed, so auto-adjustments can't look like breathing.
- Normalize slowly. Correcting brightness frame by frame leaked the breathing rhythm into every cell. A slow running average fixed it.
- Respect irregular breathing. Infants don't breathe like metronomes, so the rhythm window scales with the rate.
- Fail honestly. The detector refuses to lock when nothing is breathing, and tap mode is one tap away.
Accomplishments I'm proud of
- A working contactless breath counter in pure TypeScript signal processing, fast enough for the camera thread.
- A result screen that shows its work: the WHO cut-off, danger signs, a confidence level and the raw 60-second breathing trace.
- A business model with a conscience: the core feature is free forever.
What I learned
Following the clinical protocol beats adding features. Following the WHO protocol exactly (60 seconds, pause on movement, age cut-offs, danger signs) made Breathwise clearer and more trustworthy than any extra feature did.
What's next
- 🏥 A clinical agreement study against expert video-panel counts
- 📴 Offline health-worker mode with supervisor sync
- 🤖 Android tuning
- 🤝 Partnerships with community health programs
Demo note: the measurement footage was recorded in the iOS Simulator with a simulated baby, because the Simulator has no camera. On a phone, Breathwise uses the live camera. Music: "A Kind of Hope" by Scott Buckley, CC BY 4.0. Not a medical device. Breathwise counts breaths using WHO guidance; it does not diagnose.
Built With
- expo.io
- react-native
- react-native-vision-camera
- reanimated
- remotion
- revenuecat
- skia
- typescript
- zustand
Log in or sign up for Devpost to join the conversation.