Skip to content

Commit 453bcf9

Browse files
committed
Merge pull request #1039 from lsqshr/enh_add_average_error_2_errormap
ENH: add average error to ErrorMap
2 parents 781b8e0 + e13c90e commit 453bcf9

File tree

3 files changed

+93
-11
lines changed

3 files changed

+93
-11
lines changed

CHANGES

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Next release
22
============
3-
3+
* ENH: Add the average distance to ErrorMap (https://github.com/nipy/nipype/pull/1039)
44
* ENH: Inputs with name_source can be now chained in cascade (https://github.com/nipy/nipype/pull/938)
55
* ENH: Improve JSON interfaces: default settings when reading and consistent output creation
66
when writing (https://github.com/nipy/nipype/pull/1047)

nipype/algorithms/metrics.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ class ErrorMapInputSpec(BaseInterfaceInputSpec):
488488

489489
class ErrorMapOutputSpec(TraitedSpec):
490490
out_map = File(exists=True, desc="resulting error map")
491+
distance = traits.Float(desc="Average distance between volume 1 and 2")
491492

492493

493494
class ErrorMap(BaseInterface):
@@ -506,12 +507,13 @@ class ErrorMap(BaseInterface):
506507
_out_file = ''
507508

508509
def _run_interface(self, runtime):
509-
from scipy.spatial.distance import cdist, pdist
510+
# Get two numpy data matrices
510511
nii_ref = nb.load(self.inputs.in_ref)
511512
ref_data = np.squeeze(nii_ref.get_data())
512513
tst_data = np.squeeze(nb.load(self.inputs.in_tst).get_data())
513514
assert(ref_data.ndim == tst_data.ndim)
514515

516+
# Load mask
515517
comps = 1
516518
mapshape = ref_data.shape
517519

@@ -528,26 +530,29 @@ def _run_interface(self, runtime):
528530
else:
529531
msk = np.ones(shape=mapshape)
530532

533+
# Flatten both volumes and make the pixel differennce
531534
mskvector = msk.reshape(-1)
532535
msk_idxs = np.where(mskvector==1)
533536
refvector = ref_data.reshape(-1,comps)[msk_idxs].astype(np.float32)
534537
tstvector = tst_data.reshape(-1,comps)[msk_idxs].astype(np.float32)
535538
diffvector = (refvector-tstvector)
536539

540+
# Scale the difference
537541
if self.inputs.metric == 'sqeuclidean':
538542
errvector = diffvector**2
543+
if (comps > 1):
544+
errvector = np.sum(errvector, axis=1)
545+
else:
546+
errvector = np.squeeze(errvector)
539547
elif self.inputs.metric == 'euclidean':
540-
X = np.hstack((refvector, tstvector))
541-
errvector = np.linalg.norm(X, axis=1)
548+
errvector = np.linalg.norm(diffvector, axis=1)
542549

543-
if (comps > 1):
544-
errvector = np.sum(errvector, axis=1)
545-
else:
546-
errvector = np.squeeze(errvector)
547-
548-
errvectorexp = np.zeros_like(mskvector)
550+
errvectorexp = np.zeros_like(mskvector, dtype=np.float32) # The default type is uint8
549551
errvectorexp[msk_idxs] = errvector
550552

553+
# Get averaged error
554+
self._distance = np.average(errvector) # Only average the masked voxels
555+
551556
errmap = errvectorexp.reshape(mapshape)
552557

553558
hdr = nii_ref.get_header().copy()
@@ -567,11 +572,12 @@ def _run_interface(self, runtime):
567572
nb.Nifti1Image(errmap.astype(np.float32), nii_ref.get_affine(),
568573
hdr).to_filename(self._out_file)
569574

570-
return runtime
575+
return runtime
571576

572577
def _list_outputs(self):
573578
outputs = self.output_spec().get()
574579
outputs['out_map'] = self._out_file
580+
outputs['distance'] = self._distance
575581
return outputs
576582

577583

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
from nipype.testing import (assert_equal, example_data)
5+
from nipype.algorithms.metrics import ErrorMap
6+
import nibabel as nib
7+
import numpy as np
8+
from tempfile import mkdtemp
9+
import os
10+
11+
def test_errormap():
12+
13+
tempdir = mkdtemp()
14+
# Single-Spectual
15+
# Make two fake 2*2*2 voxel volumes
16+
volume1 = np.array([[[2.0, 8.0], [1.0, 2.0]], [[1.0, 9.0], [0.0, 3.0]]]) # John von Neumann's birthday
17+
volume2 = np.array([[[0.0, 7.0], [2.0, 3.0]], [[1.0, 9.0], [1.0, 2.0]]]) # Alan Turing's birthday
18+
mask = np.array([[[1, 0], [0, 1]], [[1, 0], [0, 1]]])
19+
20+
img1 = nib.Nifti1Image(volume1, np.eye(4))
21+
img2 = nib.Nifti1Image(volume2, np.eye(4))
22+
maskimg = nib.Nifti1Image(mask, np.eye(4))
23+
24+
nib.save(img1, os.path.join(tempdir, 'von.nii.gz'))
25+
nib.save(img2, os.path.join(tempdir, 'alan.nii.gz'))
26+
nib.save(maskimg, os.path.join(tempdir, 'mask.nii.gz'))
27+
28+
29+
# Default metric
30+
errmap = ErrorMap()
31+
errmap.inputs.in_tst = os.path.join(tempdir, 'von.nii.gz')
32+
errmap.inputs.in_ref = os.path.join(tempdir, 'alan.nii.gz')
33+
errmap.out_map = os.path.join(tempdir, 'out_map.nii.gz')
34+
result = errmap.run()
35+
yield assert_equal, result.outputs.distance, 1.125
36+
37+
# Square metric
38+
errmap.inputs.metric = 'sqeuclidean'
39+
result = errmap.run()
40+
yield assert_equal, result.outputs.distance, 1.125
41+
42+
# Linear metric
43+
errmap.inputs.metric = 'euclidean'
44+
result = errmap.run()
45+
yield assert_equal, result.outputs.distance, 0.875
46+
47+
# Masked
48+
errmap.inputs.mask = os.path.join(tempdir, 'mask.nii.gz')
49+
result = errmap.run()
50+
yield assert_equal, result.outputs.distance, 1.0
51+
52+
## Multi-Spectual
53+
volume3 = np.array([[[1.0, 6.0], [0.0, 3.0]], [[1.0, 9.0], [3.0, 6.0]]]) # Raymond Vahan Damadian's birthday
54+
55+
msvolume1 = np.zeros(shape=(2,2,2,2))
56+
msvolume1[:,:,:,0] = volume1
57+
msvolume1[:,:,:,1] = volume3
58+
msimg1 = nib.Nifti1Image(msvolume1, np.eye(4))
59+
60+
msvolume2 = np.zeros(shape=(2,2,2,2))
61+
msvolume2[:,:,:,0] = volume3
62+
msvolume2[:,:,:,1] = volume1
63+
msimg2 = nib.Nifti1Image(msvolume2, np.eye(4))
64+
65+
nib.save(msimg1, os.path.join(tempdir, 'von-ray.nii.gz'))
66+
nib.save(msimg2, os.path.join(tempdir, 'alan-ray.nii.gz'))
67+
68+
errmap.inputs.in_tst = os.path.join(tempdir, 'von-ray.nii.gz')
69+
errmap.inputs.in_ref = os.path.join(tempdir, 'alan-ray.nii.gz')
70+
errmap.inputs.metric = 'sqeuclidean'
71+
result = errmap.run()
72+
yield assert_equal, result.outputs.distance, 5.5
73+
74+
errmap.inputs.metric = 'euclidean'
75+
result = errmap.run()
76+
yield assert_equal, result.outputs.distance, np.float32(1.25 * (2**0.5))

0 commit comments

Comments
 (0)