From 9a30540fec9970845a39379bfcb53523855b6ae8 Mon Sep 17 00:00:00 2001 From: Can Zhao Date: Fri, 20 May 2022 03:05:14 -0400 Subject: [PATCH 1/6] add box transform Signed-off-by: Can Zhao --- docs/source/apps.rst | 10 ++ monai/apps/detection/transforms/__init__.py | 10 ++ monai/apps/detection/transforms/array.py | 128 ++++++++++++++ monai/apps/detection/transforms/dictionary.py | 156 ++++++++++++++++++ tests/test_box_transform.py | 55 ++++++ 5 files changed, 359 insertions(+) create mode 100644 monai/apps/detection/transforms/__init__.py create mode 100644 monai/apps/detection/transforms/array.py create mode 100644 monai/apps/detection/transforms/dictionary.py create mode 100644 tests/test_box_transform.py diff --git a/docs/source/apps.rst b/docs/source/apps.rst index 239ae9eb17..f4599ce20c 100644 --- a/docs/source/apps.rst +++ b/docs/source/apps.rst @@ -122,3 +122,13 @@ Applications :members: .. autoclass:: TileOnGridd :members: + +`Detection` +----------- + +`Transforms` +~~~~~~~~~~~~ +.. automodule:: monai.apps.detection.transforms.array + :members: +.. automodule:: monai.apps.detection.transforms.dictionary + :members: diff --git a/monai/apps/detection/transforms/__init__.py b/monai/apps/detection/transforms/__init__.py new file mode 100644 index 0000000000..1e97f89407 --- /dev/null +++ b/monai/apps/detection/transforms/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/monai/apps/detection/transforms/array.py b/monai/apps/detection/transforms/array.py new file mode 100644 index 0000000000..81936f2329 --- /dev/null +++ b/monai/apps/detection/transforms/array.py @@ -0,0 +1,128 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +A collection of "vanilla" transforms for spatial operations +https://github.com/Project-MONAI/MONAI/wiki/MONAI_Design +""" + +from typing import Type, Union + +from monai.config.type_definitions import NdarrayOrTensor +from monai.data.box_utils import BoxMode, convert_box_mode +from monai.transforms.transform import Transform +from monai.utils.enums import TransformBackends + +__all__ = ["BoxConvertToStandard", "BoxConvertMode"] + + +class BoxConvertMode(Transform): + """ + This transform converts the boxes in src_mode to the dst_mode. + + Example: + .. code-block:: python + + boxes = torch.ones(10,4) + # convert boxes with format [xmin, ymin, xmax, ymax] to [xcenter, ycenter, xsize, ysize]. + box_converter = BoxConvertMode(src_mode="xyxy", dst_mode="ccwh") + box_converter(boxes) + """ + + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + + def __init__( + self, + src_mode: Union[str, BoxMode, Type[BoxMode], None] = None, + dst_mode: Union[str, BoxMode, Type[BoxMode], None] = None, + ) -> None: + """ + + Args: + src_mode: source box mode. If it is not given, this func will assume it is ``StandardMode()``. + dst_mode: target box mode. If it is not given, this func will assume it is ``StandardMode()``. + + Note: + ``StandardMode`` = :class:`~monai.data.box_utils.CornerCornerModeTypeA`, + also represented as "xyxy" for 2D and "xyzxyz" for 3D. + + src_mode and dst_mode can be: + #. str: choose from :class:`~monai.utils.enums.BoxModeName`, for example, + - "xyxy": boxes has format [xmin, ymin, xmax, ymax] + - "xyzxyz": boxes has format [xmin, ymin, zmin, xmax, ymax, zmax] + - "xxyy": boxes has format [xmin, xmax, ymin, ymax] + - "xxyyzz": boxes has format [xmin, xmax, ymin, ymax, zmin, zmax] + - "xyxyzz": boxes has format [xmin, ymin, xmax, ymax, zmin, zmax] + - "xywh": boxes has format [xmin, ymin, xsize, ysize] + - "xyzwhd": boxes has format [xmin, ymin, zmin, xsize, ysize, zsize] + - "ccwh": boxes has format [xcenter, ycenter, xsize, ysize] + - "cccwhd": boxes has format [xcenter, ycenter, zcenter, xsize, ysize, zsize] + #. BoxMode class: choose from the subclasses of :class:`~monai.data.box_utils.BoxMode`, for example, + - CornerCornerModeTypeA: equivalent to "xyxy" or "xyzxyz" + - CornerCornerModeTypeB: equivalent to "xxyy" or "xxyyzz" + - CornerCornerModeTypeC: equivalent to "xyxy" or "xyxyzz" + - CornerSizeMode: equivalent to "xywh" or "xyzwhd" + - CenterSizeMode: equivalent to "ccwh" or "cccwhd" + #. BoxMode object: choose from the subclasses of :class:`~monai.data.box_utils.BoxMode`, for example, + - CornerCornerModeTypeA(): equivalent to "xyxy" or "xyzxyz" + - CornerCornerModeTypeB(): equivalent to "xxyy" or "xxyyzz" + - CornerCornerModeTypeC(): equivalent to "xyxy" or "xyxyzz" + - CornerSizeMode(): equivalent to "xywh" or "xyzwhd" + - CenterSizeMode(): equivalent to "ccwh" or "cccwhd" + #. None: will assume mode is ``StandardMode()`` + """ + self.src_mode = src_mode + self.dst_mode = dst_mode + + def __call__(self, boxes: NdarrayOrTensor) -> NdarrayOrTensor: + """ + Converts the boxes in src_mode to the dst_mode. + + Returns: + bounding boxes with target mode, with same data type as ``boxes``, does not share memory with ``boxes`` + """ + return convert_box_mode(boxes, src_mode=self.src_mode, dst_mode=self.dst_mode) + + +class BoxConvertToStandard(Transform): + """ + Convert given boxes to standard mode. + Standard mode is "xyxy" or "xyzxyz", + representing box format of [xmin, ymin, xmax, ymax] or [xmin, ymin, zmin, xmax, ymax, zmax]. + + Example: + .. code-block:: python + + boxes = torch.ones(10,6) + # convert boxes with format [xmin, xmax, ymin, ymax, zmin, zmax] to [xmin, ymin, zmin, xmax, ymax, zmax] + box_converter = BoxConvertToStandard(mode="xxyyzz") + box_converter(boxes) + """ + + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + + def __init__(self, mode: Union[str, BoxMode, Type[BoxMode], None] = None) -> None: + """ + Args: + mode: source box mode. If it is not given, this func will assume it is ``StandardMode()``. + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + """ + self.converter = BoxConvertMode(src_mode=mode, dst_mode=None) + + def __call__(self, boxes: NdarrayOrTensor) -> NdarrayOrTensor: + """ + Convert given boxes to standard mode. + Standard mode is "xyxy" or "xyzxyz", + representing box format of [xmin, ymin, xmax, ymax] or [xmin, ymin, zmin, xmax, ymax, zmax]. + + Returns: + bounding boxes with standard mode, with same data type as ``boxes``, does not share memory with ``boxes`` + """ + return self.converter(boxes) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py new file mode 100644 index 0000000000..1c16f961b7 --- /dev/null +++ b/monai/apps/detection/transforms/dictionary.py @@ -0,0 +1,156 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +A collection of dictionary-based wrappers around the "vanilla" transforms for spatial operations +defined in :py:class:`monai.transforms.spatial.array`. + +Class names are ended with 'd' to denote dictionary-based transforms. +""" + +from copy import deepcopy +from typing import Dict, Hashable, Mapping, Type, Union + +from monai.apps.detection.transforms.array import BoxConvertMode, BoxConvertToStandard +from monai.config import KeysCollection +from monai.config.type_definitions import NdarrayOrTensor +from monai.data.box_utils import BoxMode +from monai.transforms.inverse import InvertibleTransform +from monai.transforms.transform import MapTransform + +__all__ = [ + "BoxConvertModed", + "BoxConvertModeD", + "BoxConvertModeDict", + "BoxConvertToStandardd", + "BoxConvertToStandardD", + "BoxConvertToStandardDict", +] + + +class BoxConvertModed(MapTransform, InvertibleTransform): + """ + Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.BoxConvertMode`. + + This transform converts the boxes in src_mode to the dst_mode. + + Example: + .. code-block:: python + + data = {"boxes": torch.ones(10,4)} + # convert boxes with format [xmin, ymin, xmax, ymax] to [xcenter, ycenter, xsize, ysize]. + box_converter = BoxConvertModed(box_keys=["boxes"], src_mode="xyxy", dst_mode="ccwh") + box_converter(data) + """ + + def __init__( + self, + box_keys: KeysCollection, + src_mode: Union[str, BoxMode, Type[BoxMode], None] = None, + dst_mode: Union[str, BoxMode, Type[BoxMode], None] = None, + allow_missing_keys: bool = False, + ) -> None: + """ + Args: + box_keys: Keys to pick data for transformation. + src_mode: source box mode. If it is not given, this func will assume it is ``StandardMode()``. + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + dst_mode: target box mode. If it is not given, this func will assume it is ``StandardMode()``. + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + allow_missing_keys: don't raise exception if key is missing. + + See also :py:class:`monai.apps.detection,transforms.array.BoxConvertMode` + """ + super().__init__(box_keys, allow_missing_keys) + self.converter = BoxConvertMode(src_mode=src_mode, dst_mode=dst_mode) + self.inverse_converter = BoxConvertMode(src_mode=dst_mode, dst_mode=src_mode) + + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: + d = dict(data) + key_id = 0 + for key in self.key_iterator(d): + self.push_transform(d, key) + d[key] = self.converter(d[key]) + key_id += 1 + return d + + def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: + d = deepcopy(dict(data)) + key_id = 0 + for key in self.key_iterator(d): + _ = self.get_most_recent_transform(d, key) + # Inverse is same as forward + d[key] = self.inverse_converter(d[key]) + # Remove the applied transform + self.pop_transform(d, key) + key_id += 1 + return d + + +class BoxConvertToStandardd(MapTransform, InvertibleTransform): + """ + Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.BoxConvertToStandard`. + + Convert given boxes to standard mode. + Standard mode is "xyxy" or "xyzxyz", + representing box format of [xmin, ymin, xmax, ymax] or [xmin, ymin, zmin, xmax, ymax, zmax]. + + Example: + .. code-block:: python + + data = {"boxes": torch.ones(10,6)} + # convert boxes with format [xmin, xmax, ymin, ymax, zmin, zmax] to [xmin, ymin, zmin, xmax, ymax, zmax] + box_converter = BoxConvertModed(box_keys=["boxes"], mode="xxyyzz") + box_converter(data) + """ + + def __init__( + self, + box_keys: KeysCollection, + mode: Union[str, BoxMode, Type[BoxMode], None] = None, + allow_missing_keys: bool = False, + ) -> None: + """ + Args: + box_keys: Keys to pick data for transformation. + mode: source box mode. If it is not given, this func will assume it is ``StandardMode()``. + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + allow_missing_keys: don't raise exception if key is missing. + + See also :py:class:`monai.apps.detection,transforms.array.BoxConvertToStandard` + """ + super().__init__(box_keys, allow_missing_keys) + self.converter = BoxConvertToStandard(mode=mode) + self.inverse_converter = BoxConvertMode(src_mode=None, dst_mode=mode) + + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: + d = dict(data) + key_id = 0 + for key in self.key_iterator(d): + self.push_transform(d, key) + d[key] = self.converter(d[key]) + key_id += 1 + return d + + def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: + d = deepcopy(dict(data)) + key_id = 0 + for key in self.key_iterator(d): + _ = self.get_most_recent_transform(d, key) + # Inverse is same as forward + d[key] = self.inverse_converter(d[key]) + # Remove the applied transform + self.pop_transform(d, key) + key_id += 1 + return d + + +BoxConvertModeD = BoxConvertModeDict = BoxConvertModed +BoxConvertToStandardD = BoxConvertToStandardDict = BoxConvertToStandardd diff --git a/tests/test_box_transform.py b/tests/test_box_transform.py new file mode 100644 index 0000000000..0b82a6c9b1 --- /dev/null +++ b/tests/test_box_transform.py @@ -0,0 +1,55 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import numpy as np +from parameterized import parameterized + +from monai.apps.detection.transforms.dictionary import BoxConvertToStandardd +from monai.transforms import Invertd +from tests.utils import TEST_NDARRAYS, assert_allclose + +# this script test box_convert_mode, box_convert_standard_mode +TESTS = [] + +boxes = [[0, 0, 0, 0, 0, 0], [0, 1, 0, 2, 2, 3], [0, 1, 1, 2, 2, 3]] +image_size = [1, 5, 6, 4] +image = np.zeros(image_size) + +for p in TEST_NDARRAYS: + TESTS.append( + [ + {"box_keys": "boxes", "mode": "xyzwhd"}, + {"boxes": p(boxes), "image": p(image)}, + p([[0, 0, 0, 0, 0, 0], [0, 1, 0, 2, 3, 3], [0, 1, 1, 2, 3, 4]]), + p([[0, 1, 0, 2, 3, 3], [0, 1, 1, 2, 3, 4]]), + p([[0, 1, 1, 2, 3, 4], [0, 1, 0, 2, 3, 3]]), + ] + ) + + +class TestBoxTransform(unittest.TestCase): + @parameterized.expand(TESTS) + def test_value(self, keys, data, expected_standard_result, expected_clip_result, expected_flip_result): + transform_convert_mode = BoxConvertToStandardd(**keys) + result = transform_convert_mode(data) + assert_allclose(result["boxes"], expected_standard_result, type_test=True, device_test=True, atol=0.0) + + invert_transform_convert_mode = Invertd( + keys=["boxes"], transform=transform_convert_mode, orig_keys=["boxes"] + ) + data_back = invert_transform_convert_mode(result) + assert_allclose(data_back["boxes"], data["boxes"], type_test=False, device_test=False, atol=0.0) + + +if __name__ == "__main__": + unittest.main() From fe3598d7983fe9b7e232c8074eff77c13356f76c Mon Sep 17 00:00:00 2001 From: Can Zhao Date: Fri, 20 May 2022 03:08:37 -0400 Subject: [PATCH 2/6] update docstring Signed-off-by: Can Zhao --- monai/apps/detection/transforms/array.py | 2 +- monai/apps/detection/transforms/dictionary.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monai/apps/detection/transforms/array.py b/monai/apps/detection/transforms/array.py index 81936f2329..212f00fdd4 100644 --- a/monai/apps/detection/transforms/array.py +++ b/monai/apps/detection/transforms/array.py @@ -9,7 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -A collection of "vanilla" transforms for spatial operations +A collection of "vanilla" transforms for box operations https://github.com/Project-MONAI/MONAI/wiki/MONAI_Design """ diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index 1c16f961b7..4b6275b494 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -9,8 +9,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -A collection of dictionary-based wrappers around the "vanilla" transforms for spatial operations -defined in :py:class:`monai.transforms.spatial.array`. +A collection of dictionary-based wrappers around the "vanilla" transforms for box operations +defined in :py:class:`monai.apps.detection.transforms.array`. Class names are ended with 'd' to denote dictionary-based transforms. """ @@ -37,7 +37,7 @@ class BoxConvertModed(MapTransform, InvertibleTransform): """ - Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.BoxConvertMode`. + Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.array.BoxConvertMode`. This transform converts the boxes in src_mode to the dst_mode. @@ -96,7 +96,7 @@ def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, Nd class BoxConvertToStandardd(MapTransform, InvertibleTransform): """ - Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.BoxConvertToStandard`. + Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.array.BoxConvertToStandard`. Convert given boxes to standard mode. Standard mode is "xyxy" or "xyzxyz", From f12c0c9b3a4c78e070dc6b4c72202701f02e91bc Mon Sep 17 00:00:00 2001 From: monai-bot Date: Fri, 20 May 2022 07:13:47 +0000 Subject: [PATCH 3/6] [MONAI] code formatting Signed-off-by: monai-bot --- tests/test_box_transform.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_box_transform.py b/tests/test_box_transform.py index 0b82a6c9b1..825fe9285e 100644 --- a/tests/test_box_transform.py +++ b/tests/test_box_transform.py @@ -44,9 +44,7 @@ def test_value(self, keys, data, expected_standard_result, expected_clip_result, result = transform_convert_mode(data) assert_allclose(result["boxes"], expected_standard_result, type_test=True, device_test=True, atol=0.0) - invert_transform_convert_mode = Invertd( - keys=["boxes"], transform=transform_convert_mode, orig_keys=["boxes"] - ) + invert_transform_convert_mode = Invertd(keys=["boxes"], transform=transform_convert_mode, orig_keys=["boxes"]) data_back = invert_transform_convert_mode(result) assert_allclose(data_back["boxes"], data["boxes"], type_test=False, device_test=False, atol=0.0) From 41e91179b9e4dda0c809f1da3eb17efdc5276ef8 Mon Sep 17 00:00:00 2001 From: Can Zhao Date: Fri, 20 May 2022 09:55:36 -0400 Subject: [PATCH 4/6] update class name. etc Signed-off-by: Can Zhao --- monai/apps/detection/transforms/array.py | 18 +++--- monai/apps/detection/transforms/dictionary.py | 56 ++++++++----------- tests/test_box_transform.py | 5 +- 3 files changed, 35 insertions(+), 44 deletions(-) diff --git a/monai/apps/detection/transforms/array.py b/monai/apps/detection/transforms/array.py index 212f00fdd4..c3ea959eb8 100644 --- a/monai/apps/detection/transforms/array.py +++ b/monai/apps/detection/transforms/array.py @@ -16,14 +16,14 @@ from typing import Type, Union from monai.config.type_definitions import NdarrayOrTensor -from monai.data.box_utils import BoxMode, convert_box_mode +from monai.data.box_utils import BoxMode, convert_box_mode, convert_box_to_standard_mode from monai.transforms.transform import Transform from monai.utils.enums import TransformBackends -__all__ = ["BoxConvertToStandard", "BoxConvertMode"] +__all__ = ["ConvertBoxToStandardMode", "ConvertBoxMode"] -class BoxConvertMode(Transform): +class ConvertBoxMode(Transform): """ This transform converts the boxes in src_mode to the dst_mode. @@ -32,7 +32,7 @@ class BoxConvertMode(Transform): boxes = torch.ones(10,4) # convert boxes with format [xmin, ymin, xmax, ymax] to [xcenter, ycenter, xsize, ysize]. - box_converter = BoxConvertMode(src_mode="xyxy", dst_mode="ccwh") + box_converter = ConvertBoxMode(src_mode="xyxy", dst_mode="ccwh") box_converter(boxes) """ @@ -91,7 +91,7 @@ def __call__(self, boxes: NdarrayOrTensor) -> NdarrayOrTensor: return convert_box_mode(boxes, src_mode=self.src_mode, dst_mode=self.dst_mode) -class BoxConvertToStandard(Transform): +class ConvertBoxToStandardMode(Transform): """ Convert given boxes to standard mode. Standard mode is "xyxy" or "xyzxyz", @@ -102,7 +102,7 @@ class BoxConvertToStandard(Transform): boxes = torch.ones(10,6) # convert boxes with format [xmin, xmax, ymin, ymax, zmin, zmax] to [xmin, ymin, zmin, xmax, ymax, zmax] - box_converter = BoxConvertToStandard(mode="xxyyzz") + box_converter = ConvertBoxToStandardMode(mode="xxyyzz") box_converter(boxes) """ @@ -112,9 +112,9 @@ def __init__(self, mode: Union[str, BoxMode, Type[BoxMode], None] = None) -> Non """ Args: mode: source box mode. If it is not given, this func will assume it is ``StandardMode()``. - It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.ConvertBoxMode` . """ - self.converter = BoxConvertMode(src_mode=mode, dst_mode=None) + self.mode = mode def __call__(self, boxes: NdarrayOrTensor) -> NdarrayOrTensor: """ @@ -125,4 +125,4 @@ def __call__(self, boxes: NdarrayOrTensor) -> NdarrayOrTensor: Returns: bounding boxes with standard mode, with same data type as ``boxes``, does not share memory with ``boxes`` """ - return self.converter(boxes) + return convert_box_to_standard_mode(boxes, mode=self.mode) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index 4b6275b494..63fe1c52a2 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -18,7 +18,7 @@ from copy import deepcopy from typing import Dict, Hashable, Mapping, Type, Union -from monai.apps.detection.transforms.array import BoxConvertMode, BoxConvertToStandard +from monai.apps.detection.transforms.array import ConvertBoxMode, ConvertBoxToStandardMode from monai.config import KeysCollection from monai.config.type_definitions import NdarrayOrTensor from monai.data.box_utils import BoxMode @@ -26,18 +26,18 @@ from monai.transforms.transform import MapTransform __all__ = [ - "BoxConvertModed", - "BoxConvertModeD", - "BoxConvertModeDict", - "BoxConvertToStandardd", - "BoxConvertToStandardD", - "BoxConvertToStandardDict", + "ConvertBoxModed", + "ConvertBoxModeD", + "ConvertBoxModeDict", + "ConvertBoxToStandardModed", + "ConvertBoxToStandardModeD", + "ConvertBoxToStandardModeDict", ] -class BoxConvertModed(MapTransform, InvertibleTransform): +class ConvertBoxModed(MapTransform, InvertibleTransform): """ - Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.array.BoxConvertMode`. + Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.array.ConvertBoxMode`. This transform converts the boxes in src_mode to the dst_mode. @@ -46,7 +46,7 @@ class BoxConvertModed(MapTransform, InvertibleTransform): data = {"boxes": torch.ones(10,4)} # convert boxes with format [xmin, ymin, xmax, ymax] to [xcenter, ycenter, xsize, ysize]. - box_converter = BoxConvertModed(box_keys=["boxes"], src_mode="xyxy", dst_mode="ccwh") + box_converter = ConvertBoxModed(box_keys=["boxes"], src_mode="xyxy", dst_mode="ccwh") box_converter(data) """ @@ -61,42 +61,38 @@ def __init__( Args: box_keys: Keys to pick data for transformation. src_mode: source box mode. If it is not given, this func will assume it is ``StandardMode()``. - It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.ConvertBoxMode` . dst_mode: target box mode. If it is not given, this func will assume it is ``StandardMode()``. - It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.ConvertBoxMode` . allow_missing_keys: don't raise exception if key is missing. - See also :py:class:`monai.apps.detection,transforms.array.BoxConvertMode` + See also :py:class:`monai.apps.detection,transforms.array.ConvertBoxMode` """ super().__init__(box_keys, allow_missing_keys) - self.converter = BoxConvertMode(src_mode=src_mode, dst_mode=dst_mode) - self.inverse_converter = BoxConvertMode(src_mode=dst_mode, dst_mode=src_mode) + self.converter = ConvertBoxMode(src_mode=src_mode, dst_mode=dst_mode) + self.inverse_converter = ConvertBoxMode(src_mode=dst_mode, dst_mode=src_mode) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) - key_id = 0 for key in self.key_iterator(d): self.push_transform(d, key) d[key] = self.converter(d[key]) - key_id += 1 return d def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = deepcopy(dict(data)) - key_id = 0 for key in self.key_iterator(d): _ = self.get_most_recent_transform(d, key) # Inverse is same as forward d[key] = self.inverse_converter(d[key]) # Remove the applied transform self.pop_transform(d, key) - key_id += 1 return d -class BoxConvertToStandardd(MapTransform, InvertibleTransform): +class ConvertBoxToStandardModed(MapTransform, InvertibleTransform): """ - Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.array.BoxConvertToStandard`. + Dictionary-based wrapper of :py:class:`monai.apps.detection.transforms.array.ConvertBoxToStandardMode`. Convert given boxes to standard mode. Standard mode is "xyxy" or "xyzxyz", @@ -107,7 +103,7 @@ class BoxConvertToStandardd(MapTransform, InvertibleTransform): data = {"boxes": torch.ones(10,6)} # convert boxes with format [xmin, xmax, ymin, ymax, zmin, zmax] to [xmin, ymin, zmin, xmax, ymax, zmax] - box_converter = BoxConvertModed(box_keys=["boxes"], mode="xxyyzz") + box_converter = ConvertBoxModed(box_keys=["boxes"], mode="xxyyzz") box_converter(data) """ @@ -121,36 +117,32 @@ def __init__( Args: box_keys: Keys to pick data for transformation. mode: source box mode. If it is not given, this func will assume it is ``StandardMode()``. - It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.BoxConvertMode` . + It follows the same format with ``src_mode`` in :class:`~monai.apps.detection.transforms.array.ConvertBoxMode` . allow_missing_keys: don't raise exception if key is missing. - See also :py:class:`monai.apps.detection,transforms.array.BoxConvertToStandard` + See also :py:class:`monai.apps.detection,transforms.array.ConvertBoxToStandardMode` """ super().__init__(box_keys, allow_missing_keys) - self.converter = BoxConvertToStandard(mode=mode) - self.inverse_converter = BoxConvertMode(src_mode=None, dst_mode=mode) + self.converter = ConvertBoxToStandardMode(mode=mode) + self.inverse_converter = ConvertBoxMode(src_mode=None, dst_mode=mode) def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) - key_id = 0 for key in self.key_iterator(d): self.push_transform(d, key) d[key] = self.converter(d[key]) - key_id += 1 return d def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = deepcopy(dict(data)) - key_id = 0 for key in self.key_iterator(d): _ = self.get_most_recent_transform(d, key) # Inverse is same as forward d[key] = self.inverse_converter(d[key]) # Remove the applied transform self.pop_transform(d, key) - key_id += 1 return d -BoxConvertModeD = BoxConvertModeDict = BoxConvertModed -BoxConvertToStandardD = BoxConvertToStandardDict = BoxConvertToStandardd +ConvertBoxModeD = ConvertBoxModeDict = ConvertBoxModed +ConvertBoxToStandardModeD = ConvertBoxToStandardModeDict = ConvertBoxToStandardModed diff --git a/tests/test_box_transform.py b/tests/test_box_transform.py index 825fe9285e..a679dd3c95 100644 --- a/tests/test_box_transform.py +++ b/tests/test_box_transform.py @@ -14,11 +14,10 @@ import numpy as np from parameterized import parameterized -from monai.apps.detection.transforms.dictionary import BoxConvertToStandardd +from monai.apps.detection.transforms.dictionary import ConvertBoxToStandardModed from monai.transforms import Invertd from tests.utils import TEST_NDARRAYS, assert_allclose -# this script test box_convert_mode, box_convert_standard_mode TESTS = [] boxes = [[0, 0, 0, 0, 0, 0], [0, 1, 0, 2, 2, 3], [0, 1, 1, 2, 2, 3]] @@ -40,7 +39,7 @@ class TestBoxTransform(unittest.TestCase): @parameterized.expand(TESTS) def test_value(self, keys, data, expected_standard_result, expected_clip_result, expected_flip_result): - transform_convert_mode = BoxConvertToStandardd(**keys) + transform_convert_mode = ConvertBoxToStandardModed(**keys) result = transform_convert_mode(data) assert_allclose(result["boxes"], expected_standard_result, type_test=True, device_test=True, atol=0.0) From a16a78bc8c2fdb9ad8018dc856a5d8427691c7cb Mon Sep 17 00:00:00 2001 From: Can Zhao Date: Fri, 20 May 2022 10:23:51 -0400 Subject: [PATCH 5/6] typo Signed-off-by: Can Zhao --- monai/apps/detection/transforms/dictionary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/apps/detection/transforms/dictionary.py b/monai/apps/detection/transforms/dictionary.py index 63fe1c52a2..a0b4c76224 100644 --- a/monai/apps/detection/transforms/dictionary.py +++ b/monai/apps/detection/transforms/dictionary.py @@ -103,7 +103,7 @@ class ConvertBoxToStandardModed(MapTransform, InvertibleTransform): data = {"boxes": torch.ones(10,6)} # convert boxes with format [xmin, xmax, ymin, ymax, zmin, zmax] to [xmin, ymin, zmin, xmax, ymax, zmax] - box_converter = ConvertBoxModed(box_keys=["boxes"], mode="xxyyzz") + box_converter = ConvertBoxToStandardModed(box_keys=["boxes"], mode="xxyyzz") box_converter(data) """ From bb0b191829dde19a21a2c12aafb1b8ba4b980173 Mon Sep 17 00:00:00 2001 From: Wenqi Li Date: Fri, 20 May 2022 17:25:10 +0100 Subject: [PATCH 6/6] add __init__ Signed-off-by: Wenqi Li --- monai/apps/detection/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 monai/apps/detection/__init__.py diff --git a/monai/apps/detection/__init__.py b/monai/apps/detection/__init__.py new file mode 100644 index 0000000000..1e97f89407 --- /dev/null +++ b/monai/apps/detection/__init__.py @@ -0,0 +1,10 @@ +# Copyright (c) MONAI Consortium +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License.