โ† Kevin Yoder
Digital Dentistry ยท Flagship

AI Radiograph โ†’ Insurance-Narrative Pipeline

A dental radiograph goes in; a defensible, CV-grounded insurance-claim narrative comes out โ€” with the model documenting what a clinician confirmed, never diagnosing from the image itself.

Built 2026 Two-phase arc Reproducible clone

โš™ How it works ๐Ÿ“Š Results ๐Ÿ–ผ Screenshots โ†ป Reproduce the pipeline

01 Overview

Dental insurers want a written justification attached to a claim โ€” a short paragraph explaining why a crown, a root canal, or a bridge is medically necessary. Writing them is repetitive work: the same near-identical, copy-paste language, one claim after another.

The pipeline came together by connecting two projects I'd built independently. The first is an auto-narrative app that drafts that justification. The second is a dental computer-vision model trained on radiographs to do two things: identify and number the teeth โ€” the foundation everything else builds on โ€” and detect the pathology and restorations that public datasets can teach. Connecting the two makes an almost fully automatic tool: the vision model reads an attached radiograph, and the narrative app writes the justification from what it found. The piece that closes the gap to full accuracy is the treating dentist confirming the findings before anything is written; adding payer-specific claim details is the most recent component โ€” in place, but not yet fully tested.

The computer vision it runs on โ€” FDI tooth-numbering, per-tooth SAM masks, and the pathology stack (caries, PARL, and common restorations) โ€” is its own project: Dental Radiograph Computer Vision.

02 Why I built it

The first version of the narrative app took a shortcut that turned out to matter: it sent the radiograph straight to a vision LLM that both read the image and wrote the narrative in one pass. That single pass occasionally invented specifics โ€” millimetre measurements that were never taken, teeth mis-identified, diagnoses assumed โ€” and it produced patient-preference language that insurer-side AI screeners are known to discount as evidence of necessity.

Splitting perception from prose was the fix. In the shipped path the language model never sees the radiograph. Deterministic computer vision produces the findings; the clinician โ€” who already knows the diagnosis โ€” confirms them; and the LLM's only job is to document, not diagnose. That reframing is the whole point of the project, and it mirrors how a dentist actually works.

03 What I built & how it works

A deterministic vision pass, a clinician's confirmation, and a hard rule: the LLM composes only from validated findings, never from the image.

CV findings service FDI YOLOv8m ยท F1 0.9608 SAM 2.1-L + LoRA masks ยท 0.9657 pathology: caries ยท PARL ยท restorations image-quality grade โ†’ structured findings (JSON) narrative app findings โ†’ rules engine โ†’ suggestions clinician review ยท confirm / override 12 payer sets ยท patient-context gate text-only LLM โ†’ prose validators โ†’ audit-trail PDF findings

Fig. 1 โ€” a deterministic vision pass produces structured findings; the narrative app runs the clinician review, rules, safety gate, text-only composition, and export. The language model only ever sees the findings, never the radiograph.

  1. Detect โ€” the FDI detector numbers every tooth, SAM outlines each one, and the pathology stage flags caries, periapical lesions, and restorations. Every finding carries a confidence score.
  2. Review โ€” a human-in-the-loop modal lets the clinician anchor teeth, confirm or override rules-suggested procedures, and add prior-restoration metadata. Clinician knowledge overrides CV uncertainty.
  3. Ground โ€” the validated findings, not the image, are handed to a text-only LLM. Confidence tiers decide whether each finding is stated as fact, hedged as "evidence ofโ€ฆ", or omitted.
  4. Compose & check โ€” the model writes per-procedure prose; deterministic regex validators strip patient-preference language, map lay terms to clinical ones, and check for payer-required necessity keywords.
  5. Document โ€” an audit-trail PDF records the CV findings, the clinician's inputs, and the final narrative, so the basis for every claim is traceable.

04 ๐Ÿ›  Skills & tech used

Languages
Python 3.11 / 3.12 (async FastAPI)vanilla JavaScriptHTML / CSSBash
ML / AI
LLM-as-composerconfidence-tier promptingadversarial-reviewer optimizationLiteLLM gateway + fallback
Infra / MLOps
Docker / docker-composeCaddy reverse proxyTailscalethin-shim dependency contract
Frontend
Canvas 2D mask / bbox overlaysopacity compositinglive claim-strength meterHITL review modal
Data
12 provenance-tracked payer rulesetspatient-context effect matrixCDT coding modelheld-out FDI benchmarkWeasyPrint PDF
Techniques
test-driven developmentspec โ†’ plan โ†’ subagent workflowgraceful-degradation fallbacksafety-by-architecturecount-based micro-F1 eval

05 Notable challenges & decisions

Most of the interesting engineering was in the honesty: separating what the model may claim from what a human actually confirmed.

Architecture

The LLM never reads the radiograph

Rather than fight hallucination with prompt wording, I removed the opportunity for it. In the shipped path the model receives a compact findings dictionary and a confidence tier per item; it composes prose and nothing else. Interpretation lives entirely in the deterministic CV stack and the clinician's review โ€” the LLM cannot invent a measurement it was never given.

Safety

A matrix that suppresses harmful disclosures

Patient context is double-edged: "smoker" strengthens a deep-cleaning claim but weakens a bridge claim. So every disclosure is gated through a condition-by-procedure effect matrix. Helpful pairings become narrated facts; harmful ones are silently withheld from the LLM and surfaced to the clinician as an on-screen warning. Toggling a condition says "I have this information"; the matrix decides whether it helps.

06 Results

.9608
FDI detector F1, held-out 48-image / 295-tooth test set
12
payer rulesets, each with source provenance
5
patient-context conditions gated by the safety matrix

Sources: routed evaluation on the held-out test set. The FDI F1 is a count-based micro-F1 measured on images the detector never trained on โ€” full CV benchmarking detail lives on the Dental Radiograph Computer Vision page.

07 Screenshots

Both captures are the runnable, de-identified clone working end-to-end on a public sample radiograph โ€” no patient data.

The narrative generator UI: a periapical radiograph with per-tooth SAM masks in distinct colors, Universal tooth numbers, overlay toggles for teeth labels, masks, pathology, restorations, and FPD candidates, alongside a narrative-length slider, patient-context toggles, and a live claim-strength meter reading 58 percent with a per-category point breakdown.
The narrative generator: SAM tooth masks + FDI numbering + pathology overlays on a public sample radiograph, with a live claim-strength meter that shows how each input raises the score.
The Detect and Review modal: a per-tooth table listing anchor checkboxes, Universal and FDI numbers, detection confidence, findings such as caries with a score, and rules-suggested procedure chips (Core Buildup starred) plus a prior-restoration dropdown for each tooth.
Human-in-the-loop review before the insurance narrative is generated: the clinician anchors teeth, confirms or overrides the rules-suggested procedures, and records prior restorations. Suggestions are marked with a star; nothing is narrated until it is confirmed here.

08 Honest status

The shipped Phase-2 pipeline was built and runs. It was designed for real insurance claims in my own practice setting, but I have no usage logs to prove clinical submissions, so I make no claim of production caseload. The v2 web app is currently dormant โ€” it was paused at a checkpoint in mid-2026. The payer-specific claim-detail layer is the newest piece and the least exercised: it's built, but not yet validated against real claim adjudications.

What reproduces the whole flow today is a runnable, PHI-safe, de-identified clone that collapses the system into two co-located containers. It runs end-to-end on a single bundled CC BY 4.0 radiograph โ€” live CV detection, rules-engine suggestions, the 12-payer checklist, the approval-likelihood meter, overlay rendering, and the audit-trail PDF โ€” with the LLM narrative prose opt-in behind a local model. The clone carries no patient images, no real secrets, and no protected health information; that is the artifact behind the screenshots above.

AI Radiograph โ†’ Insurance-Narrative Pipeline โ€” Build Recipe

Take a bare machine to a running copy of an AI dental radiograph โ†’ insurance narrative pipeline. Upload a radiograph and a deterministic computer-vision pass detects + FDI-numbers the teeth (YOLOv8m), segments them (SAM 2.1), and runs a pathology stack (modality classifier, apex regressor, lesion / restoration / caries detectors). Those structured findings drive a tooth chart, image overlays, a rules-engine procedure suggester, a payer-specific documentation checklist, an approval-likelihood meter, and an audit-trail PDF โ€” and, optionally, an LLM writes the final insurance narrative from the findings (it never sees the radiograph on the grounded path).

The system runs as two co-located containers on one compose network โ€” a CV service and a narrative service. CPU-only โ€” no GPU required.

Status: โœ… Verified 2026-08-01 โ€” built from this clean clone with docker compose up --build. Both images build (cv โ‰ˆ 6.2 GB incl. ~1.1 GB of bundled weights + torch/SAM2; narrative โ‰ˆ 250 MB), both come up healthy, and the full pipeline ran end-to-end, key-free on a bundled CC BY 4.0 bitewing: the real detector found 10 FDI-numbered teeth with SAM masks and caries pathology (e.g. tooth #30 @ 0.33), the rules engine suggested โ˜… Core Buildup, the approval meter scored the claim at 58 % with a breakdown, the overlays rendered on the radiograph (see demo.png), and the WeasyPrint PDF export produced a valid PDF. The LLM-off gate returns a friendly notice, and with a local Ollama endpoint the full CV-grounded narrative generated live (_meta.path: "findings") โ€” real radiograph โ†’ real CV โ†’ real prose, no API key. All three tiers are covered โ€” the prebuilt tarball (docker save radiograph-cv radiograph-narrative, ~6069 MB, verified 2026-08-01), a from-source build, and bare-metal. The clean clone lives in clone/.

PHI-safety โ€” read this. This is a dental-imaging system, so be explicit: no patient data ships, by construction. The narrative service is stateless โ€” radiographs flow through RAM and are discarded; there is no database, no uploads dir, no bundled patient images. The author's CV research repo holds ~16 GB / ~100k radiograph-type images; that entire image surface is excluded โ€” the clone ships only source code, the bundled model weights (learned tensors, no recoverable patient images), and exactly two public, CC BY 4.0 research radiographs (samples/, see ATTRIBUTIONS.md) โ€” never a real patient image, never a bulk dataset. All secrets are placeholders.


What it is

Two services on one compose network:

  • cv (cv/, FastAPI, port 8237, internal) โ€” wraps a curated subset of the CV code + the production weights. POST /detect runs FDI detection โ†’ SAM masks โ†’ pathology and returns structured findings JSON. CPU-only (torch==2.11+cpu); a GPU is auto-detected if present.
  • narrative (narrative/, FastAPI, port 8248, published) โ€” the product: proxies images to cv, turns findings into a text prompt for an LLM, and adds the rules engine, 12 payer rulesets, a patient-context effect matrix, HITL review, overlays, the approval meter, and two WeasyPrint PDFs. No ML in-process.

Prerequisites

New machine? See ../SETUP.md for the base tools.

Tier You need
1 โ€” prebuilt container (recommended) Docker Desktop / Engine. Load the on-request radiograph-images.tar bundle and run โ€” nothing to build, no other deps.
2 โ€” build from source Docker Desktop / Engine. docker compose up --build compiles both images locally โ€” torch, SAM 2, all deps and the weights are baked into the cv image (a few minutes).
3 โ€” bare-metal A Linux/macOS host with Python 3.11, and the two services run as separate processes (the CV side needs torch/ultralytics/onnxruntime/opencv + sam2 from git; the narrative side needs WeasyPrint's system libs).

Tier 1 โ€” Run the prebuilt container(s) (recommended)

Availability: the prebuilt image bundle is available on request โ€” it is not published or linked anywhere. Ask Kevin for it, or build everything from source via Tier 2/3 below.

Both images ship in one tarball. Produce it once on a machine that has built them, then load-and-run anywhere with no build step:

# --- produce the tarball once (on a machine that has already built the images) ---
docker save radiograph-cv radiograph-narrative -o radiograph-images.tar   # ~6069 MB

# --- consume it anywhere (no --build; the images are already loaded) ---
docker load -i radiograph-images.tar
cd clone
docker compose up -d                  # โ†’ http://localhost:8248
curl -s localhost:8248/health         # โ†’ {"status":"ok"}

The tarball is ~6069 MB (5.9 GB) โ€” most of it is the cv image, which bundles ~1.1 GB of production weights plus CPU torch/SAM 2. That is the honest price of a self-contained, key-free CV pipeline: no download step, no API key. The opt-in LiteLLM gateway (--profile llm) uses a separate public image (ghcr.io/berriai/litellm) that is pulled on demand and is not in this tarball.

Tier 2 โ€” Build the image from source

cd clone
docker compose up -d --build          # โ†’ http://localhost:8248

Open http://localhost:8248, drag in samples/bitewing_demo.jpg, and click ๐Ÿ” Detect & Review. The real detector labels the teeth, SAM masks + FDI numbers + pathology badges render on the radiograph, and the rules engine suggests procedures โ€” all key-free.

First detection is slow. The cv container cold-loads the model stack on the first /detect (~1โ€“2 min on CPU incl. the 856 MB SAM base); subsequent calls are ~8โ€“13 s. A GPU is a pure speed-up, not a requirement. The build also takes a few minutes (torch + SAM 2 from source + a one-time timm cache warm).

Tier 3 โ€” Bare-metal (two processes, Linux/macOS)

cd clone
# CV service (needs torch/ultralytics/onnxruntime/opencv + sam2 from git; weights in cv/app/roboflow/)
cd cv && python -m venv .venv && . .venv/bin/activate
pip install torch==2.11.0 torchvision==0.26.0 --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt && pip install "git+https://github.com/facebookresearch/sam2.git"
python -c "import timm; timm.create_model('efficientnet_b0', pretrained=True, in_chans=4)"   # warm cache
ROBOFLOW_DIR=$PWD/app/roboflow python -m uvicorn inference_service:app --app-dir app --port 8237 &

# Narrative service (separate shell)
cd narrative && python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt          # + WeasyPrint OS libs (see narrative/Dockerfile)
INFERENCE_URL=http://localhost:8237 python -m uvicorn main:app --app-dir src --port 8248

Optional: the AI narrative (LLM)

The prose step talks to any OpenAI-compatible endpoint and is off by default โ€” the /analyze route returns a friendly notice until you enable it. Everything else (CV findings, overlays, rules, PDFs) works without it. See .env.example; the turnkey path is the bundled LiteLLM proxy:

# put a provider key in .env, then:
docker compose --profile llm up -d --build

Or point at any OpenAI-compatible base URL โ€” e.g. a local Ollama (ollama serve + ollama pull llama3.2): set LITELLM_URL=http://host.docker.internal:11434, LITELLM_KEY=ollama, LLM_CHAT_MODELS=llama3.2 and it runs key-free (this is how the live narrative above was verified).

Verify

curl -s localhost:8248/health                 # โ†’ {"status":"ok"}
curl -s localhost:8248/findings/health        # โ†’ inference ok
# Deterministic PDF export works without any LLM:
curl -s -X POST localhost:8248/export/pdf -H 'Content-Type: application/json' \
     -d '[{"procedure_type":"core_buildup","tooth":"30","narrative":"โ€ฆ","cdt_codes":["D2950"]}]' \
     -o out.pdf   # โ†’ a valid PDF

Then open the UI, upload samples/bitewing_demo.jpg, and click Detect & Review.

Provenance โ€” models, training data & hardware

Model weights (~1.1 GB, bundled). The author's custom-trained detectors โ€” a YOLOv8m FDI detector (F1 0.9608 on a held-out 48-image / 295-tooth benchmark), a SAM 2.1-L LoRA fine-tune (val Dice 0.9657, over Meta's SAM 2.1 Hiera-L base), and the pathology stack (modality ONNX + apex regressor on EfficientNet-B0, plus YOLOv11 lesion / restoration / caries detectors). These are learned tensors with no recoverable patient images โ€” PHI-safe to distribute.

Training data & licenses. The models were trained on an assembled corpus of 15+ public dental-radiograph datasets plus the author's own office radiographs (~150 hand-annotated for FDI). The principal public sources:

Dataset Role in training License
DenPAR โ€” Rasnayaka S., Leuke Bandara D., Jayasundara A., et al., "DenPAR: Annotated Intra-Oral Periapical Radiographs Dataset for Machine Learning," Sci Data 12, 1615 (2025); Zenodo DOI 10.5281/zenodo.16645076; paper DOI 10.1038/s41597-025-05906-9 per-tooth PA masks + FDI pairing; SAM-FT ground truth CC BY 4.0
DENTEX (panoramic challenge set) OPG pathology (PARL) + the separate OPG-FDI detector CC BY-NC-SA (non-commercial)
PRAD-5K โ€” Zhou Z. et al., "PRAD," MICCAI 2025, pp. 475โ€“484 (Springer); Nankai University restoration (RCF/DC/IM/DF) + PARL (AP) research/personal scope; ยฉ Nankai University
sam-ybj0w/dental-7vwaw (Roboflow Universe) bitewing caries recall booster CC BY 4.0
Additional: Mendeley bitewing/caries sets, NLM Open-i, Roboflow Universe ICCMS caries sets, Bitewing_allside caries / FDI / restoration supplements mixed โ€” CC BY 4.0 / CC BY-NC / Public Domain (per source)

The corpus carries mixed licenses, several explicitly non-commercial (DENTEX CC-BY-NC-SA, PRAD research-scope, some Mendeley sets CC BY-NC). The project is personal-use, non-commercial โ€” which is why the entire image corpus is excluded from this clone; only the learned weights and two CC BY 4.0 sample radiographs ship (see samples/ATTRIBUTIONS.md). Evaluation used strictly held-out benchmarks (the 48-image FDI test set; a public Mendeley 100-bitewing caries set, CC BY-NC 3.0), kept disjoint from the training data by perceptual-hash checks.

Training hardware & automation. The detectors' weights were produced on rented cloud GPUs โ€” Vast.ai and RunPod โ€” under a rent โ†’ train โ†’ export โ†’ download โ†’ tear-down discipline (pods destroyed after each run's weights are pulled; costs accounted per run). The FDI detector was pushed through a closed-loop retrain campaign (five cycles): each cycle added a data source, retrained on an H100/H200, and either adopted or rejected the result against a held-out floor. The adopted champion (Cycle 2, an H200 run, ~$1.94) reached F1 0.9608; the whole campaign cost โ‰ˆ $9.13 of ~$17 in credit. The honest outcome: a regression of F1 against training-instance count came out essentially flat, so later single-source cycles stopped helping โ€” a real ~0.96 data ceiling on the author's own sensor distribution, documented rather than papered over.

No training in this clone. The clone ships the finished weights and runs CPU-only โ€” there is no dataset, no GPU, and no retrain step here. Reproducing the training would require the public datasets above (per their individual licenses) and a GPU; this recipe reproduces the running pipeline.

What was stripped from the personal version (de-identification + minimalism)

  • The hardcoded LiteLLM master key (present in 5 places incl. source + Dockerfile) โ†’ removed everywhere; AI made opt-in (--profile llm, placeholder keys), the app now speaks to any OpenAI-compatible endpoint via env, and the model-group names are configurable.
  • The entire image/dataset surface (~16 GB / ~100k radiographs: datasets, annotation queues, outputs, test sets, even the training-run mosaics next to the weights) โ€” never copied. The clone is code + weights + two public samples.
  • .env secrets (Roboflow / HuggingFace / Label-Studio tokens) โ†’ never copied; a placeholder .env.example ships instead.
  • Personal paths in the CV code comments (C:\Users\โ€ฆ) โ†’ genericized. The CV code is a curated 28-file subset of the research monorepo (detector + SAM + pathology package only), not the full repo.
  • A CDN dependency โ€” the UI's html2canvas was loaded from jsDelivr โ†’ vendored locally so the app is self-contained.
  • Kept as-is (the author's call): the 12 payer rulesets with their real carrier names โ€” they encode public provider-reference documentation patterns and carry no PHI.

Known limitations (stated honestly)

Personal-LAN tool: no authentication, permissive CORS (*), and both services are stateless (nothing persisted). CPU inference is slow โ€” the first /detect cold-loads the models (~1โ€“2 min) and warm calls are ~8โ€“13 s for the full FDI+SAM+pathology stack (a GPU cuts this sharply). The cv image is large (~6 GB) because it bundles torch + SAM 2 + the weights. Put a reverse proxy with auth in front before exposing it. The modality classifier can mis-call BW vs PA on some inputs โ€” that is the real model's output, surfaced honestly.