Skip to content

Commit 8a887d3

Browse files
committed
Merge remote-tracking branch 'upstream/master' into enh/ReviseResourceProfiler
2 parents c42473a + ea62ca6 commit 8a887d3

File tree

4 files changed

+203
-36
lines changed

4 files changed

+203
-36
lines changed

nipype/algorithms/rapidart.py

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,29 @@ def _calc_norm(mc, use_differences, source, brain_pts=None):
9999
100100
"""
101101

102+
affines = [_get_affine_matrix(mc[i, :], source)
103+
for i in range(mc.shape[0])]
104+
return _calc_norm_affine(affines, use_differences, brain_pts)
105+
106+
107+
def _calc_norm_affine(affines, use_differences, brain_pts=None):
108+
"""Calculates the maximum overall displacement of the midpoints
109+
of the faces of a cube due to translation and rotation.
110+
111+
Parameters
112+
----------
113+
affines : list of [4 x 4] affine matrices
114+
use_differences : boolean
115+
brain_pts : [4 x n_points] of coordinates
116+
117+
Returns
118+
-------
119+
120+
norm : at each time point
121+
displacement : euclidean distance (mm) of displacement at each coordinate
122+
123+
"""
124+
102125
if brain_pts is None:
103126
respos = np.diag([70, 70, 75])
104127
resneg = np.diag([-70, -110, -45])
@@ -107,49 +130,34 @@ def _calc_norm(mc, use_differences, source, brain_pts=None):
107130
else:
108131
all_pts = brain_pts
109132
n_pts = all_pts.size - all_pts.shape[1]
110-
newpos = np.zeros((mc.shape[0], n_pts))
133+
newpos = np.zeros((len(affines), n_pts))
111134
if brain_pts is not None:
112-
displacement = np.zeros((mc.shape[0], int(n_pts / 3)))
113-
for i in range(mc.shape[0]):
114-
affine = _get_affine_matrix(mc[i, :], source)
115-
newpos[i, :] = np.dot(affine,
116-
all_pts)[0:3, :].ravel()
135+
displacement = np.zeros((len(affines), int(n_pts / 3)))
136+
for i, affine in enumerate(affines):
137+
newpos[i, :] = np.dot(affine, all_pts)[0:3, :].ravel()
117138
if brain_pts is not None:
118-
displacement[i, :] = \
119-
np.sqrt(np.sum(np.power(np.reshape(newpos[i, :],
120-
(3, all_pts.shape[1])) -
121-
all_pts[0:3, :],
122-
2),
123-
axis=0))
139+
displacement[i, :] = np.sqrt(np.sum(
140+
np.power(np.reshape(newpos[i, :],
141+
(3, all_pts.shape[1])) - all_pts[0:3, :],
142+
2),
143+
axis=0))
124144
# np.savez('displacement.npz', newpos=newpos, pts=all_pts)
125-
normdata = np.zeros(mc.shape[0])
145+
normdata = np.zeros(len(affines))
126146
if use_differences:
127147
newpos = np.concatenate((np.zeros((1, n_pts)),
128148
np.diff(newpos, n=1, axis=0)), axis=0)
129149
for i in range(newpos.shape[0]):
130150
normdata[i] = \
131-
np.max(np.sqrt(np.sum(np.reshape(np.power(np.abs(newpos[i, :]), 2),
132-
(3, all_pts.shape[1])), axis=0)))
151+
np.max(np.sqrt(np.sum(
152+
np.reshape(np.power(np.abs(newpos[i, :]), 2),
153+
(3, all_pts.shape[1])),
154+
axis=0)))
133155
else:
134156
newpos = np.abs(signal.detrend(newpos, axis=0, type='constant'))
135157
normdata = np.sqrt(np.mean(np.power(newpos, 2), axis=1))
136158
return normdata, displacement
137159

138160

139-
def _nanmean(a, axis=None):
140-
"""Return the mean excluding items that are nan
141-
142-
>>> a = [1, 2, np.nan]
143-
>>> _nanmean(a)
144-
1.5
145-
146-
"""
147-
if axis:
148-
return np.nansum(a, axis) / np.sum(1 - np.isnan(a), axis)
149-
else:
150-
return np.nansum(a) / np.sum(1 - np.isnan(a))
151-
152-
153161
class ArtifactDetectInputSpec(BaseInterfaceInputSpec):
154162
realigned_files = InputMultiPath(File(exists=True),
155163
desc="Names of realigned functional data files",
@@ -376,11 +384,11 @@ def _detect_outliers_core(self, imgfile, motionfile, runidx, cwd=None):
376384
vol = data[:, :, :, t0]
377385
# Use an SPM like approach
378386
mask_tmp = vol > \
379-
(_nanmean(vol) / self.inputs.global_threshold)
387+
(np.nanmean(vol) / self.inputs.global_threshold)
380388
mask = mask * mask_tmp
381389
for t0 in range(timepoints):
382390
vol = data[:, :, :, t0]
383-
g[t0] = _nanmean(vol[mask])
391+
g[t0] = np.nanmean(vol[mask])
384392
if len(find_indices(mask)) < (np.prod((x, y, z)) / 10):
385393
intersect_mask = False
386394
g = np.zeros((timepoints, 1))
@@ -390,7 +398,7 @@ def _detect_outliers_core(self, imgfile, motionfile, runidx, cwd=None):
390398
for t0 in range(timepoints):
391399
vol = data[:, :, :, t0]
392400
mask_tmp = vol > \
393-
(_nanmean(vol) / self.inputs.global_threshold)
401+
(np.nanmean(vol) / self.inputs.global_threshold)
394402
mask[:, :, :, t0] = mask_tmp
395403
g[t0] = np.nansum(vol * mask_tmp) / np.nansum(mask_tmp)
396404
elif masktype == 'file': # uses a mask image to determine intensity
@@ -400,15 +408,15 @@ def _detect_outliers_core(self, imgfile, motionfile, runidx, cwd=None):
400408
mask = mask > 0.5
401409
for t0 in range(timepoints):
402410
vol = data[:, :, :, t0]
403-
g[t0] = _nanmean(vol[mask])
411+
g[t0] = np.nanmean(vol[mask])
404412
elif masktype == 'thresh': # uses a fixed signal threshold
405413
for t0 in range(timepoints):
406414
vol = data[:, :, :, t0]
407415
mask = vol > self.inputs.mask_threshold
408-
g[t0] = _nanmean(vol[mask])
416+
g[t0] = np.nanmean(vol[mask])
409417
else:
410418
mask = np.ones((x, y, z))
411-
g = _nanmean(data[mask > 0, :], 1)
419+
g = np.nanmean(data[mask > 0, :], 1)
412420

413421
# compute normalized intensity values
414422
gz = signal.detrend(g, axis=0) # detrend the signal

nipype/interfaces/afni/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
TShift, Volreg, Warp, QwarpPlusMinus, Qwarp)
2121
from .svm import (SVMTest, SVMTrain)
2222
from .utils import (ABoverlap, AFNItoNIFTI, Autobox, Axialize, BrickStat,
23-
Bucket, Calc, Cat, CatMatvec, Copy, Dot,
23+
Bucket, Calc, Cat, CatMatvec, CenterMass, Copy, Dot,
2424
Edge3, Eval, FWHMx, MaskTool, Merge, Notes, NwarpApply,
2525
OneDToolPy,
2626
Refit, Resample, TCat, TCatSubBrick, TStat, To3D, Unifize,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
2+
from __future__ import unicode_literals
3+
from ..utils import CenterMass
4+
5+
6+
def test_CenterMass_inputs():
7+
input_map = dict(all_rois=dict(argstr='-all_rois',
8+
),
9+
args=dict(argstr='%s',
10+
),
11+
automask=dict(argstr='-automask',
12+
),
13+
cm_file=dict(argstr='> %s',
14+
descr='File to write center of mass to',
15+
hash_files=False,
16+
keep_extension=False,
17+
name_source='in_file',
18+
name_template='%s_cm.out',
19+
position=-1,
20+
),
21+
environ=dict(nohash=True,
22+
usedefault=True,
23+
),
24+
ignore_exception=dict(nohash=True,
25+
usedefault=True,
26+
),
27+
in_file=dict(argstr='%s',
28+
copyfile=True,
29+
mandatory=True,
30+
position=-2,
31+
),
32+
local_ijk=dict(argstr='-local_ijk',
33+
),
34+
mask_file=dict(argstr='-mask %s',
35+
),
36+
roi_vals=dict(argstr='-roi_vals %s',
37+
),
38+
set_cm=dict(argstr='-set %f %f %f',
39+
),
40+
terminal_output=dict(nohash=True,
41+
),
42+
)
43+
inputs = CenterMass.input_spec()
44+
45+
for key, metadata in list(input_map.items()):
46+
for metakey, value in list(metadata.items()):
47+
assert getattr(inputs.traits()[key], metakey) == value
48+
49+
50+
def test_CenterMass_outputs():
51+
output_map = dict(cm=dict(),
52+
cm_file=dict(),
53+
out_file=dict(),
54+
)
55+
outputs = CenterMass.output_spec()
56+
57+
for key, metadata in list(output_map.items()):
58+
for metakey, value in list(metadata.items()):
59+
assert getattr(outputs.traits()[key], metakey) == value

nipype/interfaces/afni/utils.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,106 @@ def _format_arg(self, name, spec, value):
642642
return spec.argstr%(' '.join([i[0]+' -'+i[1] for i in value]))
643643
return super(CatMatvec, self)._format_arg(name, spec, value)
644644

645+
646+
class CenterMassInputSpec(CommandLineInputSpec):
647+
in_file = File(
648+
desc='input file to 3dCM',
649+
argstr='%s',
650+
position=-2,
651+
mandatory=True,
652+
exists=True,
653+
copyfile=True)
654+
cm_file = File(
655+
name_source='in_file',
656+
name_template='%s_cm.out',
657+
hash_files=False,
658+
keep_extension=False,
659+
descr="File to write center of mass to",
660+
argstr="> %s",
661+
position=-1)
662+
mask_file = File(
663+
desc='Only voxels with nonzero values in the provided mask will be '
664+
'averaged.',
665+
argstr='-mask %s',
666+
exists=True)
667+
automask = traits.Bool(
668+
desc='Generate the mask automatically',
669+
argstr='-automask')
670+
set_cm = traits.Tuple(
671+
(traits.Float(), traits.Float(), traits.Float()),
672+
desc='After computing the center of mass, set the origin fields in '
673+
'the header so that the center of mass will be at (x,y,z) in '
674+
'DICOM coords.',
675+
argstr='-set %f %f %f')
676+
local_ijk = traits.Bool(
677+
desc='Output values as (i,j,k) in local orienation',
678+
argstr='-local_ijk')
679+
roi_vals = traits.List(
680+
traits.Int,
681+
desc='Compute center of mass for each blob with voxel value of v0, '
682+
'v1, v2, etc. This option is handy for getting ROI centers of '
683+
'mass.',
684+
argstr='-roi_vals %s')
685+
all_rois = traits.Bool(
686+
desc='Don\'t bother listing the values of ROIs you want: The program '
687+
'will find all of them and produce a full list',
688+
argstr='-all_rois')
689+
690+
691+
class CenterMassOutputSpec(TraitedSpec):
692+
out_file = File(
693+
exists=True,
694+
desc='output file')
695+
cm_file = File(
696+
desc='file with the center of mass coordinates')
697+
cm = traits.Either(
698+
traits.Tuple(traits.Float(), traits.Float(), traits.Float()),
699+
traits.List(traits.Tuple(traits.Float(), traits.Float(),
700+
traits.Float())),
701+
desc='center of mass')
702+
703+
704+
class CenterMass(AFNICommandBase):
705+
"""Computes center of mass using 3dCM command
706+
707+
.. note::
708+
709+
By default, the output is (x,y,z) values in DICOM coordinates. But
710+
as of Dec, 2016, there are now command line switches for other options.
711+
712+
713+
For complete details, see the `3dCM Documentation.
714+
<https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dCM.html>`_
715+
716+
Examples
717+
========
718+
719+
>>> from nipype.interfaces import afni
720+
>>> cm = afni.CenterMass()
721+
>>> cm.inputs.in_file = 'structural.nii'
722+
>>> cm.inputs.cm_file = 'cm.txt'
723+
>>> cm.inputs.roi_vals = [2, 10]
724+
>>> cm.cmdline # doctest: +ALLOW_UNICODE
725+
'3dCM -roi_vals 2 10 structural.nii > cm.txt'
726+
>>> res = 3dcm.run() # doctest: +SKIP
727+
"""
728+
729+
_cmd = '3dCM'
730+
input_spec = CenterMassInputSpec
731+
output_spec = CenterMassOutputSpec
732+
733+
def _list_outputs(self):
734+
outputs = super(CenterMass, self)._list_outputs()
735+
outputs['out_file'] = os.path.abspath(self.inputs.in_file)
736+
outputs['cm_file'] = os.path.abspath(self.inputs.cm_file)
737+
sout = np.loadtxt(outputs['cm_file']) # pylint: disable=E1101
738+
if len(sout) > 1:
739+
outputs['cm'] = [tuple(s) for s in sout]
740+
else:
741+
outputs['cm'] = tuple(sout)
742+
return outputs
743+
744+
645745
class CopyInputSpec(AFNICommandInputSpec):
646746
in_file = File(
647747
desc='input file to 3dcopy',

0 commit comments

Comments
 (0)