mirror of
https://github.com/opencv/opencv.git
synced 2026-11-09 18:04:29 -06:00
Merge pull request #30369 from ternaus:codex/python-native-byte-order
fix(python): handle non-native NumPy byte order 🤖🤖🤖 - #30369 Python bindings can read non-native-endian NumPy inputs as native bytes, producing incorrect values or NaN. Mark swapped arrays for the existing cast path before constructing `Mat`; this also rejects incompatible output/in-place destinations before writes. Closes #30368. Targets 4.x so the common bridge fix can be merged into 5.x. Validation on macOS ARM64, Python 3.11.11, NumPy 2.4.6: - Built current 4.x and 5.x. Three regression tests cover 20 dtype/layout/output cases; 18 fail before and all pass after. Read-only inputs and rejected destinations remain unchanged. - 21 native-input timing cells on 4.x, 101 alternating pairs: fixed/baseline median time 0.957–1.005×, identical results. The 5.x check also stayed within 0.971–1.004×. - Draft: Linux/hosted CI validation remains pending. ### Pull Request Readiness Checklist - [x] I agree to contribute to the project under Apache 2 License. - [x] The patch is not based on GPL or other incompatible code. - [x] The PR targets the appropriate branch. - [x] The original bug report is linked. - [x] Regression tests and native-input performance checks are included; no external test data is needed. - [x] No public API, feature documentation or sample changes are needed.
This commit is contained in:
committed by
GitHub
parent
038cd01c03
commit
df42eeef2b
@@ -127,7 +127,7 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool needcopy = false, needcast = false;
|
||||
bool needcopy = !PyArray_ISNOTSWAPPED(oarr), needcast = needcopy;
|
||||
int typenum = PyArray_TYPE(oarr), new_typenum = typenum;
|
||||
int type = typenum == NPY_UBYTE ? CV_8U :
|
||||
typenum == NPY_BYTE ? CV_8S :
|
||||
|
||||
44
modules/python/test/test_numpy_byte_order.py
Normal file
44
modules/python/test/test_numpy_byte_order.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import unittest
|
||||
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
|
||||
|
||||
class NumpyByteOrderTest(unittest.TestCase):
|
||||
|
||||
def test_numpy_non_native_byte_order_input(self):
|
||||
for dtype in (np.int16, np.uint16, np.int32, np.int64, np.float16, np.float32, np.float64):
|
||||
for strided in (False, True):
|
||||
native = np.arange(120, dtype=dtype).reshape(10, 12)
|
||||
foreign = native.astype(native.dtype.newbyteorder('S'))
|
||||
if strided:
|
||||
native = native[:, ::2]
|
||||
foreign = foreign[:, ::2]
|
||||
foreign.setflags(write=False)
|
||||
before = foreign.tobytes()
|
||||
expected = cv.copyTo(native, None)
|
||||
actual = cv.copyTo(foreign, None)
|
||||
self.assertEqual(actual.dtype, expected.dtype)
|
||||
np.testing.assert_array_equal(actual, expected)
|
||||
self.assertEqual(foreign.tobytes(), before)
|
||||
self.assertFalse(foreign.flags.writeable)
|
||||
|
||||
def test_numpy_non_native_byte_order_normalize(self):
|
||||
for dtype in (np.float32, np.float64):
|
||||
native = np.linspace(0, 1, 120, dtype=dtype).reshape(10, 12)
|
||||
foreign = native.astype(native.dtype.newbyteorder('S'))
|
||||
expected = cv.normalize(native, None, 0, 1, cv.NORM_MINMAX, dtype=cv.CV_32F)
|
||||
actual = cv.normalize(foreign, None, 0, 1, cv.NORM_MINMAX, dtype=cv.CV_32F)
|
||||
np.testing.assert_array_equal(actual, expected)
|
||||
|
||||
def test_numpy_non_native_byte_order_output_rejected(self):
|
||||
for dtype in (np.int16, np.float16, np.float32, np.float64):
|
||||
output = np.full((10, 12), 137, dtype=np.dtype(dtype).newbyteorder('S'))
|
||||
before = output.tobytes()
|
||||
with self.assertRaises(cv.error):
|
||||
cv.utils.dumpInputOutputArray(output)
|
||||
self.assertEqual(output.tobytes(), before)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user