How to convert volume to label map programmatically

I know how to do it in the GUI (go to ‘Volumes’ and at the bottom of ‘Volume Information’ go to ‘Convert to label map’).

I found the corresponding function to be:

void qSlicerVolumesModuleWidget::convertVolume()

But I cannot figure out how to access the function.

I can get
qSlicerVolumesModuleWidget
with
slicer.util.getModuleWidget('Volumes')
but it does not have the convertVolume() method (contrary to the documentation)

Am I missing something or is this a bug?

The convertVolume() method is present in the current Slicer code Slicer/qSlicerVolumesModuleWidget.cxx at cb3a1ff14fac3419f3e3f08632a6cac6bedb52ca · Slicer/Slicer · GitHub, but I can confirm that it does not seem to be accessible from Python any way I can figure out either. Maybe something has gone wrong with the wrapping?

As a hack workaround, you could programmatically press the button in the Volumes module as follows:

w = slicer.util.getModuleWidget('Volumes')
convertButton = slicer.util.findChild(w, 'ConvertVolumeButton')
convertButton.clicked()

You would need to select the active volume in the module (and the target node if you aren’t replacing the input node) if you were to go this route.

Alternatively, you could look at the code inside the convertVolume() method and reimplement it in your own module.

1 Like

Another workaround: create a label-map volume node from the pixel data of the volume node

  1. get the volume node data that you want to convert to a label map:

    volume_node = slicer.util.getFirstNodeByName(volume_name)
    volume_data = slicer.util.arrayFromVolume(volume_node)
    volume_data = volume_data.astype(np.uint8())
    
  2. Create a new label map volume node (or use an existing one):

    label_node = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
    
  3. Place the volume data into the label map node:

    slicer.util.updateVolumeFromArray(label_node, volume_data)
    
  4. The new label-map volume node will probably have a different IJKToRAS matrix (and origin), so update them with the ones from the original volume node

    volume_matrix = vtk.vtkMatrix4x4()
    volume_node.GetIJKToRASMatrix(volume_matrix)
    volume_origin = volume_node.GetOrigin()
    
    label_node.SetIJKToRASMatrix(volume_matrix)
    label_node.SetOrigin(volume_origin)
    
1 Like

Also, I just looked a little deeper, and the functionality you originally wanted (and pretty much recreated) is available through the following:

volume_node = slicer.util.getFirstNodeByName(volume_name)
label_node = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLabelMapVolumeNode')
volumes_logic = slicer.modules.volumes.logic()
volumes_logic.CreateLabelVolumeFromVolume(slicer.mrmlScene, label_node, volume_node)

This function requires a labelmap node, so it, by itself, does not handle the case where you want to created labelmap node to replace the original scalar volume node, but it does duplicate the functionality in the code you just shared, where you get a new labelmap node created from the volume node.

The CreateLabelVolumeFromVolume method code is here Slicer/vtkSlicerVolumesLogic.cxx at 409c7f035aa9015863cffc827ed37661322a550d · Slicer/Slicer · GitHub

1 Like