Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions giatools/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,19 @@ def clip_to_dtype(self, dtype: _np.dtype, force_copy: bool = False) -> _T.Self:
Raises:
TypeError: If `dtype` is `bool`.
"""
if dtype in (bool, _np.bool_):
raise TypeError('Clipping to boolean dtype is not supported.')
if _np.issubdtype(dtype, bool):
if _np.issubdtype(self.data.dtype, bool):
if force_copy:
return Image(
data=self.data.copy(),
axes=self.axes,
original_axes=self.original_axes,
metadata=self.metadata,
)
else:
return self
else:
raise TypeError('Clipping to boolean dtype is not supported.')

# Determine the actual range of the source image
min_src_value, max_src_value = _get_min_max_values(self.data)
Expand Down
2 changes: 1 addition & 1 deletion giatools/version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
VERSION_MAJOR = 0
VERSION_MINOR = 7
VERSION_PATCH = 2
VERSION_PATCH = 3

__version__ = '%d.%d.%d' % (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
29 changes: 27 additions & 2 deletions tests/integration/test__cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
import sys
import tempfile
import types
import unittest

import numpy as np
Expand All @@ -12,7 +13,10 @@
import giatools.image
import giatools.typing as _T

from ..tools import minimum_python_version
from ..tools import (
minimum_python_version,
random_io_test,
)


def _threshold(image1: np.ndarray, image2: _T.Optional[np.ndarray]) -> np.ndarray:
Expand Down Expand Up @@ -45,7 +49,7 @@ def _threshold(image1: np.ndarray, image2: _T.Optional[np.ndarray]) -> np.ndarra
proc['output'] = _threshold(proc['input1'].data, None)


class ToolBaseplate(unittest.TestCase):
class ToolBaseplate__cli(unittest.TestCase):

def setUp(self):
super().setUp()
Expand Down Expand Up @@ -159,3 +163,24 @@ def test__params(self):
'--output', output_filepath,
)
self.assertEqual(result.stdout.strip('\n'), str(params))


class ToolBaseplate(unittest.TestCase):

@minimum_python_version(3, 11)
@random_io_test(shape=(10, 10), dtype=bool, ext='tiff')
def test__preserve__bool(self, filepath, data):
output_filepath = str(pathlib.Path(filepath).parent / 'output.tiff')
tool = giatools.cli.ToolBaseplate(params_required=False)
tool.add_input_image('input')
tool.add_output_image('output')
tool.args = types.SimpleNamespace(
params=None,
verbose=False,
input_filepaths={'input': filepath},
input_images={'input': giatools.image.Image(data, axes='YX', original_axes='YX')},
output_filepaths={'output': output_filepath},
raw_args=types.SimpleNamespace(),
)
for section in tool.run('YX', output_dtype_hint='preserve'):
section['output'] = section['input'].data
8 changes: 7 additions & 1 deletion tests/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,14 @@ def wrapper(self):
# Create random image data
np.random.seed(0)
data = np.random.rand(*shape)
if not np.issubdtype(dtype, np.floating):
if np.issubdtype(dtype, np.floating):
pass
elif np.issubdtype(dtype, np.integer):
data = (data * np.iinfo(dtype).max).astype(dtype)
elif np.issubdtype(dtype, bool):
data = (data > 0.5).round().astype(bool)
else:
assert False, f'Unsupported dtype {dtype}'

# Supply a temporary file to write the image to
filepath = os.path.join(temp_path, f'test.{ext}')
Expand Down
24 changes: 20 additions & 4 deletions tests/unit/test__image.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,24 +195,40 @@ def test__spurious_axis(self):

class Image__clip_to_dtype(ImageTestCase):

def _issubdtype(self, a, b):
ret = self._issubdtype__results.get((a, b), None)
assert ret is not None, f'Unexpected args to issubdtype: {a}, {b}'
return ret

def test__bool(self):
self._issubdtype__results = {
(bool, bool): True,
(self.img1.data.dtype, bool): False,
}
self._np.issubdtype.side_effect = self._issubdtype
with self.assertRaises(TypeError):
self.img1.clip_to_dtype(bool)

def test__to_superset_int(self):
self._get_min_max_values.return_value = (-15, +15)
self._np.issubdtype.return_value = True # target dtype is an integer type
self._issubdtype__results = {
(self._np.uint8, bool): False,
(self._np.uint8, self._np.integer): True, # target dtype is an integer type
}
self._np.iinfo.return_value.min = -15
self._np.iinfo.return_value.max = +15
img_clipped = self.img1.clip_to_dtype('mocked-int-type')
img_clipped = self.img1.clip_to_dtype(self._np.uint8)
self.assertIs(img_clipped, self.img1)
self.img1.data.copy.assert_not_called()

def test__to_superset_float(self):
self._get_min_max_values.return_value = (-15, +15)
self._np.issubdtype.return_value = False # target dtype is a float type
self._issubdtype__results = {
(self._np.uint8, bool): False,
(self._np.uint8, self._np.integer): 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.
img_clipped = self.img1.clip_to_dtype('mocked-float-type')
img_clipped = self.img1.clip_to_dtype(self._np.float64)
self.assertIs(img_clipped, self.img1)
self.img1.data.copy.assert_not_called()