Skip to content

Commit d3d75f7

Browse files
committed
Merge remote-tracking branch 'upstream/master' into enh/activations-count-map
2 parents f81c1cf + a1ab518 commit d3d75f7

File tree

97 files changed

+2604
-1296
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+2604
-1296
lines changed

.circleci/config.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ _get_base_image: &get_base_image
3636
echo "Pulling base image ..."
3737
docker pull nipype/nipype:base
3838
elif [ "$GET_BASE" == "BUILD" ]; then
39-
e=1 && for i in {1..5}; do
40-
docker build -t nipype/nipype:base - < docker/Dockerfile.base && e=0 && break || sleep 15
41-
done && [ "$e" -eq "0" ]
39+
tools/retry_cmd.sh -n 5 -s 15 \
40+
docker build -t nipype/nipype:base - < docker/Dockerfile.base
4241
else
4342
echo "Error: method to get base image not understood"
4443
exit 1
@@ -48,22 +47,20 @@ _build_main_image_py36: &build_main_image_py36
4847
name: Build main image (py36)
4948
no_output_timeout: 60m
5049
command: |
51-
e=1 && for i in {1..5}; do
50+
tools/retry_cmd.sh -n 5 -s 15 \
5251
docker build \
5352
--rm=false \
5453
--tag nipype/nipype:latest \
5554
--tag nipype/nipype:py36 \
5655
--build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
5756
--build-arg VCS_REF="$(git rev-parse --short HEAD)" \
5857
--build-arg VERSION="${CIRCLE_TAG}" /home/circleci/nipype \
59-
&& e=0 && break || sleep 15
60-
done && [ "$e" -eq "0" ]
6158
6259
_build_main_image_py27: &build_main_image_py27
6360
name: Build main image (py27)
6461
no_output_timeout: 60m
6562
command: |
66-
e=1 && for i in {1..5}; do
63+
tools/retry_cmd.sh -n 5 -s 15 \
6764
docker build \
6865
--rm=false \
6966
--tag nipype/nipype:py27 \
@@ -72,8 +69,6 @@ _build_main_image_py27: &build_main_image_py27
7269
--build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
7370
--build-arg VCS_REF="$(git rev-parse --short HEAD)" \
7471
--build-arg VERSION="${CIRCLE_TAG}-py27" /home/circleci/nipype \
75-
&& e=0 && break || sleep 15
76-
done && [ "$e" -eq "0" ]
7772
7873
_download_test_data: &_download_test_data
7974
name: Download test data

nipype/algorithms/metrics.py

Lines changed: 68 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
from scipy.ndimage.measurements import center_of_mass, label
2020

2121
from .. import config, logging
22-
from ..utils.misc import package_check
2322

24-
from ..interfaces.base import (BaseInterface, traits, TraitedSpec, File,
25-
InputMultiPath, BaseInterfaceInputSpec,
26-
isdefined)
27-
from ..utils import NUMPY_MMAP
23+
from ..interfaces.base import (
24+
SimpleInterface, BaseInterface, traits, TraitedSpec, File,
25+
InputMultiPath, BaseInterfaceInputSpec,
26+
isdefined)
27+
from ..interfaces.nipy.base import NipyBaseInterface
2828

2929
iflogger = logging.getLogger('interface')
3030

@@ -383,6 +383,7 @@ class FuzzyOverlapInputSpec(BaseInterfaceInputSpec):
383383
File(exists=True),
384384
mandatory=True,
385385
desc='Test image. Requires the same dimensions as in_ref.')
386+
in_mask = File(exists=True, desc='calculate overlap only within mask')
386387
weighting = traits.Enum(
387388
'none',
388389
'volume',
@@ -403,10 +404,6 @@ class FuzzyOverlapInputSpec(BaseInterfaceInputSpec):
403404
class FuzzyOverlapOutputSpec(TraitedSpec):
404405
jaccard = traits.Float(desc='Fuzzy Jaccard Index (fJI), all the classes')
405406
dice = traits.Float(desc='Fuzzy Dice Index (fDI), all the classes')
406-
diff_file = File(
407-
exists=True,
408-
desc=
409-
'resulting difference-map of all classes, using the chosen weighting')
410407
class_fji = traits.List(
411408
traits.Float(),
412409
desc='Array containing the fJIs of each computed class')
@@ -415,7 +412,7 @@ class FuzzyOverlapOutputSpec(TraitedSpec):
415412
desc='Array containing the fDIs of each computed class')
416413

417414

418-
class FuzzyOverlap(BaseInterface):
415+
class FuzzyOverlap(SimpleInterface):
419416
"""Calculates various overlap measures between two maps, using the fuzzy
420417
definition proposed in: Crum et al., Generalized Overlap Measures for
421418
Evaluation and Validation in Medical Image Analysis, IEEE Trans. Med.
@@ -439,77 +436,75 @@ class FuzzyOverlap(BaseInterface):
439436
output_spec = FuzzyOverlapOutputSpec
440437

441438
def _run_interface(self, runtime):
442-
ncomp = len(self.inputs.in_ref)
443-
assert (ncomp == len(self.inputs.in_tst))
444-
weights = np.ones(shape=ncomp)
445-
446-
img_ref = np.array([
447-
nb.load(fname, mmap=NUMPY_MMAP).get_data()
448-
for fname in self.inputs.in_ref
449-
])
450-
img_tst = np.array([
451-
nb.load(fname, mmap=NUMPY_MMAP).get_data()
452-
for fname in self.inputs.in_tst
453-
])
454-
455-
msk = np.sum(img_ref, axis=0)
456-
msk[msk > 0] = 1.0
457-
tst_msk = np.sum(img_tst, axis=0)
458-
tst_msk[tst_msk > 0] = 1.0
459-
460-
# check that volumes are normalized
461-
# img_ref[:][msk>0] = img_ref[:][msk>0] / (np.sum( img_ref, axis=0 ))[msk>0]
462-
# img_tst[tst_msk>0] = img_tst[tst_msk>0] / np.sum( img_tst, axis=0 )[tst_msk>0]
463-
464-
self._jaccards = []
465-
volumes = []
466-
467-
diff_im = np.zeros(img_ref.shape)
468-
469-
for ref_comp, tst_comp, diff_comp in zip(img_ref, img_tst, diff_im):
470-
num = np.minimum(ref_comp, tst_comp)
471-
ddr = np.maximum(ref_comp, tst_comp)
472-
diff_comp[ddr > 0] += 1.0 - (num[ddr > 0] / ddr[ddr > 0])
473-
self._jaccards.append(np.sum(num) / np.sum(ddr))
474-
volumes.append(np.sum(ref_comp))
475-
476-
self._dices = 2.0 * (np.array(self._jaccards) /
477-
(np.array(self._jaccards) + 1.0))
439+
# Load data
440+
refdata = nb.concat_images(self.inputs.in_ref).get_data()
441+
tstdata = nb.concat_images(self.inputs.in_tst).get_data()
442+
443+
# Data must have same shape
444+
if not refdata.shape == tstdata.shape:
445+
raise RuntimeError(
446+
'Size of "in_tst" %s must match that of "in_ref" %s.' %
447+
(tstdata.shape, refdata.shape))
478448

449+
ncomp = refdata.shape[-1]
450+
451+
# Load mask
452+
mask = np.ones_like(refdata, dtype=bool)
453+
if isdefined(self.inputs.in_mask):
454+
mask = nb.load(self.inputs.in_mask).get_data()
455+
mask = mask > 0
456+
mask = np.repeat(mask[..., np.newaxis], ncomp, -1)
457+
assert mask.shape == refdata.shape
458+
459+
# Drop data outside mask
460+
refdata = refdata[mask]
461+
tstdata = tstdata[mask]
462+
463+
if np.any(refdata < 0.0):
464+
iflogger.warning('Negative values encountered in "in_ref" input, '
465+
'taking absolute values.')
466+
refdata = np.abs(refdata)
467+
468+
if np.any(tstdata < 0.0):
469+
iflogger.warning('Negative values encountered in "in_tst" input, '
470+
'taking absolute values.')
471+
tstdata = np.abs(tstdata)
472+
473+
if np.any(refdata > 1.0):
474+
iflogger.warning('Values greater than 1.0 found in "in_ref" input, '
475+
'scaling values.')
476+
refdata /= refdata.max()
477+
478+
if np.any(tstdata > 1.0):
479+
iflogger.warning('Values greater than 1.0 found in "in_tst" input, '
480+
'scaling values.')
481+
tstdata /= tstdata.max()
482+
483+
numerators = np.atleast_2d(
484+
np.minimum(refdata, tstdata).reshape((-1, ncomp)))
485+
denominators = np.atleast_2d(
486+
np.maximum(refdata, tstdata).reshape((-1, ncomp)))
487+
488+
jaccards = numerators.sum(axis=0) / denominators.sum(axis=0)
489+
490+
# Calculate weights
491+
weights = np.ones_like(jaccards, dtype=float)
479492
if self.inputs.weighting != "none":
480-
weights = 1.0 / np.array(volumes)
493+
volumes = np.sum((refdata + tstdata) > 0, axis=1).reshape((-1, ncomp))
494+
weights = 1.0 / volumes
481495
if self.inputs.weighting == "squared_vol":
482496
weights = weights**2
483497

484498
weights = weights / np.sum(weights)
499+
dices = 2.0 * jaccards / (jaccards + 1.0)
485500

486-
setattr(self, '_jaccard', np.sum(weights * self._jaccards))
487-
setattr(self, '_dice', np.sum(weights * self._dices))
488-
489-
diff = np.zeros(diff_im[0].shape)
490-
491-
for w, ch in zip(weights, diff_im):
492-
ch[msk == 0] = 0
493-
diff += w * ch
494-
495-
nb.save(
496-
nb.Nifti1Image(diff,
497-
nb.load(self.inputs.in_ref[0]).affine,
498-
nb.load(self.inputs.in_ref[0]).header),
499-
self.inputs.out_file)
500-
501+
# Fill-in the results object
502+
self._results['jaccard'] = float(weights.dot(jaccards))
503+
self._results['dice'] = float(weights.dot(dices))
504+
self._results['class_fji'] = [float(v) for v in jaccards]
505+
self._results['class_fdi'] = [float(v) for v in dices]
501506
return runtime
502507

503-
def _list_outputs(self):
504-
outputs = self._outputs().get()
505-
for method in ("dice", "jaccard"):
506-
outputs[method] = getattr(self, '_' + method)
507-
# outputs['volume_difference'] = self._volume
508-
outputs['diff_file'] = os.path.abspath(self.inputs.out_file)
509-
outputs['class_fji'] = np.array(self._jaccards).astype(float).tolist()
510-
outputs['class_fdi'] = self._dices.astype(float).tolist()
511-
return outputs
512-
513508

514509
class ErrorMapInputSpec(BaseInterfaceInputSpec):
515510
in_ref = File(
@@ -651,7 +646,7 @@ class SimilarityOutputSpec(TraitedSpec):
651646
traits.Float(desc="Similarity between volume 1 and 2, frame by frame"))
652647

653648

654-
class Similarity(BaseInterface):
649+
class Similarity(NipyBaseInterface):
655650
"""Calculates similarity between two 3D or 4D volumes. Both volumes have to be in
656651
the same coordinate system, same space within that coordinate system and
657652
with the same voxel dimensions.
@@ -674,19 +669,8 @@ class Similarity(BaseInterface):
674669

675670
input_spec = SimilarityInputSpec
676671
output_spec = SimilarityOutputSpec
677-
_have_nipy = True
678-
679-
def __init__(self, **inputs):
680-
try:
681-
package_check('nipy')
682-
except Exception:
683-
self._have_nipy = False
684-
super(Similarity, self).__init__(**inputs)
685672

686673
def _run_interface(self, runtime):
687-
if not self._have_nipy:
688-
raise RuntimeError('nipy is not installed')
689-
690674
from nipy.algorithms.registration.histogram_registration import HistogramRegistration
691675
from nipy.algorithms.registration.affine import Affine
692676

nipype/algorithms/misc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from ..interfaces.base import (
2727
BaseInterface, traits, TraitedSpec, File, InputMultiPath, OutputMultiPath,
2828
BaseInterfaceInputSpec, isdefined, DynamicTraitedSpec, Undefined)
29-
from ..utils.filemanip import fname_presuffix, split_filename, filename_to_list
29+
from ..utils.filemanip import fname_presuffix, split_filename, ensure_list
3030
from ..utils import NUMPY_MMAP
3131

3232
from . import confounds
@@ -1479,7 +1479,7 @@ def _gen_fname(self, suffix, idx=None, ext=None):
14791479
def _run_interface(self, runtime):
14801480
total = None
14811481
self._median_files = []
1482-
for idx, fname in enumerate(filename_to_list(self.inputs.in_files)):
1482+
for idx, fname in enumerate(ensure_list(self.inputs.in_files)):
14831483
img = nb.load(fname, mmap=NUMPY_MMAP)
14841484
data = np.median(img.get_data(), axis=3)
14851485
if self.inputs.median_per_file:

nipype/algorithms/modelgen.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from ..interfaces.base import (BaseInterface, TraitedSpec, InputMultiPath,
2727
traits, File, Bunch, BaseInterfaceInputSpec,
2828
isdefined)
29-
from ..utils.filemanip import filename_to_list
29+
from ..utils.filemanip import ensure_list
3030
from ..utils.misc import normalize_mc_params
3131
from .. import config, logging
3232
iflogger = logging.getLogger('interface')
@@ -383,7 +383,7 @@ def _generate_standard_design(self,
383383
if outliers is not None:
384384
for i, out in enumerate(outliers):
385385
numscans = 0
386-
for f in filename_to_list(sessinfo[i]['scans']):
386+
for f in ensure_list(sessinfo[i]['scans']):
387387
shape = load(f, mmap=NUMPY_MMAP).shape
388388
if len(shape) == 3 or shape[3] == 1:
389389
iflogger.warning('You are using 3D instead of 4D '
@@ -580,7 +580,7 @@ def _generate_design(self, infolist=None):
580580
else:
581581
infolist = gen_info(self.inputs.event_files)
582582
concatlist, nscans = self._concatenate_info(infolist)
583-
functional_runs = [filename_to_list(self.inputs.functional_runs)]
583+
functional_runs = [ensure_list(self.inputs.functional_runs)]
584584
realignment_parameters = []
585585
if isdefined(self.inputs.realignment_parameters):
586586
realignment_parameters = []

nipype/algorithms/rapidart.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from ..interfaces.base import (BaseInterface, traits, InputMultiPath,
2929
OutputMultiPath, TraitedSpec, File,
3030
BaseInterfaceInputSpec, isdefined)
31-
from ..utils.filemanip import filename_to_list, save_json, split_filename
31+
from ..utils.filemanip import ensure_list, save_json, split_filename
3232
from ..utils.misc import find_indices, normalize_mc_params
3333
from .. import logging, config
3434
iflogger = logging.getLogger('interface')
@@ -376,7 +376,7 @@ def _list_outputs(self):
376376
outputs['displacement_files'] = []
377377
if isdefined(self.inputs.save_plot) and self.inputs.save_plot:
378378
outputs['plot_files'] = []
379-
for i, f in enumerate(filename_to_list(self.inputs.realigned_files)):
379+
for i, f in enumerate(ensure_list(self.inputs.realigned_files)):
380380
(outlierfile, intensityfile, statsfile, normfile, plotfile,
381381
displacementfile, maskfile) = \
382382
self._get_output_filenames(f, os.getcwd())
@@ -616,8 +616,8 @@ def _detect_outliers_core(self, imgfile, motionfile, runidx, cwd=None):
616616
def _run_interface(self, runtime):
617617
"""Execute this module.
618618
"""
619-
funcfilelist = filename_to_list(self.inputs.realigned_files)
620-
motparamlist = filename_to_list(self.inputs.realignment_parameters)
619+
funcfilelist = ensure_list(self.inputs.realigned_files)
620+
motparamlist = ensure_list(self.inputs.realignment_parameters)
621621
for i, imgf in enumerate(funcfilelist):
622622
self._detect_outliers_core(
623623
imgf, motparamlist[i], i, cwd=os.getcwd())

nipype/algorithms/tests/test_auto_FuzzyOverlap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def test_FuzzyOverlap_inputs():
1010
nohash=True,
1111
usedefault=True,
1212
),
13+
in_mask=dict(),
1314
in_ref=dict(mandatory=True, ),
1415
in_tst=dict(mandatory=True, ),
1516
out_file=dict(usedefault=True, ),
@@ -25,7 +26,6 @@ def test_FuzzyOverlap_outputs():
2526
class_fdi=dict(),
2627
class_fji=dict(),
2728
dice=dict(),
28-
diff_file=dict(),
2929
jaccard=dict(),
3030
)
3131
outputs = FuzzyOverlap.output_spec()

0 commit comments

Comments
 (0)