How to save slicer scene with both slicer data and with "self.variables" within custom widget

I am using Slicer to create a widget for an application in which I would like to save my work. I see in slicer there is saveScene() in which I can do an mrml or a mrb file - which I can get to work. However, the widget also includes variables and structures as such as “self.variable1, self.variable2.subvariable2, etc.” These “self.variables” are important to my GUI’s widget and logic classes. The issue is when I save the scene I am successful in saving the fiducial nodes and image that was brought into slicer because they are slicer nodes (I believe) but the :self.variables" associated with the widget do not save. In other words, when I try to re-open my saved scene the program fails because the self.A and other variables are not saved within the scene.

Is there an easy way to save these “self.variables” so I can reopen slicer after closing it to where I was last with the values back in my variables? Is there a suggested solution to reopening slicer with reimporting these self.variables back into the program quickly if slicer can not save the variables?

You raise a very important point. It is easy to write a scripted module that “works” and it is very tempting to stop there - and not spend time with implementing automatic tests, saving of module state to the scene, etc.

Developers are supposed to save all information that is necessary to recreate the current state of the module in nodes in the MRML scene. You can find some useful general information about this in the first part of PerkLab Scripted Module Development tutorial. This practice is strictly followed in all Slicer core modules, but since they are mostly implemented in C++ they are not useful examples for scripted module developers.

For scripted modules, we added vtkMRMLScriptedModuleNode, a generic MRML node to store data persistently in the scene. There is a getParameterNode helper function in scripted module logic class to retrieve a parameter node. The parameter node can be chosen to be a singleton (one instance per scene) or a non-singleton node (in that case a isSingletonParameterNode member has to be set to False in module logic and parameter node selector has to be added to the module widget). See VectorToScalarVolume module as an example of a scripted module with non-singleton parameter node.

3 Likes

This point makes a lot of sense now and I did start to use the “vtkMRMLScriptedModuleNode”. However, it appears that you can only pass in a string value when using GetParameter()? In looking at the documentation it seems like you can only pass in strings with this method. Is there a way to pass in a structure array such as “A” with variables like A.x1 = some number, A.x2 = some string, A.x3 = some node, and variables containing sub variables like A.y.z1 = some number, A.y.z2 = some string, A.y.z3 = some node? I would like to pass “A” into a node like “vtkMRMLScriptedModuleNode” so the slicer scene can re-import. Or do I need to break this structure array up and put the parameters (strings, numbers, nodes, etc.) into specific slicer nodes?

In Python, you can very easily serialize any values to/from string. You can even just dump everything in a json string. However, it is a bit nicer and safer to break up the information to smaller, individually named chunks.

1 Like

I actually prefer the json approach as used here since it lets you create a very natural mapping between your mrml node and a data structure of arbitrary complexity. Whatever approach you use, you’ll need to take care about the performance if you have lots and lots of state data.

3 Likes

@lassoan and @pieper, thank you for your inputs. This has helped me greatly on both understanding slicer/slicer documentation. I did run into an issue with the serializing recommendations because my structure does contain items that are of the “qt.QStandardItemModel()” structure. I plan on modifying my code to read in the strings from json and then placing them into the variable that is of the qt.QStandardItemModel() structure at widget initiation, e.g., will have my program see if the json file exist first before defaulting to blank. I assume there are no good ways to serialize qt items like qt.QStandardItemModel() or is there?

QStandardItemModel is a GUI class, so you cannot rely on it for implementing essential features, such as data storage. You have to make your module fully functional without a GUI just using logic and MRML classes (in case if another module or a batch processing script wants to use its features).

2 Likes

Thank you for that note - and the earlier comments - as it explains a lot of what I was seeing in the documentation but wasn’t fully understanding as I was learning to work with slicer scripting. I saw GUI/widget needed to be sperate from the logic class and MRML data in the literature. However, I didn’t put those facts together with Qt being for GUI and shouldn’t be used for storage. Thank you.

1 Like

Hi @lassoan

I am trying to get my head around this ScriptedModuleNode thing. In the Perklab slides (on slide 10) it says ‘include a parameter node selector at the top (or use a singleton paratmeter node)’

I see that Vector to Scalar Volume and SegmentStatistics seem to have parameter node selectors at the top of the module GUI. What is the advantage of using a singleton vs non singleton scripted module node? What would be more appropriate for use in the module I have been working on (FiducialToModelDistance)

Are there scripted modules you can think of which use a singleton parameter node which I can look through? I tried looking through the other scripted modules in the slicer core code but cant seem to find anything.

Singleton parameter node:

  • Pro: no need to complicate the GUI with a parameter node selector
  • Con: you can only store one set of parameters in the scene (if you load a scene that had a parameter set node in it, it overwrites the parameter node in the current scene)

There are a couple of input parameters, so I think using a non-singleton parameter node would be more appropriate.

Thanks for the reply. So will the parameter node save the inputs which were in the input boxes when the apply button is pressed?

Then when the scene is loaded again it repopulates the input boxes with the same nodes somehow?

I will read through the code in segment statstics module etc and try and figure out how to store the data and retreive it again. It is a bit difficult to understand with my non programming brain :slight_smile:

No, of course nothing is saved or repopulated automatically. You implement these in updateGUIFromParameterNode and updateParameterNodeFromGUI methods. This module should be a relatively straightforward example that you can follow: https://github.com/PerkLab/SlicerVolumeClip/blob/master/VolumeClipWithModel/VolumeClipWithModel.py#L252-L288