From beab17dd107496d002da48afe32623623f1269aa Mon Sep 17 00:00:00 2001 From: Richard Brown <33289025+rijobro@users.noreply.github.com> Date: Wed, 25 Aug 2021 11:04:12 +0100 Subject: [PATCH 1/4] backends -> backend Signed-off-by: Richard Brown <33289025+rijobro@users.noreply.github.com> --- monai/transforms/__init__.py | 1 + monai/transforms/intensity/array.py | 10 +-- monai/transforms/utility/array.py | 2 +- monai/transforms/utils.py | 95 +++++++++++++++++--------- tests/test_print_transform_backends.py | 8 ++- 5 files changed, 74 insertions(+), 42 deletions(-) diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 4203724a7d..5267af4048 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -500,6 +500,7 @@ get_extreme_points, get_largest_connected_component_mask, get_number_image_type_conversions, + get_transform_backends, img_bounds, in_bounds, is_empty, diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 46c512c96c..b36c7adf96 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -131,7 +131,7 @@ class RandRicianNoise(RandomizableTransform): uniformly from 0 to std. """ - backends = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, @@ -197,7 +197,7 @@ class ShiftIntensity(Transform): offset: offset value to shift the intensity of image. """ - backends = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__(self, offset: float) -> None: self.offset = offset @@ -219,7 +219,7 @@ class RandShiftIntensity(RandomizableTransform): Randomly shift intensity with randomly picked offset. """ - backends = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__(self, offsets: Union[Tuple[float, float], float], prob: float = 0.1) -> None: """ @@ -273,7 +273,7 @@ class StdShiftIntensity(Transform): dtype: output data type, defaults to float32. """ - backends = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, factor: float, nonzero: bool = False, channel_wise: bool = False, dtype: DtypeLike = np.float32 @@ -318,7 +318,7 @@ class RandStdShiftIntensity(RandomizableTransform): by: ``v = v + factor * std(v)`` where the `factor` is randomly picked. """ - backends = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index 3ef6413090..d56bca0d8d 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -291,7 +291,7 @@ class CastToType(Transform): specified PyTorch data type. """ - backends = [TransformBackends.TORCH, TransformBackends.NUMPY] + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__(self, dtype=np.float32) -> None: """ diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index e3e61b6c97..6281319973 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -10,6 +10,7 @@ # limitations under the License. import itertools +from monai.utils.enums import TransformBackends import random import warnings from contextlib import contextmanager @@ -81,6 +82,7 @@ "zero_margins", "equalize_hist", "get_number_image_type_conversions", + "get_transform_backends", "print_transform_backends", ] @@ -1157,23 +1159,17 @@ def _get_data(obj, key): num_conversions += 1 return num_conversions +def get_transform_backends() -> dict[str, List]: + """Get the backends of all MONAI transforms. -def print_transform_backends(): - """Prints a list of backends of all MONAI transforms.""" - - class Colours: - red = "91" - green = "92" - yellow = "93" - - def print_colour(t, colour): - print(f"\033[{colour}m{t}\033[00m") - - tr_total = 0 - tr_t_or_np = 0 - tr_t = 0 - tr_np = 0 - tr_uncategorised = 0 + Returns: + Dictionary, where each key is a transform, and its + corresponding values are a boolean list, stating + whether that transform supports (1) `torch.Tensor`, + and (2) `np.ndarray` as input without needing to + convert. + """ + backends = {} unique_transforms = [] for n, obj in getmembers(monai.transforms): # skip aliases @@ -1194,21 +1190,52 @@ def print_colour(t, colour): "InverteD", ]: continue - tr_total += 1 - if obj.backend == ["torch", "numpy"]: - tr_t_or_np += 1 - print_colour(f"TorchOrNumpy: {n}", Colours.green) - elif obj.backend == ["torch"]: - tr_t += 1 - print_colour(f"Torch: {n}", Colours.green) - elif obj.backend == ["numpy"]: - tr_np += 1 - print_colour(f"Numpy: {n}", Colours.yellow) - else: - tr_uncategorised += 1 - print_colour(f"Uncategorised: {n}", Colours.red) - print("Total number of transforms:", tr_total) - print_colour(f"Number transforms allowing both torch and numpy: {tr_t_or_np}", Colours.green) - print_colour(f"Number of TorchTransform: {tr_t}", Colours.green) - print_colour(f"Number of NumpyTransform: {tr_np}", Colours.yellow) - print_colour(f"Number of uncategorised: {tr_uncategorised}", Colours.red) + + backends[n] = [ + TransformBackends.TORCH in obj.backend, + TransformBackends.NUMPY in obj.backend, + ] + return backends + + +def print_transform_backends(): + """Prints a list of backends of all MONAI transforms.""" + class Colors: + none = "" + red = "91" + green = "92" + yellow = "93" + + def print_color(t, color): + print(f"\033[{color}m{t}\033[00m") + + def print_table_column(name, torch, numpy, color=Colors.none): + print_color("{:<50} {:<8} {:<8}".format(name, torch, numpy), color) + + backends = get_transform_backends() + n_total = len(backends) + n_t_or_np, n_t, n_np, n_uncategorized = 0, 0, 0, 0 + print_table_column("Transform", "Torch?", "Numpy?") + for k, v in backends.items(): + if all(v): + color = Colors.green + n_t_or_np += 1 + elif v[0]: + color = Colors.green + n_t += 1 + elif v[1]: + color = Colors.yellow + n_np += 1 + else: + color = Colors.red + n_uncategorized += 1 + print_table_column(k, *v, color) + + print("Total number of transforms:", n_total) + print_color(f"Number transforms allowing both torch and numpy: {n_t_or_np}", Colors.green) + print_color(f"Number of TorchTransform: {n_t}", Colors.green) + print_color(f"Number of NumpyTransform: {n_np}", Colors.yellow) + print_color(f"Number of uncategorised: {n_uncategorized}", Colors.red) + +if __name__ == "__main__": + print_transform_backends() diff --git a/tests/test_print_transform_backends.py b/tests/test_print_transform_backends.py index 09828f0a27..4164687f01 100644 --- a/tests/test_print_transform_backends.py +++ b/tests/test_print_transform_backends.py @@ -11,13 +11,17 @@ import unittest -from monai.transforms.utils import print_transform_backends +from monai.transforms.utils import get_transform_backends, print_transform_backends class TestPrintTransformBackends(unittest.TestCase): def test_get_number_of_conversions(self): + tr_t_or_np, *_ = get_transform_backends() + self.assertGreater(len(tr_t_or_np), 0) print_transform_backends() if __name__ == "__main__": - unittest.main() + # unittest.main() + a = TestPrintTransformBackends() + a.test_get_number_of_conversions() From c3192bad9b6585676a06966d57930790f89c4988 Mon Sep 17 00:00:00 2001 From: Richard Brown <33289025+rijobro@users.noreply.github.com> Date: Wed, 25 Aug 2021 11:10:42 +0100 Subject: [PATCH 2/4] code format Signed-off-by: Richard Brown <33289025+rijobro@users.noreply.github.com> --- monai/transforms/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 6281319973..8bb4a38310 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -10,7 +10,6 @@ # limitations under the License. import itertools -from monai.utils.enums import TransformBackends import random import warnings from contextlib import contextmanager @@ -39,6 +38,7 @@ min_version, optional_import, ) +from monai.utils.enums import TransformBackends from monai.utils.type_conversion import convert_data_type measure, _ = optional_import("skimage.measure", "0.14.2", min_version) @@ -1159,6 +1159,7 @@ def _get_data(obj, key): num_conversions += 1 return num_conversions + def get_transform_backends() -> dict[str, List]: """Get the backends of all MONAI transforms. @@ -1200,6 +1201,7 @@ def get_transform_backends() -> dict[str, List]: def print_transform_backends(): """Prints a list of backends of all MONAI transforms.""" + class Colors: none = "" red = "91" @@ -1237,5 +1239,6 @@ def print_table_column(name, torch, numpy, color=Colors.none): print_color(f"Number of NumpyTransform: {n_np}", Colors.yellow) print_color(f"Number of uncategorised: {n_uncategorized}", Colors.red) + if __name__ == "__main__": print_transform_backends() From fe89b7c17cf7bac9f6e95c34b4ae9dea095fef81 Mon Sep 17 00:00:00 2001 From: Richard Brown <33289025+rijobro@users.noreply.github.com> Date: Wed, 25 Aug 2021 11:09:00 +0100 Subject: [PATCH 3/4] ShiftIntensityd, RandShiftIntensityd Signed-off-by: Richard Brown <33289025+rijobro@users.noreply.github.com> --- monai/transforms/intensity/dictionary.py | 8 ++++++-- tests/test_rand_shift_intensityd.py | 17 +++++++++-------- tests/test_shift_intensityd.py | 11 ++++++----- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/monai/transforms/intensity/dictionary.py b/monai/transforms/intensity/dictionary.py index 227b6fb434..f188940573 100644 --- a/monai/transforms/intensity/dictionary.py +++ b/monai/transforms/intensity/dictionary.py @@ -245,6 +245,8 @@ class ShiftIntensityd(MapTransform): Dictionary-based wrapper of :py:class:`monai.transforms.ShiftIntensity`. """ + backend = ShiftIntensity.backend + def __init__( self, keys: KeysCollection, @@ -283,7 +285,7 @@ def __init__( self.meta_key_postfix = ensure_tuple_rep(meta_key_postfix, len(self.keys)) self.shifter = ShiftIntensity(offset) - def __call__(self, data) -> Dict[Hashable, np.ndarray]: + def __call__(self, data) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) for key, factor_key, meta_key, meta_key_postfix in self.key_iterator( d, self.factor_key, self.meta_keys, self.meta_key_postfix @@ -300,6 +302,8 @@ class RandShiftIntensityd(RandomizableTransform, MapTransform): Dictionary-based version :py:class:`monai.transforms.RandShiftIntensity`. """ + backend = ShiftIntensity.backend + def __init__( self, keys: KeysCollection, @@ -355,7 +359,7 @@ def randomize(self, data: Optional[Any] = None) -> None: self._offset = self.R.uniform(low=self.offsets[0], high=self.offsets[1]) super().randomize(None) - def __call__(self, data) -> Dict[Hashable, np.ndarray]: + def __call__(self, data) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) self.randomize() if not self._do_transform: diff --git a/tests/test_rand_shift_intensityd.py b/tests/test_rand_shift_intensityd.py index 71cfd8fc50..6766236146 100644 --- a/tests/test_rand_shift_intensityd.py +++ b/tests/test_rand_shift_intensityd.py @@ -14,18 +14,19 @@ import numpy as np from monai.transforms import IntensityStatsd, RandShiftIntensityd -from tests.utils import NumpyImageTestCase2D +from tests.utils import TEST_NDARRAYS, NumpyImageTestCase2D, assert_allclose class TestRandShiftIntensityd(NumpyImageTestCase2D): def test_value(self): - key = "img" - shifter = RandShiftIntensityd(keys=[key], offsets=1.0, prob=1.0) - shifter.set_random_state(seed=0) - result = shifter({key: self.imt}) - np.random.seed(0) - expected = self.imt + np.random.uniform(low=-1.0, high=1.0) - np.testing.assert_allclose(result[key], expected) + for p in TEST_NDARRAYS: + key = "img" + shifter = RandShiftIntensityd(keys=[key], offsets=1.0, prob=1.0) + shifter.set_random_state(seed=0) + result = shifter({key: p(self.imt)}) + np.random.seed(0) + expected = self.imt + np.random.uniform(low=-1.0, high=1.0) + assert_allclose(result[key], expected) def test_factor(self): key = "img" diff --git a/tests/test_shift_intensityd.py b/tests/test_shift_intensityd.py index 71cfffc9c5..0396857781 100644 --- a/tests/test_shift_intensityd.py +++ b/tests/test_shift_intensityd.py @@ -14,16 +14,17 @@ import numpy as np from monai.transforms import IntensityStatsd, ShiftIntensityd -from tests.utils import NumpyImageTestCase2D +from tests.utils import TEST_NDARRAYS, NumpyImageTestCase2D, assert_allclose class TestShiftIntensityd(NumpyImageTestCase2D): def test_value(self): key = "img" - shifter = ShiftIntensityd(keys=[key], offset=1.0) - result = shifter({key: self.imt}) - expected = self.imt + 1.0 - np.testing.assert_allclose(result[key], expected) + for p in TEST_NDARRAYS: + shifter = ShiftIntensityd(keys=[key], offset=1.0) + result = shifter({key: p(self.imt)}) + expected = self.imt + 1.0 + assert_allclose(result[key], expected) def test_factor(self): key = "img" From f790ad7acc151e33aabe88dd8ba43ab87dd057c0 Mon Sep 17 00:00:00 2001 From: Richard Brown <33289025+rijobro@users.noreply.github.com> Date: Wed, 25 Aug 2021 11:30:33 +0100 Subject: [PATCH 4/4] code format2 Signed-off-by: Richard Brown <33289025+rijobro@users.noreply.github.com> --- monai/transforms/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 8bb4a38310..30aa5e7b99 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -1160,7 +1160,7 @@ def _get_data(obj, key): return num_conversions -def get_transform_backends() -> dict[str, List]: +def get_transform_backends(): """Get the backends of all MONAI transforms. Returns: