Skip to content

Commit 63f396a

Browse files
Glowieszachlewis
authored andcommitted
feat(python): _repr_png_ implementation for ImageBuf (AcademySoftwareFoundation#4753)
Fixes AcademySoftwareFoundation#4680 Adds a `_repr_png_` implementation to the Python bindings of ImageBuf. This allows ImageBuf objects to be displayed in Jupyter notebooks, as requested in AcademySoftwareFoundation#4680 Uses the IOVecOutput IOProxy to write the ImageBuf as a png into memory. Then, reads and returns that as a python bytes object. ![image](https://github.com/user-attachments/assets/ba1a5bd6-d50b-41ce-a00e-6746b906e763) --------- Signed-off-by: glowies <[email protected]>
1 parent 504365a commit 63f396a

File tree

8 files changed

+86
-1
lines changed

8 files changed

+86
-1
lines changed
401 KB
Loading

src/doc/pythonbindings.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4260,3 +4260,39 @@ add an alpha channel that is 1 everywhere**
42604260
out.open ("multipart.exr", specs[s], "AppendSubimage")
42614261
bufs[s].write (out)
42624262
out.close ()
4263+
4264+
4265+
4266+
|
4267+
4268+
**Running OpenImageIO in Jupyter Notebooks and displaying ImageBuf**
4269+
4270+
4271+
Like any other Python package, OpenImageIO can be used in `Jupyter notebooks <https://jupyter.org/install>`_.
4272+
The ImageBuf objects support getting displayed inline within notebooks.
4273+
4274+
.. image:: figures/imagebuf-notebook-demo.png
4275+
4276+
.. warning::
4277+
4278+
Currently, ImageBuf objects get displayed as **uint8 PNGs** inside of notebooks.
4279+
ImageBuf objects that store images with higher bit depths get dithered to account for this.
4280+
Keep in mind that directly saving the inline image to disk will not preserve the original image within the ImageBuf.
4281+
4282+
4283+
Running a Local Jupyter Notebook:
4284+
4285+
If you want to run a local Jupyter notebook with OpenImageIO, you can do so from within the Python environment in which you have installed OpenImageIO.
4286+
4287+
.. code-block:: bash
4288+
4289+
pip install jupyterlab
4290+
jupyter lab
4291+
4292+
Alternatively, if you prefer using `uv <https://github.com/astral-sh/uv>`_, you can run the following command:
4293+
4294+
.. code-block:: bash
4295+
4296+
uv run --with jupyter jupyter lab
4297+
4298+

src/python/py_imagebuf.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <memory>
88

9+
#include <OpenImageIO/filesystem.h>
910
#include <OpenImageIO/platform.h>
1011

1112

@@ -250,6 +251,35 @@ ImageBuf_set_write_format(ImageBuf& self, const py::object& py_channelformats)
250251

251252

252253

254+
py::bytes
255+
ImageBuf_repr_png(const ImageBuf& self)
256+
{
257+
ImageSpec original_spec = self.spec();
258+
259+
if (original_spec.width < 1 || original_spec.height < 1) {
260+
return py::bytes();
261+
}
262+
263+
// Alter the spec to make sure it dithers when outputting to 8 bit PNG
264+
ImageSpec altered_spec = original_spec;
265+
altered_spec.attribute("oiio:dither", 1);
266+
267+
std::vector<unsigned char> file_buffer; // bytes will go here
268+
Filesystem::IOVecOutput file_vec(file_buffer); // I/O proxy object
269+
270+
std::unique_ptr<ImageOutput> out = ImageOutput::create("temp.png",
271+
&file_vec);
272+
out->open("temp.png", altered_spec);
273+
self.write(out.get());
274+
out->close();
275+
276+
// Cast to const char* and return as python bytes
277+
const char* char_ptr = reinterpret_cast<const char*>(file_buffer.data());
278+
return py::bytes(char_ptr, file_buffer.size());
279+
}
280+
281+
282+
253283
void
254284
declare_imagebuf(py::module& m)
255285
{
@@ -491,6 +521,7 @@ declare_imagebuf(py::module& m)
491521
.def(
492522
"deepdata", [](ImageBuf& self) { return *self.deepdata(); },
493523
py::return_value_policy::reference_internal)
524+
.def("_repr_png_", &ImageBuf_repr_png)
494525

495526
// FIXME -- do we want to provide pixel iterators?
496527
;
115 Bytes
Loading
858 Bytes
Loading

testsuite/python-imagebuf/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
outputs = [ "out.tif", "outtuple.tif",
1313
"outarray.tif", "outarrayB.tif", "outarrayH.tif",
1414
"perchan.exr", "multipart.exr",
15-
"out.txt" ]
15+
"out.txt", "valid_repr_png.png", "invalid_repr_png.png" ]
1616

69.1 KB
Binary file not shown.

testsuite/python-imagebuf/src/test_imagebuf.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ def write (image, filename: str, format=oiio.UNKNOWN) :
5959

6060

6161

62+
def test_repr_png () :
63+
# Test a valid 16 bit exr
64+
b = oiio.ImageBuf("src/AllHalfValues.exr")
65+
png = b._repr_png_()
66+
67+
with open("valid_repr_png.png", "wb") as f:
68+
f.write(png)
69+
70+
# Test an invalid image with null dimensions
71+
# create ImageBuf with x dimension as 0
72+
b = oiio.ImageBuf(oiio.ImageSpec(0,2,4,"float"))
73+
png = b._repr_png_()
74+
with open("invalid_repr_png.png", "wb") as f:
75+
f.write(png)
76+
77+
78+
6279
def test_perchannel_formats () :
6380
# Test writing per-channel formats with an ImageBuf
6481
b = oiio.ImageBuf(oiio.ImageSpec(2,2,4,"float"))
@@ -298,6 +315,7 @@ def test_copy_metadata() :
298315
test_multiimage ()
299316
test_uninitialized ()
300317
test_copy_metadata ()
318+
test_repr_png ()
301319

302320
print ("\nDone.")
303321
except Exception as detail:

0 commit comments

Comments
 (0)