Fast temporary DICOM viewer for local and remote research workflows
Project description
dcmview
dcmview is a fast, temporary DICOM viewer for research and development work.
Point it at one or more DICOM files from the command line or Python, and it
starts a local browser viewer for images, tags, cine playback, and rectangular
ROI annotations. Stop the process and the server is gone.
The main problem it solves is remote-server inspection. Medical imaging research
often happens where the data already live: an SSH session, a shared compute
server, or a locked-down institutional network. Viewing those images usually
means choosing between slow notebook plots, setting up a web viewer on the
server, opening firewall ports, or uploading data and annotations into a
third-party cloud tool. dcmview keeps the workflow local to the machine with
the files: start the viewer, forward the loopback port over SSH when needed, and
inspect the study in seconds.
dcmview is intended for developer and research inspection, not clinical
diagnosis.
Why use it?
- Inspect DICOM files where they already are, including remote servers.
- Avoid notebook-based frame rendering for multi-frame studies.
- Keep data off third-party viewers when all you need is quick review.
- Open a browser UI with familiar viewer tools: pan, zoom, scroll, window/level, flips, rotation, tags, and cine playback.
- Load, edit, and export rectangular ROI annotations without modifying the source DICOM files.
- Use the same tool from a shell command, a Python script, or a notebook.
- Run as an ephemeral server with no database, config file, or persistent state.
Install
On supported Linux platforms, the Python package bundles the dcmview binary:
python -m pip install --user dcmview-py
dcmview --help
The package installs both dcmview and dcmview-py; dcmview is the primary
command.
On macOS, use the published Homebrew tap or download a prebuilt archive from GitHub Releases.
Source builds are available for contributors and unsupported platforms:
cargo install --path .
Build prerequisites for source installs:
- Rust stable 1.75+
- Node.js 18+ and npm at build time
sshonPATHonly when using SSH forwarding helpers
Quick Start
Open one file:
dcmview ./scan.dcm
Scan a study directory recursively:
dcmview ./study_dir
Run without opening a browser, useful on a remote server:
dcmview --no-browser ./study_dir
When ready, dcmview prints a URL:
dcmview: server running at http://127.0.0.1:<port>
Press Ctrl+C to stop the server.
Remote Server Workflow
The safest default is to keep dcmview bound to loopback on the remote machine
and access it through SSH port forwarding.
On the remote server:
dcmview --no-browser --port 8888 /path/to/dicom_or_study_dir
On your local machine:
ssh -L 8888:localhost:8888 user@remote-server
Then open:
http://localhost:8888
You can also let dcmview use an auto-assigned port by omitting --port; copy
the printed port into your SSH command. The optional --tunnel flags are
available for environments where the dcmview process can start the SSH helper
itself.
The HTTP server is unauthenticated. It binds to 127.0.0.1 by default. If you
bind to 0.0.0.0 or another public interface, use your own network access
controls.
Python Usage
dcmview-py is a small subprocess wrapper around the Rust binary. It is useful
when a script or notebook has already selected the cases to inspect.
from dcmview_py import view
# Blocking call; returns when dcmview exits.
view(["./scan.dcm"], browser=False, timeout=300)
# Non-blocking call.
handle = view(["./study_dir"], browser=False, block=False)
print(handle.url)
handle.stop()
Context manager:
from dcmview_py import view
with view(["./study_dir"], browser=False, block=False) as handle:
print(handle.url)
The module CLI mirrors the Rust options:
python -m dcmview_py --no-browser --timeout 120 ./study_dir
Viewer Features
The embedded browser viewer includes:
- File tabs labeled from
PatientID,Modality, andStudyDatewhen present. - Canvas-based image viewing with pan, zoom, scroll, window/level, reset, horizontal/vertical flips, and 90-degree rotation.
- Window presets including DICOM defaults, full dynamic range, and common CT presets.
- Multi-frame controls with previous/next, cine playback, FPS selection, loop, and sweep.
- Lazy DICOM tag browsing with filtering, sequence expansion, binary length display, resizable columns, and click-to-copy values.
- Rectangular ROI annotation display and editing, including draw, select, move, resize, delete, frame scoping, and CSV export.
Common shortcuts:
| Action | Shortcut |
|---|---|
| Previous/next frame | Left/Right arrows or [ / ] |
| Play/pause cine | Space |
| Window/level tool | W |
| Pan tool | P |
| Zoom tool | Z |
| Scroll tool | S |
| ROI tool | R |
| Reset viewport | Double-click |
Right-drag always zooms, middle-drag always pans, the wheel scrolls frames, and Ctrl/Cmd+wheel zooms.
Annotations
--annotations loads an EMBED-style ROI CSV into memory:
dcmview --annotations ./embed_annotations.csv ./study_dir
dcmview never modifies the input CSV or DICOM files. Viewer edits are kept in
memory and can be downloaded with Export ROIs.
Required columns:
anon_dicom_pathROI_coords
Optional columns:
num_ROI; when present, it must equallen(ROI_coords)ROI_frames; when omitted or[], ROIs apply to all frames
ROI_coords is a JSON array of [ymin, xmin, ymax, xmax] boxes. ROI_frames
is a JSON array of frame-index lists. JSON-valued fields must be CSV-quoted.
anon_dicom_path,num_ROI,ROI_coords,ROI_frames
/path/to/dbt_case.dcm,2,"[[120,340,220,430],[400,510,480,590]]","[[0,1,2],[5,6]]"
/path/to/ffdm_case.dcm,1,"[[80,150,190,260]]","[]"
Matching uses normalized path equality against loaded DICOM paths. Frame indices
are zero-based and must be less than NumberOfFrames.
CLI Reference
dcmview [OPTIONS] <PATH> [PATH ...]
| Option | Default | Description |
|---|---|---|
<PATH>... |
required | DICOM files or directories to inspect |
-p, --port <u16> |
0 |
Bind port; 0 auto-assigns an available port |
--host <addr> |
127.0.0.1 |
Bind address |
--no-browser |
false | Do not open the browser automatically |
--timeout <seconds> |
none | Exit after this many seconds without API/browser requests |
--no-recursive |
false | Scan only the top level of input directories |
--tunnel |
false | Start an SSH local port-forward helper |
--tunnel-host <host> |
none | SSH target used with --tunnel |
--tunnel-port <u16> |
0 |
Forwarded local port; 0 uses the server port |
--annotations <csv> |
none | Load EMBED-style ROI annotations |
Examples:
dcmview ./scan.dcm
dcmview --no-recursive ./study_dir
dcmview --host 127.0.0.1 --port 8888 --no-browser ./study_dir
dcmview --timeout 300 ./study_dir
dcmview --annotations ./embed_annotations.csv ./study_dir
HTTP API
The browser UI uses a small local HTTP API. It is also useful for scripts that need the same decoded frame, tag, or annotation data while the server is running.
Static frontend assets are served at / and /assets/*.
| Method | Path | Description |
|---|---|---|
| GET | /api/files |
File registry and server metadata |
| GET | /api/file/:index/info |
Frame metadata for one file |
| GET | /api/file/:index/frame/:frame |
Display frame; supported image transfer syntaxes return PNG |
| GET | /api/file/:index/frame/:frame/raw |
Decoded frame sample bytes for client-side rendering |
| GET | /api/file/:index/tags |
Lazy DICOM tag tree |
| GET | /api/file/:index/annotations |
Current in-memory ROI annotations |
| PUT | /api/file/:index/annotations |
Replace in-memory ROI annotations for one file |
| GET | /api/annotations/export.csv |
Download current annotations as EMBED CSV |
Files
GET /api/files returns:
{
"files": [
{
"index": 0,
"path": "/path/to/scan.dcm",
"label": "PATIENT - MG - 20240101",
"has_pixels": true,
"frame_count": 60,
"rows": 3000,
"columns": 2500,
"transfer_syntax_uid": "1.2.840.10008.1.2.4.50",
"default_window": { "center": 200.0, "width": 4000.0 }
}
],
"tunnelled": false,
"tunnel_host": null,
"server_start_ms": 1714300000000
}
GET /api/file/:index/info returns frame_count, rows, columns,
transfer_syntax, has_pixels, and default_window.
Display Frames
GET /api/file/:index/frame/:frame returns image/png for supported display
paths.
Query parameters:
| Param | Description |
|---|---|
wc |
Window center; used with ww in default mode |
ww |
Window width; used with wc in default mode |
mode |
default or full_dynamic |
Window selection:
default: explicitwcandww, then DICOM Window Center/Width, then 1st/99th percentile fallback.full_dynamic: true min/max of the current frame; ignores DICOM defaults and query window values.
Transfer syntax behavior:
| Transfer syntax | Display behavior |
|---|---|
| JPEG Baseline / Extended | Decoded server-side and PNG-encoded |
| JPEG Lossless / Lossless SV1 | Decoded server-side and PNG-encoded |
| JPEG 2000 lossless/lossy | Decoded server-side and PNG-encoded |
| Implicit LE / Explicit LE / Explicit BE | Windowed server-side and PNG-encoded |
| JPEG-LS / RLE / other | 422 {"error": "unsupported transfer syntax: ..."} |
Response headers include X-Cache: HIT or X-Cache: MISS.
Raw Frames
GET /api/file/:index/frame/:frame/raw returns application/octet-stream plus
metadata headers. This is a decoded sample transport for the frontend, not a
copy of the original DICOM Pixel Data element for compressed syntaxes.
Supported raw paths:
- Uncompressed frames: native sample bytes, normalized to little-endian by
dicom-object. - JPEG Baseline / Extended: decoded to 8-bit grayscale samples.
- JPEG Lossless: decoded to 8-bit or 16-bit grayscale samples when supported by the codec stack.
- Grayscale JPEG 2000: decoded to 8-bit or 16-bit samples.
JPEG-LS, RLE, unsupported syntaxes, and multi-component JP2 raw decoding return 422 or a decode error.
Tags and Annotations
GET /api/file/:index/tags returns an array of DICOM tag nodes. Pixel data and
other binary VRs are represented by byte length, not by full values. Long
numeric arrays and sequences may be truncated with truncated and total
fields.
GET /api/file/:index/annotations returns:
{
"num_roi": 2,
"roi_coords": [[120, 340, 220, 430], [400, 510, 480, 590]],
"roi_frames": [[0, 1, 2], [5, 6]]
}
PUT /api/file/:index/annotations replaces one file's in-memory annotations and
returns the canonicalized payload. Invalid coordinates or frame mappings return
400 {"error": "..."}.
Development
Frontend:
npm --prefix frontend ci
npm --prefix frontend run dev
npm --prefix frontend run build
Backend:
cargo fmt --all
cargo fmt --all -- --check
cargo check
cargo build
cargo build --release
Tests:
cargo test
Integration tests use real DICOM fixtures and cover discovery, display-frame decoding, raw-frame transport, cache headers, tag serialization, annotations, and tunnel fallback.
Architecture summary:
- Backend: Rust, Axum, Tokio
- DICOM:
dicom-rs,dicom-pixeldata,jpeg2k - Pixel pipeline: server-side display PNGs, raw sample transport for interactive rendering, LRU caches
- Frontend: Svelte 5, Vite, TypeScript, embedded via
rust-embed - Distribution: one executable with no runtime frontend assets
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dcmview_py-0.1.2-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: dcmview_py-0.1.2-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 5.2 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8245ed75bf38c503ec9d1a52536bd845113e062fceb0200b157717f6b48cdf5a
|
|
| MD5 |
bf52074efb3fa2edc787b61c932b203c
|
|
| BLAKE2b-256 |
a226a7b9a2607d3f6e499d6f387aa1ad0a5a419cf47721105d6eb2f8d33810f1
|
Provenance
The following attestation bundles were made for dcmview_py-0.1.2-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on beatrice-b-m/dcmview
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dcmview_py-0.1.2-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
8245ed75bf38c503ec9d1a52536bd845113e062fceb0200b157717f6b48cdf5a - Sigstore transparency entry: 1765895165
- Sigstore integration time:
-
Permalink:
beatrice-b-m/dcmview@a1ecfcfe4dc7b3d505e341193daecafde6d9f4b7 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/beatrice-b-m
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a1ecfcfe4dc7b3d505e341193daecafde6d9f4b7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dcmview_py-0.1.2-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: dcmview_py-0.1.2-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 4.7 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a4ef141c83ee4cb45fc8ae12147063e65619f1137ffcbf61fb5af1824f5cd44
|
|
| MD5 |
7779fa00cd87aaac5fcb096b2228c8f7
|
|
| BLAKE2b-256 |
5f0e44757d837e720f24e0a79967cb8918dca2e9d678a2acb77b304e4021baee
|
Provenance
The following attestation bundles were made for dcmview_py-0.1.2-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on beatrice-b-m/dcmview
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dcmview_py-0.1.2-py3-none-macosx_11_0_arm64.whl -
Subject digest:
4a4ef141c83ee4cb45fc8ae12147063e65619f1137ffcbf61fb5af1824f5cd44 - Sigstore transparency entry: 1765895377
- Sigstore integration time:
-
Permalink:
beatrice-b-m/dcmview@a1ecfcfe4dc7b3d505e341193daecafde6d9f4b7 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/beatrice-b-m
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a1ecfcfe4dc7b3d505e341193daecafde6d9f4b7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dcmview_py-0.1.2-py3-none-macosx_10_13_x86_64.whl.
File metadata
- Download URL: dcmview_py-0.1.2-py3-none-macosx_10_13_x86_64.whl
- Upload date:
- Size: 5.0 MB
- Tags: Python 3, macOS 10.13+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0a4d28339fab10c6048fd93145becd952060b108ef5e2358c04bb693f3e0601
|
|
| MD5 |
006fa32f002a5299a60bd52803366dba
|
|
| BLAKE2b-256 |
8b2e52d1f887d736ea15751812b0332b330a7b91ea73f41b996b5e30ce88d6e5
|
Provenance
The following attestation bundles were made for dcmview_py-0.1.2-py3-none-macosx_10_13_x86_64.whl:
Publisher:
release.yml on beatrice-b-m/dcmview
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dcmview_py-0.1.2-py3-none-macosx_10_13_x86_64.whl -
Subject digest:
a0a4d28339fab10c6048fd93145becd952060b108ef5e2358c04bb693f3e0601 - Sigstore transparency entry: 1765895515
- Sigstore integration time:
-
Permalink:
beatrice-b-m/dcmview@a1ecfcfe4dc7b3d505e341193daecafde6d9f4b7 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/beatrice-b-m
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a1ecfcfe4dc7b3d505e341193daecafde6d9f4b7 -
Trigger Event:
push
-
Statement type: