dicom_viewer/lib.rs
1//! # CDViewer — a portable, non-diagnostic DICOM viewer
2//!
3//! `dicom-viewer` is a single-executable, statically linked DICOM viewer in
4//! Rust, primarily targeting Windows but with first-class Linux and macOS
5//! support. It is designed to ship on study CDs/DVDs alongside the imaging
6//! data and run without installation.
7//!
8//! > **Not for diagnostic use.** This viewer is intended for review and
9//! > reference only. A "Not for diagnostic use" disclaimer is shown on first
10//! > launch and acknowledged in `config.toml`.
11//!
12//! ## What lives where
13//!
14//! | Module | Responsibility |
15//! |---|---|
16//! | [`app`] | The single [`eframe::App`] — owns viewer state, drives the UI |
17//! | [`config`] | Portable-first path resolution + `config.toml` load/save |
18//! | [`logging`] | `tracing` setup writing a daily-rolled log under `<data_dir>/logs/` |
19//! | [`dcm`] | DICOM domain — folder scan, pixel decode, ROI stats, annotations, export, metadata |
20//! | [`ui`] | egui composition — menu, toolbar, study browser, viewport, dialogs, status bar |
21//!
22//! `src/main.rs` is intentionally thin: it resolves paths, sets up logging,
23//! installs the panic hook, and hands off to [`eframe::run_native`].
24//! Anything that needs to be tested lives in the library and is exposed
25//! through this crate root so [`tests/sample_data.rs`][tests] can call it.
26//!
27//! [tests]: https://github.com/pejmanS21/CDViewer/blob/main/tests/sample_data.rs
28//!
29//! ## DICOM pipeline overview
30//!
31//! 1. [`dcm::loader::load_folder`] walks the directory in parallel
32//! ([`rayon`](https://docs.rs/rayon)), recognises files by extension or
33//! by probing the `DICM` magic at byte 128, and reads metadata with
34//! `OpenFileOptions::read_until(PIXEL_DATA)`. Pixel data is **never**
35//! decoded during the scan.
36//! 2. The result is a `Vec<`[`dcm::Study`]`>` — purely metadata.
37//! 3. When the viewport needs to draw a slice it calls
38//! [`dcm::pixel::load_raw`], which decodes the pixel data **once** into a
39//! rescaled `f32` buffer (modality LUT applied, VOI not applied) and
40//! caches the result in [`app::DicomViewerApp::raw_cache`] keyed by SOP
41//! Instance UID.
42//! 4. [`dcm::pixel::render_rgba`] turns those `f32` values into RGBA8 with
43//! a window/level applied. It's cheap enough to call on every drag delta
44//! for interactive W/L.
45//! 5. The egui [`TextureHandle`](egui::TextureHandle) is cached on the
46//! [`app::CellState`] and invalidated when the SOP UID, window, or invert
47//! setting drifts (see `ensure_texture` in `ui::viewport`).
48//!
49//! Measurement and ROI math lives in [`dcm::roi`]. Annotations are
50//! persisted to a JSON sidecar by [`dcm::annotation::AnnotationStore`].
51//! **Original DICOM files are never modified.**
52//!
53//! ## Locked-in design decisions
54//!
55//! These were chosen deliberately; don't change them without discussion:
56//!
57//! - **UI framework**: [`egui`] via [`eframe`] with the `wgpu` renderer.
58//! `glow` is intentionally not enabled.
59//! - **DICOM**: the [`dicom-rs`](https://docs.rs/dicom) 0.7 family
60//! (`dicom`, `dicom-pixeldata`, `dicom-dictionary-std`).
61//! - **Concurrency**: decode is on the UI thread. No `tokio`. MG-sized
62//! images are downsampled at load time (see
63//! [`dcm::pixel::RawImage::display_scale`]).
64//! - **Release profile**: `lto = "fat"`, `codegen-units = 1`,
65//! `strip = true`, `opt-level = "z"`, `panic = "abort"`. The ~30 MB
66//! binary budget is real.
67//! - **`image` crate features**: PNG only.
68//!
69//! ## Easy ways to break things
70//!
71//! - **Texture cache invalidation.** Any new visual modifier (e.g. gamma)
72//! must participate in the `(tex_uid, tex_window, tex_invert)` comparison
73//! in `ui::viewport::ensure_texture`.
74//! - **Measurement coordinate space.** Annotations are stored in
75//! *displayed-image-pixel* coordinates (post-`display_scale`). Real-world
76//! units multiply by `pixel_spacing × display_scale`.
77//! - **Drag-and-drop in egui 0.29.** Read [`ui`] for the specifics — do not
78//! wrap viewport cells in `dnd_drop_zone`.
79//!
80//! ## Further reading
81//!
82//! - [`README.md`](https://github.com/pejmanS21/CDViewer/blob/main/README.md)
83//! — install, usage, feature list.
84//! - [`docs/ARCHITECTURE.md`](https://github.com/pejmanS21/CDViewer/blob/main/docs/ARCHITECTURE.md)
85//! — extended tour of the codebase.
86//! - [`docs/CI-CD.md`](https://github.com/pejmanS21/CDViewer/blob/main/docs/CI-CD.md)
87//! — how CI, releases, and security scanning are wired up.
88//! - [`CONTRIBUTING.md`](https://github.com/pejmanS21/CDViewer/blob/main/CONTRIBUTING.md)
89//! — local dev workflow.
90
91#![doc(html_root_url = "https://docs.rs/dicom-viewer/0.1.0")]
92
93pub mod app;
94pub mod config;
95pub mod dcm;
96pub mod logging;
97pub mod ui;