How to programmatically split the RGB channels and store them separately as a segmentation node

Hi there,

I’m new to 3D Slicer, as this title mentions, here is a RGB stack TIFF image:
RGB STACK
and now I’d like to implement a procedure in python that splits the RGB channels and converts/stores the RGB channels in the three corresponding segmentation nodes.
The result is similar to the following:
Figure1

Each channel will be stored in a corresponding segmentation node.
Thank you very much for any specific guidance in advance!

Segmentations are typically represented as binary labelmap images, so normally you threshold the input scalar (grayscale) image to get the segmentation. Slicer supports fractional labelmaps for segmentations, too, but only a limited set of segmentation tools are supported for those, so I’m not sure if they would work better for you.

I had a look at the image that you provided and the R, G, B components are exactly the same, so what you have is actually single-channel image (the same channel is repeated 3 times).

When I loaded it using the default image loader then only the first component was loaded. So, instead of loading the image using drag-and-drop you can load it as 3 separate volumes using scikit-image using this Python code snippet:

filename = r"c:\Users\andra\Downloads\RGB STACK.tif"

# Install scikit-image
try:
    from skimage import io
except:
    pip_install('scikit-image')
    from skimage import io

# Read the image into numpy array
im = io.imread(filename)

# Create 3 separate volumes
# Useful for segmentation, volume rendering with color mapping, etc.

for component in range(im.shape[3]):
    volumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScalarVolumeNode", f"image-c{component}")
    slicer.util.updateVolumeFromArray(volumeNode, im[:,:,:,component])

# Show in slice views
slicer.util.setSliceViewerLayers(background=volumeNode, fit=True)

# Show volume rendering
vrDisplayNode = slicer.modules.volumerendering.logic().CreateDefaultVolumeRenderingNodes(volumeNode)

You can then create 3 segments from them using thresholding, but I’m not sure if this is really what you want to achieve.

For example, if you want to visualize the image then probably volume rendering is more appropriate. With slight adjustment of the opacity and color transfer function you can get a 3D visualization like this:

If you acquire volumes that have different data on each channel then you can load it as a single RGB volume like this:

# Create a single volume
# Useful for actual RGB volumes (that has different values in each channel)
volumeNodeRGB = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLVectorVolumeNode", "image-rgb")
slicer.util.updateVolumeFromArray(volumeNodeRGB, im)

If you do want to create segmentation (for example, to measure volumes, count objects, etc.) you can create a segmentation from a numpy array as shown in this example.

Hello @lassoan,
Thank you so much for your kind and quick reply!

I had a look at the image that you provided and the R, G, B components are exactly the same.

Sorry about that, this image was processed by using FIJI, I re-uploaded a new image and it should be fine now(It now have different data on each channel): RGB STACK2

The script you provided are very useful, after I run it, I got 3 volumeNode: image-c0(red), image-c1(green), image-c2(blue). Therefore I see I can take these volume nodes as ‘Master volume’ and create 3 segmentation nodes/segments separately for them:
Figure7

So what need to be add to the script you provided to do this? (To create 3 separate segmentation nodes/segments for these 3 volume nodes programmatically) For example, how to create 3 segmentation nodes with separate thresholds:
Red: 20 Green:30 Blue:40.

Thank you very much!

This script creates 3 segments by thresholding each channel, displays the segmentation in 3D, and displays the 3 channels as an RGB image:

# Inputs
filename = r"c:\Users\andra\OneDrive\Projects\SlicerTesting2022\20220222-Confocal\RGB STACK2.tif"
channelThresholds = [20, 30, 40]

# Install scikit-image
try:
    import skimage
except:
    pip_install('scikit-image')

from skimage import io
import numpy as np

# Read the image into numpy array
im = io.imread(filename)

# Create segments by thresholding each channel

# Create temporary labelmap volume node (it will store the thresholded image that will be imported into the segmentation)
tempLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
tempLabelmapVolumeNode.CreateDefaultDisplayNodes()
# Do not set a color node ID. This will make segment names set based on the labelmap volume's name (instead of looking up the segment name in the color node)
tempLabelmapVolumeNode.GetDisplayNode().SetAndObserveColorNodeID("")

segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
for component in range(im.shape[3]):
    componentImage = im[:,:,:,component]
    componentThreshold = channelThresholds[component]
    thresholdedComponentImage = np.zeros(componentImage.shape)
    thresholdedComponentImage[componentImage > componentThreshold] = 1
    tempLabelmapVolumeNode.SetName(f"Component-{component}")  # the volume node's name will be used as segment name
    slicer.util.updateVolumeFromArray(tempLabelmapVolumeNode, thresholdedComponentImage)
    slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(tempLabelmapVolumeNode, segmentationNode)

slicer.mrmlScene.RemoveNode(tempLabelmapVolumeNode)

# Show segmentation in 3D
segmentationNode.CreateClosedSurfaceRepresentation()
segmentationNode.GetDisplayNode().SetOpacity(0.6)

# Show the image as an RGB volume in slice views
volumeNodeRGB = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLVectorVolumeNode", "image-rgb")
slicer.util.updateVolumeFromArray(volumeNodeRGB, im)
slicer.util.setSliceViewerLayers(background=volumeNodeRGB, fit=True)

Hello @lassoan,
Thank you very much for your thoughtful guidance!
However, after I run the code you just provided above, only one empty segmentaion node was created, it doesn’t contain three separate segments as shown in the figure you provided, and there are no 3D surfaces showing in the render window :
Figure10

the code I am running
# Inputs
filename = r"d:\IMAGE\RGB STACK2.tif"
channelThresholds = [20, 30, 40]

# Install scikit-image
try:
    import skimage
except:
    pip_install('scikit-image')

from skimage import io
import numpy as np

# Read the image into numpy array
im = io.imread(filename)

# Create segments by thresholding each channel

# Create temporary labelmap volume node (it will store the thresholded image that will be imported into the segmentation)
tempLabelmapVolumeNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLabelMapVolumeNode")
tempLabelmapVolumeNode.CreateDefaultDisplayNodes()
# Do not set a color node ID. This will make segment names set based on the labelmap volume's name (instead of looking up the segment name in the color node)
tempLabelmapVolumeNode.GetDisplayNode().SetAndObserveColorNodeID("")

segmentationNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLSegmentationNode")
for component in range(im.shape[3]):
  componentImage = im[:,:,:,component]
  componentThreshold = channelThresholds[component]
  thresholdedComponentImage = np.zeros(componentImage.shape)
  tempLabelmapVolumeNode.SetName(f"Component-{component}")  # the volume node's name will be used as segment name
  slicer.util.updateVolumeFromArray(tempLabelmapVolumeNode, thresholdedComponentImage)
  slicer.modules.segmentations.logic().ImportLabelmapToSegmentationNode(tempLabelmapVolumeNode, segmentationNode)

slicer.mrmlScene.RemoveNode(tempLabelmapVolumeNode)

# Show segmentation in 3D
segmentationNode.CreateClosedSurfaceRepresentation()
segmentationNode.GetDisplayNode().SetOpacity(0.6)

Could you please point out how to fix it? Since in the next steps I need to apply the logical operator effect to these three segments. Thank you so much!

Are you using the latest Slicer Preview Release?

Hello @lassoan,
Yes, what I’m using now is the latest Slicer Preview Release for Windows: 3D Slicer 4.13.0-2022-02-22:
Or do I need to uninstall all other versions of Slicer just to keep this one?
Figure11

The Slicer version you are using is good. It seems that I missed a line when I copied the code into the post above. I’ve added the missing line, please try my script again.

1 Like

Thank you very much for your great guidance! It’s working now :+1:

1 Like