rdsx.dev

Sun Jun 15 2025

FluiDiy Labs

FluiDiy Labs

React Native

OpenCV

Computer Vision

CIELAB

Colorimetry

Medical Device

IVDR

Expo

Healthcare

An IVDR-oriented smartphone urinalysis pipeline. ArUco fiducials, CIELAB color calibration, and a per-analyte nearest-neighbor classifier (with TFLite MLP upgrade path) running on-device in React Native.

FluiDiy Labs is a smartphone-based urinalysis pipeline built for clinical accuracy, not convenience. It uses a single-shot capture model, ArUco fiducial homography, and two-stage CIELAB color calibration to read 14-parameter urine test strips on-device. No cloud. No real-time inference. No auto-exposure.


FluiDiy Labs App Interface

The Problem

Colorimetric urine dipstick reading is sensitive to lighting, camera hardware, white balance, and timing. Existing smartphone apps (e.g., Perfeqt) read strips without a printed color reference in frame, relying on the phone's auto-exposure to "get it close enough." Public reviews of these products report inconsistent results from one test to the next — exactly what you'd expect from a system with no calibration standard in the image.

I approached the problem as an embedded vision engineer, not a mobile app developer. The goal is device-independent, traceable, auditable color measurement.

Architecture

Single-Shot Capture with Locked Camera

The camera is not a scanner. It is a measurement instrument.

  • Locked exposure, ISO, white balance, and torch — react-native-vision-camera v4 with manual camera control
  • Quality gates on the preview frame — luma check, tilt check (accelerometer), card-centering check
  • Single full-resolution capture triggered when all gates pass (or by the 60–120 s chemistry timer)
  • No on-device model in the frame processor — avoids battery drain and UI jank

The capture returns a 12 MP raw image with full EXIF metadata and camera settings for audit.

ArUco Fiducials + Homography

The FluiDiy reference card contains four ArUco 4x4 markers. After detection with cv.aruco.detectMarkers (sub-pixel corner refinement), the pipeline computes a homography to warp the card into a 600x900 canonical image. This eliminates perspective distortion and makes pad coordinates deterministic.

  • Reprojection error per fiducial: target < 1 px (bench-measured on synthetic renders; real-card measurement not yet surfaced in the debug UI)
  • Pipeline runtime: target ~5 ms for detection + warp on a mid-range Android phone (not yet benchmarked on-device)
  • No strip detection model — the strip lies in known coordinates inside the canonical frame, with pose refined against the well via minAreaRect

Two-Stage Color Calibration

The card is the color standard. We do not trust the camera.

Stage 1 — Grayscale Tone Curve Seven grayscale patches (L* 5 to 96) recover a per-channel monotonic tone curve mapping observed linear RGB to expected linear RGB.

Stage 2 — 3x3 Color Correction Matrix Twelve ColorChecker patches (a representative subset of the X-Rite ColorChecker Classic, spanning skin tones, primaries, and neutrals) are sampled in CIELAB (D65). A least-squares fit produces a 3x3 CCM minimizing CIEDE2000 error.

  • Mean deltaE00 after calibration — target: < 3.0 (current measured: ~4.5, pending ground-truth Lab data and real-card tuning)
  • Max deltaE00 — target: < 5.0 (current measured: ~8.0)
  • Reduction vs. uncalibrated: meaningful, exact percentage pending formal measurement

All color math (sRGB -> XYZ -> Lab, CIEDE2000, matrix ops) follows the standard CIE formulas; validation against a published reference test dataset is planned but not yet complete.

Post-Warp Quality Control

After homography, the pipeline runs QC in canonical space:

  • Blur: Variance of Laplacian on the strip ROI
  • Glare: Specular mask — pixels above a brightness threshold in any reference patch
  • Shadow: Luminance gradient across the white reference patch

Any failure aborts the scan with a user-readable reason. The scan is not "retried" — the user repositions and re-captures.

Strip Chemistry Timing

Each analyte has a read window defined by the strip manufacturer (e.g., leukocytes: 60–120 s, glucose/bilirubin/micro-albumin: 30 s, ketone/ascorbate: 40 s, specific gravity: 45 s, remaining analytes: 60 s). The app enforces this per-analyte. Pads captured outside their window are flagged timing_invalid in the output — not silently dropped.

Per-Pad Classification

For each of the 14 analytes, the v1 classifier is nearest-neighbor in calibrated CIELAB space (CIEDE2000 distance to a per-analyte reference table) — no training required, fully interpretable, and the deliberate v1 choice rather than a placeholder. A small per-analyte TFLite int8 MLP is the planned v2 upgrade once enough labeled reference data has been collected per analyte; it is not yet trained or shipped.

  • Confidence: Derived from deltaE distance to the nearest reference class
  • v1 scope: 5 priority analytes (glucose, protein, nitrite, leukocytes, ketone) are clinically targeted; remaining 9 are computed and displayed with a "research use only" flag
blog imageblog image

Card Lot Validation

A QR code on the card encodes lot number, expiry date, and unique serial. The app:

  • Verifies the signature (HMAC in v1)
  • Rejects expired cards
  • Rejects already-used serials (tracked in expo-sqlite)

Card Lot Validation

This is not anti-piracy. It is an IVDR audit trail requirement — every result must be traceable to a specific card lot.

Offline-Only

  • No network calls
  • No analytics
  • No remote inference
  • All data in expo-sqlite, encrypted at rest
  • The "Developer Debug" screen exports a full trace (original image + JSON dump) for clinical validation

Tech Stack

  • Mobile framework: React Native / Expo (dev client)
  • Camera: react-native-vision-camera v4
  • Native CV: react-native-fast-opencv (aruco, imgproc, calib3d)
  • ML runtime: CIELAB nearest-neighbor classifier today; react-native-fast-tflite (int8 per-analyte MLPs) planned for v2
  • Storage: expo-sqlite
  • Color science: Custom CIELAB pipeline (sRGB -> XYZ -> Lab -> deltaE2000)
  • Geometry: OpenCV homography + warpPerspective
  • Reference strip: XSH-ERA URS-14, 14-parameter, CE/IVD/ISO marked, anti-ascorbic-acid (Anti-VC) chemistry

Why This Is Hard

Smartphone urinalysis is not a camera problem. It is a colorimetry problem inside a camera.

The phone's auto-white-balance, auto-exposure, and varying sensor characteristics make two captures of the same strip under the same light produce different RGB values. Without fiducial-based homography and a reference color standard in the frame, the app is measuring the phone's auto-processing, not the strip.

Existing apps solve this by asking users to calibrate against a white surface, or skip calibration entirely. We solve it by making the card itself the calibration target — 7 grayscale patches and 12 color patches in the same frame as the strip. The strip is never read without its standard.

It is also not just a geometry-and-color problem. Strip manufacturers do not standardize pad order across brands or even across SKUs from the same manufacturer — the printed order on a bottle's reference chart is not guaranteed to match the physical order of pads on the strip itself. We discovered this firsthand: our reference strip's true grip-to-tip pad order is the reverse of its own bottle chart, confirmed only by physically photographing a dipped strip and cross-referencing three independent landmark colors. Any pipeline that hardcodes pad order from a datasheet without this verification step will silently mislabel every result.

Current Status

  • Locked-camera single-shot capture
  • ArUco detection + homography (rotation/perspective invariant)
  • Two-stage color calibration (tone curve + CCM) — functional, accuracy tuning in progress
  • Post-warp QC (blur, glare, shadow)
  • Card lot validation + single-use enforcement
  • Per-analyte timing enforcement
  • CIELAB-based nearest-neighbor classification (v1 production method, not a placeholder)
  • Physical strip pad-order verified against XSH-ERA URS-14 reference card
  • Full audit trace with Developer Debug mode
  • Ground-truth Lab reference data per analyte class (bench validation)
  • Per-analyte TFLite MLP training (v2, not started)
  • Real-card calibration accuracy measurement (ΔE currently above target on available data)
  • Clinical performance validation vs. Clinitek Status reference

Selected For

  • InnoTrack Digital Health Incubator — startup accelerator program at TU Ilmenau, providing mentorship, funding, and strategic market-readiness support
  • WeCare Award — MINT & Medical Innovation — 2nd Place, recognized for technical feasibility, market potential, and patient impact

Conclusion

FluiDiy Labs is not a health app. It is a colorimetric measurement instrument that happens to run on a phone. Every design decision — from locked camera settings to CIEDE2000 calibration to single-use card enforcement — serves a single goal: making the smartphone camera produce clinically traceable, device-independent color measurements.

The pipeline is built to be explainable, auditable, and eventually IVDR-submissible. The architecture is in place; what remains is the unglamorous work of ground-truth data collection and clinical validation — the difference between a working prototype and a diagnostic device.