Skip to content

Commit a9b00c7

Browse files
committed
Add Sperr.from_filter_options
1 parent 73c2e77 commit a9b00c7

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

src/hdf5plugin/_filters.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,30 @@ def __init__(
711711
mode, quality, swap, missing_value_mode
712712
)
713713

714+
@classmethod
715+
def from_filter_options(cls, filter_options: tuple[int, ...]) -> Sperr:
716+
if len(filter_options) < 2:
717+
raise ValueError(f"Expected at least 2 values, got {len(filter_options)}")
718+
719+
mode, quality, swap, missing_value_mode = cls.__unpack_options(
720+
meta=filter_options[0], ret=filter_options[1]
721+
)
722+
723+
if mode == 2:
724+
return cls(
725+
peak_signal_to_noise_ratio=quality,
726+
swap=swap,
727+
missing_value_mode=missing_value_mode,
728+
)
729+
if mode == 3:
730+
return cls(
731+
absolute=quality, swap=swap, missing_value_mode=missing_value_mode
732+
)
733+
if mode == 1:
734+
return cls(rate=quality, swap=swap, missing_value_mode=missing_value_mode)
735+
736+
raise ValueError(f"Mode must be in [1, 3], got {mode}")
737+
714738
@classmethod
715739
def __pack_options(
716740
cls, mode: int, quality: float, swap: bool, missing_value_mode: int
@@ -746,6 +770,40 @@ def __pack_options(
746770

747771
return ret, missing_value_mode
748772

773+
@classmethod
774+
def __unpack_options(cls, meta: int, ret: int) -> tuple[int, float, bool, int]:
775+
# Unpack missing value mode from packed_info bits 6-9
776+
# See h5zsperr_unpack_extra_info
777+
missing_value_mode = (meta >> 6) & 0b1111
778+
779+
# Unpack other fields from ret
780+
# See H5Z_SPERR_decode_cd_values
781+
swap = bool(ret >> (cls._INTEGER_BITS + cls._FRACTIONAL_BITS + 3))
782+
783+
bit1 = (ret >> (cls._INTEGER_BITS + cls._FRACTIONAL_BITS)) & 1
784+
bit2 = (ret >> (cls._INTEGER_BITS + cls._FRACTIONAL_BITS + 1)) & 1
785+
if bit1 and not bit2:
786+
mode = 1
787+
elif not bit1 and bit2:
788+
mode = 2
789+
elif bit1 and bit2:
790+
mode = 3
791+
else:
792+
raise ValueError("Mode must be in [1, 3], got 0")
793+
794+
negative = bool((ret >> (cls._INTEGER_BITS + cls._FRACTIONAL_BITS - 1)) & 1)
795+
796+
mask = 1 << (cls._INTEGER_BITS + cls._FRACTIONAL_BITS - 1)
797+
masked_ret = ret & (mask - 1)
798+
799+
quality = float(masked_ret) / float(1 << cls._FRACTIONAL_BITS)
800+
if negative:
801+
quality *= -1.0
802+
if mode == 3:
803+
quality = math.exp2(quality)
804+
805+
return mode, quality, swap, missing_value_mode
806+
749807

750808
class SZ(FilterBase):
751809
"""``h5py.Group.create_dataset``'s compression arguments for using SZ2 filter.

src/hdf5plugin/test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,27 @@ def testLZ4(self):
577577
compression_filter = hdf5plugin.LZ4.from_filter_options(filter_options)
578578
self.assertEqual(compression_filter.filter_options, expected_options)
579579

580+
def testSperr(self):
581+
for filter_options, expected_filter in (
582+
((1043, 269484032, 128, 0, 0), hdf5plugin.Sperr()),
583+
(
584+
(1107, 2418016256, 256, 0, 0),
585+
hdf5plugin.Sperr(rate=32, swap=True, missing_value_mode=1),
586+
),
587+
((1043, 940177214, 256, 0, 0), hdf5plugin.Sperr(absolute=1e-3)),
588+
(
589+
(1171, 537001984, 256, 0, 0),
590+
hdf5plugin.Sperr(peak_signal_to_noise_ratio=2.0, missing_value_mode=2),
591+
),
592+
):
593+
with self.subTest(filter_options=filter_options):
594+
compression_filter = hdf5plugin.Sperr.from_filter_options(
595+
filter_options
596+
)
597+
self.assertEqual(
598+
compression_filter.filter_options, expected_filter.filter_options
599+
)
600+
580601
def testZstd(self):
581602
for filter_options, expected_options in (
582603
# (clevel,)
@@ -643,6 +664,11 @@ def testLZ4(self):
643664
data = numpy.arange(256**2, dtype=numpy.float32).reshape(256, 256)
644665
self._test(hdf5plugin.LZ4(), data)
645666

667+
@unittest.skipUnless(should_test("sperr"), "Sperr filter not available")
668+
def testSperr(self):
669+
data = numpy.arange(256**2, dtype=numpy.float32).reshape(256, 256)
670+
self._test(hdf5plugin.Sperr(), data)
671+
646672
@unittest.skipUnless(should_test("zstd"), "Zstd filter not available")
647673
def testZstd(self):
648674
data = numpy.arange(256**2, dtype=numpy.float32).reshape(256, 256)

0 commit comments

Comments
 (0)