Solo-build case study · Radiology imaging

From the scanner.To the signed report.One system owns the chain.

A complete radiology imaging chain, built solo in 33 commits: an offline-first edge gateway that scanners push into, a multi-tenant cloud archive speaking pure DICOMweb, a GPU-adaptive browser viewer with MPR and 3D volume rendering, and an AI agent that reads scans by driving that viewer itself. This page explains the system in plain language, then quotes the code.

RoleSolo design and engineering
TimelineJun-Jul 2026, 33 commits
StackTypeScript, Next.js 16, Cornerstone3D, Postgres, Orthanc, Python
SurfaceLive viewer + cloud PACS + edge gateway

If you read nothing else

  • What it is. Upload a study, or let a scanner push one: it lands in a cloud archive, appears on the imaging center's worklist, opens in a browser viewer with windowing, MPR and 3D volume rendering, and leaves as an immutable signed report with an audit trail.
  • The PACS discipline. The application speaks only standard DICOMweb to the archive: STOW-RS to store, QIDO-RS to search, WADO-RS to retrieve, never a vendor's private API. Swapping Orthanc for a managed health-imaging cloud is configuration, and the browser never touches the archive directly.
  • The agentic reader. An LLM reads the scan by driving the viewer: it requests slice montages and Hounsfield measurements, the browser executes those tools on an off-screen viewport, and every draft opens with a required line: AI-proposed, radiologist verification required. Models are scored offline against a pydicom ground-truth harness.
  • The edge reality. Clinics lose internet; scanning must not stop. A standard-library-only Python gateway store-and-forwards stable studies to the cloud, retries forever through outages, and deletes its local copy only after the cloud confirms receipt.
DICOM viewer 3D workspace: MPR slices and volume-rendered skull of a head CT with the report cockpit open
The 3D workspace on a head CT: MPR planes, volume render, and the reporting cockpit.

The journey of a study.

Nine steps from a modality in a clinic to a signed report in the archive. These are the real protocols, files and endpoints from the code.

01Scan

A modality pushes the study to the clinic's LAN Orthanc over classic DIMSE. Scanning never depends on the internet.

C-STORE :4242
02Edge gateway

The gateway holds the study until it is stable: fully received from the scanner and no longer changing.

gateway/orthanc
03Store-and-forward

A standard-library-only Python sidecar pushes stable studies to the cloud over STOW-RS, retries forever offline, and deletes locally only after the cloud confirms.

gateway/forwarder.py
04Ingest door

Each gateway authenticates with its own per-center credential; every accepted study is attributed to its center and audited.

/api/gateway/studies
05Archive + index

Pixels live in the DICOM archive keyed by SOP Instance UID. Identity, tenancy, reports and audit live in Postgres.

orthanc + studies_index
06Worklist

Radiologists see only their center's studies, filtered by patient, modality and date. Unindexed studies are denied by default.

PostgresWorklistIndex
07Read

The viewer streams slices through a credential-injecting proxy that allowlists the path shape. The archive is never exposed to the browser.

/api/dicomweb/[...path]
08Agentic read

Optionally, an AI reader drives the same viewer: montages of slices, Hounsfield measurements over a drawn ROI, then a structured draft report.

view_slices · measure_hu
09Sign

Only radiologists and admins sign. A signed report can never be edited again, and every signature lands in the append-only audit log.

reports/[studyUid]/sign

Browser upload joins the chain at the archive: drag DICOM files or a whole zip onto the home page; files are detected by the DICM magic bytes at offset 128, never by extension. Studies open from the worklist or straight from the device.

Read the actual code.

Four excerpts quoted from the repository, with paths, so the claims on this page stay checkable rather than atmospheric.

the edge survives outagesgateway/forwarder.py
"""Store-and-forward relay for the edge gateway.
Watches the gateway Orthanc for *stable* studies (fully
received from the scanner) and pushes each one to the
cloud archive via STOW-RS ...
  - internet down -> studies accumulate on the gateway
    disk and retry forever
Standard library only - the sidecar container is a bare
python:alpine."""
The clinic keeps scanning when the internet dies. Studies accumulate on the gateway disk, pushing resumes when connectivity returns, and the local copy is deleted only after the cloud archive confirms receipt.
vendor-neutral by disciplineinfrastructure/repositories/DicomWebRepository.ts
/* Portability discipline: this class speaks ONLY
 * standard DICOMweb - STOW-RS to store, QIDO-RS to
 * search, WADO-RS to retrieve - never a vendor's
 * proprietary REST API. Swapping Orthanc for AWS
 * HealthImaging / Google or Azure DICOM services
 * should be a config + auth change, not a code
 * change. */
The archive is a standard, not a dependency. The same discipline keeps the browser out: every DICOMweb request passes through a server proxy that injects credentials and validates the path shape against an allowlist.
the viewer is the agent's handsinfrastructure/services/AiSdkAgenticReader.ts
/* abridged: tools carry deliberately NO execute
   handlers - the loop stops after each model turn and
   the presentation layer executes the calls against
   the in-browser viewer, where the pixel data lives */
view_slices    up to 9 slices, labeled montage, CT presets
measure_hu     mean HU over a circular ROI, drawn back
submit_report  structured draft, called exactly once
The server plans one stateless turn at a time; the browser executes. Slices render to a throwaway off-screen viewport, Hounsfield stats compute from raw pixels with rescale handled exactly once, and the sampled ROI is drawn back so the model can verify where it measured. The patient's name never leaves the app.
defaults chosen by measurementinfrastructure/services/agenticModels.ts
export const DEFAULT_AGENTIC_MODEL_ID =
  'claude-opus-4-8';
// On a head-CT ground-truth case, Flash still
// confabulated a mass. Opus 4.8 gave the most
// calibrated, clinically sound report - so it
// is the default.
Model choice is an engineering decision with receipts: a pydicom harness builds a Hounsfield ground truth from a real head CT and scores each model's read against it. The picker ships four models; the default earned its place.

Technical depth

Under
the hood.

Exact architecture, the reliability mechanisms, and the decision record with tradeoffs stated, closing with the boundary of what this page does and does not claim.

24API routesUpload to signing
18Use-casesApplication layer
7AI endpointsOne agentic, six assistive
9Chain stepsScanner to signed report
A / Architecture

Four layers.
One protected workflow.

01

Presentation

Next.js shells, React, viewer

Route files are razor-thin shells; real UI lives in presentation components, hooks and the Cornerstone lib layer. The Zustand store mirrors transient viewport state only: no business logic, no DICOM data.

presentation/components · hooks · lib/cornerstone
02

Application

Use cases and orchestration

18 use-cases orchestrate upload, retrieval, reporting and identity. Route handlers act as composition roots, wiring concrete implementations through factories.

application/usecases · factories as composition roots
03

Domain

Rules, entities, ports

Study assembly grouped by series and instance UIDs, modality window presets, agent tool names, and the repository and AI ports all live framework-free. The AI is typed in the domain before any provider exists.

domain/services · repositories (interfaces)
04

Infrastructure

Providers and persistence

The DICOMweb repository, four Postgres stores (identity, reports, worklist, audit), a scrypt password hasher, Anthropic and Google adapters, and the Python edge gateway implement the ports.

infrastructure/repositories · services · identity
B / Reliability

Built for clinics,
not demos.

R1

The edge survives outages

Store-and-forward with stable-study detection: the gateway pushes only fully received studies, retries forever through connectivity loss, deduplicates by SOP Instance UID, and deletes its local copy only after the cloud archive confirms.

STOW-RS · retry forever
R2

The archive is never exposed

The browser reaches the archive only through a read-only proxy that injects server-held credentials, validates every path segment against an allowlist regex, checks tenancy per request, and caches privately.

Credential-injecting proxy
R3

Tenancy, audit, immutability

Center users see only their center's studies, deny-by-default for anything unindexed. Every sensitive action lands in an append-only audit log. Signing is restricted to radiologists and admins, and a signed report can never be edited again.

audit_log · signed reports
R4

A control plane that deploys itself

Raw Postgres with no ORM. The schema self-migrates on first use under a Postgres advisory lock, so a fresh deploy needs no migration step and concurrent instances cannot race. Passwords use memory-hard scrypt; session tokens are stored only as hashes.

Advisory-lock migrate · scrypt
R5

Rendering that survives real phones

A one-time WebGL2 probe reads each device's true texture ceiling and measures the largest float32 3D texture it can actually sample, fixing black volume renders on older iPhones. Pixel ratio is capped on 3x phones, a decode worker is reserved so scrolling never starves, and slices decimate to a GPU memory budget.

Device profile · WebGL2
R6

An agent under guardrails

A 16-step ceiling with wrap-up forced at step 13, image history pruned to the last two tool steps, over-call traps written into the protocol prompt, a mandatory verification pass, and a required first impression line: AI-proposed draft, radiologist verification required.

Bounded loop · protocol prompt
C / Decision record

Explicit choices.
Visible tradeoffs.

D-01

Speak DICOMweb only, never vendor APIs

Context
Orthanc's proprietary REST API is convenient and everywhere in tutorials.
Decision
The application talks to the archive exclusively through STOW-RS, QIDO-RS and WADO-RS, with original transfer syntax preserved.
Tradeoff
No vendor shortcuts, in exchange for an archive that swaps for AWS, Google or Azure health imaging as a configuration change.
D-02

Two systems of record, split by data nature

Context
A PACS holds two very different things: large immutable pixel data and small mutable operational truth.
Decision
Pixels live in the DICOM archive keyed by SOP Instance UID; identity, tenancy, reports, the worklist index and audit live in Postgres.
Tradeoff
Two stores to operate, each doing exactly what it is built for.
D-03

The browser executes the agent's tools

Context
The pixel data and the GPU renderer live client-side; shipping pixels to the server would duplicate the entire viewer.
Decision
The server plans one stateless model turn at a time; the viewer executes view_slices and measure_hu on an off-screen viewport and returns labeled montages.
Tradeoff
A chattier loop, but the model sees exactly what a radiologist sees, and the patient's name never leaves the app.
D-04

A deliberately dumb edge

Context
The gateway runs inside clinics on hardware nobody maintains.
Decision
A standard-library-only Python sidecar on a bare Alpine container: watch, forward, confirm, delete. No queue framework, no dependencies.
Tradeoff
No cleverness at the edge, and therefore nothing to operate or break inside a clinic.
D / Evidence boundary

Credibility includes
what is not claimed.

Repository demonstrates

  • A real end-to-end imaging chain in one codebase: edge gateway, cloud archive, worklist, viewer, reporting
  • Standards discipline: DICOMweb-only to the archive, classic DIMSE at the edge, swappable by configuration
  • An agentic AI reader whose tools execute in the browser, scored against an offline ground-truth harness
  • Device-adaptive GPU rendering built from debugging real failures on real phones

Not claimed without product data

  • Clinical accuracy or regulatory clearance: this is not a certified medical device
  • Hospital deployments, imaging throughput or uptime
  • Automated test coverage: the repository currently ships none
  • DICOM de-identification: PHI is governed by tenancy and an append-only audit trail, and the AI path strips the patient name

How it was built.

Thirty-three commits, June 8 to July 9, 2026. The viewer came first; the platform arrived in a five-phase finale.

08-09 Jun

Viewer MVP in two days

Clean-architecture scaffold, Cornerstone3D v4 stack viewer, upload with DICM magic-byte detection, and the first four AI report features.

10 Jun

The reporting cockpit

Structured ICRI reporting, a mobile-first PWA, synchronized multi-screen scrolling, and the 3D workspace: MPR crosshairs, volume rendering, slab projections.

11 Jun

The agentic deep read

The AI stops captioning and starts driving: view_slices and measure_hu executed by the viewer, live HU under the cursor, quad layouts, 2D MPR.

12 Jun-04 Jul

The Apple GPU war

Black 3D renders on iPhones: an on-device GPU diagnostics overlay, measuring the real float32 texture ceiling, demoting LINEAR to NEAREST filtering, and a mobile chrome redesign.

08-09 Jul

The PACS build-out

Orthanc archive over pure DICOMweb, the edge gateway for scanner connectivity, the archive worklist, and platform auth: centers, roles, signed reports, audit.