Accepting request 1111056 from home:mcalabkova:branches:devel:languages:python:numeric

- Add patches for the compatibility with libavif 1.0.0:
  * libavif.patch
  * quantize.patch
  * avif.patch
  * tests.patch
  * integrate.patch

OBS-URL: https://build.opensuse.org/request/show/1111056
OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-imagecodecs?expand=0&rev=25
This commit is contained in:
Markéta Machová 2023-09-14 09:46:45 +00:00 committed by Git OBS Bridge
parent 502793a086
commit 3ac346bdcc
7 changed files with 1100 additions and 7 deletions

177
avif.patch Normal file
View File

@ -0,0 +1,177 @@
From 2f548c9a4df443948f2dfcde30a7211ce8b3adc2 Mon Sep 17 00:00:00 2001
From: Christoph Gohlke <cgohlke@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]

77
integrate.patch Normal file
View File

@ -0,0 +1,77 @@
From e9b5a984b72c9d4e14f9d37ec99389d25645c7fb Mon Sep 17 00:00:00 2001
From: Christoph Gohlke <cgohlke@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)

387
libavif.patch Normal file
View File

@ -0,0 +1,387 @@
From d04112759c48772c4d46a2dfa4f4c6a76e23c9a9 Mon Sep 17 00:00:00 2001
From: Christoph Gohlke <cgohlke@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

View File

@ -1,3 +1,13 @@
-------------------------------------------------------------------
Wed Sep 13 11:57:38 UTC 2023 - Markéta Machová <mmachova@suse.com>
- Add patches for the compatibility with libavif 1.0.0:
* libavif.patch
* quantize.patch
* avif.patch
* tests.patch
* integrate.patch
-------------------------------------------------------------------
Tue Sep 12 14:21:29 UTC 2023 - Markéta Machová <mmachova@suse.com>

View File

@ -36,6 +36,16 @@ Source1: imagecodecs_distributor_setup.py
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 Brotli}
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(blosc2) >= 2.7.1
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: pkgconfig(zlib-ng)
BuildRequires: zfp-devel
BuildRequires: pkgconfig(SvtAv1Dec)
BuildRequires: pkgconfig(SvtAv1Enc)
# 32-bit tests fail
BuildRequires: pkgconfig(libavif)
%endif
%endif
%python_subpackages
@ -144,6 +153,9 @@ Bitshuffle, and Float24 (24-bit floating point).
%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 @@ donttest="$donttest or (test_tiff and (webp or lerc or jpeg))"
%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

233
quantize.patch Normal file
View File

@ -0,0 +1,233 @@
From 93d1f751436e357d73eb6fdebc2af833059d9ea9 Mon Sep 17 00:00:00 2001
From: Christoph Gohlke <cgohlke@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

199
tests.patch Normal file
View File

@ -0,0 +1,199 @@
From 0030b7b74fc17ceb356d1f67633ba1734108dac9 Mon Sep 17 00:00:00 2001
From: Christoph Gohlke <cgohlke@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,