Expand description
§CDViewer — a portable, non-diagnostic DICOM viewer
dicom-viewer is a single-executable, statically linked DICOM viewer in
Rust, primarily targeting Windows but with first-class Linux and macOS
support. It is designed to ship on study CDs/DVDs alongside the imaging
data and run without installation.
Not for diagnostic use. This viewer is intended for review and reference only. A “Not for diagnostic use” disclaimer is shown on first launch and acknowledged in
config.toml.
§What lives where
| Module | Responsibility |
|---|---|
app | The single [eframe::App] — owns viewer state, drives the UI |
config | Portable-first path resolution + config.toml load/save |
logging | tracing setup writing a daily-rolled log under <data_dir>/logs/ |
dcm | DICOM domain — folder scan, pixel decode, ROI stats, annotations, export, metadata |
ui | egui composition — menu, toolbar, study browser, viewport, dialogs, status bar |
src/main.rs is intentionally thin: it resolves paths, sets up logging,
installs the panic hook, and hands off to [eframe::run_native].
Anything that needs to be tested lives in the library and is exposed
through this crate root so tests/sample_data.rs can call it.
§DICOM pipeline overview
dcm::loader::load_folderwalks the directory in parallel (rayon), recognises files by extension or by probing theDICMmagic at byte 128, and reads metadata withOpenFileOptions::read_until(PIXEL_DATA). Pixel data is never decoded during the scan.- The result is a
Vec<dcm::Study>— purely metadata. - When the viewport needs to draw a slice it calls
dcm::pixel::load_raw, which decodes the pixel data once into a rescaledf32buffer (modality LUT applied, VOI not applied) and caches the result inapp::DicomViewerApp::raw_cachekeyed by SOP Instance UID. dcm::pixel::render_rgbaturns thosef32values into RGBA8 with a window/level applied. It’s cheap enough to call on every drag delta for interactive W/L.- The egui
TextureHandleis cached on theapp::CellStateand invalidated when the SOP UID, window, or invert setting drifts (seeensure_textureinui::viewport).
Measurement and ROI math lives in dcm::roi. Annotations are
persisted to a JSON sidecar by dcm::annotation::AnnotationStore.
Original DICOM files are never modified.
§Locked-in design decisions
These were chosen deliberately; don’t change them without discussion:
- UI framework: [
egui] via [eframe] with thewgpurenderer.glowis intentionally not enabled. - DICOM: the
dicom-rs0.7 family (dicom,dicom-pixeldata,dicom-dictionary-std). - Concurrency: decode is on the UI thread. No
tokio. MG-sized images are downsampled at load time (seedcm::pixel::RawImage::display_scale). - Release profile:
lto = "fat",codegen-units = 1,strip = true,opt-level = "z",panic = "abort". The ~30 MB binary budget is real. imagecrate features: PNG only.
§Easy ways to break things
- Texture cache invalidation. Any new visual modifier (e.g. gamma)
must participate in the
(tex_uid, tex_window, tex_invert)comparison inui::viewport::ensure_texture. - Measurement coordinate space. Annotations are stored in
displayed-image-pixel coordinates (post-
display_scale). Real-world units multiply bypixel_spacing × display_scale. - Drag-and-drop in egui 0.29. Read
uifor the specifics — do not wrap viewport cells indnd_drop_zone.
§Further reading
README.md— install, usage, feature list.docs/ARCHITECTURE.md— extended tour of the codebase.docs/CI-CD.md— how CI, releases, and security scanning are wired up.CONTRIBUTING.md— local dev workflow.
Modules§
- app
- Top-level egui application: holds shared state and drives the UI.
- config
- Portable-first config and path resolution.
- dcm
- DICOM domain layer: study/series/instance model, folder loading, and pixel-data decoding to RGBA for the viewport.
- logging
tracingsetup writing a daily-rolled log tologs/next to the binary.- ui
- UI composition.