How to put a volume in a 3D view programmatically?

Hello, I’ve been scratching my head and trying to look through documentation and code samples, and found nothing…

I can use this code (up to typos, edited a little here from live code), based on code found in the CompareVolumes module, to put a volume into a slice view:

def showVolumeInSlice(volumeNode, viewName, orientation) 
        layoutManager = slicer.app.layoutManager()
        sliceWidget = layoutManager.sliceWidget(viewName)  # Use the view's singletontag
        compositeNode = sliceWidget.mrmlSliceCompositeNode()
        compositeNode.SetBackgroundVolumeID(volumeNode.GetID())
        compositeNode.SetForegroundVolumeID("")
        sliceNode = sliceWidget.mrmlSliceNode()
        sliceNode.SetOrientation(orientation)

How do I do the equivalent for a 3D view? In CompareVolumes, this was left as a #TODO; none of the relevant classes (vtkMRMLViewNode, qMRMLThreeDView, qMRMLThreeDWidget) seem to carry any methods referring to volume ID. I looked at DisplayManagers, but it didn’t seem to be there either…

Am I going all wrong about it?

Volume rendering in the threeD views is handled differently from the Slice layers. What you would want to do is create display nodes with vtkSlicerVolumeRenderingLogic::CreateDefaultVolumeRenderingNodes and set the ViewIDs for each of the threeD views where you want them to show up. If you want more than one volume in a view (i.e. to show overlap or to cross-fade) you can make multiple display nodes per volume and then use the Multi-Volume renderer, which is still marked as experimental as it probably doesn’t handle all the same modes as the normal ones.

It would be great if you could expand the CompareVolumes module to include volume rendering.

1 Like

Thanks! that was definitely a step in the right direction, but things aren’t quite working the way I want them to.

Some context: I’m trying to write a module where a user can select a volume (using a standard qMRMLNodeComboBox), and then, among other things, that volume gets displayed in a 3D view (and also a slice view). I want the volume to switch if the user changes the selected volume.

My function (called, ultimately, from the selector’s currentNodeChanged(bool) signal) looks like this:

def showVolumeIn3DView(volumeNode):
    renderingLogic = slicer.modules.volumerendering.logic()
    displayNode = renderingLogic.CreateDefaultVolumeRenderingNodes(volumeNode)
    widget = slicer.app.layoutManager().threeDWidget(0)  # See note below
    displayNode.SetViewNodeIDs([widget.mrmlViewNode().GetID()])
    renderingLogic.UpdateDisplayNodeFromVolumeNode(displayNode, volumeNode)

Before I added the last line (which I found here), the volume that was active before opening my module was shown in the 3D view, and didn’t change at all. With this line, it does change – but only the first time every volume is selected. That is, if Vol1 was active, the module opens showing Vol1. Now,

  • select Vol2 → shows Vol2.
  • Then, select Vol1 again → still shows Vol2, nothing happens in the 3D view.

I’ve tried storing the displayNode, and calling renderingLogic.RemoveVolumeRenderingDisplayNode() on the “old” displayNode before the CreateDefaultVolumeRenderingNodes() for the new volume, thinking that would help reset the display node – but it did nothing. I also tried to do

    displayNode.SetViewNodeIDs([])
    displayNode.SetViewNodeIDs([widget.mrmlViewNode().GetID()])

to try to force the display node to “re-install” itself on the view node – again, to no avail. I also tried both together. I have to say, my instinct is still that I’m trying things in a fundamentally misguided way – except, I could find no guide…

While working on this, I think I also found a bug in the view node management. Docs seem to indicate I can pick a 3D-view by name; for slice views, this “name” is the value set in the layout XML by the singletontag attribute. But for 3D views – first, the name gets a funny prefix; but then, the function just doesn’t work at all, always returning None. The singleton tag I used for the 3D view was “1”.

>>> lm = slicer.app.layoutManager()
>>> w = lm.threeDWidget(0)
>>> w.name
'ThreeDWidget1'
>>> lm.threeDWidget('ThreeDWidget1')
>>> print(lm.threeDWidget(w.name))
None

Thanks again – you’ve already been very helpful!

This code may be helpful. I use it daily to switch the current volume rendered in 3D.

def doVolumeRendering(inputVolume, presetName):
    if inputVolume is None:
        return
    
    # Go to VolumeRendering module.
    mainWindow = slicer.util.mainWindow()
    mainWindow.moduleSelector().selectModule("VolumeRendering")
    
    # Hide current displayed node. N.B - using widgetRepresentation, not logic of VolumeRendering.
    currentDisplayNode=slicer.modules.volumerendering.widgetRepresentation().mrmlDisplayNode()
    if currentDisplayNode is not None:
        currentDisplayNode.SetVisibility(False)
    
    # Change current volume of VolumeRendering module.
    slicer.modules.volumerendering.widgetRepresentation().setMRMLVolumeNode(inputVolume)
    
    # Show 3D of current volume.
    displayNode = slicer.modules.volumerendering.logic().CreateDefaultVolumeRenderingNodes(inputVolume)
    displayNode.SetVisibility(True)
    
    # Apply a preset.
    preset=slicer.modules.volumerendering.logic().GetPresetByName(presetName)
    displayNode.GetVolumePropertyNode().Copy(preset)

@shai-ikko - I think what @chir.set sent looks like it should help you.

If you are still having trouble it would help if you could reduce the issue to a single script that downloads sample data, and implements the volume switching behavior so that anyone could try running it in their own slicer just by pasting into the python console. This would be helpful because it’s hard to debug code just by looking at snippets. Also (at least for me) creating such a self-contained example helps me narrow down the issue and sometimes even solve it.

I wonder if it would be valuable to be able to show volume renderings in the 3D viewer from the popup widget view controller similarly to the 2D Slice views being able to select volumes to show from the popup widget slice controller. There is currently different methods for being able to select a volume to show in the 3D view vs 2D view. There is the drag-and-drop method from the Data module which works for propagation to the 2D and 3D view, but there is not a similar thing for click selection. With such a feature then maybe @shai-ikko wouldn’t have to create a custom qMRMLNodeComboBox to do this propagation.

Hi,

Thanks, all, for your suggestions. I’ve had to go do other things, and it will take some time before I get back to this. I do intend to, but not in the next few weeks.