From a8113e7c7a79fa6a2b8337ca5e36ba824fb39c1d Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Mon, 29 Dec 2025 17:08:09 +0100 Subject: [PATCH 1/9] Add tests for `Image.clip_to_dtype` with Dask arrays (failing) --- tests/module/test__image.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/module/test__image.py b/tests/module/test__image.py index c49c6e9..69c7e63 100644 --- a/tests/module/test__image.py +++ b/tests/module/test__image.py @@ -589,3 +589,28 @@ def test__bool_to_uint8(self): ) img_clipped = img.clip_to_dtype(np.uint8) self.assertIs(img_clipped, img) + + +class Image__clip_to_dtype__dask(Image__clip_to_dtype): + + def create_non_bool_image(self, *args, **kwargs) -> giatools.image.Image: + import dask.array as da + img = super().create_non_bool_image(*args, **kwargs) + img.data = da.from_array(img.data, chunks=(1,) * img.data.ndim) + return img + + @minimum_python_version(3, 11) + def test__float32_to_int8__no_clip(self): + super().test__float32_to_int8__no_clip() + + @minimum_python_version(3, 11) + def test__float32_to_float16__clip_below(self): + super().test__float32_to_float16__clip_below() + + @minimum_python_version(3, 11) + def test__float32_to_int8__clip_above(self): + super().test__float32_to_int8__clip_above() + + @minimum_python_version(3, 11) + def test__bool_to_uint8(self): + super().test__bool_to_uint8() From c3d4d080693f01a735898a732cd881fe14c1d773 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Mon, 29 Dec 2025 17:08:31 +0100 Subject: [PATCH 2/9] Bump version to 0.7.2 --- giatools/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/giatools/version.py b/giatools/version.py index ee0eff8..0f78390 100644 --- a/giatools/version.py +++ b/giatools/version.py @@ -1,5 +1,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 7 -VERSION_PATCH = 1 +VERSION_PATCH = 2 __version__ = '%d.%d.%d' % (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) From 38dca65090c35da6b71ea26b40b6c4d80d556bde Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 30 Dec 2025 00:34:19 +0100 Subject: [PATCH 3/9] Fix --- giatools/image.py | 11 +++++++++-- tests/module/test__image.py | 31 ++++++++++++------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/giatools/image.py b/giatools/image.py index c26a011..cb67545 100644 --- a/giatools/image.py +++ b/giatools/image.py @@ -485,8 +485,15 @@ def clip_to_dtype(self, dtype: _np.dtype, force_copy: bool = False) -> _T.Self: raise TypeError('Clipping to boolean dtype is not supported.') # Determine the actual range of the source image - min_src_value = self.data.min().item() # convert to native Python type (float, int) - max_src_value = self.data.max().item() # convert to native Python type (float, int) + if hasattr(self.data, 'compute'): # Dask array + import dask.array as da + min_src_value, max_src_value = ( + value.item() # convert to native Python type (float, int) + for value in da.compute(self.data.min(), self.data.max()) + ) + else: # NumPy array + min_src_value = self.data.min().item() # convert to native Python type (float, int) + max_src_value = self.data.max().item() # convert to native Python type (float, int) # Determine the valid range for the target dtype if _np.issubdtype(dtype, _np.integer): diff --git a/tests/module/test__image.py b/tests/module/test__image.py index 69c7e63..4335253 100644 --- a/tests/module/test__image.py +++ b/tests/module/test__image.py @@ -546,6 +546,12 @@ def test__conversion__to_bool__invalid(self): class Image__clip_to_dtype(ImageTestCase, ImageTestCase__dtype_mixin): + def create_bool_image(self) -> giatools.image.Image: + return giatools.image.Image( + data=np.random.choice(a=[False, True], size=(2, 3, 26, 32)), + axes='CYXZ', + ) + def test__float32_to_int8__no_clip(self): img = self.create_non_bool_image( dtype=np.float32, @@ -583,10 +589,7 @@ def test__float32_to_int8__clip_above(self): self.assertEqual(img_clipped.data.max(), +127.0) def test__bool_to_uint8(self): - img = giatools.image.Image( - data=np.random.choice(a=[False, True], size=(2, 3, 26, 32)), - axes='CYXZ', - ) + img = self.create_bool_image() img_clipped = img.clip_to_dtype(np.uint8) self.assertIs(img_clipped, img) @@ -599,18 +602,8 @@ def create_non_bool_image(self, *args, **kwargs) -> giatools.image.Image: img.data = da.from_array(img.data, chunks=(1,) * img.data.ndim) return img - @minimum_python_version(3, 11) - def test__float32_to_int8__no_clip(self): - super().test__float32_to_int8__no_clip() - - @minimum_python_version(3, 11) - def test__float32_to_float16__clip_below(self): - super().test__float32_to_float16__clip_below() - - @minimum_python_version(3, 11) - def test__float32_to_int8__clip_above(self): - super().test__float32_to_int8__clip_above() - - @minimum_python_version(3, 11) - def test__bool_to_uint8(self): - super().test__bool_to_uint8() + def create_bool_image(self) -> giatools.image.Image: + import dask.array as da + img = super().create_bool_image() + img.data = da.from_array(img.data, chunks=(1,) * img.data.ndim) + return img From da22a40b968cb365d6862e778c33c45ccf91f4e4 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 30 Dec 2025 00:47:18 +0100 Subject: [PATCH 4/9] Refactor test__image.py --- tests/module/test__image.py | 44 ++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/tests/module/test__image.py b/tests/module/test__image.py index 4335253..ccb7c2a 100644 --- a/tests/module/test__image.py +++ b/tests/module/test__image.py @@ -320,6 +320,27 @@ def create_non_bool_image( metadata=metadata if metadata is not None else unittest.mock.Mock(), ) + def create_bool_image(self) -> giatools.image.Image: + return giatools.image.Image( + data=np.random.choice(a=[False, True], size=(2, 3, 26, 32)), + axes='CYXZ', + ) + + +class ImageTestCase__dtype_mixin__dask(ImageTestCase__dtype_mixin): + + def create_non_bool_image(self, *args, **kwargs) -> giatools.image.Image: + import dask.array as da + img = super().create_non_bool_image(*args, **kwargs) + img.data = da.from_array(img.data, chunks=(5,) * img.data.ndim) + return img + + def create_bool_image(self) -> giatools.image.Image: + import dask.array as da + img = super().create_bool_image() + img.data = da.from_array(img.data, chunks=(5,) * img.data.ndim) + return img + class Image__astype(ImageTestCase, ImageTestCase__dtype_mixin): @@ -544,13 +565,7 @@ def test__conversion__to_bool__invalid(self): img.astype(bool) -class Image__clip_to_dtype(ImageTestCase, ImageTestCase__dtype_mixin): - - def create_bool_image(self) -> giatools.image.Image: - return giatools.image.Image( - data=np.random.choice(a=[False, True], size=(2, 3, 26, 32)), - axes='CYXZ', - ) +class Image__clip_to_dtype__mixin: def test__float32_to_int8__no_clip(self): img = self.create_non_bool_image( @@ -594,16 +609,9 @@ def test__bool_to_uint8(self): self.assertIs(img_clipped, img) -class Image__clip_to_dtype__dask(Image__clip_to_dtype): +class Image__clip_to_dtype(ImageTestCase, ImageTestCase__dtype_mixin, Image__clip_to_dtype__mixin): + pass # Tests with NumPy arrays - def create_non_bool_image(self, *args, **kwargs) -> giatools.image.Image: - import dask.array as da - img = super().create_non_bool_image(*args, **kwargs) - img.data = da.from_array(img.data, chunks=(1,) * img.data.ndim) - return img - def create_bool_image(self) -> giatools.image.Image: - import dask.array as da - img = super().create_bool_image() - img.data = da.from_array(img.data, chunks=(1,) * img.data.ndim) - return img +class Image__clip_to_dtype__dask(ImageTestCase, ImageTestCase__dtype_mixin__dask, Image__clip_to_dtype__mixin): + pass # Tests with Dask arrays From fe8533e3c8c6a3ae123e55f27a0f270a10c4a652 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 30 Dec 2025 00:54:24 +0100 Subject: [PATCH 5/9] Add failing integration tests for `Image.astype` with Dask arrays --- tests/module/test__image.py | 58 +++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/tests/module/test__image.py b/tests/module/test__image.py index ccb7c2a..d095b4a 100644 --- a/tests/module/test__image.py +++ b/tests/module/test__image.py @@ -19,6 +19,21 @@ permute_axes, ) +exact_dtype_list = [ + np.uint8, + np.int8, + np.uint16, + np.int16, + np.uint32, + np.int32, + np.uint64, + np.int64, + np.float16, + np.float32, + np.float64, + bool, +] + class ImageTestCase(unittest.TestCase): @@ -269,21 +284,6 @@ def test__dask_array__zyx__iterate__yx(self, joint_axes: str): class ImageTestCase__dtype_mixin: - exact_dtype_list = [ - np.uint8, - np.int8, - np.uint16, - np.int16, - np.uint32, - np.int32, - np.uint64, - np.int64, - np.float16, - np.float32, - np.float64, - bool, - ] - def create_non_bool_image( self, dtype: np.dtype, @@ -342,9 +342,9 @@ def create_bool_image(self) -> giatools.image.Image: return img -class Image__astype(ImageTestCase, ImageTestCase__dtype_mixin): +class Image__astype__mixin: - exact_dtype_list = list(frozenset(ImageTestCase__dtype_mixin.exact_dtype_list) - {bool}) + exact_non_bool_dtype_list = list(frozenset(exact_dtype_list) - {bool}) inexact_dtype_list = [ np.floating, @@ -458,8 +458,8 @@ def convert(_img): self.assertIs(img.data, img_converted.data) def test__non_bool__exact(self): - for src_dtype in self.exact_dtype_list: - for dst_dtype in self.exact_dtype_list: + for src_dtype in self.exact_non_bool_dtype_list: + for dst_dtype in self.exact_non_bool_dtype_list: for force_copy in (False, True): with self.subTest(f'from {src_dtype} to {dst_dtype} (force_copy={force_copy})'): self._test_non_bool_conversion(src_dtype, dst_dtype, force_copy=force_copy) @@ -496,7 +496,7 @@ def _get_expected_dtype(src_dtype: np.dtype, dst_dtype: npt.DTypeLike) -> np.dty return dst_dtype def test__non_bool__inexact(self): - for src_dtype in self.exact_dtype_list: + for src_dtype in self.exact_non_bool_dtype_list: for dst_dtype in self.inexact_dtype_list: for force_copy in (False, True): with self.subTest(f'from {src_dtype} to {dst_dtype} (force_copy={force_copy})'): @@ -515,7 +515,7 @@ def test__conversion__from_bool(self): original_axes='QTCYXZ', ) assert img.data.dtype == bool # sanity check - for dst_dtype in self.exact_dtype_list + self.inexact_dtype_list: + for dst_dtype in self.exact_non_bool_dtype_list + self.inexact_dtype_list: for force_copy in (False, True): with self.subTest(f'from bool to {dst_dtype} (force_copy={force_copy})'): expected_dtype = self._get_expected_dtype(bool, dst_dtype) @@ -535,28 +535,28 @@ def _test_conversion_to_bool(self, img, expected_data): np.testing.assert_array_equal(img_converted.data, expected_data) def test__conversion__to_bool__2_labels(self): - for src_dtype in self.exact_dtype_list: + for src_dtype in self.exact_non_bool_dtype_list: img = self.create_non_bool_image(src_dtype) img.data = 10 + 5 * (img.data > img.data.mean()).astype(img.data.dtype) assert list(np.unique(img.data)) == [10, 15] # sanity check self._test_conversion_to_bool(img, expected_data=img.data > 12) def test__conversion__to_bool__1_non_zero_label(self): - for src_dtype in self.exact_dtype_list: + for src_dtype in self.exact_non_bool_dtype_list: img = self.create_non_bool_image(src_dtype) img.data.fill(15) assert img.data.dtype == src_dtype # sanity check self._test_conversion_to_bool(img, expected_data=np.ones(img.data.shape, dtype=bool)) def test__conversion__to_bool__1_zero_label(self): - for src_dtype in self.exact_dtype_list: + for src_dtype in self.exact_non_bool_dtype_list: img = self.create_non_bool_image(src_dtype) img.data.fill(0) assert img.data.dtype == src_dtype # sanity check self._test_conversion_to_bool(img, expected_data=np.zeros(img.data.shape, dtype=bool)) def test__conversion__to_bool__invalid(self): - for src_dtype in self.exact_dtype_list: + for src_dtype in self.exact_non_bool_dtype_list: img = self.create_non_bool_image(src_dtype) img.data = np.random.randint(0, 3, img.data.shape).astype(src_dtype) assert len(np.unique(img.data)) > 2 # sanity check @@ -565,6 +565,14 @@ def test__conversion__to_bool__invalid(self): img.astype(bool) +class Image__astype(ImageTestCase, ImageTestCase__dtype_mixin, Image__astype__mixin): + pass # Tests with NumPy arrays + + +class Image__astype__dask(ImageTestCase, ImageTestCase__dtype_mixin__dask, Image__astype__mixin): + pass # Tests with Dask arrays + + class Image__clip_to_dtype__mixin: def test__float32_to_int8__no_clip(self): From 03bdd79edce9dd1b415bba439cfa5884f363de41 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 30 Dec 2025 01:10:31 +0100 Subject: [PATCH 6/9] Fix --- giatools/image.py | 38 +++++++++++++++------- tests/module/test__image.py | 63 ++++++++++++++++++++++++------------- 2 files changed, 68 insertions(+), 33 deletions(-) diff --git a/giatools/image.py b/giatools/image.py index cb67545..97caf5b 100644 --- a/giatools/image.py +++ b/giatools/image.py @@ -417,7 +417,7 @@ def astype( # Special case: Conversion to `bool` elif dtype == bool: - labels = _np.unique(self.data) + labels = _unique(self.data) if len(labels) > 2: raise ValueError( f'Cannot convert image data from {self.data.dtype} to bool without overflows ' @@ -442,8 +442,7 @@ def astype( dtype = resolve_unsignedinteger_to # Check for overflows - src_min = self.data.min().item() # convert to native Python type (int, float) - src_max = self.data.max().item() # convert to native Python type (int, float) + src_min, src_max = _get_min_max_values(self.data) if _np.issubdtype(dtype, _np.integer): dst_min = _np.iinfo(dtype).min dst_max = _np.iinfo(dtype).max @@ -485,15 +484,7 @@ def clip_to_dtype(self, dtype: _np.dtype, force_copy: bool = False) -> _T.Self: raise TypeError('Clipping to boolean dtype is not supported.') # Determine the actual range of the source image - if hasattr(self.data, 'compute'): # Dask array - import dask.array as da - min_src_value, max_src_value = ( - value.item() # convert to native Python type (float, int) - for value in da.compute(self.data.min(), self.data.max()) - ) - else: # NumPy array - min_src_value = self.data.min().item() # convert to native Python type (float, int) - max_src_value = self.data.max().item() # convert to native Python type (float, int) + min_src_value, max_src_value = _get_min_max_values(self.data) # Determine the valid range for the target dtype if _np.issubdtype(dtype, _np.integer): @@ -518,3 +509,26 @@ def clip_to_dtype(self, dtype: _np.dtype, force_copy: bool = False) -> _T.Self: original_axes=self.original_axes, metadata=self.metadata, ) + + +def _get_min_max_values(array: _T.NDArray) -> _T.Tuple[_T.Union[float, int], _T.Union[float, int]]: + if hasattr(array, 'compute'): # Dask array + import dask.array as da + min_src_value, max_src_value = ( + value.item() # convert to native Python type (float, int) + for value in da.compute(array.min(), array.max()) + ) + else: # NumPy array + min_src_value, max_src_value = ( + value.item() # convert to native Python type (float, int) + for value in (array.min(), array.max()) + ) + return min_src_value, max_src_value + + +def _unique(array: _T.NDArray) -> _T.NDArray: + if hasattr(array, 'compute'): # Dask array + import dask.array as da + return da.unique(array).compute() + else: # NumPy array + return _np.unique(array) diff --git a/tests/module/test__image.py b/tests/module/test__image.py index d095b4a..d1d8693 100644 --- a/tests/module/test__image.py +++ b/tests/module/test__image.py @@ -10,6 +10,7 @@ import giatools.image from giatools.typing import ( + Any, Optional, Tuple, ) @@ -284,7 +285,7 @@ def test__dask_array__zyx__iterate__yx(self, joint_axes: str): class ImageTestCase__dtype_mixin: - def create_non_bool_image( + def create_random_non_bool_image( self, dtype: np.dtype, shape=(2, 3, 26, 32), @@ -320,25 +321,47 @@ def create_non_bool_image( metadata=metadata if metadata is not None else unittest.mock.Mock(), ) - def create_bool_image(self) -> giatools.image.Image: + def create_random_bool_image(self) -> giatools.image.Image: return giatools.image.Image( data=np.random.choice(a=[False, True], size=(2, 3, 26, 32)), axes='CYXZ', ) + def create_const_value_image( + self, + dtype: np.dtype, + value: Any, + shape=(2, 3, 26, 32), + axes='CYXZ', + original_axes='QTCYXZ', + metadata: Optional[unittest.mock.Mock] = None, + ) -> giatools.image.Image: + return giatools.image.Image( + data=np.full(shape, value, dtype=dtype), + axes=axes, + original_axes=original_axes, + metadata=metadata if metadata is not None else unittest.mock.Mock(), + ) + class ImageTestCase__dtype_mixin__dask(ImageTestCase__dtype_mixin): - def create_non_bool_image(self, *args, **kwargs) -> giatools.image.Image: + def create_random_non_bool_image(self, *args, **kwargs) -> giatools.image.Image: + import dask.array as da + img = super().create_random_non_bool_image(*args, **kwargs) + img.data = da.from_array(img.data, chunks=(10,) * img.data.ndim) + return img + + def create_random_bool_image(self) -> giatools.image.Image: import dask.array as da - img = super().create_non_bool_image(*args, **kwargs) - img.data = da.from_array(img.data, chunks=(5,) * img.data.ndim) + img = super().create_random_bool_image() + img.data = da.from_array(img.data, chunks=(10,) * img.data.ndim) return img - def create_bool_image(self) -> giatools.image.Image: + def create_const_value_image(self, *args, **kwargs) -> giatools.image.Image: import dask.array as da - img = super().create_bool_image() - img.data = da.from_array(img.data, chunks=(5,) * img.data.ndim) + img = super().create_const_value_image(*args, **kwargs) + img.data = da.from_array(img.data, chunks=(10,) * img.data.ndim) return img @@ -367,7 +390,7 @@ def _test_non_bool_conversion( expected_dtype = dst_dtype # Create test image - img = self.create_non_bool_image(src_dtype) + img = self.create_random_non_bool_image(src_dtype) original_dtype = img.data.dtype original_metadata = img.metadata original_axes = img.axes @@ -420,7 +443,7 @@ def _test_non_bool_conversion( np.float16, np.float32, np.float64, ) else np.iinfo(expected_dtype).max ) - fallback_img = self.create_non_bool_image( + fallback_img = self.create_random_non_bool_image( src_dtype, min_value=0, max_value=min((max_dst_value, max_src_value)), @@ -536,28 +559,26 @@ def _test_conversion_to_bool(self, img, expected_data): def test__conversion__to_bool__2_labels(self): for src_dtype in self.exact_non_bool_dtype_list: - img = self.create_non_bool_image(src_dtype) + img = self.create_random_non_bool_image(src_dtype) img.data = 10 + 5 * (img.data > img.data.mean()).astype(img.data.dtype) - assert list(np.unique(img.data)) == [10, 15] # sanity check + assert list(np.unique(np.asarray(img.data))) == [10, 15] # sanity check self._test_conversion_to_bool(img, expected_data=img.data > 12) def test__conversion__to_bool__1_non_zero_label(self): for src_dtype in self.exact_non_bool_dtype_list: - img = self.create_non_bool_image(src_dtype) - img.data.fill(15) + img = self.create_const_value_image(src_dtype, value=15) assert img.data.dtype == src_dtype # sanity check self._test_conversion_to_bool(img, expected_data=np.ones(img.data.shape, dtype=bool)) def test__conversion__to_bool__1_zero_label(self): for src_dtype in self.exact_non_bool_dtype_list: - img = self.create_non_bool_image(src_dtype) - img.data.fill(0) + img = self.create_const_value_image(src_dtype, value=0) assert img.data.dtype == src_dtype # sanity check self._test_conversion_to_bool(img, expected_data=np.zeros(img.data.shape, dtype=bool)) def test__conversion__to_bool__invalid(self): for src_dtype in self.exact_non_bool_dtype_list: - img = self.create_non_bool_image(src_dtype) + img = self.create_random_non_bool_image(src_dtype) img.data = np.random.randint(0, 3, img.data.shape).astype(src_dtype) assert len(np.unique(img.data)) > 2 # sanity check with self.subTest(f'from {src_dtype} to bool (invalid case)'): @@ -576,7 +597,7 @@ class Image__astype__dask(ImageTestCase, ImageTestCase__dtype_mixin__dask, Image class Image__clip_to_dtype__mixin: def test__float32_to_int8__no_clip(self): - img = self.create_non_bool_image( + img = self.create_random_non_bool_image( dtype=np.float32, min_value=-20.0, max_value=+20.0, @@ -586,7 +607,7 @@ def test__float32_to_int8__no_clip(self): self.assertIs(img_clipped, img) def test__float32_to_float16__clip_below(self): - img = self.create_non_bool_image( + img = self.create_random_non_bool_image( dtype=np.float32, min_value=-1e5, max_value=+1e2, @@ -599,7 +620,7 @@ def test__float32_to_float16__clip_below(self): self.assertEqual(img_clipped.data.max(), +1e2) def test__float32_to_int8__clip_above(self): - img = self.create_non_bool_image( + img = self.create_random_non_bool_image( dtype=np.float32, min_value=-100, max_value=+1e5, @@ -612,7 +633,7 @@ def test__float32_to_int8__clip_above(self): self.assertEqual(img_clipped.data.max(), +127.0) def test__bool_to_uint8(self): - img = self.create_bool_image() + img = self.create_random_bool_image() img_clipped = img.clip_to_dtype(np.uint8) self.assertIs(img_clipped, img) From 0ca655e87a0e7f7ed68d685c9ce1f4a34fabc826 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 30 Dec 2025 01:17:46 +0100 Subject: [PATCH 7/9] Fix tests for Python <3.11 --- tests/module/test__image.py | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/module/test__image.py b/tests/module/test__image.py index d1d8693..414e3e3 100644 --- a/tests/module/test__image.py +++ b/tests/module/test__image.py @@ -593,6 +593,34 @@ class Image__astype(ImageTestCase, ImageTestCase__dtype_mixin, Image__astype__mi class Image__astype__dask(ImageTestCase, ImageTestCase__dtype_mixin__dask, Image__astype__mixin): pass # Tests with Dask arrays + @minimum_python_version(3, 11) + def test__non_bool__exact(self): + super().test__non_bool__exact() + + @minimum_python_version(3, 11) + def test__non_bool__inexact(self): + super().test__non_bool__inexact() + + @minimum_python_version(3, 11) + def test__conversion__from_bool(self): + super().test__conversion__from_bool() + + @minimum_python_version(3, 11) + def test__conversion__to_bool__2_labels(self): + super().test__conversion__to_bool__2_labels() + + @minimum_python_version(3, 11) + def test__conversion__to_bool__1_non_zero_label(self): + super().test__conversion__to_bool__1_non_zero_label() + + @minimum_python_version(3, 11) + def test__conversion__to_bool__1_zero_label(self): + super().test__conversion__to_bool__1_zero_label() + + @minimum_python_version(3, 11) + def test__conversion__to_bool__invalid(self): + super().test__conversion__to_bool__invalid() + class Image__clip_to_dtype__mixin: @@ -644,3 +672,19 @@ class Image__clip_to_dtype(ImageTestCase, ImageTestCase__dtype_mixin, Image__cli class Image__clip_to_dtype__dask(ImageTestCase, ImageTestCase__dtype_mixin__dask, Image__clip_to_dtype__mixin): pass # Tests with Dask arrays + + @minimum_python_version(3, 11) + def test__float32_to_int8__no_clip(self): + super().test__float32_to_int8__no_clip() + + @minimum_python_version(3, 11) + def test__float32_to_float16__clip_below(self): + super().test__float32_to_float16__clip_below() + + @minimum_python_version(3, 11) + def test__float32_to_int8__clip_above(self): + super().test__float32_to_int8__clip_above() + + @minimum_python_version(3, 11) + def test__bool_to_uint8(self): + super().test__bool_to_uint8() From c4ccac250ad1685a959e1380589c604fb4c77492 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 30 Dec 2025 01:23:19 +0100 Subject: [PATCH 8/9] Fix tests for Python <3.11 --- tests/unit/test__image.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/unit/test__image.py b/tests/unit/test__image.py index 17a1f59..8976f53 100644 --- a/tests/unit/test__image.py +++ b/tests/unit/test__image.py @@ -33,6 +33,12 @@ def setUp(self): self._np = unittest.mock.patch( 'giatools.image._np' ).start() + self._get_min_max_values = unittest.mock.patch( + 'giatools.image._get_min_max_values' + ).start() + self._unique = unittest.mock.patch( + 'giatools.image._unique' + ).start() self.addCleanup(unittest.mock.patch.stopall) @@ -194,8 +200,7 @@ def test__bool(self): self.img1.clip_to_dtype(bool) def test__to_superset_int(self): - self.img1.data.min.return_value.item.return_value = -15 - self.img1.data.max.return_value.item.return_value = +15 + self._get_min_max_values.return_value = (-15, +15) self._np.issubdtype.return_value = True # target dtype is an integer type self._np.iinfo.return_value.min = -15 self._np.iinfo.return_value.max = +15 @@ -204,8 +209,7 @@ def test__to_superset_int(self): self.img1.data.copy.assert_not_called() def test__to_superset_float(self): - self.img1.data.min.return_value.item.return_value = -15 - self.img1.data.max.return_value.item.return_value = +15 + self._get_min_max_values.return_value = (-15, +15) self._np.issubdtype.return_value = False # target dtype is a float type self._np.finfo.return_value.min.item.return_value = -15. self._np.finfo.return_value.max.item.return_value = +15. From ac17eb1206eef541fefaa3b346ac1dbc93bab147 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 30 Dec 2025 01:37:59 +0100 Subject: [PATCH 9/9] Fix test for Dask --- tests/module/test__image.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/module/test__image.py b/tests/module/test__image.py index 414e3e3..0a89518 100644 --- a/tests/module/test__image.py +++ b/tests/module/test__image.py @@ -297,7 +297,7 @@ def create_random_non_bool_image( include_limits: bool = False, ) -> giatools.image.Image: """ - Create a test image with random data of the given data type. + Create a test image with random, uniformly distributed data of the given data type. """ assert dtype != bool, 'This method is only for non-boolean dtypes.' np.random.seed(0) @@ -578,9 +578,8 @@ def test__conversion__to_bool__1_zero_label(self): def test__conversion__to_bool__invalid(self): for src_dtype in self.exact_non_bool_dtype_list: - img = self.create_random_non_bool_image(src_dtype) - img.data = np.random.randint(0, 3, img.data.shape).astype(src_dtype) - assert len(np.unique(img.data)) > 2 # sanity check + img = self.create_random_non_bool_image(src_dtype, min_value=0, max_value=3) + assert len(np.unique(np.asarray(img.data))) > 2 # sanity check with self.subTest(f'from {src_dtype} to bool (invalid case)'): with self.assertRaises(ValueError): img.astype(bool)