Skip to content

Commit 6c23846

Browse files
authored
Merge pull request #1963 from effigies/ref/mris_expand
RF: Derive MRIsExpand from FSSurfaceCommand
2 parents dc68922 + 5da86ea commit 6c23846

File tree

6 files changed

+133
-73
lines changed

6 files changed

+133
-73
lines changed

nipype/interfaces/freesurfer/base.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,18 +216,19 @@ def _normalize_filenames(self):
216216
def _associated_file(in_file, out_name):
217217
"""Based on MRIsBuildFileName in freesurfer/utils/mrisurf.c
218218
219+
If no path information is provided for out_name, use path and
220+
hemisphere (if also unspecified) from in_file to determine the path
221+
of the associated file.
219222
Use in_file prefix to indicate hemisphere for out_name, rather than
220223
inspecting the surface data structure.
221-
Also, output to in_file's directory if path information not provided
222-
for out_name.
223224
"""
224225
path, base = os.path.split(out_name)
225226
if path == '':
226227
path, in_file = os.path.split(in_file)
227228
hemis = ('lh.', 'rh.')
228229
if in_file[:3] in hemis and base[:3] not in hemis:
229230
base = in_file[:3] + base
230-
return os.path.abspath(os.path.join(path, base))
231+
return os.path.join(path, base)
231232

232233

233234
class FSScriptCommand(FSCommand):
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# AUTO-GENERATED by tools/checkspecs.py on 2017.04.21
2+
# Modified 2017.04.21 by Chris Markiewicz
3+
from __future__ import unicode_literals
4+
import pytest
5+
6+
from ..base import FSSurfaceCommand
7+
from ... import freesurfer as fs
8+
from ...io import FreeSurferSource
9+
10+
11+
def test_FSSurfaceCommand_inputs():
12+
input_map = dict(args=dict(argstr='%s',
13+
),
14+
environ=dict(nohash=True,
15+
usedefault=True,
16+
),
17+
ignore_exception=dict(nohash=True,
18+
usedefault=True,
19+
),
20+
subjects_dir=dict(),
21+
terminal_output=dict(nohash=True,
22+
),
23+
)
24+
inputs = FSSurfaceCommand.input_spec()
25+
26+
for key, metadata in list(input_map.items()):
27+
for metakey, value in list(metadata.items()):
28+
assert getattr(inputs.traits()[key], metakey) == value
29+
30+
31+
@pytest.mark.skipif(fs.no_freesurfer(), reason="freesurfer is not installed")
32+
def test_associated_file():
33+
fssrc = FreeSurferSource(subjects_dir=fs.Info.subjectsdir(),
34+
subject_id='fsaverage', hemi='lh')
35+
36+
fsavginfo = fssrc.run().outputs.get()
37+
38+
# Pairs of white/pial files in the same directories
39+
for white, pial in [('lh.white', 'lh.pial'),
40+
('./lh.white', './lh.pial'),
41+
(fsavginfo['white'], fsavginfo['pial'])]:
42+
43+
# Unspecified paths, possibly with missing hemisphere information,
44+
# are equivalent to using the same directory and hemisphere
45+
for name in ('pial', 'lh.pial', pial):
46+
assert FSSurfaceCommand._associated_file(white, name) == pial
47+
48+
# With path information, no changes are made
49+
for name in ('./pial', './lh.pial', fsavginfo['pial']):
50+
assert FSSurfaceCommand._associated_file(white, name) == name

nipype/interfaces/freesurfer/tests/test_auto_FSSurfaceCommand.py

Lines changed: 0 additions & 24 deletions
This file was deleted.

nipype/interfaces/freesurfer/tests/test_auto_MRIsExpand.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ def test_MRIsExpand_inputs():
2323
mandatory=True,
2424
position=-3,
2525
),
26-
navgs=dict(argstr='-navgs %d %d',
27-
),
2826
nsurfaces=dict(argstr='-N %d',
2927
),
3028
out_name=dict(argstr='%s',

nipype/interfaces/freesurfer/tests/test_utils.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from nipype.testing.fixtures import (create_files_in_directory_plus_dummy_file,
99
create_surf_file_in_directory)
1010

11+
from nipype.pipeline import engine as pe
12+
from nipype.interfaces import freesurfer as fs
1113
from nipype.interfaces.base import TraitError
12-
import nipype.interfaces.freesurfer as fs
14+
from nipype.interfaces.io import FreeSurferSource
1315

1416

1517
@pytest.mark.skipif(fs.no_freesurfer(), reason="freesurfer is not installed")
@@ -159,3 +161,52 @@ def test_surfshots(create_files_in_directory_plus_dummy_file):
159161
os.environ["DISPLAY"] = hold_display
160162
except KeyError:
161163
pass
164+
165+
166+
@pytest.mark.skipif(fs.no_freesurfer(), reason="freesurfer is not installed")
167+
def test_mrisexpand(tmpdir):
168+
fssrc = FreeSurferSource(subjects_dir=fs.Info.subjectsdir(),
169+
subject_id='fsaverage', hemi='lh')
170+
171+
fsavginfo = fssrc.run().outputs.get()
172+
173+
# dt=60 to ensure very short runtime
174+
expand_if = fs.MRIsExpand(in_file=fsavginfo['smoothwm'],
175+
out_name='expandtmp',
176+
distance=1,
177+
dt=60)
178+
179+
expand_nd = pe.Node(
180+
fs.MRIsExpand(in_file=fsavginfo['smoothwm'],
181+
out_name='expandtmp',
182+
distance=1,
183+
dt=60),
184+
name='expand_node')
185+
186+
# Interfaces should have same command line at instantiation
187+
orig_cmdline = 'mris_expand -T 60 {} 1 expandtmp'.format(fsavginfo['smoothwm'])
188+
assert expand_if.cmdline == orig_cmdline
189+
assert expand_nd.interface.cmdline == orig_cmdline
190+
191+
# Run both interfaces
192+
if_res = expand_if.run()
193+
nd_res = expand_nd.run()
194+
195+
# Commandlines differ
196+
node_cmdline = 'mris_expand -T 60 -pial {cwd}/lh.pial {cwd}/lh.smoothwm ' \
197+
'1 expandtmp'.format(cwd=nd_res.runtime.cwd)
198+
assert if_res.runtime.cmdline == orig_cmdline
199+
assert nd_res.runtime.cmdline == node_cmdline
200+
201+
# Check output
202+
if_out_file = if_res.outputs.get()['out_file']
203+
nd_out_file = nd_res.outputs.get()['out_file']
204+
# Same filename
205+
assert op.basename(if_out_file) == op.basename(nd_out_file)
206+
# Interface places output in source directory
207+
assert op.dirname(if_out_file) == op.dirname(fsavginfo['smoothwm'])
208+
# Node places output in working directory
209+
assert op.dirname(nd_out_file) == nd_res.runtime.cwd
210+
211+
# Remove test surface
212+
os.unlink(if_out_file)

nipype/interfaces/freesurfer/utils.py

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -976,16 +976,18 @@ class MRIsCombineOutputSpec(TraitedSpec):
976976

977977
class MRIsCombine(FSSurfaceCommand):
978978
"""
979-
Uses Freesurfer's mris_convert to combine two surface files into one.
979+
Uses Freesurfer's ``mris_convert`` to combine two surface files into one.
980980
981981
For complete details, see the `mris_convert Documentation.
982982
<https://surfer.nmr.mgh.harvard.edu/fswiki/mris_convert>`_
983983
984-
If given an out_file that does not begin with 'lh.' or 'rh.',
985-
mris_convert will prepend 'lh.' to the file name.
986-
To avoid this behavior, consider setting out_file = './<filename>', or
984+
If given an ``out_file`` that does not begin with ``'lh.'`` or ``'rh.'``,
985+
``mris_convert`` will prepend ``'lh.'`` to the file name.
986+
To avoid this behavior, consider setting ``out_file = './<filename>'``, or
987987
leaving out_file blank.
988988
989+
In a Node/Workflow, ``out_file`` is interpreted literally.
990+
989991
Example
990992
-------
991993
@@ -1003,24 +1005,21 @@ class MRIsCombine(FSSurfaceCommand):
10031005

10041006
def _list_outputs(self):
10051007
outputs = self._outputs().get()
1006-
outputs['out_file'] = self._associated_file(self.inputs.in_files[0],
1007-
self.inputs.out_file)
1008-
return outputs
10091008

1010-
@staticmethod
1011-
def _associated_file(in_file, out_name):
1012-
"""Unlike the standard _associated_file, which uses the prefix from
1013-
in_file, in MRIsCombine, it uses 'lh.' as the prefix for the output
1014-
file no matter what the inputs are.
1015-
"""
1016-
path, base = os.path.split(out_name)
1017-
if path == '':
1018-
hemis = ('lh.', 'rh.')
1019-
if base[:3] not in hemis:
1020-
base = 'lh.' + base
1021-
return os.path.abspath(os.path.join(path, base))
1009+
# mris_convert --combinesurfs uses lh. as the default prefix
1010+
# regardless of input file names, except when path info is
1011+
# specified
1012+
path, base = os.path.split(self.inputs.out_file)
1013+
if path == '' and base[:3] not in ('lh.', 'rh.'):
1014+
base = 'lh.' + base
1015+
outputs['out_file'] = os.path.abspath(os.path.join(path, base))
1016+
1017+
return outputs
10221018

10231019
def _normalize_filenames(self):
1020+
""" In a Node context, interpret out_file as a literal path to
1021+
reduce surprise.
1022+
"""
10241023
if isdefined(self.inputs.out_file):
10251024
self.inputs.out_file = os.path.abspath(self.inputs.out_file)
10261025

@@ -3001,11 +3000,6 @@ class MRIsExpandInputSpec(FSTraitedSpec):
30013000
desc=('Name of thickness file (implicit: "thickness")\n'
30023001
'If no path, uses directory of `in_file`\n'
30033002
'If no path AND missing "lh." or "rh.", derive from `in_file`'))
3004-
navgs = traits.Tuple(
3005-
traits.Int, traits.Int,
3006-
argstr='-navgs %d %d',
3007-
desc=('Tuple of (n_averages, min_averages) parameters '
3008-
'(implicit: (16, 0))'))
30093003
pial = traits.Str(
30103004
argstr='-pial %s', copyfile=False,
30113005
desc=('Name of pial file (implicit: "pial")\n'
@@ -3027,6 +3021,11 @@ class MRIsExpandInputSpec(FSTraitedSpec):
30273021
desc='Number of surfacces to write during expansion')
30283022
# # Requires dev version - Re-add when min_ver/max_ver support this
30293023
# # https://github.com/freesurfer/freesurfer/blob/9730cb9/mris_expand/mris_expand.c
3024+
# navgs = traits.Tuple(
3025+
# traits.Int, traits.Int,
3026+
# argstr='-navgs %d %d',
3027+
# desc=('Tuple of (n_averages, min_averages) parameters '
3028+
# '(implicit: (16, 0))'))
30303029
# target_intensity = traits.Tuple(
30313030
# traits.Float, traits.File(exists=True),
30323031
# argstr='-intensity %g %s',
@@ -3037,7 +3036,7 @@ class MRIsExpandOutputSpec(TraitedSpec):
30373036
out_file = File(desc='Output surface file')
30383037

30393038

3040-
class MRIsExpand(FSCommand):
3039+
class MRIsExpand(FSSurfaceCommand):
30413040
"""
30423041
Expands a surface (typically ?h.white) outwards while maintaining
30433042
smoothness and self-intersection constraints.
@@ -3063,7 +3062,9 @@ def _list_outputs(self):
30633062
self.inputs.out_name)
30643063
return outputs
30653064

3066-
def _get_filecopy_info(self):
3065+
def _normalize_filenames(self):
3066+
""" Find full paths for pial, thickness and sphere files for copying
3067+
"""
30673068
in_file = self.inputs.in_file
30683069

30693070
pial = self.inputs.pial
@@ -3079,20 +3080,3 @@ def _get_filecopy_info(self):
30793080
thickness_name)
30803081

30813082
self.inputs.sphere = self._associated_file(in_file, self.inputs.sphere)
3082-
3083-
return super(MRIsExpand, self)._get_filecopy_info()
3084-
3085-
@staticmethod
3086-
def _associated_file(in_file, out_name):
3087-
"""Based on MRIsBuildFileName in freesurfer/utils/mrisurf.c
3088-
3089-
Use file prefix to indicate hemisphere, rather than inspecting the
3090-
surface data structure
3091-
"""
3092-
path, base = os.path.split(out_name)
3093-
if path == '':
3094-
path, in_file = os.path.split(in_file)
3095-
hemis = ('lh.', 'rh.')
3096-
if in_file[:3] in hemis and base[:3] not in hemis:
3097-
base = in_file[:3] + base
3098-
return os.path.join(path, base)

0 commit comments

Comments
 (0)