diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake
index 554d9e8e89..3eeca449bc 100644
--- a/src/cmake/testing.cmake
+++ b/src/cmake/testing.cmake
@@ -259,11 +259,23 @@ macro (oiio_add_all_tests)
endif ()
oiio_tests_pythonpath_env_entry (_nanobind_tests_pythonpath
"${CMAKE_BINARY_DIR}/lib/python/nanobind")
+ # Keep in sync with the pybind11 python-* tests below as dual-backend
+ # coverage expands. imageinput/imagebufalgo also need oiio-images.
set (nanobind_python_tests
+ python-colorconfig
+ python-deep
+ python-imagebuf
+ python-imagecache
+ python-imageoutput
python-imagespec
+ python-oiio
python-paramlist
python-roi
+ python-texturesys
python-typedesc)
+ set (nanobind_python_tests_imagedir
+ python-imageinput
+ python-imagebufalgo)
set (nanobind_python_test_suffix ".nanobind")
if (OIIO_BUILD_PYTHON_PYBIND11)
oiio_add_tests (
@@ -298,6 +310,12 @@ macro (oiio_add_all_tests)
SUFFIX ${nanobind_python_test_suffix}
ENVIRONMENT "${_nanobind_tests_pythonpath}"
)
+ oiio_add_tests (
+ ${nanobind_python_tests_imagedir}
+ SUFFIX ${nanobind_python_test_suffix}
+ IMAGEDIR oiio-images
+ ENVIRONMENT "${_nanobind_tests_pythonpath}"
+ )
endif ()
endif ()
diff --git a/src/python-nanobind/CMakeLists.txt b/src/python-nanobind/CMakeLists.txt
index 4fd40d037d..66c9b00c03 100644
--- a/src/python-nanobind/CMakeLists.txt
+++ b/src/python-nanobind/CMakeLists.txt
@@ -4,10 +4,18 @@
set (nanobind_srcs
${CMAKE_CURRENT_SOURCE_DIR}/../python/py_oiio.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_paramvalue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../python/py_roi.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_typedesc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../python/py_imagespec.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_typedesc.cpp)
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_paramvalue.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_deepdata.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_colorconfig.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_imageinput.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_imageoutput.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_imagebuf.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_imagecache.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_texturesys.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_imagebufalgo.cpp)
set (nanobind_build_package_dir ${CMAKE_BINARY_DIR}/lib/python/nanobind/OpenImageIO)
file (MAKE_DIRECTORY ${nanobind_build_package_dir})
diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt
index 6a3b7bb537..aab0cdd856 100644
--- a/src/python/CMakeLists.txt
+++ b/src/python/CMakeLists.txt
@@ -8,7 +8,15 @@ set (python_dual_backend_srcs
py_roi.cpp
py_typedesc.cpp
py_imagespec.cpp
- py_paramvalue.cpp)
+ py_paramvalue.cpp
+ py_deepdata.cpp
+ py_colorconfig.cpp
+ py_imageinput.cpp
+ py_imageoutput.cpp
+ py_imagebuf.cpp
+ py_imagecache.cpp
+ py_texturesys.cpp
+ py_imagebufalgo.cpp)
# Full pybind11 module (pybind11 or both backends).
if (OIIO_BUILD_PYTHON_PYBIND11)
diff --git a/src/python/MIGRATION_STATUS.md b/src/python/MIGRATION_STATUS.md
index d3ac0d2671..f2e8be46cb 100644
--- a/src/python/MIGRATION_STATUS.md
+++ b/src/python/MIGRATION_STATUS.md
@@ -5,47 +5,50 @@ Nanobind shares binding sources with pybind11 under `src/python/` (see
with `-DOIIO_PYTHON_BINDINGS_BACKEND=nanobind` for nanobind-only (`PyOpenImageIO`
/ module `OpenImageIO` in site-packages), or `both` to also build
`PyOpenImageIONanobind` (`_OpenImageIO` under `lib/python/nanobind/OpenImageIO`).
-Nanobind-only code paths live in `py_oiio.h` / `py_oiio.cpp` behind
+Nanobind-only code paths live in binding `.cpp` files and `py_oiio.cpp` behind
`OIIO_PY_BACKEND_NANOBIND`. Shared Python↔C++ conversion helpers live in
-`py_oiio.h` for both backends.
+`py_oiio.h` / `py_backend.h` for both backends.
-## Migrated — full parity with pybind (no known gaps for this surface)
+## Migrated — full dual-backend sources
-| Source file | Python / C++ API |
-| --- | --- |
-| `py_roi.cpp` | ROI
Free functions:
unionintersectionget_roiget_roi_fullset_roiset_roi_full
|
-| `py_typedesc.cpp` | TypeDesc
Enums: BASETYPE, AGGREGATE, VECSEMANTICS
Module Type* constants |
-| `py_imagespec.cpp` | ImageSpec (bound methods/properties, typed attribute / buffer paths via shared helpers). |
-| `py_paramvalue.cpp` | ParamValue, ParamValueList
Enum: Interp |
-
-## Migrated — partial (gaps or intentional deltas vs pybind)
-
-| Source file | Migrated (vs pybind) | Missing or divergent (vs pybind) |
-| --- | --- | --- |
-| `py_oiio.cpp` (`OpenImageIO` module) | attribute (one-arg and typed)get_int_attributeget_float_attributeget_string_attributegetattribute__version__, VERSION_STRING
| geterrorget_bytes_attribute- Module
set_colorspace (helper taking ImageSpec — the instance method is on ImageSpec in nanobind) set_colorspace_rec709_gammaequivalent_colorspaceis_imageio_format_nameAutoStrideopenimageio_version, VERSION, VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, INTRO_STRING- Optional: stack traces when
OPENIMAGEIO_DEBUG_PYTHON is set (Sysutil) make_pyobject: no pybind-style debugfmt when the type is unhandled (returns default quietly)
|
-| `__init__.py` (package) | Shared with pybind11: env / DLL path setup, from .OpenImageIO import *. | TODO: Python CLI entry-point trampolines when the install layout matches the full wheel. |
-
----
-
-## Not migrated — entire pybind modules
-
-These exist only in the pybind11 module today (not in `python_dual_backend_srcs`).
+All modules below compile for both pybind11 and nanobind from `src/python/`:
| Source file | Python / C++ API |
| --- | --- |
-| `py_imageinput.cpp` | ImageInput- open, read, formats, …
|
-| `py_imageoutput.cpp` | ImageOutput |
-| `py_imagebuf.cpp` | ImageBuf |
-| `py_imagebufalgo.cpp` | ImageBufAlgo (namespace)PixelStatsCompareResults- Exposed
IBA_* helpers
|
-| `py_texturesys.cpp` | WrapMipModeInterpModeTextureOptTextureSystem
|
-| `py_imagecache.cpp` | ImageCache (wrapped) |
-| `py_colorconfig.cpp` | ColorConfig |
-| `py_deepdata.cpp` | DeepData |
-
----
+| `py_roi.cpp` | `ROI`, free functions (`union`, `intersection`, `get_roi`, …) |
+| `py_typedesc.cpp` | `TypeDesc`, enums, module `Type*` constants |
+| `py_imagespec.cpp` | `ImageSpec` |
+| `py_paramvalue.cpp` | `ParamValue`, `ParamValueList`, `Interp` |
+| `py_deepdata.cpp` | `DeepData` |
+| `py_colorconfig.cpp` | `ColorConfig`, module color constants |
+| `py_imageinput.cpp` | `ImageInput` |
+| `py_imageoutput.cpp` | `ImageOutput` |
+| `py_imagebuf.cpp` | `ImageBuf` |
+| `py_imagecache.cpp` | `ImageCache` (wrapped) |
+| `py_texturesys.cpp` | `Wrap`, `MipMode`, `InterpMode`, `TextureOpt`, `TextureSystem` |
+| `py_imagebufalgo.cpp` | `ImageBufAlgo`, `PixelStats`, `CompareResults`, `IBA_*` |
+| `py_oiio.cpp` | Module-level attributes and global helpers |
+
+## Consumer-visible differences (pybind11 vs nanobind)
+
+**None intended.** Callers of the pybind11 module should see the same Python API
+and behavior with the nanobind build. Binding-side `#if` / caster / constructor
+differences exist only to keep that parity.
+
+If you find a behavioral difference, treat it as a bug and add a regression test.
+
+## Packaging / install layout
+
+| Item | Notes |
+| --- | --- |
+| `__init__.py` | Shared env setup; CLI entry-point trampolines still TODO for full wheel layout. |
+| `both` layout | With `OIIO_PYTHON_BINDINGS_BACKEND=both`, nanobind installs under `lib/python/nanobind/` via `src/python-nanobind/` (`_OpenImageIO` + package `__init__.py`). Default pybind11 install path is unchanged. |
-## Conventions
+## Conventions (maintainers)
-When adding coverage, prefer mirroring the existing `declare_*` split in `src/python/` unless a file becomes too large.
+- Binding macros: `.OIIO_PY_RW`, `.OIIO_PY_PROP_RO`, `.OIIO_PY_PROP_RW`, `.OIIO_PY_PROP_RW_NONE`, `.OIIO_PY_RO`, `.OIIO_PY_RO_STATIC` (see `py_backend.h`).
+- Declare functions use `py_module&`, not `py::module&`.
+- Buffer I/O: `oiio_py_request_buffer()` / `oiio_bufinfo_from_object()` (both backends).
+- `#if defined(OIIO_PY_BACKEND_NANOBIND)` only where backends genuinely differ.
-Extend **testsuite** coverage for any migrated code that is not already covered, so **parity with pybind11** is demonstrated rather than only claimed. Follow the existing `testsuite/python-*` scripts and `ref/out.txt` pattern where applicable.
+Extend **testsuite** coverage when adding behavior; run both pybind and `*.nanobind` ctest variants when `OIIO_PYTHON_BINDINGS_BACKEND=both`.
diff --git a/src/python/py_backend.h b/src/python/py_backend.h
index 81072aee08..68d85b468f 100644
--- a/src/python/py_backend.h
+++ b/src/python/py_backend.h
@@ -10,7 +10,11 @@
# include
# include
# include
+# include
+# include
# include
+# include
+# include
# include
namespace py = nanobind;
@@ -18,9 +22,14 @@ using py_module = nanobind::module_;
using namespace py::literals;
# define OIIO_PY_RW def_rw
+# define OIIO_PY_RO def_ro
# define OIIO_PY_PROP_RO def_prop_ro
# define OIIO_PY_PROP_RW def_prop_rw
+// Allow assigning None to a property (nanobind rejects it unless annotated).
+# define OIIO_PY_PROP_RW_NONE(name, getter, setter) \
+ def_prop_rw(name, getter, setter, py::for_setter(py::arg().none()))
# define OIIO_PY_RO_STATIC def_prop_ro_static
+# define OIIO_PY_RO def_ro
namespace oiio_py {
@@ -41,13 +50,16 @@ make_tuple(size_t size, F&& fill)
return py::steal(PyList_AsTuple(list.ptr()));
}
+inline constexpr auto ref = py::rv_policy::reference;
inline constexpr auto ref_internal = py::rv_policy::reference_internal;
+// Copy into std::string so temporary string_view contents stay valid;
+// nanobind's string caster turns this into a Python str.
template
-inline auto
+inline std::string
str(T&& x)
{
- return std::forward(x);
+ return std::string(std::forward(x));
}
inline void
@@ -102,9 +114,12 @@ using py_module = pybind11::module;
using namespace py::literals;
# define OIIO_PY_RW def_readwrite
+# define OIIO_PY_RO def_readonly
# define OIIO_PY_PROP_RO def_property_readonly
# define OIIO_PY_PROP_RW def_property
+# define OIIO_PY_PROP_RW_NONE def_property
# define OIIO_PY_RO_STATIC def_property_readonly_static
+# define OIIO_PY_RO def_readonly
namespace oiio_py {
@@ -125,6 +140,7 @@ make_tuple(size_t size, F&& fill)
return result;
}
+inline constexpr auto ref = py::return_value_policy::reference;
inline constexpr auto ref_internal = py::return_value_policy::reference_internal;
template
diff --git a/src/python/py_colorconfig.cpp b/src/python/py_colorconfig.cpp
index 075c1f3e84..a3d99528de 100644
--- a/src/python/py_colorconfig.cpp
+++ b/src/python/py_colorconfig.cpp
@@ -12,10 +12,8 @@ namespace PyOpenImageIO {
// Declare the OIIO ColorConfig class to Python
void
-declare_colorconfig(py::module& m)
+declare_colorconfig(py_module& m)
{
- using namespace pybind11::literals;
-
py::class_(m, "ColorConfig")
.def(py::init<>())
@@ -192,9 +190,12 @@ declare_colorconfig(py::module& m)
return std::nullopt;
})
.def("configname", &ColorConfig::configname)
- .def_static("default_colorconfig", []() -> const ColorConfig& {
- return ColorConfig::default_colorconfig();
- });
+ .def_static(
+ "default_colorconfig",
+ []() -> const ColorConfig& {
+ return ColorConfig::default_colorconfig();
+ },
+ oiio_py::ref);
m.attr("supportsOpenColorIO") = ColorConfig::supportsOpenColorIO();
m.attr("OpenColorIO_version_hex") = ColorConfig::OpenColorIO_version_hex();
diff --git a/src/python/py_deepdata.cpp b/src/python/py_deepdata.cpp
index 43e5aed6d2..4d57bfc8cb 100644
--- a/src/python/py_deepdata.cpp
+++ b/src/python/py_deepdata.cpp
@@ -70,29 +70,24 @@ DeepData_merge_deep_pixels(DeepData& dd, int64_t pixel, const DeepData& src,
// Declare the OIIO DeepData class to Python
void
-declare_deepdata(py::module& m)
+declare_deepdata(py_module& m)
{
- using namespace pybind11::literals;
-
py::class_(m, "DeepData")
- .def_property_readonly("pixels",
- [](const DeepData& d) { return d.pixels(); })
- .def_property_readonly("channels",
- [](const DeepData& d) { return d.channels(); })
- .def_property_readonly("A_channel",
- [](const DeepData& d) { return d.A_channel(); })
- .def_property_readonly("AR_channel",
- [](const DeepData& d) { return d.AR_channel(); })
- .def_property_readonly("AG_channel",
- [](const DeepData& d) { return d.AG_channel(); })
- .def_property_readonly("AB_channel",
- [](const DeepData& d) { return d.AB_channel(); })
- .def_property_readonly("Z_channel",
- [](const DeepData& d) { return d.Z_channel(); })
- .def_property_readonly("Zback_channel",
- [](const DeepData& d) {
- return d.Zback_channel();
- })
+ .OIIO_PY_PROP_RO("pixels", [](const DeepData& d) { return d.pixels(); })
+ .OIIO_PY_PROP_RO("channels",
+ [](const DeepData& d) { return d.channels(); })
+ .OIIO_PY_PROP_RO("A_channel",
+ [](const DeepData& d) { return d.A_channel(); })
+ .OIIO_PY_PROP_RO("AR_channel",
+ [](const DeepData& d) { return d.AR_channel(); })
+ .OIIO_PY_PROP_RO("AG_channel",
+ [](const DeepData& d) { return d.AG_channel(); })
+ .OIIO_PY_PROP_RO("AB_channel",
+ [](const DeepData& d) { return d.AB_channel(); })
+ .OIIO_PY_PROP_RO("Z_channel",
+ [](const DeepData& d) { return d.Z_channel(); })
+ .OIIO_PY_PROP_RO("Zback_channel",
+ [](const DeepData& d) { return d.Zback_channel(); })
.def(py::init<>())
.def("init", &DeepData_init, "npixels"_a, "nchannels"_a,
diff --git a/src/python/py_imagebuf.cpp b/src/python/py_imagebuf.cpp
index 540b077185..7329c8cfad 100644
--- a/src/python/py_imagebuf.cpp
+++ b/src/python/py_imagebuf.cpp
@@ -15,20 +15,18 @@ namespace PyOpenImageIO {
static ImageBuf
-ImageBuf_from_buffer(const py::buffer& buffer)
+ImageBuf_from_buffer(const py::object& buffer)
{
ImageBuf ib;
- const py::buffer_info info = buffer.request();
+ const oiio_py_buffer_view info = oiio_py_request_buffer(buffer);
TypeDesc format;
- if (info.format.size())
+ if (info.format.size()) {
format = typedesc_from_python_array_code(info.format);
- if (format == TypeUnknown)
+ }
+ if (format == TypeUnknown) {
return ib;
- // Strutil::print("IB from {} buffer: dims = {}\n", format, info.ndim);
- // for (int i = 0; i < info.ndim; ++i)
- // Strutil::print("IB from buffer: dim[{}]: size = {}, stride = {}\n", i,
- // info.shape[i], info.strides[i]);
- if (size_t(info.strides[info.ndim - 1]) != format.size()) {
+ }
+ if (info.ndim > 0 && size_t(info.strides[info.ndim - 1]) != format.size()) {
ib.errorfmt(
"ImageBuf-from-numpy-array must have contiguous stride within pixels");
return ib;
@@ -38,23 +36,23 @@ ImageBuf_from_buffer(const py::buffer& buffer)
stride_t xstride = AutoStride, ystride = AutoStride, zstride = AutoStride;
if (info.ndim == 3) {
// Assume [y][x][c]
- width = info.shape[1];
- height = info.shape[0];
- nchans = info.shape[2];
+ width = int(info.shape[1]);
+ height = int(info.shape[0]);
+ nchans = int(info.shape[2]);
xstride = info.strides[1];
ystride = info.strides[0];
} else if (info.ndim == 2) {
// Assume [y][x], single channel
- width = info.shape[1];
- height = info.shape[0];
+ width = int(info.shape[1]);
+ height = int(info.shape[0]);
xstride = info.strides[1];
ystride = info.strides[0];
} else if (info.ndim == 4) {
// Assume volume [z][y][x][c]
- width = info.shape[2];
- height = info.shape[1];
- depth = info.shape[0];
- nchans = info.shape[3];
+ width = int(info.shape[2]);
+ height = int(info.shape[1]);
+ depth = int(info.shape[0]);
+ nchans = int(info.shape[3]);
xstride = info.strides[2];
ystride = info.strides[1];
zstride = info.strides[0];
@@ -208,7 +206,7 @@ ImageBuf_set_deep_value_uint(ImageBuf& buf, int x, int y, int z, int c, int s,
bool
-ImageBuf_set_pixels_buffer(ImageBuf& self, ROI roi, py::buffer& buffer)
+ImageBuf_set_pixels_buffer(ImageBuf& self, ROI roi, const py::object& buffer)
{
if (!roi.defined())
roi = self.roi();
@@ -217,8 +215,10 @@ ImageBuf_set_pixels_buffer(ImageBuf& self, ROI roi, py::buffer& buffer)
if (size == 0) {
return true; // done
}
- oiio_bufinfo buf(buffer.request(), roi.nchannels(), roi.width(),
- roi.height(), roi.depth(), self.spec().depth > 1 ? 3 : 2);
+ oiio_bufinfo buf = oiio_bufinfo_from_object(buffer, roi.nchannels(),
+ roi.width(), roi.height(),
+ roi.depth(),
+ self.spec().depth > 1 ? 3 : 2);
if (!buf.data || buf.error.size()) {
self.errorfmt("set_pixels error: {}",
buf.error.size() ? buf.error.c_str() : "unspecified");
@@ -285,15 +285,35 @@ ImageBuf_repr_png(const ImageBuf& self)
void
-declare_imagebuf(py::module& m)
+declare_imagebuf(py_module& m)
{
- using namespace pybind11::literals;
-
py::class_(m, "ImageBuf")
.def(py::init<>())
.def(py::init())
.def(py::init())
.def(py::init())
+#if defined(OIIO_PY_BACKEND_NANOBIND)
+ .def(
+ "__init__",
+ [](ImageBuf* self, const ImageSpec& spec, bool zero) {
+ new (self) ImageBuf(spec, zero ? InitializePixels::Yes
+ : InitializePixels::No);
+ },
+ "spec"_a, "zero"_a)
+ .def(
+ "__init__",
+ [](ImageBuf* self, const std::string& name, int subimage,
+ int miplevel, const ImageSpec& config) {
+ new (self) ImageBuf(name, subimage, miplevel, nullptr, &config);
+ },
+ "name"_a, "subimage"_a, "miplevel"_a, "config"_a)
+ .def(
+ "__init__",
+ [](ImageBuf* self, const py::object& buffer) {
+ new (self) ImageBuf(ImageBuf_from_buffer(buffer));
+ },
+ "buffer"_a)
+#else
.def(py::init([](const ImageSpec& spec, bool zero) {
auto z = zero ? InitializePixels::Yes : InitializePixels::No;
return ImageBuf(spec, z);
@@ -303,10 +323,11 @@ declare_imagebuf(py::module& m)
return ImageBuf(name, subimage, miplevel, nullptr, &config);
}),
"name"_a, "subimage"_a, "miplevel"_a, "config"_a)
- .def(py::init([](const py::buffer& buffer) {
+ .def(py::init([](const py::object& buffer) {
return ImageBuf_from_buffer(buffer);
}),
"buffer"_a)
+#endif
.def("clear", &ImageBuf::clear)
.def(
"reset",
@@ -330,15 +351,13 @@ declare_imagebuf(py::module& m)
"spec"_a, "zero"_a = true)
.def(
"reset",
- [](ImageBuf& self, const py::buffer& buffer) {
+ [](ImageBuf& self, const py::object& buffer) {
self = ImageBuf_from_buffer(buffer);
},
"buffer"_a)
- .def_property_readonly("initialized",
- [](const ImageBuf& self) {
- return self.initialized();
- })
+ .OIIO_PY_PROP_RO("initialized",
+ [](const ImageBuf& self) { return self.initialized(); })
.def(
"init_spec",
[](ImageBuf& self, std::string filename, int subimage,
@@ -393,64 +412,61 @@ declare_imagebuf(py::module& m)
// FIXME -- write(ImageOut&)
.def("set_write_tiles", &ImageBuf::set_write_tiles, "width"_a = 0,
"height"_a = 0, "depth"_a = 0)
- .def("spec", &ImageBuf::spec,
- py::return_value_policy::reference_internal)
- .def("nativespec", &ImageBuf::nativespec,
- py::return_value_policy::reference_internal)
- .def("specmod", &ImageBuf::specmod,
- py::return_value_policy::reference_internal)
- .def_property_readonly("has_thumbnail",
- [](const ImageBuf& self) {
- return self.has_thumbnail();
- })
+ .def("spec", &ImageBuf::spec, oiio_py::ref_internal)
+ .def("nativespec", &ImageBuf::nativespec, oiio_py::ref_internal)
+ .def("specmod", &ImageBuf::specmod, oiio_py::ref_internal)
+ .OIIO_PY_PROP_RO("has_thumbnail",
+ [](const ImageBuf& self) {
+ return self.has_thumbnail();
+ })
.def("clear_thumbnail", &ImageBuf::clear_thumbnail)
.def("set_thumbnail", &ImageBuf::set_thumbnail, "thumb"_a)
.def("get_thumbnail",
[](const ImageBuf& self) { return *self.get_thumbnail(); })
- .def_property_readonly("name",
- [](const ImageBuf& self) {
- return PY_STR(self.name());
- })
- .def_property_readonly("file_format_name",
- [](const ImageBuf& self) {
- return PY_STR(self.file_format_name());
- })
- .def_property_readonly("subimage", &ImageBuf::subimage)
- .def_property_readonly("nsubimages", &ImageBuf::nsubimages)
- .def_property_readonly("miplevel", &ImageBuf::miplevel)
- .def_property_readonly("nmiplevels", &ImageBuf::nmiplevels)
- .def_property_readonly("nchannels", &ImageBuf::nchannels)
- .def_property("orientation", &ImageBuf::orientation,
- &ImageBuf::set_orientation)
- .def_property_readonly("oriented_width", &ImageBuf::oriented_width)
- .def_property_readonly("oriented_height", &ImageBuf::oriented_height)
- .def_property_readonly("oriented_x", &ImageBuf::oriented_x)
- .def_property_readonly("oriented_y", &ImageBuf::oriented_y)
- .def_property_readonly("oriented_full_width",
- &ImageBuf::oriented_full_width)
- .def_property_readonly("oriented_full_height",
- &ImageBuf::oriented_full_height)
- .def_property_readonly("oriented_full_x", &ImageBuf::oriented_full_x)
- .def_property_readonly("oriented_full_y", &ImageBuf::oriented_full_y)
- .def_property_readonly("xbegin", &ImageBuf::xbegin)
- .def_property_readonly("xend", &ImageBuf::xend)
- .def_property_readonly("ybegin", &ImageBuf::ybegin)
- .def_property_readonly("yend", &ImageBuf::yend)
- .def_property_readonly("zbegin", &ImageBuf::zbegin)
- .def_property_readonly("zend", &ImageBuf::zend)
- .def_property_readonly("xmin", &ImageBuf::xmin)
- .def_property_readonly("xmax", &ImageBuf::xmax)
- .def_property_readonly("ymin", &ImageBuf::ymin)
- .def_property_readonly("ymax", &ImageBuf::ymax)
- .def_property_readonly("zmin", &ImageBuf::zmin)
- .def_property_readonly("zmax", &ImageBuf::zmax)
- .def_property_readonly("roi", &ImageBuf::roi)
- .def_property("roi_full", &ImageBuf::roi_full, &ImageBuf::set_roi_full)
+ .OIIO_PY_PROP_RO("name",
+ [](const ImageBuf& self) {
+ return PY_STR(self.name());
+ })
+ .OIIO_PY_PROP_RO("file_format_name",
+ [](const ImageBuf& self) {
+ return PY_STR(self.file_format_name());
+ })
+ .OIIO_PY_PROP_RO("subimage", &ImageBuf::subimage)
+ .OIIO_PY_PROP_RO("nsubimages", &ImageBuf::nsubimages)
+ .OIIO_PY_PROP_RO("miplevel", &ImageBuf::miplevel)
+ .OIIO_PY_PROP_RO("nmiplevels", &ImageBuf::nmiplevels)
+ .OIIO_PY_PROP_RO("nchannels", &ImageBuf::nchannels)
+ .OIIO_PY_PROP_RW("orientation", &ImageBuf::orientation,
+ &ImageBuf::set_orientation)
+ .OIIO_PY_PROP_RO("oriented_width", &ImageBuf::oriented_width)
+ .OIIO_PY_PROP_RO("oriented_height", &ImageBuf::oriented_height)
+ .OIIO_PY_PROP_RO("oriented_x", &ImageBuf::oriented_x)
+ .OIIO_PY_PROP_RO("oriented_y", &ImageBuf::oriented_y)
+ .OIIO_PY_PROP_RO("oriented_full_width", &ImageBuf::oriented_full_width)
+ .OIIO_PY_PROP_RO("oriented_full_height",
+ &ImageBuf::oriented_full_height)
+ .OIIO_PY_PROP_RO("oriented_full_x", &ImageBuf::oriented_full_x)
+ .OIIO_PY_PROP_RO("oriented_full_y", &ImageBuf::oriented_full_y)
+ .OIIO_PY_PROP_RO("xbegin", &ImageBuf::xbegin)
+ .OIIO_PY_PROP_RO("xend", &ImageBuf::xend)
+ .OIIO_PY_PROP_RO("ybegin", &ImageBuf::ybegin)
+ .OIIO_PY_PROP_RO("yend", &ImageBuf::yend)
+ .OIIO_PY_PROP_RO("zbegin", &ImageBuf::zbegin)
+ .OIIO_PY_PROP_RO("zend", &ImageBuf::zend)
+ .OIIO_PY_PROP_RO("xmin", &ImageBuf::xmin)
+ .OIIO_PY_PROP_RO("xmax", &ImageBuf::xmax)
+ .OIIO_PY_PROP_RO("ymin", &ImageBuf::ymin)
+ .OIIO_PY_PROP_RO("ymax", &ImageBuf::ymax)
+ .OIIO_PY_PROP_RO("zmin", &ImageBuf::zmin)
+ .OIIO_PY_PROP_RO("zmax", &ImageBuf::zmax)
+ .OIIO_PY_PROP_RO("roi", &ImageBuf::roi)
+ .OIIO_PY_PROP_RW("roi_full", &ImageBuf::roi_full,
+ &ImageBuf::set_roi_full)
.def("set_origin", &ImageBuf::set_origin, "x"_a, "y"_a, "z"_a = 0)
.def("set_full", &ImageBuf::set_full)
- .def_property_readonly("pixels_valid", &ImageBuf::pixels_valid)
- .def_property_readonly("pixeltype", &ImageBuf::pixeltype)
- .def_property_readonly("has_error", &ImageBuf::has_error)
+ .OIIO_PY_PROP_RO("pixels_valid", &ImageBuf::pixels_valid)
+ .OIIO_PY_PROP_RO("pixeltype", &ImageBuf::pixeltype)
+ .OIIO_PY_PROP_RO("has_error", &ImageBuf::has_error)
.def(
"geterror",
[](const ImageBuf& self, bool clear) {
@@ -506,7 +522,7 @@ declare_imagebuf(py::module& m)
"roi"_a = ROI::All())
.def("set_pixels", &ImageBuf_set_pixels_buffer, "roi"_a, "pixels"_a)
- .def_property_readonly("deep", &ImageBuf::deep)
+ .OIIO_PY_PROP_RO("deep", &ImageBuf::deep)
.def("deep_samples", &ImageBuf::deep_samples, "x"_a, "y"_a, "z"_a = 0)
.def("set_deep_samples", &ImageBuf::set_deep_samples, "x"_a, "y"_a,
"z"_a = 0, "nsamples"_a = 1)
@@ -524,7 +540,7 @@ declare_imagebuf(py::module& m)
"z"_a, "channel"_a, "sample"_a, "value"_a = 0)
.def(
"deepdata", [](ImageBuf& self) { return *self.deepdata(); },
- py::return_value_policy::reference_internal)
+ oiio_py::ref_internal)
.def("_repr_png_", &ImageBuf_repr_png)
// FIXME -- do we want to provide pixel iterators?
diff --git a/src/python/py_imagebufalgo.cpp b/src/python/py_imagebufalgo.cpp
index 894fa2deee..999397fc25 100644
--- a/src/python/py_imagebufalgo.cpp
+++ b/src/python/py_imagebufalgo.cpp
@@ -185,7 +185,7 @@ IBA_channels(ImageBuf& dst, const ImageBuf& src, py::tuple channelorder_,
py::tuple newchannelnames_, bool shuffle_channel_names,
int nthreads)
{
- size_t nchannels = (size_t)len(channelorder_);
+ size_t nchannels = (size_t)py::len(channelorder_);
if (nchannels < 1) {
dst.errorfmt("No channels selected");
return false;
@@ -195,11 +195,11 @@ IBA_channels(ImageBuf& dst, const ImageBuf& src, py::tuple channelorder_,
for (size_t i = 0; i < nchannels; ++i) {
auto orderi = channelorder_[i];
if (py::isinstance(orderi)) {
- channelorder[i] = orderi.cast();
+ channelorder[i] = py::cast(orderi);
} else if (py::isinstance(orderi)) {
- channelvalues[i] = orderi.cast();
+ channelvalues[i] = py::cast(orderi);
} else if (py::isinstance(orderi)) {
- std::string chname = orderi.cast();
+ std::string chname = oiio_py::str_to_stdstring(orderi);
for (int c = 0; c < src.nchannels(); ++c) {
if (src.spec().channelnames[c] == chname)
channelorder[i] = c;
@@ -2497,51 +2497,66 @@ IBA_demosaic(ImageBuf& dst, const ImageBuf& src,
}
void
-declare_imagebufalgo(py::module& m)
+declare_imagebufalgo(py_module& m)
{
- using namespace pybind11::literals;
- using py::arg;
-
- py::enum_(m, "NonFiniteFixMode")
- .value("NONFINITE_NONE", ImageBufAlgo::NONFINITE_NONE)
+ py::enum_ nonfinite_fix_mode(
+ m, "NonFiniteFixMode");
+ nonfinite_fix_mode.value("NONFINITE_NONE", ImageBufAlgo::NONFINITE_NONE)
.value("NONFINITE_BLACK", ImageBufAlgo::NONFINITE_BLACK)
- .value("NONFINITE_BOX3", ImageBufAlgo::NONFINITE_BOX3)
- .export_values();
-
- py::enum_(m, "MakeTextureMode")
- .value("MakeTxTexture", ImageBufAlgo::MakeTxTexture)
+ .value("NONFINITE_BOX3", ImageBufAlgo::NONFINITE_BOX3);
+#if !defined(OIIO_PY_BACKEND_NANOBIND)
+ nonfinite_fix_mode.export_values();
+#else
+ m.attr("NONFINITE_NONE") = py::cast(ImageBufAlgo::NONFINITE_NONE);
+ m.attr("NONFINITE_BLACK") = py::cast(ImageBufAlgo::NONFINITE_BLACK);
+ m.attr("NONFINITE_BOX3") = py::cast(ImageBufAlgo::NONFINITE_BOX3);
+#endif
+
+ py::enum_ make_texture_mode(
+ m, "MakeTextureMode");
+ make_texture_mode.value("MakeTxTexture", ImageBufAlgo::MakeTxTexture)
.value("MakeTxShadow", ImageBufAlgo::MakeTxShadow)
.value("MakeTxEnvLatl", ImageBufAlgo::MakeTxEnvLatl)
.value("MakeTxEnvLatlFromLightProbe",
ImageBufAlgo::MakeTxEnvLatlFromLightProbe)
- .value("MakeTxBumpWithSlopes", ImageBufAlgo::MakeTxBumpWithSlopes)
- .export_values();
+ .value("MakeTxBumpWithSlopes", ImageBufAlgo::MakeTxBumpWithSlopes);
+#if !defined(OIIO_PY_BACKEND_NANOBIND)
+ make_texture_mode.export_values();
+#else
+ m.attr("MakeTxTexture") = py::cast(ImageBufAlgo::MakeTxTexture);
+ m.attr("MakeTxShadow") = py::cast(ImageBufAlgo::MakeTxShadow);
+ m.attr("MakeTxEnvLatl") = py::cast(ImageBufAlgo::MakeTxEnvLatl);
+ m.attr("MakeTxEnvLatlFromLightProbe") = py::cast(
+ ImageBufAlgo::MakeTxEnvLatlFromLightProbe);
+ m.attr("MakeTxBumpWithSlopes") = py::cast(
+ ImageBufAlgo::MakeTxBumpWithSlopes);
+#endif
py::class_(m, "PixelStats")
.def(py::init<>())
- .def_readonly("min", &ImageBufAlgo::PixelStats::min)
- .def_readonly("max", &ImageBufAlgo::PixelStats::max)
- .def_readonly("avg", &ImageBufAlgo::PixelStats::avg)
- .def_readonly("stddev", &ImageBufAlgo::PixelStats::stddev)
- .def_readonly("nancount", &ImageBufAlgo::PixelStats::nancount)
- .def_readonly("infcount", &ImageBufAlgo::PixelStats::infcount)
- .def_readonly("finitecount", &ImageBufAlgo::PixelStats::finitecount)
- .def_readonly("sum", &ImageBufAlgo::PixelStats::sum)
- .def_readonly("sum2", &ImageBufAlgo::PixelStats::sum2);
+ .OIIO_PY_RO("min", &ImageBufAlgo::PixelStats::min)
+ .OIIO_PY_RO("max", &ImageBufAlgo::PixelStats::max)
+ .OIIO_PY_RO("avg", &ImageBufAlgo::PixelStats::avg)
+ .OIIO_PY_RO("stddev", &ImageBufAlgo::PixelStats::stddev)
+ .OIIO_PY_RO("nancount", &ImageBufAlgo::PixelStats::nancount)
+ .OIIO_PY_RO("infcount", &ImageBufAlgo::PixelStats::infcount)
+ .OIIO_PY_RO("finitecount", &ImageBufAlgo::PixelStats::finitecount)
+ .OIIO_PY_RO("sum", &ImageBufAlgo::PixelStats::sum)
+ .OIIO_PY_RO("sum2", &ImageBufAlgo::PixelStats::sum2);
py::class_(m, "CompareResults")
.def(py::init<>())
- .def_readonly("meanerror", &ImageBufAlgo::CompareResults::meanerror)
- .def_readonly("rms_error", &ImageBufAlgo::CompareResults::rms_error)
- .def_readonly("PSNR", &ImageBufAlgo::CompareResults::PSNR)
- .def_readonly("maxerror", &ImageBufAlgo::CompareResults::maxerror)
- .def_readonly("maxx", &ImageBufAlgo::CompareResults::maxx)
- .def_readonly("maxy", &ImageBufAlgo::CompareResults::maxy)
- .def_readonly("maxz", &ImageBufAlgo::CompareResults::maxz)
- .def_readonly("maxc", &ImageBufAlgo::CompareResults::maxc)
- .def_readonly("nwarn", &ImageBufAlgo::CompareResults::nwarn)
- .def_readonly("nfail", &ImageBufAlgo::CompareResults::nfail)
- .def_readonly("error", &ImageBufAlgo::CompareResults::error);
+ .OIIO_PY_RO("meanerror", &ImageBufAlgo::CompareResults::meanerror)
+ .OIIO_PY_RO("rms_error", &ImageBufAlgo::CompareResults::rms_error)
+ .OIIO_PY_RO("PSNR", &ImageBufAlgo::CompareResults::PSNR)
+ .OIIO_PY_RO("maxerror", &ImageBufAlgo::CompareResults::maxerror)
+ .OIIO_PY_RO("maxx", &ImageBufAlgo::CompareResults::maxx)
+ .OIIO_PY_RO("maxy", &ImageBufAlgo::CompareResults::maxy)
+ .OIIO_PY_RO("maxz", &ImageBufAlgo::CompareResults::maxz)
+ .OIIO_PY_RO("maxc", &ImageBufAlgo::CompareResults::maxc)
+ .OIIO_PY_RO("nwarn", &ImageBufAlgo::CompareResults::nwarn)
+ .OIIO_PY_RO("nfail", &ImageBufAlgo::CompareResults::nfail)
+ .OIIO_PY_RO("error", &ImageBufAlgo::CompareResults::error);
// Put this all inside "ImageBufAlgo"
py::class_(m, "ImageBufAlgo")
diff --git a/src/python/py_imagecache.cpp b/src/python/py_imagecache.cpp
index 706f2597e9..89cbe59032 100644
--- a/src/python/py_imagecache.cpp
+++ b/src/python/py_imagecache.cpp
@@ -69,10 +69,8 @@ ImageCacheWrap::get_pixels(const std::string& filename_, int subimage,
void
-declare_imagecache(py::module& m)
+declare_imagecache(py_module& m)
{
- using namespace pybind11::literals;
-
py::class_(m, "ImageCache")
.def(py::init(), "shared"_a = true)
// .def_static("create", &ImageCacheWrap::create,
@@ -144,10 +142,10 @@ declare_imagecache(py::module& m)
// .def("release_tile", &ImageCacheWrap::release_tile)
// .def("tile_pixels", &ImageCacheWrap::tile_pixels)
- .def_property_readonly("has_error",
- [](ImageCacheWrap& self) {
- return self.m_cache->has_error();
- })
+ .OIIO_PY_PROP_RO("has_error",
+ [](ImageCacheWrap& self) {
+ return self.m_cache->has_error();
+ })
.def(
"geterror",
[](ImageCacheWrap& self, bool clear) {
diff --git a/src/python/py_imageinput.cpp b/src/python/py_imageinput.cpp
index 6f930f6d40..092cc61c30 100644
--- a/src/python/py_imageinput.cpp
+++ b/src/python/py_imageinput.cpp
@@ -173,10 +173,8 @@ ImageInput_read_native_deep_image(ImageInput& self, int subimage, int miplevel)
void
-declare_imageinput(py::module& m)
+declare_imageinput(py_module& m)
{
- using namespace pybind11::literals;
-
py::class_(m, "ImageInput")
.def_static(
"create",
@@ -335,7 +333,7 @@ declare_imageinput(py::module& m)
return buf;
},
"subimage"_a = 0)
- .def_property_readonly("has_error", &ImageInput::has_error)
+ .OIIO_PY_PROP_RO("has_error", &ImageInput::has_error)
.def(
"geterror",
[](ImageInput& self, bool clear) {
diff --git a/src/python/py_imageoutput.cpp b/src/python/py_imageoutput.cpp
index 5e0fc094d2..c6e09fa8b3 100644
--- a/src/python/py_imageoutput.cpp
+++ b/src/python/py_imageoutput.cpp
@@ -13,14 +13,14 @@ bool
ImageOutput_open_specs(ImageOutput& self, const std::string& name,
py::tuple& specs)
{
- const size_t length = len(specs);
+ const size_t length = py::len(specs);
if (length == 0)
return false;
std::vector Cspecs(length);
for (size_t i = 0; i < length; ++i) {
auto s = specs[i];
if (py::isinstance(s))
- Cspecs[i] = s.cast();
+ Cspecs[i] = py::cast(s);
else
return false; // Tuple item was not an ImageSpec
}
@@ -30,14 +30,16 @@ ImageOutput_open_specs(ImageOutput& self, const std::string& name,
bool
-ImageOutput_write_scanline(ImageOutput& self, int y, int z, py::buffer& buffer)
+ImageOutput_write_scanline(ImageOutput& self, int y, int z,
+ const py::object& buffer)
{
const ImageSpec& spec(self.spec());
if (spec.tile_width != 0) {
self.errorfmt("Cannot write scanlines to a tiled file.");
return false;
}
- oiio_bufinfo buf(buffer.request(), spec.nchannels, spec.width, 1, 1, 1);
+ oiio_bufinfo buf = oiio_bufinfo_from_object(buffer, spec.nchannels,
+ spec.width, 1, 1, 1);
if (!buf.data || buf.error.size()) {
self.errorfmt("Pixel data array error: {}",
buf.error.size() ? buf.error.c_str() : "unspecified");
@@ -56,15 +58,16 @@ ImageOutput_write_scanline(ImageOutput& self, int y, int z, py::buffer& buffer)
bool
ImageOutput_write_scanlines(ImageOutput& self, int ybegin, int yend, int z,
- py::buffer& buffer)
+ const py::object& buffer)
{
const ImageSpec& spec(self.spec());
if (spec.tile_width != 0) {
self.errorfmt("Cannot write scanlines to a filed file.");
return false;
}
- oiio_bufinfo buf(buffer.request(), spec.nchannels, spec.width,
- yend - ybegin, 1, 2);
+ oiio_bufinfo buf = oiio_bufinfo_from_object(buffer, spec.nchannels,
+ spec.width, yend - ybegin, 1,
+ 2);
if (!buf.data || buf.error.size()) {
self.errorfmt("Pixel data array error: {}",
buf.error.size() ? buf.error.c_str() : "unspecified");
@@ -84,16 +87,17 @@ ImageOutput_write_scanlines(ImageOutput& self, int ybegin, int yend, int z,
bool
ImageOutput_write_tile(ImageOutput& self, int x, int y, int z,
- py::buffer& buffer)
+ const py::object& buffer)
{
const ImageSpec& spec(self.spec());
if (spec.tile_width == 0) {
self.errorfmt("Cannot write tiles to a scanline file.");
return false;
}
- oiio_bufinfo buf(buffer.request(), spec.nchannels, spec.tile_width,
- spec.tile_height, spec.tile_depth,
- spec.tile_depth > 1 ? 3 : 2);
+ oiio_bufinfo buf
+ = oiio_bufinfo_from_object(buffer, spec.nchannels, spec.tile_width,
+ spec.tile_height, spec.tile_depth,
+ spec.tile_depth > 1 ? 3 : 2);
if (!buf.data || buf.error.size()) {
self.errorfmt("Pixel data array error: {}",
buf.error.size() ? buf.error.c_str() : "unspecified");
@@ -112,15 +116,18 @@ ImageOutput_write_tile(ImageOutput& self, int x, int y, int z,
bool
ImageOutput_write_tiles(ImageOutput& self, int xbegin, int xend, int ybegin,
- int yend, int zbegin, int zend, py::buffer& buffer)
+ int yend, int zbegin, int zend,
+ const py::object& buffer)
{
const ImageSpec& spec(self.spec());
if (spec.tile_width == 0) {
self.errorfmt("Cannot write tiles to a scanline file.");
return false;
}
- oiio_bufinfo buf(buffer.request(), spec.nchannels, xend - xbegin,
- yend - ybegin, zend - zbegin, spec.tile_depth > 1 ? 3 : 2);
+ oiio_bufinfo buf = oiio_bufinfo_from_object(buffer, spec.nchannels,
+ xend - xbegin, yend - ybegin,
+ zend - zbegin,
+ spec.tile_depth > 1 ? 3 : 2);
if (!buf.data || buf.error.size()) {
self.errorfmt("Pixel data array error: {}",
buf.error.size() ? buf.error.c_str() : "unspecified");
@@ -141,11 +148,13 @@ ImageOutput_write_tiles(ImageOutput& self, int xbegin, int xend, int ybegin,
bool
-ImageOutput_write_image(ImageOutput& self, py::buffer& buffer)
+ImageOutput_write_image(ImageOutput& self, const py::object& buffer)
{
const ImageSpec& spec(self.spec());
- oiio_bufinfo buf(buffer.request(), spec.nchannels, spec.width, spec.height,
- spec.depth, spec.depth > 1 ? 3 : 2);
+ oiio_bufinfo buf = oiio_bufinfo_from_object(buffer, spec.nchannels,
+ spec.width, spec.height,
+ spec.depth,
+ spec.depth > 1 ? 3 : 2);
if (!buf.data || buf.size < spec.image_pixels() * spec.nchannels
|| buf.error.size()) {
self.errorfmt("Pixel data array error: {}",
@@ -189,10 +198,8 @@ ImageOutput_write_deep_image(ImageOutput& self, const DeepData& deepdata)
void
-declare_imageoutput(py::module& m)
+declare_imageoutput(py_module& m)
{
- using namespace pybind11::literals;
-
py::class_(m, "ImageOutput")
.def_static(
"create",
@@ -250,7 +257,7 @@ declare_imageoutput(py::module& m)
})
.def("copy_image", [](ImageOutput& self,
ImageInput& in) { return self.copy_image(&in); })
- .def_property_readonly("has_error", &ImageOutput::has_error)
+ .OIIO_PY_PROP_RO("has_error", &ImageOutput::has_error)
.def(
"geterror",
[](ImageOutput& self, bool clear) {
diff --git a/src/python/py_imagespec.cpp b/src/python/py_imagespec.cpp
index ea64538f4d..3e1643c135 100644
--- a/src/python/py_imagespec.cpp
+++ b/src/python/py_imagespec.cpp
@@ -159,7 +159,7 @@ declare_imagespec(py_module& m)
int chan) { return spec.channelformat(chan); })
.def("channel_name",
[](const ImageSpec& spec, int chan) {
- return oiio_py::str(std::string(spec.channel_name(chan)));
+ return oiio_py::str(spec.channel_name(chan));
})
.def("channelindex",
[](const ImageSpec& spec, const std::string& name) {
@@ -200,8 +200,7 @@ declare_imagespec(py_module& m)
"get_string_attribute",
[](const ImageSpec& spec, const std::string& name,
const std::string& def) {
- return oiio_py::str(
- std::string(spec.get_string_attribute(name, def)));
+ return oiio_py::str(spec.get_string_attribute(name, def));
},
"name"_a, "defaultval"_a = "")
.def(
@@ -234,8 +233,7 @@ declare_imagespec(py_module& m)
.def_static(
"metadata_val",
[](const ParamValue& p, bool human) {
- return oiio_py::str(
- std::string(ImageSpec::metadata_val(p, human)));
+ return oiio_py::str(ImageSpec::metadata_val(p, human));
},
"param"_a, "human"_a = false)
.def(
@@ -252,13 +250,11 @@ declare_imagespec(py_module& m)
verb = ImageSpec::SerialDetailed;
else if (Strutil::iequals(verbose, "detailedhuman"))
verb = ImageSpec::SerialDetailedHuman;
- return oiio_py::str(std::string(spec.serialize(fmt, verb)));
+ return oiio_py::str(spec.serialize(fmt, verb));
},
"format"_a = "text", "verbose"_a = "detailed")
.def("to_xml",
- [](const ImageSpec& spec) {
- return oiio_py::str(std::string(spec.to_xml()));
- })
+ [](const ImageSpec& spec) { return oiio_py::str(spec.to_xml()); })
.def("from_xml",
[](ImageSpec& self, const std::string& xml) {
self.from_xml(xml.c_str());
diff --git a/src/python/py_oiio.cpp b/src/python/py_oiio.cpp
index 464162a5aa..35e029c7ae 100644
--- a/src/python/py_oiio.cpp
+++ b/src/python/py_oiio.cpp
@@ -4,12 +4,13 @@
#include "py_oiio.h"
-#include
+#include
+#include
#if defined(OIIO_PY_BACKEND_NANOBIND)
# include
-# include
#endif
+#include
namespace PyOpenImageIO {
@@ -71,18 +72,60 @@ typedesc_from_python_array_code(string_view code)
-#ifndef OIIO_PY_BACKEND_NANOBIND
+oiio_py_buffer_view
+oiio_py_request_buffer(const py::object& obj)
+{
+ oiio_py_buffer_view info;
+#if defined(OIIO_PY_BACKEND_NANOBIND)
+ Py_buffer view;
+ if (PyObject_GetBuffer(obj.ptr(), &view, PyBUF_STRIDES | PyBUF_FORMAT)
+ != 0) {
+ PyErr_Clear();
+ return info;
+ }
+ info.ptr = view.buf;
+ info.itemsize = view.itemsize;
+ info.ndim = view.ndim;
+ if (view.ndim > 0) {
+ info.shape.assign(view.shape, view.shape + view.ndim);
+ info.strides.assign(view.strides, view.strides + view.ndim);
+ info.size = 1;
+ for (int i = 0; i < view.ndim; ++i) {
+ info.size *= view.shape[i];
+ }
+ } else {
+ info.size = view.len / std::max(view.itemsize, 1);
+ }
+ if (view.format && view.format[0]) {
+ info.format = view.format;
+ }
+ PyBuffer_Release(&view);
+#else
+ const py::buffer_info req = py::cast(obj).request();
+ info.ptr = req.ptr;
+ info.itemsize = req.itemsize;
+ info.size = req.size;
+ info.ndim = req.ndim;
+ info.shape.assign(req.shape.begin(), req.shape.end());
+ info.strides.assign(req.strides.begin(), req.strides.end());
+ info.format = req.format;
+#endif
+ return info;
+}
+
+
-oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf)
+oiio_bufinfo::oiio_bufinfo(const oiio_py_buffer_view& pybuf)
{
- if (pybuf.format.size())
+ if (pybuf.format.size()) {
format = typedesc_from_python_array_code(pybuf.format);
+ }
if (format != TypeUnknown) {
data = pybuf.ptr;
xstride = format.size();
size = 1;
for (int i = pybuf.ndim - 1; i >= 0; --i) {
- if (pybuf.strides[i] != py::ssize_t(size * xstride)) {
+ if (pybuf.strides[i] != Py_ssize_t(size * xstride)) {
// Just can't handle non-contiguous strides
format = TypeUnknown;
size = 0;
@@ -95,11 +138,12 @@ oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf)
-oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf, int nchans, int width,
- int height, int depth, int pixeldims)
+oiio_bufinfo::oiio_bufinfo(const oiio_py_buffer_view& pybuf, int nchans,
+ int width, int height, int depth, int pixeldims)
{
- if (pybuf.format.size())
+ if (pybuf.format.size()) {
format = typedesc_from_python_array_code(pybuf.format);
+ }
if (size_t(pybuf.itemsize) != format.size()
|| pybuf.size
!= int64_t(width) * int64_t(height) * int64_t(depth * nchans)) {
@@ -141,10 +185,10 @@ oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf, int nchans, int width,
// Somebody collapsed a dimension. Is it [pixel][c] with x&y
// combined, or is it [y][xpixel] with channels mushed together?
if (pybuf.shape[0] == int64_t(width) * int64_t(height)
- && pybuf.shape[1] == nchans)
+ && pybuf.shape[1] == nchans) {
xstride = pybuf.strides[0];
- else if (pybuf.shape[0] == height
- && pybuf.shape[1] == int64_t(width) * int64_t(nchans)) {
+ } else if (pybuf.shape[0] == height
+ && pybuf.shape[1] == int64_t(width) * int64_t(nchans)) {
ystride = pybuf.strides[1];
xstride = pybuf.strides[0] * nchans;
} else {
@@ -163,7 +207,8 @@ oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf, int nchans, int width,
format = TypeUnknown; // No idea what's going on -- error
error = Strutil::fmt::format(
"Python array shape is [{:,}] but expecting h={}, w={}, ch={}",
- cspan(pybuf.shape), height, width, nchans);
+ cspan(pybuf.shape.data(), pybuf.shape.size()),
+ height, width, nchans);
}
} else if (pixeldims == 1) {
// Reading a 1D scanline span
@@ -187,18 +232,146 @@ oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf, int nchans, int width,
pybuf.ndim);
}
- if (nchans > 1 && format.size()
+ if (nchans > 1 && format.size() && pybuf.ndim > 0
&& size_t(pybuf.strides.back()) != format.size()) {
format = TypeUnknown; // can't handle noncontig channels
error = "Can't handle numpy array with noncontiguous channels";
}
- if (format != TypeUnknown)
+ if (format != TypeUnknown) {
data = pybuf.ptr;
+ }
}
-#endif // !OIIO_PY_BACKEND_NANOBIND
+namespace {
+
+ // Shared image layout → NumPy shape (C-contiguous pixel data).
+ void numpy_image_shape(std::vector& shape, int dims, size_t chans,
+ size_t width, size_t height, size_t depth,
+ size_t size)
+ {
+ if (dims == 4) { // volumetric
+ shape.assign({ depth, height, width, chans });
+ } else if (dims == 3 && depth == 1) { // 2D+channels
+ shape.assign({ height, width, chans });
+ } else if (dims == 2 && depth == 1
+ && height == 1) { // 1D (scanline) + channels
+ shape.assign({ width, chans });
+ } else { // punt -- make it a 1D array
+ shape.assign({ size });
+ }
+ }
+
+
+
+ template
+ py::object make_numpy_array_t(T* data, int dims, size_t chans, size_t width,
+ size_t height, size_t depth)
+ {
+ const size_t size = chans * width * height * depth;
+ T* mem = data ? data : new T[size];
+ std::vector shape;
+ numpy_image_shape(shape, dims, chans, width, height, depth, size);
+#if defined(OIIO_PY_BACKEND_NANOBIND)
+ py::capsule owner(mem, [](void* p) noexcept {
+ delete[] reinterpret_cast(p);
+ });
+ // nullptr strides → C-contiguous (element strides).
+ return py::cast(py::ndarray(mem, shape.size(),
+ shape.data(), owner),
+ py::rv_policy::move);
+#else
+ // Create a Python object that will free the allocated memory when
+ // destroyed. Shape-only ctor assumes C-contiguous byte layout.
+ py::capsule free_when_done(mem, [](void* f) {
+ delete[] (reinterpret_cast(f));
+ });
+ return py::array_t(shape, mem, free_when_done);
+#endif
+ }
+
+
+
+#if defined(OIIO_PY_BACKEND_NANOBIND)
+ // Build a real numpy float16 array (nanobind has no half dtype).
+ template<>
+ py::object make_numpy_array_t(half* data, int dims, size_t chans,
+ size_t width, size_t height,
+ size_t depth)
+ {
+ const size_t size = chans * width * height * depth;
+ half* mem = data ? data : new half[size];
+ std::vector shape;
+ numpy_image_shape(shape, dims, chans, width, height, depth, size);
+ py::object np = py::module_::import_("numpy");
+ py::list shape_list;
+ for (size_t d : shape)
+ shape_list.append(d);
+ py::object arr = np.attr("empty")(py::tuple(shape_list), "float16");
+ // Write through a uint16 view of the same buffer.
+ auto u16 = py::cast>(
+ arr.attr("view")(np.attr("uint16")));
+ std::memcpy(u16.data(), mem, size * sizeof(half));
+ delete[] mem;
+ return arr;
+ }
+#endif
+
+} // namespace
+
+
+
+template
+py::object
+make_numpy_array(T* data, int dims, size_t chans, size_t width, size_t height,
+ size_t depth)
+{
+ return make_numpy_array_t(data, dims, chans, width, height, depth);
+}
+
+
+
+py::object
+make_numpy_array(TypeDesc format, void* data, int dims, size_t chans,
+ size_t width, size_t height, size_t depth)
+{
+ if (format == TypeDesc::FLOAT) {
+ return make_numpy_array((float*)data, dims, chans, width, height,
+ depth);
+ }
+ if (format == TypeDesc::UINT8) {
+ return make_numpy_array((unsigned char*)data, dims, chans, width,
+ height, depth);
+ }
+ if (format == TypeDesc::UINT16) {
+ return make_numpy_array((unsigned short*)data, dims, chans, width,
+ height, depth);
+ }
+ if (format == TypeDesc::INT8) {
+ return make_numpy_array((char*)data, dims, chans, width, height, depth);
+ }
+ if (format == TypeDesc::INT16) {
+ return make_numpy_array((short*)data, dims, chans, width, height,
+ depth);
+ }
+ if (format == TypeDesc::DOUBLE) {
+ return make_numpy_array((double*)data, dims, chans, width, height,
+ depth);
+ }
+ if (format == TypeDesc::HALF) {
+ return make_numpy_array((half*)data, dims, chans, width, height, depth);
+ }
+ if (format == TypeDesc::UINT) {
+ return make_numpy_array((unsigned int*)data, dims, chans, width, height,
+ depth);
+ }
+ if (format == TypeDesc::INT) {
+ return make_numpy_array((int*)data, dims, chans, width, height, depth);
+ }
+ delete[] (char*)data;
+ return py::none();
+}
@@ -288,14 +461,6 @@ declare_global_bindings(py_module& m)
declare_paramvalue(m);
declare_imagespec(m);
declare_roi(m);
-}
-
-
-
-#ifndef OIIO_PY_BACKEND_NANOBIND
-static void
-declare_pybind_bindings(py_module& m)
-{
declare_deepdata(m);
declare_colorconfig(m);
@@ -314,7 +479,6 @@ declare_pybind_bindings(py_module& m)
declare_imagebufalgo(m);
}
-#endif
@@ -346,28 +510,29 @@ declare_global_attribute_functions(py_module& m)
m.def(
"get_string_attribute",
[](const std::string& name, const std::string& def) {
- return oiio_py::str(
- std::string(OIIO::get_string_attribute(name, def)));
+ return oiio_py::str(OIIO::get_string_attribute(name, def));
},
"name"_a, "defaultval"_a = "");
m.def("getattribute", &oiio_getattribute_typed, "name"_a,
"type"_a = TypeUnknown);
-}
-
-
-
-#ifndef OIIO_PY_BACKEND_NANOBIND
-static void
-declare_pybind_global_functions(py_module& m)
-{
m.def("geterror", &OIIO::geterror, "clear"_a = true);
m.def(
"get_bytes_attribute",
- [](const std::string& name, const std::string& def) {
- std::string s(OIIO::get_string_attribute(name, def));
+ [](const std::string& name, const py::object& def) {
+ // Accept str, bytes, or None (None → empty default).
+ std::string defstr;
+ if (!def.is_none()) {
+ if (PyBytes_Check(def.ptr())) {
+ defstr = oiio_py::bytes_to_stdstring(
+ py::cast(def));
+ } else {
+ defstr = oiio_py::str_to_stdstring(def);
+ }
+ }
+ std::string s(OIIO::get_string_attribute(name, defstr));
return py::bytes(s.data(), s.size());
},
- "name"_a, "defaultval"_a = "");
+ "name"_a, "defaultval"_a.none() = "");
m.def(
"set_colorspace",
[](ImageSpec& spec, const std::string& name) {
@@ -393,17 +558,12 @@ declare_pybind_global_functions(py_module& m)
},
"name"_a);
}
-#endif
static void
declare_module_attributes(py_module& m)
{
-#if defined(OIIO_PY_BACKEND_NANOBIND)
- m.attr("__version__") = OIIO_VERSION_STRING;
- m.attr("VERSION_STRING") = OIIO_VERSION_STRING;
-#else
m.attr("AutoStride") = AutoStride;
m.attr("openimageio_version") = OIIO_VERSION;
m.attr("VERSION") = OIIO_VERSION;
@@ -413,7 +573,6 @@ declare_module_attributes(py_module& m)
m.attr("VERSION_PATCH") = OIIO_VERSION_PATCH;
m.attr("INTRO_STRING") = oiio_py::str(OIIO_INTRO_STRING);
m.attr("__version__") = oiio_py::str(OIIO_VERSION_STRING);
-#endif
}
@@ -451,9 +610,7 @@ OIIO_DECLARE_PYMODULE(PYMODULE_NAME)
Sysutil::setup_crash_stacktrace("stdout");
declare_global_bindings(m);
- declare_pybind_bindings(m);
declare_global_attribute_functions(m);
- declare_pybind_global_functions(m);
declare_module_attributes(m);
}
diff --git a/src/python/py_oiio.h b/src/python/py_oiio.h
index 36e61dc422..80e4b75746 100644
--- a/src/python/py_oiio.h
+++ b/src/python/py_oiio.h
@@ -29,6 +29,8 @@
# undef copysign
#endif
+#include
+
#include
#include
#include
@@ -95,23 +97,23 @@ using namespace OIIO;
// clang-format off
-void declare_imagespec (py_module& m);
-void declare_imageinput (py_module& m);
-void declare_imageoutput (py_module& m);
-void declare_typedesc (py_module& m);
-void declare_roi (py_module& m);
-void declare_deepdata (py_module& m);
-void declare_colorconfig (py_module& m);
-void declare_imagecache (py_module& m);
-void declare_imagebuf (py_module& m);
-void declare_imagebufalgo (py_module& m);
-void declare_paramvalue (py_module& m);
-void declare_global (py_module& m);
-void declare_wrap (py_module& m);
-void declare_mipmpode (py_module& m);
-void declare_interpmode (py_module& m);
-void declare_textureopt (py_module& m);
-void declare_texturesystem (py_module& m);
+void declare_imagespec(py_module& m);
+void declare_imageinput(py_module& m);
+void declare_imageoutput(py_module& m);
+void declare_typedesc(py_module& m);
+void declare_roi(py_module& m);
+void declare_deepdata(py_module& m);
+void declare_colorconfig(py_module& m);
+void declare_imagecache(py_module& m);
+void declare_imagebuf(py_module& m);
+void declare_imagebufalgo(py_module& m);
+void declare_paramvalue(py_module& m);
+void declare_global(py_module& m);
+void declare_wrap(py_module& m);
+void declare_mipmpode(py_module& m);
+void declare_interpmode(py_module& m);
+void declare_textureopt(py_module& m);
+void declare_texturesystem(py_module& m);
// bool PyProgressCallback(void*, float);
// object C_array_to_Python_array (const char *data, TypeDesc type, size_t size);
@@ -145,9 +147,21 @@ template<> struct PyTypeForCType { typedef py::str type; };
// clang-format on
-#ifndef OIIO_PY_BACKEND_NANOBIND
-// Struct that holds OIIO style buffer info, constructed from
-// py::buffer_info
+// Buffer view shared by both backends (pybind buffer_info / Py_buffer).
+struct oiio_py_buffer_view {
+ void* ptr = nullptr;
+ Py_ssize_t itemsize = 0;
+ Py_ssize_t size = 0;
+ int ndim = 0;
+ std::vector shape;
+ std::vector strides;
+ std::string format;
+};
+
+oiio_py_buffer_view
+oiio_py_request_buffer(const py::object& obj);
+
+// Struct that holds OIIO style buffer info, constructed from a buffer object.
struct oiio_bufinfo {
TypeDesc format = TypeUnknown;
void* data = nullptr;
@@ -155,18 +169,33 @@ struct oiio_bufinfo {
size_t size = 0;
std::string error;
+ oiio_bufinfo() = default;
// Just raw buffer, no idea what to expect, treat like a flat array.
// Only works for "contiguous" buffers.
- oiio_bufinfo(const py::buffer_info& pybuf);
-
+ oiio_bufinfo(const oiio_py_buffer_view& pybuf);
// Expect a certain layout, figure out how to make sense of the buffer.
- oiio_bufinfo(const py::buffer_info& pybuf, int nchans, int width,
+ oiio_bufinfo(const oiio_py_buffer_view& pybuf, int nchans, int width,
int height, int depth, int pixeldims);
- // Retrieve presumed contiguous data value index i.
- template T dataval(size_t i) { return ((const T*)data)[i]; }
+ template T dataval(size_t i)
+ { // Retrieve presumed contiguous data value index i.
+ return ((const T*)data)[i];
+ }
};
-#endif
+
+inline oiio_bufinfo
+oiio_bufinfo_from_object(const py::object& obj)
+{
+ return oiio_bufinfo(oiio_py_request_buffer(obj));
+}
+
+inline oiio_bufinfo
+oiio_bufinfo_from_object(const py::object& obj, int nchans, int width,
+ int height, int depth, int pixeldims)
+{
+ return oiio_bufinfo(oiio_py_request_buffer(obj), nchans, width, height,
+ depth, pixeldims);
+}
@@ -352,28 +381,9 @@ template
inline bool
py_buffer_to_stdvector(std::vector& vals, const py::object& obj)
{
- Py_buffer view;
- if (PyObject_GetBuffer(obj.ptr(), &view, PyBUF_FORMAT | PyBUF_C_CONTIGUOUS)
- != 0) {
- PyErr_Clear();
- return false;
- }
-
- bool ok = view.itemsize > 0 && view.len % view.itemsize == 0;
- if (!ok) {
- PyBuffer_Release(&view);
- return false;
- }
-
- TypeDesc format = TypeUnknown;
- if (view.format && view.format[0]) {
- format = typedesc_from_python_array_code(view.format);
- }
-
- const size_t count = static_cast(view.len) / view.itemsize;
- ok = buffer_format_to_stdvector(vals, format, view.buf, count);
- PyBuffer_Release(&view);
- return ok;
+ oiio_bufinfo binfo(oiio_py_request_buffer(obj));
+ return buffer_format_to_stdvector(vals, binfo.format, binfo.data,
+ binfo.size);
}
@@ -401,7 +411,7 @@ inline bool
py_buffer_to_stdvector(std::vector& vals, const py::buffer& obj)
{
OIIO_DASSERT(py::isinstance(obj));
- oiio_bufinfo binfo(obj.request());
+ oiio_bufinfo binfo(oiio_py_request_buffer(py::cast(obj)));
return buffer_format_to_stdvector(vals, binfo.format, binfo.data,
binfo.size);
}
@@ -647,88 +657,17 @@ getattribute_typed(const T& obj, const std::string& name,
-#ifndef OIIO_PY_BACKEND_NANOBIND
-// TRANSFERS ownership of the data pointer!
+// TRANSFERS ownership of the data pointer! Multi-dimensional numpy arrays.
// N.B. There is some evidence that this doesn't work properly with
// non-float arrays. Maybe a limitation of pybind11?
template
-inline py::array_t
+py::object
make_numpy_array(T* data, int dims, size_t chans, size_t width, size_t height,
- size_t depth = 1)
-{
- const size_t size = chans * width * height * depth;
- T* mem = data ? data : new T[size];
+ size_t depth = 1);
- // Create a Python object that will free the allocated memory when
- // destroyed:
- py::capsule free_when_done(mem, [](void* f) {
- delete[] (reinterpret_cast(f));
- });
-
- std::vector shape, strides;
- if (dims == 4) { // volumetric
- shape.assign({ depth, height, width, chans });
- strides.assign({ height * width * chans * sizeof(T),
- width * chans * sizeof(T), chans * sizeof(T),
- sizeof(T) });
- } else if (dims == 3 && depth == 1) { // 2D+channels
- shape.assign({ height, width, chans });
- strides.assign(
- { width * chans * sizeof(T), chans * sizeof(T), sizeof(T) });
- } else if (dims == 2 && depth == 1
- && height == 1) { // 1D (scanline) + channels
- shape.assign({ width, chans });
- strides.assign({ chans * sizeof(T), sizeof(T) });
- } else { // punt -- make it a 1D array
- shape.assign({ size });
- strides.assign({ sizeof(T) });
- }
- return py::array_t(shape, strides, mem, free_when_done);
-}
-
-
-
-inline py::object
+py::object
make_numpy_array(TypeDesc format, void* data, int dims, size_t chans,
- size_t width, size_t height, size_t depth = 1)
-{
- if (format == TypeDesc::FLOAT) {
- return make_numpy_array((float*)data, dims, chans, width, height,
- depth);
- }
- if (format == TypeDesc::UINT8) {
- return make_numpy_array((unsigned char*)data, dims, chans, width,
- height, depth);
- }
- if (format == TypeDesc::UINT16) {
- return make_numpy_array((unsigned short*)data, dims, chans, width,
- height, depth);
- }
- if (format == TypeDesc::INT8) {
- return make_numpy_array((char*)data, dims, chans, width, height, depth);
- }
- if (format == TypeDesc::INT16) {
- return make_numpy_array((short*)data, dims, chans, width, height,
- depth);
- }
- if (format == TypeDesc::DOUBLE) {
- return make_numpy_array((double*)data, dims, chans, width, height,
- depth);
- }
- if (format == TypeDesc::HALF) {
- return make_numpy_array((half*)data, dims, chans, width, height, depth);
- }
- if (format == TypeDesc::UINT) {
- return make_numpy_array((unsigned int*)data, dims, chans, width, height,
- depth);
- }
- if (format == TypeDesc::INT) {
- return make_numpy_array((int*)data, dims, chans, width, height, depth);
- }
- delete[] (char*)data;
- return py::none();
-}
-#endif
+ size_t width, size_t height, size_t depth = 1);
diff --git a/src/python/py_texturesys.cpp b/src/python/py_texturesys.cpp
index e24b1f4a46..a10a4d95f1 100644
--- a/src/python/py_texturesys.cpp
+++ b/src/python/py_texturesys.cpp
@@ -63,7 +63,7 @@ class TextureSystemWrap {
void
-declare_wrap(py::module& m)
+declare_wrap(py_module& m)
{
py::enum_(m, "Wrap")
.value("Default", Tex::Wrap::Default)
@@ -79,7 +79,7 @@ declare_wrap(py::module& m)
void
-declare_mipmpode(py::module& m)
+declare_mipmpode(py_module& m)
{
py::enum_(m, "MipMode")
.value("Default", Tex::MipMode::Default)
@@ -92,7 +92,7 @@ declare_mipmpode(py::module& m)
void
-declare_interpmode(py::module& m)
+declare_interpmode(py_module& m)
{
py::enum_(m, "InterpMode")
.value("Closest", Tex::InterpMode::Closest)
@@ -104,13 +104,13 @@ declare_interpmode(py::module& m)
void
-declare_textureopt(py::module& m)
+declare_textureopt(py_module& m)
{
py::class_(m, "TextureOpt")
.def(py::init<>())
- .def_readwrite("firstchannel", &TextureOptWrap::firstchannel)
- .def_readwrite("subimage", &TextureOptWrap::subimage)
- .def_property(
+ .OIIO_PY_RW("firstchannel", &TextureOptWrap::firstchannel)
+ .OIIO_PY_RW("subimage", &TextureOptWrap::subimage)
+ .OIIO_PY_PROP_RW(
"subimagename",
[](const TextureOptWrap& texopt) {
return std::string(texopt.subimagename);
@@ -118,19 +118,19 @@ declare_textureopt(py::module& m)
[](TextureOptWrap& texopt, const std::string& subimagename) {
texopt.subimagename = subimagename;
})
- .def_property(
+ .OIIO_PY_PROP_RW(
"swrap",
[](const TextureOptWrap& texopt) { return (Tex::Wrap)texopt.swrap; },
[](TextureOptWrap& texopt, const Tex::Wrap wrap) {
texopt.swrap = (TextureOpt::Wrap)wrap;
})
- .def_property(
+ .OIIO_PY_PROP_RW(
"twrap",
[](const TextureOptWrap& texopt) { return (Tex::Wrap)texopt.twrap; },
[](TextureOptWrap& texopt, const Tex::Wrap wrap) {
texopt.twrap = (TextureOpt::Wrap)wrap;
})
- .def_property(
+ .OIIO_PY_PROP_RW(
"mipmode",
[](const TextureOptWrap& texopt) {
return (Tex::MipMode)texopt.mipmode;
@@ -138,7 +138,7 @@ declare_textureopt(py::module& m)
[](TextureOptWrap& texopt, const Tex::MipMode mipmode) {
texopt.mipmode = (TextureOpt::MipMode)mipmode;
})
- .def_property(
+ .OIIO_PY_PROP_RW(
"interpmode",
[](const TextureOptWrap& texopt) {
return (Tex::InterpMode)texopt.interpmode;
@@ -146,33 +146,30 @@ declare_textureopt(py::module& m)
[](TextureOptWrap& texopt, const Tex::InterpMode interp) {
texopt.interpmode = (TextureOpt::InterpMode)interp;
})
- .def_readwrite("anisotropic", &TextureOptWrap::anisotropic)
- .def_readwrite("conservative_filter",
- &TextureOptWrap::conservative_filter)
- .def_readwrite("sblur", &TextureOptWrap::sblur)
- .def_readwrite("tblur", &TextureOptWrap::tblur)
- .def_readwrite("swidth", &TextureOptWrap::swidth)
- .def_readwrite("twidth", &TextureOptWrap::twidth)
- .def_readwrite("fill", &TextureOptWrap::fill)
- .def_property("missingcolor", &TextureOptWrap::get_missingcolor,
- &TextureOptWrap::set_missingcolor)
- .def_readwrite("rnd", &TextureOptWrap::rnd)
- .def_property(
+ .OIIO_PY_RW("anisotropic", &TextureOptWrap::anisotropic)
+ .OIIO_PY_RW("conservative_filter", &TextureOptWrap::conservative_filter)
+ .OIIO_PY_RW("sblur", &TextureOptWrap::sblur)
+ .OIIO_PY_RW("tblur", &TextureOptWrap::tblur)
+ .OIIO_PY_RW("swidth", &TextureOptWrap::swidth)
+ .OIIO_PY_RW("twidth", &TextureOptWrap::twidth)
+ .OIIO_PY_RW("fill", &TextureOptWrap::fill)
+ .OIIO_PY_PROP_RW_NONE("missingcolor", &TextureOptWrap::get_missingcolor,
+ &TextureOptWrap::set_missingcolor)
+ .OIIO_PY_RW("rnd", &TextureOptWrap::rnd)
+ .OIIO_PY_PROP_RW(
"rwrap",
[](const TextureOptWrap& texopt) { return (Tex::Wrap)texopt.rwrap; },
[](TextureOptWrap& texopt, const Tex::Wrap wrap) {
texopt.rwrap = (TextureOpt::Wrap)wrap;
})
- .def_readwrite("rwidth", &TextureOptWrap::rwidth);
+ .OIIO_PY_RW("rwidth", &TextureOptWrap::rwidth);
}
void
-declare_texturesystem(py::module& m)
+declare_texturesystem(py_module& m)
{
- using namespace pybind11::literals;
-
py::class_(m, "TextureSystem")
.def(py::init(), "shared"_a = true)
.def_static("destroy", &TextureSystemWrap::destroy)
@@ -322,9 +319,10 @@ declare_texturesystem(py::module& m)
std::vector filenames;
ts.m_texsys->inventory_udim(ustring(filename), filenames,
nutiles, nvtiles);
- std::vector strs;
- for (auto f : filenames)
+ std::vector strs;
+ for (auto f : filenames) {
strs.emplace_back(f.string());
+ }
py::tuple ret = py::make_tuple(nutiles, nvtiles, strs);
return ret;
},
diff --git a/testsuite/python-colorconfig/ref/out-ocio230.txt b/testsuite/python-colorconfig/ref/out-ocio230.txt
index 17071b04df..ca19551d3d 100644
--- a/testsuite/python-colorconfig/ref/out-ocio230.txt
+++ b/testsuite/python-colorconfig/ref/out-ocio230.txt
@@ -1,3 +1,5 @@
+default_colorconfig same instance = True
+default_colorconfig usable = True
getNumColorSpaces = 15
getColorSpaceNames = ['ACES2065-1', 'ACEScc', 'ACEScct', 'ACEScg', 'Linear P3-D65', 'Linear Rec.2020', 'Linear Rec.709 (sRGB)', 'Gamma 1.8 Rec.709 - Texture', 'Gamma 2.2 AP1 - Texture', 'Gamma 2.2 Rec.709 - Texture', 'Gamma 2.4 Rec.709 - Texture', 'sRGB Encoded AP1 - Texture', 'sRGB Encoded P3-D65 - Texture', 'sRGB - Texture', 'Raw']
Index of 'lin_srgb' = 6
diff --git a/testsuite/python-colorconfig/ref/out-ocio230b.txt b/testsuite/python-colorconfig/ref/out-ocio230b.txt
index d52992d9dd..542aa2a3bb 100644
--- a/testsuite/python-colorconfig/ref/out-ocio230b.txt
+++ b/testsuite/python-colorconfig/ref/out-ocio230b.txt
@@ -1,3 +1,5 @@
+default_colorconfig same instance = True
+default_colorconfig usable = True
getNumColorSpaces = 15
getColorSpaceNames = ['ACES2065-1', 'ACEScc', 'ACEScct', 'ACEScg', 'Linear P3-D65', 'Linear Rec.2020', 'Linear Rec.709 (sRGB)', 'Gamma 1.8 Rec.709 - Texture', 'Gamma 2.2 AP1 - Texture', 'Gamma 2.2 Rec.709 - Texture', 'Gamma 2.4 Rec.709 - Texture', 'sRGB Encoded AP1 - Texture', 'sRGB Encoded P3-D65 - Texture', 'sRGB - Texture', 'Raw']
Index of 'lin_srgb' = 6
diff --git a/testsuite/python-colorconfig/ref/out-ocio232.txt b/testsuite/python-colorconfig/ref/out-ocio232.txt
index c3452050e1..9c063cdd9a 100644
--- a/testsuite/python-colorconfig/ref/out-ocio232.txt
+++ b/testsuite/python-colorconfig/ref/out-ocio232.txt
@@ -1,3 +1,5 @@
+default_colorconfig same instance = True
+default_colorconfig usable = True
getNumColorSpaces = 15
getColorSpaceNames = ['ACES2065-1', 'ACEScc', 'ACEScct', 'ACEScg', 'Linear P3-D65', 'Linear Rec.2020', 'Linear Rec.709 (sRGB)', 'Gamma 1.8 Rec.709 - Texture', 'Gamma 2.2 AP1 - Texture', 'Gamma 2.2 Rec.709 - Texture', 'Gamma 2.4 Rec.709 - Texture', 'sRGB Encoded AP1 - Texture', 'sRGB Encoded P3-D65 - Texture', 'sRGB - Texture', 'Raw']
Index of 'lin_srgb' = 6
diff --git a/testsuite/python-colorconfig/ref/out-ocio24.txt b/testsuite/python-colorconfig/ref/out-ocio24.txt
index c568d2266d..a8dab6c064 100644
--- a/testsuite/python-colorconfig/ref/out-ocio24.txt
+++ b/testsuite/python-colorconfig/ref/out-ocio24.txt
@@ -1,3 +1,5 @@
+default_colorconfig same instance = True
+default_colorconfig usable = True
getNumColorSpaces = 23
getColorSpaceNames = ['sRGB - Display', 'Display P3 - Display', 'Rec.1886 Rec.709 - Display', 'Rec.2100-PQ - Display', 'ST2084-P3-D65 - Display', 'P3-D65 - Display', 'ACES2065-1', 'ACEScc', 'ACEScct', 'ACEScg', 'sRGB Encoded Rec.709 (sRGB)', 'Gamma 1.8 Encoded Rec.709', 'Gamma 2.2 Encoded Rec.709', 'Gamma 2.4 Encoded Rec.709', 'sRGB Encoded P3-D65', 'Gamma 2.2 Encoded AdobeRGB', 'sRGB Encoded AP1', 'Gamma 2.2 Encoded AP1', 'Linear Rec.709 (sRGB)', 'Linear P3-D65', 'Linear AdobeRGB', 'Linear Rec.2020', 'Raw']
Index of 'lin_srgb' = 18
diff --git a/testsuite/python-colorconfig/ref/out-ocio25.txt b/testsuite/python-colorconfig/ref/out-ocio25.txt
index ced1f69a1c..83e831b137 100644
--- a/testsuite/python-colorconfig/ref/out-ocio25.txt
+++ b/testsuite/python-colorconfig/ref/out-ocio25.txt
@@ -1,3 +1,5 @@
+default_colorconfig same instance = True
+default_colorconfig usable = True
getNumColorSpaces = 25
getColorSpaceNames = ['sRGB - Display', 'Gamma 2.2 Rec.709 - Display', 'Display P3 - Display', 'Display P3 HDR - Display', 'P3-D65 - Display', 'Rec.1886 Rec.709 - Display', 'Rec.2100-PQ - Display', 'ST2084-P3-D65 - Display', 'ACES2065-1', 'ACEScc', 'ACEScct', 'ACEScg', 'sRGB Encoded Rec.709 (sRGB)', 'Gamma 1.8 Encoded Rec.709', 'Gamma 2.2 Encoded Rec.709', 'Gamma 2.4 Encoded Rec.709', 'sRGB Encoded P3-D65', 'Gamma 2.2 Encoded AdobeRGB', 'sRGB Encoded AP1', 'Gamma 2.2 Encoded AP1', 'Linear AdobeRGB', 'Linear P3-D65', 'Linear Rec.2020', 'Linear Rec.709 (sRGB)', 'Raw']
Index of 'lin_srgb' = 23
diff --git a/testsuite/python-colorconfig/ref/out.txt b/testsuite/python-colorconfig/ref/out.txt
index 590a997026..711f2d88e4 100644
--- a/testsuite/python-colorconfig/ref/out.txt
+++ b/testsuite/python-colorconfig/ref/out.txt
@@ -1,3 +1,5 @@
+default_colorconfig same instance = True
+default_colorconfig usable = True
getNumColorSpaces = 14
getColorSpaceNames = ['ACES2065-1', 'ACEScc', 'ACEScct', 'ACEScg', 'Linear P3-D65', 'Linear Rec.2020', 'Linear Rec.709 (sRGB)', 'Gamma 1.8 Rec.709 - Texture', 'Gamma 2.2 AP1 - Texture', 'Gamma 2.2 Rec.709 - Texture', 'Gamma 2.4 Rec.709 - Texture', 'sRGB Encoded AP1 - Texture', 'sRGB - Texture', 'Raw']
Index of 'lin_srgb' = 6
diff --git a/testsuite/python-colorconfig/src/test_colorconfig.py b/testsuite/python-colorconfig/src/test_colorconfig.py
index 16f3ce0ef2..cbbba63dc9 100755
--- a/testsuite/python-colorconfig/src/test_colorconfig.py
+++ b/testsuite/python-colorconfig/src/test_colorconfig.py
@@ -14,6 +14,15 @@
TEST_CONFIG_PATH: Path = Path(__file__).parent/"oiio_test_v0.9.2.ocio"
try:
+ # Regression test: default_colorconfig() returns a non-copyable singleton
+ # reference and must use a reference return-value policy (without it,
+ # callers can get a copy/wrapper that is not the shared instance, or the
+ # binding can fail for non-copyable ColorConfig).
+ default_a = oiio.ColorConfig.default_colorconfig()
+ default_b = oiio.ColorConfig.default_colorconfig()
+ print ("default_colorconfig same instance =", default_a is default_b)
+ print ("default_colorconfig usable =", default_a.getNumColorSpaces() > 0)
+
config = oiio.ColorConfig()
print ("getNumColorSpaces =", config.getNumColorSpaces())
print ("getColorSpaceNames =", config.getColorSpaceNames())
diff --git a/testsuite/python-imagebuf/ref/out-alt.txt b/testsuite/python-imagebuf/ref/out-alt.txt
index cb2f545958..1c557eca39 100644
--- a/testsuite/python-imagebuf/ref/out-alt.txt
+++ b/testsuite/python-imagebuf/ref/out-alt.txt
@@ -14,6 +14,10 @@ Resetting to be a writable 640x480,3 Float:
alpha channel = -1
z channel = -1
deep = False
+Constructing with ImageBuf(spec, zero):
+ zero=True pixel (0,0): (0.0, 0.0, 0.0)
+ zero=False then setpixel (0,0): (0.25, 0.5, 0.75)
+
Constructing from a bare numpy array:
from 3D, shape is float 0 2 0 3 0 1 0 4
resolution 2x3+0+0
@@ -29,6 +33,7 @@ Constructing from a bare numpy array:
from 4D, shape is float 0 2 0 2 0 2 0 4
+ from 0-d array has_error: True
Testing read of ../common/textures/grid.tx:
channels: 4
name: ../common/textures/grid.tx
diff --git a/testsuite/python-imagebuf/ref/out.txt b/testsuite/python-imagebuf/ref/out.txt
index cb2f545958..1c557eca39 100644
--- a/testsuite/python-imagebuf/ref/out.txt
+++ b/testsuite/python-imagebuf/ref/out.txt
@@ -14,6 +14,10 @@ Resetting to be a writable 640x480,3 Float:
alpha channel = -1
z channel = -1
deep = False
+Constructing with ImageBuf(spec, zero):
+ zero=True pixel (0,0): (0.0, 0.0, 0.0)
+ zero=False then setpixel (0,0): (0.25, 0.5, 0.75)
+
Constructing from a bare numpy array:
from 3D, shape is float 0 2 0 3 0 1 0 4
resolution 2x3+0+0
@@ -29,6 +33,7 @@ Constructing from a bare numpy array:
from 4D, shape is float 0 2 0 2 0 2 0 4
+ from 0-d array has_error: True
Testing read of ../common/textures/grid.tx:
channels: 4
name: ../common/textures/grid.tx
diff --git a/testsuite/python-imagebuf/src/test_imagebuf.py b/testsuite/python-imagebuf/src/test_imagebuf.py
index 789efb3724..5469227e6d 100755
--- a/testsuite/python-imagebuf/src/test_imagebuf.py
+++ b/testsuite/python-imagebuf/src/test_imagebuf.py
@@ -235,6 +235,15 @@ def test_outofrange_subimage_miplevel() :
b.reset (oiio.ImageSpec(640,480,3,oiio.FLOAT))
print_imagespec (b.spec())
+ # Cover ImageBuf(spec, zero) placement-new constructor (both backends).
+ print ("Constructing with ImageBuf(spec, zero):")
+ b = oiio.ImageBuf(oiio.ImageSpec(2, 2, 3, oiio.FLOAT), True)
+ print (" zero=True pixel (0,0):", ftupstr(b.getpixel(0, 0)))
+ b = oiio.ImageBuf(oiio.ImageSpec(2, 2, 3, oiio.FLOAT), False)
+ b.setpixel(0, 0, (0.25, 0.5, 0.75))
+ print (" zero=False then setpixel (0,0):", ftupstr(b.getpixel(0, 0)))
+ print ("")
+
print ("Constructing from a bare numpy array:")
b = oiio.ImageBuf(numpy.array([[[0.1,0.0,0.9,1.0], [0.2,0.0,0.7,1.0]],
[[0.3,0.0,0.8,1.0], [0.4,0.0,0.6,1.0]],
@@ -257,6 +266,12 @@ def test_outofrange_subimage_miplevel() :
print (" from 4D, shape is", b.spec().format, b.roi)
print ("")
+ # Regression test: 0-d numpy arrays must not crash ImageBuf construction.
+ # Without the ndim > 0 guard, strides[ndim - 1] was out-of-bounds UB.
+ b = oiio.ImageBuf(numpy.array(1.0, dtype="f"))
+ print (" from 0-d array has_error:", b.has_error)
+ b.geterror() # clear pending error so destruction is quiet
+
# Test reading from disk
print ("Testing read of ../common/textures/grid.tx:")
b = oiio.ImageBuf ("../common/textures/grid.tx")
diff --git a/testsuite/python-oiio/ref/out.txt b/testsuite/python-oiio/ref/out.txt
index e213a52b48..97ec265539 100644
--- a/testsuite/python-oiio/ref/out.txt
+++ b/testsuite/python-oiio/ref/out.txt
@@ -48,6 +48,17 @@ Testing global get_*_attribute defaults:
get_int plugin_searchpath (wrong type) default: 77
get_float on int attribute returns default: True
+Testing get_bytes_attribute default branches:
+ omitted default type bytes: True
+ omitted default value: b''
+ None default type bytes: True
+ None default value: b''
+ str default type bytes: True
+ str default value: b'from_str'
+ bytes default type bytes: True
+ bytes default value: b'from_bytes'
+ present attr ignores default: True
+
Testing getattribute() with TypeUnknown:
getattribute(missing, TypeUnknown): None
diff --git a/testsuite/python-oiio/src/test_oiio.py b/testsuite/python-oiio/src/test_oiio.py
index 6e53be5af0..6075f875f6 100644
--- a/testsuite/python-oiio/src/test_oiio.py
+++ b/testsuite/python-oiio/src/test_oiio.py
@@ -114,6 +114,26 @@ def version_code(major: int, minor: int, patch: int) -> int:
oiio.get_float_attribute ("threads", -1.0) == -1.0)
print ("")
+ # Cover get_bytes_attribute default conversion branches (str / bytes / None /
+ # omitted). Nanobind does not auto-convert bytes→std::string like pybind11.
+ print ("Testing get_bytes_attribute default branches:")
+ missing = "not_a_real_attr_for_bytes_default"
+ omitted = oiio.get_bytes_attribute (missing)
+ none_def = oiio.get_bytes_attribute (missing, None)
+ str_def = oiio.get_bytes_attribute (missing, "from_str")
+ bytes_def = oiio.get_bytes_attribute (missing, b"from_bytes")
+ print (" omitted default type bytes:", isinstance (omitted, bytes))
+ print (" omitted default value:", omitted)
+ print (" None default type bytes:", isinstance (none_def, bytes))
+ print (" None default value:", none_def)
+ print (" str default type bytes:", isinstance (str_def, bytes))
+ print (" str default value:", str_def)
+ print (" bytes default type bytes:", isinstance (bytes_def, bytes))
+ print (" bytes default value:", bytes_def)
+ present = oiio.get_bytes_attribute ("font_searchpath", b"unused")
+ print (" present attr ignores default:", present == b"/fonts")
+ print ("")
+
print ("Testing getattribute() with TypeUnknown:")
print (" getattribute(missing, TypeUnknown):",
oiio.getattribute ("not_a_real_attr", oiio.TypeUnknown))