Repository: scipy/scipy

Issue:
ndimage.geometric_transform endianness-linked bug
scipy version: 0.14.0

I tested the following snippet on a little-endian system:

``` python

import numpy as np
from scipy import ndimage

data = np.array([[1., 2.], [3., 4.]])
matrix = np.eye(2)
out = np.empty_like(data, dtype='>f8')
ndimage.affine_transform(data, np.eye(2), output=out)
print out
```

I expected to have out == data, since I am using an identity transformation. Instead out is equal to:

```
[[ -3.75375841e+255  -1.27733780e+294]
 [ -5.48612407e+303  -5.48612407e+303]]
```

On the other hand if I don't use the 'output' parameter in affine_transform or use '<f8' as dtype for 'out' everything works as expected.

I tracked it down to ndimage._nd_image.geometric_transform and then I would guess it is due to the NI_GeometricTransform C function but I am afraid figuring out what exactly is going on is beyond me.



Discussion:
The data in `out` is in little endian (see `out.byteswap()`). There are
probably no endianness checks in ndimage. The easiest fix is probably to
do `out.byteswap(True)` in the Python wrappers if the output array has
non-native endianness.

Out of interest, is there a nice way or utility function to figure out whether the array has native endianness? The only thing I found so far was to use the dtype byteorder and check against `=` or `|`.

Sorry for the noise, I just found out about `np.dtype.isnative`


Resolving patch:
diff --git a/scipy/ndimage/interpolation.py b/scipy/ndimage/interpolation.py
--- a/scipy/ndimage/interpolation.py
+++ b/scipy/ndimage/interpolation.py
@@ -121,6 +121,22 @@ def spline_filter(input, order=3, output=numpy.float64):
     return return_value
 
 
+def _geometric_transform(input, mapping, coordinates, matrix, offset, output,
+                         order, mode, cval, extra_arguments, extra_keywords):
+    """
+    Wrapper around _nd_image.geometric_transform to work around
+    endianness issues
+    """
+    _nd_image.geometric_transform(
+        input, mapping, coordinates, matrix, offset, output,
+        order, mode, cval, extra_arguments, extra_keywords)
+
+    if output is not None and not output.dtype.isnative:
+        output.byteswap(True)
+
+    return output
+
+
 def geometric_transform(input, mapping, output_shape=None,
                         output=None, order=3,
                         mode='constant', cval=0.0, prefilter=True,
@@ -205,8 +221,8 @@ def geometric_transform(input, mapping, output_shape=None,
         filtered = input
     output, return_value = _ni_support._get_output(output, input,
                                                    shape=output_shape)
-    _nd_image.geometric_transform(filtered, mapping, None, None, None,
-               output, order, mode, cval, extra_arguments, extra_keywords)
+    _geometric_transform(filtered, mapping, None, None, None, output,
+                         order, mode, cval, extra_arguments, extra_keywords)
     return return_value
 
 
@@ -304,8 +320,8 @@ def map_coordinates(input, coordinates, output=None, order=3,
         filtered = input
     output, return_value = _ni_support._get_output(output, input,
                                                    shape=output_shape)
-    _nd_image.geometric_transform(filtered, None, coordinates, None, None,
-               output, order, mode, cval, None, None)
+    _geometric_transform(filtered, None, coordinates, None, None,
+                         output, order, mode, cval, None, None)
     return return_value
 
 
@@ -398,8 +414,8 @@ def affine_transform(input, matrix, offset=0.0, output_shape=None,
         _nd_image.zoom_shift(filtered, matrix, offset, output, order,
                              mode, cval)
     else:
-        _nd_image.geometric_transform(filtered, None, None, matrix, offset,
-                            output, order, mode, cval, None, None)
+        _geometric_transform(filtered, None, None, matrix, offset,
+                             output, order, mode, cval, None, None)
     return return_value
 
 


Test patch:
diff --git a/scipy/ndimage/tests/test_ndimage.py b/scipy/ndimage/tests/test_ndimage.py
--- a/scipy/ndimage/tests/test_ndimage.py
+++ b/scipy/ndimage/tests/test_ndimage.py
@@ -1397,6 +1397,24 @@ def mapping(x):
                                                         order=order)
             assert_array_almost_equal(out, [1])
 
+    def test_geometric_transform01_with_output_parameter(self):
+        data = numpy.array([1])
+
+        def mapping(x):
+            return x
+        for order in range(0, 6):
+            out = numpy.empty_like(data)
+            ndimage.geometric_transform(data, mapping,
+                                        data.shape,
+                                        output=out)
+            assert_array_almost_equal(out, [1])
+
+            out = numpy.empty_like(data).astype(data.dtype.newbyteorder())
+            ndimage.geometric_transform(data, mapping,
+                                        data.shape,
+                                        output=out)
+            assert_array_almost_equal(out, [1])
+
     def test_geometric_transform02(self):
         data = numpy.ones([4])
 
@@ -1659,6 +1677,25 @@ def test_map_coordinates01(self):
                                        [0, 4, 1, 3],
                                        [0, 7, 6, 8]])
 
+    def test_map_coordinates01_with_output_parameter(self):
+        data = numpy.array([[4, 1, 3, 2],
+                            [7, 6, 8, 5],
+                            [3, 5, 3, 6]])
+        idx = numpy.indices(data.shape)
+        idx -= 1
+        expected = numpy.array([[0, 0, 0, 0],
+                                [0, 4, 1, 3],
+                                [0, 7, 6, 8]])
+        for order in range(0, 6):
+            out = numpy.empty_like(expected)
+            ndimage.map_coordinates(data, idx, order=order, output=out)
+            assert_array_almost_equal(out, expected)
+
+            out = numpy.empty_like(expected).astype(
+                expected.dtype.newbyteorder())
+            ndimage.map_coordinates(data, idx, order=order, output=out)
+            assert_array_almost_equal(out, expected)
+
     def test_map_coordinates02(self):
         data = numpy.array([[4, 1, 3, 2],
                                [7, 6, 8, 5],
@@ -1698,6 +1735,21 @@ def test_affine_transform01(self):
                                                      order=order)
             assert_array_almost_equal(out, [1])
 
+    def test_affine_transform01_with_output_parameter(self):
+        data = numpy.array([1])
+        for order in range(0, 6):
+            out = numpy.empty_like(data)
+            ndimage.affine_transform(data, [[1]],
+                                     order=order,
+                                     output=out)
+            assert_array_almost_equal(out, [1])
+
+            out = numpy.empty_like(data).astype(data.dtype.newbyteorder())
+            ndimage.affine_transform(data, [[1]],
+                                     order=order,
+                                     output=out)
+            assert_array_almost_equal(out, [1])
+
     def test_affine_transform02(self):
         data = numpy.ones([4])
         for order in range(0, 6):