Repository: nilearn/nilearn

Issue:
resample_img problem with big-endian data on a little-endian machine
```py
import numpy as np

import nibabel

from nilearn.plotting import plot_stat_map
from nilearn.image import new_img_like
from nilearn.image import resample_img

img = 'image_12099.nii.gz'
affine = np.diag([-3, 3, 3])
cut_coords = (0, 0, 0)

plot_stat_map(img, cut_coords=cut_coords, title='original image')

img_resampled = resample_img(img, target_affine=affine)
plot_stat_map(img_resampled, cut_coords=(0, 0, 0), title='resampled image')

img = nibabel.load(img)
data = img.get_data().astype('f8')
new_img_little_endian = new_img_like(img, data)
img_resampled_little_endian = resample_img(new_img_little_endian, target_affine=affine)

plot_stat_map(img_resampled_little_endian, cut_coords=cut_coords,
              title='resample image with little-endian type')


```

The image is [image_12099.nii.gz](https://github.com/nilearn/nilearn/files/976184/image_12099.nii.gz).

![original](https://cloud.githubusercontent.com/assets/1680079/25704776/1a2554c2-30db-11e7-8793-a079f8b2ca4a.png)
![resampled](https://cloud.githubusercontent.com/assets/1680079/25704770/1673d8ee-30db-11e7-9d03-9755a46023b2.png)
![resampled-from-little-endian](https://cloud.githubusercontent.com/assets/1680079/25704768/13dc86ee-30db-11e7-955a-f257a651d349.png)

If you look at the resampled data, you have some NaNs and big values of the order of `1e308`. It seems like the culprit is that the scipy functions we are using don't deal well with big-endian dtypes on my little-endian machine.


Discussion:

Resolving patch:
diff --git a/nilearn/image/resampling.py b/nilearn/image/resampling.py
--- a/nilearn/image/resampling.py
+++ b/nilearn/image/resampling.py
@@ -346,6 +346,15 @@ def resample_img(img, target_affine=None, target_shape=None,
     **NaNs and infinite values**
     This function handles gracefully NaNs and infinite values in the input
     data, however they make the execution of the function much slower.
+
+    **Handling non-native endian in given Nifti images**
+    This function automatically changes the byte-ordering information
+    in the image dtype to new byte order. From non-native to native, which
+    implies that if the given image has non-native endianess then the output
+    data in Nifti image will have native dtype. This is only the case when
+    if the given target_affine (transformation matrix) is diagonal and
+    homogenous.
+
     """
     from .image import new_img_like  # avoid circular imports
 
@@ -474,6 +483,20 @@ def resample_img(img, target_affine=None, target_shape=None,
     else:
         resampled_data_dtype = data.dtype
 
+    if LooseVersion(scipy.__version__) >= LooseVersion('0.18'):
+        # Since the release of 0.18, resampling nifti images have some issues
+        # when affine is passed as 1D array and if data is of non-native
+        # endianess.
+        # See issue https://github.com/nilearn/nilearn/issues/1445.
+        # If affine is passed as 1D, scipy uses _nd_image.zoom_shift rather
+        # than _geometric_transform (2D) where _geometric_transform is able
+        # to swap byte order in scipy later than 0.15 for nonnative endianess.
+
+        # We convert to 'native' order to not have any issues either with
+        # 'little' or 'big' endian data dtypes (non-native endians).
+        if len(A.shape) == 1 and not resampled_data_dtype.isnative:
+            resampled_data_dtype = resampled_data_dtype.newbyteorder('N')
+
     # Code is generic enough to work for both 3D and 4D images
     other_shape = data_shape[3:]
     resampled_data = np.empty(list(target_shape) + other_shape,


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
@@ -7,7 +7,7 @@
 
 from nose import SkipTest
 from nose.tools import assert_equal, assert_raises, \
-    assert_false, assert_true, assert_almost_equal
+    assert_false, assert_true, assert_almost_equal, assert_not_equal
 from numpy.testing import assert_array_equal, assert_array_almost_equal
 
 import numpy as np
@@ -89,6 +89,22 @@ def test_identity_resample():
                            interpolation='nearest')
     np.testing.assert_almost_equal(data, rot_img.get_data())
 
+    # Test with non native endian data
+
+    # Test with big endian data ('>f8')
+    for interpolation in ['nearest', 'continuous']:
+        rot_img = resample_img(Nifti1Image(data.astype('>f8'), affine),
+                               target_affine=affine.tolist(),
+                               interpolation=interpolation)
+        np.testing.assert_almost_equal(data, rot_img.get_data())
+
+    # Test with little endian data ('<f8')
+    for interpolation in ['nearest', 'continuous']:
+        rot_img = resample_img(Nifti1Image(data.astype('<f8'), affine),
+                               target_affine=affine.tolist(),
+                               interpolation=interpolation)
+        np.testing.assert_almost_equal(data, rot_img.get_data())
+
 
 def test_downsample():
     """ Test resampling with a 1/2 down-sampling affine.
@@ -104,6 +120,29 @@ def test_downsample():
     np.testing.assert_almost_equal(downsampled,
                                    rot_img.get_data()[:x, :y, :z, ...])
 
+    # Test with non native endian data
+
+    # Test to check that if giving non native endian data as input should
+    # work as normal and expected to return the same output as above tests.
+
+    # Big endian data ('>f8')
+    for copy in [True, False]:
+        rot_img = resample_img(Nifti1Image(data.astype('>f8'), affine),
+                               target_affine=2 * affine,
+                               interpolation='nearest',
+                               copy=copy)
+        np.testing.assert_almost_equal(downsampled,
+                                       rot_img.get_data()[:x, :y, :z, ...])
+
+    # Little endian data
+    for copy in [True, False]:
+        rot_img = resample_img(Nifti1Image(data.astype('<f8'), affine),
+                               target_affine=2 * affine,
+                               interpolation='nearest',
+                               copy=copy)
+        np.testing.assert_almost_equal(downsampled,
+                                       rot_img.get_data()[:x, :y, :z, ...])
+
 
 def test_resampling_with_affine():
     """ Test resampling with a given rotation part of the affine.
@@ -123,6 +162,17 @@ def test_resampling_with_affine():
                          np.max(rot_img.get_data()))
             assert_equal(rot_img.get_data().dtype, data.dtype)
 
+    # We take the same rotation logic as above and test with nonnative endian
+    # data as input
+    for data in [data_3d, data_4d]:
+        img = Nifti1Image(data.astype('>f8'), np.eye(4))
+        for angle in (0, np.pi, np.pi / 2., np.pi / 4., np.pi / 3.):
+            rot = rotation(0, angle)
+            rot_img = resample_img(img, target_affine=rot,
+                                   interpolation='nearest')
+            assert_equal(np.max(data),
+                         np.max(rot_img.get_data()))
+
 
 def test_resampling_continuous_with_affine():
     prng = np.random.RandomState(10)
@@ -637,6 +687,6 @@ def test_resampling_with_int_types_no_crash():
 
     for dtype in [np.int, np.int8, np.int16, np.int32, np.int64,
                   np.uint, np.uint8, np.uint16, np.uint32, np.uint64,
-                  np.float32, np.float64, np.float]:
+                  np.float32, np.float64, np.float, '>i8', '<i8']:
         img = Nifti1Image(data.astype(dtype), affine)
         resample_img(img, target_affine=2. * affine)