Unify pybind11 and nanobind into single-source bindings (#5254)

Exploring an idea Anton had about unifiying two python backends into
single source since they are so similar.

## Summary

This PR unifies the early nanobind migration with the existing pybind11
Python bindings by compiling **shared sources** from `src/python/` for
both backends, instead of maintaining parallel copies under
`src/python-nanobind/`.

Migrated modules so far: `ROI`, `TypeDesc`, `ImageSpec`, `ParamValue` /
`ParamValueList`, plus the core `OpenImageIO` module helpers in
`py_oiio.cpp`. Backend differences are isolated in `py_backend.h` (type
aliases, binding API macros like `.OIIO_PY_RW(...)`, and small
`oiio_py::` helpers). Shared Python↔C++ conversion logic lives in
`py_oiio.h` with `#if` only where the backends genuinely differ.

CMake is updated so that when `-DOIIO_PYTHON_BINDINGS_BACKEND=nanobind`
is selected, the module is built from the unified sources and installed
as the main `OpenImageIO` package in the expected location. With `both`,
pybind11 remains the primary module and nanobind builds as
`PyOpenImageIONanobind` for side-by-side testing.

Duplicate nanobind-only `.cpp` files are removed (~1.8k lines deleted
net). Testsuite coverage is extended with explicit roundtrip checks for
shared helper paths (attributes, buffers, tuples).

---

## Pros

- **Single source of truth** for migrated bindings — no more keeping two
copies of `py_roi.cpp`, `py_imagespec.cpp`, etc. in sync by hand.
- **Lower risk of backend drift** — a fix or API tweak in a migrated
module automatically applies to both pybind11 and nanobind.
- **Net code reduction** — removes a large duplicated tree under
`src/python-nanobind/` while centralizing shared logic in one place.
- **Clearer migration path** — `MIGRATION_STATUS.md` documents what is
fully migrated, partially migrated, and still pybind-only.
- **Better nanobind-only build layout** — on `main`, choosing nanobind
does not install into the same package layout as pybind11; this PR fixes
that for the migrated surface.
- **Side-by-side validation** — `OIIO_PYTHON_BINDINGS_BACKEND=both`
builds both modules so existing `testsuite/python-*` and `*.nanobind`
variants can be run against the same C++ sources.
- **Explicit parity tests** — new/extended tests cover shared conversion
paths (typed attributes, buffer→vector, tuple roundtrips) rather than
relying on import smoke tests alone.
- **Maintainable binding style** — method-name macros
(`.OIIO_PY_RW(...)`) keep clang-format happy without `clang-format off`
regions.

---

## Cons

- **Added abstraction in the pybind11 path** — even pybind-only builds
now go through `py_backend.h`, macros, and shared helpers; slightly more
indirection than plain `.def_readwrite(...)`.
- **Regression risk for pybind11** — adapting existing pybind code to
work for both backends could subtly change pybind11 behavior at the
edges (init, casting, error paths, ownership).
- **Incomplete migration** — most of the Python API (`ImageBuf`,
`ImageInput`/`ImageOutput`, `ImageBufAlgo`, texture system, etc.) is
still pybind-only; this PR is a foundation, not full nanobind coverage.
- **Known nanobind gaps remain** — module-level exports and helpers
listed in `MIGRATION_STATUS.md` are still missing or intentionally
different vs pybind11.
- **Higher review and test burden** — reviewers need to think about two
backends; CI/local testing should cover both pybind and `.nanobind`
variants for touched code.
- **Conditional compilation complexity** — `#if
defined(OIIO_PY_BACKEND_NANOBIND)` remains in a few places (e.g.
`ParamValue` constructors, buffer dispatch); easy to get wrong if
extended carelessly.
- **Slightly less obvious binding code** — `.OIIO_PY_RW("x", ...)` reads
less directly than `.def_readwrite("x", ...)` until you know the macro
map.

---------

Signed-off-by: Aleksandr Motsjonov <soswow@gmail.com>
This commit is contained in:
Aleksandr Motsjonov
2026-07-09 10:26:04 -07:00
committed by GitHub
parent fed212a115
commit 667365f45b
24 changed files with 910 additions and 1840 deletions
+4 -2
View File
@@ -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 ()
+37 -18
View File
@@ -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})
if (SKBUILD)
set (PYTHON_SITE_DIR .)
endif ()
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 ${PYTHON_SITE_DIR} COMPONENT user
LIBRARY DESTINATION ${PYTHON_SITE_DIR} COMPONENT user)
install (FILES __init__.py stubs/OpenImageIO/__init__.pyi stubs/OpenImageIO/py.typed
DESTINATION ${PYTHON_SITE_DIR} COMPONENT user)
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 ()
endmacro ()
+11 -13
View File
@@ -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)
-290
View File
@@ -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<TypeDesc> formats;
if (spec.channelformats.size() || !allow_empty)
spec.get_channelformats(formats);
return PyOpenImageIO::C_to_tuple(cspan<TypeDesc>(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<std::string>(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_<ImageSpec>(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<int, int, int, TypeDesc>())
.def(nb::init<const ROI&, TypeDesc>())
.def(nb::init<TypeDesc>())
.def(nb::init<const ImageSpec&>())
.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
-175
View File
@@ -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<const int*>(data), type, nvalues);
if (type.basetype == TypeDesc::FLOAT)
return C_to_val_or_tuple(static_cast<const float*>(data), type,
nvalues);
if (type.basetype == TypeDesc::STRING)
return C_to_val_or_tuple(static_cast<const char* const*>(data), type,
nvalues);
if (type.basetype == TypeDesc::UINT32)
return C_to_val_or_tuple(static_cast<const unsigned int*>(data), type,
nvalues);
if (type.basetype == TypeDesc::INT16)
return C_to_val_or_tuple(static_cast<const short*>(data), type,
nvalues);
if (type.basetype == TypeDesc::UINT16)
return C_to_val_or_tuple(static_cast<const unsigned short*>(data), type,
nvalues);
if (type.basetype == TypeDesc::INT64)
return C_to_val_or_tuple(static_cast<const int64_t*>(data), type,
nvalues);
if (type.basetype == TypeDesc::UINT64)
return C_to_val_or_tuple(static_cast<const uint64_t*>(data), type,
nvalues);
if (type.basetype == TypeDesc::DOUBLE)
return C_to_val_or_tuple(static_cast<const double*>(data), type,
nvalues);
if (type.basetype == TypeDesc::HALF)
return C_to_val_or_tuple(static_cast<const half*>(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<size_t>(n));
return make_numpy_array(copy, static_cast<size_t>(n));
}
if (type.basetype == TypeDesc::UINT8) {
return C_to_val_or_tuple(static_cast<const unsigned char*>(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;
}
-488
View File
@@ -1,488 +0,0 @@
// Copyright Contributors to the OpenImageIO project.
// SPDX-License-Identifier: Apache-2.0
// https://github.com/AcademySoftwareFoundation/OpenImageIO
#pragma once
#include <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/oiioversion.h>
#include <OpenImageIO/span.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/typedesc.h>
#include <nanobind/make_iterator.h>
#include <nanobind/nanobind.h>
#include <nanobind/ndarray.h>
#include <nanobind/operators.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/vector.h>
#include <cstring>
#include <string>
#include <type_traits>
#include <vector>
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<typename T> struct PyTypeForCType {};
template<> struct PyTypeForCType<int> {
using type = nb::int_;
};
template<> struct PyTypeForCType<unsigned int> {
using type = nb::int_;
};
template<> struct PyTypeForCType<short> {
using type = nb::int_;
};
template<> struct PyTypeForCType<unsigned short> {
using type = nb::int_;
};
template<> struct PyTypeForCType<char> {
using type = nb::int_;
};
template<> struct PyTypeForCType<unsigned char> {
using type = nb::int_;
};
template<> struct PyTypeForCType<int64_t> {
using type = nb::int_;
};
template<> struct PyTypeForCType<uint64_t> {
using type = nb::int_;
};
template<> struct PyTypeForCType<float> {
using type = nb::float_;
};
template<> struct PyTypeForCType<half> {
using type = nb::float_;
};
template<> struct PyTypeForCType<double> {
using type = nb::float_;
};
template<> struct PyTypeForCType<const char*> {
using type = nb::str;
};
template<> struct PyTypeForCType<std::string> {
using type = nb::str;
};
template<typename T, typename Obj>
inline bool
py_indexable_pod_to_stdvector(std::vector<T>& 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<T, float>) {
if (nb::isinstance<nb::float_>(elem)) {
vals.emplace_back(nb::cast<float>(elem));
} else if (nb::isinstance<nb::int_>(elem)) {
vals.emplace_back(static_cast<float>(nb::cast<int>(elem)));
} else {
ok = false;
}
} else if constexpr (std::is_same_v<T, int>) {
if (nb::isinstance<nb::int_>(elem)) {
vals.emplace_back(nb::cast<int>(elem));
} else {
ok = false;
}
} else if constexpr (std::is_same_v<T, unsigned int>) {
if (nb::isinstance<nb::int_>(elem)) {
vals.emplace_back(nb::cast<unsigned int>(elem));
} else {
ok = false;
}
} else if constexpr (std::is_same_v<T, unsigned char>) {
if (nb::isinstance<nb::int_>(elem)) {
vals.emplace_back(nb::cast<unsigned char>(elem));
} else {
ok = false;
}
} else {
ok = false;
}
if (!ok)
break;
}
return ok;
}
template<typename Obj>
inline bool
py_indexable_pod_to_stdvector(std::vector<std::string>& 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<nb::str>(elem)) {
vals.emplace_back(std::string(nb::cast<nb::str>(elem).c_str()));
} else {
ok = false;
break;
}
}
return ok;
}
template<typename Obj>
inline bool
py_indexable_pod_to_stdvector(std::vector<TypeDesc>& 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<TypeDesc>(elem)) {
vals.emplace_back(nb::cast<TypeDesc>(elem));
} else if (nb::isinstance<TypeDesc::BASETYPE>(elem)) {
vals.emplace_back(TypeDesc(nb::cast<TypeDesc::BASETYPE>(elem)));
} else if (nb::isinstance<nb::str>(elem)) {
vals.emplace_back(TypeDesc(nb::cast<nb::str>(elem).c_str()));
} else {
ok = false;
break;
}
}
return ok;
}
template<typename T>
inline bool
py_scalar_pod_to_stdvector(std::vector<T>& vals, const nb::handle& obj)
{
using pytype = typename PyTypeForCType<T>::type;
vals.clear();
if (nb::isinstance<pytype>(obj)) {
vals.emplace_back(nb::cast<T>(obj));
return true;
}
return false;
}
template<>
inline bool
py_scalar_pod_to_stdvector(std::vector<float>& vals, const nb::handle& obj)
{
vals.clear();
if (nb::isinstance<nb::float_>(obj)) {
vals.emplace_back(nb::cast<float>(obj));
return true;
}
if (nb::isinstance<nb::int_>(obj)) {
vals.emplace_back(static_cast<float>(nb::cast<int>(obj)));
return true;
}
return false;
}
template<>
inline bool
py_scalar_pod_to_stdvector(std::vector<std::string>& vals,
const nb::handle& obj)
{
vals.clear();
if (nb::isinstance<nb::str>(obj)) {
vals.emplace_back(std::string(nb::cast<nb::str>(obj).c_str()));
return true;
}
return false;
}
template<>
inline bool
py_scalar_pod_to_stdvector(std::vector<TypeDesc>& vals, const nb::handle& obj)
{
vals.clear();
if (nb::isinstance<TypeDesc>(obj)) {
vals.emplace_back(nb::cast<TypeDesc>(obj));
return true;
}
if (nb::isinstance<TypeDesc::BASETYPE>(obj)) {
vals.emplace_back(TypeDesc(nb::cast<TypeDesc::BASETYPE>(obj)));
return true;
}
if (nb::isinstance<nb::str>(obj)) {
vals.emplace_back(TypeDesc(nb::cast<nb::str>(obj).c_str()));
return true;
}
return false;
}
template<typename T>
inline bool
py_buffer_to_stdvector(std::vector<T>& 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<size_t>(view.len) / view.itemsize;
vals.clear();
vals.reserve(count);
const unsigned char* data = static_cast<const unsigned char*>(view.buf);
for (size_t i = 0; ok && i < count; ++i) {
if constexpr (std::is_same_v<T, float>) {
if (format.basetype == TypeDesc::FLOAT)
vals.emplace_back(reinterpret_cast<const float*>(data)[i]);
else if (format.basetype == TypeDesc::INT)
vals.emplace_back(
static_cast<float>(reinterpret_cast<const int*>(data)[i]));
else
ok = false;
} else if constexpr (std::is_same_v<T, int>) {
if (format.basetype == TypeDesc::INT)
vals.emplace_back(reinterpret_cast<const int*>(data)[i]);
else
ok = false;
} else if constexpr (std::is_same_v<T, unsigned int>) {
if (format.basetype == TypeDesc::UINT)
vals.emplace_back(
reinterpret_cast<const unsigned int*>(data)[i]);
else
ok = false;
} else if constexpr (std::is_same_v<T, unsigned char>) {
if (format.basetype == TypeDesc::UINT8)
vals.emplace_back(
reinterpret_cast<const unsigned char*>(data)[i]);
else
ok = false;
} else {
ok = false;
}
}
PyBuffer_Release(&view);
return ok;
}
template<>
inline bool
py_buffer_to_stdvector(std::vector<std::string>&, const nb::handle&)
{
return false;
}
template<>
inline bool
py_buffer_to_stdvector(std::vector<TypeDesc>&, const nb::handle&)
{
return false;
}
template<typename T>
inline bool
py_to_stdvector(std::vector<T>& vals, const nb::handle& obj)
{
if (PyTuple_Check(obj.ptr()))
return py_indexable_pod_to_stdvector(vals, nb::borrow<nb::tuple>(obj));
if (PyList_Check(obj.ptr()))
return py_indexable_pod_to_stdvector(vals, nb::borrow<nb::list>(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<typename T>
inline nb::tuple
C_to_tuple(cspan<T> vals)
{
nb::list list;
for (size_t i = 0; i < vals.size(); ++i)
list.append(nb::cast(vals[i]));
return nb::steal<nb::tuple>(PyList_AsTuple(list.ptr()));
}
template<typename T>
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<nb::tuple>(PyList_AsTuple(list.ptr()));
}
template<typename T>
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<half> vals)
{
nb::list list;
for (size_t i = 0; i < vals.size(); ++i)
list.append(static_cast<float>(vals[i]));
return nb::steal<nb::tuple>(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<float>(vals[i]));
return nb::steal<nb::tuple>(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<float>(vals[0]));
return C_to_tuple(vals, n);
}
template<typename T, typename Obj>
bool
attribute_typed(T& myobj, string_view name, TypeDesc type, const Obj& dataobj)
{
if (type.basetype == TypeDesc::INT) {
std::vector<int> 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<unsigned int> 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<unsigned char> 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<float> 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<std::string> vals;
bool ok = py_to_stdvector(vals, dataobj);
ok &= (vals.size() == type.numelements() * type.aggregate);
if (ok) {
std::vector<ustring> u;
u.reserve(vals.size());
for (auto& val : vals)
u.emplace_back(val);
myobj.attribute(name, type, u.data());
}
return ok;
}
return false;
}
template<typename T>
inline void
attribute_onearg(T& myobj, string_view name, const nb::handle& obj)
{
if (nb::isinstance<nb::float_>(obj))
myobj.attribute(name, nb::cast<float>(obj));
else if (nb::isinstance<nb::int_>(obj))
myobj.attribute(name, nb::cast<int>(obj));
else if (nb::isinstance<nb::str>(obj))
myobj.attribute(name, std::string(nb::cast<nb::str>(obj).c_str()));
else if (nb::isinstance<nb::bytes>(obj)) {
nb::bytes bytes = nb::cast<nb::bytes>(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<class T>
inline nb::object
make_numpy_array(T* data, size_t size)
{
nb::capsule owner(data, [](void* p) noexcept {
delete[] reinterpret_cast<T*>(p);
});
nb::ndarray<nb::numpy, T, nb::shape<-1>> 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<typename C>
inline void
delegate_setitem(C& self, const std::string& key, const nb::handle& obj)
{
if (nb::isinstance<nb::float_>(obj))
self[key] = nb::cast<float>(obj);
else if (nb::isinstance<nb::int_>(obj))
self[key] = nb::cast<int>(obj);
else if (nb::isinstance<nb::str>(obj))
self[key] = std::string(nb::cast<nb::str>(obj).c_str());
else if (nb::isinstance<nb::bytes>(obj)) {
nb::bytes bytes = nb::cast<nb::bytes>(obj);
self[key] = std::string(bytes.c_str(), bytes.size());
} else
throw std::invalid_argument("Bad type for __setitem__");
}
} // namespace PyOpenImageIO
-248
View File
@@ -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<typename T, typename Obj>
bool
attribute_typed_nvalues(T& myobj, string_view name, TypeDesc type, int nvalues,
const Obj& dataobj)
{
if (type.basetype == TypeDesc::INT) {
std::vector<int> 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<unsigned int> 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<float> 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<std::string> vals;
bool ok = PyOpenImageIO::py_to_stdvector(vals, dataobj);
ok &= (vals.size() == type.numelements() * type.aggregate * nvalues);
if (ok) {
std::vector<ustring> 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<uint8_t> 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<nb::bytes>(obj)) {
TypeDesc t = type;
nb::bytes b = nb::cast<nb::bytes>(obj);
const std::string s(b.c_str(), b.size());
if (t.arraylen < 0)
t.arraylen = static_cast<int>(s.size()) / nvalues;
if (t.arraylen * nvalues == static_cast<int>(s.size())) {
std::vector<uint8_t> vals(reinterpret_cast<const uint8_t*>(s.data()),
reinterpret_cast<const uint8_t*>(s.data())
+ s.size());
pv.init(name, t, nvalues, interp, vals.data());
}
return pv;
}
const size_t expected_size = static_cast<size_t>(
type.numelements() * type.aggregate * nvalues);
if (type.basetype == TypeDesc::INT) {
std::vector<int> 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<unsigned int> 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<float> 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<std::string> vals;
if (PyOpenImageIO::py_to_stdvector(vals, obj)
&& vals.size() >= expected_size) {
std::vector<ustring> 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<uint8_t> 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_<ParamValue::Interp>(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_<ParamValue>(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<const std::string&, int>())
.def(nb::init<const std::string&, float>())
.def(nb::init<const std::string&, const std::string&>())
.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_<ParamValueList>(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<ParamValueList>(), "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
-70
View File
@@ -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> 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<int, int, int, int>())
.def(nb::init<int, int, int, int, int, int>())
.def(nb::init<int, int, int, int, int, int, int, int>())
.def(nb::init<const ROI&>())
.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
-246
View File
@@ -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<class Enum>
void
typedesc_property(TypeDesc& t, Enum value);
template<>
void
typedesc_property<TypeDesc::BASETYPE>(TypeDesc& t, TypeDesc::BASETYPE value)
{
t.basetype = value;
}
template<>
void
typedesc_property<TypeDesc::AGGREGATE>(TypeDesc& t, TypeDesc::AGGREGATE value)
{
t.aggregate = value;
}
template<>
void
typedesc_property<TypeDesc::VECSEMANTICS>(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_<BASETYPE>(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_<AGGREGATE>(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_<VECSEMANTICS>(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_<TypeDesc>(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<const TypeDesc&>())
.def(nb::init<BASETYPE>())
.def(nb::init<BASETYPE, AGGREGATE>())
.def(nb::init<BASETYPE, AGGREGATE, VECSEMANTICS>())
.def(nb::init<BASETYPE, AGGREGATE, VECSEMANTICS, int>())
.def(nb::init<const char*>())
.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<TypeDesc>& 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("<TypeDesc '{}'>", t.c_str());
});
nb::implicitly_convertible<BASETYPE, TypeDesc>();
nb::implicitly_convertible<nb::str, TypeDesc>();
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
+37 -14
View File
@@ -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 ()
@@ -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) | <ul><li><code>attribute</code> (one-arg and typed)</li><li><code>get_int_attribute</code></li><li><code>get_float_attribute</code></li><li><code>get_string_attribute</code></li><li><code>getattribute</code></li><li><code>__version__</code></li></ul> | <ul><li><code>geterror</code></li><li><code>get_bytes_attribute</code></li><li>Module <code>set_colorspace</code> (helper taking <code>ImageSpec</code> — the instance method is on <code>ImageSpec</code> in nanobind)</li><li><code>set_colorspace_rec709_gamma</code></li><li><code>equivalent_colorspace</code></li><li><code>is_imageio_format_name</code></li><li><code>AutoStride</code></li><li><code>openimageio_version</code>, <code>VERSION</code>, <code>VERSION_STRING</code>, <code>VERSION_MAJOR</code>, <code>VERSION_MINOR</code>, <code>VERSION_PATCH</code>, <code>INTRO_STRING</code></li><li>Optional: stack traces when <code>OPENIMAGEIO_DEBUG_PYTHON</code> is set (<code>Sysutil</code>)</li><li><code>make_pyobject</code>: no pybind-style <code>debugfmt</code> when the type is unhandled (returns default quietly)</li></ul> |
| `__init__.py` (package) | Env / DLL path setup, <code>from ._OpenImageIO import *</code>, version docstring. | <strong>TODO:</strong> Python CLI entry-point trampolines when the install layout matches the full wheel. |
| `py_oiio.cpp` (`OpenImageIO` module) | <ul><li><code>attribute</code> (one-arg and typed)</li><li><code>get_int_attribute</code></li><li><code>get_float_attribute</code></li><li><code>get_string_attribute</code></li><li><code>getattribute</code></li><li><code>__version__</code>, <code>VERSION_STRING</code></li></ul> | <ul><li><code>geterror</code></li><li><code>get_bytes_attribute</code></li><li>Module <code>set_colorspace</code> (helper taking <code>ImageSpec</code> — the instance method is on <code>ImageSpec</code> in nanobind)</li><li><code>set_colorspace_rec709_gamma</code></li><li><code>equivalent_colorspace</code></li><li><code>is_imageio_format_name</code></li><li><code>AutoStride</code></li><li><code>openimageio_version</code>, <code>VERSION</code>, <code>VERSION_MAJOR</code>, <code>VERSION_MINOR</code>, <code>VERSION_PATCH</code>, <code>INTRO_STRING</code></li><li>Optional: stack traces when <code>OPENIMAGEIO_DEBUG_PYTHON</code> is set (<code>Sysutil</code>)</li><li><code>make_pyobject</code>: no pybind-style <code>debugfmt</code> when the type is unhandled (returns default quietly)</li></ul> |
| `__init__.py` (package) | Shared with pybind11: env / DLL path setup, <code>from .OpenImageIO import *</code>. | <strong>TODO:</strong> 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 |
| --- | --- |
+175
View File
@@ -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 <nanobind/make_iterator.h>
# include <nanobind/nanobind.h>
# include <nanobind/ndarray.h>
# include <nanobind/operators.h>
# include <nanobind/stl/string.h>
# include <nanobind/stl/vector.h>
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<py::str>(handle).c_str());
}
template<typename F>
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<py::tuple>(PyList_AsTuple(list.ptr()));
}
inline constexpr auto ref_internal = py::rv_policy::reference_internal;
template<typename T>
inline auto
str(T&& x)
{
return std::forward<T>(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<typename Container>
inline auto
make_iterator(Container& container)
{
return py::make_iterator(py::type<Container>(), "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<class T>
inline py::object
make_numpy_array(T* data, size_t size)
{
py::capsule owner(data, [](void* p) noexcept {
delete[] reinterpret_cast<T*>(p);
});
py::ndarray<py::numpy, T, py::shape<-1>> array(data, { size }, owner);
return py::cast(std::move(array), py::rv_policy::move);
}
} // namespace oiio_py
#else // pybind11
# include <pybind11/numpy.h>
# include <pybind11/operators.h>
# include <pybind11/pybind11.h>
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<py::str>(handle));
}
template<typename F>
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<typename T>
inline auto
str(T&& x)
{
return py::str(std::forward<T>(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<typename Container>
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<class T>
inline py::object
make_numpy_array(T* data, size_t size)
{
py::capsule free_when_done(data, [](void* f) {
delete[] (reinterpret_cast<T*>(f));
});
return py::array_t<T>({ size }, { sizeof(T) }, data, free_when_done);
}
} // namespace oiio_py
#endif
+46 -40
View File
@@ -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_<ImageSpec>(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<int, int, int, TypeDesc>())
@@ -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
+129 -45
View File
@@ -6,6 +6,11 @@
#include <OpenImageIO/sysutil.h>
#if defined(OIIO_PY_BACKEND_NANOBIND)
# include <OpenImageIO/oiioversion.h>
# include <cstring>
#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<size_t>(n));
return oiio_py::make_numpy_array(copy, static_cast<size_t>(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
+227 -122
View File
@@ -38,18 +38,27 @@
#include <OpenImageIO/texture.h>
#include <OpenImageIO/typedesc.h>
#include <pybind11/numpy.h>
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
#if defined(OIIO_PY_BACKEND_NANOBIND)
# include <cstring>
#endif
#include "py_backend.h"
#if defined(OIIO_PY_BACKEND_NANOBIND)
# define PY_STR(x) oiio_py::str(x)
#else
# include <pybind11/numpy.h>
# include <pybind11/stl.h>
// 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<py::str>();
}
#endif
template<typename T> struct PyTypeForCType { };
@@ -129,12 +140,12 @@ template<> struct PyTypeForCType<uint64_t> { typedef py::int_ type; };
template<> struct PyTypeForCType<float> { typedef py::float_ type; };
template<> struct PyTypeForCType<half> { typedef py::float_ type; };
template<> struct PyTypeForCType<double> { typedef py::float_ type; };
template<> struct PyTypeForCType<const char*> { typedef PY_STR type; };
template<> struct PyTypeForCType<std::string> { typedef PY_STR type; };
template<> struct PyTypeForCType<const char*> { typedef py::str type; };
template<> struct PyTypeForCType<std::string> { 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<typename T> T dataval(size_t i) { return ((const T*)data)[i]; }
};
#endif
@@ -173,13 +185,13 @@ py_indexable_pod_to_stdvector(std::vector<T>& vals, const PYT& obj)
for (size_t i = 0; i < length; ++i) {
auto elem = obj[i];
if (std::is_same<T, float>::value && py::isinstance<py::float_>(elem)) {
vals.emplace_back(T(elem.template cast<float>()));
vals.emplace_back(T(py::cast<float>(elem)));
} else if ((std::is_same<T, float>::value || std::is_same<T, int>::value)
&& py::isinstance<py::int_>(elem)) {
vals.emplace_back(T(elem.template cast<int>()));
vals.emplace_back(T(py::cast<int>(elem)));
} else if (std::is_same<T, unsigned int>::value
&& py::isinstance<py::int_>(elem)) {
vals.emplace_back(T(elem.template cast<unsigned int>()));
vals.emplace_back(T(py::cast<unsigned int>(elem)));
} else {
// FIXME? Other cases?
vals.emplace_back(T(42));
@@ -203,7 +215,7 @@ py_indexable_pod_to_stdvector(std::vector<std::string>& vals, const PYT& obj)
for (size_t i = 0; i < length; ++i) {
auto elem = obj[i];
if (py::isinstance<py::str>(elem)) {
vals.emplace_back(elem.template cast<py::str>());
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<TypeDesc>& vals, const PYT& obj)
for (size_t i = 0; i < length; ++i) {
auto elem = obj[i];
if (py::isinstance<TypeDesc>(elem)) {
vals.emplace_back(*(elem.template cast<TypeDesc*>()));
vals.emplace_back(py::cast<TypeDesc>(elem));
} else if (py::isinstance<TypeDesc::BASETYPE>(elem)) {
vals.emplace_back(*(elem.template cast<TypeDesc::BASETYPE*>()));
vals.emplace_back(TypeDesc(py::cast<TypeDesc::BASETYPE>(elem)));
} else if (py::isinstance<py::str>(elem)) {
vals.emplace_back(
TypeDesc(std::string(elem.template cast<py::str>())));
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<T>& vals, const py::object& obj)
using pytype = typename PyTypeForCType<T>::type;
vals.clear();
if (py::isinstance<pytype>(obj)) {
vals.emplace_back(obj.template cast<pytype>());
vals.emplace_back(py::cast<T>(obj));
return true;
} else {
return false;
@@ -265,10 +276,10 @@ py_scalar_pod_to_stdvector(std::vector<float>& vals, const py::object& obj)
{
vals.clear();
if (py::isinstance<py::float_>(obj)) {
vals.emplace_back(obj.template cast<py::float_>());
vals.emplace_back(py::cast<float>(obj));
return true;
} else if (py::isinstance<py::int_>(obj)) {
int i = obj.template cast<py::int_>();
int i = py::cast<int>(obj);
vals.emplace_back((float)i);
return true;
} else {
@@ -283,13 +294,13 @@ py_scalar_pod_to_stdvector(std::vector<TypeDesc>& vals, const py::object& obj)
{
vals.clear();
if (py::isinstance<TypeDesc>(obj)) {
vals.emplace_back(*(obj.template cast<TypeDesc*>()));
vals.emplace_back(py::cast<TypeDesc>(obj));
return true;
} else if (py::isinstance<TypeDesc::BASETYPE>(obj)) {
vals.emplace_back(*(obj.template cast<TypeDesc::BASETYPE*>()));
vals.emplace_back(TypeDesc(py::cast<TypeDesc::BASETYPE>(obj)));
return true;
} else if (py::isinstance<py::str>(obj)) {
vals.emplace_back(TypeDesc(std::string(obj.template cast<py::str>())));
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<TypeDesc>& vals, const py::object& obj)
// Shared: read `count` contiguous elements of `format` into vector<T>.
// Matches the element-dispatch logic from the original pybind11 implementation.
template<typename T>
inline bool
py_buffer_to_stdvector(std::vector<T>& vals, const py::buffer& obj)
buffer_format_to_stdvector(std::vector<T>& vals, TypeDesc format,
const void* data, size_t count)
{
OIIO_DASSERT(py::isinstance<py::buffer>(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<const unsigned char*>(data);
for (size_t i = 0; i < count; ++i) {
if (std::is_same<T, float>::value
&& binfo.format.basetype == TypeDesc::FLOAT) {
vals.push_back(binfo.dataval<float>(i));
&& format.basetype == TypeDesc::FLOAT) {
vals.push_back(reinterpret_cast<const float*>(bytes)[i]);
} else if ((std::is_same<T, float>::value || std::is_same<T, int>::value)
&& binfo.format.basetype == TypeDesc::INT) {
vals.push_back(T(binfo.dataval<int>(i)));
&& format.basetype == TypeDesc::INT) {
vals.push_back(T(reinterpret_cast<const int*>(bytes)[i]));
} else if (std::is_same<T, unsigned int>::value
&& binfo.format.basetype == TypeDesc::UINT) {
vals.push_back(T(binfo.dataval<unsigned int>(i)));
&& format.basetype == TypeDesc::UINT) {
vals.push_back(T(reinterpret_cast<const unsigned int*>(bytes)[i]));
} else if (std::is_same<T, unsigned char>::value
&& binfo.format.basetype == TypeDesc::UINT8) {
vals.push_back(T(binfo.dataval<unsigned char>(i)));
&& format.basetype == TypeDesc::UINT8) {
vals.push_back(T(reinterpret_cast<const unsigned char*>(bytes)[i]));
} else if (std::is_same<T, unsigned short>::value
&& binfo.format.basetype == TypeDesc::UINT16) {
vals.push_back(T(binfo.dataval<unsigned short>(i)));
&& format.basetype == TypeDesc::UINT16) {
vals.push_back(
T(reinterpret_cast<const unsigned short*>(bytes)[i]));
} else {
// FIXME? Other cases?
vals.push_back(T(42));
@@ -332,6 +346,67 @@ py_buffer_to_stdvector(std::vector<T>& vals, const py::buffer& obj)
}
#if defined(OIIO_PY_BACKEND_NANOBIND)
template<typename T>
inline bool
py_buffer_to_stdvector(std::vector<T>& 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<size_t>(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<std::string>&, const py::object&)
{
return false;
}
template<>
inline bool
py_buffer_to_stdvector(std::vector<TypeDesc>&, const py::object&)
{
return false;
}
#else
template<typename T>
inline bool
py_buffer_to_stdvector(std::vector<T>& vals, const py::buffer& obj)
{
OIIO_DASSERT(py::isinstance<py::buffer>(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<TypeDesc>& /*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<T>& vals, const py::object& obj)
{
if (py::isinstance<py::tuple>(obj)) { // if it's a Python tuple
return py_indexable_pod_to_stdvector(vals, obj.cast<py::tuple>());
return py_indexable_pod_to_stdvector(vals, py::cast<py::tuple>(obj));
}
if (py::isinstance<py::list>(obj)) { // if it's a Python list
return py_indexable_pod_to_stdvector(vals, obj.cast<py::list>());
return py_indexable_pod_to_stdvector(vals, py::cast<py::list>(obj));
}
// Apparently a str can masquerade as a buffer object, so make sure to
// exclude that from the buffer case.
if (py::isinstance<py::buffer>(obj) && !py::isinstance<py::str>(obj)) {
return py_buffer_to_stdvector(vals, obj.cast<py::buffer>());
#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<py::buffer>(obj) && !py::isinstance<py::str>(obj)) {
return py_buffer_to_stdvector(vals, py::cast<py::buffer>(obj));
}
#endif
// handle scalar case or bust
return py_scalar_pod_to_stdvector(vals, obj);
@@ -382,23 +465,22 @@ template<typename T>
inline py::tuple
C_to_tuple(cspan<T> vals)
{
size_t size = vals.size();
py::tuple result(size);
for (size_t i = 0; i < size; ++i)
result[i] = typename PyTypeForCType<T>::type(vals[i]);
return result;
return oiio_py::make_tuple(vals.size(), [&](size_t i) {
if constexpr (std::is_same_v<T, half>) {
return py::cast(static_cast<float>(vals[i]));
} else {
return py::cast(vals[i]);
}
});
}
template<typename T>
inline py::tuple
C_to_tuple(span<T> vals)
{
size_t size = vals.size();
py::tuple result(size);
for (size_t i = 0; i < size; ++i)
result[i] = typename PyTypeForCType<T>::type(vals[i]);
return result;
return C_to_tuple(cspan<T>(vals));
}
@@ -406,10 +488,13 @@ template<typename T>
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<T>::type(vals[i]);
return result;
return oiio_py::make_tuple(size, [&](size_t i) {
if constexpr (std::is_same_v<T, half>) {
return py::cast(static_cast<float>(vals[i]));
} else {
return py::cast(vals[i]);
}
});
}
@@ -417,11 +502,9 @@ template<>
inline py::tuple
C_to_tuple(cspan<unsigned char> vals)
{
size_t size = vals.size();
py::tuple result(size);
for (size_t i = 0; i < size; ++i)
result[i] = static_cast<unsigned char>(vals[i]);
return result;
return oiio_py::make_tuple(vals.size(), [&](size_t i) {
return static_cast<unsigned char>(vals[i]);
});
}
@@ -430,11 +513,8 @@ template<>
inline py::tuple
C_to_tuple<TypeDesc>(cspan<TypeDesc> 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<T>::type(vals[0]);
else
if (n == 1 && !type.arraylen) {
if constexpr (std::is_same_v<T, half>) {
return py::cast(static_cast<float>(vals[0]));
} else {
return py::cast(vals[0]);
}
} else {
return C_to_tuple(cspan<T>(vals, n));
}
}
@@ -464,32 +549,36 @@ attribute_typed(T& myobj, string_view name, TypeDesc type, const POBJ& dataobj)
std::vector<int> 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<unsigned int> 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<unsigned char> 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<float> 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<ustring> 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<typename T>
inline void
attribute_onearg(T& myobj, string_view name, const py::object& obj)
{
if (py::isinstance<py::float_>(obj))
myobj.attribute(name, float(obj.template cast<py::float_>()));
else if (py::isinstance<py::int_>(obj))
myobj.attribute(name, int(obj.template cast<py::int_>()));
else if (py::isinstance<py::str>(obj))
myobj.attribute(name, std::string(obj.template cast<py::str>()));
else if (py::isinstance<py::bytes>(obj))
myobj.attribute(name, std::string(obj.template cast<py::bytes>()));
else
if (py::isinstance<py::float_>(obj)) {
myobj.attribute(name, py::cast<float>(obj));
} else if (py::isinstance<py::int_>(obj)) {
myobj.attribute(name, py::cast<int>(obj));
} else if (py::isinstance<py::str>(obj)) {
myobj.attribute(name, oiio_py::str_to_stdstring(obj));
} else if (py::isinstance<py::bytes>(obj)) {
myobj.attribute(name,
oiio_py::bytes_to_stdstring(py::cast<py::bytes>(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<typename C>
inline void
delegate_setitem(C& self, const std::string& key, py::object obj)
{
if (py::isinstance<py::float_>(obj))
self[key] = float(obj.template cast<py::float_>());
else if (py::isinstance<py::int_>(obj))
self[key] = int(obj.template cast<py::int_>());
else if (py::isinstance<py::str>(obj))
self[key] = std::string(obj.template cast<py::str>());
else if (py::isinstance<py::bytes>(obj))
self[key] = std::string(obj.template cast<py::bytes>());
else
if (py::isinstance<py::float_>(obj)) {
self[key] = py::cast<float>(obj);
} else if (py::isinstance<py::int_>(obj)) {
self[key] = py::cast<int>(obj);
} else if (py::isinstance<py::str>(obj)) {
self[key] = oiio_py::str_to_stdstring(obj);
} else if (py::isinstance<py::bytes>(obj)) {
self[key] = oiio_py::bytes_to_stdstring(py::cast<py::bytes>(obj));
} else {
throw std::invalid_argument("Bad type for __setitem__");
}
}
} // namespace PyOpenImageIO
#endif // PYOPENIMAGEIO_PY_OIIO_H
+43 -24
View File
@@ -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<py::bytes>(obj)) {
TypeDesc t = type;
std::string s = obj.cast<py::bytes>();
TypeDesc t = type;
py::bytes b = py::cast<py::bytes>(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_<ParamValue::Interp>(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_<ParamValue>(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<const std::string&, int>())
.def(py::init<const std::string&, float>())
.def(py::init<const std::string&, const std::string&>())
#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_<ParamValueList>(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,
+18 -22
View File
@@ -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_<ROI>(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<int, int, int, int>())
@@ -48,22 +43,22 @@ declare_roi(py::module& m)
.def(py::init<const ROI&>())
// .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);
+89 -13
View File
@@ -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_<TypeDesc::BASETYPE>(m, "BASETYPE")
.value("UNKNOWN", TypeDesc::UNKNOWN)
@@ -61,27 +61,28 @@ declare_typedesc(py::module& m)
.value("BOX", TypeDesc::BOX)
.export_values();
py::class_<TypeDesc>(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<TypeDesc::BASETYPE, int>())
// .def(init<TypeDesc::BASETYPE, TypeDesc::AGGREGATE, int>())
// 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<TypeDesc>& 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("<TypeDesc '" + std::string(t.c_str()) + "'>");
return oiio_py::str("<TypeDesc '" + std::string(t.c_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<py::str, TypeDesc>();
#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;
+7
View File
@@ -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]
@@ -128,6 +128,42 @@ try:
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 :
+3
View File
@@ -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
@@ -35,8 +35,8 @@ try:
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 @@ try:
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 @@ try:
# 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:")
+5
View File
@@ -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) = <TypeDesc 'float'>
@@ -164,6 +164,22 @@ try:
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")]))