Skip to content
This repository has been archived by the owner on Feb 11, 2025. It is now read-only.

Commit

Permalink
Merge pull request #8 from openclimatefix/jacob/fix-tests
Browse files Browse the repository at this point in the history
Update testing
  • Loading branch information
jacobbieker authored Jul 19, 2024
2 parents 013207c + 4ee566e commit e521a5a
Show file tree
Hide file tree
Showing 2 changed files with 10 additions and 105 deletions.
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
numpy==1.26.4
numcodecs
blosc2
pytest
113 changes: 8 additions & 105 deletions tests/test_ocf_blosc2.py
Original file line number Diff line number Diff line change
@@ -1,111 +1,14 @@
"""Unit tests for satip.jpeg_xl_float_with_nans."""

import unittest
"""Unit tests for ocf_blosc2"""

import numpy as np

import ocf_blosc2


class TestJpegXlFloatWithNaNs(unittest.TestCase):
"""Test class for unittest for the class methods and the functions.
We only test our home-written functions.
The two similarly named class functions encode and decode are mostly wrappers
around our home-written function output piped into an external library.
Testing the functionality of the external functions is out of scope.
"""

def setUp(self) -> None: # noqa D102
# Define synthetic input array and the expected target array:
self.buf = np.asarray([np.nan, 0.0, 0.5, 1.0], dtype=np.float32)
self.encoded = np.asarray(
[NAN_VALUE, LOWER_BOUND_FOR_REAL_PIXELS, 0.5 * (1 + LOWER_BOUND_FOR_REAL_PIXELS), 1.0],
dtype=np.float32,
)

self.jpegxl = ocf_blosc2.Blosc2()

return super().setUp()

def test_encode(self):
"""Tests the encoding function.
After encoding the raw array, the nan-values should be gone and the
real values should be transformed to the range specified by the
constants imported from the source code. See there for more details.
"""
# Check that the enconded buffer matches the expected target
# (attention: use a copy of the originals!):
self.assertTrue(np.isclose(encode_nans(self.buf.copy()), self.encoded).all())

def test_decode(self):
"""Tests the decoding function.
When taking what was previously the encoded array and decode it,
we expect to get the original buf-array back again.
"""
# As np.nan != np.nan (!) and thus np.isclose or array comparison do not consider
# two nan-values to be close or equal, we have to replace all nan-values with
# a numeric value before comparison. This numeric value should be one that
# can not be created via decoding (e.g. a negative number).
nan_replacement = -3.14
self.assertTrue(
np.isclose(
np.nan_to_num(self.buf, nan_replacement),
np.nan_to_num(decode_nans(self.encoded.copy()), nan_replacement),
).all()
)

def test_class_roundtrip(self):
"""Tests the class-defined wrappers around our home-written functions.
We test whether a back-and-forth transformation (nested encode-decode)
will give us back our original input value.
"""
reshaped_buf = self.buf.copy().reshape((1, -1, 1))

roundtrip_result = self.jpegxl.decode(self.jpegxl.encode(reshaped_buf.copy()))

# For reasons explained in the decoding test, we have to manually replace
# the nan-values to make them comparable:
nan_replacement = -3.14
reshaped_buf = np.nan_to_num(reshaped_buf, nan_replacement)
roundtrip_result = np.nan_to_num(roundtrip_result, nan_replacement)

# When we do the comparison, we have to be very lenient, as the external library
# will have worked its compression magic, so values will not completely align.
# Also, going back and forth removes the information about the channel number
# in our test case (presumably b/c we here only have one channel for simplicity's sake).
# So we have to reshape both:
self.assertTrue(
np.isclose(reshaped_buf.reshape((-1)), roundtrip_result.reshape((-1)), atol=0.1).all()
)

def test_consistent_init_params(self):
"""The JpegXLFloat-class has to be initialised with specific parameter combinations.
Stuff that is allowed:
1. If lossless = None, then everything is allowed.
2. If lossless = True, then level has to be None and distance has to be None or 0
3. If lossless = False, then everything is allowed.
To test this, we will try various parameters and see that the class gets
initialised properly, w/o throwing any errors.
"""

# Sub-case 1:
self.assertTrue(JpegXlFloatWithNaNs(lossless=None, level="very_high", distance=-10))

# Sub-case 2:
with self.assertRaises(AssertionError):
JpegXlFloatWithNaNs(lossless=True, level=1, distance=1)

with self.assertRaises(AssertionError):
JpegXlFloatWithNaNs(lossless=True, level=None, distance=1)

with self.assertRaises(AssertionError):
JpegXlFloatWithNaNs(lossless=True, level=2, distance=0)

# Sub-case 3:
self.assertTrue(JpegXlFloatWithNaNs(lossless=False))
def test_roundtrip():
buf = np.asarray([np.nan, 0.0, 0.5, 1.0], dtype=np.float32)
blosc2 = ocf_blosc2.Blosc2()
comp_arr = blosc2.encode(buf)
dest = np.empty(buf.shape, buf.dtype)
blosc2.decode(comp_arr, out=dest)
assert np.allclose(buf, dest, equal_nan=True)

0 comments on commit e521a5a

Please sign in to comment.