Repository: nilearn/nilearn

Issue:
concat_imgs throws incomprehensible error for incongruent nifti shapes
I just realized that concat_imgs wants the nifti items to concat to exhibit a same dimension. The code below tried to concatenate `all_frames` (4D) and `block_mean_frame` (3D), yet failed. Perhaps we could improve the error message at this point. What do you guys think?

`````` python
In [158]: all_frames = concat_imgs([all_frames, block_mean_frame])
---------------------------------------------------------------------------
DimensionError                            Traceback (most recent call last)
<ipython-input-158-9e4d213099ce> in <module>()
----> 1 all_frames = concat_imgs([all_frames, block_mean_frame])

/home/parietal/dbzdok/.local/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/_utils/niimg_conversions.pyc in concat_niimgs(niimgs, dtype, ensure_ndim, memory, memory_level, auto_resample, verbose)
    410         # We check the dimensionality of the niimg
    411         try:
--> 412             niimg = check_niimg(niimg, ensure_ndim=ndim)
    413         except DimensionError as exc:
    414             # Keep track of the additional dimension in the error

/home/parietal/dbzdok/.local/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/_utils/niimg_conversions.pyc in check_niimg(niimg, ensure_ndim, atleast_4d, dtype, return_iterator, wildcards)
    253 
    254     if ensure_ndim is not None and len(niimg.shape) != ensure_ndim:
--> 255         raise DimensionError(len(niimg.shape), ensure_ndim)
    256 
    257     if return_iterator:

DimensionError: Data must be a 5D Niimg-like object but you provided a list of 3D images. See http://nilearn.github.io/building_blocks/manipulating_mr_images.html#niimg.```
``````



Discussion:
How can the message be enhanced?

For example, "You provided items of incongruent dimensionality (e.g. [3D 3d 4D]) to concatenate."


Resolving patch:
diff --git a/nilearn/_utils/exceptions.py b/nilearn/_utils/exceptions.py
--- a/nilearn/_utils/exceptions.py
+++ b/nilearn/_utils/exceptions.py
@@ -49,14 +49,18 @@ def increment_stack_counter(self):
 
     @property
     def message(self):
-        return ("Data must be a {0}D Niimg-like object but you provided a "
-                "{1}{2}D image{3}. "
+        return ("Input data has incompatible dimensionality: "
+                "Expected dimension is {0}D and you provided a "
+                "{1}{2}D image{3}{4}. "
                 "See http://nilearn.github.io/manipulating_images/"
                 "manipulating_images.html#niimg."
                 .format(self.required_dimension + self.stack_counter,
                         "list of " * self.stack_counter,
                         self.file_dimension,
-                        "s" * (self.stack_counter != 0)
+                        "s" * (self.stack_counter != 0),
+                        (" (%iD)" %
+                            (self.file_dimension + self.stack_counter)) *
+                        (self.stack_counter > 0)
                         )
                 )
 


Test patch:
diff --git a/nilearn/image/tests/test_image.py b/nilearn/image/tests/test_image.py
--- a/nilearn/image/tests/test_image.py
+++ b/nilearn/image/tests/test_image.py
@@ -304,7 +304,10 @@ def test_concat_imgs():
 
 def test_index_img():
     img_3d = nibabel.Nifti1Image(np.ones((3, 4, 5)), np.eye(4))
-    testing.assert_raises_regex(TypeError, '4D Niimg-like',
+    testing.assert_raises_regex(TypeError,
+                                "Input data has incompatible dimensionality: "
+                                "Expected dimension is 4D and you provided "
+                                "a 3D image.",
                                 image.index_img, img_3d, 0)
 
     affine = np.array([[1., 2., 3., 4.],
@@ -336,7 +339,10 @@ def test_index_img():
 
 def test_iter_img():
     img_3d = nibabel.Nifti1Image(np.ones((3, 4, 5)), np.eye(4))
-    testing.assert_raises_regex(TypeError, '4D Niimg-like',
+    testing.assert_raises_regex(TypeError,
+                                "Input data has incompatible dimensionality: "
+                                "Expected dimension is 4D and you provided "
+                                "a 3D image.",
                                 image.iter_img, img_3d)
 
     affine = np.array([[1., 2., 3., 4.],
diff --git a/nilearn/input_data/tests/test_multi_nifti_masker.py b/nilearn/input_data/tests/test_multi_nifti_masker.py
--- a/nilearn/input_data/tests/test_multi_nifti_masker.py
+++ b/nilearn/input_data/tests/test_multi_nifti_masker.py
@@ -105,7 +105,10 @@ def test_3d_images():
     mask_img_4d = Nifti1Image(np.ones((2, 2, 2, 2), dtype=np.int8),
                               affine=np.diag((4, 4, 4, 1)))
     masker2 = MultiNiftiMasker(mask_img=mask_img_4d)
-    assert_raises_regex(DimensionError, "Data must be a 3D",
+    assert_raises_regex(DimensionError,
+                        "Input data has incompatible dimensionality: "
+                        "Expected dimension is 3D and you provided "
+                        "a 4D image.",
                         masker2.fit)
 
 
diff --git a/nilearn/input_data/tests/test_nifti_labels_masker.py b/nilearn/input_data/tests/test_nifti_labels_masker.py
--- a/nilearn/input_data/tests/test_nifti_labels_masker.py
+++ b/nilearn/input_data/tests/test_nifti_labels_masker.py
@@ -49,7 +49,10 @@ def test_nifti_labels_masker():
 
     # verify that 4D mask arguments are refused
     masker = NiftiLabelsMasker(labels11_img, mask_img=mask_img_4d)
-    testing.assert_raises_regex(DimensionError, "Data must be a 3D",
+    testing.assert_raises_regex(DimensionError,
+                                "Input data has incompatible dimensionality: "
+                                "Expected dimension is 3D and you provided "
+                                "a 4D image.",
                                 masker.fit)
 
     # check exception when transform() called without prior fit()
diff --git a/nilearn/input_data/tests/test_nifti_maps_masker.py b/nilearn/input_data/tests/test_nifti_maps_masker.py
--- a/nilearn/input_data/tests/test_nifti_maps_masker.py
+++ b/nilearn/input_data/tests/test_nifti_maps_masker.py
@@ -144,7 +144,10 @@ def test_nifti_maps_masker_2():
 
     # verify that 4D mask arguments are refused
     masker = NiftiMapsMasker(maps33_img, mask_img=mask_img_4d)
-    testing.assert_raises_regex(DimensionError, "Data must be a 3D",
+    testing.assert_raises_regex(DimensionError,
+                                "Input data has incompatible dimensionality: "
+                                "Expected dimension is 3D and you provided "
+                                "a 4D image.",
                                 masker.fit)
 
     # Test error checking
diff --git a/nilearn/input_data/tests/test_nifti_masker.py b/nilearn/input_data/tests/test_nifti_masker.py
--- a/nilearn/input_data/tests/test_nifti_masker.py
+++ b/nilearn/input_data/tests/test_nifti_masker.py
@@ -198,8 +198,11 @@ def test_5d():
     masker = NiftiMasker(mask_img=mask_img)
     masker.fit()
     testing.assert_raises_regex(
-        DimensionError, 'Data must be a 4D Niimg-like object but you provided'
-        ' a list of 4D images.', masker.transform, data_5d)
+        DimensionError,
+        "Input data has incompatible dimensionality: "
+        "Expected dimension is 4D and you provided "
+        "a list of 4D images \(5D\).",
+        masker.transform, data_5d)
 
 
 def test_sessions():
@@ -313,5 +316,9 @@ def test_filter_and_mask():
     masker = NiftiMasker()
     params = get_params(NiftiMasker, masker)
 
-    assert_raises_regex(DimensionError, "Data must be a 3D", filter_and_mask,
-                         data_img, mask_img, params)
+    assert_raises_regex(DimensionError,
+                        "Input data has incompatible dimensionality: "
+                        "Expected dimension is 3D and you provided "
+                        "a 4D image.",
+                        filter_and_mask,
+                        data_img, mask_img, params)
diff --git a/nilearn/regions/tests/test_signal_extraction.py b/nilearn/regions/tests/test_signal_extraction.py
--- a/nilearn/regions/tests/test_signal_extraction.py
+++ b/nilearn/regions/tests/test_signal_extraction.py
@@ -16,6 +16,10 @@
 from nilearn._utils.testing import write_tmp_imgs, assert_raises_regex
 from nilearn._utils.exceptions import DimensionError
 
+_TEST_DIM_ERROR_MSG = ("Input data has incompatible dimensionality: "
+                       "Expected dimension is 3D and you provided "
+                       "a 4D image")
+
 
 def test_generate_regions_ts():
     """Minimal testing of generate_regions_ts()"""
@@ -110,7 +114,7 @@ def test_signals_extraction_with_labels():
     assert_true(np.all(data.std(axis=-1) > 0))
 
     # verify that 4D label images are refused
-    assert_raises_regex(DimensionError, "Data must be a 3D",
+    assert_raises_regex(DimensionError, _TEST_DIM_ERROR_MSG,
                         signal_extraction.img_to_signals_labels,
                         data_img, labels_4d_img)
 
@@ -136,10 +140,10 @@ def test_signals_extraction_with_labels():
         assert_true(labels_r == list(range(1, 9)))
 
     # Same thing, with mask.
-    assert_raises_regex(DimensionError, "Data must be a 3D",
+    assert_raises_regex(DimensionError, _TEST_DIM_ERROR_MSG,
                         signal_extraction.img_to_signals_labels, data_img,
                         labels_img, mask_img=mask_4d_img)
-    assert_raises_regex(DimensionError, "Data must be a 3D",
+    assert_raises_regex(DimensionError, _TEST_DIM_ERROR_MSG,
                         signal_extraction.signals_to_img_labels, data_img,
                         labels_img, mask_img=mask_4d_img)
 
@@ -227,7 +231,7 @@ def test_signal_extraction_with_maps():
     img = nibabel.Nifti1Image(data, np.eye(4))
 
     # verify that 4d masks are refused
-    assert_raises_regex(TypeError, "Data must be a 3D",
+    assert_raises_regex(TypeError, _TEST_DIM_ERROR_MSG,
                         signal_extraction.img_to_signals_maps, img, maps_img,
                         mask_img=mask_4d_img)
 
diff --git a/nilearn/tests/test_masking.py b/nilearn/tests/test_masking.py
--- a/nilearn/tests/test_masking.py
+++ b/nilearn/tests/test_masking.py
@@ -23,6 +23,10 @@
               else np.version.short_version)
 np_version = distutils.version.LooseVersion(np_version).version
 
+_TEST_DIM_ERROR_MSG = ("Input data has incompatible dimensionality: "
+                       "Expected dimension is 3D and you provided "
+                       "a %s image")
+
 
 def test_compute_epi_mask():
     mean_image = np.ones((9, 9, 3))
@@ -133,7 +137,7 @@ def test_apply_mask():
 
     # veriy that 4D masks are rejected
     mask_img_4d = Nifti1Image(np.ones((40, 40, 40, 2)), np.eye(4))
-    assert_raises_regex(DimensionError, "Data must be a 3D",
+    assert_raises_regex(DimensionError, _TEST_DIM_ERROR_MSG % "4D",
                         masking.apply_mask, data_img, mask_img_4d)
 
     # Check that 3D data is accepted
@@ -146,7 +150,7 @@ def test_apply_mask():
     assert_equal(sorted(data_3d.tolist()), [3., 4., 12.])
 
     # Check data shape and affine
-    assert_raises_regex(DimensionError, "Data must be a 3D",
+    assert_raises_regex(DimensionError, _TEST_DIM_ERROR_MSG % "2D",
                         masking.apply_mask, data_img,
                         Nifti1Image(mask[20, ...], affine))
     assert_raises(ValueError, masking.apply_mask,
diff --git a/nilearn/tests/test_niimg_conversions.py b/nilearn/tests/test_niimg_conversions.py
--- a/nilearn/tests/test_niimg_conversions.py
+++ b/nilearn/tests/test_niimg_conversions.py
@@ -100,7 +100,10 @@ def test_check_niimg_3d():
 
     # Test dimensionality error
     img = Nifti1Image(np.zeros((10, 10, 10)), np.eye(4))
-    assert_raises_regex(TypeError, 'Data must be a 3D',
+    assert_raises_regex(TypeError,
+                        "Input data has incompatible dimensionality: "
+                        "Expected dimension is 3D and you provided a list "
+                        "of 3D images \(4D\).",
                         _utils.check_niimg_3d, [img, img])
 
     # Check that a filename does not raise an error
@@ -156,9 +159,11 @@ def test_check_niimg_4d():
         assert_array_equal(img_1.get_affine(), img_2.get_affine())
 
     # This should raise an error: a 3D img is given and we want a 4D
-    assert_raises_regex(DimensionError, 'Data must be a 4D Niimg-like object '
-                                        'but you provided a 3D',
-                                        _utils.check_niimg_4d, img_3d)
+    assert_raises_regex(DimensionError,
+                        "Input data has incompatible dimensionality: "
+                        "Expected dimension is 4D and you provided a "
+                        "3D image.",
+                        _utils.check_niimg_4d, img_3d)
 
     # Test a Niimg-like object that does not hold a shape attribute
     phony_img = PhonyNiimage()
@@ -187,13 +192,17 @@ def test_check_niimg():
 
     assert_raises_regex(
         DimensionError,
-        'Data must be a 2D Niimg-like object but you provided a list of list '
-        'of list of 3D images.', _utils.check_niimg, img_3_3d, ensure_ndim=2)
+        "Input data has incompatible dimensionality: "
+        "Expected dimension is 2D and you provided "
+        "a list of list of list of 3D images \(6D\)",
+        _utils.check_niimg, img_3_3d, ensure_ndim=2)
 
     assert_raises_regex(
         DimensionError,
-        'Data must be a 4D Niimg-like object but you provided a list of list '
-        'of 4D images.', _utils.check_niimg, img_2_4d, ensure_ndim=4)
+        "Input data has incompatible dimensionality: "
+        "Expected dimension is 4D and you provided "
+        "a list of list of 4D images \(6D\)",
+        _utils.check_niimg, img_2_4d, ensure_ndim=4)
 
 
 def test_check_niimg_wildcards():
@@ -402,15 +411,16 @@ def test_concat_niimgs():
 
     # Regression test for #601. Dimensionality of first image was not checked
     # properly
-    assert_raises_regex(DimensionError, 'Data must be a 4D Niimg-like object but '
-                        'you provided',
+    _dimension_error_msg = ("Input data has incompatible dimensionality: "
+                            "Expected dimension is 4D and you provided "
+                            "a list of 4D images \(5D\)")
+    assert_raises_regex(DimensionError, _dimension_error_msg,
                         _utils.concat_niimgs, [img4d], ensure_ndim=4)
 
     # check basic concatenation with equal shape/affine
     concatenated = _utils.concat_niimgs((img1, img3, img1))
 
-    assert_raises_regex(DimensionError, 'Data must be a 4D Niimg-like object but '
-                        'you provided',
+    assert_raises_regex(DimensionError, _dimension_error_msg,
                         _utils.concat_niimgs, [img1, img4d])
 
     # smoke-test auto_resample