14 Commits
Author SHA1 Message Date
Aleksandr Motsjonov 667365f45b 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>
2026-07-09 10:26:04 -07:00
Aleksandr Motsjonov 409621b0f9 feat: Nanobind for python bindings (first steps -- pybind11 still working) (#5084)
First PR on the way to a migration from pybind11 to nanobind.

New env flag introduced `OIIO_PYTHON_BINDINGS_BACKEND` = `pybind11` or
`nanobind` or `both`

> Select which Python binding backend(s) to configure. `both` keeps the
existing pybind11 module and also builds the experimental nanobind
module.

When it is nanobine one is build it is in
`PyOpenImageIONanobindExperimental` target.

```
cmake --fresh -S . -B build \
  -DUSE_CCACHE=OFF \
  -Dfmt_DIR=/opt/homebrew/lib/cmake/fmt \
  -DOpenColorIO_DIR=/opt/homebrew/lib/cmake/OpenColorIO \
  -DOIIO_INTERNALIZE_FMT=ON \
  -DOIIO_PYTHON_BINDINGS_BACKEND=both
```

### Checklist:

<!-- Put an 'x' in the boxes as you complete the checklist items -->

- [x] **I have read the guidelines** on
[contributions](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/CONTRIBUTING.md)
and [code review
procedures](https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/docs/dev/CodeReview.md).
- [x] **I have updated the documentation** if my PR adds features or
changes
  behavior.
- [x] **I am sure that this PR's changes are tested somewhere in the
  testsuite**.
- [x] **I have run and passed the testsuite in CI** *before* submitting
the
  PR, by pushing the changes to my fork and seeing that the automated CI
passed there. (Exceptions: If most tests pass and you can't figure out
why
the remaining ones fail, it's ok to submit the PR and ask for help. Or
if
any failures seem entirely unrelated to your change; sometimes things
break
  on the GitHub runners.)
- [x] **My code follows the prevailing code style of this project** and
I
  fixed any problems reported by the clang-format CI test.
- [x] If I added or modified a public C++ API call, I have also amended
the
corresponding Python bindings. If altering ImageBufAlgo functions, I
also
  exposed the new functionality as oiiotool options.

This code contribution was assisted by Cursor/Composer-2

---------

Signed-off-by: Aleksandr Motsjonov <soswow@gmail.com>
2026-04-29 17:25:27 -07:00
Chad Dombrova e787dc6ef1 feat: Add python stub files (#4692)
Add python stub files for code completion and static analysis.

Fixes
https://github.com/AcademySoftwareFoundation/OpenImageIO/issues/4682

No new test cases, since there is not any change to the behavior. I do
have an idea for a kind of test that might validate the stubs against
the python unit tests, but I don't know if it's worth it.

---------

Signed-off-by: Chad Dombrova <chadrik@gmail.com>
2025-05-13 18:09:01 -07:00
Larry Gritz 8799aedabd build!: Move to Python 3.7 minimum (#4200)
These are build-breaking changes that impose a variety of new minimum
dependencies for the toolchain.

The new minimum Python version supported is now 3.7.

Get rid of 'from future' lines in python scripts in testsuite.

Remove python 2.7 specific ref images from testsuite.

Needless to say, this will not be backported to any release branches,
which guarantee that they will never raise the minimum supported
versions of any dependencies. This is just for future OIIO.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2024-04-05 07:50:46 -07:00
Larry Gritz ef3c02a0df admin: Repo rename -- fix all URL references (#3998)
Signed-off-by: Larry Gritz <lg@larrygritz.com>
2023-09-29 16:27:13 -07:00
Larry Gritz eda748ef0e admin: update licensing (#3903)
Since LG has re-licensed all his old code under Apache-2.0, we can
change the license notice from BSD to Apache-2.0 for all files in which
LG is the only extant author. Also change all files that LG co-authored
with other people to specify "BSD-3-Clause and Apache-2.0".

Some files (mostly test scripts) had no license notice, so in those
cases, we added one.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
2023-07-05 21:32:04 -04:00
Larry Gritz 80374c0128 Python internals: support int8[] metadata and ICCProfile, refactor (#3556)
This patch allows ParamValue, ParamValueList, and ImageSpec to support
attribute/getattribute of metadata consisting of uint8[] arrays, which
is how we store certain binary blobs or raw byte arrays.  We translate
this to Python as a numpy array of type uint8 (dtype='B').

This fixes a limitation where it was not possible to directly retrieve
or set the "ICCProfile" metadata (which is stored as a uint8 array)
from the Python APIs, as you could easily do from the C++ APIs.

In the process, I found similar bits of code in several different
places, in various states of incompleteness. I refactored to make a
single make_pyobject() utility and then used that call in place of
several other redundant pieces of functionality.
2022-09-19 10:56:07 -07:00
Larry Gritz 71c2d3da71 Python: improve ParamValueList and ImageSpec dict interface (#3252)
The Python bindings for ParamValueList and ImageSpec allow a "dict"
style (associative array) interface:

    imagespec["key"] = value
    val = imagespec["key"]

This patch makes some minor improvements:

* Clarify in the docs that like a regular Python dict, the associative
  retrieval will raise a KeyError if the key is not found.

* Add `__contains__` python method as a synonym for contains(), thus
  implementing `key in dict`.

* Add `__delitem__` python method as a synonym for erase(), thus
  implementing `del dict[key]`.

* Add an `ImageSpec.get()` method that is a synonym for getattribute
  (which retrieves the item or returns None or a default if not
  found), mirroring the behavior of Python's `dict.get()`.
2022-01-02 10:48:34 -08:00
Larry Gritz 7357ca1bdc Extend Python ParamValueList to take lists and numpy arrays (#2437)
Previously the data passed had to be tuples.

Also add ParamValueList.attribute() variety that takes an nvalues
parameter.
2019-12-30 11:52:35 -08:00
Larry Gritz 64bad0cc6f Python: add binding for missing ParamValue constructors (#2417)
We previously exposed the PV constructors from just a plain int, float,
or string, but there wasn't an easy way to construct from arbitrary data
like there is in C++.
2019-12-03 10:25:28 -08:00
Larry Gritz 39e3c32e39 Python3 safety (#2358)
With this additional set of changes, we now have a 100% clean bill of
health from pylint --py3k.
2019-10-07 22:54:34 -07:00
Larry Gritz b5bb48746b oiiotool --metamerge gives output merged metdata of all inputs (#2311)
Most oiiotool commands copy metadata to their result from their first
input. Using `--metamerge` causes >2 arity image ops to try to merge
metadata from all of its input images (though earlier arguments have
precedence in the case of name clashes).

Also make IBAprep utility have a bitfield flag for the same purpose,
though it's not the mechanism we use for the oiiotool calls.

Along the way, added `ParamValueList::merge()` merges two PVLs together.

Also fixed a couple typos and updated the oiiotool --help message to point
to the new online docs.
2019-08-09 11:26:45 -07:00
Larry Gritz 7b3cac4755 Fix test_paramlist output to be uniform across platforms / python versions (#2222)
Default was printing different number of digits of precision on
different python versions. So be specific about the format for floats
to get uniform test output on all platforms.
2019-04-27 15:11:41 -07:00
Larry Gritz d2ac60011f Enhancements to the syntax for setting and retrieving attributes. (#2204)
Lots of important classes have "attributes", which are used as option
settings and the like, basically any kind of named/typed data that
objects carry around. The prototypical way we set and retrieve them
is:

    obj.attribute (name, typedesc, &data);
    found_ok = obj.getattribute (name, typedesc, &data);

This is very general, but it's kind of a wonky syntax. So this patch
introduces an additional synax that is super handy and aesthetically
pleasing:

    obj[name] = data;     // set, works for nearly any data!

    data = obj[name]->get<type>();   // any type
    mystring = obj[name];            // turns any data into a string

This magic is accomplished with a helper template called an
AttrDelegate, which we define in attrdelegate.h. This is pretty handy
and we anticipate using it more widely over time, as we add this syntax
to additional classes.

The first class we rig up in this way is ParamValueList, which
necessitates adding attribute() and getattribute() methods to begin
with. It's a natural fit, and you can get a feel for how it looks when
used by examining the new unit tests in paramlist_test.cpp.

Also added to ImageSpec, for which I think this will be a handy new
way to set and retrieve metadata.

There's a bunch more cleanup that should be fairly straightforward to
understand.

Also, we expose two new functions (pieces of which were used internally
before, though we have now made them public and refactored them):

    std::string tostring (typedesc, void* data,
                          const tostring_foratting& fmt={})

        Turns any data (described by the typedesc) into a string,
        with optional fine-grained control over the formatting.

    bool convert_type (TypeDesc srctype, const void* src,
                       TypeDesc dsttype, void* dst)

        Copies with data conversion.
2019-04-22 17:33:16 -07:00