Centering on a segment from a script

I would like to invoke in a script “Jump slices” functionality of Segment Editor. I found jumpSlices(), but have not figured out yet how to invoke it, given Segmentation node and ID of the segment that I need to jump to.

@cpinter this must be trivial - can you help?

Related to this, would it be possible to expose in python GetSegmentCenter()/GetSegmentCenterRAS()?

Meanwhile, I will use the approach @che85 implemented in QuantitativeReporting - calculate center outside of Segmentations/Segment logic, and use Markups logic to jump: https://github.com/QIICR/QuantitativeReporting/blob/master/QuantitativeReporting/QRCustomizations/CustomSegmentEditor.py#L65-L69.

Certainly. I think once this is done the script is indeed trivial. I’ll do this change now.

1 Like

It’s in. Do you need further help with the script?

1 Like

I’ve also added VTK size hints to the header file that makes the method return a Python tuple instead of a raw pointer.

We should use these hints everywhere where we return a double* to make the result more Python-friendly. VTK also has hints for range checks on inputs (to prevent crashes in Python caused by trying to get/set an out of range item in a data set).

1 Like

Thanks a lot guys! Unfortunately, to help me further, you might need a magic wand - now I need a mac binary with the new features, and also several extensions alongside.

I guess my main worry is that extension builds are completely broken on mac, as I noted in this post, which didn’t get any response: Multiple issues with extension builds.

I guess I should hope that I was wrong and just mis-interpreted the interface of the dashboard, and magically the extensions I need will be accessible tomorrow on mac when I open ExtensionManager…

Aha! I was in a rush, and glossed over Christian’s code too quickly to realize I could use it easily, since the centroid getter function is static. I can easily solve my jumpSlices() problem with just a few lines, using my existing Slicer binary and extensions. Here’s the recipe in case anyone else needs this too (assumes that the QuantitativeReporting extension is installed!):

from QRCustomizations import CustomSegmentEditor
csl=CustomSegmentEditor.CustomSegmentEditorLogic()
segNode = slicer.mrmlScene.GetFirstNodeByClass("vtkMRMLSegmentationNode")
segmentNode = segNode.GetSegmentation().GetNthSegment(0)
centroid = csl.getSegmentCentroid(segNode, segmentNode)
markupsLogic = slicer.modules.markups.logic()
markupsLogic.JumpSlicesToLocation(centroid[0],centroid[1],centroid[2], False)

@cpinter maybe the jumpSlices() function I referenced in the initial post should reuse Markups logic to jump slices to reduce code redundancy? I didn’t look carefully whether there is anything extra done that requires a custom implementation.

The gist of the jump slices method in Markups logic is just one line of code. The jumpSlices code in the SH plugin also considers in which views the segmentation is visible, while the Markups one does not (and cannot). Other than that the rest of the code that makes that function long is getting the item, the segmentation, and the segment.

Using a custom extension is certainly a solution, but in the long term I would recommend developers to use Slicer core functions if available.

Slicer-4.10 returning a swig pointer (such as _000001bfd4897340_p_void) is a minor inconvenience. You can still access the values using the helper function below (in the nightly version the VTK hint is there so the position is directly returned as a Python tuple).

def getListFromPointer(bufferPtr, scalarType=vtk.VTK_DOUBLE, numberOfElements=3):
  """Convert swig pointer (_..._p_void) to list object"""
  bufferArray = vtk.vtkAbstractArray.CreateArray(vtk.VTK_DOUBLE)
  bufferArray.SetVoidArray(bufferPtr, numberOfElements, True)
  buffer = [bufferArray.GetValue(i) for i in range(numberOfElements)]
  return buffer

Example usage:

>>> ptr=getNode('Segmentation').GetSegmentCenter("Segment_1")
>>> ptr
'_000001bfd4897340_p_void'
>>> getListFromPointer(ptr)
[8.254768371581932, -18.07139587402301, -10.214302062987997]
2 Likes

Yes, but if an experienced Slicer engineer, such as @che85, made the decision to re-implement this functionality for one reason or another, then maybe there’s a need to revisit something. What are the chances that a beginner developer will figure this out.

Andras, can you share your thought process? I guess it is a minor inconvenience when you know what is going on. I just didn’t want my previous post to make it look as if it was a minor inconvenience for me. I simply had no clue what it means when I saw that p_void.

Do you mean how did I know how to access the buffer content? I remembered that someone asked this already on Slicer or VTK mailing list and there was a positive answer, so I knew that it was possible. I google searched for VTK p_void and found a test in VTK source code that showed how to get the values.

@che85 implemented this first, we added it to Slicer core later. I checked how he implemented and probably even used some code from his implementation.

I agree that it is not easy. I would say a good assumption is that anything that is not super-specific to a project has come up already in some other projects, so there is at least something to start from and the task is to find it. If a Google web search and local search in all Slicer’s source code (including VTK, CTK, ITK, …) does not bring up anything then I would recommend to ask on the Slicer forum.

It would be nice to proactively document things, but it would be a lot of work, not very exciting kind, and would be hard to know what topics we should focus on. In contrast, answering questions on the forum helps people immediately and it also generates an indexed, searchable knowledge base to supplement other forms of documentations. Not perfect system, but considering that we have limited amount of time, it is reasonable.

2 Likes

Thanks Andras, this is helpful, and I agree with you.

@che85 has been through the experience of implementing things that he thought are of general interest, and abstracted them out into Slicer Development Toolbox. I thought it might be nice to consider this as a central place for the functionality of general interest, which is either not ready for the core, or there are no resources to move it to the core.

1 Like

I agree that there are lots of useful features in that toolbox, and the plan is to gradually move the most useful features into Slicer core. It can indeed serve as a staging area in general.

I also like the approach of testing concepts in a single extension(not in a shared utility extension) then send a pull request to Slicer core. This allows reviewing the code earlier and dealing with smaller chunks of code at once. The disadvantage is that the API is not widely tested, so the API often has to be tuned later to support all important use cases.

This works as it should; I wonder if by chance there were any recent movements towards moving this into Slicer Core? Or if there is a way to find a centroid without QuantitativeReporting installed?

You can find a centroid function by using the Segmentstatistics module or calling it from a script.

is there an example python code for that? is the Segmentstatistics build into core Slicer?

Yes, please see this example in the script repository.

2 Likes

Since last time this issue was discussed, we added segment center computation method to the segmentation node. This center computation provides a visible part of the largest piece of the segment (while centroid may be in an empty area if the segment consists of multiple islands), so it is ideal for refocusing views to show a segment to the user. See example in the script repository on how to use it.

2 Likes