Repository: nilearn/nilearn

Issue:
Bug in nilearn.image.resample_img when using non native endianness
Using continuous resampling on a standard nifti (without NaNs) turned the image on the left into the one on the right. I encountered this problem several times. It might have something to the with the dtype of the input data.

![resample_thing](https://cloud.githubusercontent.com/assets/3907396/4906673/fe989eca-645a-11e4-87ed-3b79da3d9963.png)

**Edit**: the Nifti files @banilo was looking had been written in big-endian with SPM (probably due to the platform they were generated on?)



Discussion:
Can you provide a minimal use case?

I can send you .nii and code that reproduces the above?

Yes, that would be awesome :). Can you also send me the nii post-reampling? Just in case the result is different on my machine.

If you suspect a dtype problem, you can diagnose it yourself: just do `img.astype(float)` and see if the resampling does the same. If you give an integer image to resampling in 'continuous' mode, it is in fact known to fail because of the integer rounding.

I will sent you script + niftis.

Apart from that, why not add a check for float types in resample_img()?

IMHO, a code snippet is way better than words for us to understand what you are doing. It does seem related to #270 as @AlexandreAbraham was hinting. If you are using resample_img directly, why not use interpolation='nearest'?

Because both interpolations are relvant for different situations.
interpolation='nearest' <- I usually use this for atlases, so that the labels are not corrupted
interpolation='continuous' <- is what I usually use for Z-map-like activation data

Shouldn't resample_img() work with any nifti using interpolation='interpolation'?

Quickly looking at the picture you showed, I wrongly assumed you were using labels, my bad.

As a aside, if your problem is too complex to be reduced to a code snippet, one way you can share data (assuming is isn't less than 100MB in size) + scripts is using [gists](https://help.github.com/articles/about-gists/).

Thanks for the reminder!!

2014-11-05 11:22 GMT+01:00 Loïc Estève notifications@github.com:

> As a anside, if your problem is too complex to be reduced to a code
> snippet, one way you can share data (assuming is isn't less than 100MB in
> size) + scripts is using gists
> https://help.github.com/articles/about-gists/.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/nilearn/nilearn/issues/275#issuecomment-61786741.

## 

Viele Grüße,
Danilo

Little update: it seems to have something to do with big/little Indian encoding. Enforcing dtype='>f8' reproduces the bug, while dtype='<f8' makes the bug dissappear. Some lower-level C/Cython problem in numpy?

For completeness, here is a snippet that reproduces the problem:

``` python

import math

import numpy as np

import pandas as pd

from nilearn.image import resample_img
from nilearn import plotting

import nibabel as nib


def make_test_img(dtype):
    data = np.ones((10, 10, 10), dtype=dtype)
    data[3:7, 3:7, 3:7] = 2

    affine = np.eye(4)
    img = nib.Nifti1Image(data, affine)
    return img


def my_resample_img(img):
    theta = math.pi / 6
    c = math.cos(theta)
    s = math.sin(theta)

    affine = np.array([[1, 0, 0, 0],
                       [0, c, -s, 0],
                       [0, s, c, 0],
                       [0, 0, 0, 1]])

    tweaked_img = nib.Nifti1Image(img.get_data(), affine)
    resampled_img = resample_img(tweaked_img, target_affine=np.eye(4))
    return resampled_img


def my_test(dtype):
    cut_coords = (5, 5, 5)
    img = make_test_img(dtype)
    plotting.plot_img(img, cut_coords=cut_coords)
    resampled_img = my_resample_img(img)
    plotting.plot_img(resampled_img, cut_coords=cut_coords)
    print 'img data dtype: {}\n{}'.format(
        dtype,
        pd.Series(resampled_img.get_data().ravel()).describe())

my_test('<f8')
my_test('>f8')

```

I haven't looked into it more, but somehow in the big endian case, we end up with NaN and values of order 10^300...

Thanks to everybody involved for reporting/digging in the problem. Fixing
these issues is critical for nilearn!

It seems like the culprit is scipy.ndimage.affine_transform. As small example reproducing the issue:

``` python
import numpy as np
from scipy import ndimage

data = np.ones((2, 2, 2))
out = np.empty_like(data, dtype='>f8')
ndimage.affine_transform(data, np.eye(3), output=out)
out
```

The output:

```
array([[[ -5.48612407e+303,  -8.37116099e+298],
        [ -8.37116099e+298,  -5.48612407e+303]],

       [[ -8.37116099e+298,  -5.48612407e+303],
        [ -5.48612407e+303,  -8.37116099e+298]]])
```

> It seems like the culprit is scipy.ndimage.affine_transform. As small example
> reproducing the issue:

OK then I suggest:
- To submit a bug to the scipy tracker (with the script attached)
- To cast our data to the right endianess

+1

FYI, I just opened https://github.com/scipy/scipy/issues/4127

+1


Resolving patch:
diff --git a/nilearn/image/resampling.py b/nilearn/image/resampling.py
--- a/nilearn/image/resampling.py
+++ b/nilearn/image/resampling.py
@@ -232,15 +232,20 @@ def _resample_one_img(data, A, A_inv, b, target_shape,
         #data[not_finite] = 0
         from ..masking import _extrapolate_out_mask
         data = _extrapolate_out_mask(data, np.logical_not(not_finite),
-                                        iterations=2)[0]
+                                     iterations=2)[0]
 
     # The resampling itself
-
     ndimage.affine_transform(data, A,
                              offset=np.dot(A_inv, b),
                              output_shape=target_shape,
                              output=out,
                              order=interpolation_order)
+
+    # Bug in ndimage.affine_transform when out does not have native endianness
+    # see https://github.com/nilearn/nilearn/issues/275
+    if not out.dtype.isnative:
+        out.byteswap(True)
+
     if has_not_finite:
         # We need to resample the mask of not_finite values
         not_finite = ndimage.affine_transform(not_finite, A,


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
@@ -2,6 +2,7 @@
 Test the resampling code.
 """
 import copy
+import math
 
 from nose.tools import assert_equal, assert_raises, assert_raises_regexp, \
     assert_false, assert_true, assert_almost_equal
@@ -487,6 +488,31 @@ def test_reorder_img():
         assert_true(np.all(np.diag(img2.get_affine()) >= 0))
 
 
+def test_reorder_img_non_native_endianness():
+    def _get_resampled_img(dtype):
+        data = np.ones((10, 10, 10), dtype=dtype)
+        data[3:7, 3:7, 3:7] = 2
+
+        affine = np.eye(4)
+
+        theta = math.pi / 6
+        c = math.cos(theta)
+        s = math.sin(theta)
+
+        affine = np.array([[1, 0, 0, 0],
+                           [0, c, -s, 0],
+                           [0, s, c, 0],
+                           [0, 0, 0, 1]])
+
+        img = Nifti1Image(data, affine)
+        return resample_img(img, target_affine=np.eye(4))
+
+    img_1 = _get_resampled_img('<f8')
+    img_2 = _get_resampled_img('>f8')
+
+    np.testing.assert_equal(img_1.get_data(), img_2.get_data())
+
+
 def test_coord_transform_trivial():
     sform = np.eye(4)
     x = np.random.random((10,))