A browser-based DICOM viewer built with Flask and vanilla JS. Drag in a .dcm file and it renders the image, parses every tag, and lets you scrub through multi-frame series — no PACS, no plugins, nothing leaving your machine.
Viewer
- Canvas rendering with scroll-to-zoom, drag-to-pan, and pinch-to-zoom on touch
- Brightness and contrast sliders, invert toggle
- Built-in clinical W/L presets: Brain, Subdural, Stroke, Lung, Bone, Liver, Soft Tissue, Vascular, and more
- Pan minimap — appears in the corner when zoomed in so you don't lose your place
- Save current view as PNG (filters included)
- Resizable image / tag panel divider
- Dark and light theme toggle
Series playback
- Load multiple files at once — sorted by
InstanceNumberthenSliceLocationserver-side - Filmstrip thumbnail strip with click-to-seek
- Play/pause with adjustable FPS (1–60)
- First / prev / next / last controls and a scrubber
- Append more files to an existing series without clearing it
- Drag multiple files directly onto the viewer to load as series
Tag browser
- Every tag grouped by category: Patient, Study, Series, Equipment, Image, CT, MR, Cardiac, NM/PET, etc.
- Sorted view (grouped) or unsorted view (raw file order)
- Full-text search across tag IDs, names, keywords, and values with match highlighting
- Collapsible sequence (SQ) items with nested child tags
- Expand all / collapse all
General
- Patient banner: name, ID, DOB, sex, age, modality, study date, dimensions, frame count, institution
- Responsive layout — stacks vertically on narrow screens
- Keyboard shortcuts overlay (press
?in the viewer)
| Layer | Technology |
|---|---|
| Backend | Python 3 · Flask · pydicom · NumPy · Pillow |
| Frontend | Vanilla JS · Canvas API · HTML5 / CSS3 |
| Fonts | JetBrains Mono · Inter (Google Fonts) |
| Icons | Font Awesome 6 |
git clone https://github.com/sapphire-clouds/CardioView.git
cd cardioviewpython -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activatepip install -r requirements.txtmkdir -p static
cp index.html static/index.htmlpython app.pyOpen http://localhost:5000.
For production, use a real WSGI server instead of Flask's dev server:
pip install waitress
waitress-serve --port=5000 app:appDrag a .dcm file onto the drop zone (or click to browse). The image renders with DICOM windowing applied automatically. Use the Brightness and Contrast sliders to adjust, Invert to flip polarity, and Save PNG to download the current view.
- Drop multiple DICOM files onto the drop zone, or use Load Series in the header, or click the + button in the Series Playback panel to append files to an existing series.
- Files are parsed and sorted server-side. All frames are extracted — including multi-frame DICOMs — and returned as a flat ordered sequence.
- A filmstrip of thumbnails appears. Click any thumbnail to jump to that frame.
- Use the transport controls (⏮ ⏴ ▶ ⏵ ⏭) or the scrubber to navigate. Adjust FPS to change playback speed.
CT tip: Select all
.dcmfiles for a study at once (Ctrl+A). CardioView sorts by instance number so the stack plays in the correct anatomical order.
The right panel shows every tag in the file. Search by tag address, keyword, name, or value. Nested sequences are expandable. Toggle between Sorted (grouped by category) and Unsorted (raw file order) views.
| Key | Action |
|---|---|
Scroll |
Zoom in / out |
Drag |
Pan image |
Pinch |
Zoom (touch) |
R |
Reset image (zoom, pan, filters) |
← / → |
Previous / next frame |
Space |
Play / pause series |
Esc |
Close shortcuts overlay |
? |
Show shortcuts overlay |
Both endpoints accept multipart/form-data.
Single file.
Request: file — one .dcm file
Response:
{
"ok": true,
"filename": "scan.dcm",
"summary": { "patientName": "...", "modality": "CT", "rows": "512", ... },
"tagGroups": { "Patient": [...], "CT": [...] },
"totalTags": 142,
"image": "<base64 PNG>",
"imageError": null
}Multiple files. Sorted by InstanceNumber then SliceLocation before processing.
Request: files — one or more .dcm files
Response:
{
"ok": true,
"totalFrames": 64,
"frames": [
{ "filename": "IM001.dcm", "frameIndex": 0, "image": "<base64 PNG>" }
],
"summary": { ... },
"tagGroups": { ... },
"totalTags": 142,
"errors": []
}Partial success is fine — failed files appear in errors, the rest still load.
cardioview/
├── app.py # Flask backend — parsing, windowing, series API
├── requirements.txt
├── static/
│ └── index.html # Entire frontend — viewer, tag browser, series player
└── README.md
No build step. Everything frontend is a single self-contained HTML file.
| Type | Notes |
|---|---|
| CT | Full WC/WW windowing applied |
| MR | Auto-normalized if no window tags present |
| X-Ray / CR / DX | Monochrome and RGB |
| NM / PET | Frame extraction supported |
| Multi-frame DICOM | All frames extracted from a single file |
| JPEG Lossless (Process 14) | Handled via pylibjpeg |
| JPEG 2000 | Requires pylibjpeg-openjpeg |
| RLE Lossless | Built into pydicom |
- All frames are base64-encoded in a single response — large series (500+ frames) will be slow
- No DICOMDIR or WADO-RS support (plain file upload only)
- No authentication — built for local or trusted network use
- CORS is open (
*) — restrict it before deploying anywhere public - Not intended for clinical diagnostic use
Pull requests are welcome. For major changes please open an issue first.
MIT

