# Slice by Slice local Thresholding

**URL:** <https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195>\
**Category:** Support\
**Tags:** segmentation\
**Created:** [March 18, 2025, 6:51am UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195 "2025-03-18T06:51:07Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![tas47](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/tas47/32/78529_2.png) [@tas47](https://discourse.slicer.org/u/tas47)\
**Post date:** [March 18, 2025, 6:51am UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/1 "2025-03-18T06:51:08Z")

</div>

Hello everyone,

This is my first post. I’ve been spending a lot of time learning about 3D Slicer, particularly focusing on the docs and segmentation techniques. I’m currently faced with the challenge of segmenting tumours that don’t appear in every slice of an MRI volume.

I’m wondering if there is a method to apply the local thresholding feature in the segmentation editor on a per-slice basis similar to the brush feature. Ideally, the workflow would involve, to :

1. Highlighting masks or regions of interest (ROIs) on individual slices where a tumor is detected.
2. Applying local thresholding on each of these slices to adjust the threshold by analyzing the local histogram.
3. Repeating this process for each slice and eventually compiling these into a complete volume segmentation.

I found the local threshold useful, but it seems that it highlights all slices when the boundaries are set. Is there any way to achieve this in the segmentation editor directly on a slice by slice basis where it resets the thresholding nodes for each slice?

Alternatively, would creating this workflow as a seperate module be a good work around?

I read a few other post about this:  
[https://discourse.slicer.org/t/how-to-threshold-only-one-slice-in-the-dicom-module/10602,but](https://discourse.slicer.org/t/how-to-threshold-only-one-slice-in-the-dicom-module/10602,but) they were not too helpful for my use case. Would love some insight in tackling this!

Thank you,  
tas

---

<div class="post-metadata">

**Author:** ![muratmaga](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/muratmaga/32/3622_2.png) [@muratmaga](https://discourse.slicer.org/u/muratmaga)\
**Post date:** [March 18, 2025, 2:51pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/2 "2025-03-18T14:51:46Z")

</div>

> [@tas47](#):
>
> I found the local threshold useful, but it seems that it highlights all slices when the boundaries are set. Is there any way to achieve this in the segmentation editor directly on a slice by slice basis where it resets the thresholding nodes for each slice?

I don’t think you can have Slicer apply the threshold only in a single slice, that’s a global (3D) operation. You can do something like this: For your current slice, preview and adjust the threshold range visually but instead of apply, hit use for masking and then switch to the paint/draw tool and select the area you would like to apply that threshold in that slice. Then switch to the next slice and repeat the procedure.

> [@tas47](#):
>
> Alternatively, would creating this workflow as a seperate module be a good work around?

With python, it should be possible to extract the intensity values from a single slice, and let you choose the cut off values (or automatically calculate the cut off using one of the automated procedures). Though it is integration with segment editor might be a of work.

---

<div class="post-metadata">

**Author:** ![tas47](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/tas47/32/78529_2.png) [@tas47](https://discourse.slicer.org/u/tas47)\
**Post date:** [March 18, 2025, 5:29pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/3 "2025-03-18T17:29:04Z")

</div>

> [@muratmaga](#):
>
> I don’t think you can have Slicer apply the threshold only in a single slice, that’s a global (3D) operation. You can do something like this: For your current slice, preview and adjust the threshold range visually but instead of apply, hit use for masking and then switch to the paint/draw tool and select the area you would like to apply that threshold in that slice.

For this portion, it really defeats the purpose. I am looking for something more semi-automatic especially with almost 1000 example of annotations needed. The idea is to really avoid using paintbrush for manual segmentation, but rely on local thresholding from roi masks drawn with the brush.

it would be great practice and experience to automate this workflow using a custom module. If paint/brush is able to segment slice by slice, there has to be a way to code the local thresholding feature per slice node and reset the state each time we jump to another slice?

Would anybody be able to guide me/help in this process?

---

<div class="post-metadata">

**Author:** ![muratmaga](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/muratmaga/32/3622_2.png) [@muratmaga](https://discourse.slicer.org/u/muratmaga)\
**Post date:** [March 18, 2025, 5:56pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/4 "2025-03-18T17:56:47Z")

</div>

> [@tas47](#):
>
> it would be great practice and experience to automate this workflow using a custom module. If paint/brush is able to segment slice by slice, there has to be a way to code the local thresholding feature per slice node and reset the state each time we jump to another slice?

I mean there are many ways of doing this. Here is a chatGPT generated script that will extract the intensity values from an arbitrary slice and will put them into a numpy array that you can get min, max, std, mean, and do all sorts of histogram things to calculate a threshold

```auto
import slicer
import numpy as np

# Use "MRHead" as the volume name
volumeNode = slicer.util.getNode("MRHead")

# Get image data and check if it's valid
imageData = volumeNode.GetImageData()
if not imageData:
    raise ValueError("The volume node does not contain image data.")

# Extract the intensity values from slice index 100
sliceIndex = 100
volumeArray = slicer.util.arrayFromVolume(volumeNode)
if sliceIndex < 0 or sliceIndex >= volumeArray.shape[0]:
    raise ValueError("Slice index out of bounds.")

sliceArray = volumeArray[sliceIndex, :, :]

# Print the slice intensity values
print("Intensity values from slice index 100:")
print(sliceArray)

# get values
sliceArray.mean()
sliceArray.max()
sliceArray.std()

```

The point though, unless you have a dataset that is very unique and constructed slice by slice (as opposed to a 3D dataset), the intensity distributions from neighboring slices will be very very similar. So you will have a very complicated workflow with marginal gains (or maybe none).

If you describe what you are trying to do, there might be other options. Manual segmentaiton is too tedious.

---

<div class="post-metadata">

**Author:** ![tas47](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/tas47/32/78529_2.png) [@tas47](https://discourse.slicer.org/u/tas47)\
**Post date:** [March 18, 2025, 7:35pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/5 "2025-03-18T19:35:30Z")

</div>

> [@muratmaga](#):
>
> ```auto
> import slicer
> import numpy as np
> 
> # Use "MRHead" as the volume name
> volumeNode = slicer.util.getNode("MRHead")
> 
> # Get image data and check if it's valid
> imageData = volumeNode.GetImageData()
> if not imageData:
> raise ValueError("The volume node does not contain image data.")
> 
> # Extract the intensity values from slice index 100
> sliceIndex = 100
> volumeArray = slicer.util.arrayFromVolume(volumeNode)
> if sliceIndex < 0 or sliceIndex >= volumeArray.shape[0]:
> raise ValueError("Slice index out of bounds.")
> 
> sliceArray = volumeArray[sliceIndex, :, :]
> 
> # Print the slice intensity values
> print("Intensity values from slice index 100:")
> print(sliceArray)
> 
> # get values
> sliceArray.mean()
> sliceArray.max()
> sliceArray.std()
> 
> ```

I tried this workaround initially, but it doesnt scale, and having a gui to set local threshold boundary based on every slice is very helpful, instead of hard coding every slice instance.

Unfortunately, my dataset is composed of gliomas tumours. The intensities distribution dont often contribute to the same intensities on from neighbouring slices because they aren’t found in every slice. That is why i would need a slice by slice case workflow. Certain also have liquid build up creating artifacts thus being very complex for automatic process. The goal is to eventually use the segmentation as a training set for automatic pipelines.

---

<div class="post-metadata">

**Author:** ![muratmaga](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/muratmaga/32/3622_2.png) [@muratmaga](https://discourse.slicer.org/u/muratmaga)\
**Post date:** [March 18, 2025, 7:54pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/6 "2025-03-18T19:54:38Z")

</div>

> [@tas47](#):
>
> I tried this workaround initially, but it doesnt scale, and having a gui to set local threshold boundary based on every slice is very helpful, instead of hard coding every slice instance.

Setting the GUI right is usually the most complex part of the module development. You will have to look into Slicer development ([https://slicer.readthedocs.io](https://slicer.readthedocs.io)) or hire someone.

Also your glioma tumors have thousands of slices, what kind of modality is that, histology? If you have thousands of slices in total, but only few slices per volume/specimen, you can potentially import every single slice of a specimen as an individual volume, segment them as individual slices (in this case threshold tool will work) and then programmatically stitch them together. This might be less development effort than creating your custom segmentation tool.

---

<div class="post-metadata">

**Author:** ![tas47](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/tas47/32/78529_2.png) [@tas47](https://discourse.slicer.org/u/tas47)\
**Post date:** [March 18, 2025, 8:08pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/7 "2025-03-18T20:08:47Z")

</div>

My apologies, i want to just clarify that they are MRI images each composed about about 20-30 slices per volume with different orientations. I have a couple hundreds of of MRI images requiring segmentation in a semi-automatic segmentations. Hope that clarifies.

Thank you for the tips. Will update on what works best for me.

---

<div class="post-metadata">

**Author:** ![muratmaga](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/muratmaga/32/3622_2.png) [@muratmaga](https://discourse.slicer.org/u/muratmaga)\
**Post date:** [March 18, 2025, 8:21pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/8 "2025-03-18T20:21:32Z")

</div>

> [@tas47](#):
>
> that they are MRI images each composed about about 20-30 slices per volume with different orientations.

If that’s the case, I would at least do couple manually in the way I describe. import each slice as a separate volume, segment the way you are thinking of with local threshold, and save, and then stitch.

If this workflow does indeed show slice-by-slice segmentation gives you better results, then you can consider automating it somehow.

---

<div class="post-metadata">

**Author:** ![tas47](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/tas47/32/78529_2.png) [@tas47](https://discourse.slicer.org/u/tas47)\
**Post date:** [April 10, 2025, 8:09pm UTC](https://discourse.slicer.org/t/slice-by-slice-local-thresholding/42195/9 "2025-04-10T20:09:47Z")

</div>

Hi everyone,

I managed to generate a function for this issue. The script takes a volume node, a segmentation node, and a segment which acts as base ROI/mask. It still needs refinement, but is a good starting point.

- **Step 1: Manual ROI Creation:** First, create a ‘base’ segment in the Segmentation Editor (e.g., named ‘RoughTumorROI’). Roughly paint/outline the general area of interest on the slices where the structure appears. This first segment is important because it defines the boundary for where the thresholding will happen on each slice

- Navigate to the slice you want to refine in the Red (Axial) view.

- Go to the Segment Editor’s ‘Threshold’ effect.

- Then you paste my function into the Slicer Python console, making sure to provide the correct volume name, segmentation node ID, and the target segment name you want.

The script will automatically read the min/max threshold values set in the UI and will apply that threshold to the current slice within the boundaries (the ROI). It will then save this result into a new target segment (it creates the target segment if it doesn’t exist yet, using the geometry from the ROI segment). This can be repeated for each slice we need to adjust the threshold for and will be updated into the target segment.

So far the script only works with the axial (‘Red’) slice view with a numpy thresholding logic.

```python

def simple_slice_threshold_to_segmentation(
    volume_name="IM-0012-0001.dcm",
    segmentation_node_id="vtkMRMLSegmentationNode2",
    new_segment_name="ThresholdedSlice"
):
    """
    Thresholds current slice in Red view and creates/updates a new segmentation segment.
    (Refactored to remove all error tracking logic).
    """
    try:
        # ========================================
        # 1. Get or create segmentation node
        # ========================================
        print("Setting up segmentation node...")
        seg_node = slicer.util.getNode(segmentation_node_id)
        if seg_node is None:
            seg_node = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
            seg_node.SetName(segmentation_node_id)
            print(f"Created new segmentation node: {segmentation_node_id}")
        else:
            print(f"Using existing segmentation node: {segmentation_node_id}")

        # ========================================
        # 2. Get current slice index
        # ========================================
        print("Getting slice index...")
        red_widget = slicer.app.layoutManager().sliceWidget("Red")
        if not red_widget:
            raise RuntimeError("Red slice widget not found")
        red_logic = red_widget.sliceLogic()
        slice_index = red_logic.GetSliceIndexFromOffset(red_logic.GetSliceOffset()) - 1
        print(f"Slice index: {slice_index}")

        # ========================================
        # 3. Get input volume and segmentation
        # ========================================
        print("Loading input nodes...")
        volume = slicer.util.getNode(volume_name)
        # Assuming the input segmentation is the same as the output one initially
        input_seg_node = seg_node

        if not volume:
            raise RuntimeError(f"Volume '{volume_name}' not found")
        if not input_seg_node:
            raise RuntimeError(f"Input segmentation node not found or created")
        print("Found both input nodes")

        # ========================================
        # 4. Extract ROI (using first segment of input_seg_node)
        # ========================================
        print("Extracting ROI...")
        segmentation = input_seg_node.GetSegmentation()
        if not segmentation:
             raise RuntimeError("Segmentation data not found in the input node")
        seg_ids = segmentation.GetSegmentIDs()
        if not seg_ids:
            # If the target segmentation node was newly created, it won't have segments.
            # This logic assumes the *intent* is to use an *existing* segmentation
            # (even if it's the same node name as the output) to define the ROI.
            # If the goal is simply to threshold the *entire* volume slice,
            # the ROI extraction part should be skipped or modified.
            # Raising error as per original logic if no segments define the ROI.
            raise RuntimeError("No segments found in input segmentation to define ROI.")

        # Use the first available segment ID from the input_seg_node
        roi = slicer.util.arrayFromSegmentBinaryLabelmap(input_seg_node, seg_ids[0], volume)
        ref_vol = slicer.util.arrayFromVolume(volume)
        extracted = np.where(roi, ref_vol, 0)
        print("ROI extracted")

        # ========================================
        # 5. Threshold current slice
        # ========================================
        print("Thresholding slice...")
        # Get threshold values from Segment Editor
        segmentEditorWidget = slicer.modules.segmenteditor.widgetRepresentation().self().editor
        threshEffect = segmentEditorWidget.effectByName("Threshold")
        min_threshold=float(threshEffect.parameter("MinimumThreshold"))
        max_threshold=float(threshEffect.parameter("MaximumThreshold"))

        current_slice = extracted[slice_index, :, :]
        mask_slice = ((current_slice >= min_threshold) &
                  (current_slice <= max_threshold)).astype(np.uint8)
        print("Thresholding complete")

        # ========================================
        # 6. Create/update new segment in the output seg_node
        # ========================================
        print("Creating/updating new segment...")
        output_segmentation = seg_node.GetSegmentation() # Use the output node's segmentation
        if not output_segmentation:
             raise RuntimeError("Segmentation data not found in the output node before creating/updating segment")

        new_segment_id = output_segmentation.GetSegmentIdBySegmentName(new_segment_name)
        if new_segment_id == '':
            # Create a new segment if it doesn't exist
            new_segment_id = output_segmentation.AddEmptySegment(new_segment_name)
            print(f"New segment '{new_segment_name}' created with ID: {new_segment_id}")

            # Get labelmap from the *first segment* of the *input* node to copy its structure/mask
            # Ensure seg_ids is not empty (checked in step 4)
            first_segment_labelmap = slicer.util.arrayFromSegmentBinaryLabelmap(input_seg_node, seg_ids[0], volume)
            # Copy the labelmap structure to the new segment in the output node
            slicer.util.updateSegmentBinaryLabelmapFromArray(first_segment_labelmap, seg_node, new_segment_id, volume)
            print("Labelmap structure copied to new segment")
        else:
            print(f"Using existing segment '{new_segment_name}' with ID: {new_segment_id}")

        # Get the labelmap of the new segment (from the output node)
        new_segment_labelmap = slicer.util.arrayFromSegmentBinaryLabelmap(seg_node, new_segment_id, volume)

        # Check if the slice actually needs updating
        if not np.array_equal(new_segment_labelmap[slice_index, :, :], mask_slice):
            new_segment_labelmap[slice_index, :, :] = mask_slice
            # Save the updated labelmap back to the segment
            slicer.util.updateSegmentBinaryLabelmapFromArray(new_segment_labelmap, seg_node, new_segment_id, volume)
            print("New segment labelmap updated with thresholded slice")
        else:
            print("Thresholded slice is identical to the existing slice in the new segment; no update needed.")

        print("\nFunction completed successfully.")

    except Exception as e:
        # Basic error reporting without line tracking
        print(f"\n--- ERROR ---")
        print(f"An error occurred: {e}")
        import traceback
        traceback.print_exc() # Print full traceback for debugging
        # Optionally re-raise if needed outside this function
        # raise

# Example usage (ensure Slicer environment is running)
# simple_slice_threshold_to_segmentation_no_tracking()

```

1. **Current Limitations:**
