Is it better to integrate settings from Module B into Module A for improved user workflow (or user convenience)?

Hello Dear Developers and Users,

I wanted to get some advice on an issue.
Suppose I am trying to develop a module named A, where one of the inputs of Module A is the output from another module, Module B which has already been developed. In this case, I could develop Module A completely independently. However, this would mean that the user would first need to go to Module B to generate its output, which is required as an input for Module A. Then, user would have to go to Module A and select the output from Module B as one of the inputs before running it.

My question is: for the sake of user convenience, would it be logical to integrate the settings/options of Module B directly into Module A, so that the user doesn’t need to first run Module B and then go to Module A?

Maybe it will be clearer if I explain with an example. In the implementation of Module A, I need the output from the Isodose module, specifically the Number of iso levels and Labels (which seem to be in terms of Dose (Gy) in Iso levels table) which need to be set or adjusted by the user. Can I add these two inputs from the Isodose panel (including number of iso levels and Label (in Gy)) as Spin boxes to the panel of Module A so that when Module A runs, the Isodose module will be executed in the background?

In this regard, I realized that it is possible to access the Isodose module in the Python Interactor using slicer.util.getModule('Isodose'). There is also a method called setProperty. Can I achieve my goal by specifying the arguments of this method?

Best regards.
Shahrokh

1 Like

Regarding this issue, I came across a question titled Call SlicerRT Isodose (or create isodoses) from python which was asked in the 3DSlicer Community.

I realized that in the Python Interactor, in addition to the slicer.util.getModule method which I mentioned in my previous message, there is another method called slicer.util.getModuleLogic that seems to provide access to the settings (logic) of modules, including Isodose module. In other words, it seems that the getModuleLogic method in slicer.util is used for setting the settings of modules.

According to the help provided for the SetNumberOfIsodoseLevels method, it seems that this method takes two input arguments: a vtkMRMLIsodoseNode type and an integer.

The following commands were executed.

>>> object_vtkMRMLIsodoseNode = slicer.vtkMRMLIsodoseNode().CreateNodeInstance()
>>> type(object_vtkMRMLIsodoseNode)
<class 'vtkSlicerIsodoseModuleLogic.vtkMRMLIsodoseNode'>
>>> numberOfColors = 1
>>> myIsodose = slicer.util.getModuleLogic('Isodose').SetNumberOfIsodoseLevels(object_vtkMRMLIsodoseNode, numberOfColors)
>>> type(myIsodose)
<class 'NoneType'>
>>> myIsodose.__dir__()
['__repr__', '__bool__', '__new__', '__doc__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
>>> 

It seems that there was no syntax error when executing the above commands, but since the output type is NoneType, it seems that a logical error must have occurred.

Please guide me on how to implement the settings of the Isodose module in Python so that I can generate the output of this module by taking the RTDose input.

Best regards.
Shahrokh.

Looks like you are on the right track. SetNumberOfIsodoseLevels returns None because it just sets the value. You should be able to call SetNumberOfIsodoseLevels or something similar to confirm that it was set.

On the more general topic, yes, reusing the Logic classes from other modules is definitely the right way of doing things. Also, several modules expose widgets that can be reused in other modules although not all do. You can also include buttons that take you directly to another module, perhaps with some parameters pre-selected, which is sometimes useful.

Thank you for your helpful response! I appreciate the guidance you’ve provided.

Regarding my progress, I believe I have successfully set up the Isodose module settings through the Python Interactor with the following commands:

object_vtkMRMLIsodoseNode = slicer.vtkMRMLIsodoseNode().CreateNodeInstance()
object_vtkMRMLIsodoseNode.SetReferenceDoseValue(47)
object_vtkMRMLIsodoseNode.GetReferenceDoseValue()
object_vtkMRMLIsodoseNode = slicer.vtkMRMLIsodoseNode().CreateNodeInstance()
object_vtkMRMLIsodoseNode.SetDoseUnits(slicer.vtkMRMLIsodoseNode.Gy)
object_vtkMRMLIsodoseNode.SetShowDoseVolumesOnly(1)

Now, I want to apply the object_vtkMRMLIsodoseNode settings to a node of type RTDose. I am using the following command to retrieve the node:

doseNode = slicer.util.getFirstNodeByClassByName('vtkMRMLScalarVolumeNode', '2: RTDOSE: DOSIsoft:RTDOSE:Phase #1 Dosi Dosi 1: Beam setup 1 (GY) [11]: Beam setup 1')

However, I have not been able to find a method within object_vtkMRMLIsodoseNode that can perform the “Generate Isodose” operation. Could you please help me on the method to use for this?

Best regards.
Shahrokh.

I used the following commands to solve this problem:

# Get the dose node
doseVolmeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLScalarVolumeNode', '2: RTDOSE: DOSIsoft:RTDOSE:Phase #1 Dosi Dosi 1: Beam setup 1 (GY) [11]: Beam setup 1')

# Create the isodose node
object_vtkMRMLIsodoseNode = slicer.vtkMRMLIsodoseNode().CreateNodeInstance()

# Initial isodose settings (Similar to the settings of the Isodose module)
object_vtkMRMLIsodoseNode.SetReferenceDoseValue(47)
object_vtkMRMLIsodoseNode.SetDoseUnits(slicer.vtkMRMLIsodoseNode.Gy)
object_vtkMRMLIsodoseNode.SetShowDoseVolumesOnly(1)
object_vtkMRMLIsodoseNode.SetAndObserveDoseVolumeNode(doseVolmeNode)

# Create isodose surface
isodose_logic = slicer.modules.isodose.logic()
modelDose47 = isodose_logic.CreateIsodoseSurfaces(object_vtkMRMLIsodoseNode)

However, I still cannot get the two results below:

  1. The model that is seen in the 3D window with the Isodose module, as shown in the image below.

  2. In the Data module, a node named IsodoseLevel_47Gy has been created. The image below:

Please guide me.
Best regards.
Shahrokh

Sorry… As I mentioned in the previous message, I first set the parameters of the Isodose module in the object named object_vtkMRMLIsodoseNode from the vtkMRMLIsodoseNode class. Then, I applied it to a dose volume node named doseVolumeNode. Today, I realized (in the error log display) that the issue seems to be in this last step. The image below shows this error.

As can be seen, after running this command:
object_vtkMRMLIsodoseNode.SetAndObserveDoseVolumeNode(doseVolumeNode)
I get this error message:

Python console user input: object_vtkMRMLIsodoseNode.SetAndObserveDoseVolumeNode(doseVolumeNode)
Cannot set reference: the referenced and referencing node are not in the same scene

Sorry, I don’t understand the meaning of the phrase Cannot set reference: the referenced and referencing node are not in the same scene..

Best regards.
Shahrokh

Excuse me…
I was able to move one more step forward. Instead of using the command

myIsodoseSurface = slicer.vtkSlicerIsodoseModuleLogic().CreateIsodoseSurfaces(object_vtkMRMLIsodoseNode)

I executed the following command (as mentioned in the guidance provided in question of Call SlicerRT Isodose (or create isodoses) from python):

myIsodoseSurface = slicer.modules.isodose.logic().CreateIsodoseSurfaces(object_vtkMRMLIsodoseNode)

Therefore, the full commands executed are as follows:

doseVolumeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLScalarVolumeNode', '2: RTDOSE: DOSIsoft:RTDOSE:Phase #1 Dosi Dosi 1: Beam setup 1 (GY) [11]: Beam setup 1')
object_vtkMRMLIsodoseNode = slicer.vtkMRMLIsodoseNode().CreateNodeInstance()
object_vtkMRMLIsodoseNode.SetScene(doseVolumeNode.GetScene())
object_vtkMRMLIsodoseNode.SetReferenceDoseValue(47)
object_vtkMRMLIsodoseNode.SetDoseUnits(slicer.vtkMRMLIsodoseNode.Gy)
object_vtkMRMLIsodoseNode.SetAndObserveDoseVolumeNode(doseVolumeNode)
#myIsodoseSurface = slicer.vtkSlicerIsodoseModuleLogic().CreateIsodoseSurfaces(object_vtkMRMLIsodoseNode)
myIsodoseSurface = slicer.modules.isodose.logic().CreateIsodoseSurfaces(object_vtkMRMLIsodoseNode)

As you can see in the image below,

similar to the Isodose module execution, which produces a model output placed in a folder, when running the command of:

myIsodoseSurface = slicer.modules.isodose.logic().CreateIsodoseSurfaces(object_vtkMRMLIsodoseNode)

this folder is also created, but it is empty, and the 3DSlicer environment completely freezes, to the point where none of the icons in the red, yellow, or green windows are selectable. In the end, I have to close 3DSlicer.

I think I am getting closer to my answer.
Please guide me.
Best regards.
Shahrokh.

I think I was mostly successful. The problem I mentioned in my previous message (the freezing of the 3DSlicer display environment, such as 3D view) was because I hadn’t set up its color table. Based on this, the commands became as follows.

# Get the dose volume node by class and name
doseVolumeNode = slicer.util.getFirstNodeByClassByName('vtkMRMLScalarVolumeNode', '2: RTDOSE: DOSIsoft:RTDOSE:Phase #1 Dosi Dosi 1: Beam setup 1 (GY) [11]: Beam setup 1')

# Create a new Isodose node instance for displaying isodose surfaces
object_vtkMRMLIsodoseNode = slicer.vtkMRMLIsodoseNode().CreateNodeInstance()

# Set the scene for the isodose node to match the dose volume node's scene
object_vtkMRMLIsodoseNode.SetScene(doseVolumeNode.GetScene())

# Set the dose units to Gray (Gy)
object_vtkMRMLIsodoseNode.SetDoseUnits(slicer.vtkMRMLIsodoseNode.Gy)

# Set the reference dose value (in Gy) for isodose levels
object_vtkMRMLIsodoseNode.SetReferenceDoseValue(47.00)

# Observe the dose volume node to track its changes
object_vtkMRMLIsodoseNode.SetAndObserveDoseVolumeNode(doseVolumeNode)

# Setup the color table for the isodose node based on the dose volume
slicer.modules.isodose.logic().SetupColorTableNodeForDoseVolumeNode(doseVolumeNode)

# Create isodose surfaces based on the configured isodose node
slicer.modules.isodose.logic().CreateIsodoseSurfaces(object_vtkMRMLIsodoseNode)

As can be seen in the commands above, I want the number of iso levels to be one, and the isodose surface to be generated for a dose of 47.00 Gy. When I run these commands, the following result occurs.

as shown above, This result is exactly the same as the default settings of the Isodoses module, meaning the number of iso levels is 6, and the dose values are 5, 10, 15, 20, 25, and 30 Gy. In other words, the value I set with the above commands (47.00 Gy) has essentially had no effect here.

I don’t know where the problem is from. I hope you can guide me, and until then, I will continue with my efforts.
Please guide me.
Best regards.
Shahrokh