Repository: nilearn/nilearn

Issue:
resample_img segmentation fault
Migrated from #322.

A simple example reproducing the problem:

``` python

from nilearn.image import resample_img
import numpy as np
from nibabel import Nifti1Image


def test(fourth_dim):
    shape_in = (64, 64, 64)
    aff_in = np.diag([2., 2., 2., 1.])
    aff_out = np.diag([3., 3., 3., 1.])
    data = np.ones(shape_in + (fourth_dim, ), dtype=np.float64)

    img_in = Nifti1Image(data, aff_in)

    img_out = resample_img(img_in,
                           target_affine=aff_out,
                           interpolation='nearest')

    print 'shape: ', img_out.shape

test(1024)  # works fine on my machine
test(1025)  # segmentation fault
```



Discussion:
Cool!

I confirm -- 1024 works, 1025 segfaults. Thanks for isolating!

`np.log2(64 * 64 * 64 * 1024) == 28`

I further tracked down the problem to scipy/ndimage/interpolation.py l.399 (scipy 0.14 on my machine)

``` python

_nd_image.zoom_shift(filtered, matrix, offset, output, order,
                             mode, cval)
```

and a stand-alone example only using scipy:

``` python

import numpy as np
from scipy import ndimage

def test(fourth_dim):
    shape_in =(64, 64, 64)
    shape_out =(43, 43, 43)
    arr_base = np.ones(shape_in + (fourth_dim,))
    arr_in = arr_base[..., 0]
    arr_out = np.zeros(shape_out)
    matrix = np.array([1.5, 1.5, 1.5])
    offset = np.array([0., 0., 0.])
    order = 0
    mode = 4
    cval = 0.

    ndimage._nd_image.zoom_shift(arr_in, matrix, offset, arr_out, order, mode, cval)
    print arr_out.mean()

test(1024)  # works fine
test(1025)  # segmentation fault

```

With `shape_in == (32, 64, 64)`, @lesteve and I both find segfault at 2115, works at 2114

Here is the simplified snippet again without the power of 2 coincidence:

``` python

import numpy as np
from scipy import ndimage

def test(fourth_dim):
    shape_in = (65, 65, 65)
    shape_out = (43, 43, 43)
    arr_base = np.ones(shape_in + (fourth_dim,))
    arr_in = arr_base[..., 0]
    arr_out = np.zeros(shape_out)
    matrix = np.array([1.5, 1.5, 1.5])
    offset = np.array([0., 0., 0.])
    order = 0
    mode = 4
    cval = 0.

    ndimage._nd_image.zoom_shift(arr_in, matrix, offset, arr_out, order, mode, cval)
    print arr_out.mean()

test(992)  # works fine
test(993)  # segmentation fault
```

With `shape_in == (64, 32, 64)` it segfaults at 2050 ...

One thing of importance: all of the examples were run with scipy 0.14.0.

Some investigation with gdb indicated that the culprit was the int32 `idx` wrapping around and becoming negative causing a segmentation fault [there](https://github.com/scipy/scipy/blob/v0.14.0/scipy/ndimage/src/ni_interpolation.c#L869).

Looks like this has actually been fixed in scipy 0.14.1 through [this](https://github.com/scipy/scipy/pull/4218).  Although this doesn't seem related directly, the scipy PR changed the type of `idx` from int32 to int64, so that `idx` is a lot less likely to wrap around.

Nice reversing. Long live _gdb!_

On Mon, Jan 12, 2015 at 4:07 PM, Loïc Estève notifications@github.com
wrote:

> One thing of importance: all of the examples were run with scipy 0.14.0.
> 
> Some investigation with gdb showed indicated that the culprit was the
> int32 idx wrapping around and becoming negative causing a segmentation
> fault there
> https://github.com/scipy/scipy/blob/v0.14.0/scipy/ndimage/src/ni_interpolation.c#L869
> .
> 
> Looks like this has actually been fixed in scipy 0.14.1 through this
> https://github.com/scipy/scipy/pull/4218. Although this doesn't seem
> related directly, the scipy PR changed the type of idx from int32 to
> int64, so that idx is a lot less likely to wrap around.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/nilearn/nilearn/issues/346#issuecomment-69582909.

## 

DED


Resolving patch:
diff --git a/nilearn/image/resampling.py b/nilearn/image/resampling.py
--- a/nilearn/image/resampling.py
+++ b/nilearn/image/resampling.py
@@ -237,6 +237,12 @@ def _resample_one_img(data, A, A_inv, b, target_shape,
         data = _extrapolate_out_mask(data, np.logical_not(not_finite),
                                      iterations=2)[0]
 
+    # See https://github.com/nilearn/nilearn/issues/346 Copying the
+    # array makes it C continuous and as such the int32 index in the C
+    # code is a lot less likely to overflow
+    if (LooseVersion(scipy.__version__) < LooseVersion('0.14.1')):
+        data = data.copy()
+
     # The resampling itself
     ndimage.affine_transform(data, A,
                              offset=np.dot(A_inv, b),


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
@@ -507,3 +507,18 @@ def test_coord_transform_trivial():
     np.testing.assert_array_equal(z+1, z_)
 
 
+def test_resample_img_segmentation_fault():
+    # see https://github.com/nilearn/nilearn/issues/346
+    shape_in = (64, 64, 64)
+    aff_in = np.diag([2., 2., 2., 1.])
+    aff_out = np.diag([3., 3., 3., 1.])
+    # fourth_dim = 1024 works fine but for 1025 creates a segmentation
+    # fault with scipy < 0.14.1
+    fourth_dim = 1025
+    data = np.ones(shape_in + (fourth_dim, ), dtype=np.float64)
+
+    img_in = Nifti1Image(data, aff_in)
+
+    resample_img(img_in,
+                 target_affine=aff_out,
+                 interpolation='nearest')