Case study
A DICOM viewer that never sees your files
The viewer has two paths. The sample studies, public CT, MRI, ultrasound and X-ray scans, are converted once with the OFFIS DICOM Toolkit (DCMTK) and served as static files. A visitor’s own files are read by a small DICOM reader in the browser tab, so nothing is uploaded and there is nothing to delete on a server. The reader is tested pixel for pixel against DCMTK and against hostile files: huge lengths, deep nesting, a deflate bomb and 1,400 damaged copies of real files.
Samples: prepared once with DCMTKYour files: read in the browser, never uploaded
We wanted to show, not tell, that we can build medical imaging into a web application: a viewer anyone can try, on a site that is pre-rendered for search engines, served as static files and locked down by a strict Content Security Policy. The result is a free DICOM viewer with seven sample studies, and it opens a visitor's own files without uploading them.
This is our own tool, live on this site, not a fictional client. The first version was planned, built and deployed on October 29, 2026, with an AI coding agent doing much of the typing under review. Every number on this page comes from the code and its tests.
Architecture
Two paths to one viewer, and the server only serves files.
The sample studies and a visitor's own files reach the same viewer by different roads. The samples are converted once with the OFFIS DICOM Toolkit (DCMTK), and the results are committed with the site. A visitor's own files never leave the browser tab. The site's API takes no part.
Decisions
Each choice keeps medical files off the server and the page fast.
| Decision | What we chose | Why |
|---|---|---|
| Where DCMTK runs | Once, on the developer's machine, to prepare the samples. The output is committed with the site. | The server needs no imaging software, the pages stay static and fast, and no medical files ever sit on our server. |
| A visitor's own files | Read in the browser tab by a small DICOM reader written for the page. | Nothing is uploaded, so there is nothing to store, secure or delete. Close the file and it is gone. |
| Getting 16-bit pixels to the browser | Each stored value packed into the red (high byte) and green (low byte) of a PNG. | Lossless and compressed, and the browser decodes PNG itself. A JPEG would change the values the window and the Hounsfield units depend on. |
| Window and level | A lookup table for every stored value, built with DICOM's linear window function each time the window changes. | Dragging stays smooth, and the value under the pointer is the real modality value, in Hounsfield units for CT. |
| Tag names | DCMTK's data dictionary exported to a JSON file: 5,348 tags. | Fetched only when someone opens their own file; the samples carry their names already. |
| Security headers | No inline scripts or styles, no WebAssembly: canvas drawing and plain TypeScript. | The site's strict Content Security Policy applies to this page unchanged. |
Data
Real scans, openly licensed, credited on every view.
Every sample is public research data under a Creative Commons Attribution license, with the collection and license shown beside the image. The human scans were de-identified by The Cancer Imaging Archive before publication. Three of them move: a 4D CT rebuilt as a front view for each of ten phases of a breath, an ultrasound clip of 38 frames, and a pig heart MRI. That dataset keeps two moments of each heartbeat, squeezed and relaxed, so the heart beats in two steps, and the page says so.
| Sample | Source | License |
|---|---|---|
| Chest CT, chest X-rays (CR and DX) | COVID-19-AR, The Cancer Imaging Archive | CC BY 4.0 |
| Breathing chest CT (4D) | 4D-Lung, The Cancer Imaging Archive | CC BY 3.0 |
| Brain MRI (FLAIR) | UPENN-GBM, The Cancer Imaging Archive | CC BY 4.0 |
| Liver ultrasound clip | B-mode-and-CEUS-Liver, The Cancer Imaging Archive | CC BY 4.0 |
| Beating heart MRI (pig, 7 T) | Cardiac Cine MRI of Sus scrofa at 7T, Lohr et al., Zenodo | CC BY 4.0 |
Testing
People will try to break an upload, so we tried first.
A reader that accepts any file is an attack surface, even when it runs in the visitor's own browser: a broken header could freeze the tab or make it reserve gigabytes. The test suite reads small files made with DCMTK in every supported format, compares the result pixel for pixel with DCMTK's own export, and then throws hostile files at it.
| What we threw at it | What happens |
|---|---|
| Not DICOM at all: empty files, text, PNG, ZIP, random bytes | Refused: "not a DICOM file" |
| A length field claiming 4 GB | Refused before reading past the end of the file |
| Sequences nested 5,000 deep | Refused at 64 levels, the same default limit DCMTK uses |
| A 65,535 × 65,535 image, or 100 million frames | Refused before any memory is reserved |
| A deflated file that would expand to 620 MB | Stopped while decompressing, at a fixed limit |
| An RLE frame claiming 4 billion segments | Refused: the count must match the image |
| HTML and script in a tag value | Shown as plain text; the page escapes it |
| 1,400 randomly damaged copies of real files | Each read or refused with a reason in milliseconds; no hang, no crash |
found by the tests before visitors could find them: a damaged deflate stream let a bare browser error through instead of a reason, and the tag table's folding tripped over the way dcmdump indents sequence items, which broke the page build. Both were fixed the same day.
in two test files: the viewer's image maths and samples, then the reader's formats, the samples against DCMTK's export, the hostile files and the fuzzing, which runs in a separate process with a time limit so a hang fails the test instead of freezing it.
Limits and next steps
What the demo doesn't do yet
- JPEG lossless and JPEG-LS are common in hospital exports. The browser reader shows their tags and the DCMTK command that decompresses them; a server step running DCMTK, with the visitor's consent and the file deleted right after, would show them directly.
- JPEG 2000 needs a decoder the browser lacks, such as OpenJPEG compiled to WebAssembly, and a CSP change for this one page.
- A PACS connection: a portal would fetch studies from the hospital's archive. DCMTK's tools speak classic DICOM networking (storescu, findscu, movescu), so a small service can bridge the archive and the web page.
- Not a medical device. The viewer is a technology demonstration; diagnostic use needs regulatory clearance and clinical validation.
Need imaging in your own portal?
I build web applications for clinics, imaging centers and health-tech teams: patient and referrer portals that show studies in the browser, connections to a PACS, and the APIs between them. Every engagement starts with a free 30-minute call.
Sources
- DCMTK, the OFFIS DICOM Toolkit (official GitHub mirror), release 3.7.1, October 15, 2026
- DICOM PS3.3, C.11.2: VOI LUT module (window center and width), NEMA
- DICOM PS3.5: Data structures and encoding (value representations, transfer syntaxes), NEMA
- COVID-19-AR, The Cancer Imaging Archive, CC BY 4.0
- 4D-Lung, The Cancer Imaging Archive, CC BY 3.0
- UPENN-GBM, The Cancer Imaging Archive, CC BY 4.0
- B-mode-and-CEUS-Liver, The Cancer Imaging Archive, CC BY 4.0
- Cardiac Cine MRI of Sus scrofa at 7T, Lohr et al., Zenodo, CC BY 4.0