From 24400c595a8f8b89b087d46b3bbfad27772c739f Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 10:54:25 +1000 Subject: [PATCH 1/2] feat(python): Introduce nanobind support for Python bindings Added support for nanobind as a backend for Python bindings alongside pybind11. Updated CMake configuration to conditionally include Python modules based on the selected backend. Refactored existing Python binding declarations to utilize a unified interface for both backends. Introduced a new MIGRATION_STATUS.md file to document the migration process and current status of nanobind integration. Enhanced installation paths for nanobind modules to ensure proper isolation and organization within the build structure. This change aims to improve flexibility in Python bindings and facilitate future development with nanobind. Assisted-by: Cursor / auto Signed-off-by: Aleksandr Motsjonov --- CMakeLists.txt | 6 +- src/cmake/pythonutils.cmake | 51 +- src/python-nanobind/CMakeLists.txt | 24 +- src/python-nanobind/py_imagespec.cpp | 290 ----------- src/python-nanobind/py_oiio.cpp | 175 ------- src/python-nanobind/py_oiio.h | 488 ------------------ src/python-nanobind/py_paramvalue.cpp | 248 --------- src/python-nanobind/py_roi.cpp | 70 --- src/python-nanobind/py_typedesc.cpp | 246 --------- src/python/CMakeLists.txt | 51 +- .../MIGRATION_STATUS.md | 17 +- src/python/py_backend.h | 178 +++++++ src/python/py_imagespec.cpp | 85 +-- src/python/py_oiio.cpp | 166 ++++-- src/python/py_oiio.h | 347 ++++++++----- src/python/py_paramvalue.cpp | 62 ++- src/python/py_roi.cpp | 40 +- src/python/py_typedesc.cpp | 94 +++- testsuite/python-imagespec/ref/out.txt | 7 + .../python-imagespec/src/test_imagespec.py | 36 ++ testsuite/python-paramlist/ref/out.txt | 3 + .../python-paramlist/src/test_paramlist.py | 20 +- testsuite/python-typedesc/ref/out.txt | 5 + .../python-typedesc/src/test_typedesc.py | 16 + 24 files changed, 893 insertions(+), 1832 deletions(-) delete mode 100644 src/python-nanobind/py_imagespec.cpp delete mode 100644 src/python-nanobind/py_oiio.cpp delete mode 100644 src/python-nanobind/py_oiio.h delete mode 100644 src/python-nanobind/py_paramvalue.cpp delete mode 100644 src/python-nanobind/py_roi.cpp delete mode 100644 src/python-nanobind/py_typedesc.cpp rename src/{python-nanobind => python}/MIGRATION_STATUS.md (52%) create mode 100644 src/python/py_backend.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2586106b69..6e67c88389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -314,10 +314,12 @@ else () set (_py_dev_found Python3_Development.Module_FOUND) endif () if (USE_PYTHON AND ${_py_dev_found} AND NOT BUILD_OIIOUTIL_ONLY) - if (OIIO_BUILD_PYTHON_PYBIND11) + if (OIIO_BUILD_PYTHON_PYBIND11 + OR OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "nanobind") add_subdirectory (src/python) endif () - if (OIIO_BUILD_PYTHON_NANOBIND) + if (OIIO_BUILD_PYTHON_NANOBIND + AND OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "both") add_subdirectory (src/python-nanobind) endif () else () diff --git a/src/cmake/pythonutils.cmake b/src/cmake/pythonutils.cmake index 02a945e592..913021d001 100644 --- a/src/cmake/pythonutils.cmake +++ b/src/cmake/pythonutils.cmake @@ -263,25 +263,44 @@ macro (setup_python_module_nanobind) OUTPUT_NAME ${lib_MODULE} DEBUG_POSTFIX "") - if (SKBUILD) - set (_nanobind_install_dir .) + if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "both") + if (SKBUILD) + set (_nanobind_install_dir .) + else () + set (_nanobind_install_dir ${PYTHON_SITE_DIR}) + endif () + + # Keep nanobind modules isolated in the build tree so they don't alter + # how the existing top-level OpenImageIO module is imported during tests. + set_target_properties (${target_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nanobind/OpenImageIO + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nanobind/OpenImageIO + ) + + install (TARGETS ${target_name} + RUNTIME DESTINATION ${_nanobind_install_dir} COMPONENT user + LIBRARY DESTINATION ${_nanobind_install_dir} COMPONENT user) + + if (lib_PACKAGE_FILES) + install (FILES ${lib_PACKAGE_FILES} + DESTINATION ${_nanobind_install_dir} COMPONENT user) + endif () else () - set (_nanobind_install_dir ${PYTHON_SITE_DIR}) - endif () + if (SKBUILD) + set (PYTHON_SITE_DIR .) + endif () - # Keep nanobind modules isolated in the build tree so they don't alter - # how the existing top-level OpenImageIO module is imported during tests. - set_target_properties (${target_name} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nanobind/OpenImageIO - ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nanobind/OpenImageIO - ) + set_target_properties (${target_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/site-packages + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/site-packages + ) - install (TARGETS ${target_name} - RUNTIME DESTINATION ${_nanobind_install_dir} COMPONENT user - LIBRARY DESTINATION ${_nanobind_install_dir} COMPONENT user) + install (TARGETS ${target_name} + RUNTIME DESTINATION ${PYTHON_SITE_DIR} COMPONENT user + LIBRARY DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) - if (lib_PACKAGE_FILES) - install (FILES ${lib_PACKAGE_FILES} - DESTINATION ${_nanobind_install_dir} COMPONENT user) + install (FILES __init__.py stubs/OpenImageIO/__init__.pyi stubs/OpenImageIO/py.typed + DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) endif () + endmacro () diff --git a/src/python-nanobind/CMakeLists.txt b/src/python-nanobind/CMakeLists.txt index eeecf84a7f..4fd40d037d 100644 --- a/src/python-nanobind/CMakeLists.txt +++ b/src/python-nanobind/CMakeLists.txt @@ -3,11 +3,11 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO set (nanobind_srcs - py_oiio.cpp - py_paramvalue.cpp - py_roi.cpp - py_imagespec.cpp - py_typedesc.cpp) + ${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_imagespec.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../python/py_typedesc.cpp) set (nanobind_build_package_dir ${CMAKE_BINARY_DIR}/lib/python/nanobind/OpenImageIO) file (MAKE_DIRECTORY ${nanobind_build_package_dir}) @@ -20,13 +20,11 @@ setup_python_module_nanobind ( MODULE _OpenImageIO SOURCES ${nanobind_srcs} LIBS OpenImageIO + PACKAGE_FILES __init__.py ) -if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "nanobind") - if (SKBUILD) - install (FILES __init__.py DESTINATION . COMPONENT user) - else () - install (FILES __init__.py - DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) - endif () -endif () +target_compile_definitions (PyOpenImageIONanobind + PRIVATE OIIO_PY_BACKEND_NANOBIND + OIIO_PY_NANOBIND_ISOLATED_PACKAGE) +target_include_directories (PyOpenImageIONanobind + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../python) diff --git a/src/python-nanobind/py_imagespec.cpp b/src/python-nanobind/py_imagespec.cpp deleted file mode 100644 index fc61d016f1..0000000000 --- a/src/python-nanobind/py_imagespec.cpp +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: Apache-2.0 -// https://github.com/AcademySoftwareFoundation/OpenImageIO - -#include "py_oiio.h" - -namespace { - -using namespace OIIO; - -nb::tuple -imagespec_get_channelformats(const ImageSpec& spec, bool allow_empty = true) -{ - std::vector formats; - if (spec.channelformats.size() || !allow_empty) - spec.get_channelformats(formats); - return PyOpenImageIO::C_to_tuple(cspan(formats)); -} - - -void -imagespec_set_channelformats(ImageSpec& spec, nb::handle py_channelformats) -{ - spec.channelformats.clear(); - PyOpenImageIO::py_to_stdvector(spec.channelformats, py_channelformats); -} - - -nb::tuple -imagespec_get_channelnames(const ImageSpec& spec) -{ - return PyOpenImageIO::C_to_tuple(cspan(spec.channelnames)); -} - - -void -imagespec_set_channelnames(ImageSpec& spec, nb::handle py_channelnames) -{ - spec.channelnames.clear(); - PyOpenImageIO::py_to_stdvector(spec.channelnames, py_channelnames); -} - - -nb::object -imagespec_getattribute_typed(const ImageSpec& spec, const std::string& name, - TypeDesc type = TypeUnknown) -{ - ParamValue tmpparam; - const ParamValue* p = spec.find_attribute(name, tmpparam, type); - if (!p) - return nb::none(); - return PyOpenImageIO::make_pyobject(p->data(), p->type(), p->nvalues()); -} - -} // namespace - - -namespace PyOpenImageIO { - -void -declare_imagespec(nb::module_& m) -{ - nb::class_(m, "ImageSpec") - .def_rw("x", &ImageSpec::x) - .def_rw("y", &ImageSpec::y) - .def_rw("z", &ImageSpec::z) - .def_rw("width", &ImageSpec::width) - .def_rw("height", &ImageSpec::height) - .def_rw("depth", &ImageSpec::depth) - .def_rw("full_x", &ImageSpec::full_x) - .def_rw("full_y", &ImageSpec::full_y) - .def_rw("full_z", &ImageSpec::full_z) - .def_rw("full_width", &ImageSpec::full_width) - .def_rw("full_height", &ImageSpec::full_height) - .def_rw("full_depth", &ImageSpec::full_depth) - .def_rw("tile_width", &ImageSpec::tile_width) - .def_rw("tile_height", &ImageSpec::tile_height) - .def_rw("tile_depth", &ImageSpec::tile_depth) - .def_rw("nchannels", &ImageSpec::nchannels) - .def_rw("format", &ImageSpec::format) - .def_prop_rw( - "channelformats", - [](const ImageSpec& spec) { - return imagespec_get_channelformats(spec); - }, - &imagespec_set_channelformats) - .def_prop_rw("channelnames", &imagespec_get_channelnames, - &imagespec_set_channelnames) - .def_rw("alpha_channel", &ImageSpec::alpha_channel) - .def_rw("z_channel", &ImageSpec::z_channel) - .def_rw("deep", &ImageSpec::deep) - .def_rw("extra_attribs", &ImageSpec::extra_attribs) - .def_prop_rw("roi", &ImageSpec::roi, &ImageSpec::set_roi) - .def_prop_rw("roi_full", &ImageSpec::roi_full, &ImageSpec::set_roi_full) - .def(nb::init<>()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def("copy", [](const ImageSpec& self) { return ImageSpec(self); }) - .def("set_format", - [](ImageSpec& self, TypeDesc t) { self.set_format(t); }) - .def("default_channel_names", &ImageSpec::default_channel_names) - .def("channel_bytes", - [](const ImageSpec& spec) { return spec.channel_bytes(); }) - .def( - "channel_bytes", - [](const ImageSpec& spec, int chan, bool native) { - return spec.channel_bytes(chan, native); - }, - "channel"_a, "native"_a = false) - .def( - "pixel_bytes", - [](const ImageSpec& spec, bool native) { - return spec.pixel_bytes(native); - }, - "native"_a = false) - .def( - "pixel_bytes", - [](const ImageSpec& spec, int chbegin, int chend, bool native) { - return spec.pixel_bytes(chbegin, chend, native); - }, - "chbegin"_a, "chend"_a, "native"_a = false) - .def( - "scanline_bytes", - [](const ImageSpec& spec, bool native) { - return spec.scanline_bytes(native); - }, - "native"_a = false) - .def("scanline_bytes", - [](const ImageSpec& spec, TypeDesc type) { - return spec.scanline_bytes(type); - }) - .def( - "tile_bytes", - [](const ImageSpec& spec, bool native) { - return spec.tile_bytes(native); - }, - "native"_a = false) - .def("tile_bytes", [](const ImageSpec& spec, - TypeDesc type) { return spec.tile_bytes(type); }) - .def( - "image_bytes", - [](const ImageSpec& spec, bool native) { - return spec.image_bytes(native); - }, - "native"_a = false) - .def("image_bytes", - [](const ImageSpec& spec, TypeDesc type) { - return spec.image_bytes(type); - }) - .def("tile_pixels", &ImageSpec::tile_pixels) - .def("image_pixels", &ImageSpec::image_pixels) - .def("size_t_safe", &ImageSpec::size_t_safe) - .def("channelformat", [](const ImageSpec& spec, - int chan) { return spec.channelformat(chan); }) - .def("channel_name", - [](const ImageSpec& spec, int chan) { - return std::string(spec.channel_name(chan)); - }) - .def("channelindex", - [](const ImageSpec& spec, const std::string& name) { - return spec.channelindex(name); - }) - .def("get_channelformats", - [](const ImageSpec& spec) { - return imagespec_get_channelformats(spec, false); - }) - .def("attribute", - [](ImageSpec& spec, const std::string& name, nb::handle obj) { - attribute_onearg(spec, name, obj); - }) - .def("attribute", - [](ImageSpec& spec, const std::string& name, TypeDesc type, - nb::handle obj) { attribute_typed(spec, name, type, obj); }) - .def( - "get_int_attribute", - [](const ImageSpec& spec, const std::string& name, int def) { - return spec.get_int_attribute(name, def); - }, - "name"_a, "defaultval"_a = 0) - .def( - "get_float_attribute", - [](const ImageSpec& spec, const std::string& name, float def) { - return spec.get_float_attribute(name, def); - }, - "name"_a, "defaultval"_a = 0.0f) - .def( - "get_string_attribute", - [](const ImageSpec& spec, const std::string& name, - const std::string& def) { - return std::string(spec.get_string_attribute(name, def)); - }, - "name"_a, "defaultval"_a = "") - .def( - "get_bytes_attribute", - [](const ImageSpec& spec, const std::string& name, - const std::string& def) { - std::string s(spec.get_string_attribute(name, def)); - return nb::bytes(s.data(), s.size()); - }, - "name"_a, "defaultval"_a = "") - .def("getattribute", &imagespec_getattribute_typed, "name"_a, - "type"_a = TypeUnknown) - .def( - "get", - [](const ImageSpec& self, const std::string& key, nb::handle def) { - ParamValue tmpparam; - auto p = self.find_attribute(key, tmpparam); - if (!p) - return nb::borrow(def); - return make_pyobject(p->data(), p->type(), 1, def); - }, - "key"_a, "default"_a = nb::none()) - .def( - "erase_attribute", - [](ImageSpec& spec, const std::string& name, TypeDesc type, - bool casesensitive) { - return spec.erase_attribute(name, type, casesensitive); - }, - "name"_a = "", "type"_a = TypeUnknown, "casesensitive"_a = false) - .def_static( - "metadata_val", - [](const ParamValue& p, bool human) { - return std::string(ImageSpec::metadata_val(p, human)); - }, - "param"_a, "human"_a = false) - .def( - "serialize", - [](const ImageSpec& spec, const std::string& format, - const std::string& verbose) { - ImageSpec::SerialFormat fmt = ImageSpec::SerialText; - if (Strutil::iequals(format, "xml")) - fmt = ImageSpec::SerialXML; - ImageSpec::SerialVerbose verb = ImageSpec::SerialDetailed; - if (Strutil::iequals(verbose, "brief")) - verb = ImageSpec::SerialBrief; - else if (Strutil::iequals(verbose, "detailed")) - verb = ImageSpec::SerialDetailed; - else if (Strutil::iequals(verbose, "detailedhuman")) - verb = ImageSpec::SerialDetailedHuman; - return std::string(spec.serialize(fmt, verb)); - }, - "format"_a = "text", "verbose"_a = "detailed") - .def("to_xml", - [](const ImageSpec& spec) { return std::string(spec.to_xml()); }) - .def("from_xml", - [](ImageSpec& self, const std::string& xml) { - self.from_xml(xml.c_str()); - }) - .def( - "valid_tile_range", - [](ImageSpec& self, int xbegin, int xend, int ybegin, int yend, - int zbegin, int zend) { - return self.valid_tile_range(xbegin, xend, ybegin, yend, zbegin, - zend); - }, - "xbegin"_a, "xend"_a, "ybegin"_a, "yend"_a, "zbegin"_a = 0, - "zend"_a = 1) - .def("copy_dimensions", &ImageSpec::copy_dimensions, "other"_a) - .def( - "set_colorspace", - [](ImageSpec& self, const std::string& cs) { - self.set_colorspace(cs); - }, - "name"_a) - .def("__getitem__", - [](const ImageSpec& self, const std::string& key) { - ParamValue tmpparam; - auto p = self.find_attribute(key, tmpparam); - if (p == nullptr) { - std::string message = "key '" + key + "' does not exist"; - throw nb::key_error(message.c_str()); - } - return make_pyobject(p->data(), p->type()); - }) - .def("__setitem__", - [](ImageSpec& self, const std::string& key, nb::handle val) { - delegate_setitem(self, key, val); - }) - .def("__delitem__", - [](ImageSpec& self, const std::string& key) { - self.erase_attribute(key); - }) - .def("__contains__", [](const ImageSpec& self, const std::string& key) { - return self.extra_attribs.contains(key); - }); -} - -} // namespace PyOpenImageIO diff --git a/src/python-nanobind/py_oiio.cpp b/src/python-nanobind/py_oiio.cpp deleted file mode 100644 index 7899af7f5e..0000000000 --- a/src/python-nanobind/py_oiio.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: Apache-2.0 -// https://github.com/AcademySoftwareFoundation/OpenImageIO - -#include "py_oiio.h" - -namespace { - -using namespace OIIO; - -nb::object -oiio_getattribute_typed(const std::string& name, TypeDesc type = TypeUnknown) -{ - if (type == TypeUnknown) - return nb::none(); - char* data = OIIO_ALLOCA(char, type.size()); - if (!OIIO::getattribute(name, type, data)) - return nb::none(); - return PyOpenImageIO::make_pyobject(data, type); -} - - -struct oiio_global_attrib_wrapper { - bool attribute(string_view name, TypeDesc type, const void* data) - { - return OIIO::attribute(name, type, data); - } - bool attribute(string_view name, int val) - { - return OIIO::attribute(name, val); - } - bool attribute(string_view name, float val) - { - return OIIO::attribute(name, val); - } - bool attribute(string_view name, const std::string& val) - { - return OIIO::attribute(name, val); - } -}; - -} // namespace - - -namespace PyOpenImageIO { - -TypeDesc -typedesc_from_python_array_code(string_view code) -{ - TypeDesc t(code); - if (!t.is_unknown()) - return t; - - if (code == "b" || code == "c") - return TypeDesc::INT8; - if (code == "B") - return TypeDesc::UINT8; - if (code == "h") - return TypeDesc::INT16; - if (code == "H") - return TypeDesc::UINT16; - if (code == "i") - return TypeDesc::INT; - if (code == "I") - return TypeDesc::UINT; - if (code == "l") - return TypeDesc::INT64; - if (code == "L") - return TypeDesc::UINT64; - if (code == "q") - return TypeDesc::INT64; - if (code == "Q") - return TypeDesc::UINT64; - if (code == "f") - return TypeDesc::FLOAT; - if (code == "d") - return TypeDesc::DOUBLE; - if (code == "float16" || code == "e") - return TypeDesc::HALF; - return TypeDesc::UNKNOWN; -} - - -nb::object -make_pyobject(const void* data, TypeDesc type, int nvalues, - nb::handle defaultvalue) -{ - if (!data || !nvalues) - return nb::borrow(defaultvalue); - if (type.basetype == TypeDesc::INT32) - return C_to_val_or_tuple(static_cast(data), type, nvalues); - if (type.basetype == TypeDesc::FLOAT) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::STRING) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::UINT32) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::INT16) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::UINT16) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::INT64) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::UINT64) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::DOUBLE) - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - if (type.basetype == TypeDesc::HALF) - return C_to_val_or_tuple(static_cast(data), type, nvalues); - if (type.basetype == TypeDesc::UINT8 && type.arraylen > 0) { - int n = type.arraylen * nvalues; - if (n <= 0) - return nb::borrow(defaultvalue); - auto* copy = new uint8_t[n]; - std::memcpy(copy, data, static_cast(n)); - return make_numpy_array(copy, static_cast(n)); - } - if (type.basetype == TypeDesc::UINT8) { - return C_to_val_or_tuple(static_cast(data), type, - nvalues); - } - return nb::borrow(defaultvalue); -} - -} // namespace PyOpenImageIO - - -NB_MODULE(_OpenImageIO, m) -{ - m.doc() = "OpenImageIO nanobind bindings."; - - PyOpenImageIO::declare_typedesc(m); - PyOpenImageIO::declare_paramvalue(m); - PyOpenImageIO::declare_roi(m); - PyOpenImageIO::declare_imagespec(m); - - m.def("attribute", [](const std::string& name, nb::handle obj) { - oiio_global_attrib_wrapper wrapper; - PyOpenImageIO::attribute_onearg(wrapper, name, obj); - }); - m.def("attribute", - [](const std::string& name, TypeDesc type, nb::handle obj) { - oiio_global_attrib_wrapper wrapper; - PyOpenImageIO::attribute_typed(wrapper, name, type, obj); - }); - m.def( - "get_int_attribute", - [](const std::string& name, int def) { - return OIIO::get_int_attribute(name, def); - }, - "name"_a, "defaultval"_a = 0); - m.def( - "get_float_attribute", - [](const std::string& name, float def) { - return OIIO::get_float_attribute(name, def); - }, - "name"_a, "defaultval"_a = 0.0f); - m.def( - "get_string_attribute", - [](const std::string& name, const std::string& def) { - return std::string(OIIO::get_string_attribute(name, def)); - }, - "name"_a, "defaultval"_a = ""); - m.def("getattribute", &oiio_getattribute_typed, "name"_a, - "type"_a = TypeUnknown); - m.attr("__version__") = OIIO_VERSION_STRING; -} diff --git a/src/python-nanobind/py_oiio.h b/src/python-nanobind/py_oiio.h deleted file mode 100644 index 708b4b7344..0000000000 --- a/src/python-nanobind/py_oiio.h +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: Apache-2.0 -// https://github.com/AcademySoftwareFoundation/OpenImageIO - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace nb = nanobind; -using namespace nb::literals; - -namespace PyOpenImageIO { - -using namespace OIIO; - -TypeDesc -typedesc_from_python_array_code(string_view code); - -void -declare_roi(nb::module_& m); -void -declare_imagespec(nb::module_& m); -void -declare_typedesc(nb::module_& m); -void -declare_paramvalue(nb::module_& m); - -template struct PyTypeForCType {}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::int_; -}; -template<> struct PyTypeForCType { - using type = nb::float_; -}; -template<> struct PyTypeForCType { - using type = nb::float_; -}; -template<> struct PyTypeForCType { - using type = nb::float_; -}; -template<> struct PyTypeForCType { - using type = nb::str; -}; -template<> struct PyTypeForCType { - using type = nb::str; -}; - -template -inline bool -py_indexable_pod_to_stdvector(std::vector& vals, const Obj& obj) -{ - bool ok = true; - const size_t length = obj.size(); - vals.clear(); - vals.reserve(length); - for (size_t i = 0; i < length; ++i) { - nb::handle elem = obj[i]; - if constexpr (std::is_same_v) { - if (nb::isinstance(elem)) { - vals.emplace_back(nb::cast(elem)); - } else if (nb::isinstance(elem)) { - vals.emplace_back(static_cast(nb::cast(elem))); - } else { - ok = false; - } - } else if constexpr (std::is_same_v) { - if (nb::isinstance(elem)) { - vals.emplace_back(nb::cast(elem)); - } else { - ok = false; - } - } else if constexpr (std::is_same_v) { - if (nb::isinstance(elem)) { - vals.emplace_back(nb::cast(elem)); - } else { - ok = false; - } - } else if constexpr (std::is_same_v) { - if (nb::isinstance(elem)) { - vals.emplace_back(nb::cast(elem)); - } else { - ok = false; - } - } else { - ok = false; - } - if (!ok) - break; - } - return ok; -} - -template -inline bool -py_indexable_pod_to_stdvector(std::vector& vals, const Obj& obj) -{ - bool ok = true; - const size_t length = obj.size(); - vals.clear(); - vals.reserve(length); - for (size_t i = 0; i < length; ++i) { - nb::handle elem = obj[i]; - if (nb::isinstance(elem)) { - vals.emplace_back(std::string(nb::cast(elem).c_str())); - } else { - ok = false; - break; - } - } - return ok; -} - -template -inline bool -py_indexable_pod_to_stdvector(std::vector& vals, const Obj& obj) -{ - bool ok = true; - const size_t length = obj.size(); - vals.clear(); - vals.reserve(length); - for (size_t i = 0; i < length; ++i) { - nb::handle elem = obj[i]; - if (nb::isinstance(elem)) { - vals.emplace_back(nb::cast(elem)); - } else if (nb::isinstance(elem)) { - vals.emplace_back(TypeDesc(nb::cast(elem))); - } else if (nb::isinstance(elem)) { - vals.emplace_back(TypeDesc(nb::cast(elem).c_str())); - } else { - ok = false; - break; - } - } - return ok; -} - -template -inline bool -py_scalar_pod_to_stdvector(std::vector& vals, const nb::handle& obj) -{ - using pytype = typename PyTypeForCType::type; - vals.clear(); - if (nb::isinstance(obj)) { - vals.emplace_back(nb::cast(obj)); - return true; - } - return false; -} - -template<> -inline bool -py_scalar_pod_to_stdvector(std::vector& vals, const nb::handle& obj) -{ - vals.clear(); - if (nb::isinstance(obj)) { - vals.emplace_back(nb::cast(obj)); - return true; - } - if (nb::isinstance(obj)) { - vals.emplace_back(static_cast(nb::cast(obj))); - return true; - } - return false; -} - -template<> -inline bool -py_scalar_pod_to_stdvector(std::vector& vals, - const nb::handle& obj) -{ - vals.clear(); - if (nb::isinstance(obj)) { - vals.emplace_back(std::string(nb::cast(obj).c_str())); - return true; - } - return false; -} - -template<> -inline bool -py_scalar_pod_to_stdvector(std::vector& vals, const nb::handle& obj) -{ - vals.clear(); - if (nb::isinstance(obj)) { - vals.emplace_back(nb::cast(obj)); - return true; - } - if (nb::isinstance(obj)) { - vals.emplace_back(TypeDesc(nb::cast(obj))); - return true; - } - if (nb::isinstance(obj)) { - vals.emplace_back(TypeDesc(nb::cast(obj).c_str())); - return true; - } - return false; -} - -template -inline bool -py_buffer_to_stdvector(std::vector& vals, const nb::handle& 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; - vals.clear(); - vals.reserve(count); - const unsigned char* data = static_cast(view.buf); - - for (size_t i = 0; ok && i < count; ++i) { - if constexpr (std::is_same_v) { - if (format.basetype == TypeDesc::FLOAT) - vals.emplace_back(reinterpret_cast(data)[i]); - else if (format.basetype == TypeDesc::INT) - vals.emplace_back( - static_cast(reinterpret_cast(data)[i])); - else - ok = false; - } else if constexpr (std::is_same_v) { - if (format.basetype == TypeDesc::INT) - vals.emplace_back(reinterpret_cast(data)[i]); - else - ok = false; - } else if constexpr (std::is_same_v) { - if (format.basetype == TypeDesc::UINT) - vals.emplace_back( - reinterpret_cast(data)[i]); - else - ok = false; - } else if constexpr (std::is_same_v) { - if (format.basetype == TypeDesc::UINT8) - vals.emplace_back( - reinterpret_cast(data)[i]); - else - ok = false; - } else { - ok = false; - } - } - - PyBuffer_Release(&view); - return ok; -} - -template<> -inline bool -py_buffer_to_stdvector(std::vector&, const nb::handle&) -{ - return false; -} - -template<> -inline bool -py_buffer_to_stdvector(std::vector&, const nb::handle&) -{ - return false; -} - -template -inline bool -py_to_stdvector(std::vector& vals, const nb::handle& obj) -{ - if (PyTuple_Check(obj.ptr())) - return py_indexable_pod_to_stdvector(vals, nb::borrow(obj)); - if (PyList_Check(obj.ptr())) - return py_indexable_pod_to_stdvector(vals, nb::borrow(obj)); - if (PyObject_CheckBuffer(obj.ptr()) && !PyUnicode_Check(obj.ptr())) - return py_buffer_to_stdvector(vals, obj); - return py_scalar_pod_to_stdvector(vals, obj); -} - -template -inline nb::tuple -C_to_tuple(cspan vals) -{ - nb::list list; - for (size_t i = 0; i < vals.size(); ++i) - list.append(nb::cast(vals[i])); - return nb::steal(PyList_AsTuple(list.ptr())); -} - -template -inline nb::tuple -C_to_tuple(const T* vals, size_t size) -{ - nb::list list; - for (size_t i = 0; i < size; ++i) - list.append(nb::cast(vals[i])); - return nb::steal(PyList_AsTuple(list.ptr())); -} - -template -inline nb::object -C_to_val_or_tuple(const T* vals, TypeDesc type, int nvalues = 1) -{ - OIIO_DASSERT(vals && nvalues); - const size_t n = type.numelements() * type.aggregate * nvalues; - if (n == 1 && !type.arraylen) - return nb::cast(vals[0]); - return C_to_tuple(vals, n); -} - -template<> -inline nb::tuple -C_to_tuple(cspan vals) -{ - nb::list list; - for (size_t i = 0; i < vals.size(); ++i) - list.append(static_cast(vals[i])); - return nb::steal(PyList_AsTuple(list.ptr())); -} - -template<> -inline nb::tuple -C_to_tuple(const half* vals, size_t size) -{ - nb::list list; - for (size_t i = 0; i < size; ++i) - list.append(static_cast(vals[i])); - return nb::steal(PyList_AsTuple(list.ptr())); -} - -template<> -inline nb::object -C_to_val_or_tuple(const half* vals, TypeDesc type, int nvalues) -{ - OIIO_DASSERT(vals && nvalues); - const size_t n = type.numelements() * type.aggregate * nvalues; - if (n == 1 && !type.arraylen) - return nb::cast(static_cast(vals[0])); - return C_to_tuple(vals, n); -} - -template -bool -attribute_typed(T& myobj, string_view name, TypeDesc type, const Obj& dataobj) -{ - if (type.basetype == TypeDesc::INT) { - std::vector vals; - bool ok = py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) - myobj.attribute(name, type, vals.data()); - return ok; - } - if (type.basetype == TypeDesc::UINT) { - std::vector vals; - bool ok = py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) - myobj.attribute(name, type, vals.data()); - return ok; - } - if (type.basetype == TypeDesc::UINT8) { - std::vector vals; - bool ok = py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) - myobj.attribute(name, type, vals.data()); - return ok; - } - if (type.basetype == TypeDesc::FLOAT) { - std::vector vals; - bool ok = py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) - myobj.attribute(name, type, vals.data()); - return ok; - } - if (type.basetype == TypeDesc::STRING) { - std::vector vals; - bool ok = py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) { - std::vector u; - u.reserve(vals.size()); - for (auto& val : vals) - u.emplace_back(val); - myobj.attribute(name, type, u.data()); - } - return ok; - } - return false; -} - -template -inline void -attribute_onearg(T& myobj, string_view name, const nb::handle& obj) -{ - if (nb::isinstance(obj)) - myobj.attribute(name, nb::cast(obj)); - else if (nb::isinstance(obj)) - myobj.attribute(name, nb::cast(obj)); - else if (nb::isinstance(obj)) - myobj.attribute(name, std::string(nb::cast(obj).c_str())); - else if (nb::isinstance(obj)) { - nb::bytes bytes = nb::cast(obj); - myobj.attribute(name, std::string(bytes.c_str(), bytes.size())); - } else - throw nb::type_error("attribute() value must be int, float, or str"); -} - -template -inline nb::object -make_numpy_array(T* data, size_t size) -{ - nb::capsule owner(data, [](void* p) noexcept { - delete[] reinterpret_cast(p); - }); - nb::ndarray> array(data, { size }, owner); - return nb::cast(std::move(array), nb::rv_policy::move); -} - -nb::object -make_pyobject(const void* data, TypeDesc type, int nvalues = 1, - nb::handle defaultvalue = nb::none()); - -template -inline void -delegate_setitem(C& self, const std::string& key, const nb::handle& obj) -{ - if (nb::isinstance(obj)) - self[key] = nb::cast(obj); - else if (nb::isinstance(obj)) - self[key] = nb::cast(obj); - else if (nb::isinstance(obj)) - self[key] = std::string(nb::cast(obj).c_str()); - else if (nb::isinstance(obj)) { - nb::bytes bytes = nb::cast(obj); - self[key] = std::string(bytes.c_str(), bytes.size()); - } else - throw std::invalid_argument("Bad type for __setitem__"); -} - -} // namespace PyOpenImageIO diff --git a/src/python-nanobind/py_paramvalue.cpp b/src/python-nanobind/py_paramvalue.cpp deleted file mode 100644 index 7885a7a893..0000000000 --- a/src/python-nanobind/py_paramvalue.cpp +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: Apache-2.0 -// https://github.com/AcademySoftwareFoundation/OpenImageIO - -#include "py_oiio.h" - -namespace { - -using namespace OIIO; - -template -bool -attribute_typed_nvalues(T& myobj, string_view name, TypeDesc type, int nvalues, - const Obj& dataobj) -{ - if (type.basetype == TypeDesc::INT) { - std::vector vals; - bool ok = PyOpenImageIO::py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate * nvalues); - if (ok) - myobj.attribute(name, type, nvalues, vals.data()); - return ok; - } - if (type.basetype == TypeDesc::UINT) { - std::vector vals; - bool ok = PyOpenImageIO::py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate * nvalues); - if (ok) - myobj.attribute(name, type, nvalues, vals.data()); - return ok; - } - if (type.basetype == TypeDesc::FLOAT) { - std::vector vals; - bool ok = PyOpenImageIO::py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate * nvalues); - if (ok) - myobj.attribute(name, type, nvalues, vals.data()); - return ok; - } - if (type.basetype == TypeDesc::STRING) { - std::vector vals; - bool ok = PyOpenImageIO::py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate * nvalues); - if (ok) { - std::vector converted; - converted.reserve(vals.size()); - for (auto& val : vals) - converted.emplace_back(val); - myobj.attribute(name, type, nvalues, converted.data()); - } - return ok; - } - if (type.basetype == TypeDesc::UINT8) { - std::vector vals; - bool ok = PyOpenImageIO::py_to_stdvector(vals, dataobj); - ok &= (vals.size() == type.numelements() * type.aggregate * nvalues); - if (ok) - myobj.attribute(name, type, nvalues, vals.data()); - return ok; - } - return false; -} - -ParamValue -paramvalue_from_pyobject(string_view name, TypeDesc type, int nvalues, - ParamValue::Interp interp, nb::handle obj) -{ - ParamValue pv; - // Unsized uint8[] + bytes infers arraylen — must run before numelements(). - if (type.basetype == TypeDesc::UINT8 && type.arraylen - && nb::isinstance(obj)) { - TypeDesc t = type; - nb::bytes b = nb::cast(obj); - const std::string s(b.c_str(), b.size()); - if (t.arraylen < 0) - t.arraylen = static_cast(s.size()) / nvalues; - if (t.arraylen * nvalues == static_cast(s.size())) { - std::vector vals(reinterpret_cast(s.data()), - reinterpret_cast(s.data()) - + s.size()); - pv.init(name, t, nvalues, interp, vals.data()); - } - return pv; - } - - const size_t expected_size = static_cast( - type.numelements() * type.aggregate * nvalues); - if (type.basetype == TypeDesc::INT) { - std::vector vals; - if (PyOpenImageIO::py_to_stdvector(vals, obj) - && vals.size() >= expected_size) { - pv.init(name, type, nvalues, interp, vals.data()); - } - } else if (type.basetype == TypeDesc::UINT) { - std::vector vals; - if (PyOpenImageIO::py_to_stdvector(vals, obj) - && vals.size() >= expected_size) { - pv.init(name, type, nvalues, interp, vals.data()); - } - } else if (type.basetype == TypeDesc::FLOAT) { - std::vector vals; - if (PyOpenImageIO::py_to_stdvector(vals, obj) - && vals.size() >= expected_size) { - pv.init(name, type, nvalues, interp, vals.data()); - } - } else if (type.basetype == TypeDesc::STRING) { - std::vector vals; - if (PyOpenImageIO::py_to_stdvector(vals, obj) - && vals.size() >= expected_size) { - std::vector converted; - converted.reserve(vals.size()); - for (auto& val : vals) - converted.emplace_back(val); - pv.init(name, type, nvalues, interp, converted.data()); - } - } else if (type.basetype == TypeDesc::UINT8) { - std::vector vals; - if (PyOpenImageIO::py_to_stdvector(vals, obj) - && vals.size() >= expected_size) { - pv.init(name, type, nvalues, interp, vals.data()); - } - } - return pv; -} - -} // namespace - - -namespace PyOpenImageIO { - -void -declare_paramvalue(nb::module_& m) -{ - nb::enum_(m, "Interp") - .value("CONSTANT", ParamValue::INTERP_CONSTANT) - .value("PERPIECE", ParamValue::INTERP_PERPIECE) - .value("LINEAR", ParamValue::INTERP_LINEAR) - .value("VERTEX", ParamValue::INTERP_VERTEX) - .value("INTERP_CONSTANT", ParamValue::INTERP_CONSTANT) - .value("INTERP_PERPIECE", ParamValue::INTERP_PERPIECE) - .value("INTERP_LINEAR", ParamValue::INTERP_LINEAR) - .value("INTERP_VERTEX", ParamValue::INTERP_VERTEX); - - nb::class_(m, "ParamValue") - .def_prop_ro("name", - [](const ParamValue& self) { - return std::string(self.name().string()); - }) - .def_prop_ro("type", [](const ParamValue& self) { return self.type(); }) - .def_prop_ro("value", - [](const ParamValue& self) { - return make_pyobject(self.data(), self.type(), - self.nvalues()); - }) - .def("__len__", &ParamValue::nvalues) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def("__init__", - [](ParamValue* self, const std::string& name, TypeDesc type, - nb::handle obj) { - new (self) ParamValue(paramvalue_from_pyobject( - name, type, 1, ParamValue::INTERP_CONSTANT, obj)); - }) - .def("__init__", [](ParamValue* self, const std::string& name, - TypeDesc type, int nvalues, - ParamValue::Interp interp, nb::handle obj) { - new (self) ParamValue( - paramvalue_from_pyobject(name, type, nvalues, interp, obj)); - }); - - nb::class_(m, "ParamValueList") - .def(nb::init<>()) - .def( - "__getitem__", - [](const ParamValueList& self, size_t i) { - if (i >= self.size()) - throw nb::index_error(); - return self[i]; - }, - nb::rv_policy::reference_internal) - .def("__getitem__", - [](const ParamValueList& self, const std::string& key) { - auto p = self.find(key); - if (p == self.end()) { - std::string message = "key '" + key + "' does not exist"; - throw nb::key_error(message.c_str()); - } - return make_pyobject(p->data(), p->type()); - }) - .def("__setitem__", - [](ParamValueList& self, const std::string& key, nb::handle val) { - delegate_setitem(self, key, val); - }) - .def("__delitem__", [](ParamValueList& self, - const std::string& key) { self.remove(key); }) - .def("__contains__", - [](const ParamValueList& self, const std::string& key) { - return self.contains(key); - }) - .def("__len__", [](const ParamValueList& self) { return self.size(); }) - .def( - "__iter__", - [](const ParamValueList& self) { - return nb::make_iterator(nb::type(), "iterator", - self.begin(), self.end()); - }, - nb::keep_alive<0, 1>()) - .def("append", [](ParamValueList& self, - const ParamValue& value) { self.push_back(value); }) - .def("clear", &ParamValueList::clear) - .def("free", &ParamValueList::free) - .def("resize", - [](ParamValueList& self, size_t size) { self.resize(size); }) - .def( - "remove", - [](ParamValueList& self, const std::string& name, TypeDesc type, - bool casesensitive) { self.remove(name, type, casesensitive); }, - "name"_a, "type"_a = TypeUnknown, "casesensitive"_a = true) - .def( - "contains", - [](const ParamValueList& self, const std::string& name, - TypeDesc type, bool casesensitive) { - return self.contains(name, type, casesensitive); - }, - "name"_a, "type"_a = TypeUnknown, "casesensitive"_a = true) - .def( - "add_or_replace", - [](ParamValueList& self, const ParamValue& pv, bool casesensitive) { - return self.add_or_replace(pv, casesensitive); - }, - "value"_a, "casesensitive"_a = true) - .def("sort", &ParamValueList::sort, "casesensitive"_a = true) - .def("merge", &ParamValueList::merge, "other"_a, "override"_a = false) - .def("attribute", - [](ParamValueList& self, const std::string& name, nb::handle val) { - attribute_onearg(self, name, val); - }) - .def("attribute", - [](ParamValueList& self, const std::string& name, TypeDesc type, - nb::handle obj) { attribute_typed(self, name, type, obj); }) - .def("attribute", [](ParamValueList& self, const std::string& name, - TypeDesc type, int nvalues, nb::handle obj) { - attribute_typed_nvalues(self, name, type, nvalues, obj); - }); -} - -} // namespace PyOpenImageIO diff --git a/src/python-nanobind/py_roi.cpp b/src/python-nanobind/py_roi.cpp deleted file mode 100644 index d58716de1f..0000000000 --- a/src/python-nanobind/py_roi.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: Apache-2.0 -// https://github.com/AcademySoftwareFoundation/OpenImageIO - -#include "py_oiio.h" - -namespace { - -using namespace OIIO; - -bool -roi_contains_coord(const ROI& roi, int x, int y, int z, int ch) -{ - return roi.contains(x, y, z, ch); -} - - -bool -roi_contains_roi(const ROI& roi, const ROI& other) -{ - return roi.contains(other); -} - -} // namespace - - -namespace PyOpenImageIO { - -void -declare_roi(nb::module_& m) -{ - nb::class_ roi(m, "ROI"); - roi.def_rw("xbegin", &ROI::xbegin) - .def_rw("xend", &ROI::xend) - .def_rw("ybegin", &ROI::ybegin) - .def_rw("yend", &ROI::yend) - .def_rw("zbegin", &ROI::zbegin) - .def_rw("zend", &ROI::zend) - .def_rw("chbegin", &ROI::chbegin) - .def_rw("chend", &ROI::chend) - .def(nb::init<>()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def_prop_ro("defined", &ROI::defined) - .def_prop_ro("width", &ROI::width) - .def_prop_ro("height", &ROI::height) - .def_prop_ro("depth", &ROI::depth) - .def_prop_ro("nchannels", &ROI::nchannels) - .def_prop_ro("npixels", &ROI::npixels) - .def("contains", &roi_contains_coord, "x"_a, "y"_a, "z"_a = 0, - "ch"_a = 0) - .def("contains", &roi_contains_roi, "other"_a) - .def_prop_ro_static("All", [](nb::handle) { return ROI::All(); }) - .def("__str__", - [](const ROI& roi_) { return Strutil::fmt::format("{}", roi_); }) - .def("copy", [](const ROI& self) { return self; }) - .def(nb::self == nb::self) - .def(nb::self != nb::self); - - m.def("union", &roi_union); - m.def("intersection", &roi_intersection); - m.def("get_roi", &get_roi); - m.def("get_roi_full", &get_roi_full); - m.def("set_roi", &set_roi); - m.def("set_roi_full", &set_roi_full); -} - -} // namespace PyOpenImageIO diff --git a/src/python-nanobind/py_typedesc.cpp b/src/python-nanobind/py_typedesc.cpp deleted file mode 100644 index 883ef8430c..0000000000 --- a/src/python-nanobind/py_typedesc.cpp +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright Contributors to the OpenImageIO project. -// SPDX-License-Identifier: Apache-2.0 -// https://github.com/AcademySoftwareFoundation/OpenImageIO - -#include "py_oiio.h" - -namespace { - -using namespace OIIO; - -template -void -typedesc_property(TypeDesc& t, Enum value); - -template<> -void -typedesc_property(TypeDesc& t, TypeDesc::BASETYPE value) -{ - t.basetype = value; -} - -template<> -void -typedesc_property(TypeDesc& t, TypeDesc::AGGREGATE value) -{ - t.aggregate = value; -} - -template<> -void -typedesc_property(TypeDesc& t, - TypeDesc::VECSEMANTICS value) -{ - t.vecsemantics = value; -} - -} // namespace - - -namespace PyOpenImageIO { - -void -declare_typedesc(nb::module_& m) -{ - using BASETYPE = TypeDesc::BASETYPE; - using AGGREGATE = TypeDesc::AGGREGATE; - using VECSEMANTICS = TypeDesc::VECSEMANTICS; - - nb::enum_(m, "BASETYPE") - .value("UNKNOWN", TypeDesc::UNKNOWN) - .value("NONE", TypeDesc::NONE) - .value("UCHAR", TypeDesc::UCHAR) - .value("UINT8", TypeDesc::UINT8) - .value("CHAR", TypeDesc::CHAR) - .value("INT8", TypeDesc::INT8) - .value("UINT16", TypeDesc::UINT16) - .value("USHORT", TypeDesc::USHORT) - .value("SHORT", TypeDesc::SHORT) - .value("INT16", TypeDesc::INT16) - .value("UINT", TypeDesc::UINT) - .value("UINT32", TypeDesc::UINT32) - .value("INT", TypeDesc::INT) - .value("INT32", TypeDesc::INT32) - .value("ULONGLONG", TypeDesc::ULONGLONG) - .value("UINT64", TypeDesc::UINT64) - .value("LONGLONG", TypeDesc::LONGLONG) - .value("INT64", TypeDesc::INT64) - .value("HALF", TypeDesc::HALF) - .value("FLOAT", TypeDesc::FLOAT) - .value("DOUBLE", TypeDesc::DOUBLE) - .value("STRING", TypeDesc::STRING) - .value("PTR", TypeDesc::PTR) - .value("LASTBASE", TypeDesc::LASTBASE) - .export_values(); - - nb::enum_(m, "AGGREGATE") - .value("SCALAR", TypeDesc::SCALAR) - .value("VEC2", TypeDesc::VEC2) - .value("VEC3", TypeDesc::VEC3) - .value("VEC4", TypeDesc::VEC4) - .value("MATRIX33", TypeDesc::MATRIX33) - .value("MATRIX44", TypeDesc::MATRIX44) - .export_values(); - - nb::enum_(m, "VECSEMANTICS") - .value("NOXFORM", TypeDesc::NOXFORM) - .value("NOSEMANTICS", TypeDesc::NOSEMANTICS) - .value("COLOR", TypeDesc::COLOR) - .value("POINT", TypeDesc::POINT) - .value("VECTOR", TypeDesc::VECTOR) - .value("NORMAL", TypeDesc::NORMAL) - .value("TIMECODE", TypeDesc::TIMECODE) - .value("KEYCODE", TypeDesc::KEYCODE) - .value("RATIONAL", TypeDesc::RATIONAL) - .value("BOX", TypeDesc::BOX) - .export_values(); - - nb::class_(m, "TypeDesc") - .def_prop_rw( - "basetype", [](TypeDesc t) { return BASETYPE(t.basetype); }, - [](TypeDesc& t, BASETYPE b) { typedesc_property(t, b); }) - .def_prop_rw( - "aggregate", [](TypeDesc t) { return AGGREGATE(t.aggregate); }, - [](TypeDesc& t, AGGREGATE b) { typedesc_property(t, b); }) - .def_prop_rw( - "vecsemantics", - [](TypeDesc t) { return VECSEMANTICS(t.vecsemantics); }, - [](TypeDesc& t, VECSEMANTICS b) { typedesc_property(t, b); }) - .def_rw("arraylen", &TypeDesc::arraylen) - .def(nb::init<>()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def(nb::init()) - .def("c_str", - [](const TypeDesc& self) { return std::string(self.c_str()); }) - .def("numelements", &TypeDesc::numelements) - .def("basevalues", &TypeDesc::basevalues) - .def("size", &TypeDesc::size) - .def("elementtype", &TypeDesc::elementtype) - .def("elementsize", &TypeDesc::elementsize) - .def("basesize", &TypeDesc::basesize) - .def("fromstring", - [](TypeDesc& t, const char* typestring) { - t.fromstring(typestring); - }) - .def("equivalent", &TypeDesc::equivalent) - .def("unarray", &TypeDesc::unarray) - .def("is_vec2", - [](const TypeDesc& t, BASETYPE b = TypeDesc::FLOAT) { - return t.is_vec2(b); - }) - .def("is_vec3", - [](const TypeDesc& t, BASETYPE b = TypeDesc::FLOAT) { - return t.is_vec3(b); - }) - .def("is_vec4", - [](const TypeDesc& t, BASETYPE b = TypeDesc::FLOAT) { - return t.is_vec4(b); - }) - .def("is_box2", - [](const TypeDesc& t, BASETYPE b = TypeDesc::FLOAT) { - return t.is_box2(b); - }) - .def("is_box3", - [](const TypeDesc& t, BASETYPE b = TypeDesc::FLOAT) { - return t.is_box3(b); - }) - .def_static("all_types_equal", - [](const std::vector& types) { - return TypeDesc::all_types_equal(types); - }) - .def(nb::self == nb::self) - .def(nb::self != nb::self) - .def("__str__", [](TypeDesc t) { return std::string(t.c_str()); }) - .def("__repr__", [](TypeDesc t) { - return Strutil::fmt::format("", t.c_str()); - }); - - nb::implicitly_convertible(); - nb::implicitly_convertible(); - - m.attr("UNKNOWN") = nb::cast(TypeDesc::UNKNOWN); - m.attr("NONE") = nb::cast(TypeDesc::NONE); - m.attr("UCHAR") = nb::cast(TypeDesc::UCHAR); - m.attr("UINT8") = nb::cast(TypeDesc::UINT8); - m.attr("CHAR") = nb::cast(TypeDesc::CHAR); - m.attr("INT8") = nb::cast(TypeDesc::INT8); - m.attr("UINT16") = nb::cast(TypeDesc::UINT16); - m.attr("USHORT") = nb::cast(TypeDesc::USHORT); - m.attr("SHORT") = nb::cast(TypeDesc::SHORT); - m.attr("INT16") = nb::cast(TypeDesc::INT16); - m.attr("UINT") = nb::cast(TypeDesc::UINT); - m.attr("UINT32") = nb::cast(TypeDesc::UINT32); - m.attr("INT") = nb::cast(TypeDesc::INT); - m.attr("INT32") = nb::cast(TypeDesc::INT32); - m.attr("ULONGLONG") = nb::cast(TypeDesc::ULONGLONG); - m.attr("UINT64") = nb::cast(TypeDesc::UINT64); - m.attr("LONGLONG") = nb::cast(TypeDesc::LONGLONG); - m.attr("INT64") = nb::cast(TypeDesc::INT64); - m.attr("HALF") = nb::cast(TypeDesc::HALF); - m.attr("FLOAT") = nb::cast(TypeDesc::FLOAT); - m.attr("DOUBLE") = nb::cast(TypeDesc::DOUBLE); - m.attr("STRING") = nb::cast(TypeDesc::STRING); - m.attr("PTR") = nb::cast(TypeDesc::PTR); - m.attr("LASTBASE") = nb::cast(TypeDesc::LASTBASE); - - m.attr("SCALAR") = nb::cast(TypeDesc::SCALAR); - m.attr("VEC2") = nb::cast(TypeDesc::VEC2); - m.attr("VEC3") = nb::cast(TypeDesc::VEC3); - m.attr("VEC4") = nb::cast(TypeDesc::VEC4); - m.attr("MATRIX33") = nb::cast(TypeDesc::MATRIX33); - m.attr("MATRIX44") = nb::cast(TypeDesc::MATRIX44); - - m.attr("NOXFORM") = nb::cast(TypeDesc::NOXFORM); - m.attr("NOSEMANTICS") = nb::cast(TypeDesc::NOSEMANTICS); - m.attr("COLOR") = nb::cast(TypeDesc::COLOR); - m.attr("POINT") = nb::cast(TypeDesc::POINT); - m.attr("VECTOR") = nb::cast(TypeDesc::VECTOR); - m.attr("NORMAL") = nb::cast(TypeDesc::NORMAL); - m.attr("TIMECODE") = nb::cast(TypeDesc::TIMECODE); - m.attr("KEYCODE") = nb::cast(TypeDesc::KEYCODE); - m.attr("RATIONAL") = nb::cast(TypeDesc::RATIONAL); - m.attr("BOX") = nb::cast(TypeDesc::BOX); - - m.attr("TypeUnknown") = TypeUnknown; - m.attr("TypeFloat") = TypeFloat; - m.attr("TypeColor") = TypeColor; - m.attr("TypePoint") = TypePoint; - m.attr("TypeVector") = TypeVector; - m.attr("TypeNormal") = TypeNormal; - m.attr("TypeString") = TypeString; - m.attr("TypeInt") = TypeInt; - m.attr("TypeUInt") = TypeUInt; - m.attr("TypeInt64") = TypeInt64; - m.attr("TypeUInt64") = TypeUInt64; - m.attr("TypeInt32") = TypeInt32; - m.attr("TypeUInt32") = TypeUInt32; - m.attr("TypeInt16") = TypeInt16; - m.attr("TypeUInt16") = TypeUInt16; - m.attr("TypeInt8") = TypeInt8; - m.attr("TypeUInt8") = TypeUInt8; - m.attr("TypeHalf") = TypeHalf; - m.attr("TypeMatrix") = TypeMatrix; - m.attr("TypeMatrix33") = TypeMatrix33; - m.attr("TypeMatrix44") = TypeMatrix44; - m.attr("TypeTimeCode") = TypeTimeCode; - m.attr("TypeKeyCode") = TypeKeyCode; - m.attr("TypeFloat2") = TypeFloat2; - m.attr("TypeVector2") = TypeVector2; - m.attr("TypeFloat4") = TypeFloat4; - m.attr("TypeVector4") = TypeVector4; - m.attr("TypeVector2i") = TypeVector2i; - m.attr("TypeVector3i") = TypeVector3i; - m.attr("TypeBox2") = TypeBox2; - m.attr("TypeBox3") = TypeBox3; - m.attr("TypeBox2i") = TypeBox2i; - m.attr("TypeBox3i") = TypeBox3i; - m.attr("TypeRational") = TypeRational; - m.attr("TypeURational") = TypeURational; - m.attr("TypePointer") = TypePointer; -} - -} // namespace PyOpenImageIO diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 94b2a3e4d3..6a3b7bb537 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -4,19 +4,42 @@ add_subdirectory (stubs) -file (GLOB python_srcs *.cpp) -setup_python_module (TARGET PyOpenImageIO - MODULE OpenImageIO - SOURCES ${python_srcs} - LIBS OpenImageIO - ${OPENIMAGEIO_IMATH_TARGETS} - ) +set (python_dual_backend_srcs + py_roi.cpp + py_typedesc.cpp + py_imagespec.cpp + py_paramvalue.cpp) -# Unity builds: If in unity group mode, make the python bindings one group. If -# in unity batch mode, use the smaller batch size because these tend to be -# expensive files to compile. -set_target_properties(PyOpenImageIO PROPERTIES - UNITY_BUILD_BATCH_SIZE ${UNITY_SMALL_BATCH_SIZE}) -set_source_files_properties(${python_srcs} PROPERTIES - UNITY_GROUP PyOpenImageIO) +# Full pybind11 module (pybind11 or both backends). +if (OIIO_BUILD_PYTHON_PYBIND11) + file (GLOB python_srcs *.cpp) + setup_python_module (TARGET PyOpenImageIO + MODULE OpenImageIO + SOURCES ${python_srcs} + LIBS OpenImageIO + ${OPENIMAGEIO_IMATH_TARGETS} + ) + # Unity builds: If in unity group mode, make the python bindings one group. If + # in unity batch mode, use the smaller batch size because these tend to be + # expensive files to compile. + set_target_properties(PyOpenImageIO PROPERTIES + UNITY_BUILD_BATCH_SIZE ${UNITY_SMALL_BATCH_SIZE}) + set_source_files_properties(${python_srcs} PROPERTIES + UNITY_GROUP PyOpenImageIO) +endif () + +# Nanobind-only backend: compile the py_*.cpp files in this directory +# with nanobind and install as the main OpenImageIO Python package. +# When backend is both, skip this; nanobind is built under src/python-nanobind/. +if (OIIO_PYTHON_BINDINGS_BACKEND STREQUAL "nanobind") + setup_python_module_nanobind ( + TARGET PyOpenImageIO + MODULE OpenImageIO + SOURCES py_oiio.cpp ${python_dual_backend_srcs} + LIBS OpenImageIO + ) + + target_compile_definitions (PyOpenImageIO + PRIVATE OIIO_PY_BACKEND_NANOBIND) +endif () diff --git a/src/python-nanobind/MIGRATION_STATUS.md b/src/python/MIGRATION_STATUS.md similarity index 52% rename from src/python-nanobind/MIGRATION_STATUS.md rename to src/python/MIGRATION_STATUS.md index 361b042835..d3ac0d2671 100644 --- a/src/python-nanobind/MIGRATION_STATUS.md +++ b/src/python/MIGRATION_STATUS.md @@ -1,6 +1,13 @@ -# Nanobind migration status (vs `src/python` pybind11) +# Nanobind migration status -Generated from the binding sources. The nanobind extension is `PyOpenImageIONanobind` / module `_OpenImageIO` (see `CMakeLists.txt`). +Nanobind shares binding sources with pybind11 under `src/python/` (see +`py_backend.h` and `python_dual_backend_srcs` in `CMakeLists.txt`). Configure +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 +`OIIO_PY_BACKEND_NANOBIND`. Shared Python↔C++ conversion helpers live in +`py_oiio.h` for both backends. ## Migrated — full parity with pybind (no known gaps for this surface) @@ -15,14 +22,14 @@ Generated from the binding sources. The nanobind extension is `PyOpenImageIONano | Source file | Migrated (vs pybind) | Missing or divergent (vs pybind) | | --- | --- | --- | -| `py_oiio.cpp` (`_OpenImageIO` module) |
  • attribute (one-arg and typed)
  • get_int_attribute
  • get_float_attribute
  • get_string_attribute
  • getattribute
  • __version__
|
  • geterror
  • get_bytes_attribute
  • Module set_colorspace (helper taking ImageSpec — the instance method is on ImageSpec in nanobind)
  • set_colorspace_rec709_gamma
  • equivalent_colorspace
  • is_imageio_format_name
  • AutoStride
  • openimageio_version, VERSION, VERSION_STRING, 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) | Env / DLL path setup, from ._OpenImageIO import *, version docstring. | TODO: Python CLI entry-point trampolines when the install layout matches the full wheel. | +| `py_oiio.cpp` (`OpenImageIO` module) |
  • attribute (one-arg and typed)
  • get_int_attribute
  • get_float_attribute
  • get_string_attribute
  • getattribute
  • __version__, VERSION_STRING
|
  • geterror
  • get_bytes_attribute
  • Module set_colorspace (helper taking ImageSpec — the instance method is on ImageSpec in nanobind)
  • set_colorspace_rec709_gamma
  • equivalent_colorspace
  • is_imageio_format_name
  • AutoStride
  • openimageio_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 under `src/python/` today; there are **no** corresponding `py_*.cpp` files in `src/python-nanobind/`. +These exist only in the pybind11 module today (not in `python_dual_backend_srcs`). | Source file | Python / C++ API | | --- | --- | diff --git a/src/python/py_backend.h b/src/python/py_backend.h new file mode 100644 index 0000000000..fa2d633ef1 --- /dev/null +++ b/src/python/py_backend.h @@ -0,0 +1,178 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +#pragma once + +#if defined(OIIO_PY_BACKEND_NANOBIND) + +# include +# include +# include +# include +# include +# include + +namespace py = nanobind; +using py_module = nanobind::module_; +using namespace py::literals; + +# define OIIO_PY_DEF_RW(name, member) .def_rw(name, member) +# define OIIO_PY_DEF_PROP_RO(name, member) .def_prop_ro(name, member) +# define OIIO_PY_DEF_PROP_RW(name, get, set) .def_prop_rw(name, get, set) +# define OIIO_PY_DEF_READONLY_STATIC_LAMBDA(name, lambda) \ + .def_prop_ro_static(name, lambda) + +namespace oiio_py { + +inline std::string +str_to_stdstring(const py::handle& handle) +{ + return std::string(py::cast(handle).c_str()); +} + +template +inline py::tuple +make_tuple(size_t size, F&& fill) +{ + py::list list; + for (size_t i = 0; i < size; ++i) { + list.append(fill(i)); + } + return py::steal(PyList_AsTuple(list.ptr())); +} + +inline constexpr auto ref_internal = py::rv_policy::reference_internal; + +template +inline auto +str(T&& x) +{ + return std::forward(x); +} + +inline void +throw_key_error(const std::string& msg) +{ + throw py::key_error(msg.c_str()); +} + +inline std::string +bytes_to_stdstring(const py::bytes& b) +{ + return std::string(b.c_str(), b.size()); +} + +template +inline auto +make_iterator(Container& container) +{ + return py::make_iterator(py::type(), "iterator", + container.begin(), container.end()); +} + +// Return a py::object from a binding lambda (nanobind needs borrow). +inline py::object +return_object(py::object obj) +{ + return py::borrow(obj); +} + +// TRANSFERS ownership of data; builds a 1D numpy array of length size. +template +inline py::object +make_numpy_array(T* data, size_t size) +{ + py::capsule owner(data, [](void* p) noexcept { + delete[] reinterpret_cast(p); + }); + py::ndarray> array(data, { size }, owner); + return py::cast(std::move(array), py::rv_policy::move); +} + +} // namespace oiio_py + +#else // pybind11 + +# include +# include +# include + +namespace py = pybind11; +using py_module = pybind11::module; +using namespace py::literals; + +# define OIIO_PY_DEF_RW(name, member) .def_readwrite(name, member) +# define OIIO_PY_DEF_PROP_RO(name, member) .def_property_readonly(name, member) +# define OIIO_PY_DEF_PROP_RW(name, get, set) .def_property(name, get, set) +# define OIIO_PY_DEF_READONLY_STATIC_LAMBDA(name, lambda) \ + .def_property_readonly_static(name, lambda) + +namespace oiio_py { + +inline std::string +str_to_stdstring(const py::handle& handle) +{ + return std::string(py::cast(handle)); +} + +template +inline py::tuple +make_tuple(size_t size, F&& fill) +{ + py::tuple result(size); + for (size_t i = 0; i < size; ++i) { + result[i] = fill(i); + } + return result; +} + +inline constexpr auto ref_internal = + py::return_value_policy::reference_internal; + +template +inline auto +str(T&& x) +{ + return py::str(std::forward(x)); +} + +inline void +throw_key_error(const std::string& msg) +{ + throw py::key_error(msg); +} + +inline std::string +bytes_to_stdstring(const py::bytes& b) +{ + return std::string(b); +} + +template +inline auto +make_iterator(Container& container) +{ + return py::make_iterator(container.begin(), container.end()); +} + +inline py::object +return_object(py::object obj) +{ + return obj; +} + +// TRANSFERS ownership of data; builds a 1D numpy array of length size. +template +inline py::object +make_numpy_array(T* data, size_t size) +{ + py::capsule free_when_done(data, [](void* f) { + delete[] (reinterpret_cast(f)); + }); + return py::array_t({ size }, { sizeof(T) }, data, free_when_done); +} + +} // namespace oiio_py + +#endif diff --git a/src/python/py_imagespec.cpp b/src/python/py_imagespec.cpp index 64a637fd9d..e581c2a684 100644 --- a/src/python/py_imagespec.cpp +++ b/src/python/py_imagespec.cpp @@ -56,44 +56,42 @@ ImageSpec_getattribute_typed(const ImageSpec& spec, const std::string& name, void -declare_imagespec(py::module& m) +declare_imagespec(py_module& m) { - using namespace pybind11::literals; - py::class_(m, "ImageSpec") - .def_readwrite("x", &ImageSpec::x) - .def_readwrite("y", &ImageSpec::y) - .def_readwrite("z", &ImageSpec::z) - .def_readwrite("width", &ImageSpec::width) - .def_readwrite("height", &ImageSpec::height) - .def_readwrite("depth", &ImageSpec::depth) - .def_readwrite("full_x", &ImageSpec::full_x) - .def_readwrite("full_y", &ImageSpec::full_y) - .def_readwrite("full_z", &ImageSpec::full_z) - .def_readwrite("full_width", &ImageSpec::full_width) - .def_readwrite("full_height", &ImageSpec::full_height) - .def_readwrite("full_depth", &ImageSpec::full_depth) - .def_readwrite("tile_width", &ImageSpec::tile_width) - .def_readwrite("tile_height", &ImageSpec::tile_height) - .def_readwrite("tile_depth", &ImageSpec::tile_depth) - .def_readwrite("nchannels", &ImageSpec::nchannels) - .def_readwrite("format", &ImageSpec::format) - .def_property( + OIIO_PY_DEF_RW("x", &ImageSpec::x) + OIIO_PY_DEF_RW("y", &ImageSpec::y) + OIIO_PY_DEF_RW("z", &ImageSpec::z) + OIIO_PY_DEF_RW("width", &ImageSpec::width) + OIIO_PY_DEF_RW("height", &ImageSpec::height) + OIIO_PY_DEF_RW("depth", &ImageSpec::depth) + OIIO_PY_DEF_RW("full_x", &ImageSpec::full_x) + OIIO_PY_DEF_RW("full_y", &ImageSpec::full_y) + OIIO_PY_DEF_RW("full_z", &ImageSpec::full_z) + OIIO_PY_DEF_RW("full_width", &ImageSpec::full_width) + OIIO_PY_DEF_RW("full_height", &ImageSpec::full_height) + OIIO_PY_DEF_RW("full_depth", &ImageSpec::full_depth) + OIIO_PY_DEF_RW("tile_width", &ImageSpec::tile_width) + OIIO_PY_DEF_RW("tile_height", &ImageSpec::tile_height) + OIIO_PY_DEF_RW("tile_depth", &ImageSpec::tile_depth) + OIIO_PY_DEF_RW("nchannels", &ImageSpec::nchannels) + OIIO_PY_DEF_RW("format", &ImageSpec::format) + OIIO_PY_DEF_PROP_RW( "channelformats", [](const ImageSpec& spec) { return ImageSpec_get_channelformats(spec); }, &ImageSpec_set_channelformats) - .def_property("channelnames", &ImageSpec_get_channelnames, - &ImageSpec_set_channelnames) - .def_readwrite("alpha_channel", &ImageSpec::alpha_channel) - .def_readwrite("z_channel", &ImageSpec::z_channel) - .def_readwrite("deep", &ImageSpec::deep) - .def_readwrite("extra_attribs", &ImageSpec::extra_attribs) + OIIO_PY_DEF_PROP_RW("channelnames", &ImageSpec_get_channelnames, + &ImageSpec_set_channelnames) + OIIO_PY_DEF_RW("alpha_channel", &ImageSpec::alpha_channel) + OIIO_PY_DEF_RW("z_channel", &ImageSpec::z_channel) + OIIO_PY_DEF_RW("deep", &ImageSpec::deep) + OIIO_PY_DEF_RW("extra_attribs", &ImageSpec::extra_attribs) - .def_property("roi", &ImageSpec::roi, &ImageSpec::set_roi) - .def_property("roi_full", &ImageSpec::roi_full, - &ImageSpec::set_roi_full) + OIIO_PY_DEF_PROP_RW("roi", &ImageSpec::roi, &ImageSpec::set_roi) + OIIO_PY_DEF_PROP_RW("roi_full", &ImageSpec::roi_full, + &ImageSpec::set_roi_full) .def(py::init<>()) .def(py::init()) @@ -161,7 +159,7 @@ declare_imagespec(py::module& m) int chan) { return spec.channelformat(chan); }) .def("channel_name", [](const ImageSpec& spec, int chan) { - return PY_STR(std::string(spec.channel_name(chan))); + return oiio_py::str(std::string(spec.channel_name(chan))); }) .def("channelindex", [](const ImageSpec& spec, const std::string& name) { @@ -202,7 +200,7 @@ declare_imagespec(py::module& m) "get_string_attribute", [](const ImageSpec& spec, const std::string& name, const std::string& def) { - return PY_STR( + return oiio_py::str( std::string(spec.get_string_attribute(name, def))); }, "name"_a, "defaultval"_a = "") @@ -210,8 +208,8 @@ declare_imagespec(py::module& m) "get_bytes_attribute", [](const ImageSpec& spec, const std::string& name, const std::string& def) { - return py::bytes( - std::string(spec.get_string_attribute(name, def))); + std::string s(spec.get_string_attribute(name, def)); + return py::bytes(s.data(), s.size()); }, "name"_a, "defaultval"_a = "") .def("getattribute", &ImageSpec_getattribute_typed, "name"_a, @@ -221,7 +219,8 @@ declare_imagespec(py::module& m) [](const ImageSpec& self, const std::string& key, py::object def) { ParamValue tmpparam; auto p = self.find_attribute(key, tmpparam); - return p ? make_pyobject(p->data(), p->type(), 1, def) : def; + return p ? make_pyobject(p->data(), p->type(), 1, def) + : oiio_py::return_object(def); }, "key"_a, "default"_a = py::none()) .def( @@ -235,7 +234,8 @@ declare_imagespec(py::module& m) .def_static( "metadata_val", [](const ParamValue& p, bool human) { - return PY_STR(ImageSpec::metadata_val(p, human)); + return oiio_py::str( + std::string(ImageSpec::metadata_val(p, human))); }, "param"_a, "human"_a = false) .def( @@ -252,12 +252,17 @@ declare_imagespec(py::module& m) verb = ImageSpec::SerialDetailed; else if (Strutil::iequals(verbose, "detailedhuman")) verb = ImageSpec::SerialDetailedHuman; - return PY_STR(spec.serialize(fmt, verb)); + return oiio_py::str(std::string(spec.serialize(fmt, verb))); }, "format"_a = "text", "verbose"_a = "detailed") .def("to_xml", - [](const ImageSpec& spec) { return PY_STR(spec.to_xml()); }) - .def("from_xml", &ImageSpec::from_xml) + [](const ImageSpec& spec) { + return oiio_py::str(std::string(spec.to_xml())); + }) + .def("from_xml", + [](ImageSpec& self, const std::string& xml) { + self.from_xml(xml.c_str()); + }) .def( "valid_tile_range", [](ImageSpec& self, int xbegin, int xend, int ybegin, int yend, @@ -280,7 +285,7 @@ declare_imagespec(py::module& m) ParamValue tmpparam; auto p = self.find_attribute(key, tmpparam); if (p == nullptr) - throw py::key_error("key '" + key + "' does not exist"); + oiio_py::throw_key_error("key '" + key + "' does not exist"); return make_pyobject(p->data(), p->type()); }) // __setitem__ is the dict-like `ImageSpec[key] = value` assignment diff --git a/src/python/py_oiio.cpp b/src/python/py_oiio.cpp index 8ba16fa27e..93ed0a273d 100644 --- a/src/python/py_oiio.cpp +++ b/src/python/py_oiio.cpp @@ -6,6 +6,11 @@ #include +#if defined(OIIO_PY_BACKEND_NANOBIND) +# include +# include +#endif + namespace PyOpenImageIO { @@ -66,6 +71,8 @@ typedesc_from_python_array_code(string_view code) +#ifndef OIIO_PY_BACKEND_NANOBIND + oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf) { if (pybuf.format.size()) @@ -191,12 +198,17 @@ oiio_bufinfo::oiio_bufinfo(const py::buffer_info& pybuf, int nchans, int width, +#endif // !OIIO_PY_BACKEND_NANOBIND + + + py::object make_pyobject(const void* data, TypeDesc type, int nvalues, py::object defaultvalue) { - if (!data || !nvalues) - return defaultvalue; + if (!data || !nvalues) { + return oiio_py::return_object(defaultvalue); + } if (type.basetype == TypeDesc::INT32) return C_to_val_or_tuple((const int*)data, type, nvalues); if (type.basetype == TypeDesc::FLOAT) @@ -223,16 +235,15 @@ make_pyobject(const void* data, TypeDesc type, int nvalues, // take possession of it. int n = type.arraylen * nvalues; if (n <= 0) - return defaultvalue; - uint8_t* ucdata(new uint8_t[n]); - std::memcpy(ucdata, data, n); - return make_numpy_array(ucdata, 1, 1, size_t(type.arraylen), - size_t(nvalues)); + return oiio_py::return_object(defaultvalue); + auto* copy = new uint8_t[n]; + std::memcpy(copy, data, static_cast(n)); + return oiio_py::make_numpy_array(copy, static_cast(n)); } if (type.basetype == TypeDesc::UINT8) return C_to_val_or_tuple((const unsigned char*)data, type, nvalues); debugfmt("Don't know how to handle type {}\n", type); - return defaultvalue; + return oiio_py::return_object(defaultvalue); } @@ -240,7 +251,7 @@ make_pyobject(const void* data, TypeDesc type, int nvalues, static py::object oiio_getattribute_typed(const std::string& name, TypeDesc type = TypeUnknown) { - if (type == TypeDesc::UNKNOWN) + if (type == TypeUnknown) return py::none(); char* data = OIIO_ALLOCA(char, type.size()); if (!OIIO::getattribute(name, type, data)) @@ -270,25 +281,21 @@ struct oiio_global_attrib_wrapper { }; - -// This OIIO_DECLARE_PYMODULE mojo is necessary if we want to pass in the -// MODULE name as a #define. Google for Argument-Prescan for additional -// info on why this is necessary - -#define OIIO_DECLARE_PYMODULE(x) PYBIND11_MODULE(x, m) - -OIIO_DECLARE_PYMODULE(PYMODULE_NAME) +static void +declare_global_bindings(py_module& m) { - using namespace pybind11::literals; - - if (Sysutil::getenv("OPENIMAGEIO_DEBUG_PYTHON") != "") - Sysutil::setup_crash_stacktrace("stdout"); - - // Basic helper classes declare_typedesc(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); @@ -306,9 +313,14 @@ OIIO_DECLARE_PYMODULE(PYMODULE_NAME) declare_texturesystem(m); declare_imagebufalgo(m); +} +#endif - // Global (OpenImageIO scope) functions and symbols - m.def("geterror", &OIIO::geterror, "clear"_a = true); + + +static void +declare_global_attribute_functions(py_module& m) +{ m.def("attribute", [](const std::string& name, const py::object& obj) { oiio_global_attrib_wrapper wrapper; attribute_onearg(wrapper, name, obj); @@ -324,55 +336,125 @@ OIIO_DECLARE_PYMODULE(PYMODULE_NAME) [](const std::string& name, int def) { return OIIO::get_int_attribute(name, def); }, - py::arg("name"), py::arg("defaultval") = 0); + "name"_a, "defaultval"_a = 0); m.def( "get_float_attribute", [](const std::string& name, float def) { return OIIO::get_float_attribute(name, def); }, - py::arg("name"), py::arg("defaultval") = 0.0f); + "name"_a, "defaultval"_a = 0.0f); m.def( "get_string_attribute", [](const std::string& name, const std::string& def) { - return PY_STR(std::string(OIIO::get_string_attribute(name, def))); + return oiio_py::str( + std::string(OIIO::get_string_attribute(name, def))); }, - py::arg("name"), py::arg("defaultval") = ""); + "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) { - return py::bytes( - std::string(OIIO::get_string_attribute(name, def))); + std::string s(OIIO::get_string_attribute(name, def)); + return py::bytes(s.data(), s.size()); }, - py::arg("name"), py::arg("defaultval") = ""); - m.def("getattribute", &oiio_getattribute_typed); + "name"_a, "defaultval"_a = ""); m.def( "set_colorspace", [](ImageSpec& spec, const std::string& name) { set_colorspace(spec, name); }, - py::arg("spec"), py::arg("name")); - m.def("set_colorspace_rec709_gamma", [](ImageSpec& spec, float gamma) { - set_colorspace_rec709_gamma(spec, gamma); - }); + "spec"_a, "name"_a); + m.def("set_colorspace_rec709_gamma", + [](ImageSpec& spec, float gamma) { + set_colorspace_rec709_gamma(spec, gamma); + }, + "spec"_a, "gamma"_a); m.def("equivalent_colorspace", [](const std::string& a, const std::string& b) { return equivalent_colorspace(a, b); - }); + }, + "a"_a, "b"_a); m.def( "is_imageio_format_name", [](const std::string& name) { return OIIO::is_imageio_format_name(name); }, - py::arg("name")); + "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; - m.attr("VERSION_STRING") = PY_STR(OIIO_VERSION_STRING); + m.attr("VERSION_STRING") = oiio_py::str(OIIO_VERSION_STRING); m.attr("VERSION_MAJOR") = OIIO_VERSION_MAJOR; m.attr("VERSION_MINOR") = OIIO_VERSION_MINOR; m.attr("VERSION_PATCH") = OIIO_VERSION_PATCH; - m.attr("INTRO_STRING") = PY_STR(OIIO_INTRO_STRING); - m.attr("__version__") = PY_STR(OIIO_VERSION_STRING); + m.attr("INTRO_STRING") = oiio_py::str(OIIO_INTRO_STRING); + m.attr("__version__") = oiio_py::str(OIIO_VERSION_STRING); +#endif +} + + + +#if defined(OIIO_PY_BACKEND_NANOBIND) + +} // namespace PyOpenImageIO + +#define OIIO_DECLARE_NB_MODULE(x) NB_MODULE(x, m) + +#if defined(OIIO_PY_NANOBIND_ISOLATED_PACKAGE) +OIIO_DECLARE_NB_MODULE(_OpenImageIO) +#else +OIIO_DECLARE_NB_MODULE(OpenImageIO) +#endif +{ + m.doc() = "OpenImageIO nanobind bindings."; + + PyOpenImageIO::declare_global_bindings(m); + PyOpenImageIO::declare_global_attribute_functions(m); + PyOpenImageIO::declare_module_attributes(m); +} + +#else // pybind11 + +// This OIIO_DECLARE_PYMODULE mojo is necessary if we want to pass in the +// MODULE name as a #define. Google for Argument-Prescan for additional +// info on why this is necessary + +#define OIIO_DECLARE_PYMODULE(x) PYBIND11_MODULE(x, m) + +OIIO_DECLARE_PYMODULE(PYMODULE_NAME) +{ + if (Sysutil::getenv("OPENIMAGEIO_DEBUG_PYTHON") != "") + 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); } } // namespace PyOpenImageIO + +#endif // OIIO_PY_BACKEND_NANOBIND diff --git a/src/python/py_oiio.h b/src/python/py_oiio.h index 23301f30dc..a9a42c78f4 100644 --- a/src/python/py_oiio.h +++ b/src/python/py_oiio.h @@ -38,18 +38,27 @@ #include #include -#include -#include -#include -#include -namespace py = pybind11; +#if defined(OIIO_PY_BACKEND_NANOBIND) +# include +#endif + +#include "py_backend.h" +#if defined(OIIO_PY_BACKEND_NANOBIND) +# define PY_STR(x) oiio_py::str(x) + +#else +# include +# include // Python3 is always unicode, so return a true str -#define PY_STR py::str +# define PY_STR py::str +#endif + +#ifndef OIIO_PY_BACKEND_NANOBIND namespace pybind11 { namespace detail { @@ -76,6 +85,7 @@ namespace detail { } // namespace detail } // namespace pybind11 +#endif @@ -85,23 +95,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); @@ -109,12 +119,13 @@ TypeDesc typedesc_from_python_array_code (string_view code); // const char * python_array_code (TypeDesc format); // unused +#ifndef OIIO_PY_BACKEND_NANOBIND inline std::string object_classname(const py::object& obj) { return obj.attr("__class__").attr("__name__").cast(); } - +#endif template struct PyTypeForCType { }; @@ -129,12 +140,12 @@ template<> struct PyTypeForCType { typedef py::int_ type; }; template<> struct PyTypeForCType { typedef py::float_ type; }; template<> struct PyTypeForCType { typedef py::float_ type; }; template<> struct PyTypeForCType { typedef py::float_ type; }; -template<> struct PyTypeForCType { typedef PY_STR type; }; -template<> struct PyTypeForCType { typedef PY_STR type; }; +template<> struct PyTypeForCType { typedef py::str type; }; +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 struct oiio_bufinfo { @@ -155,6 +166,7 @@ struct oiio_bufinfo { // Retrieve presumed contiguous data value index i. template T dataval(size_t i) { return ((const T*)data)[i]; } }; +#endif @@ -173,13 +185,13 @@ py_indexable_pod_to_stdvector(std::vector& vals, const PYT& obj) for (size_t i = 0; i < length; ++i) { auto elem = obj[i]; if (std::is_same::value && py::isinstance(elem)) { - vals.emplace_back(T(elem.template cast())); + vals.emplace_back(T(py::cast(elem))); } else if ((std::is_same::value || std::is_same::value) && py::isinstance(elem)) { - vals.emplace_back(T(elem.template cast())); + vals.emplace_back(T(py::cast(elem))); } else if (std::is_same::value && py::isinstance(elem)) { - vals.emplace_back(T(elem.template cast())); + vals.emplace_back(T(py::cast(elem))); } else { // FIXME? Other cases? vals.emplace_back(T(42)); @@ -203,7 +215,7 @@ py_indexable_pod_to_stdvector(std::vector& vals, const PYT& obj) for (size_t i = 0; i < length; ++i) { auto elem = obj[i]; if (py::isinstance(elem)) { - vals.emplace_back(elem.template cast()); + vals.emplace_back(oiio_py::str_to_stdstring(elem)); } else { // FIXME? Other cases? vals.emplace_back(""); @@ -227,12 +239,12 @@ py_indexable_pod_to_stdvector(std::vector& vals, const PYT& obj) for (size_t i = 0; i < length; ++i) { auto elem = obj[i]; if (py::isinstance(elem)) { - vals.emplace_back(*(elem.template cast())); + vals.emplace_back(py::cast(elem)); } else if (py::isinstance(elem)) { - vals.emplace_back(*(elem.template cast())); + vals.emplace_back(TypeDesc(py::cast(elem))); } else if (py::isinstance(elem)) { vals.emplace_back( - TypeDesc(std::string(elem.template cast()))); + TypeDesc(oiio_py::str_to_stdstring(elem))); } else { // FIXME? Other cases? vals.emplace_back(TypeUnknown); @@ -251,7 +263,7 @@ py_scalar_pod_to_stdvector(std::vector& vals, const py::object& obj) using pytype = typename PyTypeForCType::type; vals.clear(); if (py::isinstance(obj)) { - vals.emplace_back(obj.template cast()); + vals.emplace_back(py::cast(obj)); return true; } else { return false; @@ -265,10 +277,10 @@ py_scalar_pod_to_stdvector(std::vector& vals, const py::object& obj) { vals.clear(); if (py::isinstance(obj)) { - vals.emplace_back(obj.template cast()); + vals.emplace_back(py::cast(obj)); return true; } else if (py::isinstance(obj)) { - int i = obj.template cast(); + int i = py::cast(obj); vals.emplace_back((float)i); return true; } else { @@ -283,13 +295,13 @@ py_scalar_pod_to_stdvector(std::vector& vals, const py::object& obj) { vals.clear(); if (py::isinstance(obj)) { - vals.emplace_back(*(obj.template cast())); + vals.emplace_back(py::cast(obj)); return true; } else if (py::isinstance(obj)) { - vals.emplace_back(*(obj.template cast())); + vals.emplace_back(TypeDesc(py::cast(obj))); return true; } else if (py::isinstance(obj)) { - vals.emplace_back(TypeDesc(std::string(obj.template cast()))); + vals.emplace_back(TypeDesc(oiio_py::str_to_stdstring(obj))); return true; } else { return false; @@ -298,30 +310,33 @@ py_scalar_pod_to_stdvector(std::vector& vals, const py::object& obj) +// Shared: read `count` contiguous elements of `format` into vector. +// Matches the element-dispatch logic from the original pybind11 implementation. template inline bool -py_buffer_to_stdvector(std::vector& vals, const py::buffer& obj) +buffer_format_to_stdvector(std::vector& vals, TypeDesc format, + const void* data, size_t count) { - OIIO_DASSERT(py::isinstance(obj)); - oiio_bufinfo binfo(obj.request()); bool ok = true; - vals.reserve(binfo.size); - for (size_t i = 0; i < binfo.size; ++i) { + vals.reserve(count); + const unsigned char* bytes = static_cast(data); + for (size_t i = 0; i < count; ++i) { if (std::is_same::value - && binfo.format.basetype == TypeDesc::FLOAT) { - vals.push_back(binfo.dataval(i)); + && format.basetype == TypeDesc::FLOAT) { + vals.push_back(reinterpret_cast(bytes)[i]); } else if ((std::is_same::value || std::is_same::value) - && binfo.format.basetype == TypeDesc::INT) { - vals.push_back(T(binfo.dataval(i))); + && format.basetype == TypeDesc::INT) { + vals.push_back(T(reinterpret_cast(bytes)[i])); } else if (std::is_same::value - && binfo.format.basetype == TypeDesc::UINT) { - vals.push_back(T(binfo.dataval(i))); + && format.basetype == TypeDesc::UINT) { + vals.push_back(T(reinterpret_cast(bytes)[i])); } else if (std::is_same::value - && binfo.format.basetype == TypeDesc::UINT8) { - vals.push_back(T(binfo.dataval(i))); + && format.basetype == TypeDesc::UINT8) { + vals.push_back(T(reinterpret_cast(bytes)[i])); } else if (std::is_same::value - && binfo.format.basetype == TypeDesc::UINT16) { - vals.push_back(T(binfo.dataval(i))); + && format.basetype == TypeDesc::UINT16) { + vals.push_back( + T(reinterpret_cast(bytes)[i])); } else { // FIXME? Other cases? vals.push_back(T(42)); @@ -332,6 +347,67 @@ py_buffer_to_stdvector(std::vector& vals, const py::buffer& obj) } + +#if defined(OIIO_PY_BACKEND_NANOBIND) +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; +} + + + +template<> +inline bool +py_buffer_to_stdvector(std::vector&, const py::object&) +{ + return false; +} + + + +template<> +inline bool +py_buffer_to_stdvector(std::vector&, const py::object&) +{ + return false; +} + +#else + +template +inline bool +py_buffer_to_stdvector(std::vector& vals, const py::buffer& obj) +{ + OIIO_DASSERT(py::isinstance(obj)); + oiio_bufinfo binfo(obj.request()); + return buffer_format_to_stdvector(vals, binfo.format, binfo.data, + binfo.size); +} + + // Specialization for reading strings template<> inline bool @@ -351,6 +427,8 @@ py_buffer_to_stdvector(std::vector& /*vals*/, return false; // not supported } +#endif + // Suck up a tuple (or list, or single instance) of presumed T values into a @@ -361,16 +439,22 @@ inline bool py_to_stdvector(std::vector& vals, const py::object& obj) { if (py::isinstance(obj)) { // if it's a Python tuple - return py_indexable_pod_to_stdvector(vals, obj.cast()); + return py_indexable_pod_to_stdvector(vals, py::cast(obj)); } if (py::isinstance(obj)) { // if it's a Python list - return py_indexable_pod_to_stdvector(vals, obj.cast()); + return py_indexable_pod_to_stdvector(vals, py::cast(obj)); } // Apparently a str can masquerade as a buffer object, so make sure to // exclude that from the buffer case. +#if defined(OIIO_PY_BACKEND_NANOBIND) + if (PyObject_CheckBuffer(obj.ptr()) && !PyUnicode_Check(obj.ptr())) { + return py_buffer_to_stdvector(vals, obj); + } +#else if (py::isinstance(obj) && !py::isinstance(obj)) { - return py_buffer_to_stdvector(vals, obj.cast()); + return py_buffer_to_stdvector(vals, py::cast(obj)); } +#endif // handle scalar case or bust return py_scalar_pod_to_stdvector(vals, obj); @@ -382,23 +466,22 @@ template inline py::tuple C_to_tuple(cspan vals) { - size_t size = vals.size(); - py::tuple result(size); - for (size_t i = 0; i < size; ++i) - result[i] = typename PyTypeForCType::type(vals[i]); - return result; + return oiio_py::make_tuple(vals.size(), [&](size_t i) { + if constexpr (std::is_same_v) { + return py::cast(static_cast(vals[i])); + } else { + return py::cast(vals[i]); + } + }); } + template inline py::tuple C_to_tuple(span vals) { - size_t size = vals.size(); - py::tuple result(size); - for (size_t i = 0; i < size; ++i) - result[i] = typename PyTypeForCType::type(vals[i]); - return result; + return C_to_tuple(cspan(vals)); } @@ -406,10 +489,13 @@ template inline py::tuple C_to_tuple(const T* vals, size_t size) { - py::tuple result(size); - for (size_t i = 0; i < size; ++i) - result[i] = typename PyTypeForCType::type(vals[i]); - return result; + return oiio_py::make_tuple(size, [&](size_t i) { + if constexpr (std::is_same_v) { + return py::cast(static_cast(vals[i])); + } else { + return py::cast(vals[i]); + } + }); } @@ -417,11 +503,9 @@ template<> inline py::tuple C_to_tuple(cspan vals) { - size_t size = vals.size(); - py::tuple result(size); - for (size_t i = 0; i < size; ++i) - result[i] = static_cast(vals[i]); - return result; + return oiio_py::make_tuple(vals.size(), [&](size_t i) { + return static_cast(vals[i]); + }); } @@ -430,11 +514,9 @@ template<> inline py::tuple C_to_tuple(cspan vals) { - size_t size = vals.size(); - py::tuple result(size); - for (size_t i = 0; i < size; ++i) - result[i] = py::cast(vals[i]); - return result; + return oiio_py::make_tuple(vals.size(), [&](size_t i) { + return py::cast(vals[i]); + }); } @@ -448,10 +530,15 @@ C_to_val_or_tuple(const T* vals, TypeDesc type, int nvalues = 1) { OIIO_DASSERT(vals && nvalues); size_t n = type.numelements() * type.aggregate * nvalues; - if (n == 1 && !type.arraylen) - return typename PyTypeForCType::type(vals[0]); - else + if (n == 1 && !type.arraylen) { + if constexpr (std::is_same_v) { + return py::cast(static_cast(vals[0])); + } else { + return py::cast(vals[0]); + } + } else { return C_to_tuple(cspan(vals, n)); + } } @@ -464,32 +551,36 @@ attribute_typed(T& myobj, string_view name, TypeDesc type, const POBJ& dataobj) std::vector vals; bool ok = py_to_stdvector(vals, dataobj); ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) + if (ok) { myobj.attribute(name, type, &vals[0]); + } return ok; } if (type.basetype == TypeDesc::UINT) { std::vector vals; bool ok = py_to_stdvector(vals, dataobj); ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) + if (ok) { myobj.attribute(name, type, &vals[0]); + } return ok; } if (type.basetype == TypeDesc::UINT8) { std::vector vals; bool ok = py_to_stdvector(vals, dataobj); ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) + if (ok) { myobj.attribute(name, type, &vals[0]); + } return ok; } if (type.basetype == TypeDesc::FLOAT) { std::vector vals; bool ok = py_to_stdvector(vals, dataobj); ok &= (vals.size() == type.numelements() * type.aggregate); - if (ok) + if (ok) { myobj.attribute(name, type, &vals[0]); + } return ok; } if (type.basetype == TypeDesc::STRING) { @@ -498,8 +589,9 @@ attribute_typed(T& myobj, string_view name, TypeDesc type, const POBJ& dataobj) ok &= (vals.size() == type.numelements() * type.aggregate); if (ok) { std::vector u; - for (auto& val : vals) + for (auto& val : vals) { u.emplace_back(val); + } myobj.attribute(name, type, &u[0]); } return ok; @@ -515,16 +607,18 @@ template inline void attribute_onearg(T& myobj, string_view name, const py::object& obj) { - if (py::isinstance(obj)) - myobj.attribute(name, float(obj.template cast())); - else if (py::isinstance(obj)) - myobj.attribute(name, int(obj.template cast())); - else if (py::isinstance(obj)) - myobj.attribute(name, std::string(obj.template cast())); - else if (py::isinstance(obj)) - myobj.attribute(name, std::string(obj.template cast())); - else + if (py::isinstance(obj)) { + myobj.attribute(name, py::cast(obj)); + } else if (py::isinstance(obj)) { + myobj.attribute(name, py::cast(obj)); + } else if (py::isinstance(obj)) { + myobj.attribute(name, oiio_py::str_to_stdstring(obj)); + } else if (py::isinstance(obj)) { + myobj.attribute(name, + oiio_py::bytes_to_stdstring(py::cast(obj))); + } else { throw py::type_error("attribute() value must be int, float, or str"); + } } @@ -542,17 +636,20 @@ py::object getattribute_typed(const T& obj, const std::string& name, TypeDesc type = TypeUnknown) { - if (type == TypeUnknown) + if (type == TypeUnknown) { return py::none(); + } char* data = OIIO_ALLOCA(char, type.size()); bool ok = obj.getattribute(name, type, data); - if (!ok) + if (!ok) { return py::none(); // None + } return make_pyobject(data, type); } +#ifndef OIIO_PY_BACKEND_NANOBIND // TRANSFERS ownership of the data pointer! // N.B. There is some evidence that this doesn't work properly with // non-float arrays. Maybe a limitation of pybind11? @@ -597,33 +694,43 @@ inline 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) + if (format == TypeDesc::FLOAT) { return make_numpy_array((float*)data, dims, chans, width, height, depth); - if (format == TypeDesc::UINT8) + } + if (format == TypeDesc::UINT8) { return make_numpy_array((unsigned char*)data, dims, chans, width, height, depth); - if (format == TypeDesc::UINT16) + } + if (format == TypeDesc::UINT16) { return make_numpy_array((unsigned short*)data, dims, chans, width, height, depth); - if (format == TypeDesc::INT8) + } + if (format == TypeDesc::INT8) { return make_numpy_array((char*)data, dims, chans, width, height, depth); - if (format == TypeDesc::INT16) + } + if (format == TypeDesc::INT16) { return make_numpy_array((short*)data, dims, chans, width, height, depth); - if (format == TypeDesc::DOUBLE) + } + if (format == TypeDesc::DOUBLE) { return make_numpy_array((double*)data, dims, chans, width, height, depth); - if (format == TypeDesc::HALF) + } + if (format == TypeDesc::HALF) { return make_numpy_array((half*)data, dims, chans, width, height, depth); - if (format == TypeDesc::UINT) + } + if (format == TypeDesc::UINT) { return make_numpy_array((unsigned int*)data, dims, chans, width, height, depth); - if (format == TypeDesc::INT) + } + if (format == TypeDesc::INT) { return make_numpy_array((int*)data, dims, chans, width, height, depth); + } delete[] (char*)data; return py::none(); } +#endif @@ -631,19 +738,19 @@ template inline void delegate_setitem(C& self, const std::string& key, py::object obj) { - if (py::isinstance(obj)) - self[key] = float(obj.template cast()); - else if (py::isinstance(obj)) - self[key] = int(obj.template cast()); - else if (py::isinstance(obj)) - self[key] = std::string(obj.template cast()); - else if (py::isinstance(obj)) - self[key] = std::string(obj.template cast()); - else + if (py::isinstance(obj)) { + self[key] = py::cast(obj); + } else if (py::isinstance(obj)) { + self[key] = py::cast(obj); + } else if (py::isinstance(obj)) { + self[key] = oiio_py::str_to_stdstring(obj); + } else if (py::isinstance(obj)) { + self[key] = oiio_py::bytes_to_stdstring(py::cast(obj)); + } else { throw std::invalid_argument("Bad type for __setitem__"); + } } - } // namespace PyOpenImageIO #endif // PYOPENIMAGEIO_PY_OIIO_H diff --git a/src/python/py_paramvalue.cpp b/src/python/py_paramvalue.cpp index 1c3e750a95..beb7d7b644 100644 --- a/src/python/py_paramvalue.cpp +++ b/src/python/py_paramvalue.cpp @@ -16,7 +16,8 @@ ParamValue_from_pyobject(string_view name, TypeDesc type, int nvalues, if (type.basetype == TypeDesc::UINT8 && type.arraylen && py::isinstance(obj)) { TypeDesc t = type; - std::string s = obj.cast(); + py::bytes b = py::cast(obj); + const std::string s = oiio_py::bytes_to_stdstring(b); if (type.arraylen < 0) t.arraylen = int(s.size()) / nvalues; if (t.arraylen * nvalues == int(s.size())) { @@ -140,10 +141,8 @@ attribute_typed(T& myobj, string_view name, TypeDesc type, int nvalues, void -declare_paramvalue(py::module& m) +declare_paramvalue(py_module& m) { - using namespace pybind11::literals; - py::enum_(m, "Interp") .value("CONSTANT", ParamValue::INTERP_CONSTANT) .value("PERPIECE", ParamValue::INTERP_PERPIECE) @@ -156,24 +155,40 @@ declare_paramvalue(py::module& m) .value("INTERP_VERTEX", ParamValue::INTERP_VERTEX); py::class_(m, "ParamValue") - .def_property_readonly("name", - [](const ParamValue& self) { - return PY_STR(self.name().string()); - }) - .def_property_readonly("type", - [](const ParamValue& self) { - return self.type(); - }) - .def_property_readonly("value", - [](const ParamValue& self) { - return make_pyobject(self.data(), - self.type(), - self.nvalues()); - }) - .def_property_readonly("__len__", &ParamValue::nvalues) + OIIO_PY_DEF_PROP_RO("name", + [](const ParamValue& self) { + return oiio_py::str(self.name().string()); + }) + OIIO_PY_DEF_PROP_RO("type", + [](const ParamValue& self) { + return self.type(); + }) + OIIO_PY_DEF_PROP_RO("value", + [](const ParamValue& self) { + return make_pyobject(self.data(), + self.type(), + self.nvalues()); + }) + OIIO_PY_DEF_PROP_RO("__len__", &ParamValue::nvalues) .def(py::init()) .def(py::init()) .def(py::init()) +#if defined(OIIO_PY_BACKEND_NANOBIND) + .def("__init__", + [](ParamValue* self, const std::string& name, TypeDesc type, + const py::object& obj) { + new (self) ParamValue(ParamValue_from_pyobject( + name, type, 1, ParamValue::INTERP_CONSTANT, obj)); + }, + "name"_a, "type"_a, "value"_a) + .def("__init__", + [](ParamValue* self, const std::string& name, TypeDesc type, + int nvalues, ParamValue::Interp interp, const py::object& obj) { + new (self) ParamValue(ParamValue_from_pyobject( + name, type, nvalues, interp, obj)); + }, + "name"_a, "type"_a, "nvalues"_a, "interp"_a, "value"_a); +#else .def(py::init([](const std::string& name, TypeDesc type, const py::object& obj) { return ParamValue_from_pyobject(name, type, 1, @@ -187,6 +202,7 @@ declare_paramvalue(py::module& m) obj); }), "name"_a, "type"_a, "nvalues"_a, "interp"_a, "value"_a); +#endif py::class_(m, "ParamValueList") .def(py::init<>()) @@ -197,17 +213,17 @@ declare_paramvalue(py::module& m) throw py::index_error(); return self[i]; }, - py::return_value_policy::reference_internal) + oiio_py::ref_internal) // __getitem__ is the dict-like `pvl[key]` lookup .def( "__getitem__", [](const ParamValueList& self, const std::string& key) { auto p = self.find(key); if (p == self.end()) - throw py::key_error("key '" + key + "' does not exist"); + oiio_py::throw_key_error("key '" + key + "' does not exist"); return make_pyobject(p->data(), p->type()); }, - py::return_value_policy::reference_internal) + oiio_py::ref_internal) // __setitem__ is the dict-like `pvl[key] = value` assignment .def("__setitem__", [](ParamValueList& self, const std::string& key, py::object val) { @@ -225,7 +241,7 @@ declare_paramvalue(py::module& m) .def( "__iter__", [](const ParamValueList& self) { - return py::make_iterator(self.begin(), self.end()); + return oiio_py::make_iterator(self); }, py::keep_alive<0, 1>()) .def("append", [](ParamValueList& p, diff --git a/src/python/py_roi.cpp b/src/python/py_roi.cpp index bc06d67926..a32f35ca14 100644 --- a/src/python/py_roi.cpp +++ b/src/python/py_roi.cpp @@ -7,9 +7,6 @@ namespace PyOpenImageIO { -static ROI ROI_All; - - static bool roi_contains_coord(const ROI& roi, int x, int y, int z, int ch) { @@ -27,19 +24,17 @@ roi_contains_roi(const ROI& roi, const ROI& other) // Declare the OIIO ROI class to Python void -declare_roi(py::module& m) +declare_roi(py_module& m) { - using namespace py; - py::class_(m, "ROI") - .def_readwrite("xbegin", &ROI::xbegin) - .def_readwrite("xend", &ROI::xend) - .def_readwrite("ybegin", &ROI::ybegin) - .def_readwrite("yend", &ROI::yend) - .def_readwrite("zbegin", &ROI::zbegin) - .def_readwrite("zend", &ROI::zend) - .def_readwrite("chbegin", &ROI::chbegin) - .def_readwrite("chend", &ROI::chend) + OIIO_PY_DEF_RW("xbegin", &ROI::xbegin) + OIIO_PY_DEF_RW("xend", &ROI::xend) + OIIO_PY_DEF_RW("ybegin", &ROI::ybegin) + OIIO_PY_DEF_RW("yend", &ROI::yend) + OIIO_PY_DEF_RW("zbegin", &ROI::zbegin) + OIIO_PY_DEF_RW("zend", &ROI::zend) + OIIO_PY_DEF_RW("chbegin", &ROI::chbegin) + OIIO_PY_DEF_RW("chend", &ROI::chend) .def(py::init<>()) .def(py::init()) @@ -48,22 +43,23 @@ declare_roi(py::module& m) .def(py::init()) // .def("defined", [](const ROI& roi) { return (int)roi.defined(); }) - .def_property_readonly("defined", &ROI::defined) - .def_property_readonly("width", &ROI::width) - .def_property_readonly("height", &ROI::height) - .def_property_readonly("depth", &ROI::depth) - .def_property_readonly("nchannels", &ROI::nchannels) - .def_property_readonly("npixels", &ROI::npixels) + OIIO_PY_DEF_PROP_RO("defined", &ROI::defined) + OIIO_PY_DEF_PROP_RO("width", &ROI::width) + OIIO_PY_DEF_PROP_RO("height", &ROI::height) + OIIO_PY_DEF_PROP_RO("depth", &ROI::depth) + OIIO_PY_DEF_PROP_RO("nchannels", &ROI::nchannels) + OIIO_PY_DEF_PROP_RO("npixels", &ROI::npixels) .def("contains", &roi_contains_coord, "x"_a, "y"_a, "z"_a = 0, "ch"_a = 0) .def("contains", &roi_contains_roi, "other"_a) - .def_readonly_static("All", &ROI_All) + OIIO_PY_DEF_READONLY_STATIC_LAMBDA( + "All", [](const py::object&) { return ROI::All(); }) // Conversion to string .def("__str__", [](const ROI& roi) { - return PY_STR(Strutil::fmt::format("{}", roi)); + return oiio_py::str(Strutil::fmt::format("{}", roi)); }) // Copy diff --git a/src/python/py_typedesc.cpp b/src/python/py_typedesc.cpp index 1d286c5f30..cd9818d6c2 100644 --- a/src/python/py_typedesc.cpp +++ b/src/python/py_typedesc.cpp @@ -10,7 +10,7 @@ namespace PyOpenImageIO { // Declare the OIIO TypeDesc type to Python void -declare_typedesc(py::module& m) +declare_typedesc(py_module& m) { py::enum_(m, "BASETYPE") .value("UNKNOWN", TypeDesc::UNKNOWN) @@ -67,21 +67,21 @@ declare_typedesc(py::module& m) // char, def_readwrite() doesn't do the right thing. Instead, we // use set_foo/get_foo wrappers, but from Python it looks like // regular member access. - .def_property( + OIIO_PY_DEF_PROP_RW( "basetype", [](TypeDesc t) { return TypeDesc::BASETYPE(t.basetype); }, [](TypeDesc& t, TypeDesc::BASETYPE b) { return t.basetype = b; }) - .def_property( + OIIO_PY_DEF_PROP_RW( "aggregate", [](TypeDesc t) { return TypeDesc::AGGREGATE(t.aggregate); }, [](TypeDesc& t, TypeDesc::AGGREGATE b) { return t.aggregate = b; }) - .def_property( + OIIO_PY_DEF_PROP_RW( "vecsemantics", [](TypeDesc t) { return TypeDesc::VECSEMANTICS(t.vecsemantics); }, [](TypeDesc& t, TypeDesc::VECSEMANTICS b) { return t.vecsemantics = b; }) - .def_readwrite("arraylen", &TypeDesc::arraylen) + OIIO_PY_DEF_RW("arraylen", &TypeDesc::arraylen) // Constructors: () [defined implicitly], (base), (base, agg), // (base,agg,vecsem), (base,agg,vecsem,arraylen), string. .def(py::init<>()) @@ -101,7 +101,7 @@ declare_typedesc(py::module& m) // .def(init()) // .def(init()) // FIXME -- I bet this works with Pybind11 - .def("c_str", [](const TypeDesc& self) { return PY_STR(self.c_str()); }) + .def("c_str", [](const TypeDesc& self) { return oiio_py::str(self.c_str()); }) .def("numelements", &TypeDesc::numelements) .def("basevalues", &TypeDesc::basevalues) .def("size", &TypeDesc::size) @@ -114,11 +114,31 @@ declare_typedesc(py::module& m) }) .def("equivalent", &TypeDesc::equivalent) .def("unarray", &TypeDesc::unarray) - .def("is_vec2", &TypeDesc::is_vec2) - .def("is_vec3", &TypeDesc::is_vec3) - .def("is_vec4", &TypeDesc::is_vec4) - .def("is_box2", &TypeDesc::is_box2) - .def("is_box3", &TypeDesc::is_box3) + .def("is_vec2", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_vec2(b); + }, + "b"_a = TypeDesc::FLOAT) + .def("is_vec3", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_vec3(b); + }, + "b"_a = TypeDesc::FLOAT) + .def("is_vec4", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_vec4(b); + }, + "b"_a = TypeDesc::FLOAT) + .def("is_box2", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_box2(b); + }, + "b"_a = TypeDesc::FLOAT) + .def("is_box3", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_box3(b); + }, + "b"_a = TypeDesc::FLOAT) .def_static("all_types_equal", [](const std::vector& types) { return TypeDesc::all_types_equal(types); @@ -129,9 +149,9 @@ declare_typedesc(py::module& m) .def(py::self != py::self) // operator!= //NOSONAR // Conversion to string - .def("__str__", [](TypeDesc t) { return PY_STR(t.c_str()); }) + .def("__str__", [](TypeDesc t) { return oiio_py::str(t.c_str()); }) .def("__repr__", [](TypeDesc t) { - return PY_STR(""); + return oiio_py::str(""); }); // Declare that a BASETYPE is implicitly convertible to a TypeDesc. @@ -144,6 +164,54 @@ declare_typedesc(py::module& m) // foo(TypeUInt8). py::implicitly_convertible(); +#if defined(OIIO_PY_BACKEND_NANOBIND) + // Pybind11's .export_values() above copies enum members onto the module + // (oiio.FLOAT, oiio.VEC3, ...). Nanobind has no equivalent here, so set + // them explicitly to keep the same public API. + m.attr("UNKNOWN") = py::cast(TypeDesc::UNKNOWN); + m.attr("NONE") = py::cast(TypeDesc::NONE); + m.attr("UCHAR") = py::cast(TypeDesc::UCHAR); + m.attr("UINT8") = py::cast(TypeDesc::UINT8); + m.attr("CHAR") = py::cast(TypeDesc::CHAR); + m.attr("INT8") = py::cast(TypeDesc::INT8); + m.attr("UINT16") = py::cast(TypeDesc::UINT16); + m.attr("USHORT") = py::cast(TypeDesc::USHORT); + m.attr("SHORT") = py::cast(TypeDesc::SHORT); + m.attr("INT16") = py::cast(TypeDesc::INT16); + m.attr("UINT") = py::cast(TypeDesc::UINT); + m.attr("UINT32") = py::cast(TypeDesc::UINT32); + m.attr("INT") = py::cast(TypeDesc::INT); + m.attr("INT32") = py::cast(TypeDesc::INT32); + m.attr("ULONGLONG") = py::cast(TypeDesc::ULONGLONG); + m.attr("UINT64") = py::cast(TypeDesc::UINT64); + m.attr("LONGLONG") = py::cast(TypeDesc::LONGLONG); + m.attr("INT64") = py::cast(TypeDesc::INT64); + m.attr("HALF") = py::cast(TypeDesc::HALF); + m.attr("FLOAT") = py::cast(TypeDesc::FLOAT); + m.attr("DOUBLE") = py::cast(TypeDesc::DOUBLE); + m.attr("STRING") = py::cast(TypeDesc::STRING); + m.attr("PTR") = py::cast(TypeDesc::PTR); + m.attr("LASTBASE") = py::cast(TypeDesc::LASTBASE); + + m.attr("SCALAR") = py::cast(TypeDesc::SCALAR); + m.attr("VEC2") = py::cast(TypeDesc::VEC2); + m.attr("VEC3") = py::cast(TypeDesc::VEC3); + m.attr("VEC4") = py::cast(TypeDesc::VEC4); + m.attr("MATRIX33") = py::cast(TypeDesc::MATRIX33); + m.attr("MATRIX44") = py::cast(TypeDesc::MATRIX44); + + m.attr("NOXFORM") = py::cast(TypeDesc::NOXFORM); + m.attr("NOSEMANTICS") = py::cast(TypeDesc::NOSEMANTICS); + m.attr("COLOR") = py::cast(TypeDesc::COLOR); + m.attr("POINT") = py::cast(TypeDesc::POINT); + m.attr("VECTOR") = py::cast(TypeDesc::VECTOR); + m.attr("NORMAL") = py::cast(TypeDesc::NORMAL); + m.attr("TIMECODE") = py::cast(TypeDesc::TIMECODE); + m.attr("KEYCODE") = py::cast(TypeDesc::KEYCODE); + m.attr("RATIONAL") = py::cast(TypeDesc::RATIONAL); + m.attr("BOX") = py::cast(TypeDesc::BOX); +#endif + // Global constants of common TypeDescs m.attr("TypeUnknown") = TypeUnknown; m.attr("TypeFloat") = TypeFloat; diff --git a/testsuite/python-imagespec/ref/out.txt b/testsuite/python-imagespec/ref/out.txt index 78f4d62ede..76906e7825 100644 --- a/testsuite/python-imagespec/ref/out.txt +++ b/testsuite/python-imagespec/ref/out.txt @@ -93,6 +93,13 @@ getattribute('foo_no') retrieves None getattribute('smpte:TimeCode') retrieves (18356486, 4294967295) getattribute('ucarr') retrieves [49 50 51 0 0 97 98 99 1 88] getattribute('unknown') retrieves None + +Conversion helper roundtrips (py_to_stdvector, C_to_tuple): +Passed: C_to_tuple vector getattribute +Passed: py_buffer_to_stdvector float numpy buffer +Passed: buffer_format int buffer to float[2] +Passed: py_indexable list to float vector +Passed: py_indexable tuple to float vector s.get('foo_int') = 14 s.get('ucarr') retrieves [49 50 51 0 0 97 98 99 1 88] s['ucarr'] retrieves [49 50 51 0 0 97 98 99 1 88] diff --git a/testsuite/python-imagespec/src/test_imagespec.py b/testsuite/python-imagespec/src/test_imagespec.py index b78cb3b008..67121737e3 100755 --- a/testsuite/python-imagespec/src/test_imagespec.py +++ b/testsuite/python-imagespec/src/test_imagespec.py @@ -128,6 +128,42 @@ def print_imagespec (spec: oiio.ImageSpec, msg="") : print ("getattribute('smpte:TimeCode') retrieves", s.getattribute("smpte:TimeCode")) print ("getattribute('ucarr') retrieves", s.getattribute("ucarr")) print ("getattribute('unknown') retrieves", s.getattribute("unknown")) + print () + print ("Conversion helper roundtrips (py_to_stdvector, C_to_tuple):") + s_conv = oiio.ImageSpec() + s_conv.attribute("foo_vector", oiio.TypeVector, (1.0, 0.0, 11.0)) + vec = s_conv.getattribute("foo_vector") + if tuple(vec) == (1.0, 0.0, 11.0): + print ("Passed: C_to_tuple vector getattribute") + else: + print ("Failed: C_to_tuple vector getattribute expected (1.0, 0.0, 11.0) got", vec) + s_conv.attribute("float_buf_probe", oiio.TypeDesc("float[4]"), + numpy.array([1.0, 2.0, 3.0, 4.0], dtype="f")) + fb = s_conv.getattribute("float_buf_probe") + if fb is not None and tuple(fb) == (1.0, 2.0, 3.0, 4.0): + print ("Passed: py_buffer_to_stdvector float numpy buffer") + else: + print ("Failed: py_buffer_to_stdvector float numpy buffer got", fb) + s_conv.attribute("float_from_int_probe", oiio.TypeDesc("float[2]"), + numpy.array([3, 4], dtype="i")) + fi = s_conv.getattribute("float_from_int_probe") + if fi is not None and tuple(fi) == (3.0, 4.0): + print ("Passed: buffer_format int buffer to float[2]") + else: + print ("Failed: buffer_format int buffer to float[2] got", fi) + s_conv.attribute("list_float_probe", oiio.TypeDesc("float[4]"), + [1.0, 2.0, 3.0, 4.0]) + lf = s_conv.getattribute("list_float_probe") + if lf is not None and tuple(lf) == (1.0, 2.0, 3.0, 4.0): + print ("Passed: py_indexable list to float vector") + else: + print ("Failed: py_indexable list to float vector got", lf) + s_conv.attribute("tuple_float_probe", oiio.TypeDesc("float[2]"), (5.0, 6.0)) + tf = s_conv.getattribute("tuple_float_probe") + if tf is not None and tuple(tf) == (5.0, 6.0): + print ("Passed: py_indexable tuple to float vector") + else: + print ("Failed: py_indexable tuple to float vector got", tf) print ("s.get('foo_int') =", s.get('foo_int')) print ("s.get('ucarr') retrieves", s.get("ucarr")) try : diff --git a/testsuite/python-paramlist/ref/out.txt b/testsuite/python-paramlist/ref/out.txt index 7b564d74b2..b28d4ffb6d 100644 --- a/testsuite/python-paramlist/ref/out.txt +++ b/testsuite/python-paramlist/ref/out.txt @@ -11,6 +11,9 @@ Testing individual ParamValue: item u8unsized uint8[10] [49 50 51 52 53 54 55 56 57 48] item u8fix_n2 uint8[2] [ 97 98 99 100 101 102] item u8var_n2 uint8[4] [49 50 51 52 53 54 55 56] +ParamValue conversion helper checks: +Passed: ParamValue tuple to float[4] +Passed: ParamValue numpy float buffer to color Testing ParamValueList: pl length is 9 diff --git a/testsuite/python-paramlist/src/test_paramlist.py b/testsuite/python-paramlist/src/test_paramlist.py index ceb0b9c0ba..5bb1d17d67 100755 --- a/testsuite/python-paramlist/src/test_paramlist.py +++ b/testsuite/python-paramlist/src/test_paramlist.py @@ -35,8 +35,8 @@ def print_param_list(pl: oiio.ParamValueList) : pv = oiio.ParamValue("c", "xyzpdq") print_param_value(pv) # Construct from tuple - pv = oiio.ParamValue("d", "float[4]", (3.5, 4.5, 5.5, 6.5)) - print_param_value(pv) + pv_tuple = oiio.ParamValue("d", "float[4]", (3.5, 4.5, 5.5, 6.5)) + print_param_value(pv_tuple) # Construct from tuple with nvalues/interp pv = oiio.ParamValue("e", "float", 4, oiio.Interp.LINEAR, (1, 3, 5, 7)) print_param_value(pv) @@ -44,9 +44,9 @@ def print_param_list(pl: oiio.ParamValueList) : pv = oiio.ParamValue("f", "point", [3.5, 4.5, 5.5, 6.5]) print_param_value(pv) # Construct from a numpy array - pv = oiio.ParamValue("g", "color", - numpy.array([0.25, 0.5, 0.75], dtype='f')) - print_param_value(pv) + pv_np = oiio.ParamValue("g", "color", + numpy.array([0.25, 0.5, 0.75], dtype='f')) + print_param_value(pv_np) # Construct from numpy byte array pv = oiio.ParamValue("ucarr", "uint8[10]", numpy.array([49, 50, 51, 0, 0, 97, 98, 99, 1, 88], dtype='B')) print_param_value(pv) @@ -87,6 +87,16 @@ def print_param_list(pl: oiio.ParamValueList) : # Check: type uint8[4] with 2 nvalues; exercises inference with nvalues>1. print_param_value(pv) + print ("ParamValue conversion helper checks:") + if pv_tuple.value == (3.5, 4.5, 5.5, 6.5): + print ("Passed: ParamValue tuple to float[4]") + else: + print ("Failed: ParamValue tuple to float[4] got", pv_tuple.value) + if pv_np.value is not None and tuple(pv_np.value) == (0.25, 0.5, 0.75): + print ("Passed: ParamValue numpy float buffer to color") + else: + print ("Failed: ParamValue numpy float buffer to color got", pv_np.value) + print ("") print ("Testing ParamValueList:") diff --git a/testsuite/python-typedesc/ref/out.txt b/testsuite/python-typedesc/ref/out.txt index d0ca95d7fb..9313bc8cda 100644 --- a/testsuite/python-typedesc/ref/out.txt +++ b/testsuite/python-typedesc/ref/out.txt @@ -177,6 +177,11 @@ type 'fromstring('point')' after unarray('float[2]') = float vector is_vec2,is_vec3,is_vec4 = False True False box2i is_box2,is_box3 = True False +vector is_vec2(),is_vec3(),is_vec4() defaults = False True False +vector defaults match explicit FLOAT = True True True +box2 is_box2(),is_box3() defaults = True False +box2 defaults match explicit FLOAT = True True +box2i is_box2() default vs INT = False True all_types_equal([uint8,uint8]) = True all_types_equal([uint8,uint16]) = False repr(TypeFloat) = diff --git a/testsuite/python-typedesc/src/test_typedesc.py b/testsuite/python-typedesc/src/test_typedesc.py index b0d1d7db8e..2fc70e96f9 100755 --- a/testsuite/python-typedesc/src/test_typedesc.py +++ b/testsuite/python-typedesc/src/test_typedesc.py @@ -164,6 +164,22 @@ def breakdown_test(t, name="", verbose=True): print ("box2i is_box2,is_box3 =", oiio.TypeDesc("box2i").is_box2(oiio.INT), oiio.TypeDesc("box2i").is_box3(oiio.INT)) + vector = oiio.TypeDesc("vector") + box2 = oiio.TypeDesc("box2") + box2i = oiio.TypeDesc("box2i") + print ("vector is_vec2(),is_vec3(),is_vec4() defaults =", + vector.is_vec2(), vector.is_vec3(), vector.is_vec4()) + print ("vector defaults match explicit FLOAT =", + vector.is_vec2() == vector.is_vec2(oiio.FLOAT), + vector.is_vec3() == vector.is_vec3(oiio.FLOAT), + vector.is_vec4() == vector.is_vec4(oiio.FLOAT)) + print ("box2 is_box2(),is_box3() defaults =", + box2.is_box2(), box2.is_box3()) + print ("box2 defaults match explicit FLOAT =", + box2.is_box2() == box2.is_box2(oiio.FLOAT), + box2.is_box3() == box2.is_box3(oiio.FLOAT)) + print ("box2i is_box2() default vs INT =", + box2i.is_box2(), box2i.is_box2(oiio.INT)) print ("all_types_equal([uint8,uint8]) =", oiio.TypeDesc.all_types_equal([oiio.TypeDesc("uint8"), oiio.TypeDesc("uint8")])) From a78db1c74d37312a51708768c770bbd970335d10 Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 11:06:11 +1000 Subject: [PATCH 2/2] refactor(python): Update Python binding macros for consistency Refactored Python binding macros in the py_backend.h and related files to use a more consistent syntax with dot notation. This change enhances readability and maintains uniformity across the codebase. Adjusted the ImageSpec, ParamValue, ROI, and TypeDesc classes to utilize the new macro definitions. Minor formatting improvements were also made for better code clarity. Signed-off-by: Aleksandr Motsjonov --- src/python/py_backend.h | 27 +++++++------- src/python/py_imagespec.cpp | 57 +++++++++++++++--------------- src/python/py_oiio.cpp | 34 +++++++++--------- src/python/py_oiio.h | 10 +++--- src/python/py_paramvalue.cpp | 67 ++++++++++++++++++----------------- src/python/py_roi.cpp | 32 ++++++++--------- src/python/py_typedesc.cpp | 68 ++++++++++++++++++++---------------- 7 files changed, 152 insertions(+), 143 deletions(-) diff --git a/src/python/py_backend.h b/src/python/py_backend.h index fa2d633ef1..81072aee08 100644 --- a/src/python/py_backend.h +++ b/src/python/py_backend.h @@ -13,15 +13,14 @@ # include # include -namespace py = nanobind; +namespace py = nanobind; using py_module = nanobind::module_; using namespace py::literals; -# define OIIO_PY_DEF_RW(name, member) .def_rw(name, member) -# define OIIO_PY_DEF_PROP_RO(name, member) .def_prop_ro(name, member) -# define OIIO_PY_DEF_PROP_RW(name, get, set) .def_prop_rw(name, get, set) -# define OIIO_PY_DEF_READONLY_STATIC_LAMBDA(name, lambda) \ - .def_prop_ro_static(name, lambda) +# define OIIO_PY_RW def_rw +# define OIIO_PY_PROP_RO def_prop_ro +# define OIIO_PY_PROP_RW def_prop_rw +# define OIIO_PY_RO_STATIC def_prop_ro_static namespace oiio_py { @@ -92,21 +91,20 @@ make_numpy_array(T* data, size_t size) } // namespace oiio_py -#else // pybind11 +#else // pybind11 # include # include # include -namespace py = pybind11; +namespace py = pybind11; using py_module = pybind11::module; using namespace py::literals; -# define OIIO_PY_DEF_RW(name, member) .def_readwrite(name, member) -# define OIIO_PY_DEF_PROP_RO(name, member) .def_property_readonly(name, member) -# define OIIO_PY_DEF_PROP_RW(name, get, set) .def_property(name, get, set) -# define OIIO_PY_DEF_READONLY_STATIC_LAMBDA(name, lambda) \ - .def_property_readonly_static(name, lambda) +# define OIIO_PY_RW def_readwrite +# define OIIO_PY_PROP_RO def_property_readonly +# define OIIO_PY_PROP_RW def_property +# define OIIO_PY_RO_STATIC def_property_readonly_static namespace oiio_py { @@ -127,8 +125,7 @@ make_tuple(size_t size, F&& fill) return result; } -inline constexpr auto ref_internal = - py::return_value_policy::reference_internal; +inline constexpr auto ref_internal = py::return_value_policy::reference_internal; template inline auto diff --git a/src/python/py_imagespec.cpp b/src/python/py_imagespec.cpp index e581c2a684..ea64538f4d 100644 --- a/src/python/py_imagespec.cpp +++ b/src/python/py_imagespec.cpp @@ -59,39 +59,39 @@ void declare_imagespec(py_module& m) { py::class_(m, "ImageSpec") - OIIO_PY_DEF_RW("x", &ImageSpec::x) - OIIO_PY_DEF_RW("y", &ImageSpec::y) - OIIO_PY_DEF_RW("z", &ImageSpec::z) - OIIO_PY_DEF_RW("width", &ImageSpec::width) - OIIO_PY_DEF_RW("height", &ImageSpec::height) - OIIO_PY_DEF_RW("depth", &ImageSpec::depth) - OIIO_PY_DEF_RW("full_x", &ImageSpec::full_x) - OIIO_PY_DEF_RW("full_y", &ImageSpec::full_y) - OIIO_PY_DEF_RW("full_z", &ImageSpec::full_z) - OIIO_PY_DEF_RW("full_width", &ImageSpec::full_width) - OIIO_PY_DEF_RW("full_height", &ImageSpec::full_height) - OIIO_PY_DEF_RW("full_depth", &ImageSpec::full_depth) - OIIO_PY_DEF_RW("tile_width", &ImageSpec::tile_width) - OIIO_PY_DEF_RW("tile_height", &ImageSpec::tile_height) - OIIO_PY_DEF_RW("tile_depth", &ImageSpec::tile_depth) - OIIO_PY_DEF_RW("nchannels", &ImageSpec::nchannels) - OIIO_PY_DEF_RW("format", &ImageSpec::format) - OIIO_PY_DEF_PROP_RW( + .OIIO_PY_RW("x", &ImageSpec::x) + .OIIO_PY_RW("y", &ImageSpec::y) + .OIIO_PY_RW("z", &ImageSpec::z) + .OIIO_PY_RW("width", &ImageSpec::width) + .OIIO_PY_RW("height", &ImageSpec::height) + .OIIO_PY_RW("depth", &ImageSpec::depth) + .OIIO_PY_RW("full_x", &ImageSpec::full_x) + .OIIO_PY_RW("full_y", &ImageSpec::full_y) + .OIIO_PY_RW("full_z", &ImageSpec::full_z) + .OIIO_PY_RW("full_width", &ImageSpec::full_width) + .OIIO_PY_RW("full_height", &ImageSpec::full_height) + .OIIO_PY_RW("full_depth", &ImageSpec::full_depth) + .OIIO_PY_RW("tile_width", &ImageSpec::tile_width) + .OIIO_PY_RW("tile_height", &ImageSpec::tile_height) + .OIIO_PY_RW("tile_depth", &ImageSpec::tile_depth) + .OIIO_PY_RW("nchannels", &ImageSpec::nchannels) + .OIIO_PY_RW("format", &ImageSpec::format) + .OIIO_PY_PROP_RW( "channelformats", [](const ImageSpec& spec) { return ImageSpec_get_channelformats(spec); }, &ImageSpec_set_channelformats) - OIIO_PY_DEF_PROP_RW("channelnames", &ImageSpec_get_channelnames, - &ImageSpec_set_channelnames) - OIIO_PY_DEF_RW("alpha_channel", &ImageSpec::alpha_channel) - OIIO_PY_DEF_RW("z_channel", &ImageSpec::z_channel) - OIIO_PY_DEF_RW("deep", &ImageSpec::deep) - OIIO_PY_DEF_RW("extra_attribs", &ImageSpec::extra_attribs) + .OIIO_PY_PROP_RW("channelnames", &ImageSpec_get_channelnames, + &ImageSpec_set_channelnames) + .OIIO_PY_RW("alpha_channel", &ImageSpec::alpha_channel) + .OIIO_PY_RW("z_channel", &ImageSpec::z_channel) + .OIIO_PY_RW("deep", &ImageSpec::deep) + .OIIO_PY_RW("extra_attribs", &ImageSpec::extra_attribs) - OIIO_PY_DEF_PROP_RW("roi", &ImageSpec::roi, &ImageSpec::set_roi) - OIIO_PY_DEF_PROP_RW("roi_full", &ImageSpec::roi_full, - &ImageSpec::set_roi_full) + .OIIO_PY_PROP_RW("roi", &ImageSpec::roi, &ImageSpec::set_roi) + .OIIO_PY_PROP_RW("roi_full", &ImageSpec::roi_full, + &ImageSpec::set_roi_full) .def(py::init<>()) .def(py::init()) @@ -285,7 +285,8 @@ declare_imagespec(py_module& m) ParamValue tmpparam; auto p = self.find_attribute(key, tmpparam); if (p == nullptr) - oiio_py::throw_key_error("key '" + key + "' does not exist"); + oiio_py::throw_key_error("key '" + key + + "' does not exist"); return make_pyobject(p->data(), p->type()); }) // __setitem__ is the dict-like `ImageSpec[key] = value` assignment diff --git a/src/python/py_oiio.cpp b/src/python/py_oiio.cpp index 93ed0a273d..464162a5aa 100644 --- a/src/python/py_oiio.cpp +++ b/src/python/py_oiio.cpp @@ -7,8 +7,8 @@ #include #if defined(OIIO_PY_BACKEND_NANOBIND) -# include # include +# include #endif namespace PyOpenImageIO { @@ -374,16 +374,18 @@ declare_pybind_global_functions(py_module& m) set_colorspace(spec, name); }, "spec"_a, "name"_a); - m.def("set_colorspace_rec709_gamma", - [](ImageSpec& spec, float gamma) { - set_colorspace_rec709_gamma(spec, gamma); - }, - "spec"_a, "gamma"_a); - m.def("equivalent_colorspace", - [](const std::string& a, const std::string& b) { - return equivalent_colorspace(a, b); - }, - "a"_a, "b"_a); + m.def( + "set_colorspace_rec709_gamma", + [](ImageSpec& spec, float gamma) { + set_colorspace_rec709_gamma(spec, gamma); + }, + "spec"_a, "gamma"_a); + m.def( + "equivalent_colorspace", + [](const std::string& a, const std::string& b) { + return equivalent_colorspace(a, b); + }, + "a"_a, "b"_a); m.def( "is_imageio_format_name", [](const std::string& name) { @@ -420,13 +422,13 @@ declare_module_attributes(py_module& m) } // namespace PyOpenImageIO -#define OIIO_DECLARE_NB_MODULE(x) NB_MODULE(x, m) +# define OIIO_DECLARE_NB_MODULE(x) NB_MODULE(x, m) -#if defined(OIIO_PY_NANOBIND_ISOLATED_PACKAGE) +# if defined(OIIO_PY_NANOBIND_ISOLATED_PACKAGE) OIIO_DECLARE_NB_MODULE(_OpenImageIO) -#else +# else OIIO_DECLARE_NB_MODULE(OpenImageIO) -#endif +# endif { m.doc() = "OpenImageIO nanobind bindings."; @@ -441,7 +443,7 @@ OIIO_DECLARE_NB_MODULE(OpenImageIO) // MODULE name as a #define. Google for Argument-Prescan for additional // info on why this is necessary -#define OIIO_DECLARE_PYMODULE(x) PYBIND11_MODULE(x, m) +# define OIIO_DECLARE_PYMODULE(x) PYBIND11_MODULE(x, m) OIIO_DECLARE_PYMODULE(PYMODULE_NAME) { diff --git a/src/python/py_oiio.h b/src/python/py_oiio.h index a9a42c78f4..36e61dc422 100644 --- a/src/python/py_oiio.h +++ b/src/python/py_oiio.h @@ -243,8 +243,7 @@ py_indexable_pod_to_stdvector(std::vector& vals, const PYT& obj) } else if (py::isinstance(elem)) { vals.emplace_back(TypeDesc(py::cast(elem))); } else if (py::isinstance(elem)) { - vals.emplace_back( - TypeDesc(oiio_py::str_to_stdstring(elem))); + vals.emplace_back(TypeDesc(oiio_py::str_to_stdstring(elem))); } else { // FIXME? Other cases? vals.emplace_back(TypeUnknown); @@ -372,7 +371,7 @@ py_buffer_to_stdvector(std::vector& vals, const py::object& obj) } const size_t count = static_cast(view.len) / view.itemsize; - ok = buffer_format_to_stdvector(vals, format, view.buf, count); + ok = buffer_format_to_stdvector(vals, format, view.buf, count); PyBuffer_Release(&view); return ok; } @@ -514,9 +513,8 @@ template<> inline py::tuple C_to_tuple(cspan vals) { - return oiio_py::make_tuple(vals.size(), [&](size_t i) { - return py::cast(vals[i]); - }); + return oiio_py::make_tuple(vals.size(), + [&](size_t i) { return py::cast(vals[i]); }); } diff --git a/src/python/py_paramvalue.cpp b/src/python/py_paramvalue.cpp index beb7d7b644..a38a2f6f2b 100644 --- a/src/python/py_paramvalue.cpp +++ b/src/python/py_paramvalue.cpp @@ -15,8 +15,8 @@ ParamValue_from_pyobject(string_view name, TypeDesc type, int nvalues, // Unsized uint8[] + bytes infers arraylen — must run before numelements(). if (type.basetype == TypeDesc::UINT8 && type.arraylen && py::isinstance(obj)) { - TypeDesc t = type; - py::bytes b = py::cast(obj); + TypeDesc t = type; + py::bytes b = py::cast(obj); const std::string s = oiio_py::bytes_to_stdstring(b); if (type.arraylen < 0) t.arraylen = int(s.size()) / nvalues; @@ -154,40 +154,41 @@ declare_paramvalue(py_module& m) .value("INTERP_LINEAR", ParamValue::INTERP_LINEAR) .value("INTERP_VERTEX", ParamValue::INTERP_VERTEX); + py::class_(m, "ParamValue") - OIIO_PY_DEF_PROP_RO("name", - [](const ParamValue& self) { - return oiio_py::str(self.name().string()); - }) - OIIO_PY_DEF_PROP_RO("type", - [](const ParamValue& self) { - return self.type(); - }) - OIIO_PY_DEF_PROP_RO("value", - [](const ParamValue& self) { - return make_pyobject(self.data(), - self.type(), - self.nvalues()); - }) - OIIO_PY_DEF_PROP_RO("__len__", &ParamValue::nvalues) + .OIIO_PY_PROP_RO("name", + [](const ParamValue& self) { + return oiio_py::str(self.name().string()); + }) + .OIIO_PY_PROP_RO("type", + [](const ParamValue& self) { return self.type(); }) + .OIIO_PY_PROP_RO("value", + [](const ParamValue& self) { + return make_pyobject(self.data(), self.type(), + self.nvalues()); + }) + .OIIO_PY_PROP_RO("__len__", &ParamValue::nvalues) .def(py::init()) .def(py::init()) .def(py::init()) #if defined(OIIO_PY_BACKEND_NANOBIND) - .def("__init__", - [](ParamValue* self, const std::string& name, TypeDesc type, - const py::object& obj) { - new (self) ParamValue(ParamValue_from_pyobject( - name, type, 1, ParamValue::INTERP_CONSTANT, obj)); - }, - "name"_a, "type"_a, "value"_a) - .def("__init__", - [](ParamValue* self, const std::string& name, TypeDesc type, - int nvalues, ParamValue::Interp interp, const py::object& obj) { - new (self) ParamValue(ParamValue_from_pyobject( - name, type, nvalues, interp, obj)); - }, - "name"_a, "type"_a, "nvalues"_a, "interp"_a, "value"_a); + .def( + "__init__", + [](ParamValue* self, const std::string& name, TypeDesc type, + const py::object& obj) { + new (self) ParamValue( + ParamValue_from_pyobject(name, type, 1, + ParamValue::INTERP_CONSTANT, obj)); + }, + "name"_a, "type"_a, "value"_a) + .def( + "__init__", + [](ParamValue* self, const std::string& name, TypeDesc type, + int nvalues, ParamValue::Interp interp, const py::object& obj) { + new (self) ParamValue( + ParamValue_from_pyobject(name, type, nvalues, interp, obj)); + }, + "name"_a, "type"_a, "nvalues"_a, "interp"_a, "value"_a); #else .def(py::init([](const std::string& name, TypeDesc type, const py::object& obj) { @@ -204,6 +205,7 @@ declare_paramvalue(py_module& m) "name"_a, "type"_a, "nvalues"_a, "interp"_a, "value"_a); #endif + py::class_(m, "ParamValueList") .def(py::init<>()) .def( @@ -220,7 +222,8 @@ declare_paramvalue(py_module& m) [](const ParamValueList& self, const std::string& key) { auto p = self.find(key); if (p == self.end()) - oiio_py::throw_key_error("key '" + key + "' does not exist"); + oiio_py::throw_key_error("key '" + key + + "' does not exist"); return make_pyobject(p->data(), p->type()); }, oiio_py::ref_internal) diff --git a/src/python/py_roi.cpp b/src/python/py_roi.cpp index a32f35ca14..a39bd0ff75 100644 --- a/src/python/py_roi.cpp +++ b/src/python/py_roi.cpp @@ -27,14 +27,14 @@ void declare_roi(py_module& m) { py::class_(m, "ROI") - OIIO_PY_DEF_RW("xbegin", &ROI::xbegin) - OIIO_PY_DEF_RW("xend", &ROI::xend) - OIIO_PY_DEF_RW("ybegin", &ROI::ybegin) - OIIO_PY_DEF_RW("yend", &ROI::yend) - OIIO_PY_DEF_RW("zbegin", &ROI::zbegin) - OIIO_PY_DEF_RW("zend", &ROI::zend) - OIIO_PY_DEF_RW("chbegin", &ROI::chbegin) - OIIO_PY_DEF_RW("chend", &ROI::chend) + .OIIO_PY_RW("xbegin", &ROI::xbegin) + .OIIO_PY_RW("xend", &ROI::xend) + .OIIO_PY_RW("ybegin", &ROI::ybegin) + .OIIO_PY_RW("yend", &ROI::yend) + .OIIO_PY_RW("zbegin", &ROI::zbegin) + .OIIO_PY_RW("zend", &ROI::zend) + .OIIO_PY_RW("chbegin", &ROI::chbegin) + .OIIO_PY_RW("chend", &ROI::chend) .def(py::init<>()) .def(py::init()) @@ -43,18 +43,17 @@ declare_roi(py_module& m) .def(py::init()) // .def("defined", [](const ROI& roi) { return (int)roi.defined(); }) - OIIO_PY_DEF_PROP_RO("defined", &ROI::defined) - OIIO_PY_DEF_PROP_RO("width", &ROI::width) - OIIO_PY_DEF_PROP_RO("height", &ROI::height) - OIIO_PY_DEF_PROP_RO("depth", &ROI::depth) - OIIO_PY_DEF_PROP_RO("nchannels", &ROI::nchannels) - OIIO_PY_DEF_PROP_RO("npixels", &ROI::npixels) + .OIIO_PY_PROP_RO("defined", &ROI::defined) + .OIIO_PY_PROP_RO("width", &ROI::width) + .OIIO_PY_PROP_RO("height", &ROI::height) + .OIIO_PY_PROP_RO("depth", &ROI::depth) + .OIIO_PY_PROP_RO("nchannels", &ROI::nchannels) + .OIIO_PY_PROP_RO("npixels", &ROI::npixels) .def("contains", &roi_contains_coord, "x"_a, "y"_a, "z"_a = 0, "ch"_a = 0) .def("contains", &roi_contains_roi, "other"_a) - OIIO_PY_DEF_READONLY_STATIC_LAMBDA( - "All", [](const py::object&) { return ROI::All(); }) + .OIIO_PY_RO_STATIC("All", [](const py::object&) { return ROI::All(); }) // Conversion to string .def("__str__", @@ -73,6 +72,7 @@ declare_roi(py_module& m) .def(py::self != py::self) // operator!= // NOSONAR ; + m.def("union", &roi_union); m.def("intersection", &roi_intersection); m.def("get_roi", &get_roi); diff --git a/src/python/py_typedesc.cpp b/src/python/py_typedesc.cpp index cd9818d6c2..ba42876a37 100644 --- a/src/python/py_typedesc.cpp +++ b/src/python/py_typedesc.cpp @@ -61,27 +61,28 @@ declare_typedesc(py_module& m) .value("BOX", TypeDesc::BOX) .export_values(); + py::class_(m, "TypeDesc") // basetype, aggregate, and vecsemantics should look like BASETYPE, // AGGREGATE, VECSEMANTICS, but since they are stored as unsigned // char, def_readwrite() doesn't do the right thing. Instead, we // use set_foo/get_foo wrappers, but from Python it looks like // regular member access. - OIIO_PY_DEF_PROP_RW( + .OIIO_PY_PROP_RW( "basetype", [](TypeDesc t) { return TypeDesc::BASETYPE(t.basetype); }, [](TypeDesc& t, TypeDesc::BASETYPE b) { return t.basetype = b; }) - OIIO_PY_DEF_PROP_RW( + .OIIO_PY_PROP_RW( "aggregate", [](TypeDesc t) { return TypeDesc::AGGREGATE(t.aggregate); }, [](TypeDesc& t, TypeDesc::AGGREGATE b) { return t.aggregate = b; }) - OIIO_PY_DEF_PROP_RW( + .OIIO_PY_PROP_RW( "vecsemantics", [](TypeDesc t) { return TypeDesc::VECSEMANTICS(t.vecsemantics); }, [](TypeDesc& t, TypeDesc::VECSEMANTICS b) { return t.vecsemantics = b; }) - OIIO_PY_DEF_RW("arraylen", &TypeDesc::arraylen) + .OIIO_PY_RW("arraylen", &TypeDesc::arraylen) // Constructors: () [defined implicitly], (base), (base, agg), // (base,agg,vecsem), (base,agg,vecsem,arraylen), string. .def(py::init<>()) @@ -101,7 +102,8 @@ declare_typedesc(py_module& m) // .def(init()) // .def(init()) // FIXME -- I bet this works with Pybind11 - .def("c_str", [](const TypeDesc& self) { return oiio_py::str(self.c_str()); }) + .def("c_str", + [](const TypeDesc& self) { return oiio_py::str(self.c_str()); }) .def("numelements", &TypeDesc::numelements) .def("basevalues", &TypeDesc::basevalues) .def("size", &TypeDesc::size) @@ -114,31 +116,36 @@ declare_typedesc(py_module& m) }) .def("equivalent", &TypeDesc::equivalent) .def("unarray", &TypeDesc::unarray) - .def("is_vec2", - [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { - return t.is_vec2(b); - }, - "b"_a = TypeDesc::FLOAT) - .def("is_vec3", - [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { - return t.is_vec3(b); - }, - "b"_a = TypeDesc::FLOAT) - .def("is_vec4", - [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { - return t.is_vec4(b); - }, - "b"_a = TypeDesc::FLOAT) - .def("is_box2", - [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { - return t.is_box2(b); - }, - "b"_a = TypeDesc::FLOAT) - .def("is_box3", - [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { - return t.is_box3(b); - }, - "b"_a = TypeDesc::FLOAT) + .def( + "is_vec2", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_vec2(b); + }, + "b"_a = TypeDesc::FLOAT) + .def( + "is_vec3", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_vec3(b); + }, + "b"_a = TypeDesc::FLOAT) + .def( + "is_vec4", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_vec4(b); + }, + "b"_a = TypeDesc::FLOAT) + .def( + "is_box2", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_box2(b); + }, + "b"_a = TypeDesc::FLOAT) + .def( + "is_box3", + [](const TypeDesc& t, TypeDesc::BASETYPE b = TypeDesc::FLOAT) { + return t.is_box3(b); + }, + "b"_a = TypeDesc::FLOAT) .def_static("all_types_equal", [](const std::vector& types) { return TypeDesc::all_types_equal(types); @@ -154,6 +161,7 @@ declare_typedesc(py_module& m) return oiio_py::str(""); }); + // Declare that a BASETYPE is implicitly convertible to a TypeDesc. // This keeps us from having to separately declare func(TypeDesc) // and func(TypeDesc::BASETYPE) everywhere.