Skip to content

Commit 0ba4b24

Browse files
committed
fix: update for use in workflow and reflect new nipype conventions
1 parent 99240e0 commit 0ba4b24

File tree

2 files changed

+142
-99
lines changed

2 files changed

+142
-99
lines changed

nipype/interfaces/fsl/epi.py

Lines changed: 117 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -103,45 +103,89 @@ def _run_interface( self, runtime ):
103103
return runtime
104104

105105

106-
107-
class TOPUPInputSpec( FSLCommandInputSpec ):
108-
in_file = File( exists=True, mandatory=True, desc='name of 4D file with images', argstr='--imain=%s' )
109-
encoding_file = File( exists=True, desc='name of text file with PE directions/times', argstr='--datain=%s' )
110-
encoding_direction = traits.Enum( 'y','x','z','x-','y-','z-', desc='encoding direction for automatic generation of encoding_file' )
111-
readout_times = traits.List(traits.Float, desc='readout times (dwell times by # phase-encode steps minus 1)' )
112-
out_base = File( desc='base-name of output files (spline coefficients (Hz) and movement parameters)', argstr='--out=%s' )
113-
out_field = File( argstr='--fout=%s', desc='name of image file with field (Hz)' )
114-
out_corrected = File( argstr='--iout=%s', desc='name of 4D image file with unwarped images' )
115-
out_logfile = File( argstr='--logout=%s', desc='name of log-file' )
116-
warp_res = traits.Float( 10.0, argstr='--warpres=%f', desc='(approximate) resolution (in mm) of warp basis for the different sub-sampling levels' )
117-
subsamp = traits.Int( 1, argstr='--subsamp=%d', desc='sub-sampling scheme, default 1' )
118-
fwhm = traits.Float( 8.0, argstr='--fwhm=%f', desc='FWHM (in mm) of gaussian smoothing kernel' )
119-
config = traits.String('b02b0.cnf', desc='Name of config file specifying command line arguments', argstr='--config=%s', usedefault=True )
120-
max_iter = traits.Int( 5, argstr='--miter=%d', desc='max # of non-linear iterations')
121-
# @oesteban: I don't know how to implement these 3 parameters, AFAIK there's no documentation.
106+
class TOPUPInputSpec(FSLCommandInputSpec):
107+
in_file = File(exists=True, mandatory=True,
108+
desc='name of 4D file with images', argstr='--imain=%s')
109+
encoding_file = File(exists=True, mandatory=True,
110+
xor=['encoding_direction'],
111+
desc='name of text file with PE directions/times',
112+
argstr='--datain=%s')
113+
encoding_direction = traits.List(traits.Enum('y','x','z','x-','y-','z-'),
114+
mandatory=True, xor=['encoding_file'],
115+
requires=['readout_times'],
116+
argstr='--datain=%s',
117+
desc=('encoding direction for automatic '
118+
'generation of encoding_file'))
119+
readout_times = InputMultiPath(traits.Float,
120+
requires=['encoding_direction'],
121+
xor=['encoding_file'], mandatory=True,
122+
desc=('readout times (dwell times by # '
123+
'phase-encode steps minus 1)'))
124+
out_base = File(desc=('base-name of output files (spline '
125+
'coefficients (Hz) and movement parameters)'),
126+
name_source=['in_file'], name_template='%s_base',
127+
keep_extension=True,
128+
argstr='--out=%s', hash_files=False)
129+
out_field = File(argstr='--fout=%s', hash_files=False,
130+
name_source=['in_file'], name_template='%s_field',
131+
desc='name of image file with field (Hz)')
132+
out_corrected = File(argstr='--iout=%s', hash_files=False,
133+
name_source=['in_file'], name_template='%s_corrected',
134+
desc='name of 4D image file with unwarped images')
135+
out_logfile = File(argstr='--logout=%s', desc='name of log-file',
136+
name_source=['in_file'], name_template='%s_topup.log',
137+
keep_extension=True, hash_files=False)
138+
warp_res = traits.Float(10.0, argstr='--warpres=%f',
139+
desc=('(approximate) resolution (in mm) of warp '
140+
'basis for the different sub-sampling levels'))
141+
subsamp = traits.Int(1, argstr='--subsamp=%d',
142+
desc='sub-sampling scheme')
143+
fwhm = traits.Float(8.0, argstr='--fwhm=%f',
144+
desc='FWHM (in mm) of gaussian smoothing kernel')
145+
config = traits.String('b02b0.cnf', argstr='--config=%s', usedefault=True,
146+
desc=('Name of config file specifying command line '
147+
'arguments'))
148+
max_iter = traits.Int(5, argstr='--miter=%d',
149+
desc='max # of non-linear iterations')
150+
# @oesteban: I don't know how to implement these 3 parameters, AFAIK there's
151+
# no documentation.
122152
#lambda Weight of regularisation, default depending on --ssqlambda and --regmod switches. See user documetation.
123153
#ssqlambda If set (=1), lambda is weighted by current ssq, default 1
124154
#regmod Model for regularisation of warp-field [membrane_energy bending_energy], default bending_energy
125-
estmov = traits.Enum( 1, 0, desc='estimate movements if set', argstr='--estmov=%d' )
126-
minmet = traits.Enum( 0, 1, desc='Minimisation method 0=Levenberg-Marquardt, 1=Scaled Conjugate Gradient', argstr='--minmet=%d' )
127-
splineorder = traits.Int( 3, argstr='--splineorder=%d', desc='order of spline, 2->Qadratic spline, 3->Cubic spline' )
128-
numprec = traits.Enum( 'double', 'float', argstr='--numprec=%s', desc='Precision for representing Hessian, double or float.' )
129-
interp = traits.Enum( 'spline', 'linear' , argstr='--interp=%s', desc='Image interpolation model, linear or spline.' )
130-
scale = traits.Enum( 0, 1, argstr='--scale=%d', desc='If set (=1), the images are individually scaled to a common mean' )
131-
regrid = traits.Enum( 1, 0, argstr='--regrid=%d', desc='If set (=1), the calculations are done in a different grid' )
132-
133-
class TOPUPOutputSpec( TraitedSpec ):
134-
out_fieldcoef = File( exists=True, desc='file containing the field coefficients' )
135-
out_movpar = File( exists=True, desc='movpar.txt output file' )
136-
137-
out_enc_file= File( desc='encoding directions file output for applytopup' )
138-
out_topup = File( desc='basename for the <out_base>_fieldcoef.nii.gz and <out_base>_movpar.txt files' )
139-
out_field = File( desc='name of image file with field (Hz)' )
140-
out_corrected = File( desc='name of 4D image file with unwarped images' )
141-
out_logfile = File( desc='name of log-file' )
142-
143-
class TOPUP( FSLCommand ):
144-
""" Interface for FSL topup, a tool for estimating and correcting susceptibility induced distortions
155+
estmov = traits.Enum(1, 0,
156+
desc='estimate movements if set', argstr='--estmov=%d')
157+
minmet = traits.Enum(0, 1, argstr='--minmet=%d',
158+
desc=('Minimisation method 0=Levenberg-Marquardt, '
159+
'1=Scaled Conjugate Gradient'))
160+
splineorder = traits.Int(3, argstr='--splineorder=%d',
161+
desc=('order of spline, 2->Qadratic spline, '
162+
'3->Cubic spline'))
163+
numprec = traits.Enum('double', 'float', argstr='--numprec=%s',
164+
desc=('Precision for representing Hessian, double '
165+
'or float.'))
166+
interp = traits.Enum('spline', 'linear', argstr='--interp=%s',
167+
desc='Image interpolation model, linear or spline.')
168+
scale = traits.Enum(0, 1, argstr='--scale=%d',
169+
desc=('If set (=1), the images are individually scaled '
170+
'to a common mean'))
171+
regrid = traits.Enum(1, 0, argstr='--regrid=%d',
172+
desc=('If set (=1), the calculations are done in a '
173+
'different grid'))
174+
175+
176+
class TOPUPOutputSpec(TraitedSpec):
177+
out_fieldcoef = File(exists=True,
178+
desc='file containing the field coefficients')
179+
out_movpar = File(exists=True, desc='movpar.txt output file')
180+
out_enc_file = File(desc='encoding directions file output for applytopup')
181+
out_field = File(desc='name of image file with field (Hz)')
182+
out_corrected = File(desc='name of 4D image file with unwarped images')
183+
out_logfile = File(desc='name of log-file')
184+
185+
186+
class TOPUP(FSLCommand):
187+
""" Interface for FSL topup, a tool for estimating and correcting
188+
susceptibility induced distortions
145189
Reference: http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/TOPUP
146190
Example: http://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/ExampleTopupFollowedByApplytopup
147191
@@ -164,73 +208,50 @@ class TOPUP( FSLCommand ):
164208
input_spec = TOPUPInputSpec
165209
output_spec = TOPUPOutputSpec
166210

167-
def _parse_inputs( self, skip=None ):
168-
if skip is None:
169-
skip = []
170-
171-
if not isdefined(self.inputs.out_base ):
172-
self.inputs.out_base = './nipypetu'
173-
174-
self.inputs.out_base = os.path.abspath(self.inputs.out_base)
175-
176-
if isdefined( self.inputs.encoding_file ):
177-
skip.append( 'encoding_direction' )
178-
skip.append( 'readout_times' )
179-
else:
180-
encdir = 'y'
181-
enctimes = None
182-
183-
if isdefined( self.inputs.encoding_direction ):
184-
encdir = self.inputs.encoding_direction
185-
186-
if isdefined( self.inputs.readout_times ):
187-
enctimes = self.inputs.readout_times
188-
189-
self.inputs.encoding_file = self._generate_encfile( encdir, enctimes )
190-
191-
return super(TOPUP, self)._parse_inputs(skip=skip)
211+
def _format_arg(self, name, trait_spec, value):
212+
if name == 'encoding_direction':
213+
return trait_spec.argstr % self._generate_encfile()
214+
return super(TOPUP, self)._format_arg(name, trait_spec, value)
192215

193216
def _list_outputs(self):
194-
outputs = self.output_spec().get()
195-
outputs['out_topup'] = self.inputs.out_base
196-
outputs['out_fieldcoef'] = '%s_%s.nii.gz' % (self.inputs.out_base, 'fieldcoef' )
197-
outputs['out_movpar'] = '%s_%s.txt' % (self.inputs.out_base, 'movpar' )
198-
outputs['out_enc_file'] = self.inputs.encoding_file
199-
200-
if isdefined( self.inputs.out_field ):
201-
outputs['out_field'] = self.inputs.out_field
217+
outputs = super(TOPUP, self)._list_outputs()
218+
if isdefined(self.inputs.out_base):
219+
base = self.inputs.out_base
202220
else:
203-
outputs['out_field'] = Undefined
221+
base = split_filename(self.inputs.in_file)[1] + '_base'
222+
outputs['out_fieldcoef'] = self._gen_fname(base, suffix='_fieldcoeff')
223+
outputs['out_movpar'] = self._gen_fname(base, suffix='_movpar')
204224

205-
if isdefined( self.inputs.out_corrected ):
206-
outputs['out_corrected'] = self.inputs.out_corrected
207-
else:
208-
outputs['out_corrected'] = Undefined
209-
210-
if isdefined( self.inputs.out_logfile ):
211-
outputs['out_logfile'] = self.inputs.out_logfile
212-
else:
213-
outputs['out_logfile'] = Undefined
225+
if isdefined(self.inputs.encoding_direction):
226+
outputs['out_enc_file'] = self._get_encfilename()
214227
return outputs
215228

216-
def _generate_encfile( self, encdir, enctime=None ):
217-
out_file = '%s_encfile.txt' % self.inputs.out_base
218-
direction = 1.0
219-
if len(encdir)==2 and encdir[1]=='-':
220-
direction = -1.0
221-
222-
if enctime is None:
223-
enctime=[ 1.0, 1.0 ]
224-
225-
file1 = [ float(val[0]==encdir[0]) * direction for val in [ 'x', 'y', 'z' ] ]
226-
file2 = [ float(val[0]==encdir[0]) * direction * -1.0 for val in [ 'x', 'y', 'z' ] ]
227-
228-
file1.append( enctime[0] )
229-
file2.append( enctime[1] )
230-
231-
232-
np.savetxt( out_file, np.array( [ file1, file2 ] ), fmt='%.2f' )
229+
def _get_encfilename(self):
230+
out_file = os.path.join(os.getcwd(),
231+
('%s_encfile.txt' %
232+
split_filename(self.inputs.in_file)[1]))
233+
return out_file
233234

235+
def _generate_encfile(self):
236+
"""Generate a topup compatible encoding file based on given directions
237+
"""
238+
out_file = self._get_encfilename()
239+
durations = self.inputs.readout_times
240+
if len(self.inputs.encoding_direction) != len(durations):
241+
if len(self.inputs.readout_times) != 1:
242+
raise ValueError(('Readout time must be a float or match length'
243+
' of encoding directions'))
244+
durations = durations * len(self.inputs.encoding_direction)
245+
246+
lines = []
247+
for idx, encdir in enumerate(self.inputs.encoding_direction):
248+
direction = 1.0
249+
if encdir.endswith('-'):
250+
direction = -1.0
251+
line = [float(val[0] == encdir[0]) * direction
252+
for val in ['x', 'y', 'z']] + [durations[idx]]
253+
lines.append(line)
254+
np.savetxt(out_file, np.array(lines), fmt='%d %d %d %.3f')
234255
return out_file
235256

236257
class ApplyTOPUPInputSpec( FSLCommandInputSpec ):

nipype/interfaces/fsl/tests/test_auto_TOPUP.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ def test_TOPUP_inputs():
88
config=dict(argstr='--config=%s',
99
usedefault=True,
1010
),
11-
encoding_direction=dict(),
11+
encoding_direction=dict(argstr='--datain=%s',
12+
mandatory=True,
13+
requires=['readout_times'],
14+
xor=['encoding_file'],
15+
),
1216
encoding_file=dict(argstr='--datain=%s',
17+
mandatory=True,
18+
xor=['encoding_direction'],
1319
),
1420
environ=dict(nohash=True,
1521
usedefault=True,
@@ -33,15 +39,32 @@ def test_TOPUP_inputs():
3339
numprec=dict(argstr='--numprec=%s',
3440
),
3541
out_base=dict(argstr='--out=%s',
42+
hash_files=False,
43+
keep_extension=True,
44+
name_source=['in_file'],
45+
name_template='%s_base',
3646
),
3747
out_corrected=dict(argstr='--iout=%s',
48+
hash_files=False,
49+
name_source=['in_file'],
50+
name_template='%s_corrected',
3851
),
3952
out_field=dict(argstr='--fout=%s',
53+
hash_files=False,
54+
name_source=['in_file'],
55+
name_template='%s_field',
4056
),
4157
out_logfile=dict(argstr='--logout=%s',
58+
hash_files=False,
59+
keep_extension=True,
60+
name_source=['in_file'],
61+
name_template='%s_topup.log',
4262
),
4363
output_type=dict(),
44-
readout_times=dict(),
64+
readout_times=dict(mandatory=True,
65+
requires=['encoding_direction'],
66+
xor=['encoding_file'],
67+
),
4568
regrid=dict(argstr='--regrid=%d',
4669
),
4770
scale=dict(argstr='--scale=%d',
@@ -69,7 +92,6 @@ def test_TOPUP_outputs():
6992
out_fieldcoef=dict(),
7093
out_logfile=dict(),
7194
out_movpar=dict(),
72-
out_topup=dict(),
7395
)
7496
outputs = TOPUP.output_spec()
7597

0 commit comments

Comments
 (0)