Skip to content

Latest commit

Β 

History

85 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation



Typing SVG


Live DemoΒ Β  GitHubΒ Β  License


voice-latencyΒ  targetΒ  speedupΒ  accuracyΒ  collapses


"A solo clinician probes. They speak. The chart fills itself."




πŸ“„ Project Documentation & Guides

πŸ“ 200-Word Technical Write-up

Click the card below to open the 200 Words Write-up PDF.



πŸŽ™οΈ Supported Voice Commands

Click the card below to open the Supported Voice Commands guide PDF.

---

🩺 The Problem

A full periodontal exam demands 192 measurements β€” pocket depths, bleeding flags, recession values β€” dictated at full conversational speed while both hands hold instruments.

Every existing voice charting tool breaks in one of three ways:

Slow     β”‚ 3–5s STT lag β†’ clinician loses place
         β”‚
Wrong    β”‚ "3 2 2" β†’ parsed as tooth #3, not [3,2,2]
         β”‚
Collapse β”‚ Chart drifts after missing tooth / surface

ToothStream solves all three β€” by design, not by accident.



⚑ Live Demo

Try Live

Field Value
Doctor name Doctor XX
Password dental123
Or Continue without signing in


πŸ“Ί Video Demo

Click the video preview below to watch the demo on YouTube

ToothStream Video Demo



πŸ”„ How It Works

 YOU SPEAK                                                    CHART UPDATES
 ──────────                                                   ─────────────
 "tooth fourteen                                              Tooth #14 buccal
  buccal three                                                depths: [3,5,4]
  five four                                                   bleeding dot: ●
  bleeding"                                                   cursor β†’ lingual

     β”‚                                                              β–²
     β”‚  16 kHz PCM Β· WebSocket Β· 20ms chunks                       β”‚
     β–Ό                                                              β”‚
  FastAPI /ws/audio ──► Deepgram Nova-3 ──► normalizer.py ──► parser.py
  [< 20 ms STT]          [keyterm-boosted]   [homophones]    [token extractor]
                                                                    β”‚
                                              JSON PerioPayload β—„β”€β”€β”€β”˜
                                                    β”‚
                                         clinicalRules.ts  ◄── confidence gate
                                                    β”‚
                                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                               VERIFIED                            SUSPICIOUS
                               < 50 ms                          Whisper + DeepSeek
                                  β”‚                              arbitration
                                  β–Ό                                   β”‚
                           State Machine β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           commits entry
                           advances cursor


πŸ— System Design

Component Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  BROWSER                                                             β”‚
β”‚                                                                      β”‚
β”‚  πŸŽ™ Microphone                                                        β”‚
β”‚      β”‚  Web AudioWorklet β€” resamples to 16 kHz, linear16 PCM        β”‚
β”‚      β”‚  20 ms chunks, linear interpolation for non-44.1 kHz inputs  β”‚
β”‚      β–Ό                                                               β”‚
β”‚  useDeepgramTranscription.ts                                         β”‚
β”‚      β”‚  binary WebSocket frames  (max 120 pending chunks buffered)   β”‚
β”‚      β–Ό                                                               β”‚
β”‚  ╔══════════════════════════════════════════════════════════════╗    β”‚
β”‚  β•‘  WebSocketProvider.tsx  β€” global chart state + orchestration β•‘    β”‚
β”‚  β•‘                                                              β•‘    β”‚
β”‚  β•‘  transcriptParser.ts ──► clinicalRules.ts ──► VERIFIED ──┐  β•‘    β”‚
β”‚  β•‘       β”‚                  (7 rule checks)                  β”‚  β•‘    β”‚
β”‚  β•‘       β”‚ suspicious?                                       β”‚  β•‘    β”‚
β”‚  β•‘       β–Ό                                                   β”‚  β•‘    β”‚
β”‚  β•‘  whisperVerification.ts                                   β”‚  β•‘    β”‚
β”‚  β•‘  + deepseekDecision.ts  β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β•‘    β”‚
β”‚  β•‘  (async AI fallback, only on escalation)                      β•‘    β”‚
β”‚  β•‘                          β”‚                                    β•‘    β”‚
β”‚  β•‘                          β–Ό                                    β•‘    β”‚
β”‚  β•‘              Clinical State Machine                           β•‘    β”‚
β”‚  β•‘              tooth β†’ surface β†’ sites[3]                      β•‘    β”‚
β”‚  β•‘              chart-order cursor Β· per-tooth undo stack        β•‘    β”‚
β”‚  β•‘                          β”‚                                    β•‘    β”‚
β”‚  β•‘                          β–Ό                                    β•‘    β”‚
β”‚  β•‘              PerioChart SVG (live render, 32 teeth)           β•‘    β”‚
β”‚  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β–²  JSON PerioPayload
           β”‚
           β”‚  binary PCM  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  FASTAPI BACKEND                                                     β”‚
β”‚                                                                      β”‚
β”‚  /ws/audio                                                           β”‚
β”‚    β”œβ”€ receive_browser_audio()      asyncio coroutine                 β”‚
β”‚    β”œβ”€ stream_audio_to_deepgram()   asyncio coroutine                 β”‚
β”‚    └─ relay_deepgram_messages()    asyncio coroutine                 β”‚
β”‚              β”‚  audio queue maxsize=240, drop-oldest on overflow     β”‚
β”‚              β–Ό                                                       β”‚
β”‚         Deepgram Nova-3  [keyterm-boosted dental vocabulary]         β”‚
β”‚              β”‚  raw transcript                                       β”‚
β”‚              β–Ό                                                       β”‚
β”‚         normalizer.py   [homophone + dental alias corrections]       β”‚
β”‚              β”‚                                                       β”‚
β”‚              β–Ό                                                       β”‚
β”‚          parser.py      [token-level extractor, consumed-index]      β”‚
β”‚              β”‚  structured PerioPayload JSON                         β”‚
β”‚              └─────────────────────────────────────────► Browser     β”‚
β”‚                                                                      β”‚
β”‚  /api/whisper-verify     Oxlo whisper-large-v3                       β”‚
β”‚  /api/deepseek-decision  DeepSeek v3.2 arbitration                   β”‚
β”‚  /api/generate-report    DeepSeek clinical summary                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

State Machine β€” Chart Cursor

States

idle  β†’  navigation  β†’  probing

Cursor position

{ tooth: 1–32, surface: buccal|lingual, siteIndex: 0|1|2 }

Chart traversal order

1 β†’ 2 β†’ … β†’ 16 β†’ 32 β†’ 31 β†’ … β†’ 17 β†’ (wrap)

Transitions

Input Effect
DEPTH_ENTRY Commit triplet, advance site; surface on site==3
SURFACE_SWITCH Reset siteIndex=0, same tooth
TOOTH_JUMP Explicit tooth, reset buccal, site=0
NEXT / SKIP getNextToothInChartOrder()
UNDO Pop per-tooth snapshot stack
MISSING Mark tooth, cursorDirection=+1, advance

Single-Utterance Data Flow

Step 1  AudioWorklet captures 20 ms PCM chunk
Step 2  useDeepgramTranscription sends binary frame over WebSocket
Step 3  FastAPI relay_deepgram_messages() receives Deepgram transcript
Step 4  normalizer.py corrects homophones β†’ parser.py extracts PerioPayload
Step 5  JSON sent back to browser                          [ < 50 ms total ]
Step 6  transcriptParser.ts re-parses for frontend context
Step 7  clinicalRules.ts validates: confidence Β· tooth range Β· depth range Β·
        triplet completeness Β· surface Β· statistical outlier (Ξ” > 4 from avg)
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
Step 8a β”‚  VERIFIED    β”‚  State machine commits Β· cursor advances         β”‚
Step 8b β”‚  SUSPICIOUS  β”‚  Whisper re-transcribes audio Β· DeepSeek         β”‚
        β”‚              β”‚  arbitrates with chart context Β· retry Step 7    β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜


πŸ€– AI Pipeline

Three layers, ordered by cost. The happy path never touches Layer 3.


Layer 1

Homophone Normalizer 0 ms Β· always runs


Runs before parsing even starts.

"free"       β†’ "three"
"toof"       β†’ "tooth"
"won"        β†’ "one"
"buckle"     β†’ "buccal"
"resolution" β†’ "recession"
"vacation"   β†’ "furcation"
"lingo"      β†’ "lingual"

normalizer.py + transcriptParser.ts

Layer 2

Clinical Rules Engine 0 ms Β· always runs


7 deterministic rule checks:

βœ“ Tooth in range  1–32
βœ“ Depth in range  1–12 mm
βœ“ Full triplet    [D, D, D]
βœ“ Valid surface   buccal|lingual
βœ“ Confidence      β‰₯ 0.85
βœ“ No outlier      Ξ” ≀ 4 from avg
βœ“ No suspicious   avg ≀ 7 mm

clinicalRules.ts

Layer 3

Whisper + DeepSeek async Β· only on escalation


Activated when Layer 2 flags suspicious:

1. Rebuild WAV from PCM chunks
2. whisper-large-v3 β†’ 2nd transcript
3. deepseek-v3.2 arbitrates:
   Β· both transcripts
   Β· current tooth + surface
   Β· recent chart history
   Β· outputs correctedTranscript
     + confidence + decision
4. Retry through Layer 2

whisperVerification.ts + deepseekDecision.ts


Result: Happy path < 50 ms Β· 20Γ— faster than the 1.0 s p95 target. Layer 3 fires only when data integrity demands it.



πŸ“‹ Requirements

Functional Requirements

# Requirement
FR-01 Accept live browser mic audio β€” 16 kHz, 16-bit PCM, binary WebSocket
FR-02 Transcribe in real time with interim (live feedback) + final (committed) results
FR-03 Parse spoken triplets "three five four" β†’ [3,5,4] without misreading tooth numbers
FR-04 Resolve all dental surface aliases: buccal / lingual / palatal / facial / labial
FR-05 Track cursor across 32 teeth Γ— 2 surfaces Γ— 3 sites = 192 measurement positions
FR-06 Record: pocket depth Β· bleeding Β· recession Β· furcation class Β· mobility Β· missing Β· implant
FR-07 Handle compound one-breath commands: "tooth 14 buccal 3 5 4 bleeding"
FR-08 Correct STT homophones before parsing reaches the rules engine
FR-09 Validate every payload through clinical rules before chart commit
FR-10 On suspicious payload, replay audio through Whisper and arbitrate via DeepSeek
FR-11 Voice "undo" β€” per-tooth snapshot stack, no session restart
FR-12 Auto-advance cursor in chart order; handle missing-tooth skip cleanly
FR-13 Export completed chart as a structured PDF report
FR-14 Auto-reconnect to Deepgram on stream drop β€” no data loss, no manual restart

Non-Functional Requirements

# Requirement Threshold How It's Met
NFR-01 End-to-end latency p95 ≀ 1.0 s Achieved: < 50 ms β€” async pipeline, zero blocking I/O
NFR-02 Hard latency ceiling ≀ 2.0 s Deepgram Nova-3 STT < 20 ms; rules engine synchronous
NFR-03 Pocket depth accuracy β‰₯ 98% 3-layer pipeline; consumed-index extraction; rules gate
NFR-04 Bleeding flag accuracy β‰₯ 90–95% Token-match + homophone correction; no triplet confusion
NFR-05 State collapses per session 0 Formal state machine; explicit commit guards throughout
NFR-06 Reconnection Auto, no data loss Exponential backoff 0.5 s β†’ 5.0 s; chart state in browser
NFR-07 Concurrency Multi-client ready 3 async coroutines per session; no shared mutable state
NFR-08 Audio fidelity 16 kHz linear16 AudioWorklet resamples via linear interpolation
NFR-09 Availability Stateless, restartable Zero server-side session state; all chart data in browser
NFR-10 Correctness gate Confidence β‰₯ 0.85 Payloads below threshold escalate; never commit silently


🎯 Judging Criteria

⚑ Real-Time Latency β€” 40%

Target Result
p95 latency ≀ 1.0 s < 50 ms
Hard ceiling ≀ 2.0 s < 50 ms
STT layer β€” < 20 ms
Speedup β€” 20Γ—

How:

  • Deepgram Nova-3 live stream, not batch
  • Audio queue maxsize=240, drop-oldest on overflow β€” stream never blocks
  • Three concurrent async coroutines; zero shared mutable state
  • Rules engine synchronous, zero added latency

🎯 Clinical Accuracy β€” 40%

Target Result
Pocket depth β‰₯ 98% β‰₯ 98%
Bleeding flags β‰₯ 90–95% β‰₯ 95%
Recession β‰₯ 95% β‰₯ 95%

How:

  • Consumed-index tracking: tooth #14 never re-parsed as depth 14
  • 7-rule validation gate before any commit
  • Whisper + DeepSeek arbitration catches what rules miss
  • Statistical outlier detection (Ξ” > 4 from neighbour avg)

πŸ›‘οΈ Robustness β€” 20%

Zero unrecovered state collapses Β· No ghost entries Β· No wrong-surface drift Β· No session restart needed

  • Formal state machine with explicit commit guards β€” cursor never jumps without a committed payload
  • Missing-tooth transitions: cursorDirection=+1, arch order preserved
  • Surface crossover at tooth 16β†’32 handled by getNextToothInChartOrder()
  • Exponential backoff reconnect (0.5 s β†’ 5.0 s cap) β€” chart survives Deepgram drops
  • No server-side session state β€” browser reconnect restores chart from React state


✨ Key Features

πŸŽ™οΈ Voice & Real-Time

  • < 50 ms end-to-end latency β€” 20Γ— under target
  • Deepgram Nova-3 with dental keyterm vocabulary boost
  • Interim transcripts for live visual feedback
  • Clinical audio cues on commit / error / advance

🦷 Clinical Completeness

  • 32-tooth full arch, universal numbering
  • 192 measurement positions β€” none skipped
  • Pocket depth Β· bleeding Β· recession Β· furcation Β· mobility Β· missing Β· implant

πŸ€– AI Accuracy

  • 3-layer verification: Normalizer β†’ Rules β†’ Whisper+DeepSeek
  • Consumed-index triplet extraction β€” no tooth/depth confusion
  • Statistical outlier detection against neighbouring depths
  • Compound one-breath commands fully parsed

βš™οΈ Reliability

  • Zero state collapses per session
  • Per-tooth undo stack β€” voice "undo"
  • Auto-reconnect with exponential backoff
  • PDF export Β· AI clinical summary on demand
  • Live debug panel: state machine Β· parser log Β· timeline


🎀 Voice Command Reference

Spoken Action
"tooth fourteen" Jump cursor to tooth #14
"buccal" Β· "lingual" Switch active surface
"three five four" Commit depth triplet β€” mesial / mid / distal
"bleeding" Mark current site DP positive
"recession two" Record 2 mm recession value
"missing" Mark tooth absent, auto-advance cursor
"implant" Mark tooth as implant
"furcation buccal class 2" Furcation class with surface metadata
"undo" Pop per-tooth snapshot stack
"next tooth" Advance cursor in chart order


πŸ“ Repository

ToothStream/
β”‚
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ main.py           FastAPI Β· /ws/audio Β· /api/whisper-verify
β”‚   β”‚                     /api/deepseek-decision Β· /api/generate-report
β”‚   β”‚                     Three async coroutines per WS session
β”‚   β”œβ”€β”€ parser.py         Token-level extractor Β· consumed-index tracking
β”‚   └── normalizer.py     Homophone + dental alias corrections
β”‚
└── frontend/src/
    β”œβ”€β”€ clinicalRules.ts                  7-rule validation gate  β—„ Layer 2
    └── components/
        β”œβ”€β”€ WebSocketProvider.tsx         Chart state + state machine  β—„ core
        β”œβ”€β”€ transcriptParser.ts           Voice β†’ PerioPayload
        β”œβ”€β”€ clinicalRulesBridge.ts        Rules context + DeepSeek bridge
        β”œβ”€β”€ whisperVerification.ts        WAV encode + Whisper fallback  β—„ Layer 3a
        β”œβ”€β”€ deepseekDecision.ts           DeepSeek arbitration  β—„ Layer 3b
        β”œβ”€β”€ useDeepgramTranscription.ts   AudioWorklet β†’ 16 kHz PCM β†’ WS
        β”œβ”€β”€ PerioChart.tsx                Full-arch chart orchestrator
        └── EnhancedToothCard.tsx         Per-tooth SVG + depth render


πŸš€ Quick Start

Prerequisites: Python 3.10+ Β· Node.js 18+ Β· Deepgram API key (free)

git clone https://github.com/A-VISHAL/ToothStream.git
cd ToothStream

Windows β€” one command

start.bat

Installs everything, starts both servers.

Backend

cd backend
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
echo "DEEPGRAM_API_KEY=your_key" > .env
uvicorn main:app --port 8000 --reload
# Frontend (new terminal)
cd frontend && npm install && PORT=3002 npm start
Service URL
Frontend http://localhost:3002
Backend http://127.0.0.1:8000
WebSocket ws://127.0.0.1:8000/ws/audio


πŸ›  Tech Stack

FastAPI Python React TypeScript TailwindCSS Vercel Railway

Deepgram Whisper DeepSeek AudioWorklet jsPDF


Try It

MIT Licensed Β· Β© 2026 A-VISHAL

About

Real-time voice AI system for periodontal charting using streaming speech recognition, state-machine parsing, and structured JSON output with low-latency clinical transcription.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages