Open a hospital DICOM folder or scan CD and read it in 3D — multiplanar reformats, volume rendering, MIP/MinIP, windowing. No server, no upload, no install.
Patients get handed a CD or a ZIP of DICOM files and no way to look at it. The desktop options mean installing something; the web options mean uploading a medical scan to a stranger's server.
This viewer does neither. The DICOM→NIfTI conversion runs as WebAssembly inside the browser tab, and the volume is rendered by WebGL2 on the same machine. Nothing is transmitted. Open a folder, read the scan, close the tab.
It is the production viewer behind ReadYourScan's DICOM viewer, extracted and released standalone — same code, minus the AI-reading layer.
- Live demo of this repo: open it, drop a DICOM folder, done → lgdesignee.github.io/readyourscan-viewer
- Hosted, nothing to clone: the same viewer at readyourscan.com/tools/dicom-viewer
- The full product, with an AI explanation of what the scan shows: readyourscan.com
| Reads what hospitals actually give you | A folder of .dcm, a scan CD/DVD, UID-named files (1.2.840.113619.2…), Siemens .ima, or plain NIfTI .nii / .nii.gz |
| Multiplanar reformats | Axial, coronal and sagittal rebuilt from the volume — not just the acquired plane |
| Volume rendering | A real 3D render tile in the montage, with an adjustable section cut to slice into it |
| MIP / MinIP | Slab projections of adjustable thickness, in millimetres, with the slice count it covers |
| Windowing | Min/max intensity sliders, plus Hounsfield presets (lung, bone, brain, abdomen, soft tissue) that appear on their own when a CT is detected |
| Multi-series studies | Every series listed with a thumbnail; only the one you're reading is decompressed in memory |
| Colormaps | Grayscale, bone, hot, viridis, plasma, inferno |
| Export | Save the volume as a de-identified .nii.gz |
| Drag and drop | Drop a whole study folder straight onto the canvas |
git clone https://github.com/lgdesignee/readyourscan-viewer.gitcd readyourscan-viewer && pnpm install && pnpm devThen open http://localhost:5173. pnpm build produces a fully static dist/ — host it anywhere, no backend required.
npm install && npm run devworks too; pnpm is just what the lockfile is built with.
The viewer is one self-contained React component with no global state, no router and no data fetching of its own:
import DicomViewer from 'readyourscan-viewer/src/DicomViewer'
export function ScanPage() {
return (
<div className="h-screen">
<DicomViewer />
</div>
)
}Every prop is optional:
| Prop | Type | What it does |
|---|---|---|
initialSources |
RemoteSource[] |
Load NIfTI volumes from URLs on mount. The only code path in the viewer that touches the network. |
initialFiles |
File[] |
Hand it files your own UI already picked; ingested as if dropped on the canvas. |
modality |
'CT' | 'MRI' | 'XR' |
'CT' adds the Hounsfield presets. Left out, it is read from the DICOM Modality tag. |
showHeader |
boolean |
Hide the built-in title bar when embedding under your own chrome. |
showUploadControls |
boolean |
Hide the file pickers. Drag-and-drop keeps working. |
The hosted demo wraps the viewer in DemoShell — a footer with the attribution and a link to the hosted version. That shell exists only for the standalone build; importing DicomViewer gets you the viewer and nothing else.
src/index.ts also exports the pieces that are useful without the UI — the DICOM series splitter (groupFilesBySeries), the slab projector (SlabProjector), the thumbnail renderer (nvImageThumbnail) and a thin NiiVue wrapper.
Four decisions do most of the work, and they're the interesting part of the code:
One dcm2niix run per series, not per study. Handing a whole study to a single conversion blows the WASM heap and kills the tab on anything complex. So lib/dicom-series.ts does a conservative client-side parse of each file's header — little-endian only, proper sequence skipping — to group the selection by SeriesInstanceUID first. Anything the parser isn't sure about lands in one shared group, so a real series is never split across two conversion batches. Then each series converts on its own, on demand.
Only the series you're looking at is in memory. Series are registered as lazy loaders, not volumes. The picker lists all of them from metadata; switching series releases the previous volume. A multi-GB study never holds more than one decompressed volume at a time.
MIP is done on the voxels. NiiVue uploads a volume as one 3D texture and samples a single slice per plane — there is no projection to turn on. So lib/slab.ts rewrites every voxel to be the brightest (or darkest) of the w around it along the axis the on-screen plane cuts through, using a monotonic-deque sliding window so a 40 mm slab costs the same per voxel as a 4 mm one. Each slice then is a projection of the slab around it, and windowing, the crosshair, the readout and scrolling all keep working untouched. The acquired voxels are held aside — an exported NIfTI is never a picture of a MIP.
The viewport is pinned dir="ltr". Under an RTL page the world-coordinate readout (9.1 -36.9 -139.0 mm) and the L/R orientation labels would be bidi-reordered. A reversed left/right in medical imaging is a patient-safety bug, not a cosmetic one. Localize the copy around the viewer, never the viewport.
There is no backend and no analytics in this repo. DICOM is decoded by dcm2niix compiled to WebAssembly, running in a worker in your tab; pixels go from the file handle to a WebGL texture without a network round-trip. The one exception is explicit and opt-in: if a host app passes initialSources, those URLs are fetched.
The viewer also never displays patient-identifying DICOM tags — the metadata panel shows geometry and the series description only.
Needs WebGL2 and SharedArrayBuffer-free WASM (no COOP/COEP headers required). Chrome, Edge and Firefox on desktop are the tested targets; Safari works but is slower on large volumes. Phones drop the 3D render tile from the montage automatically — it's a heavy volume render squeezed into a quarter of a small screen.
Some hospital exports use transfer syntaxes the WASM build of dcm2niix can't decode (certain JPEG 2000 variants). The viewer names the problem and shows the CLI fallback (dcm2niix -z y -o . "<folder>") instead of failing silently.
This is an image viewer for education and personal understanding. It is not certified as a medical device, it makes no diagnostic claim, and it is not a substitute for a radiologist's report. Do not use it to make medical decisions.
ReadYourScan — upload an MRI, CT or X-ray and get a plain-language explanation of what it shows, alongside this viewer. If this repo is useful to you, the product is where the work on it gets funded.
What the product does, what is free and what is paid, written out in one place: Read Your Scan — overview.
Standing on much larger shoulders:
- NiiVue — the WebGL2 rendering engine (BSD-2-Clause)
- dcm2niix by Chris Rorden — DICOM→NIfTI conversion, and
@niivue/dcm2niixfor the WASM build (BSD-2-Clause)
MIT © ReadYourScan
