Decoupled (custom) slice widget in messagebox

Hi Developers,

I am curious if it would be possible for have a message box showing a slice widget. I somewhat “hacked” that but I am not happy with it and I would like to know if there is a clean way to do it.

I am asking because prior to doing prostate biopsy our module receives pre-procedural images. The operator has to confirm if an endo-rectal coil was used during pre-procedural acquisition.

The following screenshot displays, what I ‘hacked’ so far.

class ...
  def __init__(self, widgetName, text="", parent=None, **kwargs):
    qt.QMessageBox.__init__(self, parent if parent else slicer.util.mainWindow())
    self.widgetName = widgetName
    self.text = text
    for key, value in kwargs.iteritems():
      if hasattr(self, key):
        setattr(self, key, value)
    self.setup()

  def setup(self):
    widget = self.layoutManager.sliceWidget(self.widgetName)
    if not widget:
      raise AttributeError("Slice widget with name %s not found" %self.widgetName)
    sliceNode = widget.sliceLogic().GetSliceNode()

    self.sliceWidget = slicer.qMRMLSliceWidget()
    self.sliceWidget.setMRMLScene(widget.mrmlScene())
    self.sliceWidget.setMRMLSliceNode(sliceNode)

    self.layout().addWidget(self.sliceWidget, 0, 1)
    self.layout().addWidget(qt.QLabel(self.text), 1 ,1)
    self.layout().addWidget(self.createHLayout(self.buttons()), 2, 1)

  def exec_(self):
      widget = self.layoutManager.sliceWidget(self.widgetName)
      self.sliceWidget.setFixedSize(widget.size)
      return qt.QMessageBox.exec_(self)

Thanks for any help.

You should be able to just show the slice widget directly, without a layout manager. Have you tried that?

Hi Andras,

I tried the following. For any reason it displays the red slice widget, but I didn’t even specify the name…

sliceWidget = slicer.qMRMLSliceWidget()
sliceWidget.setMRMLScene(slicer.mrmlScene)
sliceWidget.show()

I would like to specify my own slice widget name, so I tried the following code which crashes Slicer:

sliceWidget = slicer.qMRMLSliceWidget()
sliceNode = slicer.vtkMRMLSliceNode()
sliceNode.SetLayoutName(“Black”)
slicer.mrmlScene.AddNode(sliceNode) # crashes here
sliceWidget.setMRMLSliceNode(sliceNode)

Did I miss something?

You need to create nodes in a certain order. I needed this some time ago and remember that Slicer crashed when that strict order was not followed bit otherwise worked. I’m not sure I can find that code, but you should be able to replicate what the layout manager does by inspecting its source code. Let me know if you have trouble finding it.

Andras I found this code https://github.com/Slicer/Slicer/blob/dddd78bbf570644825caed947d42def104bfbc4c/Libs/MRML/Widgets/qMRMLLayoutManager.cxx#L296-L329

The following code partially works partially. Unfortunately the method calls, that are commented out, are not accessible from Python.

sliceLogic = slicer.vtkMRMLSliceLayerLogic()
sliceLogic.SetMRMLScene(slicer.mrmlScene)
sliceNode = slicer.vtkMRMLSliceNode()
sliceNode.SetLayoutName("Black")
slicer.mrmlScene.AddNode(sliceNode)
sliceLogic.SetSliceNode(sliceNode)

lm = slicer.app.layoutManager()
sliceLayoutColor = qt.QColor.fromRgbF(sliceNode.GetLayoutColor()[0], sliceNode.GetLayoutColor()[1], sliceNode.GetLayoutColor()[2])
sliceWidget = slicer.qMRMLSliceWidget(lm.viewport())
#sliceWidget.setSliceViewName("test") # not accessible from python
sliceWidget.setObjectName("qMRMLSliceWidget" + sliceNode.GetLayoutName())
#sliceWidget.setSliceViewLabel(sliceViewLabel) # not accessible from python
#sliceWidget.setSliceViewColor(sliceLayoutColor) # not accessible from python
sliceWidget.setMRMLScene(slicer.mrmlScene)
sliceWidget.setMRMLSliceNode(sliceNode) # crashes here again


widget = qt.QWidget()
widget.setLayout(qt.QGridLayout())
widget.layout().addWidget(sliceWidget)
sliceWidget.show()

widget.show()

When hovering with the mouse on that sliceWidget, I am not getting the following errors:

Traceback (most recent call last):

 File "/Applications/Slicer.app/Contents/lib/Slicer-4.9/qt-scripted-modules/DataProbe.py", line 265, in processEvent

sliceView = slicer.app.layoutManager().sliceWidget(sliceNode.GetLayoutName()).sliceView()

AttributeError: 'NoneType' object has no attribute 'sliceView'

Seems like I need to register the newly created sliceWidget in the layoutManager?

These should be made Q_INVOKABLE:

@che85 can you try adding that to see if it fixes the python example?

Considering Q_PROPERTY similar to

This works for me on latest nightly build:

import SampleData
volumeNode = SampleData.SampleDataLogic().downloadMRHead()

sliceWidget = slicer.qMRMLSliceWidget()
sliceWidget.setMRMLScene(slicer.mrmlScene)
sliceNode = slicer.vtkMRMLSliceNode()
sliceNode.SetName("MySlice")
sliceNode.SetLayoutName("Black")
slicer.mrmlScene.AddNode(sliceNode)
sliceWidget.setMRMLSliceNode(sliceNode)

sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)
sliceLogic.GetSliceCompositeNode().SetBackgroundVolumeID(volumeNode.GetID())

sliceWidget.show()

I am getting a bunch of errors. I assume that the additional slice widget needs to be registered somehow, right?

That’s fine, those are only reported by data probe, as it uses the layout manager to look up slice view node. I’ll fix that today. No need to register anything.

Another thing is, that only “Reformat” is available in the slice orientation drop down

I’ve checked this again and found that the layout manager automatically creates a widget for all slice nodes that are added to the scene. The simplest is to use this widget, as there are no side effects.

# Load some data into the scene
import SampleData
volumeNode = SampleData.SampleDataLogic().downloadMRHead()

# Create slice node (this automatically creates a slice widget)
sliceNode = slicer.mrmlScene.CreateNodeByClass('vtkMRMLSliceNode')
sliceNode.SetName("Black")
sliceNode.SetLayoutName("Black")
sliceNode.SetLayoutLabel("BL")
sliceNode.SetOrientation("Sagittal")
sliceNode = slicer.mrmlScene.AddNode(sliceNode)

# Select background volume
sliceLogic = slicer.app.applicationLogic().GetSliceLogic(sliceNode)
sliceLogic.GetSliceCompositeNode().SetBackgroundVolumeID(volumeNode.GetID())

# Get the automatically created slice widget
lm=slicer.app.layoutManager()
sliceWidget=lm.viewWidget(sliceNode)

# Move slice widget to a different layout
# (it can be added to any other layout)
sliceWidget.setParent(None)
# Show slice widget
sliceNode.SetMappedInLayout(1)

Note: when you switch layouts, the widget gets hidden, so you have to call sliceNode.SetMappedInLayout(1) again.

Great! That’s working. Thanks a lot!

1 Like

This is awesome, thanks for sharing!
Is there a way to instead of decoupling the slice widget, have it appear to the left of the current preset layout as part of the currently loaded layout template?

Do I use the sliceWidget.setParent() feature? If so, how do I obtain an instance of the currently used layout?

If you want to add views to the layout then you can customize viewer layout as shown here.

Views are grouped based on viewgroup property. To get an “independent” view, set its viewgroup property to a unique value.

Ok, but i’m unsure how the syntax would be. Can you please give me an example of Slicer’s preset layout named “Conventional Widescreen” where the slice nodes are grouped on the right and the 3d view on the left. I should be able to play around with that to get what I am looking for. Thanks!

Go to the script repository link that I posted above. XML description of all the built-in view presets are linked from there.