Two-way, real-time communication between a Deaf patient who signs American Sign Language (ASL) and a speaking clinician in an emergency room. Everything runs live on one MacBook. Nothing is pre-recorded, and the AI output is never faked.
Research prototype. SignWave is not a certified medical interpreter. Do not use it for clinical decisions. Use a qualified ASL interpreter for care.
Natural signing is switched off for now. The patient view offers one sign at a time only. The natural-signing code is commented out in
medbridge/server.pyandweb/src/components/PatientView.tsx; the natural-signing parts of this README describe it for when it is switched back on.
| Natural signing (default) | One sign at a time (precise) | |
|---|---|---|
| Model | Uni-Sign (ICLR 2025), pose-only, fine-tuned on OpenASL | ASL Citizen ST-GCN (Microsoft, NeurIPS 2023) |
| Input | A whole sentence, signed naturally. Rest your hands to finish. | One sign, then lower your hands. |
| Output | Top-3 closest phrases from an editable ER phrase bank (data/er_phrases.json), plus the model's free translation | Top-5 signs from a 129-label ER vocabulary (data/medical_vocab.json) |
| Quality | Right ER phrase ranked 1st 53%, in top 3 66% (held-out Deaf signers). Free translation of ER phrases is usually wrong. | 86% top-1 / 98.5% top-5 on held-out Deaf signers (ER vocabulary) |
It cannot:
That is why every patient message is confirmed by the patient, and why unconfirmed model output is always labelled as such on the doctor's screen.
Why these two models: no open model for continuous ASL → English translation performs better (checked October 2026).
Uni-Sign is the strongest open continuous model. ASL Citizen is the strongest open isolated-sign model and is trained on webcam video from 52 Deaf or hard-of-hearing signers.
All numbers come from running our live pipeline code on held-out data (scripts/eval_*.py).
| Component | Test | Result | Published |
|---|---|---|---|
| Natural signing (Uni-Sign) | 60 held-out OpenASL test clips, lightweight pose | BLEU-4 15.5, chrF 37.7 | BLEU-4 22.7 (full 976-clip test set) |
| Quality vs. model confidence | Above-median score: chrF 42.6. Below: chrF 21.2 (r = 0.64) | — | |
| Translation latency (M4 Max, MPS) | p50 498 ms, max 1.5 s after the sentence ends | — | |
| Natural signing, ER phrase bank | 220 bank phrases built from 5 held-out ASL Citizen signers' signs, 58-phrase bank | right phrase 1st 53%, top-3 66% (chance 1.7%); free translation ~0% | — |
| Phrase ranking latency | p50 ~140 ms extra | — | |
| Live camera pipeline | MacBook camera, M4 Max | 30 fps in both modes (camera limit) | — |
| Precise (ASL Citizen) | 150 random held-out clips, all 2,731 signs | top-1 60.7%, top-5 88.0% | 59.5% / 82.7% |
| 339 held-out clips of ER signs, ER vocabulary | top-1 86.1%, top-5 98.5% | — | |
| Doctor ASR (whisper-large-v3-turbo) | 20 ER sentences × 4 synthetic voices | WER 1.5%, medical-term recall 100% | — |
| Caption latency | p50 358 ms compute + 600 ms end-of-speech wait | — |
Notes:
performance) scored lower (BLEU-4 11.4) and runs at about 6 fps, so lightweight is used.python scripts/eval_asr.py --live with a real speaker to measure the real case.ASR_ENGINE=elevenlabs, ElevenLabs Scribe v2 for the final captions): on the same 80
clips on an M2 Pro, WER 0.9%, medical-term recall 100%, p50 514 ms / p95 769 ms, against Whisper's 5.6%, 94% and
616 / 659 ms on that machine. Nearly all of the gap is one voice (macOS "Aman": Whisper 18.8%, Scribe 0%); on the
other three both are near 1%. Reproduce with python scripts/eval_asr.py --engine elevenlabs. Mac A (patient laptop, runs everything)
Logitech cam ─► vision.py camera thread (unmirrored frames, memory only)
├─ natural ─► rtmlib whole-body pose ─► sentence segmenter ─► Uni-Sign (mT5) on MPS ─► top-3 sentences
├─ precise ─► MediaPipe Holistic ─► sign segmenter ─► ST-GCN ─► top-5 signs ─► translate.py
└─► landmarks (WebSocket, patient view only) + MJPEG preview
Mic ─► asr.py: Silero VAD ─► utterance segmenter ─► mlx-whisper ─► interim / final captions
└─► final caption ─► text2sign.py: word → ASL Citizen sign (Deaf signer motion, local cache)
or sign.mt (missing words only) ─► joined 3D motion ─► patient's signing figure
hub.py: session state, role-checked actions ─► WebSocket snapshots ─► /patient and /doctor
GLOSS_TRANSLATOR=llm, Claude rephrases precise-mode sign labels. Natural-signing output is never rewritten by an LLM.Requirements: Python 3.11, Node 20+, Homebrew ffmpeg (only for fetch_eval_clips.py).
python3.11 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python scripts/fetch_models.py # ~3 GB: Uni-Sign, ASL Citizen weights + gloss list, whisper
python scripts/build_sign_lexicon.py # one-time ~10 min: signing motion for common doctor words
(cd web && npm install && npm run build)
cp .env.example .env # then set CAMERA_INDEX / MIC_NAME
python scripts/check_devices.py # finds the Logitech camera + mic, checks permissions
python -m medbridge.server
| Who | Where |
|---|---|
| Patient (Mac A, full screen) | http://localhost:8000/patient |
| Doctor (second window on Mac A) | http://localhost:8000/doctor |
| Doctor (second Mac, same Wi-Fi) | http://<mac-a-name>.local:8000/doctor |
PATIENT_LAN=1 to allow the patient view from another machine.cd web && npm run dev, then open http://localhost:5173/patient.
The dev server proxies /ws, /video.mjpg and /api to port 8000.Tips:
Step-by-step test examples for one Mac (signs, sentences, doctor lines, what to record) are in docs/testing.md.
pytest # 69 tests: parity with upstream preprocessing, segmenters, hub, server, ASR
(cd web && npx vitest run) # frontend helpers
python scripts/fetch_eval_clips.py --n 60 && python scripts/eval_unisign.py --pose-mode lightweight
python scripts/eval_islr.py --n-random 150 --per-gloss 3
python scripts/eval_phrase_bank.py # natural signing + ER phrase bank
python scripts/eval_asr.py # add --live for a real speaker
METRICS_LOG=1 python -m medbridge.server # logs timings/scores/labels (no text/audio/video) to data/metrics.jsonl
Live signing protocol (Phase 6):
data/er_phrases.json. Judge whether the meaning is right in the top-1 and in the top-3.| Symptom | Fix |
|---|---|
| "Camera unavailable" / black frames | System Settings → Privacy & Security → Camera: enable the app running Python (Terminal, iTerm or VS Code), then restart that app. Check CAMERA_INDEX with scripts/check_devices.py. Continuity Camera (an iPhone) can take index 0. |
| No captions; mic level flat | Privacy & Security → Microphone, same as above. Set MIC_NAME=Logitech to use the webcam mic. |
module 'mediapipe' has no attribute 'solutions' | Install exactly mediapipe==0.10.21. Later versions removed solutions. |
| CoreML error from onnxruntime (YOLOX shape) | The person detector must stay on CPU. Only the pose model runs on CoreML (make_pose in unisign_runner.py). |
| Low camera fps in natural mode | The System panel on the doctor view shows fps; expect about 30. Use NATURAL_POSE_MODE=lightweight and keep the laptop plugged in. |
| Doctor's Mac can't connect | Use the same Wi-Fi, keep HOST=0.0.0.0, and allow incoming connections for Python in the macOS firewall prompt. |
GLOSS_TRANSLATOR=llm shows "literal" | No or invalid Claude credentials (ANTHROPIC_API_KEY or ant auth login), or the network is down. The app falls back to literal text by design. |
| MPS errors | PYTORCH_ENABLE_MPS_FALLBACK=1 python -m medbridge.server |
ASR_ENGINE=elevenlabs is set: then the audio of each thing the doctor
says is sent to ElevenLabs for the final caption.SIGN_MT=0 to keep everything on the Mac; missing words then show as text chips.ELEVENLABS_API_KEY is set, the text of each confirmed patient message is sent to ElevenLabs, which
returns the audio the doctor view plays. Leave the key empty to keep this off.medbridge/ server.py hub.py vision.py segmenter.py asr.py translate.py metrics.py
unisign/ (trimmed Uni-Sign pose model) unisign_runner.py
islr.py stgcn/ (vendored ASL Citizen ST-GCN, MIT)
web/ React + Vite + Tailwind + Motion: patient (dark) and clinician (light) views
src/theme.css holds every colour, as five themes (classic is the default; cougar, ocean, midnight, sage);
add ?theme=NAME to the address to try one, or edit the file and `npm run build`
scripts/ check_devices, fetch_models, fetch_eval_clips, eval_unisign, eval_islr, eval_asr
data/ medical_vocab.json, er_phrases.json (everything else here is downloaded and gitignored)
tests/ pytest suite
medbridge/unisign/ are adapted from https://github.com/ZechengLi19/Uni-Sign.
The upstream repository has no license file, so treat this use as research and evaluation only.medbridge/stgcn/LICENSE).
The dataset (only its split CSVs and a few test videos are fetched) is under the Microsoft Research License Terms in use.txt.mlx-whisper.Two-way, real-time communication between a Deaf patient who signs American Sign Language (ASL) and a speaking clinician in an emergency room. Everything runs live on one MacBook. Nothing is pre-recorded, and the AI output is never faked.
Research prototype. SignWave is not a certified medical interpreter. Do not use it for clinical decisions. Use a qualified ASL interpreter for care.
Natural signing is switched off for now. The patient view offers one sign at a time only. The natural-signing code is commented out in
medbridge/server.pyandweb/src/components/PatientView.tsx; the natural-signing parts of this README describe it for when it is switched back on.
| Natural signing (default) | One sign at a time (precise) | |
|---|---|---|
| Model | Uni-Sign (ICLR 2025), pose-only, fine-tuned on OpenASL | ASL Citizen ST-GCN (Microsoft, NeurIPS 2023) |
| Input | A whole sentence, signed naturally. Rest your hands to finish. | One sign, then lower your hands. |
| Output | Top-3 closest phrases from an editable ER phrase bank (data/er_phrases.json), plus the model's free translation | Top-5 signs from a 129-label ER vocabulary (data/medical_vocab.json) |
| Quality | Right ER phrase ranked 1st 53%, in top 3 66% (held-out Deaf signers). Free translation of ER phrases is usually wrong. | 86% top-1 / 98.5% top-5 on held-out Deaf signers (ER vocabulary) |
It cannot:
That is why every patient message is confirmed by the patient, and why unconfirmed model output is always labelled as such on the doctor's screen.
Why these two models: no open model for continuous ASL → English translation performs better (checked October 2026).
Uni-Sign is the strongest open continuous model. ASL Citizen is the strongest open isolated-sign model and is trained on webcam video from 52 Deaf or hard-of-hearing signers.
All numbers come from running our live pipeline code on held-out data (scripts/eval_*.py).
| Component | Test | Result | Published |
|---|---|---|---|
| Natural signing (Uni-Sign) | 60 held-out OpenASL test clips, lightweight pose | BLEU-4 15.5, chrF 37.7 | BLEU-4 22.7 (full 976-clip test set) |
| Quality vs. model confidence | Above-median score: chrF 42.6. Below: chrF 21.2 (r = 0.64) | — | |
| Translation latency (M4 Max, MPS) | p50 498 ms, max 1.5 s after the sentence ends | — | |
| Natural signing, ER phrase bank | 220 bank phrases built from 5 held-out ASL Citizen signers' signs, 58-phrase bank | right phrase 1st 53%, top-3 66% (chance 1.7%); free translation ~0% | — |
| Phrase ranking latency | p50 ~140 ms extra | — | |
| Live camera pipeline | MacBook camera, M4 Max | 30 fps in both modes (camera limit) | — |
| Precise (ASL Citizen) | 150 random held-out clips, all 2,731 signs | top-1 60.7%, top-5 88.0% | 59.5% / 82.7% |
| 339 held-out clips of ER signs, ER vocabulary | top-1 86.1%, top-5 98.5% | — | |
| Doctor ASR (whisper-large-v3-turbo) | 20 ER sentences × 4 synthetic voices | WER 1.5%, medical-term recall 100% | — |
| Caption latency | p50 358 ms compute + 600 ms end-of-speech wait | — |
Notes:
performance) scored lower (BLEU-4 11.4) and runs at about 6 fps, so lightweight is used.python scripts/eval_asr.py --live with a real speaker to measure the real case.ASR_ENGINE=elevenlabs, ElevenLabs Scribe v2 for the final captions): on the same 80
clips on an M2 Pro, WER 0.9%, medical-term recall 100%, p50 514 ms / p95 769 ms, against Whisper's 5.6%, 94% and
616 / 659 ms on that machine. Nearly all of the gap is one voice (macOS "Aman": Whisper 18.8%, Scribe 0%); on the
other three both are near 1%. Reproduce with python scripts/eval_asr.py --engine elevenlabs. Mac A (patient laptop, runs everything)
Logitech cam ─► vision.py camera thread (unmirrored frames, memory only)
├─ natural ─► rtmlib whole-body pose ─► sentence segmenter ─► Uni-Sign (mT5) on MPS ─► top-3 sentences
├─ precise ─► MediaPipe Holistic ─► sign segmenter ─► ST-GCN ─► top-5 signs ─► translate.py
└─► landmarks (WebSocket, patient view only) + MJPEG preview
Mic ─► asr.py: Silero VAD ─► utterance segmenter ─► mlx-whisper ─► interim / final captions
└─► final caption ─► text2sign.py: word → ASL Citizen sign (Deaf signer motion, local cache)
or sign.mt (missing words only) ─► joined 3D motion ─► patient's signing figure
hub.py: session state, role-checked actions ─► WebSocket snapshots ─► /patient and /doctor
GLOSS_TRANSLATOR=llm, Claude rephrases precise-mode sign labels. Natural-signing output is never rewritten by an LLM.Requirements: Python 3.11, Node 20+, Homebrew ffmpeg (only for fetch_eval_clips.py).
python3.11 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python scripts/fetch_models.py # ~3 GB: Uni-Sign, ASL Citizen weights + gloss list, whisper
python scripts/build_sign_lexicon.py # one-time ~10 min: signing motion for common doctor words
(cd web && npm install && npm run build)
cp .env.example .env # then set CAMERA_INDEX / MIC_NAME
python scripts/check_devices.py # finds the Logitech camera + mic, checks permissions
python -m medbridge.server
| Who | Where |
|---|---|
| Patient (Mac A, full screen) | http://localhost:8000/patient |
| Doctor (second window on Mac A) | http://localhost:8000/doctor |
| Doctor (second Mac, same Wi-Fi) | http://<mac-a-name>.local:8000/doctor |
PATIENT_LAN=1 to allow the patient view from another machine.cd web && npm run dev, then open http://localhost:5173/patient.
The dev server proxies /ws, /video.mjpg and /api to port 8000.Tips:
Step-by-step test examples for one Mac (signs, sentences, doctor lines, what to record) are in docs/testing.md.
pytest # 69 tests: parity with upstream preprocessing, segmenters, hub, server, ASR
(cd web && npx vitest run) # frontend helpers
python scripts/fetch_eval_clips.py --n 60 && python scripts/eval_unisign.py --pose-mode lightweight
python scripts/eval_islr.py --n-random 150 --per-gloss 3
python scripts/eval_phrase_bank.py # natural signing + ER phrase bank
python scripts/eval_asr.py # add --live for a real speaker
METRICS_LOG=1 python -m medbridge.server # logs timings/scores/labels (no text/audio/video) to data/metrics.jsonl
Live signing protocol (Phase 6):
data/er_phrases.json. Judge whether the meaning is right in the top-1 and in the top-3.| Symptom | Fix |
|---|---|
| "Camera unavailable" / black frames | System Settings → Privacy & Security → Camera: enable the app running Python (Terminal, iTerm or VS Code), then restart that app. Check CAMERA_INDEX with scripts/check_devices.py. Continuity Camera (an iPhone) can take index 0. |
| No captions; mic level flat | Privacy & Security → Microphone, same as above. Set MIC_NAME=Logitech to use the webcam mic. |
module 'mediapipe' has no attribute 'solutions' | Install exactly mediapipe==0.10.21. Later versions removed solutions. |
| CoreML error from onnxruntime (YOLOX shape) | The person detector must stay on CPU. Only the pose model runs on CoreML (make_pose in unisign_runner.py). |
| Low camera fps in natural mode | The System panel on the doctor view shows fps; expect about 30. Use NATURAL_POSE_MODE=lightweight and keep the laptop plugged in. |
| Doctor's Mac can't connect | Use the same Wi-Fi, keep HOST=0.0.0.0, and allow incoming connections for Python in the macOS firewall prompt. |
GLOSS_TRANSLATOR=llm shows "literal" | No or invalid Claude credentials (ANTHROPIC_API_KEY or ant auth login), or the network is down. The app falls back to literal text by design. |
| MPS errors | PYTORCH_ENABLE_MPS_FALLBACK=1 python -m medbridge.server |
ASR_ENGINE=elevenlabs is set: then the audio of each thing the doctor
says is sent to ElevenLabs for the final caption.SIGN_MT=0 to keep everything on the Mac; missing words then show as text chips.ELEVENLABS_API_KEY is set, the text of each confirmed patient message is sent to ElevenLabs, which
returns the audio the doctor view plays. Leave the key empty to keep this off.medbridge/ server.py hub.py vision.py segmenter.py asr.py translate.py metrics.py
unisign/ (trimmed Uni-Sign pose model) unisign_runner.py
islr.py stgcn/ (vendored ASL Citizen ST-GCN, MIT)
web/ React + Vite + Tailwind + Motion: patient (dark) and clinician (light) views
src/theme.css holds every colour, as five themes (classic is the default; cougar, ocean, midnight, sage);
add ?theme=NAME to the address to try one, or edit the file and `npm run build`
scripts/ check_devices, fetch_models, fetch_eval_clips, eval_unisign, eval_islr, eval_asr
data/ medical_vocab.json, er_phrases.json (everything else here is downloaded and gitignored)
tests/ pytest suite
medbridge/unisign/ are adapted from https://github.com/ZechengLi19/Uni-Sign.
The upstream repository has no license file, so treat this use as research and evaluation only.medbridge/stgcn/LICENSE).
The dataset (only its split CSVs and a few test videos are fetched) is under the Microsoft Research License Terms in use.txt.mlx-whisper.