Run Freesurfer command from Slicer extension

Hi, I’m developing an Extension in python to anonymize MRIs and I need to use some Freesurfer commands (mri_info, mris_convert and mri_watershed).

Is there a way to run Freesurfer commands from inside a Slicer Extension?

I’m using subprocess to perform the function calls but I get an error “non-zero exit status -6”. I tried the solution showed in Subprocess call in Python interpreter results in memory corruption, adding the binaries directory to the path as done in Elastix, and also using slicer.utils.startupEnvironment() but I can’t make it work.

Here is an example code:

...
def getFsEnv(self):
    fsBinDir = '/path/to/binaries'
    fsEnv = os.environ.copy()
    fsEnv["PATH"] = fsBinDir + os.pathsep + fsEnv["PATH"] if fsEnv.get("PATH") else fsBinDir

...
fsEnv=self.getFsEnv()
subprocess.check_output(shlex.split('mri_info /path/to/file.mgz'), env=fsEnv)

I’ve also tried with subprocess.Popen(). In this case I think it starts and then stops:

logfile=open("filename", "w")
subprocess.Popen(shlex.split('mri_watershed -useSRAS -surf /path/ /file.mgz /path'), env=fsEnv, stdout=logfile)
logfile.close()

If i do like that, I see the beginning of the output on the logfile (only 2 lines) but the process doesn’t continue (i.e. the surfaces are not created and htop shows no cpu usage). If I use subprocess.check_call() I also get the -6 error.

Any help would be greatly appreaciated, thanks.

Operating system: OSX 10.15.6
Slicer version: 4.10.2
Expected behavior: Using subprocess to run Freesurfer commands
Actual behavior: “non-zero exit status -6”

Probably the freesurfer commands use some of the same dynamic libraries as Slicer and this causes a conflict. To resolve this, run the external process in the startup environment (instead of Slicer’s environment), as shown here: https://www.slicer.org/wiki/Documentation/Nightly/ScriptRepository#Run_process_in_default_environment

Thanks for your reply. I had tried using slicer.util.startupEnvironment() but it produces the same error.

For example:

subprocess.check_output(shlex.split('/Applications/freesurfer/bin/mri_info /filename.mgz'),
                        env=slicer.util.startupEnvironment())

I also tried adding the whole folder with binaries to the startupEnvironment:

slEnv=slicer.util.startupEnvironment()
slEnv['PATH']='/Applications/freesurfer/bin:' + slEnv['PATH']

subprocess.check_output(shlex.split('mri_info /filename.mgz'),
                        env=slEnv)

But I still get the same error. (The example code in the link you provided works without problems)

Thanks again,

Please try with latest Slicer Preview Release.
Also, try the same command from a regular Python3 interpreter to see if the syntax of the call is correct.

You may also use these convenience functions:

proc = slicer.util.launchConsoleProcess(commandLine, useStartupEnvironment=True)
slicer.util.logProcessOutput(proc)

I think I found the key to the problem. Your idea about dynamic libraries was correct.

If I add:

fsEnv['DYLD_LIBRARY_PATH']='/Applications/freesurfer/lib/gcc/lib'

I’m able to run freesurfer commands without problems. I’ll keep studying the problem and add any new insights and a more complete answer here for future reference. Thanks for all your help!

1 Like

I was able to make it work on both Mac and Linux, here’s what I did:

First I define a function to set up the environment:

def getFsEnv(self):
    """Create an environment where executables are added to the path"""
    base_dir = os.path.split(slicer.modules.modulename.path)[0]
    fsBinDir = '%s/Resources/bin:/usr/lib:' % base_dir
    fsEnv = os.environ.copy()
    fsEnv["PATH"] = fsBinDir + os.pathsep + fsEnv["PATH"] if fsEnv.get("PATH") else fsBinDir
    fsEnv['DYLD_LIBRARY_PATH'] = '%s/Resources/lib/gcc/lib' % base_dir
    fsEnv['FREESURFER_HOME'] = '%s/Resources' % base_dir
    return fsEnv

Then at the point where I need to call freesurfer’s function I get the environment and run the command using it:

import subprocess as sp
import shlex
fsEnv = self.getFsEnv()
command = 'mri_watershed -useSRAS -surf %s/%s %s %s/bems/ws' % (file_dir, base_name, f, file_dir)
p = sp.Popen(shlex.split(command), env=fsEnv, stdout=logfile, stderr=logfile)
p.wait()    

For this to work the freesurfer scripts and files required should be included in the right hierarchy or pointed to. For example on the code above I’m adding ‘%s/Resources/lib/gcc/lib’ % base_dir to ‘DYLD_LIBRARY_PATH’ and defining ‘%s/Resources’ % base_dir as ‘FREESURFER_HOME’.

The folder structure (including only the files relevant for this topic) in my case looks like this:

.
├── ModuleName.py
├── ModuleName.pyc
├── CMakeLists.txt
├── Resources
│ ├── bin
│ │ ├── mri_info
│ │ ├── mri_watershed
│ │ └── mris_convert
│ ├── lib
│ │ ├── bem
│ │ │ ├── ic0.tri
│ │ │ ├── ic1.tri
│ │ │ ├── ic2.tri
│ │ │ ├── ic3.tri
│ │ │ ├── ic4.tri
│ │ │ ├── ic5.tri
│ │ │ ├── ic6.tri
│ │ │ ├── ic7.tri
│ │ │ ├── inner_skull.dat
│ │ │ ├── outer_skin.dat
│ │ │ └── outer_skull.dat
│ │ └── gcc
│ │ └── lib
│ │ ├── libgcc_s.1.dylib
│ │ ├── libgfortran.3.dylib
│ │ ├── libgomp.1.dylib
│ │ ├── libquadmath.0.dylib
│ │ └── libstdc++.6.dylib
│ ├── license.txt

Note that you will need to add a freesurfer license.txt for the scripts to work.

It is also possible to find a permissions problem on Mac computers when trying to run freesurfer commands. If this happens, for example on my case, I had to go to the Resources → bin, or Resources → lib → gcc folder and double click on the command that I was unable to run (e.g. mri_watershed) and then going to System Preferences → Security & Privacy and allowing the execution from there (the window will show the last blocked command). If needed you will have to repeat the operation for each of the commands that are being blocked.

Hope this helps,