Setting widget variable from --python-code on Slicer.exe start

Hey all,
I am launching 3d slicer via python script using subprocess as such

Slicer.exe --python-code slicer.util.getModule(‘BlenderMonitor’).widgetRepresentation().self().test = ‘TESTING’

in the python widget I have

class BlenderMonitorWidget:
    def __init__(self, parent = None):
        if not parent:
            self.parent = slicer.qMRMLWidget()
            self.parent.setLayout(qt.QVBoxLayout())
            self.parent.setMRMLScene(slicer.mrmlScene)
        else:
            self.parent = parent
        self.layout = self.parent.layout()
        if not parent:
            self.setup()
            self.parent.show()
        self.watching = True #False before but now we are automating things
        self.sock = None
        self.sliceSock = None
        self.host_address = asyncsock.address[0]
        self.host_port = asyncsock.address[1]
        self.SlicerSelectedModelsList = []
        #self.toSync = []
        #slice list
        self.sliceViewCache = {}
        #self.workingVolume = None
        self.test = None

for some reason when I try to set self.test on launch, once 3d slicer starts I check the variable and it is still None.
how can i set self.test automatically as 3D slicer is loading? I’ve tried it with other variables and it doesn’t seem to work. Thoughts?

Best,
Georgi

Hi Georgi

Did you add your module to the additional-paths ?

here?
I added it before, module loads.
If i do

slicer.util.getModule(‘BlenderMonitor’).widgetRepresentation().self().test = ‘TESTING’

in the python CLI console once 3d slicer is launched it works. its only on startup via --python-code

Try adding it in the same line of code

When you are opening the app I mean

hey,
apologies but i am not following.
Do you mean adding the module path via code?
I found this but the links are broken: Can I add an "additional module path" with python code

~Georgi

If a command-line argument contains spaces then you must put the argument between quotes ("). If this is cumbersome then you can write your custom startup code into a .py file and run it by using the --python-script argument.

Widget representation of a module may not be created or fully initialized until you actually open the module, so you may want to first call slicer.util.selectedModule('BlenderMonitor') and then get the module widget.

1 Like

Hey,
Thank you for the suggestion. Let me provide some more code context.
Below is my test script I launch from Python IDLE

import subprocess

slicer_startup_parameters = ''.join((
  "slicer.util.selectModule('BlenderMonitor')\n",
  "slicer.util.getModule('BlenderMonitor').widgetRepresentation().self().connect_to_blender(55)"))
subprocess.Popen(["C:\\ProgramData\\NA-MIC\\Slicer 4.11.20210226\\Slicer.exe", "--python-code", slicer_startup_parameters])

below is part of the code from my 3d slicer widget i am trying to work with.

class BlenderMonitorWidget:
    def __init__(self, parent = None):
        if not parent:
            self.parent = slicer.qMRMLWidget()
            self.parent.setLayout(qt.QVBoxLayout())
            self.parent.setMRMLScene(slicer.mrmlScene)
        else:
            self.parent = parent
        self.layout = self.parent.layout()
        if not parent:
            self.setup()
            self.parent.show()
        self.host_address = asyncsock.address[0]
        self.host_port = 5959

    def setup(self):
        # Instantiate and connect widgets ...
        
    def connect_to_blender(self, port):
        self.host_port = port
        print(self.host_port)

I created a separate function connect_to_blender(self,port) that takes int port value when 3D slicer is launched with --python-code

the first paramater, selectModule, works well and slicer switches to the widget on startup. when connect_to_blender is called after, i do not see the print statement in the console when slicer is finished startup and --python-code has been fully executed
image

and when I try to access host_port via the console, the value of the variable has not changed. in --python-code i pass int 55, the hard-coded value is 5959
image

I’m not sure if newline character is acceptable in command-line arguments. If you need to run multiple commands, pass multiple arguments, etc. then it is safer to use --python-script argument, see a complete example here: SlicerRT/BatchProcessing at master · SlicerRt/SlicerRT · GitHub

hey,
multiline arguments work with --python-code
The code below, for example, works just fine for me using \t and \n

            slicer_startup_parameters = ''.join((
                "dicomDataDir = '%s'\n"%os.path.dirname(context.scene.DICOM_dir).replace(os.sep, '/'),
                "loadedNodeIDs = []\n",
                "from DICOMLib import DICOMUtils\n",
                "with DICOMUtils.TemporaryDICOMDatabase() as db:\n",
                "\tDICOMUtils.importDicom(dicomDataDir, db)\n",
                "\tpatientUIDs = db.patients()\n",
                "\tfor patientUID in patientUIDs:\n",
                "\t\tloadedNodeIDs.extend(DICOMUtils.loadPatientByUID(patientUID))\n",
                "slicer.util.selectModule('BlenderMonitor')\n",
                "slicer.util.setSliceViewerLayers(background=slicer.util.getNode(loadedNodeIDs[0]))\n",
                "slicer.util.getModule('BlenderMonitor').widgetRepresentation().self().workingVolume = slicer.util.getNode(loadedNodeIDs[0])"
            ))

its just that you cannot set variables on startup. I have some ideas how to work around that via configuration file. I just wanted to check if there was a solution to this as it is easier to pass data directly

best,
Georgi

Could you provide a complete, minimal example that reproduces the problem? (your BlenderMonitorWidget example was not a complete script file that I could use for testing)

Passing of parameters via --python-code works welll for example in SlicerJupyter:

Hey,
I have provided working examples that you should be able to use to reproduce the problem.
I am just trying to set the variable self.host_port on start-up via —python-code

BlenderTest.py is the add-on module, please add via additional modules path

start_slicer.py is the startup code. Please set your 3D slicer install directory.

Keep me posted.

Best,
Georgi

2 Likes

Maybe newline character can be passed through inside command-line arguments in certain situations, but I would not even attempt that. Especially because it is not necessary. You can separate commands by semicolon instead. For example, this works well for me:

import subprocess

slicer_startup_parameters = """print('Connecting to Blender...')
slicer.util.selectModule('BlenderTest')
slicer.util.getModuleWidget('BlenderTest').connect_to_blender(55)
""".replace('\n', ';')

subprocess.Popen([r"c:\Users\andra\AppData\Local\NA-MIC\Slicer 5.0.2\Slicer.exe", "--python-code", slicer_startup_parameters])

Note that on Windows, the Slicer installer registers Slicer.exe with the last installed instance, so you can start the application without knowing the full path, like this:

subprocess.Popen(["Slicer.exe", "--python-code", slicer_startup_parameters])
1 Like

Hey,
Apologies it took me a while to respond, thank you so much for your help.
For future reference to those who stumble on this post I would like to elaborate on the solution.
@lassoan thank you for the suggestion of improving on the string formatting. Use of

“\n” and “\t”

within the string was not a problem. Although for future reference it is good to know that I can separate commands inline with semicolon “;”
The real problem was that I was trying to set

self.host_port

by accessing

slicer.util.getModule(‘BlenderMonitor’).widgetRepresentation().self().[widget init var]

Reviewing @lassoan suggestion, widget class functions are more appropriately accessed by the

slicer.util.getModuleWidget().[widget class method] ()

method. When I switched to getModuleWidget() and wrapped self.host_port variable in the self.connect_to_blender() method I was able to pass and set self.host_port on widget startup.

TL;DR @lassoan code suggestions worked well, use of semicolon was not necessary and use of \n and \t in string formatting is acceptable when --python-code

1 Like