From b8ba50c846da569fbb74fc80cc74470852487f7f Mon Sep 17 00:00:00 2001 From: Sara Faghih-Naini Date: Wed, 30 Sep 2026 13:28:54 +0200 Subject: [PATCH] Add a value parameter so that the codec can decode to any constant --- README.md | 2 +- src/numcodecs_zero/__init__.py | 40 +++++++++++++++++++++++++++++----- tests/test_zero.py | 22 ++++++++++++++++--- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index bdde7f9..1a3a21c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # numcodecs-zero -`ZeroCodec` for the [`numcodecs`] buffer compression API. +`ZeroCodec` for the [`numcodecs`] buffer compression API: the data decodes to an array of the same shape and data type filled with a constant `value` (zero by default). [`numcodecs`]: https://numcodecs.readthedocs.io/en/stable/ diff --git a/src/numcodecs_zero/__init__.py b/src/numcodecs_zero/__init__.py index f147ede..1cfae97 100644 --- a/src/numcodecs_zero/__init__.py +++ b/src/numcodecs_zero/__init__.py @@ -16,17 +16,26 @@ class ZeroCodec(Codec): """ - Codec that decodes to an all-zero array of the same data type and shape as - the original data. + Codec that decodes to an array filled with a constant `value` (zero by + default) of the same data type and shape as the original data. Encoding produces a bytestring containing information on the data type and - shape. + shape; the `value` is part of the codec configuration. + + Parameters + ---------- + value : int | float, optional + The constant value that the data decodes to. """ - __slots__ = () + __slots__: tuple[str, ...] = ("_value",) + _value: int | float codec_id: str = "zero" # type: ignore + def __init__(self, *, value: int | float = 0) -> None: + self._value = value + def encode(self, buf: Buffer) -> bytes: """ Encode the `buf`fer information. @@ -76,7 +85,8 @@ def decode(self, buf: Buffer, out: None | Buffer = None) -> Buffer: ------- dec : Buffer Decoded data. May be any object supporting the new-style buffer - protocol. The decoded data will be all-zero after decoding. + protocol. The decoded data will be filled with the constant + `value` after decoding. """ b = numcodecs.compat.ensure_bytes(buf) @@ -89,9 +99,27 @@ def decode(self, buf: Buffer, out: None | Buffer = None) -> Buffer: for _ in range(leb128.u.decode_reader(b_io)[0]) ) - decoded = np.zeros(shape, dtype) + decoded = np.full(shape, self._value, dtype) return numcodecs.compat.ndarray_copy(decoded, out) # type: ignore + def get_config(self) -> dict: + """ + Returns the configuration of this codec. + + [`numcodecs.registry.get_codec(config)`][numcodecs.registry.get_codec] + can be used to reconstruct this codec from the returned config. + + Returns + ------- + config : dict + Configuration of this codec. + """ + + return dict(id=type(self).codec_id, value=self._value) + + def __repr__(self) -> str: + return f"{type(self).__name__}(value={self._value!r})" + numcodecs.registry.register_codec(ZeroCodec) diff --git a/tests/test_zero.py b/tests/test_zero.py index 3bb4875..6353ecb 100644 --- a/tests/test_zero.py +++ b/tests/test_zero.py @@ -9,15 +9,19 @@ def test_from_config(): assert codec.__class__.__module__ == "numcodecs_zero" -def check_roundtrip(data: np.ndarray): - codec = numcodecs.registry.get_codec(dict(id="zero")) +def check_roundtrip(data: np.ndarray, value: int | float = 0): + codec = numcodecs.registry.get_codec(dict(id="zero", value=value)) + assert codec.get_config() == dict(id="zero", value=value) encoded = codec.encode(data) decoded = codec.decode(encoded) assert decoded.dtype == data.dtype assert decoded.shape == data.shape - assert np.all(decoded == 0) + if isinstance(value, float) and np.isnan(value): + assert np.all(np.isnan(decoded)) + else: + assert np.all(decoded == value) def test_roundtrip(): @@ -25,3 +29,15 @@ def test_roundtrip(): check_roundtrip(np.zeros((0,))) check_roundtrip(np.arange(1000).reshape(10, 10, 10)) check_roundtrip(np.array([np.inf, -np.inf, np.nan, -np.nan, 0.0, -0.0])) + + +def test_constant_value(): + check_roundtrip(np.zeros((3, 4)), value=2.5) + check_roundtrip(np.zeros((3, 4), dtype=np.int32), value=-7) + check_roundtrip(np.zeros((3, 4)), value=np.nan) + check_roundtrip(np.zeros((0,)), value=1.0) + + # the default is still an all-zero reconstruction + codec = numcodecs.registry.get_codec(dict(id="zero")) + assert codec.get_config() == dict(id="zero", value=0) + assert repr(codec) == "ZeroCodec(value=0)"