Make it easier to select input nodes for Python scripts

While discussing a problem due to selecting the wrong input node in a Python script, we started to brainstorm about how to make it easier to select input nodes for Python scripts.

The first idea was to change vtkMRMLScene::GetFirstNodeByName to return non-hidden node by default.

Then @pieper raised the idea of adding a slicer.util.pickNode method to show a popup where the user can select a node.

I’ve created this topic to make this discussion more visible and get some more ideas and feedback from more developers.

Adding a pickNode function could be useful, especially if it supported multiple nodes, each with a label and a default selection.

We could also consider @pieper’s idea that came up some time ago: expose nodes as attribute of a Python object so that auto-complete could be used to look up nodes. It is nice that it also works in Jupyter notebooks.

I’ve implemented a very simple prototype - see a short demo:

Implementation:

To try it, copy-paste all the highlighted lines into the Python console and then type this to make the nodes object conveniently available there as n:

slicer.util.n = Nodes()
1 Like

Oh, I like that very much!

I like it a lot also. As your prototype acknowledges for spaces, might there be possible problems due to other non-letter characters? Names of nodes can pretty much be arbitrary strings, right? Leading numbers could be an issue (as is typical of DICOM imports which start with series number). Parentheses and brackets also aren’t allowed as part of attribute names.

Even with a simple and lossy solution like “replace any non-permitted character with an underscore”, I think this would be very helpful and I would use this all the time in the python interactor.

I am not so familiar with working directly in the interactor - would probably vote to have a picknode function or to have that auto-complete (which is cool) be transferred into notepad++ or VS .
Could we not just make a better error message if someone calls on a hidden input node?

Just having a filter search tool for the Data module’s list of nodes would be helpful for me. That would allow easier finding the correct node name or node ID and then using Python to get that node reference. Maybe not the exact type of issue trying to be solved here, but in general it would allow for finding the node that I’m interested in getting a reference to for Python scripts.

I just discovered that this exists… Having the filter be above the table would be better as that is where I expected it to be. A filter above the table exists for the Segment table. Maybe a filter can be added for all Node tables.

image

image

1 Like

Yes, Python variable names are restricted to a-z, A-Z, numbers, and underscore, starting with non-number. I’ve added a regexp to replace invalid characters by _ and prepend an n if the name starts with number (starting with underscore would mean that it is a private attribute and it would be placed to the end of the auto-complete list).

As an experiment, I’ve added this feature to the DebuggingTools extension:

The NodeInfo module adds a nodes variable in the global slicer namespace, which is available from everywhere. For user convenience, it also imports this variable as n in the Python console’s namespace.

In tomorrow’s Slicer Preview Release, if DebuggingTools extension is installed then typing n. in the Python console and hitting Tab key will allow selecting MRML nodes using auto-complete.

Let’s see if it proves to be useful. If it does and it matures a bit then we may consider adding it to the Slicer core (maybe enabled just in developer mode).

The nice thing in this auto-complete method that it works everywhere where you already have auto-complete - in Jupyter notebooks or any Python debugger (PyCharm, VS Code, etc.).

Hidden nodes simply don’t show up, so I don’t think a special error message would be needed.
Note that you can run n.ignoreHiddenNodes=False to show hidden nodes, too. But it mostly just makes color nodes show up in the list, making it harder to find what you need. Maybe we could display hidden nodes with a _ prefix (it means private variable) if it turns out that access of hidden variables is useful.

Invalid characters is easy to handle (see above). There are still complications, for example multiple nodes with the same name. However, accessing nodes as attributes is just a convenience function for interactive debugging, so it does not have to handle edge cases robustly (we have plenty of other ways to get nodes in a robust way).

I agree that a right-click menu for creating Python variable (initialized with the simplified node name, and potentially allowing the user to change it) could be useful. It would be also easy to implement because it could be added via a scripted subject hierarchy plugin.

The right-click menu in the subject hierarchy tree is probably more convenient when you have dozens or more nodes in the scene (or you have already found your node of interest in the tree), while n.NodeName is more convenient if you work in the console and you already have an idea of the node name or you don’t have too many nodes.

1 Like

I think this would be good and fairly safe compared to trying to add attributes of a class which could open a bunch of edge cases based on node name as indicated by @mikebind. The right click would just create a reference using a simple variable name like volume_node or something or let the user define the variable themselves.

I agree node names are very free-form text, but I think it would be really useful to be able to tab complete them in the python console and it’s okay to map non-identifier-friendly characters to _.

It could also be cool to add a ways of getting python access to data based on the state of the gui. For example slicer.gui.views.red.background could give you the node currently displayed in the background of the red viewer. Tab completing slicer.gui.views which give you the list of view ids that you could quickly pick. Something like slicer.gui.data.selection could give you the currently selected node from the data module. This kind of access could be pretty easy to add with custom __getattr__ code.

1 Like

This is a very interesting idea, it would result in kind of a “macro language” for Slicer programming. Do you envision this could be used in module development or just for automation scripts?

Macro scripts could be generated by MRML-based event recording or QtTesting-based recording.