Change data probe to view MIP HU

Hi, I’m looking at very small features on CTA scans and there are 2 things I’d like data probe to do but it doesn’t and I’m not sure how to modify it to get the behaviour I want.

  1. When I am in one of the slice viewers, e.g. the red one, and scroll up and down with the mouse wheel or the arrows in a fixed point, data probe does not update the HU nor the position. I have to move the mouse in the viewer for data probe to update. Is there a way to make it update even when the mouse position has not changed but the slice has?

  2. I am visualizing MIP in the slice views with this code, however when I move the mouse around one of the slice viewer, the scalar value that I see in data probe in the left bottom corner is still the HU of the single slice, not the one of the MIP. Is there a way to change this behaviour? (I have attached an example in case it’s not clear: in the screenshot I am looking at a 5 mm MIP. The mouse arrow is not captured but I was pointing at the red slice exactly on the intersection of the yellow and green axes. I’d like data probe to show me the HU of the MIP view, so something around 300, but it shows me the HU of the single slice which is 7)

    .

Thank you for any help,

Marta

Hi Marta,

Sorry for not getting a response sooner!

Regarding 1) what you are describing is behavior that is like the same as issue 3262 in Slicer’s issue tracker. I have added a note to that issue specifying with your experience as a way to increase its likelihood of it getting fixed in the future.

Regarding 2) I’m not exactly sure what is going on there. I would point you to using the SimpleFilters module which makes it easy to generate projections and use other volume filters. You would set the output volume to be a new volume, and DataProbe should provide you updated results when you move your mouse position over this output volume.

1 Like

Since thick slice feature is not exposed on the GUI, we most likely not update the data probe’s behavior for now. However, you can print pixel value of the resliced image by copy-pasting this code snippet into the Python interactor:

def onMouseMoved(observer,eventid):  
  xyz = [0,0,0]
  crosshairNode.GetCursorPositionXYZ(xyz)
  pixelValue = reslice.GetOutput().GetScalarComponentAsDouble(int(xyz[0]),int(xyz[1]), int(xyz[2]), 0)
  print("{0} -> {1}".format(xyz, pixelValue))

sliceNode = slicer.mrmlScene.GetNodeByID('vtkMRMLSliceNodeRed')
appLogic = slicer.app.applicationLogic()
sliceLogic = appLogic.GetSliceLogic(sliceNode)
sliceLayerLogic = sliceLogic.GetBackgroundLayer()
reslice = sliceLayerLogic.GetReslice()

crosshairNode=slicer.util.getNode('Crosshair') 
crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, onMouseMoved)

Why do you need the MIP value?

1 Like

@mag To provide you with an update, the issue described in 1) I can now confirm is solved thanks to a fix by @lassoan. You can download and install today’s nightly build and it will have that fix.

1 Like

That’s awesome, thanks so much!

I am extending an existing study and need to determine the location of maximum contrast enhancement in some vascular segments. The way the previous research fellow did it was to look at 5 mm MIP to find the maximum HU on the axial view, then refine the location by scrolling up and down single slices, so I am trying to follow the exact same steps even if with a different software. Once I have the point of interest I create a small spherical segment and get the average HU at that position.

Hi,

I have come back to this problem after some time and I’d have some more questions if anybody has time to help.
Last time when I had to look at the MIPs I used to copy the code that Andras posted into the Python interactor whenever I needed it. However I would like to have it in my script so that when I click my custom button to display the MIP this code is automatically executed. Also, I’d like to add some code such that when I click my custom button to return to the non-MIP visualization, Slicer stops printing the mouse position/HU in the interactor.

I already had the buttons to show/disable MIPs. So I tried to just add the onMouseMoved function to print mouse position/HU, but I get an error about the number of arguments every time I move the mouse (see screenshot) and I don’t understand why.

In the widget code I have:

[…]

    self.showMIPRedButton = qt.QPushButton("MIP Red")
    self.showMIPRedButton.toolTip = "Show MIP in red viewer"
    self.showMIPRedButton.enabled = True
    self.parametersFormLayout.addRow(self.showMIPRedButton)

    self.undoMIPButton = qt.QPushButton("Default view")
    self.undoMIPButton.toolTip = "Reset view to default mode"
    self.undoMIPButton.enabled = True
    self.layout.addWidget(self.undoMIPButton)

    self.showMIPRedButton.connect('clicked(bool)', self.onShowMIPRedButton)
    self.undoMIPButton.connect('clicked(bool)', self.onUndoMIPButton)

def onShowMIPRedButton(self):
    logic = probeMIPLogic()
    logic.showMIPRed(self.inputSelector.currentNode())

def onUndoMIPButton(self):
    logic = probeMIPLogic()
    logic.MIPreset()

And then in the logic code:

    # ======== FROM ANDRAS POST ======== #
def onMouseMoved(observer,eventid):
    xyz = [0,0,0]
    crosshairNode.GetCursorPositionXYZ(xyz)
    pixelValue = reslice.GetOutput().GetScalarComponentAsDouble(int(xyz[0]),int(xyz[1]), int(xyz[2]), 0)
    print("{0} -> {1}".format(xyz, pixelValue))
    # ==================================== #

def showMIPRed(self, inputVolume):
    """ Show maximum intensity projection in Red slice viewer. Does not change scalar volume, only modifies
    viewing settings.
        """
    sliceNode = None
    sliceLogic = None
    vtkMRMLSliceNode = 'vtkMRMLSliceNodeRed'
    sliceNode = slicer.mrmlScene.GetNodeByID(vtkMRMLSliceNode)
    if sliceNode:
        appLogic = slicer.app.applicationLogic()
    if appLogic:
        sliceLogic = appLogic.GetSliceLogic(sliceNode)
    if not sliceNode or not sliceLogic:
        print "Err"
        return
    sliceLayerLogic = sliceLogic.GetBackgroundLayer()
    self.reslice = sliceLayerLogic.GetReslice()
    self.reslice.SetSlabModeToMax()
    self.reslice.SetSlabNumberOfSlices(20)
    self.reslice.SetSlabSliceSpacingFraction(1)
    sliceNode.Modified()

    # ======== FROM ANDRAS POST ======== #
    crosshairNode=slicer.util.getNode('Crosshair')
    crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, self.onMouseMoved)
    # =================================== #
    return

def MIPreset(self):
    """ Reset slice viewers to default setting (no MIP).
        """
    print "Disabling MIP in the slice viewers"
    sliceNode = None
    sliceLogic = None
    for slice_color in ["Green", "Red", "Yellow"]:
        vtkMRMLSliceNode = 'vtkMRMLSliceNode' + slice_color
        sliceNode = slicer.mrmlScene.GetNodeByID(vtkMRMLSliceNode)
        if sliceNode:
            appLogic = slicer.app.applicationLogic()
        if appLogic:
            sliceLogic = appLogic.GetSliceLogic(sliceNode)
        if not sliceNode or not sliceLogic:
            print "Err"
            return
        sliceLayerLogic = sliceLogic.GetBackgroundLayer()
        reslice = sliceLayerLogic.GetReslice()
        reslice.SetSlabModeToMax()
        reslice.SetSlabNumberOfSlices(1)
        reslice.SetSlabSliceSpacingFraction(1)
        sliceNode.Modified()
    return

Thanks in advance for any help!

def onMouseMoved(observer,eventid):
Previously it was a simple function. If you have made it a method of a class you will need self as an argument like other methods such as showMIPRed. The error you’re having is because of the mismatch of expected arguments.

Try changing to def onMouseMoved(self, observer, eventid).

That’s right, sorry. As you can tell I’m a very beginner at Python.
I am still a bit confused though because when I call the method in this line

crosshairNode.AddObserver(slicer.vtkMRMLCrosshairNode.CursorPositionModifiedEvent, self.onMouseMoved)

I’m not specifying any argument at all. How does onMouseMoved know what observer and eventid are? Is it AddObserver who passes these argument to onMouseMoved? And if so how can I pass crosshairNode to onMouseMoved without having to specifying eventid and observer which I don’t have?

Thanks

The easiest way is to store variables in the object (for example, you can store the crosshair node in self.crosshairNode variable).

You can use lambda functions to pass arbitrary additional arguments to callback functions. If you need to access the caller object then you can use the technique described here.