Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-imagecodecs for openSUSE:Factory checked in at 2023-09-14 16:26:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-imagecodecs (Old) and /work/SRC/openSUSE:Factory/.python-imagecodecs.new.1766 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-imagecodecs" Thu Sep 14 16:26:05 2023 rev:12 rq:1111059 version:2023.3.16 Changes: -------- --- /work/SRC/openSUSE:Factory/python-imagecodecs/python-imagecodecs.changes 2023-09-13 20:47:55.353261085 +0200 +++ /work/SRC/openSUSE:Factory/.python-imagecodecs.new.1766/python-imagecodecs.changes 2023-09-14 16:29:02.493818131 +0200 @@ -1,0 +2,10 @@ +Wed Sep 13 11:57:38 UTC 2023 - Markéta Machová <mmach...@suse.com> + +- Add patches for the compatibility with libavif 1.0.0: + * libavif.patch + * quantize.patch + * avif.patch + * tests.patch + * integrate.patch + +------------------------------------------------------------------- New: ---- avif.patch integrate.patch libavif.patch quantize.patch tests.patch ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-imagecodecs.spec ++++++ --- /var/tmp/diff_new_pack.xO1S9z/_old 2023-09-14 16:29:04.101875576 +0200 +++ /var/tmp/diff_new_pack.xO1S9z/_new 2023-09-14 16:29:04.105875719 +0200 @@ -36,6 +36,16 @@ Patch0: always-cythonize.patch # PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/14bb6012a8c9f48df264ea996f3376e57166201a Update imagecodecs/_heif.pyx Patch1: cython3.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/d04112759c48772c4d46a2dfa4f4c6a76e23c9a9 Update imagecodecs/libavif.pxd +Patch2: libavif.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/93d1f751436e357d73eb6fdebc2af833059d9ea9 Add imagecodecs/_quantize.pyx +Patch3: quantize.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/2f548c9a4df443948f2dfcde30a7211ce8b3adc2 Update imagecodecs/_avif.pyx +Patch4: avif.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/0030b7b74fc17ceb356d1f67633ba1734108dac9 Update tests/test_imagecodecs.py +Patch5: tests.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/e9b5a984b72c9d4e14f9d37ec99389d25645c7fb Update imagecodecs/imagecodecs.py +Patch6: integrate.patch BuildRequires: %{python_module Cython >= 0.29.19} BuildRequires: %{python_module base >= 3.8} BuildRequires: %{python_module numpy-devel} @@ -60,9 +70,9 @@ BuildRequires: %{python_module Pillow} BuildRequires: %{python_module blosc} BuildRequires: %{python_module czifile} -BuildRequires: %{python_module dask if %python-base < 3.11} -BuildRequires: %{python_module dask-array if %python-base < 3.11} -BuildRequires: %{python_module dask-delayed if %python-base < 3.11} +BuildRequires: %{python_module dask-array} +BuildRequires: %{python_module dask-delayed} +BuildRequires: %{python_module dask} BuildRequires: %{python_module imagecodecs >= %{version}} BuildRequires: %{python_module lz4} BuildRequires: %{python_module matplotlib >= 3.3} @@ -102,6 +112,7 @@ BuildRequires: pkgconfig(bzip2) BuildRequires: pkgconfig(cfitsio) BuildRequires: pkgconfig(lcms2) +BuildRequires: pkgconfig(libavif) >= 1.0.0 BuildRequires: pkgconfig(libbrotlicommon) BuildRequires: pkgconfig(libheif) # Beta, not available in minimum version @@ -122,8 +133,6 @@ BuildRequires: zfp-devel BuildRequires: pkgconfig(SvtAv1Dec) BuildRequires: pkgconfig(SvtAv1Enc) -# 32-bit tests fail -BuildRequires: pkgconfig(libavif) %endif %endif %python_subpackages @@ -144,6 +153,9 @@ %setup -q -n imagecodecs-%{version} # the patch from github requires unix line endings to apply dos2unix tests/test_imagecodecs.py +dos2unix imagecodecs/libavif.pxd +dos2unix imagecodecs/_avif.pyx +dos2unix imagecodecs/imagecodecs.py %autopatch -p1 cp %SOURCE1 ./ @@ -179,8 +191,6 @@ %ifarch %ix86 %arm32 donttest="$donttest or spng" %endif -# no dask because of numba for python 3.11 -python311_donttest="or imagecodecs.imagecodecs" %pytest_arch -n auto tests -rsXfE --doctest-modules %{$python_sitearch}/imagecodecs/imagecodecs.py -k "not ($donttest ${$python_donttest})" %endif ++++++ avif.patch ++++++ >From 2f548c9a4df443948f2dfcde30a7211ce8b3adc2 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke <cgoh...@cgohlke.com> Date: Sat, 2 Sep 2023 22:41:40 -0700 Subject: [PATCH] Update imagecodecs/_avif.pyx --- imagecodecs/_avif.pyx | 60 +++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 28 deletions(-) Index: imagecodecs-2023.3.16/imagecodecs/_avif.pyx =================================================================== --- imagecodecs-2023.3.16.orig/imagecodecs/_avif.pyx +++ imagecodecs-2023.3.16/imagecodecs/_avif.pyx @@ -56,12 +56,14 @@ class AVIF: YUV422 = AVIF_PIXEL_FORMAT_YUV422 YUV420 = AVIF_PIXEL_FORMAT_YUV420 YUV400 = AVIF_PIXEL_FORMAT_YUV400 + COUNT = AVIF_PIXEL_FORMAT_COUNT - class QUANTIZER(enum.IntEnum): - """AVIF codec quantizers.""" - LOSSLESS = AVIF_QUANTIZER_LOSSLESS - BEST_QUALITY = AVIF_QUANTIZER_BEST_QUALITY - WORST_QUALITY = AVIF_QUANTIZER_WORST_QUALITY + class QUALITY(enum.IntEnum): + """AVIF codec quality.""" + DEFAULT = AVIF_QUALITY_DEFAULT # -1 + LOSSLESS = AVIF_QUALITY_LOSSLESS # 100 + WORST = AVIF_QUALITY_WORST # 0 + BEST = AVIF_QUALITY_BEST # 100 class SPEED(enum.IntEnum): """AVIF codec speeds.""" @@ -85,6 +87,7 @@ class AVIF: LIBGAV1 = AVIF_CODEC_CHOICE_LIBGAV1 RAV1E = AVIF_CODEC_CHOICE_RAV1E SVT = AVIF_CODEC_CHOICE_SVT + AVM = AVIF_CODEC_CHOICE_AVM class AvifError(RuntimeError): @@ -143,8 +146,8 @@ def avif_encode( const uint8_t[::1] dst # must be const to write to bytes ssize_t dstsize, size ssize_t itemsize = data.dtype.itemsize + int quality = AVIF_QUALITY_LOSSLESS int speed_ = AVIF_SPEED_DEFAULT - int quantizer = AVIF_QUANTIZER_LOSSLESS int tilerowslog2 = 0 int tilecolslog2 = 0 int duration = 1 @@ -172,10 +175,10 @@ def avif_encode( src.dtype in (numpy.uint8, numpy.uint16) # and numpy.PyArray_ISCONTIGUOUS(src) and src.ndim in (2, 3, 4) - and src.shape[0] < 2 ** 31 - and src.shape[1] < 2 ** 31 - and src.shape[src.ndim - 1] < 2 ** 31 - and src.shape[src.ndim - 2] < 2 ** 31 + and src.shape[0] <= 2147483647 + and src.shape[1] <= 2147483647 + and src.shape[src.ndim - 1] <= 2147483647 + and src.shape[src.ndim - 2] <= 2147483647 ): raise ValueError('invalid data shape, strides, or dtype') @@ -205,10 +208,6 @@ def avif_encode( monochrome = samples < 3 hasalpha = samples in (2, 4) - if monochrome: - raise NotImplementedError('cannot encode monochome images') - # TODO: check status of libavif/aom monochome support - if bitspersample is None: depth = <uint32_t> itemsize * 8 else: @@ -223,11 +222,11 @@ def avif_encode( if 0 <= tilecolslog2 <= 6: raise ValueError('invalid tileColsLog2') - quantizer = _default_value( + quality = _default_value( level, - AVIF_QUANTIZER_LOSSLESS, - AVIF_QUANTIZER_BEST_QUALITY, - AVIF_QUANTIZER_WORST_QUALITY + AVIF_QUALITY_LOSSLESS, # 100 + AVIF_QUALITY_DEFAULT, # -1 + AVIF_QUALITY_BEST # 100 ) speed_ = _default_value( @@ -239,8 +238,8 @@ def avif_encode( if monochrome: yuvformat = AVIF_PIXEL_FORMAT_YUV400 - quantizer = AVIF_QUANTIZER_LOSSLESS - elif quantizer == AVIF_QUANTIZER_LOSSLESS: + quality = AVIF_QUALITY_LOSSLESS + elif quality == AVIF_QUALITY_LOSSLESS: yuvformat = AVIF_PIXEL_FORMAT_YUV444 elif pixelformat is not None: yuvformat = _avif_pixelformat(pixelformat) @@ -254,11 +253,9 @@ def avif_encode( if encoder == NULL: raise AvifError('avifEncoderCreate', 'NULL') + encoder.quality = quality + encoder.qualityAlpha = AVIF_QUALITY_LOSSLESS encoder.maxThreads = maxthreads - encoder.minQuantizer = quantizer - encoder.maxQuantizer = quantizer - encoder.minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS - encoder.maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS encoder.tileRowsLog2 = tilerowslog2 encoder.tileColsLog2 = tilecolslog2 encoder.speed = speed_ @@ -269,7 +266,7 @@ def avif_encode( if image == NULL: raise AvifError('avifImageCreate', 'NULL') - if monochrome or quantizer == AVIF_QUANTIZER_LOSSLESS: + if monochrome or quality == AVIF_QUALITY_LOSSLESS: if codecchoice == AVIF_CODEC_CHOICE_AUTO: encoder.codecChoice = AVIF_CODEC_CHOICE_AOM else: @@ -287,7 +284,9 @@ def avif_encode( avifRGBImageSetDefaults(&rgb, image) if monochrome: - avifRGBImageAllocatePixels(&rgb) + res = avifRGBImageAllocatePixels(&rgb) + if res != AVIF_RESULT_OK: + raise AvifError('avifRGBImageAllocatePixels', res) if rgb.format != AVIF_RGB_FORMAT_RGBA: raise RuntimeError('rgb.format != AVIF_RGB_FORMAT_RGBA') srcptr = <uint8_t *> src.data @@ -419,7 +418,7 @@ def avif_encode( return _return_output(out, dstsize, rawsize, outgiven) -def avif_decode(data, index=None, out=None): +def avif_decode(data, index=None, numthreads=None, out=None): """Return decoded AVIF image.""" cdef: numpy.ndarray dst @@ -429,6 +428,7 @@ def avif_decode(data, index=None, out=No ssize_t samples, size, itemsize, i, j, k, dstindex, imagecount bint monochrome = 0 # must be initialized bint hasalpha = 0 + int maxthreads = <int> _default_threads(numthreads) uint8_t* dstptr = NULL uint8_t* srcptr = NULL avifDecoder* decoder = NULL @@ -448,6 +448,8 @@ def avif_decode(data, index=None, out=No # required to read AVIF files created by ImageMagick decoder.strictFlags = AVIF_STRICT_DISABLED + decoder.maxThreads = maxthreads + res = avifDecoderSetSource(decoder, AVIF_DECODER_SOURCE_AUTO) if res != AVIF_RESULT_OK: raise AvifError('avifDecoderSetSource', res) @@ -632,10 +634,12 @@ cdef _avif_codecchoice(codec): AVIF_CODEC_CHOICE_LIBGAV1: AVIF_CODEC_CHOICE_LIBGAV1, AVIF_CODEC_CHOICE_RAV1E: AVIF_CODEC_CHOICE_RAV1E, AVIF_CODEC_CHOICE_SVT: AVIF_CODEC_CHOICE_SVT, + AVIF_CODEC_CHOICE_AVM: AVIF_CODEC_CHOICE_AVM, 'auto': AVIF_CODEC_CHOICE_AUTO, 'aom': AVIF_CODEC_CHOICE_AOM, 'dav1d': AVIF_CODEC_CHOICE_DAV1D, 'libgav1': AVIF_CODEC_CHOICE_LIBGAV1, 'rav1e': AVIF_CODEC_CHOICE_RAV1E, 'svt': AVIF_CODEC_CHOICE_SVT, + 'avm': AVIF_CODEC_CHOICE_AVM, }[codec] ++++++ integrate.patch ++++++ >From e9b5a984b72c9d4e14f9d37ec99389d25645c7fb Mon Sep 17 00:00:00 2001 From: Christoph Gohlke <cgoh...@cgohlke.com> Date: Sun, 3 Sep 2023 09:37:58 -0700 Subject: [PATCH] Update imagecodecs/imagecodecs.py --- imagecodecs/imagecodecs.py | 98 ++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 36 deletions(-) Index: imagecodecs-2023.3.16/imagecodecs/imagecodecs.py =================================================================== --- imagecodecs-2023.3.16.orig/imagecodecs/imagecodecs.py +++ imagecodecs-2023.3.16/imagecodecs/imagecodecs.py @@ -483,9 +483,6 @@ import sys import io import importlib import threading - -import numpy - from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -496,6 +493,8 @@ if TYPE_CHECKING: from numpy.typing import ArrayLike, NDArray +import numpy + # map extension module names to attribute names _MODULES: dict[str, list[str]] = { '': [ @@ -834,6 +833,14 @@ _MODULES: dict[str, list[str]] = { 'pglz_check', 'pglz_version', ], + '_png': [ + 'PNG', + 'PngError', + 'png_encode', + 'png_decode', + 'png_check', + 'png_version', + ], '_qoi': [ 'QOI', 'QoiError', @@ -842,13 +849,13 @@ _MODULES: dict[str, list[str]] = { 'qoi_check', 'qoi_version', ], - '_png': [ - 'PNG', - 'PngError', - 'png_encode', - 'png_decode', - 'png_check', - 'png_version', + '_quantize': [ + 'QUANTIZE', + 'QuantizeError', + 'quantize_encode', + 'quantize_decode', + 'quantize_check', + 'quantize_version', ], '_rgbe': [ 'RGBE', @@ -1144,6 +1151,8 @@ def _stub(name: str, module: ModuleType return StubError class StubType(type): + """Stub type metaclass.""" + def __getattr__(cls, arg: str, /) -> Any: raise DelayedImportError(name) ++++++ libavif.patch ++++++ >From d04112759c48772c4d46a2dfa4f4c6a76e23c9a9 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke <cgoh...@cgohlke.com> Date: Sat, 2 Sep 2023 16:52:31 -0700 Subject: [PATCH] Update imagecodecs/libavif.pxd --- imagecodecs/libavif.pxd | 159 ++++++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 57 deletions(-) diff --git a/imagecodecs/libavif.pxd b/imagecodecs/libavif.pxd index 9d17cc5..c9db115 100644 --- a/imagecodecs/libavif.pxd +++ b/imagecodecs/libavif.pxd @@ -1,10 +1,10 @@ # imagecodecs/libavif.pxd # cython: language_level = 3 -# Cython declarations for the `libavif 0.11.1` library. +# Cython declarations for the `libavif 1.0.1` library. # https://github.com/AOMediaCodec/libavif -from libc.stdint cimport uint8_t, uint32_t, uint64_t +from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, int32_t cdef extern from 'avif/avif.h': @@ -24,6 +24,11 @@ cdef extern from 'avif/avif.h': int AVIF_DIAGNOSTICS_ERROR_BUFFER_SIZE int AVIF_DEFAULT_IMAGE_COUNT_LIMIT + int AVIF_QUALITY_DEFAULT + int AVIF_QUALITY_LOSSLESS + int AVIF_QUALITY_WORST + int AVIF_QUALITY_BEST + int AVIF_QUANTIZER_LOSSLESS int AVIF_QUANTIZER_BEST_QUALITY int AVIF_QUANTIZER_WORST_QUALITY @@ -34,6 +39,11 @@ cdef extern from 'avif/avif.h': int AVIF_SPEED_SLOWEST int AVIF_SPEED_FASTEST + int AVIF_REPETITION_COUNT_INFINITE + int AVIF_REPETITION_COUNT_UNKNOWN + + int AVIF_MAX_AV1_LAYER_COUNT + ctypedef enum avifPlanesFlag: AVIF_PLANES_YUV AVIF_PLANES_A @@ -42,12 +52,10 @@ cdef extern from 'avif/avif.h': ctypedef uint32_t avifPlanesFlags ctypedef enum avifChannelIndex: - AVIF_CHAN_R - AVIF_CHAN_G - AVIF_CHAN_B AVIF_CHAN_Y AVIF_CHAN_U AVIF_CHAN_V + AVIF_CHAN_A # Version @@ -82,7 +90,7 @@ cdef extern from 'avif/avif.h': AVIF_RESULT_ENCODE_COLOR_FAILED AVIF_RESULT_ENCODE_ALPHA_FAILED AVIF_RESULT_BMFF_PARSE_FAILED - AVIF_RESULT_NO_AV1_ITEMS_FOUND + AVIF_RESULT_MISSING_IMAGE_ITEM AVIF_RESULT_DECODE_COLOR_FAILED AVIF_RESULT_DECODE_ALPHA_FAILED AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH @@ -101,6 +109,7 @@ cdef extern from 'avif/avif.h': AVIF_RESULT_OUT_OF_MEMORY AVIF_RESULT_CANNOT_CHANGE_SETTING AVIF_RESULT_INCOMPATIBLE_IMAGE + AVIF_RESULT_NO_AV1_ITEMS_FOUND const char* avifResultToString( avifResult result @@ -118,12 +127,12 @@ cdef extern from 'avif/avif.h': # int AVIF_DATA_EMPTY { NULL, 0 } - void avifRWDataRealloc( + avifResult avifRWDataRealloc( avifRWData* raw, size_t newSize ) nogil - void avifRWDataSet( + avifResult avifRWDataSet( avifRWData* raw, const uint8_t* data, size_t len @@ -133,6 +142,20 @@ cdef extern from 'avif/avif.h': avifRWData* raw ) nogil + # Metadata + + avifResult avifGetExifTiffHeaderOffset( + const uint8_t* exif, + size_t exifSize, + size_t* offset + ) nogil + + avifResult avifGetExifOrientationOffset( + const uint8_t* exif, + size_t exifSize, + size_t* offset + ) nogil + # avifPixelFormat ctypedef enum avifPixelFormat: @@ -218,6 +241,15 @@ cdef extern from 'avif/avif.h': AVIF_TRANSFER_CHARACTERISTICS_SMPTE428 AVIF_TRANSFER_CHARACTERISTICS_HLG + avifResult avifTransferCharacteristicsGetGamma( + avifTransferCharacteristics atc, + float* gamma + ) nogil + + avifTransferCharacteristics avifTransferCharacteristicsFindByGamma( + float gamma + ) nogil + ctypedef enum avifMatrixCoefficients: AVIF_MATRIX_COEFFICIENTS_IDENTITY AVIF_MATRIX_COEFFICIENTS_BT709 @@ -233,12 +265,21 @@ cdef extern from 'avif/avif.h': AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL AVIF_MATRIX_COEFFICIENTS_ICTCP + AVIF_MATRIX_COEFFICIENTS_YCGCO_RE + AVIF_MATRIX_COEFFICIENTS_YCGCO_RO + AVIF_MATRIX_COEFFICIENTS_LAST ctypedef struct avifDiagnostics: char error[256] # [AVIF_DIAGNOSTICS_ERROR_BUFFER_SIZE] void avifDiagnosticsClearError(avifDiagnostics* diag) nogil + # Fraction utility + + ctypedef struct avifFraction: + int32_t n + int32_t d + # Optional transformation structs ctypedef enum avifTransformFlag: @@ -268,7 +309,7 @@ cdef extern from 'avif/avif.h': uint8_t angle ctypedef struct avifImageMirror: - uint8_t mode + uint8_t axis ctypedef struct avifCropRect: uint32_t x @@ -294,6 +335,10 @@ cdef extern from 'avif/avif.h': avifDiagnostics* diag ) nogil + ctypedef struct avifContentLightLevelInformationBox: + uint16_t maxCLL + uint16_t maxPALL + # avifImage ctypedef struct avifImage: @@ -314,6 +359,7 @@ cdef extern from 'avif/avif.h': avifColorPrimaries colorPrimaries avifTransferCharacteristics transferCharacteristics avifMatrixCoefficients matrixCoefficients + avifContentLightLevelInformationBox clli avifTransformFlags transformFlags avifPixelAspectRatioBox pasp avifCleanApertureBox clap @@ -347,19 +393,19 @@ cdef extern from 'avif/avif.h': avifImage* image ) nogil - void avifImageSetProfileICC( + avifResult avifImageSetProfileICC( avifImage* image, const uint8_t* icc, size_t iccSize ) nogil - void avifImageSetMetadataExif( + avifResult avifImageSetMetadataExif( avifImage* image, const uint8_t* exif, size_t exifSize ) nogil - void avifImageSetMetadataXMP( + avifResult avifImageSetMetadataXMP( avifImage* image, const uint8_t* xmp, size_t xmpSize @@ -422,8 +468,9 @@ cdef extern from 'avif/avif.h': avifChromaDownsampling chromaDownsampling avifBool avoidLibYUV avifBool ignoreAlpha - avifBool isFloat avifBool alphaPremultiplied + avifBool isFloat + int maxThreads uint8_t* pixels uint32_t rowBytes @@ -438,7 +485,7 @@ cdef extern from 'avif/avif.h': # Convenience functions - void avifRGBImageAllocatePixels( + avifResult avifRGBImageAllocatePixels( avifRGBImage* rgb ) nogil @@ -471,62 +518,25 @@ cdef extern from 'avif/avif.h': # YUV Utils int avifFullToLimitedY( - int depth, + uint32_t depth, int v ) nogil int avifFullToLimitedUV( - int depth, + uint32_t depth, int v ) nogil int avifLimitedToFullY( - int depth, + uint32_t depth, int v ) nogil int avifLimitedToFullUV( - int depth, + uint32_t depth, int v ) nogil - # removed in v0.9 - # - # ctypedef enum avifReformatMode: - # AVIF_REFORMAT_MODE_YUV_COEFFICIENTS - # AVIF_REFORMAT_MODE_IDENTITY - - # ctypedef struct avifReformatState: - # float kr - # float kg - # float kb - # uint32_t yuvChannelBytes - # uint32_t rgbChannelBytes - # uint32_t rgbChannelCount - # uint32_t rgbPixelBytes - # uint32_t rgbOffsetBytesR - # uint32_t rgbOffsetBytesG - # uint32_t rgbOffsetBytesB - # uint32_t rgbOffsetBytesA - # uint32_t yuvDepth - # uint32_t rgbDepth - # avifRange yuvRange - # int yuvMaxChannel - # int rgbMaxChannel - # float yuvMaxChannelF - # float rgbMaxChannelF - # int uvBias - # avifPixelFormatInfo formatInfo - # float unormFloatTableY[1 << 12] - # float unormFloatTableUV[1 << 12] - # avifReformatMode mode - - # avifBool avifPrepareReformatState( - # const avifImage* image, - # const avifRGBImage* rgb, - # avifReformatState* state - # ) nogil - # Codec selection ctypedef enum avifCodecChoice: @@ -536,6 +546,7 @@ cdef extern from 'avif/avif.h': AVIF_CODEC_CHOICE_LIBGAV1 AVIF_CODEC_CHOICE_RAV1E AVIF_CODEC_CHOICE_SVT + AVIF_CODEC_CHOICE_AVM ctypedef enum avifCodecFlag: AVIF_CODEC_FLAG_CAN_DECODE @@ -657,6 +668,7 @@ cdef extern from 'avif/avif.h': uint64_t timescale double duration uint64_t durationInTimescales + int repetitionCount avifBool alphaPresent avifIOStats ioStats avifDiagnostics diag @@ -753,12 +765,20 @@ cdef extern from 'avif/avif.h': struct avifCodecSpecificOptions: pass + ctypedef struct avifScalingMode: + avifFraction horizontal + avifFraction vertical + ctypedef struct avifEncoder: avifCodecChoice codecChoice int maxThreads int speed int keyframeInterval uint64_t timescale + int repetitionCount + uint32_t extraLayerCount + int quality + int qualityAlpha int minQuantizer int maxQuantizer int minQuantizerAlpha @@ -766,6 +786,7 @@ cdef extern from 'avif/avif.h': int tileRowsLog2 int tileColsLog2 avifBool autoTiling + avifScalingMode scalingMode avifIOStats ioStats avifDiagnostics diag avifEncoderData* data @@ -801,7 +822,7 @@ cdef extern from 'avif/avif.h': avifEncoder* encoder, uint32_t gridCols, uint32_t gridRows, - const avifImage* const *cellImages, + const avifImage* const* cellImages, avifAddImageFlags addImageFlags ) @@ -810,7 +831,7 @@ cdef extern from 'avif/avif.h': avifRWData* output ) nogil - void avifEncoderSetCodecSpecificOption( + avifResult avifEncoderSetCodecSpecificOption( avifEncoder* encoder, const char* key, const char* value @@ -822,6 +843,30 @@ cdef extern from 'avif/avif.h': const avifImage* image ) nogil + avifBool avifImageIsOpaque( + const avifImage* image + ) nogil + + uint8_t* avifImagePlane( + const avifImage* image, + int channel + ) nogil + + uint32_t avifImagePlaneRowBytes( + const avifImage* image, + int channel + ) nogil + + uint32_t avifImagePlaneWidth( + const avifImage* image, + int channel + ) nogil + + uint32_t avifImagePlaneHeight( + const avifImage* image, + int channel + ) nogil + avifBool avifPeekCompatibleFileType( const avifROData* input ) nogil ++++++ quantize.patch ++++++ >From 93d1f751436e357d73eb6fdebc2af833059d9ea9 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke <cgoh...@cgohlke.com> Date: Sat, 2 Sep 2023 22:41:40 -0700 Subject: [PATCH] Add imagecodecs/_quantize.pyx --- imagecodecs/_quantize.pyx | 217 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 imagecodecs/_quantize.pyx diff --git a/imagecodecs/_quantize.pyx b/imagecodecs/_quantize.pyx new file mode 100644 index 0000000..912eb32 --- /dev/null +++ b/imagecodecs/_quantize.pyx @@ -0,0 +1,217 @@ +# imagecodecs/_quantize.pyx +# distutils: language = c +# cython: language_level = 3 +# cython: boundscheck=False +# cython: wraparound=False +# cython: cdivision=True +# cython: nonecheck=False + +# Copyright (c) 2023, Christoph Gohlke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +"""Quantize codec for the imagecodecs package.""" + +__version__ = '2023.9.4' + +include '_shared.pxi' + +from nc4var cimport * + +from libc.math cimport log10, log2, floor, ceil, round, pow + + +class QUANTIZE: + """Quantize codec constants.""" + + available = True + + class MODE(enum.IntEnum): + """Quantize mode.""" + + NOQUANTIZE = NC_NOQUANTIZE + BITGROOM = NC_QUANTIZE_BITGROOM + GRANULARBR = NC_QUANTIZE_GRANULARBR + BITROUND = NC_QUANTIZE_BITROUND + SCALE = 100 + + +class QuantizeError(RuntimeError): + """Quantize codec exceptions.""" + + +def quantize_version(): + """Return nc4var library version string.""" + return f'nc4var {NC_VERSION_MAJOR}.{NC_VERSION_MINOR}.{NC_VERSION_PATCH}' + + +def quantize_encode(data, mode, int nsd, out=None): + """Return quantized floating point data. + + ``nsd`` ("number of significant digits") is interpreted differently + by the modes: + + - BitGroom and Scale: number of significant decimal digits + - BitRound: number of significant binary digits (bits). + - Granular BitRound: ? + + """ + cdef: + numpy.ndarray src + numpy.ndarray dst + size_t src_size + nc_type src_type + int ret + int range_error = 0 + int strict_nc3 = 0 + int quantize_mode = NC_NOQUANTIZE + # uint64_t fill_value = 0 + + data = numpy.ascontiguousarray(data) + src = data + src_size = <size_t>src.size + + if src.dtype.kind != b'f' and src.itemsize not in {4, 8}: + raise ValueError('not a floating-point array') + + if src.itemsize == 4: + src_type = NC_FLOAT + else: + src_type = NC_DOUBLE + + if mode in { + NC_NOQUANTIZE, + NC_QUANTIZE_BITGROOM, + NC_QUANTIZE_GRANULARBR, + NC_QUANTIZE_BITROUND, + 100 + }: + quantize_mode = mode + elif mode == 'bitround': + quantize_mode = NC_QUANTIZE_BITROUND + elif mode == 'bitgroom': + quantize_mode = NC_QUANTIZE_BITGROOM + elif mode in {'granularbr', 'gbr'}: + quantize_mode = NC_QUANTIZE_GRANULARBR + elif mode == 'scale': + quantize_mode = 100 + elif mode == 'noquantize': + quantize_mode = NC_NOQUANTIZE + else: + raise ValueError(f'invalid quantize mode {mode!r}') + + if not 0 <= nsd < 64: + raise ValueError(f'invalid number of significant digits {nsd!r}') + + out = _create_array(out, data.shape, data.dtype) + dst = out + + with nogil: + if quantize_mode == 100: + if src_type == NC_FLOAT: + quantize_scale_f( + <float*>src.data, + <float*>dst.data, + src_size, + nsd + ) + else: + quantize_scale_d( + <double*>src.data, + <double*>dst.data, + src_size, + nsd + ) + else: + ret = nc4_convert_type( + <const void *>src.data, + <void *>dst.data, + src_type, + src_type, + src_size, + &range_error, + NULL, # fill_value + 1, # strict_nc3 + quantize_mode, + nsd + ) + if ret < 0: + raise QuantizeError(f'nc4_convert_type returnd {ret!r}') + + return out + + +def quantize_decode(data, mode, nsd, out=None): + """Return de-quantized data. Raise QuantizeError if lossy.""" + if mode != NC_NOQUANTIZE: + raise QuantizeError(f'Quantize mode {mode} is lossy.') + return data + +############################################################################### + +# quantize_scale +# Data is quantized using round(scale*data)/scale, where scale is 2**bits, +# and bits is determined from the nsd. For example, if nsd=1, bits will be 4. +# https://github.com/Blosc/bcolz utils.py + +cdef void quantize_scale_f( + const float* data, + float* out, + ssize_t size, + int nsb +) nogil: + cdef: + float scale + double exp + ssize_t i + + exp = log10(pow(10.0, -nsb)) + exp = floor(exp) if exp < 0.0 else ceil(exp) + scale = <float> pow(2.0, ceil(log2(pow(10.0, -exp)))) + + for i in range(size): + out[i] = round(data[i] * scale) / scale + + +cdef void quantize_scale_d( + const double* data, + double* out, + ssize_t size, + int nsb +) nogil: + cdef: + double scale + double exp + ssize_t i + + exp = log10(pow(10.0, -nsb)) + exp = floor(exp) if exp < 0.0 else ceil(exp) + scale = <double> pow(2.0, ceil(log2(pow(10.0, -exp)))) + + for i in range(size): + out[i] = round(data[i] * scale) / scale ++++++ tests.patch ++++++ >From 0030b7b74fc17ceb356d1f67633ba1734108dac9 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke <cgoh...@cgohlke.com> Date: Sun, 3 Sep 2023 09:36:27 -0700 Subject: [PATCH] Update tests/test_imagecodecs.py --- tests/test_imagecodecs.py | 131 ++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 25 deletions(-) Index: imagecodecs-2023.3.16/tests/test_imagecodecs.py =================================================================== --- imagecodecs-2023.3.16.orig/tests/test_imagecodecs.py +++ imagecodecs-2023.3.16/tests/test_imagecodecs.py @@ -79,6 +79,7 @@ except ImportError as exc: try: import zarr + from imagecodecs import numcodecs except ImportError: SKIP_NUMCODECS = True @@ -172,7 +173,7 @@ def test_dependency_exist(name): if SKIP_NUMCODECS and IS_PYPY: mayfail = True elif name in ('blosc', 'blosc2', 'snappy'): - if IS_PYPY or sys.version_info[1] >= 10: + if IS_PYPY or not IS_CG: mayfail = True try: importlib.import_module(name) @@ -194,7 +195,7 @@ def test_version_functions(): def test_stubs(): """Test stub attributes for non-existing extension.""" with pytest.raises(AttributeError): - imagecodecs._STUB + assert imagecodecs._STUB _add_codec('_stub') assert not imagecodecs._STUB # typing: ignore assert not imagecodecs._STUB.available @@ -873,6 +874,74 @@ def test_lzw_msb(): @pytest.mark.skipif( + not imagecodecs.QUANTIZE.available, reason='QUANTIZE missing' +) +@pytest.mark.parametrize( + 'mode', ['bitgroom', 'granularbr', 'bitround', 'scale'] +) +@pytest.mark.parametrize('dtype', ['f4', 'f8']) +def test_quantize_roundtrip(mode, dtype): + """Test quantize roundtrips.""" + nsd = 12 + atol = 0.006 + if mode == 'bitgroom': + nsd = (nsd - 1) // 3 # bits = math.ceil(nsd * 3.32) + 1 + if dtype == 'f4': + nsd //= 2 + atol = 0.5 + data = numpy.linspace(-2.1, 31.4, 51, dtype=dtype).reshape((3, 17)) + encoded = imagecodecs.quantize_encode(data, mode, nsd) + out = data.copy() + imagecodecs.quantize_encode(data, mode, nsd, out=out) + assert_array_equal(out, encoded) + assert_allclose(data, encoded, atol=atol) + + +@pytest.mark.skipif( + SKIP_NUMCODECS or not imagecodecs.QUANTIZE.available, + reason='QUANTIZE missing', +) +@pytest.mark.parametrize('nsd', [1, 4]) +@pytest.mark.parametrize('dtype', ['f4', 'f8']) +def test_quantize_bitround(dtype, nsd): + """Test BitRound quantize against numcodecs.""" + from numcodecs import BitRound + + from imagecodecs.numcodecs import Quantize + + # TODO: 31.4 fails + data = numpy.linspace(-2.1, 31.5, 51, dtype=dtype).reshape((3, 17)) + encoded = Quantize( + mode=imagecodecs.QUANTIZE.MODE.BITROUND, + nsd=nsd, + ).encode(data) + nc = BitRound(keepbits=nsd) + encoded2 = nc.decode(nc.encode(data)) + assert_array_equal(encoded, encoded2) + + +@pytest.mark.skipif( + SKIP_NUMCODECS or not imagecodecs.QUANTIZE.available, + reason='QUANTIZE missing', +) +@pytest.mark.parametrize('nsd', [1, 4]) +@pytest.mark.parametrize('dtype', ['f4', 'f8']) +def test_quantize_scale(dtype, nsd): + """Test Scale quantize against numcodecs.""" + from numcodecs import Quantize as Quantize2 + + from imagecodecs.numcodecs import Quantize + + data = numpy.linspace(-2.1, 31.4, 51, dtype=dtype).reshape((3, 17)) + encoded = Quantize( + mode=imagecodecs.QUANTIZE.MODE.SCALE, + nsd=nsd, + ).encode(data) + encoded2 = Quantize2(digits=nsd, dtype=dtype).encode(data) + assert_array_equal(encoded, encoded2) + + +@pytest.mark.skipif( not (imagecodecs.LZW.available and imagecodecs.DELTA.available), reason='skip', ) @@ -1818,7 +1896,7 @@ def test_rgbe_roundtrip(): @pytest.mark.skipif(not imagecodecs.CMS.available, reason='cms missing') def test_cms_profile(): """Test cms_profile function.""" - from imagecodecs import cms_profile, cms_profile_validate, CmsError + from imagecodecs import CmsError, cms_profile, cms_profile_validate with pytest.raises(CmsError): cms_profile_validate(b'12345') @@ -1934,7 +2012,7 @@ def test_cms_profile(): @pytest.mark.skipif(not imagecodecs.CMS.available, reason='cms missing') def test_cms_output_shape(): """Test _cms_output_shape function.""" - from imagecodecs._cms import _cms_output_shape, _cms_format + from imagecodecs._cms import _cms_format, _cms_output_shape for args, colorspace, planar, expected in ( (((6, 7), 'u1', 'gray'), 'gray', 0, (6, 7)), @@ -2086,7 +2164,7 @@ def test_cms_format(): @pytest.mark.parametrize('out', [None, True]) def test_cms_identity_transforms(dtype, outdtype, planar, outplanar, out): """Test CMS identity transforms.""" - from imagecodecs import cms_transform, cms_profile + from imagecodecs import cms_profile, cms_transform shape = (3, 256, 253) if planar else (256, 253, 3) dtype = numpy.dtype(dtype) @@ -2480,7 +2558,10 @@ def test_avif_strict_disabled(): @pytest.mark.skipif(not IS_CG, reason='avif missing') -@pytest.mark.parametrize('codec', ['auto', 'aom', 'rav1e', 'svt']) # 'libgav1' +@pytest.mark.parametrize( + 'codec', + ['auto', 'aom', 'rav1e', 'svt'], # 'libgav1', 'avm' +) def test_avif_encoder(codec): """Test various AVIF encoder codecs.""" data = numpy.load(datafiles('rgb.u1.npy')) @@ -2490,9 +2571,9 @@ def test_avif_encoder(codec): else: pixelformat = None encoded = imagecodecs.avif_encode( - data, level=6, codec=codec, pixelformat=pixelformat + data, level=95, codec=codec, pixelformat=pixelformat, numthreads=2 ) - decoded = imagecodecs.avif_decode(encoded) + decoded = imagecodecs.avif_decode(encoded, numthreads=2) assert_allclose(decoded, data, atol=5, rtol=0) @@ -3353,6 +3434,8 @@ def test_image_roundtrips(codec, dtype, data, bitspersample=12, *args, **kwargs ) + if level: + level += 95 atol = 10 elif codec == 'heif': if not imagecodecs.HEIF.available: @@ -3431,13 +3514,8 @@ def test_image_roundtrips(codec, dtype, if level < 100: atol *= 4 assert_allclose(data, decoded, atol=atol) - elif codec == 'avif' and level == 5: - if dtype.itemsize > 1: - # TODO: bug in libavif? - pytest.xfail('why does this fail?') - atol = 32 - else: - atol = 6 + elif codec == 'avif' and level == 94: + atol = 38 if dtype.itemsize > 1 else 6 assert_allclose(data, decoded, atol=atol) else: assert_array_equal(data, decoded, verbose=True) @@ -3847,7 +3926,7 @@ def test_numcodecs(codec, photometric): if photometric != 'rgb': pytest.xfail('AVIF does not support grayscale') compressor = numcodecs.Avif( - level=0, + level=100, speed=None, tilelog2=None, bitspersample=None,