Import order in a scripted loadable module

Problem

What is the order of the module loading in a scripted loadable module? I realized that I cannot access the slicer module in a class attribute. For example, the code

class materWidget(ScriptedLoadableModuleWidget, VTKObservationMixin):
    pointSet = qt.Signal(slicer.vtkMRMLMarkupsFiducialNode)
    ...
    def __init__(self, parent=None):
        ScriptedLoadableModuleWidget.__init__(self, parent)
        ...

    def setup(self):
        ScriptedLoadableModuleWidget.setup(self)
        ...

fails on the line where the class attribute is defined with the following error:

AttributeError: module 'slicer' has no attribute 'vtkMRMLMarkupsFiducialNode'

On the other hand, if I make pointSet an instance variable (by putting it into __init__ for instance), it works. Note that import slicer is already present at the top of the file. I must be missing something in the Python import logic… How could I use the slicer module in a class attribute?


Background information

I want to emit a Qt signal once a specific MRML event has been triggered:

mrml_event = slicer.vtkMRMLMarkupsFiducialNode.PointPositionDefinedEvent

def onPointSet(observer, eventid):
    self.pointSet.emit(observer)

id = self.markup_node.AddObserver(mrml_event, onPointSet)

@qt.Slot(slicer.vtkMRMLMarkupsFiducialNode)
def test_slot(markup_node):
    print(markup_node.GetName())

self.pointSet.connect(test_slot)

Why do I complicate my life by connecting a Qt signal to an MRML event callback? Because I want to connect multiple slots to the same event. In this case, when a control point is added to the Markups node, I want to perform multiple tasks:

  • change the color of a button (widget-related)
  • execute a computation (logic-raleted)
  • … (features that will come in the future)

The signal-slot mechanism allows me to completely separate the logic from the GUI.
A PyQt signal must be a defined as a class attribute, that’s why this problem with pointSet came up.

Hi, that’s not the right way to connect MRML events to Qt functions. MRML nodes are VTK-based objects, so they don’t have Qt signals. Typically, you would add a VTK-style observer function to your module widget class. That observes events from your Markups node. The observer function is in your module widget class, so it has direct access to Qt objects. It can update button colors directly using code like self.ui.myButton.setStyleSheet(…).

There are probably lots of examples if you search in the source code of other modules. E.g. look at this example module: https://github.com/PerkLab/PerkLabBootcamp/blob/master/Examples/CampTutorial2/CampTutorial2.py
This module is just an example for a tutorial, it doesn’t do exactly what you need. It updates a MRML node (sphere model) when a Markups node is modified. But it’s a good example for how to observe a Markups node.

1 Like

Hi @ungi . I considered that design choice too. However, it requires that the observation (AddObserver) is defined in the widget class. But I want my application to work without the GUI too, meaning that I must allocate the Markups node in the logic class. That class does not know about the widget class. That is why I included an intermediate step (VTK event → Qt event → perform action): this way the VTK event on the Markups node can be observed by methods (slots) of both the widget class and the logic class.

When a module class is instantiated, it is still the module discovery phase (the application scans what modules are available, builds a dependency tree, determines in which order the modules will be initialized). Slicer application or other modules are not available yet. If you want to do something at application startup then you can connect a method to the application’s startupCompleted() signal:

Create logic object on startupCompleted() signal and store it in the module class. This is only necessary if only one logic instance is allowed in the application. For example, this is the case when the logic observes the scene and reacts to changes, modifies nodes automatically

(source: PerkLab bootcamp, Slicer programming tutorial, slide 11)

See the scripted module template for an example of using this signal:

1 Like

Thank you, this was very valuable!
I read the bootcamp tutorials before, but skipped the details that I did not understand then; time to reread them with my current, deeper, understanding…
I found startupCompleted in the API docs. One question though: how could I know that slicer.app.startupCompleted() is the Python wrapper of the corresponding C++ method qSlicerApplication::startupCompleted()? I found it by trial and error. Is there a systematic way to discover it (the debugger doesn’t work for me and I don’t have autocompletion for the slicer module in my IDE)?

You can find here a set of rules how C++ classes, methods, properties are accessible in Python.

As with most APIs, probably the most effective ways of learning examples, tutorials, and trial and error. API documentation is sometimes useful, too. If none of these help then you can get all the answers from the source code or by asking here.

Multiple debuggers can be used. Feel free to create a new topic if you have trouble setting up a Python debugger.

1 Like