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

nipype.algorithms.modelgen.SpecifySPMModel can't handle relative filepaths when being inside a node #3564

Open
JohannesWiesner opened this issue Apr 14, 2023 · 4 comments

Comments

@JohannesWiesner
Copy link

Summary

This issue is potentially related to #3301. I would like to use nipype.algorithms.modelgen.SpecifySPMModel to set up my design matrix. I noticed that the class itself is able to handle relative filepaths for its argument functional_runs, but not when being inside a node. Is this an expected behavior?

How to replicate the behavior

import nipype.interfaces.matlab as nim
from nilearn.datasets import fetch_spm_auditory
from nilearn.image import concat_imgs
from nipype.algorithms import modelgen
from nipype.interfaces.base import Bunch
from nipype import Node
from nipype.algorithms.modelgen import SpecifySPMModel
import os

mlab = nim.MatlabCommand()
nim.MatlabCommand.set_default_matlab_cmd('/opt/matlab/R2022a/bin/matlab')

# get data (this will automatically create a separate data directory)
subject_data = fetch_spm_auditory(data_dir='./data')

# create .nii file and save to data dir. We pretend this would be two different runs
fmri_img = concat_imgs(subject_data.func)
fmri_img.to_filename('./data/functional2.nii')
fmri_img.to_filename('./data/functional3.nii')

# taken from docs: https://nipype.readthedocs.io/en/latest/api/generated/nipype.algorithms.modelgen.html#specifyspmmodel
s = modelgen.SpecifySPMModel()
s.inputs.input_units = 'secs'
s.inputs.output_units = 'scans'
s.inputs.high_pass_filter_cutoff = 128.
s.inputs.functional_runs = ['./data/functional2.nii', './data/functional3.nii']
s.inputs.time_repetition = 6
s.inputs.concatenate_runs = True
evs_run2 = Bunch(conditions=['cond1'], onsets=[[2, 50, 100, 180]], durations=[[1]])
evs_run3 = Bunch(conditions=['cond1'], onsets=[[30, 40, 100, 150]], durations=[[1]])
s.inputs.subject_info = [evs_run2, evs_run3]
s.run()

# but when we put the same code into a node it will fail
model_specifier = Node(SpecifySPMModel(input_units='secs',
                                       output_units='scans',
                                       high_pass_filter_cutoff=128,
                                       functional_runs = ['./data/functional2.nii', './data/functional3.nii'],
                                       time_repetition = 6,
                                       concatenate_runs = True,
                                       subject_info=[evs_run2, evs_run3]),
                       name='model_specifier')

model_specifier.run()

Actual behavior

model_specifier.run() results in TraitError: Each element of the 'functional_runs' trait of a SpecifySPMModelInputSpec instance must be a list of items which are a pathlike object or string representing an existing file or a pathlike object or string representing an existing file, but a value of '/tmp/tmprbnvs25y/model_specifier/functional2.nii' <class 'str'> was specified.

Expected behavior

As a user I would expect that model_specifier.run() should run successfully just like s.run() , because the only difference between the first and the second case is that the latter is packed inside a Node, everything else is the same. One can fix this by Node(SpecifySPMModel(functional_runs=...) with absolute paths like such:

functional_runs = [os.path.join(os.getcwd(),'./data/functional2.nii'),
                   os.path.join(os.getcwd(),'./data/functional3.nii')]

But as a user I would not expect to do this,

Script/Workflow details

Please put URL to code or code here (if not too long).

Platform details:

{'commit_hash': '%h',
 'commit_source': 'archive substitution',
 'networkx_version': '3.0',
 'nibabel_version': '5.0.1',
 'nipype_version': '1.8.5',
 'numpy_version': '1.23.5',
 'pkg_path': '/home/johannes.wiesner/.conda/envs/csp_wiesner_johannes/lib/python3.9/site-packages/nipype',
 'scipy_version': '1.10.1',
 'sys_executable': '/home/johannes.wiesner/.conda/envs/csp_wiesner_johannes/bin/python',
 'sys_platform': 'linux',
 'sys_version': '3.9.16 | packaged by conda-forge | (main, Feb  1 2023, '
                '21:39:03) \n'
                '[GCC 11.3.0]',
 'traits_version': '6.4.1'}

Execution environment

Choose one

  • My python environment outside container
@effigies
Copy link
Member

Yes. One of the functions of Nodes is isolation so that your CWD doesn't influence the behavior of tools. You will want to use absolute paths here.

@JohannesWiesner
Copy link
Author

@effigies Alright thanks! I wonder if one could adapt the Error message for this case as tmp/tmprbnvs25y/model_specifier/functional2.nii is a symbolic link pointing to an existing file so I couldn't really understanding what's going on.

@JohannesWiesner
Copy link
Author

Perhaps one could implement a method within the Node() class that checks if the provided paths are absolute or relative and throw an error for the latter case. One could use os.path.isabs() for example.

@effigies
Copy link
Member

Relative paths are frequently used for output filenames. I don't think we could make it an error without breaking that use case.

You could dig through the traceback and find where we make the inputs absolute, and put that in a try/catch block and provide a better error message than the more opaque TraitError.

I should note though that Nipype 1 is in maintenance mode and it will probably die by the time Python 3.11 hits end-of-life. In my opinion, a better use of time would be to work to improve Pydra and write Tasks (which replaces Interfaces and Nodes) to cover your use cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants