Repository: nilearn/nilearn

Issue:
Offer clip option in resample_img
Currently using `resample_img` with `interpolation='continuous'` (default) can lead to values outside of the input range (e.g. getting negative values). `skimage` has a [`clip` option](http://scikit-image.org/docs/dev/api/skimage.transform.html?highlight=resize#skimage.transform.resize) which states:
>Whether to clip the output to the range of values of the input image. This is enabled by default, since higher order interpolation may produce values outside the given input range.


Discussion:
Sounds like a good idea. It is indeed a behavior that surprises people.
Do you mind doing a PR?

The question is: do we want it on or not by default?


Resolving patch:
diff --git a/nilearn/image/resampling.py b/nilearn/image/resampling.py
--- a/nilearn/image/resampling.py
+++ b/nilearn/image/resampling.py
@@ -299,7 +299,7 @@ def _resample_one_img(data, A, b, target_shape,
 
 
 def resample_img(img, target_affine=None, target_shape=None,
-                 interpolation='continuous', copy=True, order="F"):
+                 interpolation='continuous', copy=True, order="F", clip=False):
     """Resample a Niimg-like object
 
     Parameters
@@ -331,6 +331,11 @@ def resample_img(img, target_affine=None, target_shape=None,
         Data ordering in output array. This function is slightly faster with
         Fortran ordering.
 
+    clip: bool, optional
+        If False (default) no clip is preformed.
+        If True all resampled image values above max(img) and under min(img) are
+        clipped to min(img) and max(img)
+
     Returns
     -------
     resampled: nibabel.Nifti1Image
@@ -542,11 +547,16 @@ def resample_img(img, target_affine=None, target_shape=None,
                           out=resampled_data[all_img + ind],
                           copy=not input_img_is_string)
 
+    if clip:
+        # force resampled data to have a range contained in the original data
+        # preventing ringing artefact
+        resampled_data.clip(data.min(), data.max(), out=resampled_data)
+
     return new_img_like(img, resampled_data, target_affine)
 
 
 def resample_to_img(source_img, target_img,
-                    interpolation='continuous', copy=True, order='F'):
+                    interpolation='continuous', copy=True, order='F', clip=False):
     """Resample a Niimg-like source image on a target Niimg-like image
     (no registration is performed: the image should already be aligned).
 
@@ -575,6 +585,11 @@ def resample_to_img(source_img, target_img,
         Data ordering in output array. This function is slightly faster with
         Fortran ordering.
 
+    clip: bool, optional
+        If False (default) no clip is preformed.
+        If True all resampled image values above max(img) and under min(img) are
+        clipped to min(img) and max(img)
+
     Returns
     -------
     resampled: nibabel.Nifti1Image
@@ -597,7 +612,7 @@ def resample_to_img(source_img, target_img,
     return resample_img(source_img,
                         target_affine=target.affine,
                         target_shape=target_shape,
-                        interpolation=interpolation, copy=copy, order=order)
+                        interpolation=interpolation, copy=copy, order=order, clip=clip)
 
 
 def reorder_img(img, resample=None):


Test patch:
diff --git a/nilearn/image/tests/test_resampling.py b/nilearn/image/tests/test_resampling.py
--- a/nilearn/image/tests/test_resampling.py
+++ b/nilearn/image/tests/test_resampling.py
@@ -521,6 +521,30 @@ def test_resample_to_img():
     np.testing.assert_almost_equal(downsampled,
                                    result_img.get_data()[:x, :y, :z, ...])
 
+def test_resample_clip():
+    # Resample and image and get larger and smaller
+    # value than in the original. Use clip to get rid of these images
+
+    shape = (6, 3, 6)
+    data = np.zeros(shape=shape)
+    data[1:-2, 1:-1, 1:-2] = 1
+
+    source_affine = np.diag((2, 2, 2, 1))
+    source_img = Nifti1Image(data, source_affine)
+
+    target_affine = np.eye(4)
+    no_clip_data = resample_img(source_img, target_affine).get_data()
+    clip_data = resample_img(source_img,
+                             target_affine, clip=True).get_data()
+
+    not_clip = np.where((no_clip_data > data.min()) & (no_clip_data < data.max()))
+
+    assert_true(np.any(no_clip_data > data.max()))
+    assert_true(np.any(no_clip_data < data.min()))
+    assert_true(np.all(clip_data <= data.max()))
+    assert_true(np.all(clip_data >= data.min()))
+    assert_array_equal(no_clip_data[not_clip], clip_data[not_clip])
+
 
 def test_reorder_img():
     # We need to test on a square array, as rotation does not change