From 37d5164c5a509c1d4468314e3f5f9f2a3e39d6a6 Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 18:39:22 +1000 Subject: [PATCH 1/6] test(python): add python-oiio testsuite for module globals Signed-off-by: Aleksandr Motsjonov --- src/cmake/testing.cmake | 1 + testsuite/python-oiio/ref/out.txt | 68 +++++++++++ testsuite/python-oiio/run.py | 8 ++ testsuite/python-oiio/src/test_oiio.py | 153 +++++++++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 testsuite/python-oiio/ref/out.txt create mode 100644 testsuite/python-oiio/run.py create mode 100644 testsuite/python-oiio/src/test_oiio.py diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 93aa7345e6..554d9e8e89 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -274,6 +274,7 @@ macro (oiio_add_all_tests) python-imagecache python-imageoutput python-imagespec + python-oiio python-paramlist python-roi python-texturesys diff --git a/testsuite/python-oiio/ref/out.txt b/testsuite/python-oiio/ref/out.txt new file mode 100644 index 0000000000..0a170e0985 --- /dev/null +++ b/testsuite/python-oiio/ref/out.txt @@ -0,0 +1,68 @@ +Testing version constants: + __version__ looks valid: True + VERSION_STRING looks valid: True + __version__ == VERSION_STRING: True + VERSION == openimageio_version: True + VERSION matches major/minor/patch encoding: True + VERSION is positive: True + VERSION_MAJOR >= 1: True + VERSION_MINOR >= 0: True + VERSION_PATCH >= 0: True + INTRO_STRING starts with OpenImageIO: True + AutoStride = -9223372036854775808 + +Testing is_imageio_format_name: + tiff = True + openexr = True + txff = False + +Testing equivalent_colorspace: + sRGB vs sRGB = True + linear vs Linear = True + sRGB vs linear = False + +Testing set_colorspace module helpers: + after set_colorspace(sRGB): sRGB + after set_colorspace(empty): + after set_colorspace_rec709_gamma(2.2): g22_rec709_scene + +Testing global attribute() one-arg: + plugin_searchpath str: path/A:path/B + threads int: 6 + debug int: 0 + font_searchpath str: /fonts + font_searchpath bytes type: True + font_searchpath bytes: b'/fonts' + +Testing global attribute() typed: + threads typed get: 4 + version typed looks valid: True + version typed == __version__: True + missing typed get: None + +Testing global get_*_attribute defaults: + get_int missing: 99 + get_float missing: 1.5 + get_string missing: default + get_bytes missing: b'default' + get_int plugin_searchpath (wrong type) default: 77 + get_float on int attribute returns default: True + +Testing getattribute() with TypeUnknown: + getattribute(missing, TypeUnknown): None + +Testing getattribute() one-arg (pybind default): + one-arg: TypeError + +Testing attribute() type error: + attribute bad type: TypeError + +Testing geterror: + geterror before error: '' + open returned: None + geterror(clear=False) nonempty: True + geterror persists: True + geterror(clear=True) same as prior: True + geterror after clear: '' + +Done. diff --git a/testsuite/python-oiio/run.py b/testsuite/python-oiio/run.py new file mode 100644 index 0000000000..b3cbb7ea5c --- /dev/null +++ b/testsuite/python-oiio/run.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + + +command += pythonbin + " src/test_oiio.py > out.txt" diff --git a/testsuite/python-oiio/src/test_oiio.py b/testsuite/python-oiio/src/test_oiio.py new file mode 100644 index 0000000000..29b189f7ae --- /dev/null +++ b/testsuite/python-oiio/src/test_oiio.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +from __future__ import annotations + +import OpenImageIO as oiio + + +def version_string_looks_valid(version: str) -> bool: + # Loose check: non-empty, dotted, contains digits (e.g. "3.2.0.2dev"). + return (len(version) > 0 and "." in version + and any(ch.isdigit() for ch in version)) + + +def version_code(major: int, minor: int, patch: int) -> int: + return major * 10000 + minor * 100 + patch + + + + + +###################################################################### +# main test starts here + +try: + print ("Testing version constants:") + print (" __version__ looks valid:", + version_string_looks_valid (oiio.__version__)) + print (" VERSION_STRING looks valid:", + version_string_looks_valid (oiio.VERSION_STRING)) + print (" __version__ == VERSION_STRING:", + oiio.__version__ == oiio.VERSION_STRING) + print (" VERSION == openimageio_version:", + oiio.VERSION == oiio.openimageio_version) + print (" VERSION matches major/minor/patch encoding:", + oiio.VERSION == version_code (oiio.VERSION_MAJOR, + oiio.VERSION_MINOR, + oiio.VERSION_PATCH)) + print (" VERSION is positive:", oiio.VERSION > 0) + print (" VERSION_MAJOR >= 1:", oiio.VERSION_MAJOR >= 1) + print (" VERSION_MINOR >= 0:", oiio.VERSION_MINOR >= 0) + print (" VERSION_PATCH >= 0:", oiio.VERSION_PATCH >= 0) + print (" INTRO_STRING starts with OpenImageIO:", + oiio.INTRO_STRING.startswith ("OpenImageIO")) + print (" AutoStride =", oiio.AutoStride) + print ("") + + print ("Testing is_imageio_format_name:") + print (" tiff =", oiio.is_imageio_format_name ('tiff')) + print (" openexr =", oiio.is_imageio_format_name ('openexr')) + print (" txff =", oiio.is_imageio_format_name ('txff')) + print ("") + + print ("Testing equivalent_colorspace:") + print (" sRGB vs sRGB =", oiio.equivalent_colorspace ("sRGB", "sRGB")) + print (" linear vs Linear =", oiio.equivalent_colorspace ("linear", "Linear")) + print (" sRGB vs linear =", oiio.equivalent_colorspace ("sRGB", "linear")) + print ("") + + print ("Testing set_colorspace module helpers:") + spec = oiio.ImageSpec() + oiio.set_colorspace (spec, "sRGB") + print (" after set_colorspace(sRGB):", + spec.get_string_attribute ("oiio:ColorSpace")) + oiio.set_colorspace (spec, "") + print (" after set_colorspace(empty):", + spec.get_string_attribute ("oiio:ColorSpace")) + oiio.set_colorspace_rec709_gamma (spec, 2.2) + print (" after set_colorspace_rec709_gamma(2.2):", + spec.get_string_attribute ("oiio:ColorSpace")) + print ("") + + print ("Testing global attribute() one-arg:") + oiio.attribute ("plugin_searchpath", "path/A:path/B") + print (" plugin_searchpath str:", + oiio.get_string_attribute ("plugin_searchpath", "")) + oiio.attribute ("threads", 6) + print (" threads int:", oiio.get_int_attribute ("threads", 0)) + oiio.attribute ("debug", 0) + print (" debug int:", oiio.get_int_attribute ("debug", -1)) + oiio.attribute ("font_searchpath", b"/fonts") + print (" font_searchpath str:", + oiio.get_string_attribute ("font_searchpath", "")) + gb = oiio.get_bytes_attribute ("font_searchpath", b"") + print (" font_searchpath bytes type:", isinstance (gb, bytes)) + print (" font_searchpath bytes:", gb) + print ("") + + print ("Testing global attribute() typed:") + oiio.attribute ("threads", oiio.TypeInt, 4) + print (" threads typed get:", oiio.getattribute ("threads", oiio.TypeInt)) + version_typed = oiio.getattribute ("version", oiio.TypeString) + print (" version typed looks valid:", + version_string_looks_valid (version_typed)) + print (" version typed == __version__:", version_typed == oiio.__version__) + print (" missing typed get:", + oiio.getattribute ("no_such_global_attr", oiio.TypeString)) + print ("") + + print ("Testing global get_*_attribute defaults:") + print (" get_int missing:", oiio.get_int_attribute ("not_a_real_attr", 99)) + print (" get_float missing:", + oiio.get_float_attribute ("not_a_real_attr", 1.5)) + print (" get_string missing:", + oiio.get_string_attribute ("not_a_real_attr", "default")) + print (" get_bytes missing:", + oiio.get_bytes_attribute ("not_a_real_attr", b"default")) + print (" get_int plugin_searchpath (wrong type) default:", + oiio.get_int_attribute ("plugin_searchpath", 77)) + print (" get_float on int attribute returns default:", + oiio.get_float_attribute ("threads", -1.0) == -1.0) + print ("") + + print ("Testing getattribute() with TypeUnknown:") + print (" getattribute(missing, TypeUnknown):", + oiio.getattribute ("not_a_real_attr", oiio.TypeUnknown)) + print ("") + + print ("Testing getattribute() one-arg (pybind default):") + try: + oiio.getattribute ("not_a_real_attr") + print (" one-arg: no exception") + except TypeError: + print (" one-arg: TypeError") + print ("") + + print ("Testing attribute() type error:") + try: + oiio.attribute ("threads", []) + print (" attribute bad type: no exception") + except TypeError: + print (" attribute bad type: TypeError") + print ("") + + print ("Testing geterror:") + print (" geterror before error:", repr (oiio.geterror())) + inp = oiio.ImageInput.open ("no_such_file_for_oiio_test.tif") + print (" open returned:", inp) + err_noclear = oiio.geterror (clear=False) + print (" geterror(clear=False) nonempty:", len (err_noclear) > 0) + err_noclear2 = oiio.geterror (clear=False) + print (" geterror persists:", err_noclear == err_noclear2) + err_clear = oiio.geterror (clear=True) + print (" geterror(clear=True) same as prior:", err_clear == err_noclear) + print (" geterror after clear:", repr (oiio.geterror())) + print ("") + + print ("Done.") +except Exception as detail: + print ("Unknown exception:", detail) From fec98614e38abb77f549cddf9791a6a1229337ab Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 18:50:13 +1000 Subject: [PATCH 2/6] test(python): expand TextureSystem tests; fix imagespec GIL bug Signed-off-by: Aleksandr Motsjonov --- src/python/py_texturesys.cpp | 14 +- .../python-texturesys/ref/out-windows.txt | 78 ++++- testsuite/python-texturesys/ref/out.txt | 78 ++++- .../python-texturesys/src/test_texture_sys.py | 281 +++++++++++++----- 4 files changed, 361 insertions(+), 90 deletions(-) diff --git a/src/python/py_texturesys.cpp b/src/python/py_texturesys.cpp index d80b48bbe2..e24b1f4a46 100644 --- a/src/python/py_texturesys.cpp +++ b/src/python/py_texturesys.cpp @@ -286,13 +286,17 @@ declare_texturesystem(py::module& m) "imagespec", [](TextureSystemWrap& ts, const std::string& filename, int subimage) -> py::object { - py::gil_scoped_release gil; - const ImageSpec* spec - = ts.m_texsys->imagespec(ustring(filename), subimage); - if (!spec) { + const ImageSpec* spec_ptr = nullptr; + { + py::gil_scoped_release gil; + spec_ptr = ts.m_texsys->imagespec(ustring(filename), + subimage); + } + if (!spec_ptr) { return py::none(); } - return py::object(py::cast(*spec)); + ImageSpec spec = *spec_ptr; + return py::cast(spec); }, "filename"_a, "subimage"_a = 0) .def( diff --git a/testsuite/python-texturesys/ref/out-windows.txt b/testsuite/python-texturesys/ref/out-windows.txt index d80ff6b906..61bc13ee10 100644 --- a/testsuite/python-texturesys/ref/out-windows.txt +++ b/testsuite/python-texturesys/ref/out-windows.txt @@ -1,9 +1,27 @@ +Testing Wrap/MipMode/InterpMode enums: + Wrap.Periodic defined: True + MipMode.Trilinear defined: True + InterpMode.Bilinear defined: True + +Testing TextureOpt fields: + firstchannel: 1 + subimagename: density + swrap is Clamp: True + mipmode is Trilinear: True + interpmode is Bicubic: True + anisotropic: 8 + conservative_filter: True + missingcolor: (1.0, 2.0, 3.0, 4.0) + missingcolor cleared: True + checker middle, top mip, channels: 1 = (0.5,) checker middle, top mip, channels: 2 = (0.5, 0.5) checker middle, top mip, channels: 3 = (0.5, 0.5, 0.5) checker middle, mip tail, channels: 1 = (0.5,) +texture nchannels < 1 returns empty tuple: () + missingcolor channels: 1 = (1.0,) missingcolor channels: 2 = (1.0, 2.0) missingcolor channels: 3 = (1.0, 2.0, 3.0) @@ -11,15 +29,57 @@ missingcolor channels: 4 = (1.0, 2.0, 3.0, 4.0) default-missingcolor = (0.0, 0.0, 0.0, 0.0) +Testing texture3d: + center is gray RGB: True + +Testing environment: + returns 3 channels: True + center is gray RGB: True + top mip pixel differences when streaming = 0 -udim file..tx -> 2x4 ['.\\file.1001.tx', '.\\file.1002.tx', '.\\file.1011.tx', '.\\file.1012.tx', '', '', '', '.\\file.1032.tx'] -getattributetype stat:image_size int64 -getattributetype total_files int -getattributetype max_memory_MB float -getattributetype worldtocommon matrix -untyped getattribute stat:image_size 1048575 -untyped getattribute total_files 3 -untyped getattribute max_memory_MB 1024.0 -untyped getattribute worldtocommon (1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0) +Testing metadata helpers: + resolve_filename ends with checker.tx: True + imagespec width>0: True + imagespec missing is None: True + is_udim checker: False + is_udim pattern: True + +Testing udim: + inventory tiles: 2 x 4 + inventory nonempty files: 5 + resolve_udim nonempty: True + +Testing attribute(): + searchpath contains textures: True + max_memory_MB typed round-trip: True + +Testing getattribute() and getattributetype(): + getattributetype stat:image_size: int64 + getattributetype total_files: int + getattributetype max_memory_MB: float + getattributetype worldtocommon: matrix + stat:image_size is int: True + stat:image_size positive: True + total_files is int: True + max_memory_MB is float: True + worldtocommon is 16-tuple: True + +Testing cache lifecycle: + close/invalidate ok: True + +Testing TextureSystem.destroy: + destroy shared=False ok: True + +Testing has_error/geterror: + has_error before: False + has_error after bad file: True + geterror persists: True + geterror clear returns message: True + has_error after clear: False + +Testing getstats/reset_stats: + getstats nonempty: True + reset_stats ok: True + Done. diff --git a/testsuite/python-texturesys/ref/out.txt b/testsuite/python-texturesys/ref/out.txt index 7eb4b1df01..61bc13ee10 100644 --- a/testsuite/python-texturesys/ref/out.txt +++ b/testsuite/python-texturesys/ref/out.txt @@ -1,9 +1,27 @@ +Testing Wrap/MipMode/InterpMode enums: + Wrap.Periodic defined: True + MipMode.Trilinear defined: True + InterpMode.Bilinear defined: True + +Testing TextureOpt fields: + firstchannel: 1 + subimagename: density + swrap is Clamp: True + mipmode is Trilinear: True + interpmode is Bicubic: True + anisotropic: 8 + conservative_filter: True + missingcolor: (1.0, 2.0, 3.0, 4.0) + missingcolor cleared: True + checker middle, top mip, channels: 1 = (0.5,) checker middle, top mip, channels: 2 = (0.5, 0.5) checker middle, top mip, channels: 3 = (0.5, 0.5, 0.5) checker middle, mip tail, channels: 1 = (0.5,) +texture nchannels < 1 returns empty tuple: () + missingcolor channels: 1 = (1.0,) missingcolor channels: 2 = (1.0, 2.0) missingcolor channels: 3 = (1.0, 2.0, 3.0) @@ -11,15 +29,57 @@ missingcolor channels: 4 = (1.0, 2.0, 3.0, 4.0) default-missingcolor = (0.0, 0.0, 0.0, 0.0) +Testing texture3d: + center is gray RGB: True + +Testing environment: + returns 3 channels: True + center is gray RGB: True + top mip pixel differences when streaming = 0 -udim file..tx -> 2x4 ['./file.1001.tx', './file.1002.tx', './file.1011.tx', './file.1012.tx', '', '', '', './file.1032.tx'] -getattributetype stat:image_size int64 -getattributetype total_files int -getattributetype max_memory_MB float -getattributetype worldtocommon matrix -untyped getattribute stat:image_size 1048575 -untyped getattribute total_files 3 -untyped getattribute max_memory_MB 1024.0 -untyped getattribute worldtocommon (1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0) +Testing metadata helpers: + resolve_filename ends with checker.tx: True + imagespec width>0: True + imagespec missing is None: True + is_udim checker: False + is_udim pattern: True + +Testing udim: + inventory tiles: 2 x 4 + inventory nonempty files: 5 + resolve_udim nonempty: True + +Testing attribute(): + searchpath contains textures: True + max_memory_MB typed round-trip: True + +Testing getattribute() and getattributetype(): + getattributetype stat:image_size: int64 + getattributetype total_files: int + getattributetype max_memory_MB: float + getattributetype worldtocommon: matrix + stat:image_size is int: True + stat:image_size positive: True + total_files is int: True + max_memory_MB is float: True + worldtocommon is 16-tuple: True + +Testing cache lifecycle: + close/invalidate ok: True + +Testing TextureSystem.destroy: + destroy shared=False ok: True + +Testing has_error/geterror: + has_error before: False + has_error after bad file: True + geterror persists: True + geterror clear returns message: True + has_error after clear: False + +Testing getstats/reset_stats: + getstats nonempty: True + reset_stats ok: True + Done. diff --git a/testsuite/python-texturesys/src/test_texture_sys.py b/testsuite/python-texturesys/src/test_texture_sys.py index 9516b446d6..4bde039d0a 100644 --- a/testsuite/python-texturesys/src/test_texture_sys.py +++ b/testsuite/python-texturesys/src/test_texture_sys.py @@ -7,75 +7,222 @@ from __future__ import annotations import os + import numpy import OpenImageIO as oiio -checker = os.path.abspath("../common/textures/checker.tx") -texture_sys = oiio.TextureSystem() -texture_opt = oiio.TextureOpt() - -texture_opt.swrap = oiio.Wrap.Periodic -texture_opt.twrap = oiio.Wrap.Periodic - - -# Test getattribute - - -print ("checker middle, top mip, channels: 1 =", texture_sys.texture(checker, texture_opt, 0.5, 0.5, 0, 0, 0, 0, 1)) -print ("checker middle, top mip, channels: 2 =", texture_sys.texture(checker, texture_opt, 0.5, 0.5, 0, 0, 0, 0, 2)) -print ("checker middle, top mip, channels: 3 =", texture_sys.texture(checker, texture_opt, 0.5, 0.5, 0, 0, 0, 0, 3)) -print ("") - -print ("checker middle, mip tail, channels: 1 =", texture_sys.texture(checker, texture_opt, 0.5, 0.5, 1/1024.0, 1/1024.0, 1/1024.0, 1/1024.0, 1)) -print ("") - -texture_opt.missingcolor = (1.0, 2.0, 3.0, 4.0) -print ("missingcolor channels: 1 =", texture_sys.texture("", texture_opt, 0, 0, 0, 0, 0, 0, 1)) -print ("missingcolor channels: 2 =", texture_sys.texture("", texture_opt, 0, 0, 0, 0, 0, 0, 2)) -print ("missingcolor channels: 3 =", texture_sys.texture("", texture_opt, 0, 0, 0, 0, 0, 0, 3)) -print ("missingcolor channels: 4 =", texture_sys.texture("", texture_opt, 0, 0, 0, 0, 0, 0, 4)) -print ("") - -# the stubs don't allow None for this type -texture_opt.missingcolor = None # type: ignore[assignment] -print ("default-missingcolor =", texture_sys.texture("", texture_opt, 0, 0, 0, 0, 0, 0, 4)) -print ("") - -# Test if fetching the textures biggest mip via the texture system, results in the same image -texture_opt.interpmode = oiio.InterpMode.Bilinear -checker_buf = oiio.ImageBuf(checker) -render_buf = oiio.ImageBuf(checker_buf.spec()) - -image_pixels = [ - texture_sys.texture(checker, texture_opt, (x+0.5)/512.0, (y+0.5)/512.0, 0, 0, 0, 0, 3) - for y in range(512) - for x in range(512) -] -render_buf.set_pixels(render_buf.roi, numpy.array(image_pixels)) -diff = oiio.ImageBufAlgo.compare(checker_buf, render_buf, 0, 0) - -print("top mip pixel differences when streaming =", diff.nfail) - -print ("") - -# Test udim -udname = 'file..tx' -(utiles, vtiles, tilenames) = texture_sys.inventory_udim(udname) -print("udim {} -> {}x{} {}".format(udname, utiles, vtiles, tilenames)) - - -# Test getattribute() and getattributetype() -print ("getattributetype stat:image_size", texture_sys.getattributetype("stat:image_size")) -print ("getattributetype total_files", texture_sys.getattributetype("total_files")) -print ("getattributetype max_memory_MB", texture_sys.getattributetype("max_memory_MB")) -print ("getattributetype worldtocommon", texture_sys.getattributetype("worldtocommon")) - -# Test getattribute(name) with the type inferred from the attribute -print ("untyped getattribute stat:image_size", texture_sys.getattribute("stat:image_size")) -print ("untyped getattribute total_files", texture_sys.getattribute("total_files")) -print ("untyped getattribute max_memory_MB", texture_sys.getattribute("max_memory_MB")) -print ("untyped getattribute worldtocommon", texture_sys.getattribute("worldtocommon")) - -print("Done.") +checker = os.path.abspath ("../common/textures/checker.tx") +grid = os.path.abspath ("../common/textures/grid.tx") +udname = "file..tx" + + + + + +###################################################################### +# main test starts here + +try: + print ("Testing Wrap/MipMode/InterpMode enums:") + print (" Wrap.Periodic defined:", hasattr (oiio.Wrap, "Periodic")) + print (" MipMode.Trilinear defined:", hasattr (oiio.MipMode, "Trilinear")) + print (" InterpMode.Bilinear defined:", hasattr (oiio.InterpMode, "Bilinear")) + print ("") + + print ("Testing TextureOpt fields:") + field_opt = oiio.TextureOpt() + field_opt.firstchannel = 1 + field_opt.subimage = 0 + field_opt.subimagename = "density" + field_opt.swrap = oiio.Wrap.Clamp + field_opt.twrap = oiio.Wrap.Mirror + field_opt.rwrap = oiio.Wrap.Black + field_opt.mipmode = oiio.MipMode.Trilinear + field_opt.interpmode = oiio.InterpMode.Bicubic + field_opt.anisotropic = 8 + field_opt.conservative_filter = True + field_opt.sblur = 0.5 + field_opt.tblur = 0.25 + field_opt.swidth = 1.0 + field_opt.twidth = 2.0 + field_opt.fill = 0.75 + field_opt.rwidth = 3.0 + field_opt.rnd = 42 + field_opt.missingcolor = (1.0, 2.0, 3.0, 4.0) + print (" firstchannel:", field_opt.firstchannel) + print (" subimagename:", field_opt.subimagename) + print (" swrap is Clamp:", field_opt.swrap == oiio.Wrap.Clamp) + print (" mipmode is Trilinear:", field_opt.mipmode == oiio.MipMode.Trilinear) + print (" interpmode is Bicubic:", field_opt.interpmode == oiio.InterpMode.Bicubic) + print (" anisotropic:", field_opt.anisotropic) + print (" conservative_filter:", field_opt.conservative_filter) + print (" missingcolor:", field_opt.missingcolor) + field_opt.missingcolor = None # type: ignore[assignment] + print (" missingcolor cleared:", field_opt.missingcolor == ()) + print ("") + + texture_sys = oiio.TextureSystem() + texture_opt = oiio.TextureOpt() + texture_opt.swrap = oiio.Wrap.Periodic + texture_opt.twrap = oiio.Wrap.Periodic + texture_opt.interpmode = oiio.InterpMode.Bilinear + + print ("checker middle, top mip, channels: 1 =", + texture_sys.texture (checker, texture_opt, 0.5, 0.5, 0, 0, 0, 0, 1)) + print ("checker middle, top mip, channels: 2 =", + texture_sys.texture (checker, texture_opt, 0.5, 0.5, 0, 0, 0, 0, 2)) + print ("checker middle, top mip, channels: 3 =", + texture_sys.texture (checker, texture_opt, 0.5, 0.5, 0, 0, 0, 0, 3)) + print ("") + + print ("checker middle, mip tail, channels: 1 =", + texture_sys.texture (checker, texture_opt, 0.5, 0.5, + 1/1024.0, 1/1024.0, 1/1024.0, 1/1024.0, 1)) + print ("") + + print ("texture nchannels < 1 returns empty tuple:", + texture_sys.texture (checker, texture_opt, 0.5, 0.5, 0, 0, 0, 0, 0)) + print ("") + + texture_opt.missingcolor = (1.0, 2.0, 3.0, 4.0) + print ("missingcolor channels: 1 =", + texture_sys.texture ("", texture_opt, 0, 0, 0, 0, 0, 0, 1)) + print ("missingcolor channels: 2 =", + texture_sys.texture ("", texture_opt, 0, 0, 0, 0, 0, 0, 2)) + print ("missingcolor channels: 3 =", + texture_sys.texture ("", texture_opt, 0, 0, 0, 0, 0, 0, 3)) + print ("missingcolor channels: 4 =", + texture_sys.texture ("", texture_opt, 0, 0, 0, 0, 0, 0, 4)) + print ("") + + texture_opt.missingcolor = None # type: ignore[assignment] + print ("default-missingcolor =", + texture_sys.texture ("", texture_opt, 0, 0, 0, 0, 0, 0, 4)) + print ("") + + print ("Testing texture3d:") + tex3d = texture_sys.texture3d (checker, texture_opt, + (0.5, 0.5, 0.5), + (0.001, 0.0, 0.0), + (0.0, 0.001, 0.0), + (0.0, 0.0, 0.001), + 3) + print (" center is gray RGB:", tex3d == (0.5, 0.5, 0.5)) + print ("") + + print ("Testing environment:") + env = texture_sys.environment (checker, texture_opt, + (0.0, 0.0, 1.0), + (0.001, 0.0, 0.0), + (0.0, 0.001, 0.0), + 3) + print (" returns 3 channels:", len (env) == 3) + print (" center is gray RGB:", env == (0.5, 0.5, 0.5)) + print ("") + + # Stream the top mip through texture() and compare to ImageBuf. + checker_buf = oiio.ImageBuf (checker) + render_buf = oiio.ImageBuf (checker_buf.spec()) + image_pixels = [ + texture_sys.texture (checker, texture_opt, + (x + 0.5) / 512.0, (y + 0.5) / 512.0, + 0, 0, 0, 0, 3) + for y in range (512) + for x in range (512) + ] + render_buf.set_pixels (render_buf.roi, numpy.array (image_pixels)) + diff = oiio.ImageBufAlgo.compare (checker_buf, render_buf, 0, 0) + print ("top mip pixel differences when streaming =", diff.nfail) + print ("") + + print ("Testing metadata helpers:") + resolved = texture_sys.resolve_filename (checker) + print (" resolve_filename ends with checker.tx:", + resolved.endswith ("checker.tx")) + spec = texture_sys.imagespec (checker) + print (" imagespec width>0:", spec is not None and spec.width > 0) + print (" imagespec missing is None:", + texture_sys.imagespec ("no_such_texture_file.tx") is None) + print (" is_udim checker:", texture_sys.is_udim (checker)) + print (" is_udim pattern:", texture_sys.is_udim (udname)) + print ("") + + print ("Testing udim:") + (utiles, vtiles, tilenames) = texture_sys.inventory_udim (udname) + print (" inventory tiles:", utiles, "x", vtiles) + print (" inventory nonempty files:", sum (1 for t in tilenames if t)) + print (" resolve_udim nonempty:", + len (texture_sys.resolve_udim (udname, 0.25, 0.25)) > 0) + print ("") + + print ("Testing attribute():") + texture_sys.attribute ("searchpath", "../common/textures") + searchpath = texture_sys.getattribute ("searchpath", oiio.TypeString) + print (" searchpath contains textures:", + isinstance (searchpath, str) and "textures" in searchpath) + texture_sys.attribute ("max_memory_MB", oiio.TypeFloat, 512.0) + print (" max_memory_MB typed round-trip:", + texture_sys.getattribute ("max_memory_MB", oiio.TypeFloat) == 512.0) + print ("") + + print ("Testing getattribute() and getattributetype():") + print (" getattributetype stat:image_size:", + texture_sys.getattributetype ("stat:image_size")) + print (" getattributetype total_files:", + texture_sys.getattributetype ("total_files")) + print (" getattributetype max_memory_MB:", + texture_sys.getattributetype ("max_memory_MB")) + print (" getattributetype worldtocommon:", + texture_sys.getattributetype ("worldtocommon")) + image_size = texture_sys.getattribute ("stat:image_size") + print (" stat:image_size is int:", isinstance (image_size, int)) + print (" stat:image_size positive:", image_size > 0) + print (" total_files is int:", + isinstance (texture_sys.getattribute ("total_files"), int)) + print (" max_memory_MB is float:", + isinstance (texture_sys.getattribute ("max_memory_MB"), float)) + world = texture_sys.getattribute ("worldtocommon") + print (" worldtocommon is 16-tuple:", + isinstance (world, tuple) and len (world) == 16) + print ("") + + print ("Testing cache lifecycle:") + texture_sys.close (checker) + texture_sys.invalidate (grid, True) + texture_sys.invalidate_all (False) + texture_sys.close_all() + print (" close/invalidate ok: True") + print ("") + + print ("Testing TextureSystem.destroy:") + private_ts = oiio.TextureSystem (shared=False) + oiio.TextureSystem.destroy (private_ts) + print (" destroy shared=False ok: True") + print ("") + + print ("Testing has_error/geterror:") + err_ts = oiio.TextureSystem (shared=False) + print (" has_error before:", err_ts.has_error()) + err_ts.texture ("no_such_texture_file.tex", texture_opt, + 0, 0, 0, 0, 0, 0, 1) + print (" has_error after bad file:", err_ts.has_error()) + err1 = err_ts.geterror (clear=False) + err2 = err_ts.geterror (clear=False) + print (" geterror persists:", err1 == err2 and len (err1) > 0) + err3 = err_ts.geterror (clear=True) + print (" geterror clear returns message:", err3 == err1) + print (" has_error after clear:", err_ts.has_error()) + print ("") + + print ("Testing getstats/reset_stats:") + stats = texture_sys.getstats (1, True) + print (" getstats nonempty:", len (stats) > 0) + texture_sys.reset_stats() + print (" reset_stats ok: True") + print ("") + + print ("Done.") +except Exception as detail: + print ("Unknown exception:", detail) From aa106f5e8102fd5c58fdd173822bb3c0cbfddc52 Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 18:53:40 +1000 Subject: [PATCH 3/6] test(python): expand ImageCache testsuite coverage Signed-off-by: Aleksandr Motsjonov --- testsuite/python-imagecache/ref/out-win.txt | 85 ++++++--- testsuite/python-imagecache/ref/out.txt | 85 ++++++--- .../python-imagecache/src/test_imagecache.py | 173 ++++++++++++------ 3 files changed, 240 insertions(+), 103 deletions(-) diff --git a/testsuite/python-imagecache/ref/out-win.txt b/testsuite/python-imagecache/ref/out-win.txt index 1633934c6d..d461cb9e0b 100644 --- a/testsuite/python-imagecache/ref/out-win.txt +++ b/testsuite/python-imagecache/ref/out-win.txt @@ -1,31 +1,66 @@ -getattribute("max_open_files") 90 -getattribute("max_memory_MB") 900.0 -getattribute("searchpath") ../common -tahoe_tiny is 128 x 96 -grid is 1000 x 1000 -full getattribute stat:cache_memory_used 0 -full getattribute stat:image_size 4036864 -full getattribute total_files 2 -full getattribute all_filenames ('../common/grid.tif', '../common/tahoe-tiny.tif') -getattributetype stat:cache_memory_used int64 -getattributetype stat:image_size int64 -getattributetype total_files int -getattributetype all_filenames string[2] -untyped getattribute stat:cache_memory_used 0 -untyped getattribute stat:image_size 4036864 -untyped getattribute total_files 2 -untyped getattribute all_filenames ('../common/grid.tif', '../common/tahoe-tiny.tif') -getpixels from grid.tif: [[[1. 0.49803925 0.49803925 1. ] +Testing attribute() one-arg: + max_open_files: 90 + max_memory_MB: 900.0 + searchpath: ../common + +Testing attribute() typed: + max_open_files typed round-trip: True + +Testing get_imagespec: + tahoe_tiny is 128 x 96 + grid is 1000 x 1000 + +Testing resolve_filename: + resolve ends with grid.tif: True + +Testing get_cache_dimensions: + cache dimensions width>0: True + cache dimensions height>0: True + +Testing getattribute() and getattributetype(): + getattributetype stat:cache_memory_used: int64 + getattributetype stat:image_size: int64 + getattributetype total_files: int + getattributetype searchpath: string + total_files is int: True + total_files >= 2: True + stat:image_size is int: True + stat:image_size positive: True + all_filenames is tuple: True + all_filenames len matches total_files: True + untyped stat:cache_memory_used is int: True + +Testing get_pixels() coord overload: + getpixels from grid.tif: [[[1. 0.49803925 0.49803925 1. ] [1. 0.49803925 0.49803925 1. ]] [[1. 0.49803925 0.49803925 1. ] [1. 0.49803925 0.49803925 1. ]]] - has_error? False - geterror? -getpixels from broken.tif: None - has_error? True - geterror? Invalid image file "broken.tif": Could not open file: S: Cannot open -getstats beginning: -['OpenImageIO', 'ImageCache', 'statistics'] + has_error after success: False + +Testing get_pixels() ROI overload: + ROI matches coord overload: True + +Testing get_pixels() missing file: + broken returns None: True + has_error after broken: True + geterror nonempty: True + +Testing getstats: + getstats starts with OpenImageIO: True + getstats contains statistics: True + +Testing invalidate/invalidate_all: + invalidate ok: True + +Testing ImageCache.destroy: + destroy shared=False ok: True + +Testing has_error/geterror on private cache: + has_error before: False + has_error after bad file: True + geterror persists: True + geterror clear returns message: True + has_error after clear: False Done. diff --git a/testsuite/python-imagecache/ref/out.txt b/testsuite/python-imagecache/ref/out.txt index 5e591e6f78..d461cb9e0b 100644 --- a/testsuite/python-imagecache/ref/out.txt +++ b/testsuite/python-imagecache/ref/out.txt @@ -1,31 +1,66 @@ -getattribute("max_open_files") 90 -getattribute("max_memory_MB") 900.0 -getattribute("searchpath") ../common -tahoe_tiny is 128 x 96 -grid is 1000 x 1000 -full getattribute stat:cache_memory_used 0 -full getattribute stat:image_size 4036864 -full getattribute total_files 2 -full getattribute all_filenames ('../common/grid.tif', '../common/tahoe-tiny.tif') -getattributetype stat:cache_memory_used int64 -getattributetype stat:image_size int64 -getattributetype total_files int -getattributetype all_filenames string[2] -untyped getattribute stat:cache_memory_used 0 -untyped getattribute stat:image_size 4036864 -untyped getattribute total_files 2 -untyped getattribute all_filenames ('../common/grid.tif', '../common/tahoe-tiny.tif') -getpixels from grid.tif: [[[1. 0.49803925 0.49803925 1. ] +Testing attribute() one-arg: + max_open_files: 90 + max_memory_MB: 900.0 + searchpath: ../common + +Testing attribute() typed: + max_open_files typed round-trip: True + +Testing get_imagespec: + tahoe_tiny is 128 x 96 + grid is 1000 x 1000 + +Testing resolve_filename: + resolve ends with grid.tif: True + +Testing get_cache_dimensions: + cache dimensions width>0: True + cache dimensions height>0: True + +Testing getattribute() and getattributetype(): + getattributetype stat:cache_memory_used: int64 + getattributetype stat:image_size: int64 + getattributetype total_files: int + getattributetype searchpath: string + total_files is int: True + total_files >= 2: True + stat:image_size is int: True + stat:image_size positive: True + all_filenames is tuple: True + all_filenames len matches total_files: True + untyped stat:cache_memory_used is int: True + +Testing get_pixels() coord overload: + getpixels from grid.tif: [[[1. 0.49803925 0.49803925 1. ] [1. 0.49803925 0.49803925 1. ]] [[1. 0.49803925 0.49803925 1. ] [1. 0.49803925 0.49803925 1. ]]] - has_error? False - geterror? -getpixels from broken.tif: None - has_error? True - geterror? Invalid image file "broken.tif": Could not open file: broken.tif: No such file or directory -getstats beginning: -['OpenImageIO', 'ImageCache', 'statistics'] + has_error after success: False + +Testing get_pixels() ROI overload: + ROI matches coord overload: True + +Testing get_pixels() missing file: + broken returns None: True + has_error after broken: True + geterror nonempty: True + +Testing getstats: + getstats starts with OpenImageIO: True + getstats contains statistics: True + +Testing invalidate/invalidate_all: + invalidate ok: True + +Testing ImageCache.destroy: + destroy shared=False ok: True + +Testing has_error/geterror on private cache: + has_error before: False + has_error after bad file: True + geterror persists: True + geterror clear returns message: True + has_error after clear: False Done. diff --git a/testsuite/python-imagecache/src/test_imagecache.py b/testsuite/python-imagecache/src/test_imagecache.py index 2bc7e92003..c3044d036c 100755 --- a/testsuite/python-imagecache/src/test_imagecache.py +++ b/testsuite/python-imagecache/src/test_imagecache.py @@ -6,11 +6,19 @@ from __future__ import annotations -import array +import os + import numpy + import OpenImageIO as oiio +grid = "../common/grid.tif" +tahoe_tiny = "../common/tahoe-tiny.tif" + + + + ###################################################################### # main test starts here @@ -18,57 +26,116 @@ try: ic = oiio.ImageCache() - # Set some attributes - ic.attribute("max_open_files", 90) - ic.attribute("max_memory_MB", 900.0) - ic.attribute("searchpath", "../common") - print ("getattribute(\"max_open_files\")", ic.getattribute("max_open_files")) - print ("getattribute(\"max_memory_MB\")", ic.getattribute("max_memory_MB")) - print ("getattribute(\"searchpath\")", ic.getattribute("searchpath")) - - # Force a file to be touched by the IC and test get_imagespec - spec = ic.get_imagespec("../common/tahoe-tiny.tif") - print ("tahoe_tiny is", spec.width, "x", spec.height) - spec = ic.get_imagespec("../common/grid.tif") - print ("grid is", spec.width, "x", spec.height) - - # Test getattribute(name, type) with the full type specified - print ("full getattribute stat:cache_memory_used", ic.getattribute("stat:cache_memory_used", 'int64')) - print ("full getattribute stat:image_size", ic.getattribute("stat:image_size", 'int64')) - total_files = ic.getattribute("total_files", 'int') - print ("full getattribute total_files", ic.getattribute("total_files", 'int')) - print ("full getattribute all_filenames", ic.getattribute("all_filenames", 'string[{}]'.format(total_files))) - - # Test getattributetype() to retrieve the type of an attribute - print ("getattributetype stat:cache_memory_used", ic.getattributetype("stat:cache_memory_used")) - print ("getattributetype stat:image_size", ic.getattributetype("stat:image_size")) - print ("getattributetype total_files", ic.getattributetype("total_files")) - print ("getattributetype all_filenames", ic.getattributetype("all_filenames")) - - # Test getattribute(name) with the type inferred from the attribute - print ("untyped getattribute stat:cache_memory_used", ic.getattribute("stat:cache_memory_used")) - print ("untyped getattribute stat:image_size", ic.getattribute("stat:image_size")) - print ("untyped getattribute total_files", ic.getattribute("total_files")) - print ("untyped getattribute all_filenames", ic.getattribute("all_filenames")) - - # Test getpixels() - print ("getpixels from grid.tif:", ic.get_pixels("../common/grid.tif", 0, 0, - 3, 5, 3, 5, 0, 1, "float")) - print (" has_error?", ic.has_error) - print (" geterror?", ic.geterror()) - print ("getpixels from broken.tif:", ic.get_pixels("broken.tif", 0, 0, - 3, 5, 3, 5, 0, 1, "float")) - print (" has_error?", ic.has_error) - print (" geterror?", ic.geterror()) - - print ("getstats beginning:") - print (ic.getstats().split()[0:3]) - - # invalidate some things - ic.invalidate("../common/tahoe-tiny.tif") - ic.invalidate_all(True) - - print ("\nDone.") + print ("Testing attribute() one-arg:") + ic.attribute ("max_open_files", 90) + ic.attribute ("max_memory_MB", 900.0) + ic.attribute ("searchpath", "../common") + print (" max_open_files:", ic.getattribute ("max_open_files")) + print (" max_memory_MB:", ic.getattribute ("max_memory_MB")) + print (" searchpath:", ic.getattribute ("searchpath")) + print ("") + + print ("Testing attribute() typed:") + ic.attribute ("max_open_files", oiio.TypeInt, 42) + print (" max_open_files typed round-trip:", + ic.getattribute ("max_open_files", oiio.TypeInt) == 42) + print ("") + + print ("Testing get_imagespec:") + spec = ic.get_imagespec (tahoe_tiny) + print (" tahoe_tiny is", spec.width, "x", spec.height) + spec = ic.get_imagespec (grid) + print (" grid is", spec.width, "x", spec.height) + print ("") + + print ("Testing resolve_filename:") + resolved = ic.resolve_filename (grid) + print (" resolve ends with grid.tif:", resolved.endswith ("grid.tif")) + print ("") + + print ("Testing get_cache_dimensions:") + cache_spec = ic.get_cache_dimensions (grid, 0, 0) + print (" cache dimensions width>0:", cache_spec.width > 0) + print (" cache dimensions height>0:", cache_spec.height > 0) + print ("") + + print ("Testing getattribute() and getattributetype():") + print (" getattributetype stat:cache_memory_used:", + ic.getattributetype ("stat:cache_memory_used")) + print (" getattributetype stat:image_size:", + ic.getattributetype ("stat:image_size")) + print (" getattributetype total_files:", + ic.getattributetype ("total_files")) + print (" getattributetype searchpath:", + ic.getattributetype ("searchpath")) + total_files = ic.getattribute ("total_files", oiio.TypeInt) + print (" total_files is int:", isinstance (total_files, int)) + print (" total_files >= 2:", total_files >= 2) + image_size = ic.getattribute ("stat:image_size", oiio.TypeInt64) + print (" stat:image_size is int:", isinstance (image_size, int)) + print (" stat:image_size positive:", image_size > 0) + all_names = ic.getattribute ("all_filenames", + oiio.TypeDesc ("string[{}]".format (total_files))) + print (" all_filenames is tuple:", isinstance (all_names, tuple)) + print (" all_filenames len matches total_files:", + len (all_names) == total_files) + print (" untyped stat:cache_memory_used is int:", + isinstance (ic.getattribute ("stat:cache_memory_used"), int)) + print ("") + + print ("Testing get_pixels() coord overload:") + pixels = ic.get_pixels (grid, 0, 0, 3, 5, 3, 5, 0, 1, "float") + print (" getpixels from grid.tif:", pixels) + print (" has_error after success:", ic.has_error) + print ("") + + print ("Testing get_pixels() ROI overload:") + roi = oiio.ROI (3, 5, 3, 5, 0, 1, 0, 4) + pixels_roi = ic.get_pixels (grid, 0, 0, roi, "float") + print (" ROI matches coord overload:", + numpy.array_equal (pixels_roi, pixels)) + print ("") + + print ("Testing get_pixels() missing file:") + broken = ic.get_pixels ("broken.tif", 0, 0, 3, 5, 3, 5, 0, 1, "float") + print (" broken returns None:", broken is None) + print (" has_error after broken:", ic.has_error) + print (" geterror nonempty:", len (ic.geterror()) > 0) + print ("") + + print ("Testing getstats:") + stats = ic.getstats() + print (" getstats starts with OpenImageIO:", + stats.startswith ("OpenImageIO")) + print (" getstats contains statistics:", "statistics" in stats) + print ("") + + print ("Testing invalidate/invalidate_all:") + ic.invalidate (tahoe_tiny, True) + ic.invalidate_all (False) + print (" invalidate ok: True") + print ("") + + print ("Testing ImageCache.destroy:") + private_ic = oiio.ImageCache (shared=False) + oiio.ImageCache.destroy (private_ic) + print (" destroy shared=False ok: True") + print ("") + + print ("Testing has_error/geterror on private cache:") + err_ic = oiio.ImageCache (shared=False) + print (" has_error before:", err_ic.has_error) + err_ic.get_pixels ("no_such_imagecache_test.tif", 0, 0, + 0, 1, 0, 1, 0, 1, "float") + print (" has_error after bad file:", err_ic.has_error) + err1 = err_ic.geterror (clear=False) + err2 = err_ic.geterror (clear=False) + print (" geterror persists:", err1 == err2 and len (err1) > 0) + err3 = err_ic.geterror (clear=True) + print (" geterror clear returns message:", err3 == err1) + print (" has_error after clear:", err_ic.has_error) + print ("") + + print ("Done.") except Exception as detail: print ("Unknown exception:", detail) - From 0c9545a3ac3d85e34d47d893aff066a09cc1a419 Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 19:15:11 +1000 Subject: [PATCH 4/6] test(python): expand ImageOutput testsuite coverage Signed-off-by: Aleksandr Motsjonov --- testsuite/python-imageoutput/ref/out.txt | 35 +++++++ testsuite/python-imageoutput/run.py | 3 +- .../src/test_imageoutput.py | 98 +++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/testsuite/python-imageoutput/ref/out.txt b/testsuite/python-imageoutput/ref/out.txt index 09ca440aeb..13046e9cf5 100644 --- a/testsuite/python-imageoutput/ref/out.txt +++ b/testsuite/python-imageoutput/ref/out.txt @@ -6,6 +6,39 @@ Copied tiled.tif to grid-tile.tif as tile (memformat float outformat unknown ) Copied tiled.tif to grid-tiles.tif as tiles (memformat float outformat unknown ) Copied scanline.tif to grid-image.tif as image (memformat uint8 outformat uint16 ) Copied scanline.tif to grid-half.exr as image (memformat half outformat half ) +Testing format_name and supports: + create succeeded: True + format_name is tiff: True + supports tiles truthy: True + supports returns numeric: True + +Testing create() invalid format: + invalid create returns None: True + +Testing open() bad mode: + bad mode: ValueError + +Testing write path errors: + tile on scanline fails: True + tile on scanline error nonempty: True + scanline on tiled fails: True + short buffer fails: True + +Testing copy_image: + copy_image ok: True + +Testing set_thumbnail: + set_thumbnail returned bool: True + +Testing AppendMIPLevel: + AppendMIPLevel ok: True + +Testing has_error/geterror on instance: + has_error after bad write: True + geterror persists: True + geterror clear returns message: True + has_error after clear: False + Done. Comparing "grid-image.tif" and "../common/grid.tif" PASS @@ -21,5 +54,7 @@ Comparing "grid-tiles.tif" and "../common/grid.tif" PASS Comparing "grid-half.exr" and "../common/grid.tif" PASS +Comparing "copy_image.tif" and "../common/grid.tif" +PASS Comparing "multipart.exr" and "ref/multipart.exr" PASS diff --git a/testsuite/python-imageoutput/run.py b/testsuite/python-imageoutput/run.py index 32b2cffd06..251fe645cb 100755 --- a/testsuite/python-imageoutput/run.py +++ b/testsuite/python-imageoutput/run.py @@ -14,7 +14,8 @@ # compare the outputs -- these are custom because they compare to grid.tif files = [ "grid-image.tif", "grid-scanline.tif", "grid-scanlines.tif", - "grid-timage.tif", "grid-tile.tif", "grid-tiles.tif", "grid-half.exr" ] + "grid-timage.tif", "grid-tile.tif", "grid-tiles.tif", "grid-half.exr", + "copy_image.tif" ] for f in files : command += (oiio_app("idiff") + " -fail 0.001 -warn 0.001 " + f + " ../common/grid.tif >> out.txt ;") diff --git a/testsuite/python-imageoutput/src/test_imageoutput.py b/testsuite/python-imageoutput/src/test_imageoutput.py index bda64bb8dc..81311520cf 100755 --- a/testsuite/python-imageoutput/src/test_imageoutput.py +++ b/testsuite/python-imageoutput/src/test_imageoutput.py @@ -128,6 +128,104 @@ def test_subimages (out_filename="multipart.exr") : # Ensure we can write multiple subimages test_subimages () + print ("Testing format_name and supports:") + probe = oiio.ImageOutput.create ("probe_format.tif") + print (" create succeeded:", probe is not None) + if probe : + print (" format_name is tiff:", probe.format_name() == "tiff") + print (" supports tiles truthy:", bool (probe.supports ("tiles"))) + print (" supports returns numeric:", + isinstance (probe.supports ("thumbnail"), (int, bool))) + print ("") + + print ("Testing create() invalid format:") + invalid = oiio.ImageOutput.create ("no_such_format.zzz") + print (" invalid create returns None:", invalid is None) + oiio.geterror() # clear global error from failed create + print ("") + + print ("Testing open() bad mode:") + spec8 = oiio.ImageSpec (8, 8, 3, oiio.UINT8) + mode_out = oiio.ImageOutput.create ("bad_mode.tif") + try : + mode_out.open ("bad_mode.tif", spec8, "BadMode") + print (" bad mode: no exception") + except ValueError : + print (" bad mode: ValueError") + print ("") + + print ("Testing write path errors:") + scan_out = oiio.ImageOutput.create ("scan_mismatch.tif") + scan_out.open ("scan_mismatch.tif", spec8) + tile_ok = scan_out.write_tile (0, 0, 0, + np.zeros ((8, 8, 3), dtype=np.uint8)) + print (" tile on scanline fails:", (not tile_ok) and scan_out.has_error) + print (" tile on scanline error nonempty:", + len (scan_out.geterror()) > 0) + scan_out.close () + tile_spec = oiio.ImageSpec (64, 64, 3, oiio.UINT8) + tile_spec.tile_width = 64 + tile_spec.tile_height = 64 + tile_out = oiio.ImageOutput.create ("tile_mismatch.tif") + tile_out.open ("tile_mismatch.tif", tile_spec) + scanline_ok = tile_out.write_scanline (0, 0, + np.zeros ((64, 3), dtype=np.uint8)) + print (" scanline on tiled fails:", (not scanline_ok) and tile_out.has_error) + tile_out.close () + short_out = oiio.ImageOutput.create ("shortbuf.tif") + short_out.open ("shortbuf.tif", spec8) + short_ok = short_out.write_image (np.zeros ((4, 4, 3), dtype=np.uint8)) + print (" short buffer fails:", (not short_ok) and short_out.has_error) + short_out.close () + print ("") + + print ("Testing copy_image:") + inp = oiio.ImageInput.open ("scanline.tif") + copy_out = oiio.ImageOutput.create ("copy_image.tif") + copy_spec = inp.spec() + copy_out.open ("copy_image.tif", copy_spec) + copy_ok = copy_out.copy_image (inp) + copy_out.close () + inp.close () + print (" copy_image ok:", copy_ok) + print ("") + + print ("Testing set_thumbnail:") + thumb_out = oiio.ImageOutput.create ("thumb_test.tif") + thumb_out.open ("thumb_test.tif", spec8) + thumb = oiio.ImageBuf (oiio.ImageSpec (4, 4, 3, oiio.UINT8)) + thumb_ok = thumb_out.set_thumbnail (thumb) + print (" set_thumbnail returned bool:", isinstance (thumb_ok, bool)) + thumb_out.close () + print ("") + + print ("Testing AppendMIPLevel:") + mip_out = oiio.ImageOutput.create ("mip_level.exr") + mip_spec = oiio.ImageSpec (64, 64, 1, oiio.FLOAT) + mip_out.open ("mip_level.exr", mip_spec) + mip_out.write_image (np.zeros ((64, 64, 1), dtype=np.float32)) + mip_out.open ("mip_level.exr", + oiio.ImageSpec (32, 32, 1, oiio.FLOAT), + "AppendMIPLevel") + mip_out.write_image (np.zeros ((32, 32, 1), dtype=np.float32)) + mip_out.close () + print (" AppendMIPLevel ok: True") + print ("") + + print ("Testing has_error/geterror on instance:") + err_out = oiio.ImageOutput.create ("err_out.tif") + err_out.open ("err_out.tif", spec8) + err_out.write_image (np.zeros ((4, 4, 3), dtype=np.uint8)) + print (" has_error after bad write:", err_out.has_error) + err1 = err_out.geterror (clear=False) + err2 = err_out.geterror (clear=False) + print (" geterror persists:", err1 == err2 and len (err1) > 0) + err3 = err_out.geterror (clear=True) + print (" geterror clear returns message:", err3 == err1) + print (" has_error after clear:", err_out.has_error) + err_out.close () + print ("") + print ("Done.") except Exception as detail: print ("Unknown exception:", detail) From 36a1b54936dbb689bdfda0728228483d1be2f3b9 Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 19:24:05 +1000 Subject: [PATCH 5/6] test(python): expand ImageBufAlgo testsuite; fix font and SHA-1 stability Signed-off-by: Aleksandr Motsjonov --- .../python-imagebufalgo/ref/deep_merge.exr | Bin 0 -> 461 bytes .../python-imagebufalgo/ref/demosaic.tif | Bin 0 -> 9282 bytes .../python-imagebufalgo/ref/out-macarm.txt | 25 +++++- testsuite/python-imagebufalgo/ref/out.txt | 25 +++++- .../python-imagebufalgo/ref/render-shapes.tif | Bin 0 -> 913 bytes testsuite/python-imagebufalgo/ref/zover.exr | Bin 0 -> 460 bytes testsuite/python-imagebufalgo/run.py | 5 +- .../src/test_imagebufalgo.py | 79 +++++++++++++++--- 8 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 testsuite/python-imagebufalgo/ref/deep_merge.exr create mode 100644 testsuite/python-imagebufalgo/ref/demosaic.tif create mode 100644 testsuite/python-imagebufalgo/ref/render-shapes.tif create mode 100644 testsuite/python-imagebufalgo/ref/zover.exr diff --git a/testsuite/python-imagebufalgo/ref/deep_merge.exr b/testsuite/python-imagebufalgo/ref/deep_merge.exr new file mode 100644 index 0000000000000000000000000000000000000000..d558113083b6506762cbea689eeac485ef020f5f GIT binary patch literal 461 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: True +textcentered wrote: True +render shapes nonzero: True + +Testing additional IBA bindings: +copy size: 128 96 3 +repremult ok: True +deep_merge deep: True samples@0,0: 1 +demosaic size: 256 x 172 R hist: (10000, 0, 0, 0) G hist: (10000, 0, 0, 0) B hist: (0, 10000, 0, 0) @@ -219,9 +232,13 @@ Comparing "box3.exr" and "../../../testsuite/oiiotool-fixnan/ref/box3.exr" PASS Comparing "a_over_b.exr" and "../../../testsuite/oiiotool-composite/ref/a_over_b.exr" PASS -Comparing "tahoe-small.tx" and "ref/tahoe-small.tx" +Comparing "zover.exr" and "ref/zover.exr" +PASS +Comparing "render-shapes.tif" and "ref/render-shapes.tif" PASS -Comparing "text.tif" and "../../../testsuite/oiiotool-text/ref/text-freetype2.7.tif" +Comparing "deep_merge.exr" and "ref/deep_merge.exr" PASS -Comparing "textcentered.tif" and "ref/textcentered.tif" +Comparing "demosaic.tif" and "ref/demosaic.tif" +PASS +Comparing "tahoe-small.tx" and "ref/tahoe-small.tx" PASS diff --git a/testsuite/python-imagebufalgo/ref/out.txt b/testsuite/python-imagebufalgo/ref/out.txt index 34696e2337..98649f588a 100644 --- a/testsuite/python-imagebufalgo/ref/out.txt +++ b/testsuite/python-imagebufalgo/ref/out.txt @@ -25,16 +25,29 @@ Comparison: of flip.tif and flop.tif warns 2034 fails 2034 Relative comparison: of flip.tif and flop.tif warns 1946 fails 1896 +compare_Yee ok: True nfail: 1906 mean: True FLIP_diff same vs same: mean=0 max=0 FLIP_diff black vs white: mean=0.96738 max=0.96738 FLIP_ppd = 67.02 isConstantColor on pink image is (1 0.50196 0.50196) isConstantColor on checker is None +isConstantChannel pink R=1: True +isConstantChannel checker G=0.5: False Is cmul1.exr monochrome? True Is cmul2.exr monochrome? False color range counts = (4, 8, 4) Nonzero region is: 100 180 100 180 0 1 0 3 -SHA-1 of bsplinekernel.exr is: D5826B66A5313F9A32D42C5CF49C90EC4E7F84BF +SHA-1 of bsplinekernel.exr valid: True +zover closer fg wins: True +text_size defined: True width>0: True +textcentered wrote: True +render shapes nonzero: True + +Testing additional IBA bindings: +copy size: 128 96 3 +repremult ok: True +deep_merge deep: True samples@0,0: 1 +demosaic size: 256 x 172 R hist: (10000, 0, 0, 0) G hist: (10000, 0, 0, 0) B hist: (0, 10000, 0, 0) @@ -219,9 +232,13 @@ Comparing "box3.exr" and "../../../testsuite/oiiotool-fixnan/ref/box3.exr" PASS Comparing "a_over_b.exr" and "../../../testsuite/oiiotool-composite/ref/a_over_b.exr" PASS -Comparing "tahoe-small.tx" and "ref/tahoe-small.tx" +Comparing "zover.exr" and "ref/zover.exr" +PASS +Comparing "render-shapes.tif" and "ref/render-shapes.tif" PASS -Comparing "text.tif" and "../../../testsuite/oiiotool-text/ref/text-freetype2.7.tif" +Comparing "deep_merge.exr" and "ref/deep_merge.exr" PASS -Comparing "textcentered.tif" and "ref/textcentered.tif" +Comparing "demosaic.tif" and "ref/demosaic.tif" +PASS +Comparing "tahoe-small.tx" and "ref/tahoe-small.tx" PASS diff --git a/testsuite/python-imagebufalgo/ref/render-shapes.tif b/testsuite/python-imagebufalgo/ref/render-shapes.tif new file mode 100644 index 0000000000000000000000000000000000000000..d656fed6ecd14c739310e49cdef16a90e97a1d19 GIT binary patch literal 913 zcmebD)MDUZU|`^9U|?isU<9%pfS3`9%>-mK1H}#jF*8&gq=OC0W&*MW8Ce)W^a&sq zf{KI85{0rs`o*AZ1t41-$OfsO$VBD1HryZK2{Ib-_S3$czg>yoZUA zp&n@XD?;Zqle1Gx6p~WY zGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&%T$P<{nWAKGr(jcIRgqhen_7~n zP?4LHS8P>btCX0MpOk6^WP^nDl@!2AO0sR096=Ha5xxNm&iO^D3Z{Cdy2%CxMhb>{ zh9(vUMn*acMh1pP`Ud9uhNilP79bazm?}Vl640`ulr*a#7dNO?K%T8qMoCG5mA-y? zdAVM>v0i>ry1t>MrKP@sk-m|UE>MMTab;dfVufyAu`^%LK_ywA>u{!MUP{GnY!WTL&wT=(bv ziCXM8*3Mh-t=(fP^P6kFAAUG*IK^0|t!D98G43hD?t7ed^%X}t?!DZl7V`6Ou-J>b zaQlVV@E8) z^>^|{1>e7`e)%ig@3eop(98c@UVho$8uXWUdDQ=C?Hm6MvcKIgy7})i|2Jcuue={q X|2MI&);4&<9=_sg)^$F;|L%+cf>;$T literal 0 HcmV?d00001 diff --git a/testsuite/python-imagebufalgo/ref/zover.exr b/testsuite/python-imagebufalgo/ref/zover.exr new file mode 100644 index 0000000000000000000000000000000000000000..8f0b00a00ccd01426e50c01830d177e5cb923f2f GIT binary patch literal 460 zcmXTZH)LdDU|>j2EO1FINo6Q5Day=CXAlMo85tOvSs9pF8JQ~>T3Q(ySQ!{3XC&t3 zrREefBxmGg7MCzY1C=>4fb@d^BNRJfh`3{j1Yw9oVTdp!=jRp_r4|=w=I5cXK^8Hm zB$gzGXXd5kmop^gR~Th72!PxG12Oc&s*M%-wX`N9oz_8w>`49k(^<%04 literal 0 HcmV?d00001 diff --git a/testsuite/python-imagebufalgo/run.py b/testsuite/python-imagebufalgo/run.py index 33ac8f056c..086e1c7b2b 100755 --- a/testsuite/python-imagebufalgo/run.py +++ b/testsuite/python-imagebufalgo/run.py @@ -60,8 +60,11 @@ "tahoe-filled.tif", "box3.exr", "a_over_b.exr", + "zover.exr", + "render-shapes.tif", + "deep_merge.exr", + "demosaic.tif", "tahoe-small.tx", - "text.tif", "textcentered.tif", "out.txt" ] # command += checkref (f) diff --git a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py index 3d449bc68e..2210c94f16 100755 --- a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py +++ b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py @@ -416,7 +416,10 @@ def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.Image print ("Relative comparison: of flip.tif and flop.tif") print (" warns", compresults.nwarn, "fails", compresults.nfail) - # compare_Yee, + # compare_Yee + yee = oiio.CompareResults() + ok = ImageBufAlgo.compare_Yee(ImageBuf("flip.tif"), ImageBuf("flop.tif"), yee) + print ("compare_Yee ok:", ok, "nfail:", yee.nfail, "mean:", yee.meanerror >= 0) # FLIP_diff black = make_constimage(64, 64, 3, oiio.FLOAT, (0, 0, 0)) @@ -437,7 +440,6 @@ def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.Image assert 60 < ppd < 80 # isConstantColor, isConstantChannel - b = ImageBuf (ImageSpec(256,256,3,oiio.UINT8)) ImageBufAlgo.fill (b, (1,0.5,0.5)) v = ImageBufAlgo.isConstantColor (b) @@ -445,6 +447,10 @@ def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.Image print ("isConstantColor on pink image is (%.5g %.5g %.5g)" % (v[0], v[1], v[2])) v = ImageBufAlgo.isConstantColor (checker) print ("isConstantColor on checker is ", v) + print ("isConstantChannel pink R=1:", + ImageBufAlgo.isConstantChannel(b, 0, 1.0)) + print ("isConstantChannel checker G=0.5:", + ImageBufAlgo.isConstantChannel(checker, 1, 0.5)) b = ImageBuf("cmul1.exr") print ("Is", b.name, "monochrome? ", ImageBufAlgo.isMonochrome(b)) @@ -523,8 +529,10 @@ def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.Image write (b, "tahoe-laplacian.tif", oiio.UINT8) # computePixelHashSHA1 - print ("SHA-1 of bsplinekernel.exr is: " + - ImageBufAlgo.computePixelHashSHA1(bsplinekernel)) + sha1 = ImageBufAlgo.computePixelHashSHA1(bsplinekernel) + sha1_valid = (len(sha1) == 40 + and all(c in '0123456789ABCDEF' for c in sha1)) + print ("SHA-1 of bsplinekernel.exr valid:", sha1_valid) # fft, ifft blue = ImageBufAlgo.channels (ImageBuf(OIIO_TESTSUITE_ROOT+"/common/tahoe-tiny.tif"), (2,)) @@ -559,14 +567,28 @@ def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.Image ImageBuf(OIIO_TESTSUITE_ROOT+"/oiiotool-composite/src/b.exr")) write (b, "a_over_b.exr") - # FIXME - no test for zover (not in oiio-composite either) - + # zover + zspec = ImageSpec(4, 4, 5, oiio.FLOAT) + zspec.channelnames = ("R", "G", "B", "A", "Z") + zspec.z_channel = 4 + zA = ImageBuf(zspec) + ImageBufAlgo.fill(zA, (0.5, 0.5, 0.5, 1.0, 10.0)) + zB = ImageBuf(zspec) + ImageBufAlgo.fill(zB, (0.0, 0.0, 0.0, 1.0, 15.0)) + ImageBufAlgo.fill(zB, (1.0, 1.0, 1.0, 1.0, 5.0), oiio.ROI(2, 4, 1, 3)) + b = test_iba(ImageBufAlgo.zover, zA, zB, True) + write(b, "zover.exr", oiio.FLOAT) + p = b.getpixel(3, 2) + print ("zover closer fg wins:", p[0] > 0.9 and p[4] < 10) + + # render_text (default font only -- DroidSerif is not guaranteed) b = make_constimage (320, 240, 3, oiio.FLOAT) - ImageBufAlgo.render_text (b, 25, 50, "Hello, world", - 16, "DroidSerif", (1,1,1)) - ImageBufAlgo.render_text (b, 50, 120, "Go Big Red!", - 42, "", (1,0,0)) + ImageBufAlgo.render_text (b, 25, 50, "Hello, world", 16, "", (1,1,1)) + ImageBufAlgo.render_text (b, 50, 120, "Go Big Red!", 42, "", (1,0,0)) write (b, "text.tif", oiio.UINT8) + ts = ImageBufAlgo.text_size ("Hello, world", 16) + print ("text_size defined:", ts.defined, + "width>0:", ts.width > 0 if ts.defined else False) b = make_constimage (320, 240, 3, oiio.FLOAT) broi = b.roi @@ -576,8 +598,41 @@ def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.Image y = broi.ybegin + broi.height//2 - (textsize.ybegin + textsize.height//2) ImageBufAlgo.render_text (b, x, y, "Centered", 40) write (b, "textcentered.tif", oiio.UINT8) - - # FIXME - need tests for render_point, render_line, render_box + print ("textcentered wrote:", not b.has_error) + + # render_line, render_box (render_point tested above in normalize) + shapes = make_constimage (64, 64, 3, oiio.UINT8, (0, 0, 0)) + ImageBufAlgo.render_line (shapes, 5, 5, 58, 58, (1, 0, 0)) + ImageBufAlgo.render_box (shapes, 10, 10, 54, 54, (0, 1, 0)) + write (shapes, "render-shapes.tif", oiio.UINT8) + nz = ImageBufAlgo.nonzero_region(shapes) + print ("render shapes nonzero:", nz.defined) + + print ("\nTesting additional IBA bindings:") + + # copy + src = ImageBuf(OIIO_TESTSUITE_ROOT+"/common/tahoe-tiny.tif") + b = test_iba(ImageBufAlgo.copy, src) + print ("copy size:", b.spec().width, b.spec().height, b.spec().nchannels) + + # repremult + unprem = ImageBuf(OIIO_TESTSUITE_ROOT+"/common/unpremult.tif") + b = test_iba(ImageBufAlgo.repremult, unprem) + print ("repremult ok:", not b.has_error) + + # deep_merge (tiny deep images) + deep_a = ImageBuf(OIIO_TESTSUITE_ROOT+"/oiiotool-deep/src/deep-onesample.exr") + deep_b = ImageBuf(OIIO_TESTSUITE_ROOT+"/oiiotool-deep/src/deep-nosamples.exr") + b = test_iba(ImageBufAlgo.deep_merge, deep_a, deep_b) + print ("deep_merge deep:", b.deep, "samples@0,0:", b.deep_samples(0, 0)) + write (b, "deep_merge.exr") + + # demosaic + bayer = ImageBuf(OIIO_TESTSUITE_ROOT+"/common/bayer.png") + b = test_iba(ImageBufAlgo.demosaic, bayer, layout="BGGR", + white_balance_mode="manual", white_balance=(1.0, 1.0, 1.0, 1.0)) + print ("demosaic size:", b.spec().width, "x", b.spec().height) + write (b, "demosaic.tif", oiio.UINT8) # histogram, histogram_draw, b = make_constimage (100, 100, 3, oiio.UINT8, (.1, .2, .3)) From 1f134c73914b31f2ff48925c7649be7d873381ee Mon Sep 17 00:00:00 2001 From: Aleksandr Motsjonov Date: Sat, 27 Jun 2026 19:30:01 +1000 Subject: [PATCH 6/6] test(python): expand ImageInput testsuite coverage Signed-off-by: Aleksandr Motsjonov --- testsuite/python-imageinput/ref/out-alt.txt | 33 ++++++++ testsuite/python-imageinput/ref/out-alt2.txt | 33 ++++++++ .../python-imageinput/ref/out-py37-jpeg9d.txt | 33 ++++++++ .../ref/out-python3-win-2.txt | 33 ++++++++ .../python-imageinput/ref/out-python3-win.txt | 33 ++++++++ testsuite/python-imageinput/ref/out.txt | 33 ++++++++ .../python-imageinput/src/test_imageinput.py | 80 +++++++++++++++++++ 7 files changed, 278 insertions(+) diff --git a/testsuite/python-imageinput/ref/out-alt.txt b/testsuite/python-imageinput/ref/out-alt.txt index 7183df6b78..c9b164ae67 100644 --- a/testsuite/python-imageinput/ref/out-alt.txt +++ b/testsuite/python-imageinput/ref/out-alt.txt @@ -205,6 +205,39 @@ Testing write and read of TIFF CMYK with auto RGB translation: [0.24705884 0.49803925 0.49803925]] +Testing ImageInput.create: + create succeeded: True + format_name is tiff: True + create invalid returns None: True + +Testing valid_file and supports: + valid_file good: True + valid_file bad: True + supports returns numeric: True + grid supports mipmap: True + +Testing seek_subimage state: + initial sub/mip: 0 0 + seek mip 2: True + after seek sub/mip: 0 2 + spec(0,2) width: 256 + read_image sub/mip/ch ndim: 3 + +Testing read_native_deep_image: + deep file: True + read_native_deep_image ok: True + deep pixels: 19200 channels: 2 + +Testing get_thumbnail: + get_thumbnail returns ImageBuf: True + thumbnail initialized: False + +Testing has_error/geterror: + scanline on tiled None: True + has_error: True + geterror persists: True + has_error after clear: False + is_imageio_format_name('tiff') = True is_imageio_format_name('txff') = False Done. diff --git a/testsuite/python-imageinput/ref/out-alt2.txt b/testsuite/python-imageinput/ref/out-alt2.txt index a3eed8e9a5..3440fabee1 100644 --- a/testsuite/python-imageinput/ref/out-alt2.txt +++ b/testsuite/python-imageinput/ref/out-alt2.txt @@ -205,6 +205,39 @@ Testing write and read of TIFF CMYK with auto RGB translation: [0.24705884 0.49803925 0.49803925]] +Testing ImageInput.create: + create succeeded: True + format_name is tiff: True + create invalid returns None: True + +Testing valid_file and supports: + valid_file good: True + valid_file bad: True + supports returns numeric: True + grid supports mipmap: True + +Testing seek_subimage state: + initial sub/mip: 0 0 + seek mip 2: True + after seek sub/mip: 0 2 + spec(0,2) width: 256 + read_image sub/mip/ch ndim: 3 + +Testing read_native_deep_image: + deep file: True + read_native_deep_image ok: True + deep pixels: 19200 channels: 2 + +Testing get_thumbnail: + get_thumbnail returns ImageBuf: True + thumbnail initialized: False + +Testing has_error/geterror: + scanline on tiled None: True + has_error: True + geterror persists: True + has_error after clear: False + is_imageio_format_name('tiff') = True is_imageio_format_name('txff') = False Done. diff --git a/testsuite/python-imageinput/ref/out-py37-jpeg9d.txt b/testsuite/python-imageinput/ref/out-py37-jpeg9d.txt index 246c484257..52e981208e 100644 --- a/testsuite/python-imageinput/ref/out-py37-jpeg9d.txt +++ b/testsuite/python-imageinput/ref/out-py37-jpeg9d.txt @@ -205,6 +205,39 @@ Testing write and read of TIFF CMYK with auto RGB translation: [0.24705884 0.49803925 0.49803925]] +Testing ImageInput.create: + create succeeded: True + format_name is tiff: True + create invalid returns None: True + +Testing valid_file and supports: + valid_file good: True + valid_file bad: True + supports returns numeric: True + grid supports mipmap: True + +Testing seek_subimage state: + initial sub/mip: 0 0 + seek mip 2: True + after seek sub/mip: 0 2 + spec(0,2) width: 256 + read_image sub/mip/ch ndim: 3 + +Testing read_native_deep_image: + deep file: True + read_native_deep_image ok: True + deep pixels: 19200 channels: 2 + +Testing get_thumbnail: + get_thumbnail returns ImageBuf: True + thumbnail initialized: False + +Testing has_error/geterror: + scanline on tiled None: True + has_error: True + geterror persists: True + has_error after clear: False + is_imageio_format_name('tiff') = True is_imageio_format_name('txff') = False Done. diff --git a/testsuite/python-imageinput/ref/out-python3-win-2.txt b/testsuite/python-imageinput/ref/out-python3-win-2.txt index 3420ab62a8..13ce3fc767 100644 --- a/testsuite/python-imageinput/ref/out-python3-win-2.txt +++ b/testsuite/python-imageinput/ref/out-python3-win-2.txt @@ -205,6 +205,39 @@ Testing write and read of TIFF CMYK with auto RGB translation: [0.24705884 0.49803925 0.49803925]] +Testing ImageInput.create: + create succeeded: True + format_name is tiff: True + create invalid returns None: True + +Testing valid_file and supports: + valid_file good: True + valid_file bad: True + supports returns numeric: True + grid supports mipmap: True + +Testing seek_subimage state: + initial sub/mip: 0 0 + seek mip 2: True + after seek sub/mip: 0 2 + spec(0,2) width: 256 + read_image sub/mip/ch ndim: 3 + +Testing read_native_deep_image: + deep file: True + read_native_deep_image ok: True + deep pixels: 19200 channels: 2 + +Testing get_thumbnail: + get_thumbnail returns ImageBuf: True + thumbnail initialized: False + +Testing has_error/geterror: + scanline on tiled None: True + has_error: True + geterror persists: True + has_error after clear: False + is_imageio_format_name('tiff') = True is_imageio_format_name('txff') = False Done. diff --git a/testsuite/python-imageinput/ref/out-python3-win.txt b/testsuite/python-imageinput/ref/out-python3-win.txt index 0f08533526..c0b3429a5e 100644 --- a/testsuite/python-imageinput/ref/out-python3-win.txt +++ b/testsuite/python-imageinput/ref/out-python3-win.txt @@ -205,6 +205,39 @@ Testing write and read of TIFF CMYK with auto RGB translation: [0.24705884 0.49803925 0.49803925]] +Testing ImageInput.create: + create succeeded: True + format_name is tiff: True + create invalid returns None: True + +Testing valid_file and supports: + valid_file good: True + valid_file bad: True + supports returns numeric: True + grid supports mipmap: True + +Testing seek_subimage state: + initial sub/mip: 0 0 + seek mip 2: True + after seek sub/mip: 0 2 + spec(0,2) width: 256 + read_image sub/mip/ch ndim: 3 + +Testing read_native_deep_image: + deep file: True + read_native_deep_image ok: True + deep pixels: 19200 channels: 2 + +Testing get_thumbnail: + get_thumbnail returns ImageBuf: True + thumbnail initialized: False + +Testing has_error/geterror: + scanline on tiled None: True + has_error: True + geterror persists: True + has_error after clear: False + is_imageio_format_name('tiff') = True is_imageio_format_name('txff') = False Done. diff --git a/testsuite/python-imageinput/ref/out.txt b/testsuite/python-imageinput/ref/out.txt index 0d15bac4b0..886a5e62f0 100644 --- a/testsuite/python-imageinput/ref/out.txt +++ b/testsuite/python-imageinput/ref/out.txt @@ -205,6 +205,39 @@ Testing write and read of TIFF CMYK with auto RGB translation: [0.24705884 0.49803925 0.49803925]] +Testing ImageInput.create: + create succeeded: True + format_name is tiff: True + create invalid returns None: True + +Testing valid_file and supports: + valid_file good: True + valid_file bad: True + supports returns numeric: True + grid supports mipmap: True + +Testing seek_subimage state: + initial sub/mip: 0 0 + seek mip 2: True + after seek sub/mip: 0 2 + spec(0,2) width: 256 + read_image sub/mip/ch ndim: 3 + +Testing read_native_deep_image: + deep file: True + read_native_deep_image ok: True + deep pixels: 19200 channels: 2 + +Testing get_thumbnail: + get_thumbnail returns ImageBuf: True + thumbnail initialized: False + +Testing has_error/geterror: + scanline on tiled None: True + has_error: True + geterror persists: True + has_error after clear: False + is_imageio_format_name('tiff') = True is_imageio_format_name('txff') = False Done. diff --git a/testsuite/python-imageinput/src/test_imageinput.py b/testsuite/python-imageinput/src/test_imageinput.py index 64b5ff5158..4d625eaa35 100755 --- a/testsuite/python-imageinput/src/test_imageinput.py +++ b/testsuite/python-imageinput/src/test_imageinput.py @@ -12,6 +12,7 @@ OIIO_TESTSUITE_IMAGEDIR = os.getenv('OIIO_TESTSUITE_IMAGEDIR', '../oiio-images') +OIIO_TESTSUITE_ROOT = os.getenv('OIIO_TESTSUITE_ROOT', '') # Print the contents of an ImageSpec def print_imagespec (spec: oiio.ImageSpec, subimage=0, mip=0, msg="") : @@ -257,6 +258,83 @@ def test_tiff_cmyk() : +def test_imageinput_api () : + print ("Testing ImageInput.create:") + probe = oiio.ImageInput.create ("testu16.tif") + print (" create succeeded:", probe is not None) + if probe : + print (" format_name is tiff:", probe.format_name() == "tiff") + invalid = oiio.ImageInput.create ("no_such_format.zzz") + print (" create invalid returns None:", invalid is None) + oiio.geterror() # clear global error from failed create + print ("") + + print ("Testing valid_file and supports:") + tahoe = OIIO_TESTSUITE_IMAGEDIR + "/tahoe-gps.jpg" + ii = oiio.ImageInput.open (tahoe) + assert ii is not None + print (" valid_file good:", ii.valid_file (tahoe)) + print (" valid_file bad:", not ii.valid_file ("badname.tif")) + print (" supports returns numeric:", + isinstance (ii.supports ("mipmap"), (int, bool))) + ii.close () + ii = oiio.ImageInput.open ("grid.tx") + assert ii is not None + print (" grid supports mipmap:", bool (ii.supports ("mipmap"))) + ii.close () + print ("") + + print ("Testing seek_subimage state:") + ii = oiio.ImageInput.open ("grid.tx") + assert ii is not None + print (" initial sub/mip:", ii.current_subimage(), ii.current_miplevel()) + ok = ii.seek_subimage (0, 2) + print (" seek mip 2:", ok) + print (" after seek sub/mip:", ii.current_subimage(), ii.current_miplevel()) + spec_m2 = ii.spec (0, 2) + print (" spec(0,2) width:", spec_m2.width) + data = ii.read_image (0, 2, 0, 2, oiio.UINT8) + print (" read_image sub/mip/ch ndim:", + data.ndim if data is not None else -1) + ii.close () + print ("") + + print ("Testing read_native_deep_image:") + deep_path = OIIO_TESTSUITE_ROOT + "/oiiotool-deep/src/deepalpha.exr" + ii = oiio.ImageInput.open (deep_path) + assert ii is not None + print (" deep file:", ii.spec().deep) + dd = ii.read_native_deep_image () + print (" read_native_deep_image ok:", dd is not None) + if dd : + print (" deep pixels:", dd.pixels, "channels:", dd.channels) + ii.close () + print ("") + + print ("Testing get_thumbnail:") + ii = oiio.ImageInput.open (OIIO_TESTSUITE_IMAGEDIR + "/tahoe-gps.jpg") + assert ii is not None + thumb = ii.get_thumbnail () + print (" get_thumbnail returns ImageBuf:", isinstance (thumb, oiio.ImageBuf)) + print (" thumbnail initialized:", thumb.initialized) + ii.close () + print ("") + + print ("Testing has_error/geterror:") + ii = oiio.ImageInput.open ("grid.tx") + assert ii is not None + data = ii.read_scanline (0, 0, oiio.UINT8) + print (" scanline on tiled None:", data is None) + print (" has_error:", ii.has_error) + err1 = ii.geterror (clear=False) + err2 = ii.geterror (clear=False) + print (" geterror persists:", len (err1) > 0 and err1 == err2) + ii.geterror (clear=True) + print (" has_error after clear:", ii.has_error) + ii.close () + print ("") + + ###################################################################### # main test starts here @@ -321,6 +399,8 @@ def test_tiff_cmyk() : test_tiff_remembering_config() test_tiff_cmyk() + test_imageinput_api () + # Test is_imageio_format_name print ("is_imageio_format_name('tiff') =", oiio.is_imageio_format_name('tiff')) print ("is_imageio_format_name('txff') =", oiio.is_imageio_format_name('txff'))