Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/bindings/python/PyCPUProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ written to the dstImgDesc image, leaving srcImgDesc unchanged.
py::buffer_info info = data.request();
checkBufferDivisible(info, 3);

// Interpret as single row of RGB pixels
// --- detect C-contiguous ---
checkCContiguousArray(info);

// --- proceed normally ---
BitDepth bitDepth = getBufferBitDepth(info);

py::gil_scoped_release release;
Expand All @@ -115,6 +118,7 @@ written to the dstImgDesc image, leaving srcImgDesc unchanged.
chanStrideBytes,
xStrideBytes,
yStrideBytes);

self->apply(img);
},
"data"_a,
Expand Down Expand Up @@ -171,6 +175,8 @@ float values is returned, leaving the input list unchanged.
py::buffer_info info = data.request();
checkBufferDivisible(info, 4);

// --- detect C-contiguous ---
checkCContiguousArray(info);
// Interpret as single row of RGBA pixels
BitDepth bitDepth = getBufferBitDepth(info);

Expand Down
21 changes: 21 additions & 0 deletions src/bindings/python/PyUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,27 @@ void checkBufferType(const py::buffer_info & info, BitDepth bitDepth)
checkBufferType(info, bitDepthToDtype(bitDepth));
}

void checkCContiguousArray(const py::buffer_info & info)
{
bool isC = true;
ptrdiff_t itemsize = info.itemsize;
auto shape = info.shape;
auto strides = info.strides;
py::ssize_t ndim = info.ndim;

ptrdiff_t expected = itemsize;
for (py::ssize_t i = ndim - 1; i >= 0; --i)
{
if (strides[i] != expected) { isC = false; break; }
expected *= shape[i];
}

if (!isC)
{
throw std::runtime_error("function only supports C-contiguous (row-major) arrays");
}
}

void checkBufferDivisible(const py::buffer_info & info, py::ssize_t numChannels)
{
if (info.size % numChannels != 0)
Expand Down
3 changes: 3 additions & 0 deletions src/bindings/python/PyUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ unsigned long getBufferLut3DGridSize(const py::buffer_info & info);
// Throw if vector size is not divisible by channel count
void checkVectorDivisible(const std::vector<float> & pixel, size_t numChannels);

// Throw if array is not C-contiguous
void checkCContiguousArray(const py::buffer_info & info);

} // namespace OCIO_NAMESPACE

#endif // INCLUDED_OCIO_PYUTILS_H
69 changes: 68 additions & 1 deletion tests/python/CPUProcessorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


class CPUProcessorTest(unittest.TestCase):
FLOAT_DELTA = 1e+5
FLOAT_DELTA = 1e-5
UINT_DELTA = 1

@classmethod
Expand Down Expand Up @@ -385,6 +385,28 @@ def test_apply_rgb_list(self):
delta=self.FLOAT_DELTA
)

def test_apply_rgb_buffer_column_major(self):
if not np:
logger.warning("NumPy not found. Skipping test!")
return

for arr, cpu_proc_fwd in [
(self.float_rgb_2d, self.default_cpu_proc_fwd),
(self.float_rgb_3d, self.default_cpu_proc_fwd),
(self.half_rgb_2d, self.half_cpu_proc_fwd),
(self.half_rgb_3d, self.half_cpu_proc_fwd),
(self.uint16_rgb_2d, self.uint16_cpu_proc_fwd),
(self.uint16_rgb_3d, self.uint16_cpu_proc_fwd),
(self.uint8_rgb_2d, self.uint8_cpu_proc_fwd),
(self.uint8_rgb_3d, self.uint8_cpu_proc_fwd),
]:
# Transpose to F-order (column-major)
arr_copy = arr.copy().T

# Expect runtime error for non-C-contiguous array
with self.assertRaises(RuntimeError):
cpu_proc_fwd.applyRGB(arr_copy)

def test_apply_rgb_buffer(self):
if not np:
logger.warning("NumPy not found. Skipping test!")
Expand Down Expand Up @@ -627,3 +649,48 @@ def test_apply_rgba_buffer(self):
arr.flat[i],
delta=self.UINT_DELTA
)

def test_apply_rgba_buffer_column_major(self):
if not np:
logger.warning("NumPy not found. Skipping test!")
return

for arr, cpu_proc_fwd in [
(
self.float_rgba_2d,
self.default_cpu_proc_fwd
),
(
self.float_rgba_3d,
self.default_cpu_proc_fwd
),
(
self.half_rgba_2d,
self.half_cpu_proc_fwd
),
(
self.half_rgba_3d,
self.half_cpu_proc_fwd
),
(
self.uint16_rgba_2d,
self.uint16_cpu_proc_fwd
),
(
self.uint16_rgba_3d,
self.uint16_cpu_proc_fwd
),
(
self.uint8_rgba_2d,
self.uint8_cpu_proc_fwd
),
(
self.uint8_rgba_3d,
self.uint8_cpu_proc_fwd,
),
]:
# Transpose to F-order (column-major)
arr_copy = arr.copy().T
# Expect runtime error for non-C-contiguous array
with self.assertRaises(RuntimeError):
cpu_proc_fwd.applyRGBA(arr_copy)