Skip to content

rodriguez46p-ui/SmartTumorQuant

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SmartTumorQuant

End-to-end tumor segmentation, quantification, and reproducible reporting workflow for 3D Slicer.

SmartTumorQuant is a Python-scripted 3D Slicer extension that takes an operator from a raw volumetric scan to a quantitative, exportable lesion report in a single panel. The codebase is shaped the way a production imaging-research lab would expect: clean separation of UI and logic, real input validation, a NumPy fallback for the SegmentStatistics module, and full reproducibility metadata baked into every export.

⚠️ Research / portfolio use only. SmartTumorQuant is not a medical device and must not be used for diagnosis or treatment decisions.


Why this module exists

Quantitative imaging workflows in research and clinical labs share a recurring shape:

  1. Load a scan.
  2. Segment a region of interest.
  3. Pull biomarkers out of that segment.
  4. Ship the numbers somewhere downstream with enough context that another human (or pipeline) can trust them.

Every step has well-known failure modes — operators trust mocked stats, physical units drift from voxel counts, reports leave the lab without spacing information attached, IJK/RAS conversions silently flip handedness. SmartTumorQuant addresses each of those concretely: it respects voxel spacing for every physical measurement, uses Slicer's official SegmentStatistics plugin where possible with a tested NumPy fallback, converts IJK→RAS through the volume's transform matrix, and embeds a ReproducibilityMetadata block plus a QualityChecklist in every export.


Feature highlights

Area What's in the box
Inputs Modality-agnostic scalar volume selection, segmentation node creation with reference geometry alignment, named target segment with deterministic color
Segmentation Threshold seeding (NumPy, works headlessly), median smoothing, largest-island cleanup, ± margin expansion/shrink — all via a transient Segment Editor that doesn't disturb the user's own editor session
Quantification Voxel count, volume in mm³ and mL, mean / median / min / max / std-dev intensity, axis-aligned bounding box in mm, centroid in RAS
Visualization One-click 3D closed-surface model with default opacity, slice-jump to the segment centroid, refreshable metrics table
Exports CSV (long format), JSON (round-trippable), human-readable Markdown report with a quality checklist
Reproducibility Timestamp, Slicer version, module version, volume/segmentation/segment names, voxel spacing, image dimensions, every refinement parameter, and the statistics backend that produced the numbers
Extensibility registerSegmentationBackend(name, fn) hook so future AI segmenters (nnU-Net, MONAI Label, SAM2-Med, etc.) can drop in without UI changes
Testing A synthetic phantom test asserting computed volume is within 10 % of the analytical sphere volume, plus round-trip JSON checks

Screenshots

End-to-end run on the built-in MRHead MR volume, captured by driving the module's own logic programmatically and then grabbing the live Qt widgets.

Module panel after a complete run

The four sections — Inputs, Segmentation, Quantitative analysis, Reproducibility and export — with the metrics table populated by the live analysis (voxel count, volume in mm³ and mL, intensity statistics, bounding box) and the status bar reporting the last action.

Module panel with populated metrics

Full Slicer window — slice views, 3D model, panel

Four-up layout: axial / sagittal / coronal slices with the segmentation overlay plus the rendered 3D model. The slice views were re-centered on the segment centroid by the Jump slices to centroid action.

Full Slicer window with SmartTumorQuant running

3D model from the segmentation

Closed-surface model exported from the target segment, rendered with the module's default opacity and color so the geometry reads clearly against the slice plane.

Rendered 3D tumor model

Axial slice with segmentation overlay

Axial slice at the segment centroid showing the segmentation overlay - the same view a clinician/researcher would scrub through to validate the boundary before exporting.

Axial slice with segmentation overlay


Installation

Option A — load as a scripted module (fastest)

  1. Clone or download this repository.
  2. Launch 3D Slicer (5.4 or newer recommended; built and tested against the current stable scripted-module API).
  3. Open Edit → Application Settings → Modules.
  4. Add the local SmartTumorQuant/ (the inner directory containing SmartTumorQuant.py) to Additional module paths.
  5. Restart Slicer. The module appears under Quantification → Smart Tumor Quant.

Option B — build as an extension

For installation alongside other Slicer extensions, the repository ships a top-level CMakeLists.txt that wraps the module in the standard Slicer extension build, exactly as the Extensions Index expects:

mkdir build && cd build
cmake -DSlicer_DIR=/path/to/Slicer-build/Slicer-build ..
cmake --build . --config Release

Point Slicer at the resulting bundle via Application Settings → Modules → Additional module paths, or package with cpack and install through the Extensions Manager UI.


Running the workflow

  1. Inputs. Select a scalar volume. Either pick an existing segmentation or leave the field empty and let the module create one. Name the target segment (default: Target Tumor). Click Setup target segment — the segmentation node is created (if needed), aligned to the volume's reference geometry, and gets a colored empty segment to fill in.
  2. Segmentation. Click Suggest from volume to auto-pick a threshold range from the upper quartile of intensities (modality-agnostic). Tune the slider, then Apply threshold (seed segment). Optionally enable median smoothing, largest-island cleanup, and a positive/negative margin, then Apply refinements.
  3. Analysis. Compute metrics populates the table with voxel count, physical volume, intensity statistics, bounding box, and centroid. Generate 3D model materializes a closed-surface model node ready for the 3D view. Jump slices to centroid re-centers all slice views on the segment.
  4. Report. Tick the operator quality gates (segmentation reviewed, spacing validated). Pick an output directory and a base file name. Export writes CSV / JSON / Markdown side by side.

The status row at the bottom reports progress and the most recent action; the buttons disable themselves whenever prerequisites are missing, so the workflow is obvious from the UI state alone.


Architecture

SmartTumorQuant/
├── SmartTumorQuant/
│   ├── SmartTumorQuant.py        ← manifest, widget, logic, test
│   ├── CMakeLists.txt
│   ├── Testing/
│   │   └── CMakeLists.txt
│   └── Resources/
│       └── Icons/                ← place SmartTumorQuant.png here
├── SampleData/                   ← how to load DICOM / NRRD samples
├── Screenshots/                  ← drop README screenshots here
├── Outputs/                      ← default export target (gitignored)
├── CMakeLists.txt                ← extension-level build
├── LICENSE
└── README.md

Inside SmartTumorQuant.py, four concerns are kept strictly separated so the codebase scales:

  • SmartTumorQuant — module manifest (categories, dependencies, help text).
  • SmartTumorQuantWidget — presentation only. Builds the four collapsible sections (Inputs, Segmentation, Analysis, Reproducibility/Export), wires signals, manages button-enabled state, refreshes the metrics table, and delegates everything substantive to the logic class.
  • SmartTumorQuantLogic — UI-free processing core. This is the part you would import from a notebook, a pipeline, or another module.
  • SmartTumorQuantTest — a ScriptedLoadableModuleTest that builds a synthetic spherical phantom, runs the full segmentation → analysis → export pipeline, and asserts physical-volume correctness within 10 % of the analytical sphere volume.

Three small dataclasses formalize the result contract:

Dataclass Purpose
QuantitativeMetrics The numeric biomarkers themselves, with a as_rows() helper for table rendering
ReproducibilityMetadata Provenance: timestamp, Slicer version, module version, geometry, every refinement parameter, the statistics backend used
QualityChecklist Four operator-facing gates: segmentation reviewed, spacing validated, non-empty segment, export completed

A fourth, AnalysisResult, bundles the three together and round-trips through JSON via dataclasses.asdict.


The technically interesting bits

These are the parts I would point at in a code review:

  • _writeMaskToSegment — threshold seeding bypasses the Segment Editor's Threshold effect (which needs a real Qt widget) and writes a NumPy mask through a transient vtkMRMLLabelMapVolumeNode, copying the source volume's IJKToRASMatrix so geometry is preserved exactly. This is the reason the test can run headless in CI.
  • applyRefinements — for the refinement effects (Smoothing / Islands / Margin) we do use the Segment Editor, but through a transient qMRMLSegmentEditorWidget plus a temporary vtkMRMLSegmentEditorNode that is cleaned up in a finally block. The user's own editor session is untouched.
  • computeMetrics — prefers Slicer's SegmentStatistics plugin (which is battle-tested across modalities), and falls back to a NumPy implementation that the test exercises explicitly. Provenance — which backend produced the numbers — is recorded in the metadata block, so a downstream reviewer can always trace a result back to its math.
  • _bboxAndCentroidRasFromMask — IJK→RAS conversion goes through the volume's IJKToRASMatrix (vectorized with NumPy), so oblique scans don't silently produce wrong physical extents. Bounding box dimensions get an extra voxel of physical thickness so the reported bbox represents the outer extent rather than centers of corner voxels.
  • suggestThresholdRange — the auto-threshold heuristic picks the upper quartile of finite intensities rather than hardcoding a CT window. The module never assumes modality.
  • registerSegmentationBackend — an explicit hook so an AI segmenter can drop in tomorrow without touching the widget. The contract is small: (volumeNode, segmentationNode, segmentId, **kwargs) -> None.
  • Reproducibility & quality — every export carries the metadata and the checklist. The checklist's export_completed flag is only flipped to True after the report files are on disk, so a partial export is visible to the reviewer.

Example outputs

After exporting, the chosen directory contains three files:

smart_tumor_quant_report.csv
smart_tumor_quant_report.json
smart_tumor_quant_report.md

The Markdown report is structured for human review:

# SmartTumorQuant Report

_Generated 2026-05-12T14:11:09Z - Slicer 5.6.2 - SmartTumorQuant v1.0.0_

## Acquisition
| Field | Value |
|---|---|
| Volume | `CTChest` |
| Segmentation | `CTChest_Segmentation` |
| Segment | `Target Tumor` |
| Spacing (mm) | 0.703 × 0.703 × 1.250 |
| Dimensions (voxels) | 512 × 512 × 320 |
| Statistics backend | `SegmentStatistics` |

## Segmentation parameters
| Step | Value |
|---|---|
| Threshold range | 150.0 - 3000.0 |
| Smoothing | yes (2.0 mm median) |
| Largest-island cleanup | yes |
| Margin | 1.0 mm |

## Quantitative metrics
| Metric | Value | Units |
|---|---|---|
| Voxel count | 12842 | voxels |
| Volume | 7935.27 | mm³ |
| Volume | 7.9353 | mL |
| Mean intensity | 132.451 | a.u. |
...

## Quality checklist
- [x] Segmentation reviewed by operator
- [x] Voxel spacing validated against source DICOM
- [x] Segment is non-empty after refinement
- [x] Export completed successfully

The CSV is in long format (section, key, value, units) so it ingests cleanly into a pandas/SQL pipeline, and the JSON round-trips through json.load directly into the AnalysisResult shape used by the module.


Running the tests

From the Python Interactor inside Slicer:

import SmartTumorQuant
test = SmartTumorQuant.SmartTumorQuantTest()
test.runTest()

The test creates a synthetic 64³ spherical phantom (no network required), runs the entire pipeline including export, and asserts:

  • segment is non-empty,
  • computed volume is within 10 % of the analytical sphere volume,
  • intensity statistics are internally consistent,
  • bounding box has positive extent on every axis,
  • CSV / JSON / Markdown files are written and non-empty,
  • the JSON file round-trips back to a dict whose metrics.voxel_count matches the live result.

Limitations and future enhancements

These are honest, not toy. Things I'd ship next, in order:

  1. Histogram / shape features. Skewness, kurtosis, sphericity, surface area, elongation — SegmentStatistics already exposes most of them; the missing piece is plumbing them into QuantitativeMetrics and the report template.
  2. Per-slice scrubbing view. A second tab with a sortable per-slice table (slice index, voxel count, mean intensity) helps spot heterogeneity that a single set of aggregate stats hides.
  3. DICOM SR export. Convert the report into a DICOM Structured Report (TID 1500) so it can flow back into a PACS for storage alongside the source study.
  4. AI segmentation integration. Wire the existing registerSegmentationBackend hook to MONAI Label / nnU-Net Inference for first-pass segmentation, keeping the manual refinement pipeline as a reviewer step.
  5. Multi-segment / multi-lesion mode. Currently the workflow is single-target by design. Extending it to a longitudinal multi-lesion tracker is straightforward once a segment-id loop replaces the single _lastSegmentId field on the widget.
  6. Unit & integration test split. The current end-to-end test is the right shape for a portfolio sample; production deployment would split it into fast logic-only unit tests plus a slower Slicer-integrated suite.

What this codebase demonstrates

Concrete capabilities a reviewer can verify in the source:

  • Real Slicer fluency. Correct use of vtkMRMLScalarVolumeNode, vtkMRMLSegmentationNode, qMRMLSegmentEditorWidget, vtkMRMLSegmentEditorNode, SegmentStatistics, slicer.util.arrayFromVolume, slicer.util.arrayFromSegmentBinaryLabelmap, the segmentations logic, the reference-geometry mechanism, and the scripted module test framework.
  • Imaging engineering judgement. Physical units throughout. Modality agnostic. IJK→RAS through the matrix, not by accident. Bounding box reported as outer extent. Auto-threshold by quantile, not by a CT-specific constant. A real fallback for SegmentStatistics rather than failing loudly.
  • Software engineering discipline. UI/logic separation, dataclasses for the result contract, type hints, structured error paths that disable bad actions rather than crash, transient Slicer nodes always cleaned up in finally blocks, headless-runnable tests, reproducibility metadata baked into every export.
  • Operational thinking. A quality checklist that intentionally leaves some gates to the operator, a status row that surfaces progress, a default output directory under the user's home, and an export that is safe to ingest into a downstream pipeline (long-format CSV + round-trippable JSON).

That mix — imaging math correct, Slicer APIs idiomatic, code shaped for a team to extend — is what the role actually requires.


License

MIT. See LICENSE.

About

End-to-end tumor segmentation, quantification, and reproducible reporting for 3D Slicer. Threshold + smoothing + island cleanup pipeline, NumPy-based metrics with SegmentStatistics fallback, IJK->RAS bbox/centroid, CSV/JSON/Markdown exports with full provenance metadata.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors