Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use matlab_cmd provided as input closes #2442 #2452

Merged
merged 8 commits into from
Feb 23, 2018
42 changes: 30 additions & 12 deletions nipype/interfaces/spm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,28 +126,30 @@ def scans_for_fnames(fnames, keep4d=False, separate_sessions=False):

class Info(PackageInfo):
"""Handles SPM version information

If you use `SPMCommand.set_mlab_paths` to set alternate entries for
matlab_cmd, paths, and use_mcr, then you will need to use the same entries
to any call in the Info class to maintain memoization. Otherwise, it will
default to the parameters in the `getinfo` function below.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm conceptually a little displeased with this memoization principle, but in practice I imagine nobody's using multiple MATLABs and SPMs within a single nipype script, so it's probably fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually i think this exactly handles the multiple matlab/spm scenario. let's say i have two different versions of spm. i can explicitly use Info to retrieve them (yes, it will loose the first memory on collecting the second one).

the situation, i think, boils down to this.

SPMCommand.set_mlab_paths is a class method, so every instance derived will respect it. and i can adjust any given instance to change its command/path.

Info only has class methods and we would somehow need to detect a change. this method decouples the change detection saying the only memory it has is when you pass the same parameters.

an alternative was the previous augmented version to set the Info attributes everytime set_mlab_paths is called, retrieve that information and store it and always return that unless its changed again with get_info call.

i don't feel strongly about either. just think that the latter seems more magical.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, in the sense that this will be correct. The issue I was seeing with multiple versions I have is that you're running a MATLAB instance every time the cache is invalidated to get the information.

But it really doesn't matter. If people start complaining about slowdowns, we can consider keeping a memoizing dict keyed on a 3-tuple. This is probably fine, though.

"""
_path = None
_name = None
_command = None
_paths = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't you also add _version = None

_version = None

@classmethod
def path(klass, matlab_cmd=None, paths=None, use_mcr=None):
if klass._path:
return klass._path
klass.getinfo(matlab_cmd, paths, use_mcr)
return klass._path

@classmethod
def version(klass, matlab_cmd=None, paths=None, use_mcr=None):
if klass._version:
return klass._version
klass.getinfo(matlab_cmd, paths, use_mcr)
return klass._version

@classmethod
def name(klass, matlab_cmd=None, paths=None, use_mcr=None):
if klass._name:
return klass._name
klass.getinfo(matlab_cmd, paths, use_mcr)
return klass._name

Expand All @@ -169,7 +171,10 @@ def getinfo(klass, matlab_cmd=None, paths=None, use_mcr=None):
If none of the above was successful, the fallback value of
'matlab -nodesktop -nosplash' will be used.
paths : str
Add paths to matlab session
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we suggest that here should be also path to spm (that can be completely different than matlab) if we want to run spm interfaces ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, that's clear from the function description: "Returns the path to the SPM directory in the Matlab path".

use_mcr : bool
Whether to use the MATLAB Common Runtime. In this case, the
matlab_cmd is expected to be a valid MCR call.

Returns
-------
Expand All @@ -178,17 +183,19 @@ def getinfo(klass, matlab_cmd=None, paths=None, use_mcr=None):
returns None of path not found
"""

if klass._name and klass._path and klass._version:
use_mcr = use_mcr or 'FORCE_SPMMCR' in os.environ
matlab_cmd = matlab_cmd or ((use_mcr and os.getenv('SPMMCRCMD'))
or os.getenv('MATLABCMD', 'matlab -nodesktop -nosplash'))

if klass._name and klass._path and klass._version and \
klass._command == matlab_cmd and klass._paths == paths:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to your discussion with @effigies. Shouldn't we add some warnings if klass._command != matlab_cmd or klass._paths != paths. Not sure how likely this might eb due to mistake.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not a big fan of warnings. almost no one reads it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, you're probably right, but personally I sometimes read them if I feel hopeless...


return {
'name': klass._name,
'path': klass._path,
'release': klass._version
}

use_mcr = use_mcr or 'FORCE_SPMMCR' in os.environ
matlab_cmd = ((use_mcr and os.getenv('SPMMCRCMD'))
or os.getenv('MATLABCMD', 'matlab -nodesktop -nosplash'))

logger.debug('matlab command or path has changed. recomputing version.')
mlab = MatlabCommand(matlab_cmd=matlab_cmd, resource_monitor=False)
mlab.inputs.mfile = False
if paths:
Expand All @@ -214,6 +221,11 @@ def getinfo(klass, matlab_cmd=None, paths=None, use_mcr=None):
# if no Matlab at all -- exception could be raised
# No Matlab -- no spm
logger.debug('%s', e)
klass._version = None
klass._path = None
klass._name = None
klass._command = matlab_cmd
klass._paths = paths
return None

out = sd._strip_header(out.runtime.stdout)
Expand All @@ -225,6 +237,8 @@ def getinfo(klass, matlab_cmd=None, paths=None, use_mcr=None):
klass._version = out_dict['release']
klass._path = out_dict['path']
klass._name = out_dict['name']
klass._command = matlab_cmd
klass._paths = paths
return out_dict


Expand Down Expand Up @@ -296,6 +310,10 @@ def set_mlab_paths(cls, matlab_cmd=None, paths=None, use_mcr=None):
cls._matlab_cmd = matlab_cmd
cls._paths = paths
cls._use_mcr = use_mcr
info_dict = Info.getinfo(
matlab_cmd=matlab_cmd,
paths=paths,
use_mcr=use_mcr)

def _find_mlab_cmd_defaults(self):
# check if the user has set environment variables to enforce
Expand Down