Skip to content

Commit 6069c70

Browse files
author
Oscar Esteban
committed
[ENH] Added afni.FWHMx interface
1 parent e2ec11c commit 6069c70

File tree

3 files changed

+283
-4
lines changed

3 files changed

+283
-4
lines changed

nipype/interfaces/afni/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
Fourier, Allineate, Maskave, SkullStrip, TCat, Fim,
1313
BlurInMask, Autobox, TCorrMap, Bandpass, Retroicor,
1414
TCorrelate, TCorr1D, BrickStat, ROIStats, AutoTcorrelate,
15-
AFNItoNIFTI, Eval, Means, Hist)
15+
AFNItoNIFTI, Eval, Means, Hist, FWHMx)
1616
from .svm import (SVMTest, SVMTrain)

nipype/interfaces/afni/preprocess.py

Lines changed: 202 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@
1010
"""
1111

1212
import os
13+
from sys import platform
1314
import os.path as op
1415
import re
15-
from warnings import warn
16+
import numpy as np
1617

1718
from .base import AFNICommand, AFNICommandInputSpec, AFNICommandOutputSpec, Info, no_afni
18-
from ..base import CommandLineInputSpec, CommandLine, OutputMultiPath
19+
from ..base import CommandLineInputSpec, CommandLine
1920
from ..base import (Directory, TraitedSpec,
2021
traits, isdefined, File, InputMultiPath, Undefined)
22+
from ...external.six import string_types
2123
from ...utils.filemanip import (load_json, save_json, split_filename)
22-
from ...utils.filemanip import fname_presuffix
24+
2325

2426
class To3DInputSpec(AFNICommandInputSpec):
2527
out_file = File(name_template="%s", desc='output image file name',
@@ -2160,3 +2162,200 @@ def _list_outputs(self):
21602162
if not self.inputs.showhist:
21612163
outputs['out_show'] = Undefined
21622164
return outputs
2165+
2166+
2167+
class FWHMxInputSpec(CommandLineInputSpec):
2168+
in_file = File(desc='input dataset', argstr='-input %s', mandatory=True, exists=True)
2169+
out_file = File(argstr='> %s', name_source='in_file', name_template='%s_fwhmx.out',
2170+
position=-1, keep_extension=False, desc='output file')
2171+
out_subbricks = File(argstr='-out %s', name_source='in_file', name_template='%s_subbricks.out',
2172+
keep_extension=False, desc='output file listing the subbricks FWHM')
2173+
mask = File(desc='use only voxels that are nonzero in mask', argstr='-mask %s', exists=True)
2174+
automask = traits.Bool(False, usedefault=True, argstr='-automask',
2175+
desc='compute a mask from THIS dataset, a la 3dAutomask')
2176+
detrend = traits.Either(
2177+
traits.Bool(), traits.Int(), default=False, argstr='-detrend', xor=['demed'], usedefault=True,
2178+
desc='instead of demed (0th order detrending), detrend to the specified order. If order '
2179+
'is not given, the program picks q=NT/30. -detrend disables -demed, and includes '
2180+
'-unif.')
2181+
demed = traits.Bool(
2182+
False, argstr='-demed', xorg=['detrend'],
2183+
desc='If the input dataset has more than one sub-brick (e.g., has a time axis), then '
2184+
'subtract the median of each voxel\'s time series before processing FWHM. This will '
2185+
'tend to remove intrinsic spatial structure and leave behind the noise.')
2186+
unif = traits.Bool(False, argstr='-unif',
2187+
desc='If the input dataset has more than one sub-brick, then normalize each'
2188+
' voxel\'s time series to have the same MAD before processing FWHM.')
2189+
out_detrend = File(argstr='-detprefix %s', name_source='in_file', name_template='%s_detrend',
2190+
keep_extension=False, desc='Save the detrended file into a dataset')
2191+
geom = traits.Bool(argstr='-geom', xor=['arith'],
2192+
desc='if in_file has more than one sub-brick, compute the final estimate as'
2193+
'the geometric mean of the individual sub-brick FWHM estimates')
2194+
arith = traits.Bool(argstr='-arith', xor=['geom'],
2195+
desc='if in_file has more than one sub-brick, compute the final estimate as'
2196+
'the arithmetic mean of the individual sub-brick FWHM estimates')
2197+
combine = traits.Bool(argstr='-combine', desc='combine the final measurements along each axis')
2198+
compat = traits.Bool(argstr='-compat', desc='be compatible with the older 3dFWHM')
2199+
acf = traits.Either(
2200+
traits.Bool(), File(exists=True), traits.Tuple(File(exists=True), traits.Float()),
2201+
argstr='-acf', desc='computes the spatial autocorrelation')
2202+
2203+
2204+
class FWHMxOutputSpec(TraitedSpec):
2205+
out_file = File(exists=True, desc='output file')
2206+
out_subbricks = File(exists=True, desc='output file (subbricks)')
2207+
out_detrend = File(desc='output file, detrended')
2208+
fwhm = traits.Either(
2209+
traits.Tuple(traits.Float(), traits.Float(), traits.Float()),
2210+
traits.Tuple(traits.Float(), traits.Float(), traits.Float(), traits.Float()),
2211+
desc='FWHM along each axis')
2212+
2213+
2214+
class FWHMx(CommandLine):
2215+
"""
2216+
Unlike the older 3dFWHM, this program computes FWHMs for all sub-bricks
2217+
in the input dataset, each one separately. The output for each one is
2218+
written to the file specified by '-out'. The mean (arithmetic or geometric)
2219+
of all the FWHMs along each axis is written to stdout. (A non-positive
2220+
output value indicates something bad happened; e.g., FWHM in z is meaningless
2221+
for a 2D dataset; the estimation method computed incoherent intermediate results.)
2222+
2223+
Examples
2224+
--------
2225+
2226+
>>> from nipype.interfaces import afni as afp
2227+
>>> fwhm = afp.FWHMx()
2228+
>>> fwhm.inputs.in_file = 'functional.nii'
2229+
>>> fwhm.cmdline
2230+
'3dFWHMx -input functional.nii -out functional_subbricks.out > functional_fwhmx.out'
2231+
2232+
2233+
(Classic) METHOD:
2234+
2235+
* Calculate ratio of variance of first differences to data variance.
2236+
* Should be the same as 3dFWHM for a 1-brick dataset.
2237+
(But the output format is simpler to use in a script.)
2238+
2239+
2240+
.. note:: IMPORTANT NOTE [AFNI > 16]
2241+
2242+
A completely new method for estimating and using noise smoothness values is
2243+
now available in 3dFWHMx and 3dClustSim. This method is implemented in the
2244+
'-acf' options to both programs. 'ACF' stands for (spatial) AutoCorrelation
2245+
Function, and it is estimated by calculating moments of differences out to
2246+
a larger radius than before.
2247+
2248+
Notably, real FMRI data does not actually have a Gaussian-shaped ACF, so the
2249+
estimated ACF is then fit (in 3dFWHMx) to a mixed model (Gaussian plus
2250+
mono-exponential) of the form
2251+
2252+
.. math::
2253+
2254+
ACF(r) = a * exp(-r*r/(2*b*b)) + (1-a)*exp(-r/c)
2255+
2256+
2257+
where :math:`r` is the radius, and :math:`a, b, c` are the fitted parameters.
2258+
The apparent FWHM from this model is usually somewhat larger in real data
2259+
than the FWHM estimated from just the nearest-neighbor differences used
2260+
in the 'classic' analysis.
2261+
2262+
The longer tails provided by the mono-exponential are also significant.
2263+
3dClustSim has also been modified to use the ACF model given above to generate
2264+
noise random fields.
2265+
2266+
2267+
.. note:: TL;DR or summary
2268+
2269+
The take-awaymessage is that the 'classic' 3dFWHMx and
2270+
3dClustSim analysis, using a pure Gaussian ACF, is not very correct for
2271+
FMRI data -- I cannot speak for PET or MEG data.
2272+
2273+
2274+
.. warning::
2275+
2276+
Do NOT use 3dFWHMx on the statistical results (e.g., '-bucket') from
2277+
3dDeconvolve or 3dREMLfit!!! The function of 3dFWHMx is to estimate
2278+
the smoothness of the time series NOISE, not of the statistics. This
2279+
proscription is especially true if you plan to use 3dClustSim next!!
2280+
2281+
2282+
.. note:: Recommendations
2283+
2284+
* For FMRI statistical purposes, you DO NOT want the FWHM to reflect
2285+
the spatial structure of the underlying anatomy. Rather, you want
2286+
the FWHM to reflect the spatial structure of the noise. This means
2287+
that the input dataset should not have anatomical (spatial) structure.
2288+
* One good form of input is the output of '3dDeconvolve -errts', which is
2289+
the dataset of residuals left over after the GLM fitted signal model is
2290+
subtracted out from each voxel's time series.
2291+
* If you don't want to go to that much trouble, use '-detrend' to approximately
2292+
subtract out the anatomical spatial structure, OR use the output of 3dDetrend
2293+
for the same purpose.
2294+
* If you do not use '-detrend', the program attempts to find non-zero spatial
2295+
structure in the input, and will print a warning message if it is detected.
2296+
2297+
2298+
.. note:: Notes on -demend
2299+
2300+
* I recommend this option, and it is not the default only for historical
2301+
compatibility reasons. It may become the default someday.
2302+
* It is already the default in program 3dBlurToFWHM. This is the same detrending
2303+
as done in 3dDespike; using 2*q+3 basis functions for q > 0.
2304+
* If you don't use '-detrend', the program now [Aug 2010] checks if a large number
2305+
of voxels are have significant nonzero means. If so, the program will print a
2306+
warning message suggesting the use of '-detrend', since inherent spatial
2307+
structure in the image will bias the estimation of the FWHM of the image time
2308+
series NOISE (which is usually the point of using 3dFWHMx).
2309+
2310+
2311+
"""
2312+
_cmd = '3dFWHMx'
2313+
input_spec = FWHMxInputSpec
2314+
output_spec = FWHMxOutputSpec
2315+
2316+
def _parse_inputs(self, skip=None):
2317+
if not self.inputs.detrend:
2318+
if skip is None:
2319+
skip = []
2320+
skip += ['out_detrend']
2321+
return super(FWHMx, self)._parse_inputs(skip=skip)
2322+
2323+
def _format_arg(self, name, trait_spec, value):
2324+
if name == 'detrend':
2325+
if isinstance(value, bool):
2326+
if value:
2327+
return trait_spec.argstr
2328+
else:
2329+
return None
2330+
elif isinstance(value, int):
2331+
return trait_spec.argstr + ' %d' % value
2332+
2333+
if name == 'acf':
2334+
if isinstance(value, tuple):
2335+
return trait_spec.argstr + ' %s %f' % value
2336+
elif isinstance(value, string_types):
2337+
return trait_spec.argstr + ' ' + value
2338+
return super(FWHMx, self)._format_arg(name, trait_spec, value)
2339+
2340+
def _run_interface(self, runtime):
2341+
if platform == 'darwin':
2342+
# http://afni.nimh.nih.gov/afni/community/board/read.php?1,145346,145347#msg-145347
2343+
runtime.environ['DYLD_FALLBACK_LIBRARY_PATH'] = '/usr/local/afni/'
2344+
2345+
return super(FWHMx, self)._run_interface(runtime)
2346+
2347+
2348+
def _list_outputs(self):
2349+
outputs = super(FWHMx, self)._list_outputs()
2350+
2351+
if self.inputs.detrend:
2352+
fname, ext = op.splitext(self.inputs.in_file)
2353+
if '.gz' in ext:
2354+
_, ext2 = op.splitext(fname)
2355+
ext = ext2 + ext
2356+
outputs['out_detrend'] += ext
2357+
else:
2358+
outputs['out_detrend'] = Undefined
2359+
2360+
outputs['fwhm'] = tuple(np.loadtxt(outputs['out_file'])) #pylint: disable=E1101
2361+
return outputs
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
2+
from ....testing import assert_equal
3+
from ..preprocess import FWHMx
4+
5+
6+
def test_FWHMx_inputs():
7+
input_map = dict(acf=dict(argstr='-acf',
8+
),
9+
args=dict(argstr='%s',
10+
),
11+
arith=dict(argstr='-arith',
12+
xor=['geom'],
13+
),
14+
automask=dict(argstr='-automask',
15+
usedefault=True,
16+
),
17+
combine=dict(argstr='-combine',
18+
),
19+
compat=dict(argstr='-compat',
20+
),
21+
demed=dict(argstr='-demed',
22+
xorg=['detrend'],
23+
),
24+
detrend=dict(argstr='-detrend',
25+
usedefault=True,
26+
xor=['demed'],
27+
),
28+
environ=dict(nohash=True,
29+
usedefault=True,
30+
),
31+
geom=dict(argstr='-geom',
32+
xor=['arith'],
33+
),
34+
ignore_exception=dict(nohash=True,
35+
usedefault=True,
36+
),
37+
in_file=dict(argstr='-input %s',
38+
mandatory=True,
39+
),
40+
mask=dict(argstr='-mask %s',
41+
),
42+
out_detrend=dict(argstr='-detprefix %s',
43+
keep_extension=False,
44+
name_source='in_file',
45+
name_template='%s_detrend',
46+
),
47+
out_file=dict(argstr='> %s',
48+
keep_extension=False,
49+
name_source='in_file',
50+
name_template='%s_fwhmx.out',
51+
position=-1,
52+
),
53+
out_subbricks=dict(argstr='-out %s',
54+
keep_extension=False,
55+
name_source='in_file',
56+
name_template='%s_subbricks.out',
57+
),
58+
terminal_output=dict(nohash=True,
59+
),
60+
unif=dict(argstr='-unif',
61+
),
62+
)
63+
inputs = FWHMx.input_spec()
64+
65+
for key, metadata in list(input_map.items()):
66+
for metakey, value in list(metadata.items()):
67+
yield assert_equal, getattr(inputs.traits()[key], metakey), value
68+
69+
70+
def test_FWHMx_outputs():
71+
output_map = dict(fwhm=dict(),
72+
out_detrend=dict(),
73+
out_file=dict(),
74+
out_subbricks=dict(),
75+
)
76+
outputs = FWHMx.output_spec()
77+
78+
for key, metadata in list(output_map.items()):
79+
for metakey, value in list(metadata.items()):
80+
yield assert_equal, getattr(outputs.traits()[key], metakey), value

0 commit comments

Comments
 (0)