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..81072aee08 --- /dev/null +++ b/src/python/py_backend.h @@ -0,0 +1,175 @@ +// 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_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 { + +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_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 { + +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..ea64538f4d 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_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) - .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_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) - .def_property("roi", &ImageSpec::roi, &ImageSpec::set_roi) - .def_property("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()) @@ -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,8 @@ 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..464162a5aa 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,127 @@ 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); - }); - m.def("equivalent_colorspace", - [](const std::string& a, const std::string& b) { - return equivalent_colorspace(a, b); - }); + "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..36e61dc422 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,11 @@ 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()))); + vals.emplace_back(TypeDesc(oiio_py::str_to_stdstring(elem))); } else { // FIXME? Other cases? vals.emplace_back(TypeUnknown); @@ -251,7 +262,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 +276,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 +294,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 +309,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 +346,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 +426,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 +438,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 +465,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 +488,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 +502,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 +513,8 @@ 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 +528,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 +549,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 +587,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 +605,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 +634,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 +692,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 +736,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..a38a2f6f2b 100644 --- a/src/python/py_paramvalue.cpp +++ b/src/python/py_paramvalue.cpp @@ -15,8 +15,9 @@ 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; - std::string s = obj.cast(); + 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; 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) @@ -155,25 +154,42 @@ declare_paramvalue(py::module& m) .value("INTERP_LINEAR", ParamValue::INTERP_LINEAR) .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_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); +#else .def(py::init([](const std::string& name, TypeDesc type, const py::object& obj) { return ParamValue_from_pyobject(name, type, 1, @@ -187,6 +203,8 @@ 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 +215,18 @@ 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 +244,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..a39bd0ff75 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_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()) @@ -48,22 +43,22 @@ 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_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) - .def_readonly_static("All", &ROI_All) + .OIIO_PY_RO_STATIC("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 @@ -77,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 1d286c5f30..ba42876a37 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) @@ -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. - .def_property( + .OIIO_PY_PROP_RW( "basetype", [](TypeDesc t) { return TypeDesc::BASETYPE(t.basetype); }, [](TypeDesc& t, TypeDesc::BASETYPE b) { return t.basetype = b; }) - .def_property( + .OIIO_PY_PROP_RW( "aggregate", [](TypeDesc t) { return TypeDesc::AGGREGATE(t.aggregate); }, [](TypeDesc& t, TypeDesc::AGGREGATE b) { return t.aggregate = b; }) - .def_property( + .OIIO_PY_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_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 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 +116,36 @@ 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,11 +156,12 @@ 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. // This keeps us from having to separately declare func(TypeDesc) // and func(TypeDesc::BASETYPE) everywhere. @@ -144,6 +172,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")]))