|
12 | 12 | import os
|
13 | 13 | import os.path as op
|
14 | 14 | import re
|
15 |
| -from warnings import warn |
| 15 | +import numpy as np |
16 | 16 |
|
17 |
| -from .base import AFNICommand, AFNICommandInputSpec, AFNICommandOutputSpec, Info, no_afni |
18 |
| -from ..base import CommandLineInputSpec, CommandLine, OutputMultiPath |
| 17 | +from .base import (AFNICommandBase, AFNICommand, AFNICommandInputSpec, AFNICommandOutputSpec, |
| 18 | + Info, no_afni) |
| 19 | +from ..base import CommandLineInputSpec |
19 | 20 | from ..base import (Directory, TraitedSpec,
|
20 | 21 | traits, isdefined, File, InputMultiPath, Undefined)
|
| 22 | +from ...external.six import string_types |
21 | 23 | from ...utils.filemanip import (load_json, save_json, split_filename)
|
22 |
| -from ...utils.filemanip import fname_presuffix |
| 24 | + |
23 | 25 |
|
24 | 26 | class To3DInputSpec(AFNICommandInputSpec):
|
25 | 27 | out_file = File(name_template="%s", desc='output image file name',
|
@@ -180,7 +182,7 @@ class RefitInputSpec(CommandLineInputSpec):
|
180 | 182 | ' template type, e.g. TLRC, MNI, ORIG')
|
181 | 183 |
|
182 | 184 |
|
183 |
| -class Refit(CommandLine): |
| 185 | +class Refit(AFNICommandBase): |
184 | 186 | """Changes some of the information inside a 3D dataset's header
|
185 | 187 |
|
186 | 188 | For complete details, see the `3drefit Documentation.
|
@@ -1544,7 +1546,7 @@ class ROIStatsOutputSpec(TraitedSpec):
|
1544 | 1546 | stats = File(desc='output tab separated values file', exists=True)
|
1545 | 1547 |
|
1546 | 1548 |
|
1547 |
| -class ROIStats(CommandLine): |
| 1549 | +class ROIStats(AFNICommandBase): |
1548 | 1550 | """Display statistics over masked regions
|
1549 | 1551 |
|
1550 | 1552 | For complete details, see the `3dROIstats Documentation.
|
@@ -2113,7 +2115,7 @@ class HistOutputSpec(TraitedSpec):
|
2113 | 2115 | out_show = File(desc='output visual histogram')
|
2114 | 2116 |
|
2115 | 2117 |
|
2116 |
| -class Hist(CommandLine): |
| 2118 | +class Hist(AFNICommandBase): |
2117 | 2119 | """Computes average of all voxels in the input dataset
|
2118 | 2120 | which satisfy the criterion in the options list
|
2119 | 2121 |
|
@@ -2160,3 +2162,213 @@ def _list_outputs(self):
|
2160 | 2162 | if not self.inputs.showhist:
|
2161 | 2163 | outputs['out_show'] = Undefined
|
2162 | 2164 | 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(), traits.Tuple(File(exists=True), traits.Float()), |
| 2201 | + default=False, usedefault=True, 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 | + acf_param = traits.Either( |
| 2213 | + traits.Tuple(traits.Float(), traits.Float(), traits.Float()), |
| 2214 | + traits.Tuple(traits.Float(), traits.Float(), traits.Float(), traits.Float()), |
| 2215 | + desc='fitted ACF model parameters') |
| 2216 | + out_acf = File(exists=True, desc='output acf file') |
| 2217 | + |
| 2218 | + |
| 2219 | +class FWHMx(AFNICommandBase): |
| 2220 | + """ |
| 2221 | + Unlike the older 3dFWHM, this program computes FWHMs for all sub-bricks |
| 2222 | + in the input dataset, each one separately. The output for each one is |
| 2223 | + written to the file specified by '-out'. The mean (arithmetic or geometric) |
| 2224 | + of all the FWHMs along each axis is written to stdout. (A non-positive |
| 2225 | + output value indicates something bad happened; e.g., FWHM in z is meaningless |
| 2226 | + for a 2D dataset; the estimation method computed incoherent intermediate results.) |
| 2227 | +
|
| 2228 | + Examples |
| 2229 | + -------- |
| 2230 | +
|
| 2231 | + >>> from nipype.interfaces import afni as afp |
| 2232 | + >>> fwhm = afp.FWHMx() |
| 2233 | + >>> fwhm.inputs.in_file = 'functional.nii' |
| 2234 | + >>> fwhm.cmdline |
| 2235 | + '3dFWHMx -input functional.nii -out functional_subbricks.out > functional_fwhmx.out' |
| 2236 | +
|
| 2237 | +
|
| 2238 | + (Classic) METHOD: |
| 2239 | +
|
| 2240 | + * Calculate ratio of variance of first differences to data variance. |
| 2241 | + * Should be the same as 3dFWHM for a 1-brick dataset. |
| 2242 | + (But the output format is simpler to use in a script.) |
| 2243 | +
|
| 2244 | +
|
| 2245 | + .. note:: IMPORTANT NOTE [AFNI > 16] |
| 2246 | +
|
| 2247 | + A completely new method for estimating and using noise smoothness values is |
| 2248 | + now available in 3dFWHMx and 3dClustSim. This method is implemented in the |
| 2249 | + '-acf' options to both programs. 'ACF' stands for (spatial) AutoCorrelation |
| 2250 | + Function, and it is estimated by calculating moments of differences out to |
| 2251 | + a larger radius than before. |
| 2252 | +
|
| 2253 | + Notably, real FMRI data does not actually have a Gaussian-shaped ACF, so the |
| 2254 | + estimated ACF is then fit (in 3dFWHMx) to a mixed model (Gaussian plus |
| 2255 | + mono-exponential) of the form |
| 2256 | +
|
| 2257 | + .. math:: |
| 2258 | +
|
| 2259 | + ACF(r) = a * exp(-r*r/(2*b*b)) + (1-a)*exp(-r/c) |
| 2260 | +
|
| 2261 | +
|
| 2262 | + where :math:`r` is the radius, and :math:`a, b, c` are the fitted parameters. |
| 2263 | + The apparent FWHM from this model is usually somewhat larger in real data |
| 2264 | + than the FWHM estimated from just the nearest-neighbor differences used |
| 2265 | + in the 'classic' analysis. |
| 2266 | +
|
| 2267 | + The longer tails provided by the mono-exponential are also significant. |
| 2268 | + 3dClustSim has also been modified to use the ACF model given above to generate |
| 2269 | + noise random fields. |
| 2270 | +
|
| 2271 | +
|
| 2272 | + .. note:: TL;DR or summary |
| 2273 | +
|
| 2274 | + The take-awaymessage is that the 'classic' 3dFWHMx and |
| 2275 | + 3dClustSim analysis, using a pure Gaussian ACF, is not very correct for |
| 2276 | + FMRI data -- I cannot speak for PET or MEG data. |
| 2277 | +
|
| 2278 | +
|
| 2279 | + .. warning:: |
| 2280 | +
|
| 2281 | + Do NOT use 3dFWHMx on the statistical results (e.g., '-bucket') from |
| 2282 | + 3dDeconvolve or 3dREMLfit!!! The function of 3dFWHMx is to estimate |
| 2283 | + the smoothness of the time series NOISE, not of the statistics. This |
| 2284 | + proscription is especially true if you plan to use 3dClustSim next!! |
| 2285 | +
|
| 2286 | +
|
| 2287 | + .. note:: Recommendations |
| 2288 | +
|
| 2289 | + * For FMRI statistical purposes, you DO NOT want the FWHM to reflect |
| 2290 | + the spatial structure of the underlying anatomy. Rather, you want |
| 2291 | + the FWHM to reflect the spatial structure of the noise. This means |
| 2292 | + that the input dataset should not have anatomical (spatial) structure. |
| 2293 | + * One good form of input is the output of '3dDeconvolve -errts', which is |
| 2294 | + the dataset of residuals left over after the GLM fitted signal model is |
| 2295 | + subtracted out from each voxel's time series. |
| 2296 | + * If you don't want to go to that much trouble, use '-detrend' to approximately |
| 2297 | + subtract out the anatomical spatial structure, OR use the output of 3dDetrend |
| 2298 | + for the same purpose. |
| 2299 | + * If you do not use '-detrend', the program attempts to find non-zero spatial |
| 2300 | + structure in the input, and will print a warning message if it is detected. |
| 2301 | +
|
| 2302 | +
|
| 2303 | + .. note:: Notes on -demend |
| 2304 | +
|
| 2305 | + * I recommend this option, and it is not the default only for historical |
| 2306 | + compatibility reasons. It may become the default someday. |
| 2307 | + * It is already the default in program 3dBlurToFWHM. This is the same detrending |
| 2308 | + as done in 3dDespike; using 2*q+3 basis functions for q > 0. |
| 2309 | + * If you don't use '-detrend', the program now [Aug 2010] checks if a large number |
| 2310 | + of voxels are have significant nonzero means. If so, the program will print a |
| 2311 | + warning message suggesting the use of '-detrend', since inherent spatial |
| 2312 | + structure in the image will bias the estimation of the FWHM of the image time |
| 2313 | + series NOISE (which is usually the point of using 3dFWHMx). |
| 2314 | +
|
| 2315 | +
|
| 2316 | + """ |
| 2317 | + _cmd = '3dFWHMx' |
| 2318 | + input_spec = FWHMxInputSpec |
| 2319 | + output_spec = FWHMxOutputSpec |
| 2320 | + _acf = True |
| 2321 | + |
| 2322 | + def _parse_inputs(self, skip=None): |
| 2323 | + if not self.inputs.detrend: |
| 2324 | + if skip is None: |
| 2325 | + skip = [] |
| 2326 | + skip += ['out_detrend'] |
| 2327 | + return super(FWHMx, self)._parse_inputs(skip=skip) |
| 2328 | + |
| 2329 | + def _format_arg(self, name, trait_spec, value): |
| 2330 | + if name == 'detrend': |
| 2331 | + if isinstance(value, bool): |
| 2332 | + if value: |
| 2333 | + return trait_spec.argstr |
| 2334 | + else: |
| 2335 | + return None |
| 2336 | + elif isinstance(value, int): |
| 2337 | + return trait_spec.argstr + ' %d' % value |
| 2338 | + |
| 2339 | + if name == 'acf': |
| 2340 | + if isinstance(value, bool): |
| 2341 | + if value: |
| 2342 | + return trait_spec.argstr |
| 2343 | + else: |
| 2344 | + self._acf = False |
| 2345 | + return None |
| 2346 | + elif isinstance(value, tuple): |
| 2347 | + return trait_spec.argstr + ' %s %f' % value |
| 2348 | + elif isinstance(value, string_types): |
| 2349 | + return trait_spec.argstr + ' ' + value |
| 2350 | + return super(FWHMx, self)._format_arg(name, trait_spec, value) |
| 2351 | + |
| 2352 | + def _list_outputs(self): |
| 2353 | + outputs = super(FWHMx, self)._list_outputs() |
| 2354 | + |
| 2355 | + if self.inputs.detrend: |
| 2356 | + fname, ext = op.splitext(self.inputs.in_file) |
| 2357 | + if '.gz' in ext: |
| 2358 | + _, ext2 = op.splitext(fname) |
| 2359 | + ext = ext2 + ext |
| 2360 | + outputs['out_detrend'] += ext |
| 2361 | + else: |
| 2362 | + outputs['out_detrend'] = Undefined |
| 2363 | + |
| 2364 | + sout = np.loadtxt(outputs['out_file']) #pylint: disable=E1101 |
| 2365 | + if self._acf: |
| 2366 | + outputs['acf_param'] = tuple(sout[1]) |
| 2367 | + sout = tuple(sout[0]) |
| 2368 | + |
| 2369 | + outputs['out_acf'] = op.abspath('3dFWHMx.1D') |
| 2370 | + if isinstance(self.inputs.acf, string_types): |
| 2371 | + outputs['out_acf'] = op.abspath(self.inputs.acf) |
| 2372 | + |
| 2373 | + outputs['fwhm'] = tuple(sout) |
| 2374 | + return outputs |
0 commit comments