A lightweight, ITK-SNAP-style desktop application for inspecting 3D medical volumes (CT / MRI / PET) alongside their segmentation label maps. Three synchronised multi-planar (Axial / Coronal / Sagittal) views, a rotatable Marching-Cubes 3D surface panel, an editable label table, and a live coordinate/intensity readout — in under 1000 lines of Python across 7 files.
Built on PyQt6, VTK, SimpleITK, and scikit-image. Any integer label map is auto-detected and auto-coloured on load — no hard-coded organ list.
Screenshots below use the public FLARE22 abdominal CT dataset. No patient or clinical data is included in this repository.
Full application — three synchronised MPR views + auto-built 3D surfaces
A FLARE22 CT volume with a 13-label segmentation: synchronised Axial/Coronal/Sagittal reslices, the auto-built 3D surface scene, and the per-label control table on the right.
3D surface scene — one Marching-Cubes mesh per label
Each segmentation label is extracted with Marching Cubes and Laplacian-smoothed into an individually coloured, semi-transparent mesh — rotatable and zoomable for at-a-glance anatomical inspection.
- Three synchronised 2D MPR views — Axial, Coronal, Sagittal, sharing one crosshair. A click in any panel repositions all others instantly.
- Rotatable 3D surface panel — one Marching-Cubes mesh per label, with Laplacian smoothing and per-label colour/opacity/visibility.
- Editable segmentation label table — rename, recolour, toggle visibility, and set opacity per label; changes apply live to both the 2D overlays and the 3D meshes.
- Live coordinate/intensity bar — voxel index, world-space (mm) coordinate, raw intensity (HU for CT), and the label under the cursor.
- Auto modality + label detection — CT/MRI inferred from intensity range; any integer label map supported with zero configuration.
- Radiological interaction conventions — left-click to navigate, right-drag for window/level, wheel to scroll slices.
pip install PyQt6 vtk SimpleITK scikit-image nibabel numpy# Open the viewer, then load data via the File menu
python main.py
# Pre-load an image
python main.py image.nii.gz
# Pre-load an image + segmentation (auto-builds the 3D scene)
python main.py image.nii.gz segmentation.nii.gz| Action | Effect |
|---|---|
| Mouse wheel (Axial / Coronal / Sagittal) | Scroll Z / Y / X slices |
| Left click (2D panel) | Move crosshair — syncs all views |
| Right drag (2D panel) | Window / Level (L–R width, U–D center) |
| Left drag (3D panel) | Rotate |
| Right drag (3D panel) | Zoom |
| ↑ ↓ / ← → | Scroll axial / sagittal slices |
| V | Toggle segmentation overlay |
| B | Build / rebuild 3D surfaces |
| R | Reset all cameras |
| J | Jump to voxel coordinate |
| Ctrl+O / Ctrl+Shift+O | Open image / segmentation |
| Ctrl+S | Save screenshot |
| Ctrl+Q | Quit |
Strict model–view separation. All volumetric data and per-label metadata live in a single data-model module with no Qt/VTK UI dependency; every other module is a view/controller that reads from it and re-renders on change.
medviewer_app/
├── main.py ← Entry point
├── main_window.py ← Window layout, menus, crosshair-sync hub
├── slice_widget.py ← 2D MPR slice viewer (×3: Axial/Coronal/Sagittal)
├── view3d_widget.py ← Rotatable 3D surface panel (Marching Cubes)
├── seg_panel.py ← Segmentation label table (colour/opacity/visibility)
├── info_bar.py ← Coordinate / intensity status bar
└── data.py ← Data model: ImageVolume, SegmentationVolume
All views are kept consistent through a single hub: any widget that moves the
current voxel emits a crosshairMoved(i, j, k) signal, and one subscriber in
main_window.py pushes the new position out to every panel — a hub-and-spoke
pattern that makes adding further synchronised views straightforward.
- NIfTI (
.nii,.nii.gz) - CT / MRI / PET — auto-detected from intensity range
- Any integer label map — labels auto-detected, no hard-coding
A detailed technical report documenting the architecture, the core algorithms (image reslicing, window/level mapping, Marching Cubes, Laplacian mesh smoothing, voxel-to-world mapping), and an annotated walkthrough is available here:
📄 MedViewer — Technical Report (PDF)
If you use MedViewer, please cite it. A CITATION.cff file is included, so you can
use the "Cite this repository" button in the GitHub sidebar, or:
Leonard, C. S. (2026). MedViewer: A Modular, ITK-SNAP-Style Medical Volume Viewer. GitHub repository.
Released under the MIT License — free to use, modify, and distribute with attribution.
- Uses voxel spacing only; does not yet apply the full NIfTI direction/origin affine, so obliquely acquired scans may be displayed in an approximate frame.
- One image volume + one segmentation at a time (no side-by-side comparison yet).
- NIfTI only — DICOM series need converting upstream (e.g.
dcm2niix). - The tool reads label maps produced upstream (MOOSE / TotalSegmentator / VoxTell / etc.); it performs no segmentation or reconstruction itself.
Screenshots use the public FLARE22 dataset. The viewer is built on the open-source PyQt6, VTK, SimpleITK, and scikit-image projects.

