Skip to content

Commit

Permalink
convert to pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
leoschwarz committed Sep 30, 2024
1 parent f82f315 commit d3ea28d
Showing 1 changed file with 106 additions and 109 deletions.
215 changes: 106 additions & 109 deletions tests/unit/image/test_spatial_smoothing_sparse_aware.py
Original file line number Diff line number Diff line change
@@ -1,118 +1,115 @@
import unittest
import warnings
from datetime import timedelta
from functools import cached_property

import hypothesis
import numpy as np
from hypothesis import given, strategies, settings
import pytest
from hypothesis import given, strategies
from sparse import GCXS
from xarray import DataArray

from depiction.image.spatial_smoothing_sparse_aware import SpatialSmoothingSparseAware


def _treat_warnings_as_error(test_case) -> None:
"""To be called from setUp."""
# TODO reuse or refactor (and move out of integration_test_utils and rather a more general test_utils?)
warnings_ctx = warnings.catch_warnings()
warnings_ctx.__enter__()
warnings.simplefilter("error")
test_case.addCleanup(warnings_ctx.__exit__, None, None, None)


class TestSpatialSmoothingSparseAware(unittest.TestCase):
def setUp(self) -> None:
self.mock_kernel_size = 5
self.mock_kernel_std = 1.0
self.mock_use_interpolation = False
_treat_warnings_as_error(self)

@cached_property
def mock_smooth(self) -> SpatialSmoothingSparseAware:
return SpatialSmoothingSparseAware(
kernel_size=self.mock_kernel_size,
kernel_std=self.mock_kernel_std,
use_interpolation=self.mock_use_interpolation,
)

def _convert_array(self, arr: DataArray, variant: str) -> DataArray:
# TODO duplicated with SpatialSmoothing test
if variant == "dense":
return arr
elif variant == "sparse":
values = GCXS.from_numpy(arr)
return DataArray(values, dims=arr.dims, coords=arr.coords, attrs=arr.attrs, name=arr.name)

def _test_variants(self, *variants, subtest=True):
# TODO duplicated with SpatialSmoothing test (but this one is extended)
for variant in variants:
if subtest:
with self.subTest(variant=variant):
yield variant
else:
yield variant

def test_smooth_when_unchanged(self) -> None:
for variant in self._test_variants("dense", "sparse"):
image = DataArray(np.concatenate([np.ones((5, 5, 1)), np.zeros((5, 5, 1))], axis=0), dims=("y", "x", "c"))
image = self._convert_array(image, variant)
smoothed = self.mock_smooth.smooth(image, bg_value=0)
np.testing.assert_array_almost_equal(1.0, smoothed.values[:5, :, 0], decimal=8)
np.testing.assert_array_almost_equal(0.0, smoothed.values[-5:, :, 0], decimal=8)
self.assertEqual(("y", "x", "c"), smoothed.dims)

@given(strategies.floats(min_value=0, max_value=1e12, allow_subnormal=False))
@settings(deadline=timedelta(seconds=1))
def test_smooth_preserves_values(self, fill_value) -> None:
for variant in self._test_variants("dense", "sparse", subtest=False):
dense_image = DataArray(np.full((2, 5, 1), fill_value=fill_value), dims=("y", "x", "c"))
image = self._convert_array(dense_image, variant)
smoothed = self.mock_smooth.smooth(image, bg_value=0)
np.testing.assert_allclose(dense_image.values, smoothed.values, rtol=1e-8)

def test_smooth_when_bg_nan(self) -> None:
for variant in self._test_variants("dense", "sparse"):
dense_image = DataArray(
np.concatenate([np.full((5, 5, 1), np.nan), np.ones((5, 5, 1)), np.zeros((5, 5, 1))]),
dims=("y", "x", "c"),
)
image = self._convert_array(dense_image, variant)
smoothed = self.mock_smooth.smooth(image, bg_value=np.nan)
np.testing.assert_array_equal(np.nan, smoothed.values[:5, 0])
np.testing.assert_array_almost_equal(1.0, smoothed.values[5:8, :, 0], decimal=8)
np.testing.assert_array_almost_equal(0.0, smoothed.values[-3:, :, 0], decimal=8)
smoothed_part = smoothed.values[8:12, :, 0]
for i_value, value in enumerate([0.94551132, 0.70130997, 0.29869003, 0.05448868]):
np.testing.assert_array_almost_equal(value, smoothed_part[i_value, :], decimal=8)

def test_smooth_casts_when_integer(self) -> None:
for variant in self._test_variants("dense", "sparse"):
image_dense = DataArray(np.full((2, 5, 1), fill_value=10, dtype=int), dims=("y", "x", "c"))
image = self._convert_array(image_dense, variant)
res_values = self.mock_smooth.smooth(image=image)
self.assertEqual(np.float64, res_values.dtype)
np.testing.assert_allclose(image_dense.values, res_values.values, rtol=1e-8)

def test_smooth_dense_when_use_interpolation(self) -> None:
self.mock_use_interpolation = True
mock_image = np.full((9, 5), fill_value=3.0)
mock_image[4, 2] = np.nan
smoothed = self.mock_smooth._smooth_dense(image_2d=mock_image, bg_value=np.nan)
self.assertEqual(0, np.sum(np.isnan(smoothed)))
self.assertAlmostEqual(3, smoothed[4, 2], places=6)

def test_gaussian_kernel(self) -> None:
self.mock_kernel_size = 3
self.mock_kernel_std = 1.0
expected_arr = np.array(
[
[0.075114, 0.123841, 0.075114],
[0.123841, 0.20418, 0.123841],
[0.075114, 0.123841, 0.075114],
]
)
np.testing.assert_allclose(expected_arr, self.mock_smooth.gaussian_kernel, atol=1e-6)


if __name__ == "__main__":
unittest.main()
@pytest.fixture(autouse=True)
def _setup_test(treat_warnings_as_error):
pass


@pytest.fixture
def mock_kernel_size():
return 5


@pytest.fixture
def mock_kernel_std():
return 1.0


@pytest.fixture
def mock_use_interpolation():
return False


@pytest.fixture
def mock_smooth(mock_kernel_size, mock_kernel_std, mock_use_interpolation):
return SpatialSmoothingSparseAware(
kernel_size=mock_kernel_size,
kernel_std=mock_kernel_std,
use_interpolation=mock_use_interpolation,
)


def _convert_array(arr: DataArray, variant: str) -> DataArray:
if variant == "dense":
return arr
elif variant == "sparse":
values = GCXS.from_numpy(arr)
return DataArray(values, dims=arr.dims, coords=arr.coords, attrs=arr.attrs, name=arr.name)


@pytest.mark.parametrize("variant", ["dense", "sparse"])
def test_smooth_when_unchanged(mock_smooth, variant):
image = DataArray(np.concatenate([np.ones((5, 5, 1)), np.zeros((5, 5, 1))], axis=0), dims=("y", "x", "c"))
image = _convert_array(image, variant)
smoothed = mock_smooth.smooth(image, bg_value=0)
np.testing.assert_array_almost_equal(1.0, smoothed.values[:5, :, 0], decimal=8)
np.testing.assert_array_almost_equal(0.0, smoothed.values[-5:, :, 0], decimal=8)
assert smoothed.dims == ("y", "x", "c")


@pytest.mark.parametrize("variant", ["dense", "sparse"])
@given(fill_value=strategies.floats(min_value=0, max_value=1e12, allow_subnormal=False))
@hypothesis.settings(
deadline=timedelta(seconds=1), suppress_health_check=[hypothesis.HealthCheck.function_scoped_fixture]
)
def test_smooth_preserves_values(mock_smooth, fill_value, variant):
dense_image = DataArray(np.full((2, 5, 1), fill_value=fill_value), dims=("y", "x", "c"))
image = _convert_array(dense_image, variant)
smoothed = mock_smooth.smooth(image, bg_value=0)
np.testing.assert_allclose(dense_image.values, smoothed.values, rtol=1e-8)


@pytest.mark.parametrize("variant", ["dense", "sparse"])
def test_smooth_when_bg_nan(mock_smooth, variant):
dense_image = DataArray(
np.concatenate([np.full((5, 5, 1), np.nan), np.ones((5, 5, 1)), np.zeros((5, 5, 1))]),
dims=("y", "x", "c"),
)
image = _convert_array(dense_image, variant)
smoothed = mock_smooth.smooth(image, bg_value=np.nan)
np.testing.assert_array_equal(np.nan, smoothed.values[:5, 0])
np.testing.assert_array_almost_equal(1.0, smoothed.values[5:8, :, 0], decimal=8)
np.testing.assert_array_almost_equal(0.0, smoothed.values[-3:, :, 0], decimal=8)
smoothed_part = smoothed.values[8:12, :, 0]
for i_value, value in enumerate([0.94551132, 0.70130997, 0.29869003, 0.05448868]):
np.testing.assert_array_almost_equal(value, smoothed_part[i_value, :], decimal=8)


@pytest.mark.parametrize("variant", ["dense", "sparse"])
def test_smooth_casts_when_integer(mock_smooth, variant):
image_dense = DataArray(np.full((2, 5, 1), fill_value=10, dtype=int), dims=("y", "x", "c"))
image = _convert_array(image_dense, variant)
res_values = mock_smooth.smooth(image=image)
assert res_values.dtype == np.float64
np.testing.assert_allclose(image_dense.values, res_values.values, rtol=1e-8)


@pytest.mark.parametrize("mock_use_interpolation", [True])
def test_smooth_dense_when_use_interpolation(mock_smooth):
mock_image = np.full((9, 5), fill_value=3.0)
mock_image[4, 2] = np.nan
smoothed = mock_smooth._smooth_dense(image_2d=mock_image, bg_value=np.nan)
assert np.sum(np.isnan(smoothed)) == 0
np.testing.assert_almost_equal(smoothed[4, 2], 3, decimal=6)


@pytest.mark.parametrize("mock_kernel_size, mock_kernel_std", [(3, 1.0)])
def test_gaussian_kernel(mock_smooth):
expected_arr = np.array(
[
[0.075114, 0.123841, 0.075114],
[0.123841, 0.20418, 0.123841],
[0.075114, 0.123841, 0.075114],
]
)
np.testing.assert_allclose(expected_arr, mock_smooth.gaussian_kernel, atol=1e-6)

0 comments on commit d3ea28d

Please sign in to comment.