An open research prototype for andrology & embryology image analysis. Sperm and follicle measurements are computed inside the browser via ONNX Runtime Web. The localhost server only ever sees structured JSON. Every deterministic verdict cites its ruleset. Every measurement carries a physician sign-off.
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Browser (this site) │ │ Local FastAPI (your Mac) │
│ ──────────────────────── │ │ ───────────────────────── │
│ static/app.js + detect.js │ JSON │ uvicorn server.app:app │
│ • ONNX YOLO (WebGPU/WASM) │ ─────► │ • Review queue: AI_DRAFT → │
│ • deterministic JS fallback │ ◄───── │ UNDER_REVIEW → SIGNED_OFF │
│ • SHA-256 in browser │ │ • WHO 2021 / Gardner rules │
│ • no fetch(image) ever │ • SQLite (fv_local.db) │
│ │ │ • PDF/JSON signed report │
│ Images NEVER leave browser. │ │ • /narrate → Ollama LFM2.5 │
└──────────────────────────────┘ └──────────────────────────────┘
127.0.0.1:9000 (static) 127.0.0.1:8000 (uvicorn)
The infrastructure, the contracts, and the audit trail. The pieces you can independently verify on your own machine:
Pure JS, runs in the browser, polarity-agnostic (dark-on-bright
and bright-on-dark). Unit-tested against synthetic ground
truth in tests/test_detect_blobs.mjs — 6 cases including
touching blobs, speckle rejection, and the area-gate threshold.
WHO 2021 5th-percentile cut points and Gardner blastocyst grading,
covered by tests/test_rules.py — 35 boundary cases
(OAT combinations, missing fields, C-grade blastocysts, exactly-at-limit).
Every image analysis is gated by the study's "sheet contract"
(chamber, field width, capture time). Calibration is fixed per study,
so a series is always on the same scale.
tests/test_protocol.py — 19 cases.
Deterministic deltas, rates per day, ±15% flagging rule, divide-by-zero
guards. tests/test_progression.py — 8 cases, including
a 0-baseline guard.
The next training run is bit-reproducible: SEED=0 pinned in
train/train_sperm_yolo.py, torch.use_deterministic_algorithms
enabled, participant holdout written to models/fold_split.json
and committed. validation/validate.py computes a 95% bootstrap
CI on mAP50 from per-frame scores — the v2 report will report
0.83 ± 0.04 instead of a single number.
| Run command | What it proves |
|---|---|
python3 tests/run_all.py |
73 individual checks across 4 test files. Single CI command. |
node validation/test_exif.mjs |
EXIF DateTimeOriginal parser round-trips a real JPEG header. |
python3 validation/validate.py |
Honest gate verdict; refuses to enable a sub-gate model. |
python3 validation/smoke_test.py |
Server end-to-end: HITL, protocol 422, calibration 409, feed timeline. |
# 1. Python server (review queue + SQLite)
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn server.app:app --port 8000
# 2. Serve the browser app (any static server)
python3 -m http.server 9000 --directory static
# 3. Open http://localhost:9000
# Images are processed in the browser. Nothing is uploaded.
# Only structured JSON crosses 127.0.0.1.
| Component | Artifact | Proof | License |
|---|---|---|---|
| Sperm detector (v1) | models/sperm_yolov8n.onnx |
VISEM-Tracking (sperm-net, HF): 29,196 expert-annotated frames, participant-held-out | Dataset CC BY 4.0; ultralytics AGPL-3.0 (train-time) |
| Sperm fallback | static/detect.js |
Auditable algorithm; unit-tested; no ML claim | our code |
| Rules engine | server/rules.py |
WHO 2021 5th percentile; Gardner 1999; 35 boundary tests | our code |