From fad4e76429b041864dd2d4689a3399dceaadaf99 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Sun, 20 Apr 2025 08:00:28 -0700 Subject: [PATCH 1/2] feat: Add python stub files for code completion and static analysis Signed-off-by: Chad Dombrova --- .github/workflows/wheel.yml | 4 + INSTALL.md | 13 + Makefile | 5 + pyproject.toml | 20 +- src/cmake/pythonutils.cmake | 3 +- src/python/CMakeLists.txt | 3 +- src/python/stubs/CMakeLists.txt | 27 + src/python/stubs/__init__.pyi | 1617 ++++++++++++++++++++++ src/python/stubs/generate_stubs.py | 215 +++ src/python/stubs/generate_stubs_local.py | 62 + src/python/stubs/py.typed | 0 11 files changed, 1966 insertions(+), 3 deletions(-) create mode 100644 src/python/stubs/CMakeLists.txt create mode 100644 src/python/stubs/__init__.pyi create mode 100644 src/python/stubs/generate_stubs.py create mode 100644 src/python/stubs/generate_stubs_local.py create mode 100644 src/python/stubs/py.typed diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 22906ae7e9..3dc4ed01c0 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -148,8 +148,12 @@ jobs: python-version: '3.9' - name: Build wheels + # Note: the version of cibuildwheel should be kept in sync with src/python/stubs/CMakeLists.txt uses: pypa/cibuildwheel@d4a2945fcc8d13f20a1b99d461b8e844d5fc6e23 # v2.21.1 env: + # pass GITHUB_ACTIONS through to the build container so that custom + # processes can tell they are running in CI. + CIBW_ENVIRONMENT_PASS_LINUX: GITHUB_ACTIONS CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} diff --git a/INSTALL.md b/INSTALL.md index 78090489db..9856e81a95 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -389,6 +389,19 @@ the headers, and the CLI tools to a platform-specific, Python-specific location. See the [scikit-build-core docs](https://scikit-build-core.readthedocs.io/en/latest/configuration.html#configuring-cmake-arguments-and-defines) for more information on customizing and overriding build-tool options and CMake arguments. +This repo contains python type stubs which are generated from `pybind11` signatures. +The workflow for releasing new stubs is as follows: + +- Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) +- Run `make pystubs` locally to generate updated stubs in `src/python/stubs/__init__.pyi` +- Commit the new stubs and push to Github +- In CI, the stubs will be included in the wheels built by `cibuildwheel`, as defined in `.github/wheel.yml` +- In CI, one of the `cibuildwheel` Github actions will rebuild the stubs to a + temp location and verify that they match what has been committed to the repo. + This step ensures that if changes to the C++ source code and bindings results + in a change to the stubs, developers are notified of the need to regenerate + the stubs, so that changes can be reviewed and the rules in `generate_stubs.py` + can be updated, if necessary. Test Images ----------- diff --git a/Makefile b/Makefile index 211fefd4a6..bf7543140f 100644 --- a/Makefile +++ b/Makefile @@ -243,6 +243,11 @@ build: config ${CMAKE} --build . --config ${CMAKE_BUILD_TYPE} \ ) +pystubs: config + @ ( cd ${build_dir} ; \ + ${CMAKE} --build . --config ${CMAKE_BUILD_TYPE} --target pystubs \ + ) + # 'make install' builds everthing and installs it in 'dist'. # Suppress pointless output from docs installation. install: build diff --git a/pyproject.toml b/pyproject.toml index f87303460a..7f89fbb9f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,4 +122,22 @@ CXXFLAGS = "-Wno-error=stringop-overflow -Wno-pragmas" SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" [tool.cibuildwheel.windows.environment] -SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" \ No newline at end of file +SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" + +[[tool.cibuildwheel.overrides]] +# Trigger the build & validation of the python stubs for certain platforms. +# The test command acts as a kind of "post-build" callback where it's possible +# for the stub-generator to import the freshly-built wheel. +# There are two entry-points which are designed to call generate_stubs.py through +# this test command: +# - `make pystubs` is called during local development to generate the +# stubs and copy them into the git repo to be committed and reviewed. +# - in CI, the cibuildwheel action is used to validate that the stubs match what +# has been committed to the repo. +test-requires = "mypy~=1.15.0 stubgenlib~=0.1.0" +# Note: the python version here must be kept in sync with src/python/stubs/CMakeLists.txt +select = "cp311-manylinux_*64" +inherit.test-command = "append" +test-command = [ + "python {project}/src/python/stubs/generate_stubs.py --out-path '/output' --validate-path '{project}/src/python/stubs/__init__.pyi'", +] diff --git a/src/cmake/pythonutils.cmake b/src/cmake/pythonutils.cmake index 33c1584f54..adf3fbcbb8 100644 --- a/src/cmake/pythonutils.cmake +++ b/src/cmake/pythonutils.cmake @@ -156,7 +156,8 @@ macro (setup_python_module) RUNTIME DESTINATION ${PYTHON_SITE_DIR} COMPONENT user LIBRARY DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) - install(FILES __init__.py DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) + install (FILES __init__.py stubs/__init__.pyi stubs/py.typed + DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) endmacro () diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index e0b50dde5c..94b2a3e4d3 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO - +add_subdirectory (stubs) file (GLOB python_srcs *.cpp) setup_python_module (TARGET PyOpenImageIO @@ -19,3 +19,4 @@ set_target_properties(PyOpenImageIO PROPERTIES UNITY_BUILD_BATCH_SIZE ${UNITY_SMALL_BATCH_SIZE}) set_source_files_properties(${python_srcs} PROPERTIES UNITY_GROUP PyOpenImageIO) + diff --git a/src/python/stubs/CMakeLists.txt b/src/python/stubs/CMakeLists.txt new file mode 100644 index 0000000000..c83732ca7f --- /dev/null +++ b/src/python/stubs/CMakeLists.txt @@ -0,0 +1,27 @@ + +# Setup the pystub target, which is not built by default. + +set (_stub_file "${CMAKE_SOURCE_DIR}/src/python/stubs/__init__.pyi") + +# Note: the python version must be kept in sync with `[[tool.cibuildwheel.overrides]]` in pyproject.toml. +# The stubs are generated within a container so the version of python does not need to match +# the version of python that OpenImageIO is being built against. +# Note: the version of cibuildwheel should be kept in sync with .github/workflows/wheel.yml +add_custom_command (COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/src/python/stubs/generate_stubs_local.py + --repo-root ${CMAKE_SOURCE_DIR} --python-version="3.11" --cibuildwheel-version="2.21.1" + --output-dir "${CMAKE_BINARY_DIR}/wheelhouse" + OUTPUT "${CMAKE_BINARY_DIR}/wheelhouse/OpenImageIO/__init__.pyi" + DEPENDS "${CMAKE_SOURCE_DIR}/src/python/stubs/generate_stubs.py" + DEPENDS "${CMAKE_SOURCE_DIR}/src/python/stubs/generate_stubs_local.py" + COMMENT "pystubs: Generating python stubs" + ) + +add_custom_command (COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_BINARY_DIR}/wheelhouse/OpenImageIO/__init__.pyi" + ${_stub_file} + OUTPUT ${_stub_file} + DEPENDS "${CMAKE_BINARY_DIR}/wheelhouse/OpenImageIO/__init__.pyi" + COMMENT "pystubs: Copying generated stubs to source" + ) + +add_custom_target (pystubs DEPENDS ${_stub_file}) diff --git a/src/python/stubs/__init__.pyi b/src/python/stubs/__init__.pyi new file mode 100644 index 0000000000..0fe6f20c98 --- /dev/null +++ b/src/python/stubs/__init__.pyi @@ -0,0 +1,1617 @@ +# +# This file is auto-generated. DO NOT MODIFY! Run `make pystubs` to regenerate +# + +import numpy +import typing +from _typeshed import Incomplete +from typing import ClassVar, Iterator, overload + +AutoStride: int +BOX: VECSEMANTICS +CHAR: BASETYPE +COLOR: VECSEMANTICS +DOUBLE: BASETYPE +FLOAT: BASETYPE +HALF: BASETYPE +INT: BASETYPE +INT16: BASETYPE +INT32: BASETYPE +INT64: BASETYPE +INT8: BASETYPE +INTRO_STRING: str +KEYCODE: VECSEMANTICS +LASTBASE: BASETYPE +LONGLONG: BASETYPE +MATRIX33: AGGREGATE +MATRIX44: AGGREGATE +MakeTxBumpWithSlopes: MakeTextureMode +MakeTxEnvLatl: MakeTextureMode +MakeTxEnvLatlFromLightProbe: MakeTextureMode +MakeTxShadow: MakeTextureMode +MakeTxTexture: MakeTextureMode +NONE: BASETYPE +NONFINITE_BLACK: NonFiniteFixMode +NONFINITE_BOX3: NonFiniteFixMode +NONFINITE_NONE: NonFiniteFixMode +NORMAL: VECSEMANTICS +NOSEMANTICS: VECSEMANTICS +NOXFORM: VECSEMANTICS +OpenColorIO_version_hex: int +POINT: VECSEMANTICS +PTR: BASETYPE +RATIONAL: VECSEMANTICS +SCALAR: AGGREGATE +SHORT: BASETYPE +STRING: BASETYPE +TIMECODE: VECSEMANTICS +TypeBox2: TypeDesc +TypeBox2i: TypeDesc +TypeBox3: TypeDesc +TypeBox3i: TypeDesc +TypeColor: TypeDesc +TypeFloat: TypeDesc +TypeFloat2: TypeDesc +TypeFloat4: TypeDesc +TypeHalf: TypeDesc +TypeInt: TypeDesc +TypeInt16: TypeDesc +TypeInt32: TypeDesc +TypeInt64: TypeDesc +TypeInt8: TypeDesc +TypeKeyCode: TypeDesc +TypeMatrix: TypeDesc +TypeMatrix33: TypeDesc +TypeMatrix44: TypeDesc +TypeNormal: TypeDesc +TypePoint: TypeDesc +TypePointer: TypeDesc +TypeRational: TypeDesc +TypeString: TypeDesc +TypeTimeCode: TypeDesc +TypeUInt: TypeDesc +TypeUInt16: TypeDesc +TypeUInt32: TypeDesc +TypeUInt64: TypeDesc +TypeUInt8: TypeDesc +TypeUnknown: TypeDesc +TypeVector: TypeDesc +TypeVector2: TypeDesc +TypeVector2i: TypeDesc +TypeVector3i: TypeDesc +TypeVector4: TypeDesc +UCHAR: BASETYPE +UINT: BASETYPE +UINT16: BASETYPE +UINT32: BASETYPE +UINT64: BASETYPE +UINT8: BASETYPE +ULONGLONG: BASETYPE +UNKNOWN: BASETYPE +USHORT: BASETYPE +VEC2: AGGREGATE +VEC3: AGGREGATE +VEC4: AGGREGATE +VECTOR: VECSEMANTICS +VERSION: int +VERSION_MAJOR: int +VERSION_MINOR: int +VERSION_PATCH: int +VERSION_STRING: str +__version__: str +openimageio_version: int +supportsOpenColorIO: bool + +class AGGREGATE: + __members__: ClassVar[dict] = ... # read-only + MATRIX33: ClassVar[AGGREGATE] = ... + MATRIX44: ClassVar[AGGREGATE] = ... + SCALAR: ClassVar[AGGREGATE] = ... + VEC2: ClassVar[AGGREGATE] = ... + VEC3: ClassVar[AGGREGATE] = ... + VEC4: ClassVar[AGGREGATE] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class BASETYPE: + __members__: ClassVar[dict] = ... # read-only + CHAR: ClassVar[BASETYPE] = ... + DOUBLE: ClassVar[BASETYPE] = ... + FLOAT: ClassVar[BASETYPE] = ... + HALF: ClassVar[BASETYPE] = ... + INT: ClassVar[BASETYPE] = ... + INT16: ClassVar[BASETYPE] = ... + INT32: ClassVar[BASETYPE] = ... + INT64: ClassVar[BASETYPE] = ... + INT8: ClassVar[BASETYPE] = ... + LASTBASE: ClassVar[BASETYPE] = ... + LONGLONG: ClassVar[BASETYPE] = ... + NONE: ClassVar[BASETYPE] = ... + PTR: ClassVar[BASETYPE] = ... + SHORT: ClassVar[BASETYPE] = ... + STRING: ClassVar[BASETYPE] = ... + UCHAR: ClassVar[BASETYPE] = ... + UINT: ClassVar[BASETYPE] = ... + UINT16: ClassVar[BASETYPE] = ... + UINT32: ClassVar[BASETYPE] = ... + UINT64: ClassVar[BASETYPE] = ... + UINT8: ClassVar[BASETYPE] = ... + ULONGLONG: ClassVar[BASETYPE] = ... + UNKNOWN: ClassVar[BASETYPE] = ... + USHORT: ClassVar[BASETYPE] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class ColorConfig: + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: str, /) -> None: ... + def configname(self) -> str: ... + @staticmethod + def default_colorconfig() -> ColorConfig: ... + def equivalent(self, color_space: str, other_color_space: str) -> bool: ... + def getAliases(self, arg0: str, /) -> list[str]: ... + def getColorSpaceDataType(self, name: str) -> tuple[TypeDesc, int]: ... + def getColorSpaceFamilyByName(self, name: str) -> str: ... + def getColorSpaceFromFilepath(self, arg0: str, /) -> str: ... + def getColorSpaceIndex(self, name: str) -> int: ... + def getColorSpaceNameByIndex(self, arg0: int, /) -> str: ... + def getColorSpaceNameByRole(self, role: str) -> str: ... + def getColorSpaceNames(self) -> list[str]: ... + def getDefaultDisplayName(self) -> str: ... + def getDefaultViewName(self, display: str = ...) -> str: ... + def getDisplayNameByIndex(self, arg0: int, /) -> str: ... + def getDisplayNames(self) -> list[str]: ... + def getDisplayViewColorSpaceName(self, display: str, view: str) -> str: ... + def getDisplayViewLooks(self, display: str, view: str) -> str: ... + def getLookNameByIndex(self, arg0: int, /) -> str: ... + def getLookNames(self) -> list[str]: ... + def getNamedTransformAliases(self, arg0: str, /) -> list[str]: ... + def getNamedTransformNameByIndex(self, arg0: int, /) -> str: ... + def getNamedTransformNames(self) -> list[str]: ... + def getNumColorSpaces(self) -> int: ... + def getNumDisplays(self) -> int: ... + def getNumLooks(self) -> int: ... + def getNumNamedTransforms(self) -> int: ... + def getNumRoles(self) -> int: ... + def getNumViews(self, display: str = ...) -> int: ... + def getRoleByIndex(self, arg0: int, /) -> str: ... + def getRoles(self) -> list[str]: ... + def getViewNameByIndex(self, display: str = ..., *, index: int) -> str: ... + def getViewNames(self, display: str = ...) -> list[str]: ... + def geterror(self) -> str: ... + def parseColorSpaceFromString(self, arg0: str, /) -> str: ... + def resolve(self, name: str) -> str: ... + +class CompareResults: + def __init__(self) -> None: ... + @property + def PSNR(self) -> float: ... + @property + def error(self) -> bool: ... + @property + def maxc(self) -> int: ... + @property + def maxerror(self) -> float: ... + @property + def maxx(self) -> int: ... + @property + def maxy(self) -> int: ... + @property + def maxz(self) -> int: ... + @property + def meanerror(self) -> float: ... + @property + def nfail(self) -> int: ... + @property + def nwarn(self) -> int: ... + @property + def rms_error(self) -> float: ... + +class DeepData: + def __init__(self) -> None: ... + def allocated(self) -> bool: ... + def capacity(self, pixel: int) -> int: ... + def channelname(self, arg0: int, /) -> str: ... + def channelsize(self, arg0: int, /) -> int: ... + def channeltype(self, arg0: int, /) -> TypeDesc: ... + def clear(self) -> None: ... + def copy_deep_pixel(self, pixel: int, src: DeepData, srcpixel: int) -> bool: ... + def copy_deep_sample(self, pixel: int, sample: int, src: DeepData, srcpixel: int, srcsample: int) -> bool: ... + def deep_value(self, pixel: int, channel: int, sample: int) -> float: ... + def deep_value_uint(self, pixel: int, channel: int, sample: int) -> int: ... + def erase_samples(self, pixel: int, samplepos: int, nsamples: int = ...) -> None: ... + def free(self) -> None: ... + @overload + def init(self, npixels: int, nchannels: int, channeltypes: object, channelnames: object) -> None: ... + @overload + def init(self, arg0: ImageSpec, /) -> None: ... + def initialized(self) -> bool: ... + def insert_samples(self, pixel: int, samplepos: int, nsamples: int = ...) -> None: ... + def merge_deep_pixels(self, pixel: int, src: DeepData, srcpixel: int) -> None: ... + def merge_overlaps(self, pixel: int) -> None: ... + def occlusion_cull(self, pixel: int) -> None: ... + def opaque_z(self, pixel: int) -> float: ... + def same_channeltypes(self, arg0: DeepData, /) -> bool: ... + def samples(self, pixel: int) -> int: ... + def samplesize(self) -> int: ... + def set_capacity(self, pixel: int, nsamples: int) -> None: ... + def set_deep_value(self, pixel: int, channel: int, sample: int, value: float) -> None: ... + def set_deep_value_uint(self, pixel: int, channel: int, sample: int, value: int) -> None: ... + def set_samples(self, pixel: int, nsamples: int) -> None: ... + def sort(self, pixel: int) -> None: ... + def split(self, pixel: int, depth: float) -> bool: ... + @property + def AB_channel(self) -> int: ... + @property + def AG_channel(self) -> int: ... + @property + def AR_channel(self) -> int: ... + @property + def A_channel(self) -> int: ... + @property + def Z_channel(self) -> int: ... + @property + def Zback_channel(self) -> int: ... + @property + def channels(self) -> int: ... + @property + def pixels(self) -> int: ... + +class ImageBuf: + orientation: int + roi_full: ROI + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: str, /) -> None: ... + @overload + def __init__(self, arg0: str, arg1: int, arg2: int, /) -> None: ... + @overload + def __init__(self, arg0: ImageSpec, /) -> None: ... + @overload + def __init__(self, arg0: ImageSpec, arg1: bool, /) -> None: ... + @overload + def __init__(self, name: str, subimage: int, miplevel: int, config: ImageSpec) -> None: ... + @overload + def __init__(self, buffer: numpy.ndarray) -> None: ... + def clear(self) -> None: ... + def clear_thumbnail(self) -> None: ... + @overload + def copy(self, src: ImageBuf, format: TypeDesc | BASETYPE | str = ...) -> bool: ... + @overload + def copy(self, format: TypeDesc | BASETYPE | str = ...) -> ImageBuf: ... + def copy_metadata(self, arg0: ImageBuf, /) -> None: ... + def copy_pixels(self, arg0: ImageBuf, /) -> bool: ... + def deep_erase_samples(self, x: int, y: int, z: int = ..., *, samplepos: int, nsamples: int = ...) -> None: ... + def deep_insert_samples(self, x: int, y: int, z: int = ..., *, samplepos: int, nsamples: int = ...) -> None: ... + def deep_samples(self, x: int, y: int, z: int = ...) -> int: ... + def deep_value(self, x: int, y: int, z: int, channel: int, sample: int) -> float: ... + def deep_value_uint(self, x: int, y: int, z: int, channel: int, sample: int) -> int: ... + def deepdata(self) -> DeepData: ... + def get_pixels(self, format: TypeDesc | BASETYPE | str = ..., roi: ROI = ...) -> numpy.ndarray | None: ... + def get_thumbnail(self) -> ImageBuf: ... + def getchannel(self, x: int, y: int, z: int, c: int, wrap=...) -> float: ... + def geterror(self, clear: bool = ...) -> str: ... + def getpixel(self, x: int, y: int, z: int = ..., wrap: str = ...) -> tuple[float, ...]: ... + def init_spec(self, filename: str, subimage: int = ..., miplevel: int = ...) -> None: ... + def interppixel(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_NDC(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_NDC_full(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_bicubic(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... + def interppixel_bicubic_NDC(self, x: float, y: float, wrap: str = ...) -> tuple[float, ...]: ... + def make_writable(self, keep_cache_type: bool = ...) -> bool: ... + def merge_metadata(self, src: ImageBuf, override: bool = ..., pattern: str = ...) -> None: ... + def nativespec(self) -> ImageSpec: ... + def pixelindex(self, x: int, y: int, z: int, check_range: bool = ...) -> int: ... + @overload + def read(self, subimage: int, miplevel: int, chbegin: int, chend: int, force: bool, convert: TypeDesc | BASETYPE | str) -> bool: ... + @overload + def read(self, subimage: int = ..., miplevel: int = ..., force: bool = ..., convert: TypeDesc | BASETYPE | str = ...) -> bool: ... + @overload + def reset(self, name: str, subimage: int = ..., miplevel: int = ...) -> None: ... + @overload + def reset(self, name: str, subimage: int = ..., miplevel: int = ..., config: ImageSpec = ...) -> None: ... + @overload + def reset(self, spec: ImageSpec, zero: bool = ...) -> None: ... + @overload + def reset(self, buffer: numpy.ndarray) -> None: ... + def set_deep_samples(self, x: int, y: int, z: int = ..., nsamples: int = ...) -> None: ... + def set_deep_value(self, x: int, y: int, z: int, channel: int, sample: int, value: float = ...) -> None: ... + def set_deep_value_uint(self, x: int, y: int, z: int, channel: int, sample: int, value: int = ...) -> None: ... + def set_full(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, /) -> None: ... + def set_origin(self, x: int, y: int, z: int = ...) -> None: ... + def set_pixels(self, roi: ROI, pixels: numpy.ndarray) -> bool: ... + def set_thumbnail(self, thumb: ImageBuf) -> None: ... + def set_write_format(self, arg0: object, /) -> None: ... + def set_write_tiles(self, width: int = ..., height: int = ..., depth: int = ...) -> None: ... + @overload + def setpixel(self, x: int, y: int, z: int, pixel: object) -> None: ... + @overload + def setpixel(self, x: int, y: int, pixel: object) -> None: ... + @overload + def setpixel(self, i: int, pixel: object) -> None: ... + def spec(self) -> ImageSpec: ... + def specmod(self) -> ImageSpec: ... + def swap(self, arg0: ImageBuf, /) -> None: ... + @overload + def write(self, filename: str, dtype: TypeDesc | BASETYPE | str = ..., fileformat: str = ...) -> bool: ... + @overload + def write(self, out: ImageOutput) -> bool: ... + @property + def deep(self) -> bool: ... + @property + def file_format_name(self) -> str: ... + @property + def has_error(self) -> bool: ... + @property + def has_thumbnail(self) -> bool: ... + @property + def initialized(self) -> bool: ... + @property + def miplevel(self) -> int: ... + @property + def name(self) -> str: ... + @property + def nchannels(self) -> int: ... + @property + def nmiplevels(self) -> int: ... + @property + def nsubimages(self) -> int: ... + @property + def oriented_full_height(self) -> int: ... + @property + def oriented_full_width(self) -> int: ... + @property + def oriented_full_x(self) -> int: ... + @property + def oriented_full_y(self) -> int: ... + @property + def oriented_height(self) -> int: ... + @property + def oriented_width(self) -> int: ... + @property + def oriented_x(self) -> int: ... + @property + def oriented_y(self) -> int: ... + @property + def pixels_valid(self) -> bool: ... + @property + def pixeltype(self) -> TypeDesc: ... + @property + def roi(self) -> ROI: ... + @property + def subimage(self) -> int: ... + @property + def xbegin(self) -> int: ... + @property + def xend(self) -> int: ... + @property + def xmax(self) -> int: ... + @property + def xmin(self) -> int: ... + @property + def ybegin(self) -> int: ... + @property + def yend(self) -> int: ... + @property + def ymax(self) -> int: ... + @property + def ymin(self) -> int: ... + @property + def zbegin(self) -> int: ... + @property + def zend(self) -> int: ... + @property + def zmax(self) -> int: ... + @property + def zmin(self) -> int: ... + +class ImageBufAlgo: + def __init__(self, *args, **kwargs) -> None: ... + @overload + @staticmethod + def abs(dst: ImageBuf, A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def abs(A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def absdiff(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def absdiff(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def absdiff(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def absdiff(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def add(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def add(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def add(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def add(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def bluenoise_image() -> ImageBuf: ... + @overload + @staticmethod + def channel_append(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def channel_append(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def channel_sum(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def channel_sum(dst: ImageBuf, src: ImageBuf, weight: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def channel_sum(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def channel_sum(src: ImageBuf, weight: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def channels(dst: ImageBuf, src: ImageBuf, channelorder: tuple, newchannelnames: tuple = ..., shuffle_channel_names: bool = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def channels(src: ImageBuf, channelorder: tuple, newchannelnames: tuple = ..., shuffle_channel_names: bool = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def checker(dst: ImageBuf, width: int, height: int, depth: int, color1: object, color2: object, xoffset: int = ..., yoffset: int = ..., zoffset: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def checker(width: int, height: int, depth: int, color1: object, color2: object, xoffset: int = ..., yoffset: int = ..., zoffset: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def circular_shift(dst: ImageBuf, src: ImageBuf, xshift: int, yshift: int, zshift: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def circular_shift(src: ImageBuf, xshift: int, yshift: int, zshift: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def clamp(dst: ImageBuf, src: ImageBuf, min: float | typing.Iterable[float], max: float | typing.Iterable[float], clampalpha01: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def clamp(src: ImageBuf, min: float | typing.Iterable[float], max: float | typing.Iterable[float], clampalpha01: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def color_map(dst: ImageBuf, src: ImageBuf, srcchannel: int, mapname: str, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def color_map(dst: ImageBuf, src: ImageBuf, srcchannel: int, nknots: int, channels: int, knots: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def color_map(src: ImageBuf, srcchannel: int, mapname: str, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def color_map(src: ImageBuf, srcchannel: int, nknots: int, channels: int, knots: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def color_range_check(src: ImageBuf, low: object, high: object, roi: ROI = ..., nthreads: int = ...) -> tuple[int, ...] | None: ... + @overload + @staticmethod + def colorconvert(dst: ImageBuf, src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def colorconvert(dst: ImageBuf, src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def colorconvert(src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def colorconvert(src: ImageBuf, fromspace: str, tospace: str, unpremult: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def colormatrixtransform(dst: ImageBuf, src: ImageBuf, M: object, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def colormatrixtransform(src: ImageBuf, M: object, unpremult: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def compare(A: ImageBuf, B: ImageBuf, failthresh: float, warnthresh: float, result: CompareResults, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def compare(A: ImageBuf, B: ImageBuf, failthresh: float, warnthresh: float, failrelative: float = ..., warnrelative: float = ..., roi: ROI = ..., nthreads: int = ...) -> CompareResults: ... + @overload + @staticmethod + def compare(A: ImageBuf, B: ImageBuf, failthresh: float, warnthresh: float, roi: ROI = ..., nthreads: int = ...) -> CompareResults: ... + @staticmethod + def compare_Yee(A: ImageBuf, B: ImageBuf, result: CompareResults, luminance: float = ..., fov: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def complex_to_polar(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def complex_to_polar(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def computePixelHashSHA1(src: ImageBuf, extrainfo: str = ..., roi: ROI = ..., blocksize: int = ..., nthreads: int = ...) -> str: ... + @overload + @staticmethod + def computePixelStats(src: ImageBuf, stats: PixelStats, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def computePixelStats(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> PixelStats: ... + @overload + @staticmethod + def contrast_remap(dst: ImageBuf, src: ImageBuf, black: float | typing.Iterable[float] = ..., white: float | typing.Iterable[float] = ..., min: float | typing.Iterable[float] = ..., max: float | typing.Iterable[float] = ..., scontrast: float | typing.Iterable[float] = ..., sthresh: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def contrast_remap(src: ImageBuf, black: float | typing.Iterable[float] = ..., white: float | typing.Iterable[float] = ..., min: float | typing.Iterable[float] = ..., max: float | typing.Iterable[float] = ..., scontrast: float | typing.Iterable[float] = ..., sthresh: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def convolve(dst: ImageBuf, src: ImageBuf, kernel: ImageBuf, normalze: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def convolve(src: ImageBuf, kernel: ImageBuf, normalze: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def copy(dst: ImageBuf, src: ImageBuf, convert: TypeDesc | BASETYPE | str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def copy(src: ImageBuf, convert: TypeDesc | BASETYPE | str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def crop(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def crop(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def cut(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def cut(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def deep_holdout(dst: ImageBuf, src: ImageBuf, holdout: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def deep_holdout(src: ImageBuf, holdout: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def deep_merge(dst: ImageBuf, A: ImageBuf, B: ImageBuf, occlusion_cull: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def deep_merge(A: ImageBuf, B: ImageBuf, occlusion_cull: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def deepen(dst: ImageBuf, src: ImageBuf, zvalue: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def deepen(src: ImageBuf, zvalue: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def demosaic(dst: ImageBuf, src: ImageBuf, pattern: str = ..., algorithm: str = ..., layout: str = ..., white_balance: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def demosaic(src: ImageBuf, pattern: str = ..., algorithm: str = ..., layout: str = ..., white_balance: float | typing.Iterable[float] = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def dilate(dst: ImageBuf, src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def dilate(src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def div(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def div(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def div(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def div(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def erode(dst: ImageBuf, src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def erode(src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def fft(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def fft(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def fill(dst: ImageBuf, values: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def fill(dst: ImageBuf, top: float | typing.Iterable[float], bottom: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def fill(dst: ImageBuf, topleft: float | typing.Iterable[float], topright: float | typing.Iterable[float], bottomleft: float | typing.Iterable[float], bottomright: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def fill(values: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def fill(top: float | typing.Iterable[float], bottom: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def fill(topleft: float | typing.Iterable[float], topright: float | typing.Iterable[float], bottomleft: float | typing.Iterable[float], bottomright: float | typing.Iterable[float], roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def fillholes_pushpull(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def fillholes_pushpull(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def fit(dst: ImageBuf, src: ImageBuf, filtername: str = ..., filterwidth: float = ..., fillmode: str = ..., exact: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def fit(src: ImageBuf, filtername: str = ..., filterwidth: float = ..., fillmode: str = ..., exact: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def fixNonFinite(dst: ImageBuf, src: ImageBuf, mode: NonFiniteFixMode = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def fixNonFinite(src: ImageBuf, mode: NonFiniteFixMode = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def flatten(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def flatten(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def flip(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def flip(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def flop(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def flop(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def histogram(src: ImageBuf, channel: int = ..., bins: int = ..., min: float = ..., max: float = ..., ignore_empty: bool = ..., roi: ROI = ..., nthreads: int = ...) -> tuple[int, ...]: ... + @overload + @staticmethod + def ifft(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ifft(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def invert(dst: ImageBuf, A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def invert(A: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def isConstantChannel(src: ImageBuf, channel: int, val: float, threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @staticmethod + def isConstantColor(src: ImageBuf, threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> tuple[float, ...] | None: ... + @staticmethod + def isMonochrome(src: ImageBuf, threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def laplacian(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def laplacian(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def mad(dst: ImageBuf, A: ImageBuf, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def mad(dst: ImageBuf, A: ImageBuf, B: object, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def mad(dst: ImageBuf, A: object, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def mad(dst: ImageBuf, A: ImageBuf, B: object, C: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def mad(A: ImageBuf, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def mad(A: ImageBuf, B: object, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def mad(A: object, B: ImageBuf, C: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def mad(A: ImageBuf, B: object, C: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def make_kernel(dst: ImageBuf, name: str, width: float, height: float, depth: float = ..., normalize: bool = ...) -> bool: ... + @overload + @staticmethod + def make_kernel(name: str, width: float, height: float, depth: float = ..., normalize: bool = ...) -> ImageBuf: ... + @overload + @staticmethod + def make_texture(mode: MakeTextureMode, filename: str, outputfilename: str, config: ImageSpec = ...) -> bool: ... + @overload + @staticmethod + def make_texture(mode: MakeTextureMode, buf: ImageBuf, outputfilename: str, config: ImageSpec = ...) -> bool: ... + @overload + @staticmethod + def max(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def max(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def max(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def max(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def maxchan(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def maxchan(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def median_filter(dst: ImageBuf, src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def median_filter(src: ImageBuf, width: int = ..., height: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def min(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def min(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def min(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def min(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def minchan(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def minchan(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def mul(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def mul(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def mul(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def mul(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def noise(dst: ImageBuf, type: str = ..., A: float = ..., B: float = ..., mono: bool = ..., seed: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def noise(type: str = ..., A: float = ..., B: float = ..., mono: bool = ..., seed: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def nonzero_region(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ROI: ... + @overload + @staticmethod + def normalize(dst: ImageBuf, src: ImageBuf, inCenter: float = ..., outCenter: float = ..., scale: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def normalize(src: ImageBuf, inCenter: float = ..., outCenter: float = ..., scale: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str = ..., looks: str = ..., unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociodisplay(dst: ImageBuf, src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str, colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociodisplay(src: ImageBuf, display: str, view: str, fromspace: str, looks: str, unpremult: bool, context_key: str, context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociofiletransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociofiletransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociofiletransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociofiletransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociolook(dst: ImageBuf, src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociolook(dst: ImageBuf, src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ociolook(src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ociolook(src: ImageBuf, looks: str, fromspace: str, tospace: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ocionamedtransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ocionamedtransform(dst: ImageBuf, src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def ocionamedtransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def ocionamedtransform(src: ImageBuf, name: str, unpremult: bool = ..., inverse: bool = ..., context_key: str = ..., context_value: str = ..., colorconfig: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def over(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def over(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def paste(dst: ImageBuf, xbegin: int, ybegin: int, zbegin: int, chbegin: int, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def polar_to_complex(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def polar_to_complex(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def pow(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def pow(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def premult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def premult(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def rangecompress(dst: ImageBuf, src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def rangecompress(src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def rangeexpand(dst: ImageBuf, src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def rangeexpand(src: ImageBuf, useluma: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def render_box(dst: ImageBuf, x1: int, y1: int, x2: int, y2: int, color: float | typing.Iterable[float] = ..., fill: bool = ...) -> bool: ... + @staticmethod + def render_line(dst: ImageBuf, x1: int, y1: int, x2: int, y2: int, color: float | typing.Iterable[float] = ..., skip_first_point: bool = ...) -> bool: ... + @staticmethod + def render_point(dst: ImageBuf, x: int, y: int, color: float | typing.Iterable[float] = ...) -> bool: ... + @staticmethod + def render_text(dst: ImageBuf, x: int, y: int, text: str, fontsize: int = ..., fontname: str = ..., textcolor: object = ..., alignx: str = ..., aligny: str = ..., shadow: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def reorient(dst: ImageBuf, src: ImageBuf, nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def reorient(src: ImageBuf, nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def repremult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def repremult(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def resample(dst: ImageBuf, src: ImageBuf, interpolate: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def resample(src: ImageBuf, interpolate: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def resize(dst: ImageBuf, src: ImageBuf, filtername: str = ..., filterwidth: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def resize(src: ImageBuf, filtername: str = ..., filterwidth: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def rotate(dst: ImageBuf, src: ImageBuf, angle: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def rotate(dst: ImageBuf, src: ImageBuf, angle: float, center_x: float, center_y: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def rotate(src: ImageBuf, angle: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def rotate(src: ImageBuf, angle: float, center_x: float, center_y: float, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def rotate180(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def rotate180(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def rotate270(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def rotate270(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def rotate90(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def rotate90(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def saturate(dst: ImageBuf, src: ImageBuf, scale: float = ..., firstchannel: int = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def saturate(src: ImageBuf, scale: float = ..., firstchannel: int = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def scale(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def scale(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def st_warp(dst: ImageBuf, src: ImageBuf, stbuf: ImageBuf, filtername: str = ..., filterwidth: float = ..., chan_s: int = ..., chan_t: int = ..., flip_s: bool = ..., flip_t: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def st_warp(src: ImageBuf, stbuf: ImageBuf, filtername: str = ..., filterwidth: float = ..., chan_s: int = ..., chan_t: int = ..., flip_s: bool = ..., flip_t: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def sub(dst: ImageBuf, A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def sub(dst: ImageBuf, A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def sub(A: ImageBuf, B: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def sub(A: ImageBuf, B: object, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @staticmethod + def text_size(text: str, fontsize: int = ..., fontname: str = ...) -> ROI: ... + @overload + @staticmethod + def transpose(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def transpose(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def unpremult(dst: ImageBuf, src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def unpremult(src: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def unsharp_mask(dst: ImageBuf, src: ImageBuf, kernel: str = ..., width: float = ..., contrast: float = ..., threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def unsharp_mask(src: ImageBuf, kernel: str = ..., width: float = ..., contrast: float = ..., threshold: float = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def warp(dst: ImageBuf, src: ImageBuf, M: object, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., wrap: str = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def warp(src: ImageBuf, M: object, filtername: str = ..., filterwidth: float = ..., recompute_roi: bool = ..., wrap: str = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def zero(dst: ImageBuf, roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def zero(roi: ROI, nthreads: int = ...) -> ImageBuf: ... + @overload + @staticmethod + def zover(dst: ImageBuf, A: ImageBuf, B: ImageBuf, z_zeroisinf: bool = ..., roi: ROI = ..., nthreads: int = ...) -> bool: ... + @overload + @staticmethod + def zover(A: ImageBuf, B: ImageBuf, z_zeroisinf: bool = ..., roi: ROI = ..., nthreads: int = ...) -> ImageBuf: ... + +class ImageCache: + def __init__(self, shared: bool = ...) -> None: ... + @overload + def attribute(self, arg0: str, arg1: float, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: int, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: str, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... + @staticmethod + def destroy(cache: ImageCache, teardown: bool = ...) -> None: ... + def get_cache_dimensions(self, filename: str, subimage: int = ..., miplevel: int = ...) -> ImageSpec: ... + def get_imagespec(self, filename: str, subimage: int = ...) -> ImageSpec: ... + def get_pixels(self, filename: str, subimage: int, miplevel: int, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int = ..., zend: int = ..., datatype: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def getattribute(self, name: str, type: TypeDesc | BASETYPE | str = ...) -> typing.Any: ... + def getattributetype(self, name: str) -> TypeDesc: ... + def geterror(self, clear: bool = ...) -> str: ... + def getstats(self, level: int = ...) -> str: ... + def invalidate(self, filename: str, force: bool = ...) -> None: ... + def invalidate_all(self, force: bool = ...) -> None: ... + def resolve_filename(self, arg0: str, /) -> str: ... + @property + def has_error(self) -> bool: ... + +class ImageInput: + def __init__(self, *args, **kwargs) -> None: ... + def close(self) -> bool: ... + @staticmethod + def create(filename: str, plugin_searchpath: str = ...) -> ImageInput: ... + def current_miplevel(self) -> int: ... + def current_subimage(self) -> int: ... + def format_name(self) -> str: ... + def get_thumbnail(self, *args, **kwargs): ... + def geterror(self, clear: bool = ...) -> str: ... + @overload + @staticmethod + def open(filename: str) -> ImageInput: ... + @overload + @staticmethod + def open(filename: str, config: ImageSpec) -> ImageInput: ... + @overload + def read_image(self, subimage: int, miplevel: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + @overload + def read_image(self, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + @overload + def read_image(self, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_native_deep_image(self, subimage: int = ..., miplevel: int = ...) -> DeepData: ... + def read_native_deep_scanlines(self, subimage: int, miplevel: int, ybegin: int, yend: int, z: int, chbegin: int, chend: int) -> DeepData: ... + def read_native_deep_tiles(self, subimage: int, miplevel: int, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, chbegin: int, chend: int) -> DeepData: ... + def read_scanline(self, y: int, z: int = ..., format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + @overload + def read_scanlines(self, subimage: int, miplevel: int, ybegin: int, yend: int, z: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + @overload + def read_scanlines(self, ybegin: int, yend: int, z: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def read_tile(self, x: int, y: int, z: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + @overload + def read_tiles(self, subimage: int, miplevel: int, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + @overload + def read_tiles(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, chbegin: int, chend: int, format: TypeDesc | BASETYPE | str = ...) -> numpy.ndarray | None: ... + def seek_subimage(self, arg0: int, arg1: int, /) -> bool: ... + @overload + def spec(self) -> ImageSpec: ... + @overload + def spec(self, subimage: int, miplevel: int = ...) -> ImageSpec: ... + def spec_dimensions(self, subimage: int, miplevel: int = ...) -> ImageSpec: ... + def supports(self, arg0: str, /) -> int: ... + def valid_file(self, arg0: str, /) -> bool: ... + @property + def has_error(self) -> bool: ... + +class ImageOutput: + def __init__(self, *args, **kwargs) -> None: ... + def close(self) -> bool: ... + def copy_image(self, arg0: ImageInput, /) -> bool: ... + @staticmethod + def create(filename: str, plugin_searchpath: str = ...) -> ImageOutput: ... + def format_name(self) -> str: ... + def geterror(self, clear: bool = ...) -> str: ... + @overload + def open(self, filename: str, spec: ImageSpec, mode: str = ...) -> bool: ... + @overload + def open(self, filename: str, specs: list[ImageSpec]) -> bool: ... + @overload + def open(self, arg0: str, arg1: tuple, /) -> bool: ... + def set_thumbnail(self, arg0, /) -> bool: ... + def spec(self) -> ImageSpec: ... + def supports(self, arg0: str, /) -> int: ... + def write_deep_image(self, arg0: DeepData, /) -> bool: ... + def write_deep_scanlines(self, ybegin: int, yend: int, z: int, deepdata: DeepData) -> bool: ... + def write_deep_tiles(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, deepdata: DeepData) -> bool: ... + def write_image(self, arg0: numpy.ndarray, /) -> bool: ... + def write_scanline(self, y: int, z: int, pixels: numpy.ndarray) -> bool: ... + def write_scanlines(self, ybegin: int, yend: int, z: int, pixels: numpy.ndarray) -> bool: ... + def write_tile(self, x: int, y: int, z: int, pixels: numpy.ndarray) -> bool: ... + def write_tiles(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int, zend: int, pixels: numpy.ndarray) -> bool: ... + @property + def has_error(self) -> bool: ... + +class ImageSpec: + alpha_channel: int + channelformats: tuple + channelnames: tuple + deep: bool + depth: int + extra_attribs: ParamValueList + format: TypeDesc + full_depth: int + full_height: int + full_width: int + full_x: int + full_y: int + full_z: int + height: int + nchannels: int + roi: Incomplete + roi_full: Incomplete + tile_depth: int + tile_height: int + tile_width: int + width: int + x: int + y: int + z: int + z_channel: int + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: int, arg1: int, arg2: int, arg3: TypeDesc | BASETYPE | str, /) -> None: ... + @overload + def __init__(self, arg0, arg1: TypeDesc | BASETYPE | str, /) -> None: ... + @overload + def __init__(self, arg0: TypeDesc | BASETYPE | str, /) -> None: ... + @overload + def __init__(self, arg0: ImageSpec, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: float, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: int, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: str, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... + @overload + def channel_bytes(self) -> int: ... + @overload + def channel_bytes(self, channel: int, native: bool = ...) -> int: ... + def channel_name(self, arg0: int, /) -> str: ... + def channelformat(self, arg0: int, /) -> TypeDesc: ... + def channelindex(self, arg0: str, /) -> int: ... + def copy(self) -> ImageSpec: ... + def copy_dimensions(self, other: ImageSpec) -> None: ... + def default_channel_names(self) -> None: ... + def erase_attribute(self, name: str = ..., type: TypeDesc | BASETYPE | str = ..., casesensitive: bool = ...) -> None: ... + def from_xml(self, arg0: str, /) -> None: ... + def get(self, key: str, default: object = ...) -> typing.Any: ... + def get_bytes_attribute(self, name: str, defaultval: str = ...) -> bytes: ... + def get_channelformats(self) -> tuple[TypeDesc, ...]: ... + def get_float_attribute(self, name: str, defaultval: float = ...) -> float: ... + def get_int_attribute(self, name: str, defaultval: int = ...) -> int: ... + def get_string_attribute(self, name: str, defaultval: str = ...) -> str: ... + def getattribute(self, name: str, type: TypeDesc | BASETYPE | str = ...) -> typing.Any: ... + @overload + def image_bytes(self, native: bool = ...) -> int: ... + @overload + def image_bytes(self, native: TypeDesc | BASETYPE | str = ...) -> int: ... + def image_pixels(self) -> int: ... + @staticmethod + def metadata_val(param: ParamValue, human: bool = ...) -> str: ... + @overload + def pixel_bytes(self, native: bool = ...) -> int: ... + @overload + def pixel_bytes(self, chbegin: int, chend: int, native: bool = ...) -> int: ... + @overload + def scanline_bytes(self, native: bool = ...) -> int: ... + @overload + def scanline_bytes(self, arg0: TypeDesc | BASETYPE | str, /) -> int: ... + def serialize(self, format: str = ..., verbose: str = ...) -> str: ... + def set_colorspace(self, name: str) -> None: ... + def set_format(self, arg0: TypeDesc | BASETYPE | str, /) -> None: ... + def size_t_safe(self) -> bool: ... + @overload + def tile_bytes(self, native: bool = ...) -> int: ... + @overload + def tile_bytes(self, arg0: TypeDesc | BASETYPE | str, /) -> int: ... + def tile_pixels(self) -> int: ... + def to_xml(self) -> str: ... + def valid_tile_range(self, xbegin: int, xend: int, ybegin: int, yend: int, zbegin: int = ..., zend: int = ...) -> bool: ... + def __contains__(self, arg0: str, /) -> bool: ... + def __delitem__(self, arg0: str, /) -> None: ... + def __getitem__(self, arg0: str, /) -> object: ... + def __setitem__(self, arg0: str, arg1: object, /) -> None: ... + +class Interp: + __members__: ClassVar[dict] = ... # read-only + CONSTANT: ClassVar[Interp] = ... + INTERP_CONSTANT: ClassVar[Interp] = ... + INTERP_LINEAR: ClassVar[Interp] = ... + INTERP_PERPIECE: ClassVar[Interp] = ... + INTERP_VERTEX: ClassVar[Interp] = ... + LINEAR: ClassVar[Interp] = ... + PERPIECE: ClassVar[Interp] = ... + VERTEX: ClassVar[Interp] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class InterpMode: + __members__: ClassVar[dict] = ... # read-only + Bicubic: ClassVar[InterpMode] = ... + Bilinear: ClassVar[InterpMode] = ... + Closest: ClassVar[InterpMode] = ... + SmartBicubic: ClassVar[InterpMode] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class MakeTextureMode: + __members__: ClassVar[dict] = ... # read-only + MakeTxBumpWithSlopes: ClassVar[MakeTextureMode] = ... + MakeTxEnvLatl: ClassVar[MakeTextureMode] = ... + MakeTxEnvLatlFromLightProbe: ClassVar[MakeTextureMode] = ... + MakeTxShadow: ClassVar[MakeTextureMode] = ... + MakeTxTexture: ClassVar[MakeTextureMode] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class MipMode: + __members__: ClassVar[dict] = ... # read-only + Aniso: ClassVar[MipMode] = ... + Default: ClassVar[MipMode] = ... + NoMIP: ClassVar[MipMode] = ... + OneLevel: ClassVar[MipMode] = ... + Trilinear: ClassVar[MipMode] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class NonFiniteFixMode: + __members__: ClassVar[dict] = ... # read-only + NONFINITE_BLACK: ClassVar[NonFiniteFixMode] = ... + NONFINITE_BOX3: ClassVar[NonFiniteFixMode] = ... + NONFINITE_NONE: ClassVar[NonFiniteFixMode] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class ParamValue: + @overload + def __init__(self, arg0: str, arg1: int, /) -> None: ... + @overload + def __init__(self, arg0: str, arg1: float, /) -> None: ... + @overload + def __init__(self, arg0: str, arg1: str, /) -> None: ... + @overload + def __init__(self, name: str, type: TypeDesc | BASETYPE | str, value: object) -> None: ... + @overload + def __init__(self, name: str, type: TypeDesc | BASETYPE | str, nvalues: int, interp: Interp, value: object) -> None: ... + @property + def name(self) -> str: ... + @property + def type(self) -> TypeDesc: ... + @property + def value(self) -> object: ... + @property + def __len__(self) -> int: ... + +class ParamValueList: + def __init__(self) -> None: ... + def add_or_replace(self, value: ParamValue, casesensitive: bool = ...) -> None: ... + def append(self, arg0: ParamValue, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: float, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: int, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: str, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: int, arg3: object, /) -> None: ... + def clear(self) -> None: ... + def contains(self, name: str, type: TypeDesc | BASETYPE | str = ..., casesensitive: bool = ...) -> bool: ... + def free(self) -> None: ... + def merge(self, other: ParamValueList, override: bool = ...) -> None: ... + def remove(self, name: str, type: TypeDesc | BASETYPE | str = ..., casesensitive: bool = ...) -> None: ... + def resize(self, arg0: int, /) -> None: ... + def sort(self, casesensitive: bool = ...) -> None: ... + def __contains__(self, arg0: str, /) -> bool: ... + def __delitem__(self, arg0: str, /) -> None: ... + @overload + def __getitem__(self, arg0: int, /) -> ParamValue: ... + @overload + def __getitem__(self, arg0: str, /) -> object: ... + def __iter__(self) -> Iterator[ParamValue]: ... + def __len__(self) -> int: ... + def __setitem__(self, arg0: str, arg1: object, /) -> None: ... + +class PixelStats: + def __init__(self) -> None: ... + @property + def avg(self) -> list[float]: ... + @property + def finitecount(self) -> list[int]: ... + @property + def infcount(self) -> list[int]: ... + @property + def max(self) -> list[float]: ... + @property + def min(self) -> list[float]: ... + @property + def nancount(self) -> list[int]: ... + @property + def stddev(self) -> list[float]: ... + @property + def sum(self) -> list[float]: ... + @property + def sum2(self) -> list[float]: ... + +class ROI: + All: ClassVar[ROI] = ... # read-only + chbegin: int + chend: int + xbegin: int + xend: int + ybegin: int + yend: int + zbegin: int + zend: int + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, /) -> None: ... + @overload + def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, /) -> None: ... + @overload + def __init__(self, arg0: int, arg1: int, arg2: int, arg3: int, arg4: int, arg5: int, arg6: int, arg7: int, /) -> None: ... + @overload + def __init__(self, arg0: ROI, /) -> None: ... + @overload + def contains(self, x: int, y: int, z: int = ..., ch: int = ...) -> bool: ... + @overload + def contains(self, other: ROI) -> bool: ... + def copy(self) -> ROI: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + @property + def defined(self) -> bool: ... + @property + def depth(self) -> int: ... + @property + def height(self) -> int: ... + @property + def nchannels(self) -> int: ... + @property + def npixels(self) -> int: ... + @property + def width(self) -> int: ... + +class TextureOpt: + anisotropic: int + conservative_filter: bool + fill: float + firstchannel: int + interpmode: InterpMode + mipmode: MipMode + missingcolor: tuple + rnd: float + rwidth: float + rwrap: Wrap + sblur: float + subimage: int + subimagename: str + swidth: float + swrap: Wrap + tblur: float + twidth: float + twrap: Wrap + def __init__(self) -> None: ... + +class TextureSystem: + def __init__(self, shared: bool = ...) -> None: ... + @overload + def attribute(self, arg0: str, arg1: float, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: int, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: str, /) -> None: ... + @overload + def attribute(self, arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... + def close(self, filename: str) -> None: ... + def close_all(self) -> None: ... + @staticmethod + def destroy(arg0: TextureSystem, /) -> None: ... + def environment(self, filename: str, options: TextureOpt, R, dRdx, dRdy, nchannels: int) -> tuple[float, ...]: ... + def getattribute(self, name: str, type: TypeDesc | BASETYPE | str = ...) -> typing.Any: ... + def getattributetype(self, name: str) -> TypeDesc: ... + def geterror(self, clear: bool = ...) -> str: ... + def getstats(self, level: int = ..., icstats: bool = ...) -> str: ... + def has_error(self) -> bool: ... + def imagespec(self, filename: str, subimage: int = ...) -> ImageSpec | None: ... + def invalidate(self, filename: str, force: bool = ...) -> None: ... + def invalidate_all(self, force: bool = ...) -> None: ... + def inventory_udim(self, filename: str) -> tuple: ... + def is_udim(self, filename: str) -> bool: ... + def reset_stats(self) -> None: ... + def resolve_filename(self, filename: str) -> str: ... + def resolve_udim(self, filename: str, s: float, t: float) -> str: ... + def texture(self, filename: str, options: TextureOpt, s: float, t: float, dsdx: float, dtdx: float, dsdy: float, dtdy: float, nchannels: int) -> tuple[float, ...]: ... + def texture3d(self, filename: str, options: TextureOpt, P, dPdx, dPdy, dPdz, nchannels: int) -> tuple[float, ...]: ... + +class TypeDesc: + aggregate: AGGREGATE + arraylen: int + basetype: BASETYPE + vecsemantics: VECSEMANTICS + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: TypeDesc | BASETYPE | str, /) -> None: ... + @overload + def __init__(self, arg0: BASETYPE, /) -> None: ... + @overload + def __init__(self, arg0: BASETYPE, arg1: AGGREGATE, /) -> None: ... + @overload + def __init__(self, arg0: BASETYPE, arg1: AGGREGATE, arg2: VECSEMANTICS, /) -> None: ... + @overload + def __init__(self, arg0: BASETYPE, arg1: AGGREGATE, arg2: VECSEMANTICS, arg3: int, /) -> None: ... + @overload + def __init__(self, arg0: str, /) -> None: ... + def basesize(self) -> int: ... + def basevalues(self) -> int: ... + def c_str(self) -> str: ... + def elementsize(self) -> int: ... + def elementtype(self) -> TypeDesc: ... + def equivalent(self, arg0: TypeDesc | BASETYPE | str, /) -> bool: ... + def fromstring(self, arg0: str, /) -> None: ... + def is_box2(self, arg0: BASETYPE, /) -> bool: ... + def is_box3(self, arg0: BASETYPE, /) -> bool: ... + def is_vec2(self, arg0: BASETYPE, /) -> bool: ... + def is_vec3(self, arg0: BASETYPE, /) -> bool: ... + def is_vec4(self, arg0: BASETYPE, /) -> bool: ... + def numelements(self) -> int: ... + def size(self) -> int: ... + def unarray(self) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + +class VECSEMANTICS: + __members__: ClassVar[dict] = ... # read-only + BOX: ClassVar[VECSEMANTICS] = ... + COLOR: ClassVar[VECSEMANTICS] = ... + KEYCODE: ClassVar[VECSEMANTICS] = ... + NORMAL: ClassVar[VECSEMANTICS] = ... + NOSEMANTICS: ClassVar[VECSEMANTICS] = ... + NOXFORM: ClassVar[VECSEMANTICS] = ... + POINT: ClassVar[VECSEMANTICS] = ... + RATIONAL: ClassVar[VECSEMANTICS] = ... + TIMECODE: ClassVar[VECSEMANTICS] = ... + VECTOR: ClassVar[VECSEMANTICS] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class Wrap: + __members__: ClassVar[dict] = ... # read-only + Black: ClassVar[Wrap] = ... + Clamp: ClassVar[Wrap] = ... + Default: ClassVar[Wrap] = ... + Last: ClassVar[Wrap] = ... + Mirror: ClassVar[Wrap] = ... + Periodic: ClassVar[Wrap] = ... + PeriodicPow2: ClassVar[Wrap] = ... + PeriodicSharedBorder: ClassVar[Wrap] = ... + __entries: ClassVar[dict] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +@overload +def attribute(arg0: str, arg1: float, /) -> None: ... +@overload +def attribute(arg0: str, arg1: int, /) -> None: ... +@overload +def attribute(arg0: str, arg1: str, /) -> None: ... +@overload +def attribute(arg0: str, arg1: TypeDesc | BASETYPE | str, arg2: object, /) -> None: ... +def equivalent_colorspace(arg0: str, arg1: str, /) -> bool: ... +def get_bytes_attribute(name: str, defaultval: str = ...) -> bytes: ... +def get_float_attribute(name: str, defaultval: float = ...) -> float: ... +def get_int_attribute(name: str, defaultval: int = ...) -> int: ... +def get_roi(arg0: ImageSpec, /) -> ROI: ... +def get_roi_full(arg0: ImageSpec, /) -> ROI: ... +def get_string_attribute(name: str, defaultval: str = ...) -> str: ... +def getattribute(arg0: str, arg1: TypeDesc | BASETYPE | str, /) -> typing.Any: ... +def geterror(clear: bool = ...) -> str: ... +def intersection(arg0: ROI, arg1: ROI, /) -> ROI: ... +def is_imageio_format_name(name: str) -> bool: ... +def set_colorspace(spec: ImageSpec, name: str) -> None: ... +def set_colorspace_rec709_gamma(arg0: ImageSpec, arg1: float, /) -> None: ... +def set_roi(arg0: ImageSpec, arg1: ROI, /) -> None: ... +def set_roi_full(arg0: ImageSpec, arg1: ROI, /) -> None: ... +def union(arg0: ROI, arg1: ROI, /) -> ROI: ... diff --git a/src/python/stubs/generate_stubs.py b/src/python/stubs/generate_stubs.py new file mode 100644 index 0000000000..c62b74553d --- /dev/null +++ b/src/python/stubs/generate_stubs.py @@ -0,0 +1,215 @@ +""" +Script to generate pyi stubs by patching and calling mypy's stubgen tool. + +There are two entry-points which are designed to call this script: + - `make pystubs` is called during local development to generate the + stubs and copy them into the git repo to be committed and reviewed. + - in CI, the cibuildwheel action is used to validate that the stubs match what + has been committed to the repo. + +The depdendencies for the script are defined in pyproject.toml. +""" + +from __future__ import absolute_import, annotations, division, print_function + +import mypy.stubgen +import mypy.stubgenc +from mypy.stubgenc import SignatureGenerator, DocstringSignatureGenerator + +from stubgenlib.siggen import ( + AdvancedSignatureGenerator, + AdvancedSigMatcher, +) +from stubgenlib.utils import add_positional_only_args + + +PY_TO_STDVECTOR_ARG = "float | typing.Iterable[float]" + + +class OIIOSignatureGenerator(AdvancedSignatureGenerator): + sig_matcher = AdvancedSigMatcher( + signature_overrides={ + # signatures for these special methods include many inaccurate overloads + "*.__ne__": "(self, other: object) -> bool", + "*.__eq__": "(self, other: object) -> bool", + }, + arg_type_overrides={ + # FIXME: Buffer may in fact be more accurate here + ("*", "*", "Buffer"): "numpy.ndarray", + # these use py_to_stdvector util + ("*.ImageBufAlgo.*", "min", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "max", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "black", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "white", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "sthresh", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "scontrast", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "white_balance", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "values", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "top", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "bottom", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "topleft", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "topright", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "bottomleft", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "bottomright", "object"): PY_TO_STDVECTOR_ARG, + ("*.ImageBufAlgo.*", "color", "object"): PY_TO_STDVECTOR_ARG, + # BASETYPE & str are implicitly converible to TypeDesc + ("*", "*", "*.TypeDesc"): "Union[TypeDesc, BASETYPE, str]", + # list is not strictly required + ("*.ImageOutput.open", "specs", "list[ImageSpec]"): "typing.Iterable[ImageSpec]", + }, + result_type_overrides={ + # FIXME: is there a way to use std::optional for these? + ("*.ImageOutput.create", "object"): "ImageOutput | None", + ("*.ImageOutput.open", "object"): "ImageOutput | None", + ("*.ImageInput.create", "object"): "ImageInput | None", + ("*.ImageInput.open", "object"): "ImageInput | None", + + # ("*.TextureSystem.imagespec", "object"): "ImageSpec | None", + + # if you return an uninitialized unique_ptr to pybind11 it will convert to `None` (see #4685) + ("*.ImageInput.read_native_deep_*", "DeepData"): "DeepData | None", + + # pybind11 has numpy support, so it may be possible to get it to emit these types + # by using py::numpy in our wrapper code. + ("*.ImageInput.read_*", "object"): "numpy.ndarray | None", + ("*", "Buffer"): "numpy.ndarray", + ("*.get_pixels", "object"): "numpy.ndarray | None", + + # For results, `object` is too restrictive (produces spurious errors during type analysis) + ("*.getattribute", "object"): "typing.Any", + ("*.ImageSpec.get", "object"): "typing.Any", + + ("*.ImageBufAlgo.histogram", "*"): "tuple[int, ...]", + # pybind11 treats std:vector as list[T], but we want tuple[T, ...] + # our custom code to convert vector to tuple obscures the contained type. + ("*.ImageBufAlgo.isConstantColor", "*"): "tuple[float, ...] | None", + ("*.ImageBufAlgo.color_range_check", "*"): "tuple[int, ...] | None", + + ("*.TextureSystem.imagespec", "object"): "ImageSpec | None", + ("*.TextureSystem.texture", "tuple"): "tuple[float, ...]", + ("*.TextureSystem.texture3d", "tuple"): "tuple[float, ...]", + ("*.TextureSystem.environment", "tuple"): "tuple[float, ...]", + + ("*.ImageBuf.getpixel", "tuple"): "tuple[float, ...]", + ("*.ImageBuf.interppixel*", "tuple"): "tuple[float, ...]", + ("*.ImageSpec.get_channelformats", "tuple"): "tuple[TypeDesc, ...]", + }, + + property_type_overrides={ + # FIXME: this isn't working + ("*.ParamValue.value", "object"): "typing.Any", + }, + ) + + def process_sig( + self, ctx: mypy.stubgen.FunctionContext, sig: mypy.stubgen.FunctionSig + ) -> mypy.stubgen.FunctionSig: + # Analyze the signature and add a '/' argument if necessary to mark + # arguments which cannot be access by name. + return add_positional_only_args(ctx, super().process_sig(ctx, sig)) + + +class InspectionStubGenerator(mypy.stubgenc.InspectionStubGenerator): + def get_sig_generators(self) -> list[SignatureGenerator]: + return [ + OIIOSignatureGenerator( + fallback_sig_gen=DocstringSignatureGenerator(), + ) + ] + + +mypy.stubgen.InspectionStubGenerator = InspectionStubGenerator # type: ignore[attr-defined,misc] +mypy.stubgenc.InspectionStubGenerator = InspectionStubGenerator # type: ignore[misc] + + +def get_colored_diff(old_text: str, new_text: str): + """ + Generates a colored diff between two strings. + + Returns: + A string containing the colored diff output. + """ + import difflib + + red = '\033[31m' + green = '\033[32m' + reset = '\033[0m' + + diff = difflib.unified_diff( + old_text.splitlines(keepends=True), + new_text.splitlines(keepends=True), + lineterm="", + ) + lines = [] + for line in diff: + if line.startswith('-'): + lines.append(f"{red}{line}{reset}") + elif line.startswith('+'): + lines.append(f"{green}{line}{reset}") + else: + lines.append(line) + return "".join(lines) + + +def main() -> None: + import argparse + import pathlib + import os + import sys + + parser = argparse.ArgumentParser() + parser.add_argument( + "--out-path", + default="out", + help="Directory to write the stubs." + ) + parser.add_argument( + "--validate-path", + default=None, + help="If provided, compare the generated stub to this file. Exits with code 2 if the " + "contents differ." + ) + args = parser.parse_args() + out_path = pathlib.Path(args.out_path) + print(f"Stub output directory: {out_path}") + + # perform import so we can see the traceback if it fails. + import OpenImageIO + + sys.argv[1:] = ["-p", "OpenImageIO", "-o", str(out_path)] + mypy.stubgen.main() + source_path = out_path.joinpath("OpenImageIO", "OpenImageIO.pyi") + if not source_path.exists(): + print("Stub generation failed") + sys.exit(1) + + dest_path = out_path.joinpath("OpenImageIO", "__init__.pyi") + print(f"Renaming to {dest_path}") + os.rename(source_path, dest_path) + + new_text = dest_path.read_text() + new_text = ( + "#\n# This file is auto-generated. DO NOT MODIFY! " + "Run `make pystubs` to regenerate\n#\n\n" + ) + new_text + dest_path.write_text(new_text) + + if args.validate_path and os.environ.get("GITHUB_ACTIONS", "false").lower() == "true": + # in CI, validate that what has been committed to the repo is what we expect. + validate_path = pathlib.Path(args.validate_path) + + print("Validating stubs against repository") + print(f"Comparing {dest_path} to {validate_path}") + + old_text = validate_path.read_text() + + if old_text != new_text: + print("Stub verification failed!") + print("Changes to the source code have resulted in a change to the stubs.") + print(get_colored_diff(old_text, new_text)) + print("Run `make pystubs` locally and commit the results for review.") + sys.exit(2) + + +if __name__ == "__main__": + main() diff --git a/src/python/stubs/generate_stubs_local.py b/src/python/stubs/generate_stubs_local.py new file mode 100644 index 0000000000..28637e4ff3 --- /dev/null +++ b/src/python/stubs/generate_stubs_local.py @@ -0,0 +1,62 @@ +""" +Script that is called when buildling the stubs during local development, via +`make pystubs` or `cmake --target pystubs`. +""" +import platform + +import argparse +import subprocess +import sys + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--repo-root") + parser.add_argument("--output-dir") + parser.add_argument("--python-version") + parser.add_argument("--cibuildwheel-version") + + args = parser.parse_args() + + version_major, version_minor = args.python_version.split(".") + + # detect the platform so that we can avoid running docker under emulation. + if platform.system() == "Darwin": + # On Mac, `uname -m`, $CMAKE_HOST_SYSTEM_PROCESSOR, and platform.machine() returns + # x86_64 even on arm64 processors (i.e. Apple Silicon) if cmake or python is running under + # emulation (Rosetta). sysctl.proc_translated tells us if we're under emulation. + arch = subprocess.check_output(["uname", "-m"], text=True).strip() + if arch == "x86_64": + process_uses_emulation = subprocess.check_output( + ["sysctl", "-in", "sysctl.proc_translated"], text=True + ).strip() + if process_uses_emulation == "1": + arch = "arm64" + else: + arch = platform.machine() + if arch == "arm64": + arch = "aarch64" + python_build_id = f"cp{version_major}{version_minor}-manylinux_{arch}" + print(f"Building {python_build_id}") + + try: + subprocess.check_call( + [ + "uv", + "tool", + "run", + f"--python={sys.executable}", + f"cibuildwheel@{args.cibuildwheel_version}", + f"--output-dir={args.output_dir}", + f"--only={python_build_id}", + ".", + ], + cwd=args.repo_root, + ) + except FileNotFoundError: + print("\nERROR: You must install uv to build the stubs. See https://docs.astral.sh/uv/\n") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src/python/stubs/py.typed b/src/python/stubs/py.typed new file mode 100644 index 0000000000..e69de29bb2 From f4ae93f4b42ae7f2e691ed84b72a0f224e10b304 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Sun, 30 Mar 2025 13:35:54 -0700 Subject: [PATCH 2/2] testing: Add type annotations to the python test suite Signed-off-by: Chad Dombrova --- INSTALL.md | 2 ++ Makefile | 7 +++++ pyproject.toml | 11 +++++++- src/cmake/pythonutils.cmake | 2 +- src/python/stubs/CMakeLists.txt | 2 +- .../stubs/{ => OpenImageIO}/__init__.pyi | 0 src/python/stubs/{ => OpenImageIO}/py.typed | 0 .../src/test_colorconfig.py | 2 ++ testsuite/python-deep/src/test_deep.py | 22 ++++++--------- .../python-imagebuf/src/test_imagebuf.py | 10 ++++--- .../src/test_imagebufalgo.py | 27 +++++++++++-------- .../python-imagecache/src/test_imagecache.py | 1 + .../python-imageinput/src/test_imageinput.py | 21 +++++++++------ .../src/test_imageoutput.py | 10 ++++--- .../python-imagespec/src/test_imagespec.py | 3 ++- .../python-paramlist/src/test_paramlist.py | 5 ++-- testsuite/python-roi/src/test_roi.py | 1 + .../python-texturesys/src/test_texture_sys.py | 5 +++- .../python-typedesc/src/test_typedesc.py | 4 ++- 19 files changed, 86 insertions(+), 49 deletions(-) rename src/python/stubs/{ => OpenImageIO}/__init__.pyi (100%) rename src/python/stubs/{ => OpenImageIO}/py.typed (100%) diff --git a/INSTALL.md b/INSTALL.md index 9856e81a95..c0172c314a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -394,6 +394,8 @@ The workflow for releasing new stubs is as follows: - Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) - Run `make pystubs` locally to generate updated stubs in `src/python/stubs/__init__.pyi` +- Run `make test-pystubs` locally to use mypy to test the stubs against the code in + the python testsuite. - Commit the new stubs and push to Github - In CI, the stubs will be included in the wheels built by `cibuildwheel`, as defined in `.github/wheel.yml` - In CI, one of the `cibuildwheel` Github actions will rebuild the stubs to a diff --git a/Makefile b/Makefile index bf7543140f..830e39a208 100644 --- a/Makefile +++ b/Makefile @@ -243,11 +243,18 @@ build: config ${CMAKE} --build . --config ${CMAKE_BUILD_TYPE} \ ) +# generate python stubs and add them to the repo pystubs: config @ ( cd ${build_dir} ; \ ${CMAKE} --build . --config ${CMAKE_BUILD_TYPE} --target pystubs \ ) +# run mypy on python tests to confirm the stubs are working +test-pystubs: pystubs + @ ( uv export --quiet --no-dev --no-build --no-emit-project --no-hashes -o ${build_dir}/requirements.txt ; \ + uvx --with-requirements ${build_dir}/requirements.txt mypy==1.15.0 \ + ) + # 'make install' builds everthing and installs it in 'dist'. # Suppress pointless output from docs installation. install: build diff --git a/pyproject.toml b/pyproject.toml index 7f89fbb9f5..4507cfa0b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -139,5 +139,14 @@ test-requires = "mypy~=1.15.0 stubgenlib~=0.1.0" select = "cp311-manylinux_*64" inherit.test-command = "append" test-command = [ - "python {project}/src/python/stubs/generate_stubs.py --out-path '/output' --validate-path '{project}/src/python/stubs/__init__.pyi'", + "python {project}/src/python/stubs/generate_stubs.py --out-path '/output' --validate-path '{project}/src/python/stubs/OpenImageIO/__init__.pyi'", ] + +[tool.mypy] +files = [ + "testsuite/python-*/src/", +] +mypy_path = [ + "src/python/stubs", +] +check_untyped_defs = true diff --git a/src/cmake/pythonutils.cmake b/src/cmake/pythonutils.cmake index adf3fbcbb8..d8c18d4230 100644 --- a/src/cmake/pythonutils.cmake +++ b/src/cmake/pythonutils.cmake @@ -156,7 +156,7 @@ macro (setup_python_module) RUNTIME DESTINATION ${PYTHON_SITE_DIR} COMPONENT user LIBRARY DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) - install (FILES __init__.py stubs/__init__.pyi stubs/py.typed + install (FILES __init__.py stubs/OpenImageIO/__init__.pyi stubs/OpenImageIO/py.typed DESTINATION ${PYTHON_SITE_DIR} COMPONENT user) endmacro () diff --git a/src/python/stubs/CMakeLists.txt b/src/python/stubs/CMakeLists.txt index c83732ca7f..bde2ed55c3 100644 --- a/src/python/stubs/CMakeLists.txt +++ b/src/python/stubs/CMakeLists.txt @@ -1,7 +1,7 @@ # Setup the pystub target, which is not built by default. -set (_stub_file "${CMAKE_SOURCE_DIR}/src/python/stubs/__init__.pyi") +set (_stub_file "${CMAKE_SOURCE_DIR}/src/python/stubs/OpenImageIO/__init__.pyi") # Note: the python version must be kept in sync with `[[tool.cibuildwheel.overrides]]` in pyproject.toml. # The stubs are generated within a container so the version of python does not need to match diff --git a/src/python/stubs/__init__.pyi b/src/python/stubs/OpenImageIO/__init__.pyi similarity index 100% rename from src/python/stubs/__init__.pyi rename to src/python/stubs/OpenImageIO/__init__.pyi diff --git a/src/python/stubs/py.typed b/src/python/stubs/OpenImageIO/py.typed similarity index 100% rename from src/python/stubs/py.typed rename to src/python/stubs/OpenImageIO/py.typed diff --git a/testsuite/python-colorconfig/src/test_colorconfig.py b/testsuite/python-colorconfig/src/test_colorconfig.py index 46c28112f5..31aa295df6 100755 --- a/testsuite/python-colorconfig/src/test_colorconfig.py +++ b/testsuite/python-colorconfig/src/test_colorconfig.py @@ -4,6 +4,8 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations + import os import OpenImageIO as oiio diff --git a/testsuite/python-deep/src/test_deep.py b/testsuite/python-deep/src/test_deep.py index be472b4b77..14208eeceb 100755 --- a/testsuite/python-deep/src/test_deep.py +++ b/testsuite/python-deep/src/test_deep.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations import OpenImageIO as oiio @@ -15,19 +16,10 @@ test_channames = ("R", "G", "B", "A", "Z", "Zback") print ("test_chantypes ", str(test_chantypes[0]), str(test_chantypes[1]), str(test_chantypes[2]), str(test_chantypes[3]), str(test_chantypes[4]), str(test_chantypes[5])) -def set_dd_sample (dd, pixel, sample, vals) : - if dd.samples(pixel) <= sample : - dd.set_samples(pixel, sample+1) - for c in range(ib.nchannels) : - dd.set_value (pixel, c, sample, vals[c]) - -def add_dd_sample (dd, pixel, sample, vals) : - set_dd_sample (dd, pixel, dd.samples(pixel), vale) - # Make a simple deep image # Only odd pixel indes have samples, and they have #samples = pixel index. -def make_test_deep_image () : +def make_test_deep_image () -> oiio.DeepData: dd = oiio.DeepData() dd.init (test_xres*test_yres, test_nchannels, test_chantypes, test_channames) for p in range(dd.pixels) : @@ -54,7 +46,7 @@ def make_test_deep_image () : -def print_deep_image (dd, prefix="After init,") : +def print_deep_image (dd: oiio.DeepData, prefix: str = "After init,") : print (prefix, "dd has", dd.pixels, "pixels,", dd.channels, "channels.") print (" Channel indices: Z=", dd.Z_channel, "Zback=", dd.Zback_channel, "A=", dd.A_channel, "AR=", dd.AR_channel, "AG=", dd.AG_channel, "AB=", dd.AB_channel) for p in range(dd.pixels) : @@ -68,7 +60,7 @@ def print_deep_image (dd, prefix="After init,") : print () -def print_deep_imagebuf (buf, prefix) : +def print_deep_imagebuf (buf: oiio.ImageBuf, prefix: str) : print_deep_image (buf.deepdata(), prefix) @@ -241,13 +233,13 @@ def test_opaque_z () : print () -def set_ib_sample (ib, x, y, sample, vals) : +def set_ib_sample (ib: oiio.ImageBuf, x: int, y: int, sample: int, vals: tuple[float, ...]) : if ib.deep_samples(x, y) <= sample : ib.set_deep_samples (x, y, 0, sample+1) for c in range(ib.nchannels) : ib.set_deep_value (x, y, 0, c, sample, vals[c]) -def add_ib_sample (ib, x, y, vals) : +def add_ib_sample (ib: oiio.ImageBuf, x: int, y: int, vals: tuple[float, ...]) : set_ib_sample (ib, x, y, ib.deep_samples(x,y), vals) @@ -301,6 +293,7 @@ def test_iba_deep_holdout () : spec.channelformats = test_chantypes spec.deep = True output = oiio.ImageOutput.create ("deeptest.exr") + assert output is not None output.open ("deeptest.exr", spec, "create") output.write_deep_image (dd) output.close () @@ -308,6 +301,7 @@ def test_iba_deep_holdout () : # read the exr file and double check it print ("\nReading image...") input = oiio.ImageInput.open ("deeptest.exr") + assert input is not None ddr = input.read_native_deep_image () if ddr : print_deep_image (ddr) diff --git a/testsuite/python-imagebuf/src/test_imagebuf.py b/testsuite/python-imagebuf/src/test_imagebuf.py index 12801fb87d..a12703301b 100755 --- a/testsuite/python-imagebuf/src/test_imagebuf.py +++ b/testsuite/python-imagebuf/src/test_imagebuf.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations import array import numpy @@ -11,7 +12,7 @@ # Print the contents of an ImageSpec -def print_imagespec (spec, subimage=0, mip=0, msg="") : +def print_imagespec (spec: oiio.ImageSpec, subimage=0, mip=0, msg="") : if msg != "" : print (str(msg)) if spec.depth <= 1 : @@ -39,9 +40,9 @@ def print_imagespec (spec, subimage=0, mip=0, msg="") : print (" deep = ", spec.deep) for attrib in spec.extra_attribs : if type(attrib.value) == str : - print (" ", attrib.name, "= \"" + attrib.value + "\"") + print (" {} = \"{}\"".format(attrib.name, attrib.value)) else : - print (" ", attrib.name, "=", attrib.value) + print (" {} = {}".format(attrib.name,attrib.value)) # Equivalent, using indexing rather than iterating: #for i in range(len(spec.extra_attribs)) : # if type(spec.extra_attribs[i].value) == str : @@ -50,7 +51,7 @@ def print_imagespec (spec, subimage=0, mip=0, msg="") : # print (" ", spec.extra_attribs[i].name, "=", spec.extra_attribs[i].value) -def write (image, filename, format=oiio.UNKNOWN) : +def write (image, filename: str, format=oiio.UNKNOWN) : if not image.has_error : image.write (filename, format) if image.has_error : @@ -118,6 +119,7 @@ def test_multiimage () : print ("Writing multi-image file") spec = oiio.ImageSpec (128, 64, 3, "float") out = oiio.ImageOutput.create ("multipart.exr") + assert out is not None # Open with intent to write two subimages, each with same spec if not out.open ("multipart.exr", (spec, spec)) : print ("Error on initial open:", out.geterror()) diff --git a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py index 74e528d46f..17e943e7af 100755 --- a/testsuite/python-imagebufalgo/src/test_imagebufalgo.py +++ b/testsuite/python-imagebufalgo/src/test_imagebufalgo.py @@ -4,16 +4,20 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations + import math, os import OpenImageIO as oiio from OpenImageIO import ImageBuf, ImageSpec, ImageBufAlgo, ROI +from typing import Callable, TypeVar +T = TypeVar("T") OIIO_TESTSUITE_ROOT = os.getenv('OIIO_TESTSUITE_ROOT', '') OIIO_TESTSUITE_IMAGEDIR = os.getenv('OIIO_TESTSUITE_IMAGEDIR', '') -def make_constimage (xres, yres, chans=3, format=oiio.UINT8, value=(0,0,0), - xoffset=0, yoffset=0) : +def make_constimage (xres: int, yres: int, chans=3, format: oiio.BASETYPE | str = oiio.UINT8, value=(0,0,0), + xoffset=0, yoffset=0) -> oiio.ImageBuf: spec = ImageSpec (xres,yres,chans,format) spec.x = xoffset spec.y = yoffset @@ -22,13 +26,13 @@ def make_constimage (xres, yres, chans=3, format=oiio.UINT8, value=(0,0,0), return b -def write (image, filename, format=oiio.UNKNOWN) : +def write (image: oiio.ImageBuf, filename: str, format: oiio.BASETYPE | str = oiio.UNKNOWN) : if not image.has_error : image.write (filename, format) if image.has_error : print ("Error writing", filename, ":", image.geterror()) -def dumpimg (image, fmt="{:.3f}", msg="") : +def dumpimg (image: oiio.ImageBuf, fmt="{:.3f}", msg="") : spec = image.spec() print (msg, end="") if image.has_error : @@ -48,7 +52,7 @@ def dumpimg (image, fmt="{:.3f}", msg="") : # both the variety that returns an ImageBuf with the result, and also the kind # that modifies an existing ImageBuf in place. An error is printed if the # results differ. The "returned" IB is returned from the function. -def test_iba (func, *args, **kwargs) : +def test_iba (func: Callable[..., oiio.ImageBuf], *args, **kwargs) -> oiio.ImageBuf: # Test the version of func that returns an IB # func = getattr(ImageBufAlgo, funcname) b = func(*args, **kwargs) @@ -415,12 +419,13 @@ def test_iba (func, *args, **kwargs) : # compare_Yee, # isConstantColor, isConstantChannel - b = ImageBuf (ImageSpec(256,256,3,oiio.UINT8)); + b = ImageBuf (ImageSpec(256,256,3,oiio.UINT8)) ImageBufAlgo.fill (b, (1,0.5,0.5)) - r = ImageBufAlgo.isConstantColor (b) - print ("isConstantColor on pink image is (%.5g %.5g %.5g)" % r) - r = ImageBufAlgo.isConstantColor (checker) - print ("isConstantColor on checker is ", r) + v = ImageBufAlgo.isConstantColor (b) + assert v is not None + 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) b = ImageBuf("cmul1.exr") print ("Is", b.name, "monochrome? ", ImageBufAlgo.isMonochrome(b)) @@ -482,7 +487,7 @@ def test_iba (func, *args, **kwargs) : write (b, "dilate.tif", oiio.UINT8) b = test_iba (ImageBufAlgo.erode, undilated, 3, 3) write (b, "erode.tif", oiio.UINT8) - undilated = None + del undilated # unsharp_mask b = test_iba (ImageBufAlgo.unsharp_mask, ImageBuf(OIIO_TESTSUITE_ROOT+"/common/tahoe-small.tif"), diff --git a/testsuite/python-imagecache/src/test_imagecache.py b/testsuite/python-imagecache/src/test_imagecache.py index ce811dd48e..2bc7e92003 100755 --- a/testsuite/python-imagecache/src/test_imagecache.py +++ b/testsuite/python-imagecache/src/test_imagecache.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations import array import numpy diff --git a/testsuite/python-imageinput/src/test_imageinput.py b/testsuite/python-imageinput/src/test_imageinput.py index 7941b945e2..64b5ff5158 100755 --- a/testsuite/python-imageinput/src/test_imageinput.py +++ b/testsuite/python-imageinput/src/test_imageinput.py @@ -4,6 +4,8 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations + import OpenImageIO as oiio import os @@ -12,7 +14,7 @@ '../oiio-images') # Print the contents of an ImageSpec -def print_imagespec (spec, subimage=0, mip=0, msg="") : +def print_imagespec (spec: oiio.ImageSpec, subimage=0, mip=0, msg="") : if msg != "" : print (str(msg)) if spec.depth <= 1 : @@ -40,13 +42,13 @@ def print_imagespec (spec, subimage=0, mip=0, msg="") : print (" deep = ", spec.deep) for i in range(len(spec.extra_attribs)) : if type(spec.extra_attribs[i].value) == str : - print (" ", spec.extra_attribs[i].name, "= \"" + spec.extra_attribs[i].value + "\"") + print (" {} = \"{}\"".format(spec.extra_attribs[i].name, spec.extra_attribs[i].value)) else : - print (" ", spec.extra_attribs[i].name, "=", spec.extra_attribs[i].value) + print (" {} = {}".format(spec.extra_attribs[i].name, spec.extra_attribs[i].value)) -def poor_mans_iinfo (filename) : +def poor_mans_iinfo (filename: str) : input = oiio.ImageInput.open (filename) if not input : print ('Could not open "' + filename + '"') @@ -78,7 +80,7 @@ def poor_mans_iinfo (filename) : # pixel values ot prove that we have the right data. Read nchannels # channels, if nonzero, otherwise read the full channel range in the # file. -def test_readimage (filename, sub=0, mip=0, type=oiio.UNKNOWN, +def test_readimage (filename: str, sub=0, mip=0, type: oiio.BASETYPE | str = oiio.UNKNOWN, method="image", nchannels=0, print_pixels = True, keep_unknown=False) : input = oiio.ImageInput.open (filename) @@ -134,7 +136,7 @@ def test_readimage (filename, sub=0, mip=0, type=oiio.UNKNOWN, # Read the image, one scanline at a time, print a couple values # at particular locations to make sure we have the correct data. -def test_readscanline (filename, sub=0, mip=0, type=oiio.UNKNOWN) : +def test_readscanline (filename: str, sub=0, mip=0, type: oiio.BASETYPE | str = oiio.UNKNOWN) : input = oiio.ImageInput.open (filename) if not input : print ('Could not open "' + filename + '"') @@ -164,7 +166,7 @@ def test_readscanline (filename, sub=0, mip=0, type=oiio.UNKNOWN) : # Read the whole image, one tile at a time, print a couple values # at particular locations to make sure we have the correct data. -def test_readtile (filename, sub=0, mip=0, type=oiio.UNKNOWN) : +def test_readtile (filename: str, sub=0, mip=0, type: oiio.BASETYPE | str = oiio.UNKNOWN) : input = oiio.ImageInput.open (filename) if not input : print ('Could not open "' + filename + '"') @@ -189,12 +191,13 @@ def test_readtile (filename, sub=0, mip=0, type=oiio.UNKNOWN) : print ("@", (x,y), "=", data[y,x]) (tx,ty) = (spec.x+2*spec.tile_width, spec.y+2*spec.tile_height) data = input.read_tile (tx+spec.x, ty+spec.y, spec.z, type) + assert data is not None print ("@", (x,y), "=", data[y,x]) input.close () print () -def write (image, filename, format=oiio.UNKNOWN) : +def write (image: oiio.ImageBuf, filename: str, format: oiio.BASETYPE | str = oiio.UNKNOWN) : if not image.has_error : image.set_write_format (format) image.write (filename) @@ -221,6 +224,7 @@ def test_tiff_remembering_config() : print ("\n reading as IB with unassoc hint: ", rbuf.get_pixels()) print ("\n reading as II with hint, read scanlines backward: ") ii = oiio.ImageInput.open("test_unassoc.tif", config) + assert ii is not None print (" [1] = ", ii.read_scanline(1)) print (" [0] = ", ii.read_scanline(0)) print ("\n") @@ -246,6 +250,7 @@ def test_tiff_cmyk() : print ("\n reading as IB with rawcolor=1: ", rbuf.get_pixels()) print ("\n reading as II with rawcolor=0, read scanlines backward: ") ii = oiio.ImageInput.open(filename) + assert ii is not None print (" [1] = ", ii.read_scanline(1)) print (" [0] = ", ii.read_scanline(0)) print ("\n") diff --git a/testsuite/python-imageoutput/src/test_imageoutput.py b/testsuite/python-imageoutput/src/test_imageoutput.py index e8cd72bf18..bda64bb8dc 100755 --- a/testsuite/python-imageoutput/src/test_imageoutput.py +++ b/testsuite/python-imageoutput/src/test_imageoutput.py @@ -3,6 +3,7 @@ # 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 @@ -13,7 +14,7 @@ # write_image, write_scanlines, write_scanline, write_tile, or write_tiles, # depending on the 'method' argument). (Just copy one subimage, one MIP # level.) -def copy_subimage (input, output, method="image", +def copy_subimage (in_filename: str, input: oiio.ImageInput, output: oiio.ImageOutput, method="image", memformat=oiio.TypeFloat) : spec = input.spec () if method == "image" : @@ -64,11 +65,11 @@ def copy_subimage (input, output, method="image", # Read the whole image then write using write_image, write_scanlines, # write_scanline, write_tile, or write_tiles, depending on the 'method' # argument). (Just copy one subimage, one MIP level.) -def copy_image (in_filename, out_filename, method="image", +def copy_image (in_filename: str, out_filename: str, method="image", memformat=oiio.TypeFloat, outformat=oiio.TypeUnknown) : input = oiio.ImageInput.open (in_filename) if not input : - print ('Could not open "' + filename + '"') + print ('Could not open "' + in_filename + '"') print ("\tError: ", oiio.geterror()) print () return @@ -83,7 +84,7 @@ def copy_image (in_filename, out_filename, method="image", if not ok : print ("Could not open", out_filename) return - ok = copy_subimage (input, output, method, memformat) + ok = copy_subimage (in_filename, input, output, method, memformat) input.close () output.close () if ok : @@ -93,6 +94,7 @@ def copy_image (in_filename, out_filename, method="image", def test_subimages (out_filename="multipart.exr") : output = oiio.ImageOutput.create (out_filename) + assert output is not None spec = oiio.ImageSpec (64, 64, 3, "half") specs = (spec, spec, spec) output.open (out_filename, specs) diff --git a/testsuite/python-imagespec/src/test_imagespec.py b/testsuite/python-imagespec/src/test_imagespec.py index 645d8dc28e..c6523ff380 100755 --- a/testsuite/python-imagespec/src/test_imagespec.py +++ b/testsuite/python-imagespec/src/test_imagespec.py @@ -4,13 +4,14 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations import numpy import OpenImageIO as oiio # Print the contents of an ImageSpec -def print_imagespec (spec, msg="") : +def print_imagespec (spec: oiio.ImageSpec, msg="") : if msg != "" : print (str(msg)) print (" resolution (width,height,depth) = ", spec.width, spec.height, spec.depth) diff --git a/testsuite/python-paramlist/src/test_paramlist.py b/testsuite/python-paramlist/src/test_paramlist.py index 0256ffd319..afe3f3ee82 100755 --- a/testsuite/python-paramlist/src/test_paramlist.py +++ b/testsuite/python-paramlist/src/test_paramlist.py @@ -4,18 +4,19 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations import numpy import OpenImageIO as oiio -def print_param_value(p) : +def print_param_value(p: oiio.ParamValue) : if type(p.value) == float : print (" item {} {} {:.6}".format(p.name, p.type, p.value)) else : print (" item {} {} {}".format(p.name, p.type, p.value)) -def print_param_list(pl) : +def print_param_list(pl: oiio.ParamValueList) : for p in pl : print_param_value(p) diff --git a/testsuite/python-roi/src/test_roi.py b/testsuite/python-roi/src/test_roi.py index 0cd7f8016d..3534e11458 100755 --- a/testsuite/python-roi/src/test_roi.py +++ b/testsuite/python-roi/src/test_roi.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations import OpenImageIO as oiio diff --git a/testsuite/python-texturesys/src/test_texture_sys.py b/testsuite/python-texturesys/src/test_texture_sys.py index b1239e02c9..9516b446d6 100644 --- a/testsuite/python-texturesys/src/test_texture_sys.py +++ b/testsuite/python-texturesys/src/test_texture_sys.py @@ -4,6 +4,8 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations + import os import numpy @@ -36,7 +38,8 @@ print ("missingcolor channels: 4 =", texture_sys.texture("", texture_opt, 0, 0, 0, 0, 0, 0, 4)) print ("") -texture_opt.missingcolor = None +# 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 ("") diff --git a/testsuite/python-typedesc/src/test_typedesc.py b/testsuite/python-typedesc/src/test_typedesc.py index 45693e6700..ead07f9401 100755 --- a/testsuite/python-typedesc/src/test_typedesc.py +++ b/testsuite/python-typedesc/src/test_typedesc.py @@ -4,6 +4,8 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +from __future__ import annotations + import OpenImageIO as oiio @@ -72,7 +74,7 @@ def vecsemantics_enum_test(): print ("Failed VECSEMANTICS") # print the details of a type t -def breakdown_test(t, name="", verbose=True): +def breakdown_test(t: oiio.TypeDesc, name="", verbose=True): print ("type '%s'" % name) print (" c_str \"" + t.c_str() + "\"") if verbose: