forked from nipy/nipype
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase.py
332 lines (271 loc) · 9.85 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# -*- coding: utf-8 -*-
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""Provide a base interface to AFNI commands."""
import os
from sys import platform
from distutils import spawn
from ... import logging, LooseVersion
from ...utils.filemanip import split_filename, fname_presuffix
from ..base import (
CommandLine,
traits,
CommandLineInputSpec,
isdefined,
File,
TraitedSpec,
PackageInfo,
)
from ...external.due import BibTeX
# Use nipype's logging system
IFLOGGER = logging.getLogger("nipype.interface")
class Info(PackageInfo):
"""Handle afni output type and version information."""
__outputtype = "AFNI"
ftypes = {"NIFTI": ".nii", "AFNI": "", "NIFTI_GZ": ".nii.gz"}
version_cmd = "afni --version"
@staticmethod
def parse_version(raw_info):
"""Check and parse AFNI's version."""
version_stamp = raw_info.split("\n")[0].split("Version ")[1]
if version_stamp.startswith("AFNI"):
version_stamp = version_stamp.split("AFNI_")[1]
elif version_stamp.startswith("Debian"):
version_stamp = version_stamp.split("Debian-")[1].split("~")[0]
else:
return None
version = LooseVersion(version_stamp.replace("_", ".")).version[:3]
if version[0] < 1000:
version[0] = version[0] + 2000
return tuple(version)
@classmethod
def output_type_to_ext(cls, outputtype):
"""
Get the file extension for the given output type.
Parameters
----------
outputtype : {'NIFTI', 'NIFTI_GZ', 'AFNI'}
String specifying the output type.
Returns
-------
extension : str
The file extension for the output type.
"""
try:
return cls.ftypes[outputtype]
except KeyError as e:
msg = "Invalid AFNIOUTPUTTYPE: ", outputtype
raise KeyError(msg) from e
@classmethod
def outputtype(cls):
"""
Set default output filetype.
AFNI has no environment variables, Output filetypes get set in command line calls
Nipype uses ``AFNI`` as default
Returns
-------
None
"""
return "AFNI"
@staticmethod
def standard_image(img_name):
"""
Grab an image from the standard location.
Could be made more fancy to allow for more relocatability
"""
clout = CommandLine(
"which afni",
ignore_exception=True,
resource_monitor=False,
terminal_output="allatonce",
).run()
if clout.runtime.returncode is not 0:
return None
out = clout.runtime.stdout
basedir = os.path.split(out)[0]
return os.path.join(basedir, img_name)
class AFNICommandBase(CommandLine):
"""
A base class to fix a linking problem in OSX and AFNI.
See Also
--------
`This thread
<http://afni.nimh.nih.gov/afni/community/board/read.php?1,145346,145347#msg-145347>`__
about the particular environment variable that fixes this problem.
"""
def _run_interface(self, runtime, correct_return_codes=(0,)):
if platform == "darwin":
runtime.environ["DYLD_FALLBACK_LIBRARY_PATH"] = "/usr/local/afni/"
return super(AFNICommandBase, self)._run_interface(
runtime, correct_return_codes
)
class AFNICommandInputSpec(CommandLineInputSpec):
num_threads = traits.Int(
1, usedefault=True, nohash=True, desc="set number of threads"
)
outputtype = traits.Enum(
"AFNI", list(Info.ftypes.keys()), desc="AFNI output filetype"
)
out_file = File(
name_template="%s_afni",
desc="output image file name",
argstr="-prefix %s",
name_source=["in_file"],
)
class AFNICommandOutputSpec(TraitedSpec):
out_file = File(desc="output file", exists=True)
class AFNICommand(AFNICommandBase):
"""Shared options for several AFNI commands."""
input_spec = AFNICommandInputSpec
_outputtype = None
references_ = [
{
"entry": BibTeX(
"@article{Cox1996,"
"author={R.W. Cox},"
"title={AFNI: software for analysis and "
"visualization of functional magnetic "
"resonance neuroimages},"
"journal={Computers and Biomedical research},"
"volume={29},"
"number={3},"
"pages={162-173},"
"year={1996},"
"}"
),
"tags": ["implementation"],
},
{
"entry": BibTeX(
"@article{CoxHyde1997,"
"author={R.W. Cox and J.S. Hyde},"
"title={Software tools for analysis and "
"visualization of fMRI data},"
"journal={NMR in Biomedicine},"
"volume={10},"
"number={45},"
"pages={171-178},"
"year={1997},"
"}"
),
"tags": ["implementation"],
},
]
@property
def num_threads(self):
"""Get number of threads."""
return self.inputs.num_threads
@num_threads.setter
def num_threads(self, value):
self.inputs.num_threads = value
@classmethod
def set_default_output_type(cls, outputtype):
"""
Set the default output type for AFNI classes.
This method is used to set the default output type for all afni
subclasses. However, setting this will not update the output
type for any existing instances. For these, assign the
<instance>.inputs.outputtype.
"""
if outputtype in Info.ftypes:
cls._outputtype = outputtype
else:
raise AttributeError("Invalid AFNI outputtype: %s" % outputtype)
def __init__(self, **inputs):
"""Instantiate an AFNI command tool wrapper."""
super(AFNICommand, self).__init__(**inputs)
self.inputs.on_trait_change(self._output_update, "outputtype")
if hasattr(self.inputs, "num_threads"):
self.inputs.on_trait_change(self._nthreads_update, "num_threads")
if self._outputtype is None:
self._outputtype = Info.outputtype()
if not isdefined(self.inputs.outputtype):
self.inputs.outputtype = self._outputtype
else:
self._output_update()
def _nthreads_update(self):
"""Update environment with new number of threads."""
self.inputs.environ["OMP_NUM_THREADS"] = "%d" % self.inputs.num_threads
def _output_update(self):
"""
Update the internal property with the provided input.
i think? updates class private attribute based on instance input
in fsl also updates ENVIRON variable....not valid in afni
as it uses no environment variables
"""
self._outputtype = self.inputs.outputtype
def _overload_extension(self, value, name=None):
path, base, _ = split_filename(value)
return os.path.join(
path, base + Info.output_type_to_ext(self.inputs.outputtype)
)
def _list_outputs(self):
outputs = super(AFNICommand, self)._list_outputs()
metadata = dict(name_source=lambda t: t is not None)
out_names = list(self.inputs.traits(**metadata).keys())
if out_names:
for name in out_names:
if outputs[name]:
_, _, ext = split_filename(outputs[name])
if ext == "":
outputs[name] = outputs[name] + "+orig.BRIK"
return outputs
def _gen_fname(self, basename, cwd=None, suffix=None, change_ext=True, ext=None):
"""
Generate a filename based on the given parameters.
The filename will take the form: cwd/basename<suffix><ext>.
If change_ext is True, it will use the extentions specified in
<instance>intputs.output_type.
Parameters
----------
basename : str
Filename to base the new filename on.
cwd : str
Path to prefix to the new filename. (default is os.getcwd())
suffix : str
Suffix to add to the `basename`. (defaults is '' )
change_ext : bool
Flag to change the filename extension to the FSL output type.
(default True)
Returns
-------
fname : str
New filename based on given parameters.
"""
if not basename:
msg = "Unable to generate filename for command %s. " % self.cmd
msg += "basename is not set!"
raise ValueError(msg)
if cwd is None:
cwd = os.getcwd()
if ext is None:
ext = Info.output_type_to_ext(self.inputs.outputtype)
if change_ext:
suffix = "".join((suffix, ext)) if suffix else ext
if suffix is None:
suffix = ""
fname = fname_presuffix(basename, suffix=suffix, use_ext=False, newpath=cwd)
return fname
class AFNIPythonCommandInputSpec(CommandLineInputSpec):
outputtype = traits.Enum(
"AFNI", list(Info.ftypes.keys()), desc="AFNI output filetype"
)
py27_path = traits.Either(
"python2", File(exists=True), usedefault=True, default="python2"
)
class AFNIPythonCommand(AFNICommand):
"""A subtype of AFNI command line for Python scripts."""
@property
def cmd(self):
"""Revise the command path."""
orig_cmd = super(AFNIPythonCommand, self).cmd
found = spawn.find_executable(orig_cmd)
return found if found is not None else orig_cmd
@property
def _cmd_prefix(self):
return "{} ".format(self.inputs.py27_path)
def no_afni():
"""Check whether AFNI is not available."""
if Info.version() is None:
return True
return False