Repository: nilearn/nilearn

Issue:
check for proper coords on plot_stat_map
currently plot_stat_map will crash if a scalar is passed to cut_coords in single-axis display mode:

```
Traceback (most recent call last):
  File "mk_tsne_figure.py", line 57, in <module>
    draw_cross=False,display_mode='z',colorbar=False,cut_coords=0.)
  File "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/img_plotting.py", line 891, in plot_stat_map
    **kwargs)
  File "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/img_plotting.py", line 163, in _plot_img_with_bg
    colorbar=colorbar)
  File "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/displays.py", line 400, in init_with_figure
    cut_coords = cls.find_cut_coords(img, threshold, cut_coords)
  File "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/displays.py", line 950, in find_cut_coords
    n_cuts=cut_coords)
  File "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/find_cuts.py", line 213, in find_cut_slices
    spacing = max(int(.5 / n_cuts * data.shape[axis]), 1)
ZeroDivisionError: float division by zero
```

should check to see if the arguments to cut_coords are a list, and coerce single coord into a list if passed as a scalar.



Discussion:
Russ,

cut_coords : None, a tuple of floats, or an integer
            The MNI coordinates of the point where the cut is performed
            If display_mode is 'ortho', this should be a 3-tuple: (x, y, z)
            For display_mode == 'x', 'y', or 'z', then these are the
            coordinates of each cut in the corresponding direction.
            If None is given, the cuts is calculated automaticaly.
            If display_mode is 'x', 'y' or 'z', cut_coords can be an integer,
            in which case it specifies the number of cuts to perform

If _cut_coords_ is a scalar (as in your example), then it's expected to be
and integer representing the number of cuts you want. We can't do the
coercing automatically, as this would run into conflicts with the rest of
the logic. The error you're getting is because the 0 you passed for
_cut_coords_ is being interpreted as number of slices (_n_cuts_ in your
traceback), leading to a zero-division. The latter tells us there is a
stealth bug in the _find_cuts_ function: it should sanitize the value
_n_cuts_ param and throw a meaningful error in case invalid (for example,
in case it's _0_).

thanks - yes, better error reporting sounds like a good solution.

> On Sep 26, 2015, at 7:55 PM, DOHMATOB Elvis notifications@github.com wrote:
> 
> Russ,
> 
> _cut_coords : None, a tuple of floats, or an integer_
> - The MNI coordinates of the point where the cut is performed*
> - If display_mode is 'ortho', this should be a 3-tuple: (x, y,
>   z)*
> - For display_mode == 'x', 'y', or 'z', then these are the*
> - coordinates of each cut in the corresponding direction.*
> - If None is given, the cuts is calculated automaticaly.*
> - If display_mode is 'x', 'y' or 'z', cut_coords can be an
>   integer,*
> - in which case it specifies the number of cuts to perform*
> 
> If _cut_coords_ is a scalar (as in your example), then it's expected to be
> and integer representing the number of cuts you want. We can't do the
> coercing automatically, as this would run into conflicts with the rest of
> the logic. The error you're getting is because the 0 you passed for
> _cut_coords_ is being interpreted as number of slices (_n_cuts_ in your
> traceback), leading to a zero-division. The latter tells us there is a
> stealth bug in the _find_cuts_ function: it should sanitize the value
> _n_cuts_ param and throw a meaningful error in case invalid (for example,
> in case it's _0_).
> 
> On Sat, Sep 26, 2015 at 6:55 PM, Russ Poldrack notifications@github.com
> wrote:
> 
> > currently plot_stat_map will crash if a scalar is passed to cut_coords in
> > single-axis display mode:
> > 
> > Traceback (most recent call last):
> > File "mk_tsne_figure.py", line 57, in
> > draw_cross=False,display_mode='z',colorbar=False,cut_coords=0.)
> > File
> > "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/img_plotting.py",
> > line 891, in plot_stat_map
> > **kwargs)
> > File
> > "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/img_plotting.py",
> > line 163, in _plot_img_with_bg
> > colorbar=colorbar)
> > File
> > "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/displays.py",
> > line 400, in init_with_figure
> > cut_coords = cls.find_cut_coords(img, threshold, cut_coords)
> > File
> > "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/displays.py",
> > line 950, in find_cut_coords
> > n_cuts=cut_coords)
> > File
> > "/Users/poldrack/anaconda/lib/python2.7/site-packages/nilearn-0.1.5.dev0-py2.7.egg/nilearn/plotting/find_cuts.py",
> > line 213, in find_cut_slices
> > spacing = max(int(.5 / n_cuts \* data.shape[axis]), 1)
> > ZeroDivisionError: float division by zero
> > 
> > should check to see if the arguments to cut_coords are a list, and coerce
> > single coord into a list if passed as a scalar.
> > 
> > —
> > Reply to this email directly or view it on GitHub
> > https://github.com/nilearn/nilearn/issues/784.
> 
> ## 
> 
> DED
> —
> Reply to this email directly or view it on GitHub https://github.com/nilearn/nilearn/issues/784#issuecomment-143514209.

---

Russell A. Poldrack
Professor of Psychology
Bldg. 420, Jordan Hall
Stanford University
Stanford, CA 94305

poldrack@stanford.edu
http://www.poldracklab.org/

It looks like we should check that n_cuts is an integer > 0 in nilearn.plotting.find_cuts.find_cut_slices indeed and raise a meaningful error message if not.

@KamalakerDadi, if you have some spare time, that shouldn't be too hard to do.


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
@@ -158,7 +158,7 @@ def _transform_cut_coords(cut_coords, direction, affine):
     return np.atleast_1d(cut_coords)
 
 
-def find_cut_slices(img, direction='z', n_cuts=12, spacing='auto'):
+def find_cut_slices(img, direction='z', n_cuts=7, spacing='auto'):
     """ Find 'good' cross-section slicing positions along a given axis.
 
     Parameters
@@ -167,7 +167,7 @@ def find_cut_slices(img, direction='z', n_cuts=12, spacing='auto'):
         the brain map
     direction: string, optional (default "z")
         sectional direction; possible values are "x", "y", or "z"
-    n_cuts: int, optional (default 12)
+    n_cuts: int, optional (default 7)
         number of cuts in the plot
     spacing: 'auto' or int, optional (default 'auto')
         minimum spacing between cuts (in voxels, not milimeters)
@@ -196,6 +196,11 @@ def find_cut_slices(img, direction='z', n_cuts=12, spacing='auto'):
     orig_data = np.abs(img.get_data())
     this_shape = orig_data.shape[axis]
 
+    if not isinstance(n_cuts, numbers.Number):
+        raise ValueError("The number of cuts (n_cuts) must be an integer "
+                         "greater than or equal to 1. "
+                         "You provided a value of n_cuts=%s. " % n_cuts)
+
     # BF issue #575: Return all the slices along and axis if this axis
     # is the display mode and there are at least as many requested
     # n_slices as there are slices.
@@ -214,15 +219,13 @@ def find_cut_slices(img, direction='z', n_cuts=12, spacing='auto'):
     # during given input value "n_cuts"
     epsilon = np.finfo(np.float32).eps
     difference = abs(round(n_cuts) - n_cuts)
-    message = ("The number of cuts in the given direction %s must be "
-               "an integer which should be greater than 0 and "
-               "less than or equal to %d. You provided n_cuts=%s " % (
-                   direction, this_shape, n_cuts))
-    if not isinstance(n_cuts, numbers.Number) or n_cuts < epsilon:
+    if round(n_cuts) < 1. or difference > epsilon:
+        message = ("Image has %d slices in direction %s. "
+                   "Therefore, the number of cuts must be between 1 and %d. "
+                   "You provided n_cuts=%s " % (
+                       this_shape, direction, this_shape, n_cuts))
         raise ValueError(message)
-    elif not isinstance(n_cuts, int) or difference != 0.:
-        warnings.warn("'n_cuts' is expected as integer but given as "
-                      "float value=%s. Rounding to integer." % n_cuts)
+    else:
         n_cuts = int(round(n_cuts))
 
     if spacing == 'auto':


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
@@ -79,20 +79,28 @@ def test_validity_of_ncuts_error_in_find_cut_slices():
     x_map, y_map, z_map = 25, 5, 20
     data[x_map - 15:x_map + 15, y_map - 3:y_map + 3, z_map - 10:z_map + 10] = 1
     img = nibabel.Nifti1Image(data, affine)
-    for direction, n_cuts in zip('xyz', (0, -2, -10.00034)):
-        message = ("The number of cuts in the given direction %s must be an "
-                   "integer which should be greater than 0 and less than "
-                   "or equal to %d. You provided n_cuts=%s" % (
-                       direction, data.shape[0], n_cuts))
+    direction = 'z'
+    for n_cuts in (0, -2, -10.00034, 0.999999, 0.4, 0.11111111):
+        message = ("Image has %d slices in direction %s. Therefore, the number "
+                   "of cuts must be between 1 and %d. You provided n_cuts=%s " % (
+                       data.shape[0], direction, data.shape[0], n_cuts))
         assert_raises_regex(ValueError,
                             message,
                             find_cut_slices,
-                            img, direction=direction,
-                            n_cuts=n_cuts)
+                            img, n_cuts=n_cuts)
+
+
+def test_passing_of_ncuts_in_find_cut_slices():
+    data = np.zeros((50, 50, 50))
+    affine = np.eye(4)
+    x_map, y_map, z_map = 25, 5, 20
+    data[x_map - 15:x_map + 15, y_map - 3:y_map + 3, z_map - 10:z_map + 10] = 1
+    img = nibabel.Nifti1Image(data, affine)
     # smoke test to check if it rounds the floating point inputs
-    n_cuts = [0.999999, 9.99999, 1, 5., 10.0000045]
-    for n_cut in n_cuts:
-        find_cut_slices(img, direction='x', n_cuts=n_cut)
+    for n_cuts in (1, 5., 0.9999999, 2.000000004):
+        cut1 = find_cut_slices(img, direction='x', n_cuts=n_cuts)
+        cut2 = find_cut_slices(img, direction='x', n_cuts=round(n_cuts))
+        np.testing.assert_array_equal(cut1, cut2)
 
 
 def test_singleton_ax_dim():