From cbbec6dc541c9e3de86ffc648b3b849a3309a9b7 Mon Sep 17 00:00:00 2001 From: Mohammad Adil Date: Fri, 6 Mar 2020 16:03:03 -0800 Subject: [PATCH 1/2] Add RandomRotate. --- monai/transforms/transforms.py | 49 +++++++++++++++++++++++++++++----- tests/test_random_rotate.py | 41 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 tests/test_random_rotate.py diff --git a/monai/transforms/transforms.py b/monai/transforms/transforms.py index fb1bf468d6..11d47439a7 100644 --- a/monai/transforms/transforms.py +++ b/monai/transforms/transforms.py @@ -441,10 +441,45 @@ def __call__(self, img): return data -# if __name__ == "__main__": -# img = np.array((1, 2, 3, 4)).reshape((1, 2, 2)) -# rotator = RandRotate90(prob=0.0, max_k=3, axes=(1, 2)) -# # rotator.set_random_state(1234) -# img_result = rotator(img) -# print(type(img)) -# print(img_result) +@export +class RandomRotate(Randomizable): + """Randomly rotates the input arrays. + + Args: + prob (float): Probability of rotation. + angle (float): Rotation angle in degrees. + axes (tuple of 2 ints): Axes of rotation. Default: (1, 2). This is the first two + axis in spatial dimensions according to MONAI channel first shape assumption. + reshape (bool): If true, output shape is made same as input. Default: True. + order (int): Order of spline interpolation. Range 0-5. Default: 1. This is + different from scipy where default interpolation is 3. + mode (str): Points outside boundary filled according to this mode. Options are + 'constant', 'nearest', 'reflect', 'wrap'. Default: 'constant'. + cval (scalar): Values to fill outside boundary. Default: 0. + prefiter (bool): Apply spline_filter before interpolation. Default: True. + """ + + def __init__(self, angle, prob=0.1, axes=(1, 2), reshape=True, order=1, + mode='constant', cval=0, prefilter=True): + self.prob = prob + self.angle = angle + self.reshape = reshape + self.order = order + self.mode = mode + self.cval = cval + self.prefilter = prefilter + self.axes = axes + + self._do_transform = False + self._zoom = None + + def randomize(self): + self._do_transform = self.R.random_sample() < self.prob + + def __call__(self, img): + self.randomize() + if not self._do_transform: + return img + rotator = Rotate(self.angle, self.axes, self.reshape, self.order, + self.mode, self.cval, self.prefilter) + return rotator(img) diff --git a/tests/test_random_rotate.py b/tests/test_random_rotate.py new file mode 100644 index 0000000000..7448d5793f --- /dev/null +++ b/tests/test_random_rotate.py @@ -0,0 +1,41 @@ +# Copyright 2020 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 + +import scipy.ndimage +from parameterized import parameterized + +from monai.transforms import RandomRotate +from tests.utils import NumpyImageTestCase2D + + +class RandomRotateTest(NumpyImageTestCase2D): + + @parameterized.expand([ + (90, (1, 2), True, 1, 'reflect', 0, True), + (-90, (2, 1), True, 3, 'constant', 0, True), + (180, (2, 3), False, 2, 'constant', 4, False), + ]) + def test_correct_results(self, angle, axes, reshape, + order, mode, cval, prefilter): + rotate_fn = RandomRotate(angle, prob=1.0, axes=axes, reshape=reshape, + order=order, mode=mode, cval=cval, prefilter=prefilter) + rotated = rotate_fn(self.imt) + + expected = scipy.ndimage.rotate(self.imt, angle, axes, reshape, order=order, + mode=mode, cval=cval, prefilter=prefilter) + self.assertTrue(np.allclose(expected, rotated)) + + +if __name__ == '__main__': + unittest.main() From fc04b1e9a4dec20b834cf5301ee6e5a13a21deea Mon Sep 17 00:00:00 2001 From: Mohammad Adil Date: Mon, 9 Mar 2020 22:02:47 -0700 Subject: [PATCH 2/2] Change argument from number to range. --- monai/transforms/transforms.py | 18 ++++++++++++------ tests/test_random_rotate.py | 12 +++++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/monai/transforms/transforms.py b/monai/transforms/transforms.py index 11d47439a7..ea4889f44d 100644 --- a/monai/transforms/transforms.py +++ b/monai/transforms/transforms.py @@ -442,12 +442,13 @@ def __call__(self, img): @export -class RandomRotate(Randomizable): +class RandRotate(Randomizable): """Randomly rotates the input arrays. Args: prob (float): Probability of rotation. - angle (float): Rotation angle in degrees. + degrees (tuple of float or float): Range of rotation in degrees. If single number, + angle is picked from (-degrees, degrees). axes (tuple of 2 ints): Axes of rotation. Default: (1, 2). This is the first two axis in spatial dimensions according to MONAI channel first shape assumption. reshape (bool): If true, output shape is made same as input. Default: True. @@ -455,14 +456,14 @@ class RandomRotate(Randomizable): different from scipy where default interpolation is 3. mode (str): Points outside boundary filled according to this mode. Options are 'constant', 'nearest', 'reflect', 'wrap'. Default: 'constant'. - cval (scalar): Values to fill outside boundary. Default: 0. + cval (scalar): Value to fill outside boundary. Default: 0. prefiter (bool): Apply spline_filter before interpolation. Default: True. """ - def __init__(self, angle, prob=0.1, axes=(1, 2), reshape=True, order=1, + def __init__(self, degrees, prob=0.1, axes=(1, 2), reshape=True, order=1, mode='constant', cval=0, prefilter=True): self.prob = prob - self.angle = angle + self.degrees = degrees self.reshape = reshape self.order = order self.mode = mode @@ -470,11 +471,16 @@ def __init__(self, angle, prob=0.1, axes=(1, 2), reshape=True, order=1, self.prefilter = prefilter self.axes = axes + if not hasattr(self.degrees, '__iter__'): + self.degrees = (-self.degrees, self.degrees) + assert len(self.degrees) == 2, "degrees should be a number or pair of numbers." + self._do_transform = False - self._zoom = None + self.angle = None def randomize(self): self._do_transform = self.R.random_sample() < self.prob + self.angle = self.R.uniform(low=self.degrees[0], high=self.degrees[1]) def __call__(self, img): self.randomize() diff --git a/tests/test_random_rotate.py b/tests/test_random_rotate.py index 7448d5793f..29036663af 100644 --- a/tests/test_random_rotate.py +++ b/tests/test_random_rotate.py @@ -15,7 +15,7 @@ import scipy.ndimage from parameterized import parameterized -from monai.transforms import RandomRotate +from monai.transforms import RandRotate from tests.utils import NumpyImageTestCase2D @@ -23,15 +23,17 @@ class RandomRotateTest(NumpyImageTestCase2D): @parameterized.expand([ (90, (1, 2), True, 1, 'reflect', 0, True), - (-90, (2, 1), True, 3, 'constant', 0, True), + ((-45, 45), (2, 1), True, 3, 'constant', 0, True), (180, (2, 3), False, 2, 'constant', 4, False), ]) - def test_correct_results(self, angle, axes, reshape, + def test_correct_results(self, degrees, axes, reshape, order, mode, cval, prefilter): - rotate_fn = RandomRotate(angle, prob=1.0, axes=axes, reshape=reshape, - order=order, mode=mode, cval=cval, prefilter=prefilter) + rotate_fn = RandRotate(degrees, prob=1.0, axes=axes, reshape=reshape, + order=order, mode=mode, cval=cval, prefilter=prefilter) + rotate_fn.set_random_state(243) rotated = rotate_fn(self.imt) + angle = rotate_fn.angle expected = scipy.ndimage.rotate(self.imt, angle, axes, reshape, order=order, mode=mode, cval=cval, prefilter=prefilter) self.assertTrue(np.allclose(expected, rotated))