Repository: nilearn/nilearn

Issue:
nilearn.plotting.find_xyz_cut_coords returns inconsistent variable types
I just noticed that `nilearn.plotting.find_xyz_cut_coords` sometimes returns a list() object and sometimes a np.array object. I guess, this should be consistent.



Discussion:

Resolving patch:
diff --git a/nilearn/plotting/find_cuts.py b/nilearn/plotting/find_cuts.py
--- a/nilearn/plotting/find_cuts.py
+++ b/nilearn/plotting/find_cuts.py
@@ -96,7 +96,9 @@ def find_xyz_cut_coords(img, mask_img=None, activation_threshold=None):
         # check against empty mask
         if mask.sum() == 0.:
             warnings.warn(
-                "Provided mask is empty. Returning center of mass instead.")
+                "Could not determine cut coords: "
+                "Provided mask is empty. "
+                "Returning center of mass instead.")
             cut_coords = ndimage.center_of_mass(np.abs(my_map)) + offset
             x_map, y_map, z_map = cut_coords
             return np.asarray(coord_transform(x_map, y_map, z_map,
@@ -106,10 +108,18 @@ def find_xyz_cut_coords(img, mask_img=None, activation_threshold=None):
         mask = mask[slice_x, slice_y, slice_z]
         my_map *= mask
         offset += [slice_x.start, slice_y.start, slice_z.start]
-
     # Testing min and max is faster than np.all(my_map == 0)
     if (my_map.max() == 0) and (my_map.min() == 0):
-        return .5 * np.array(data.shape)
+        warnings.warn(
+            "Could not determine cut coords: "
+            "All values were masked. "
+            "Returning center of mass of unmasked data instead.")
+        # Call center of mass on initial data since my_map is zero.
+        # Therefore, do not add offset to cut_coords.
+        cut_coords = ndimage.center_of_mass(np.abs(data))
+        x_map, y_map, z_map = cut_coords
+        return np.asarray(coord_transform(x_map, y_map, z_map,
+                                          img.affine)).tolist()
     if activation_threshold is None:
         activation_threshold = fast_abs_percentile(my_map[my_map != 0].ravel(),
                                                    80)
@@ -122,7 +132,15 @@ def find_xyz_cut_coords(img, mask_img=None, activation_threshold=None):
     mask = np.abs(my_map) > (activation_threshold - eps)
     # mask may be zero everywhere in rare cases
     if mask.max() == 0:
-        return .5 * np.array(data.shape)
+        warnings.warn(
+            "Could not determine cut coords: "
+            "All voxels were masked by the thresholding. "
+            "Returning the center of mass instead.")
+        cut_coords = ndimage.center_of_mass(np.abs(my_map)) + offset
+        x_map, y_map, z_map = cut_coords
+        return np.asarray(coord_transform(x_map, y_map, z_map,
+                                          img.affine)).tolist()
+
     mask = largest_connected_component(mask)
     slice_x, slice_y, slice_z = ndimage.find_objects(mask)[0]
     my_map = my_map[slice_x, slice_y, slice_z]


Test patch:
diff --git a/nilearn/plotting/tests/test_find_cuts.py b/nilearn/plotting/tests/test_find_cuts.py
--- a/nilearn/plotting/tests/test_find_cuts.py
+++ b/nilearn/plotting/tests/test_find_cuts.py
@@ -40,13 +40,49 @@ def test_find_cut_coords():
 
     # regression test (cf. #473)
     # test case: no data exceeds the activation threshold
+    # Cut coords should be the center of mass rather than
+    # the center of the image (10, 10, 10).
     data = np.ones((36, 43, 36))
     affine = np.eye(4)
     img = nibabel.Nifti1Image(data, affine)
     x, y, z = find_xyz_cut_coords(img, activation_threshold=1.1)
-    np.testing.assert_array_equal(
-        np.array([x, y, z]),
-        0.5 * np.array(data.shape).astype(np.float))
+    np.testing.assert_array_equal([x, y, z],
+                                  [17.5, 21., 17.5])
+
+    data = np.zeros((20, 20, 20))
+    data[4:6, 4:6, 4:6] = 1000
+    img = nibabel.Nifti1Image(data, 2 * np.eye(4))
+    mask_data = np.ones((20, 20, 20), dtype=int)
+    mask_img = nibabel.Nifti1Image(mask_data, 2 * np.eye(4))
+    cut_coords = find_xyz_cut_coords(img, mask_img=mask_img)
+    np.testing.assert_array_equal(cut_coords,
+                                  [9., 9., 9.])
+
+    # Check that a warning is given when all values are masked
+    # and that the center of mass is returned
+    img = nibabel.Nifti1Image(data, np.eye(4))
+    mask_data[np.argwhere(data == 1000)] = 0
+    mask_img = nibabel.Nifti1Image(mask_data, np.eye(4))
+    with pytest.warns(UserWarning,
+                      match=("Could not determine cut coords: "
+                             "All values were masked.")):
+        cut_coords = find_xyz_cut_coords(img, mask_img=mask_img)
+    np.testing.assert_array_equal(cut_coords,
+                                  [4.5, 4.5, 4.5])
+
+    # Check that a warning is given when all values are masked
+    # due to thresholding and that the center of mass is returned
+    mask_data = np.ones((20, 20, 20), dtype=int)
+    mask_img = nibabel.Nifti1Image(mask_data, np.eye(4))
+    with pytest.warns(UserWarning,
+                      match=("Could not determine cut coords: "
+                             "All voxels were masked by the thresholding.")):
+        cut_coords = find_xyz_cut_coords(img,
+                                         mask_img=mask_img,
+                                         activation_threshold=10**3)
+    np.testing.assert_array_equal(cut_coords,
+                                  [4.5, 4.5, 4.5])
+
 
     # regression test (cf. #922)
     # pseudo-4D images as input (i.e., X, Y, Z, 1)