# Segmenting a DICOM volume in 3D Slicer's Segment Editor

End-to-end workflow for segmenting an anatomical structure from a DICOM volume in 3D Slicer's Segment Editor: loading the DICOM series, creating a segmentation, applying the Threshold, Grow from seeds, Islands, Margin, and Smoothing effects in a proven order, and exporting the result — with matching Python-console equivalents (DICOMUtils, qMRMLSegmentEditorWidget) for automation.

Exact reference: {"kind":"skill_version","skill_id":"skl_mIL56GDn1fyTjO8IRFja0A","version_id":"skv_6U644HKRnrvMk8uqtOYaZg"}

Applicability: [{"constraint":">=5.0","technology":"3D Slicer","version_scheme":"semver"}]

# Segmenting a DICOM volume in 3D Slicer's Segment Editor

A concrete end-to-end workflow for segmenting an anatomical structure (bone, organ, lesion) from a DICOM series using the **Segment Editor** module in 3D Slicer, with matching Python-console equivalents for automation. Verified against 3D Slicer 5.x docs, the Slicer script repository, and core-developer Discourse answers.

## 1. Load the DICOM volume

**GUI:** Open the **DICOM** module. If the database browser is not visible, click **Show DICOM database**. Import your folder, then double-click the patient, study, or series to load it (or select items — Shift/Ctrl for ranges — and click **Load**). Dragging a folder onto the Slicer window also offers to import it into the DICOM database.

**Python (console or scripted module):**

```python
from DICOMLib import DICOMUtils

dicomDataDir = "/PATH/TO/DICOM_FOLDER"
loadedNodeIDs = []
with DICOMUtils.TemporaryDICOMDatabase() as db:
    DICOMUtils.importDicom(dicomDataDir, db)
    patientUIDs = db.patients()
    for patientUID in patientUIDs:
        loadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))

volumeNode = slicer.util.getNode(loadedNodeIDs[0])  # loaded scalar volume
```

To load one specific series instead of the whole patient: `DICOMUtils.loadSeriesByUID([SERIES_INSTANCE_UID])`, which returns the loaded node IDs.

## 2. Set up the Segment Editor

**GUI:** Switch to the **Segment Editor** module. Set **Master volume** to the loaded volume, create a new segmentation (the **Segmentation** selector), and click **Add** to create a segment. Click **Show 3D** to see a live surface preview while you work. **Undo/Redo** is available if an effect misbehaves.

**Python:** create the segmentation node, match it to the volume geometry, and add segments:

```python
segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
segmentationNode.CreateDefaultDisplayNodes()
segmentationNode.SetReferenceImageGeometryParameterFromVolumeNode(volumeNode)
segmentID = segmentationNode.GetSegmentation().AddEmptySegment("bone")
```

To drive effects without a GUI, create a headless Segment Editor widget (this is the documented pattern; the Slicer script repository advises that for full batch scripting it can be simpler to run the underlying VTK/ITK filters directly):

```python
segmentEditorWidget = slicer.qMRMLSegmentEditorWidget()
segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
segmentEditorNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentEditorNode")
segmentEditorWidget.setMRMLSegmentEditorNode(segmentEditorNode)
segmentEditorWidget.setSegmentationNode(segmentationNode)
segmentEditorWidget.setMasterVolumeNode(volumeNode)
```

If the Segment Editor module has been opened before, you can reuse its live widget instead of creating a new one: `segmentEditorWidget = slicer.modules.SegmentEditorWidget.editor`, then `slicer.util.selectModule("SegmentEditor")` to jump to it.

Select the active segment before each effect:

```python
segmentEditorNode.SetSelectedSegmentID(segmentID)
```

## 3. Segment with effects

### Threshold — fast first pass for high-contrast structures

**GUI:** Choose **Threshold**, move the lower/upper sliders (use the histogram), and click **Apply** to write the thresholded region into the selected segment. Global thresholding works best for cases like bone in CT; it is the classic starting point.

**Python:**

```python
segmentEditorWidget.setActiveEffectByName("Threshold")
effect = segmentEditorWidget.activeEffect()
effect.setParameter("MinimumThreshold", LOWER_VALUE)
effect.setParameter("MaximumThreshold", UPPER_VALUE)
effect.self().onApply()
```

**Tip:** Threshold also works as an intensity gate for other tools: set it as the **Editable intensity range** (masking settings) so Paint, Scissors, and other effects only touch voxels inside your intensity range.

### Grow from seeds — for structures thresholding cannot isolate

**GUI (this is an interactive, two-step effect):**
1. Add at least **two segments** — one inside the target structure, one inside the background/tissue around it. Only **visible** segments participate.
2. With **Paint** (or another brush), paint seed strokes inside the target on several slices, and seeds for the background segment in the surrounding anatomy.
3. Choose **Grow from seeds** and click **Initialize** to compute the preview (first computation is the slow one). Refine by adding more seeds and clicking **Update**, or enable **Auto-update**. Click **Apply** to overwrite the seed segments with the grown result; **Cancel** discards the preview but keeps your seeds.

### Islands — remove stray fragments

**GUI:** Choose **Islands**, pick **Keep largest island** to drop disconnected fragments, or **Remove small islands** with a **Minimum size** (in voxels). **Split islands to segments** turns each qualifying island into its own segment; **Keep/Remove selected island** keeps only the island you click.

**Python:**

```python
segmentEditorWidget.setActiveEffectByName("Islands")
effect = segmentEditorWidget.activeEffect()
effect.setParameter("Operation", "REMOVE_SMALL_ISLANDS")
effect.setParameter("MinimumSize", MIN_VOXELS)
effect.self().onApply()
```

### Margin — grow or shrink the boundary

**GUI:** Choose **Margin** and set a positive margin to dilate the segment or a negative one to erode it (millimeters).

**Python:**

```python
segmentEditorWidget.setActiveEffectByName("Margin")
effect = segmentEditorWidget.activeEffect()
effect.setParameter("MarginSizeMm", MARGIN_MM)  # positive = grow, negative = shrink
effect.self().onApply()
```

### Smoothing — clean up the result

**GUI:** Choose **Smoothing**, pick a method, and click **Apply** to smooth the whole segment. Dragging with the mouse applies the same method locally (brush size only marks the region, not the strength). Methods:
- **Median** — removes small extrusions, fills small gaps; keeps contours mostly unchanged.
- **Opening** — removes extrusions smaller than the kernel size (removes only).
- **Closing** — fills sharp corners and holes smaller than the kernel size (adds only).
- **Gaussian** — strongest smoothing, tends to shrink the segment.
- **Joint smoothing** — smooths all visible segments together while preserving a watertight interface between them; higher segments in the table take priority on overlap.

**Python:**

```python
segmentEditorWidget.setActiveEffectByName("Smoothing")
effect = segmentEditorWidget.activeEffect()
effect.setParameter("SmoothingMethod", "MEDIAN")  # or OPENING, CLOSING, GAUSSIAN, JOINT
effect.setParameter("KernelSizeMm", KERNEL_MM)
effect.self().onApply()
```

Clean up the headless widget when done:

```python
segmentEditorWidget = None
slicer.mrmlScene.RemoveNode(segmentEditorNode)
```

## 4. Export the segmentation

**GUI:** Go to the **Segmentations** module, open the **Export to files** section (also reachable from the Segment Editor via the dropdown of the **Segmentations** button), choose **NRRD** or **NIFTI** as the file format — NRRD is recommended for general use — and optionally set a **Reference volume** so the output geometry (origin, spacing, axes) matches your source volume exactly. Saving a binary-labelmap segmentation as `.seg.nrrd` also works from **File / Save** and preserves segment names, colors, and terminology metadata in the file's custom fields.

**Python:** export visible segments to a labelmap volume matching your source volume, then save:

```python
labelmapNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
slicer.modules.segmentations.logic().ExportVisibleSegmentsToLabelmapNode(
    segmentationNode, labelmapNode, volumeNode)
slicer.util.saveNode(labelmapNode, "/PATH/TO/segmentation.nrrd")
```

## A proven effect order

For a structure like a bone in CT: **Threshold** (rough pass) → **Islands / Keep largest island** (drop fragments) → **Margin** (small dilation to recover thin edges, or erosion to separate touching structures) → **Smoothing / Median** (clean contours) → **Export to files**. For soft-tissue structures where thresholding fails: paint seeds for target and background and use **Grow from seeds** instead of Threshold, then continue with Margin/Smoothing cleanup.


## Supporting basis and limitations

Grounded in the 3D Slicer Segment Editor module documentation (Slicer/Slicer repo, lassoan fork), the Slicer wiki script repository's segment-effect examples, slicer.readthedocs.io DICOM loading and segmentation export guidance, and core-developer answers on Discourse (automating segment-editor effects, DICOMUtils import/load patterns, labelmap export).

## Change and rationale

Initial publication: end-to-end DICOM-to-segmentation workflow for 3D Slicer's Segment Editor

Gives coding agents a single grounded reference for a very common 3D Slicer task — segmenting a DICOM volume — covering both the GUI effect sequence (Threshold, Grow from seeds, Margin, Smoothing) and the real Python-console APIs for automation, reducing invented API calls.
