diff --git a/nion/data/Core.py b/nion/data/Core.py index b76a0ff..cab1dbe 100755 --- a/nion/data/Core.py +++ b/nion/data/Core.py @@ -677,7 +677,7 @@ def function_make_elliptical_mask(data_shape: DataAndMetadata.ShapeType, center: bounds = Geometry.FloatRect.from_center_and_size(center_point, size_size) if bounds.height <= 0 or bounds.width <= 0: return DataAndMetadata.new_data_and_metadata(data=mask) - a, b = bounds.center.y, bounds.center.x + a, b = bounds.center.y - 0.5, bounds.center.x - 0.5 # work around incomplete numpy typing y: _ImageDataType x: _ImageDataType @@ -696,6 +696,23 @@ def function_make_elliptical_mask(data_shape: DataAndMetadata.ShapeType, center: mask[mask_eq] = 1 return DataAndMetadata.new_data_and_metadata(data=mask) +def function_make_rectangular_mask(data_shape: DataAndMetadata.ShapeType, center: Geometry.FloatPoint, size: Geometry.FloatSize, rotation: float) -> DataAndMetadata.DataAndMetadata: + data_size = Geometry.IntSize.make(typing.cast(Geometry.SizeIntTuple, data_shape)) + data_rect = Geometry.FloatRect(origin=Geometry.FloatPoint(), size=Geometry.FloatSize.make(typing.cast(Geometry.SizeFloatTuple, data_size))) + center_point = Geometry.map_point(center, Geometry.FloatRect.unit_rect(), data_rect) + size_size = Geometry.map_size(size, Geometry.FloatRect.unit_rect(), data_rect) + mask = numpy.zeros(data_shape) + bounds = Geometry.FloatRect.from_center_and_size(center_point, size_size) + a, b = bounds.top + bounds.height * 0.5 - 0.5, bounds.left + bounds.width * 0.5 - 0.5 + y, x = numpy.ogrid[-a:data_shape[0] - a, -b:data_shape[1] - b] # type: ignore + if rotation == 0.0: + mask_eq = (numpy.fabs(x) / (bounds.width / 2) <= 1) & (numpy.fabs(y) / (bounds.height / 2) <= 1) + else: + angle_sin = math.sin(rotation) + angle_cos = math.cos(rotation) + mask_eq = (numpy.fabs(x*angle_cos - y*angle_sin) / (bounds.width / 2) <= 1) & (numpy.fabs(y*angle_cos + x*angle_sin) / (bounds.height / 2) <= 1) + mask[mask_eq] = 1 + return DataAndMetadata.new_data_and_metadata(data=mask) def function_fourier_mask(data_and_metadata_in: _DataAndMetadataIndeterminateSizeLike, mask_data_and_metadata_in: _DataAndMetadataIndeterminateSizeLike) -> DataAndMetadata.DataAndMetadata: data_and_metadata_c = DataAndMetadata.promote_indeterminate_array(data_and_metadata_in) diff --git a/nion/data/test/Core_test.py b/nion/data/test/Core_test.py index 9ccfd9b..a2297f8 100755 --- a/nion/data/test/Core_test.py +++ b/nion/data/test/Core_test.py @@ -18,6 +18,7 @@ from nion.data import Core from nion.data import DataAndMetadata from nion.data.DataAndMetadata import _ImageDataType +from nion.utils import Geometry class TestCore(unittest.TestCase): @@ -1054,6 +1055,270 @@ def test_element_data_returns_ndarray(self) -> None: # test directly its type self.assertIsInstance(element.data, numpy.ndarray) + def test_elliptical_mask_generation(self) -> None: + bounds = Geometry.FloatRect.make(((0.2, 0.2), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[200, 200], 0) # top left + self.assertEqual(mask.data[200, 299], 0) # bottom left + self.assertEqual(mask.data[299, 299], 0) # bottom right + self.assertEqual(mask.data[299, 200], 0) # top right + + self.assertEqual(mask.data[249, 200], 1) # center top + self.assertEqual(mask.data[249, 199], 0) # center top + self.assertEqual(mask.data[299, 249], 1) # center right + self.assertEqual(mask.data[300, 249], 0) # center right + self.assertEqual(mask.data[249, 299], 1) # center bottom + self.assertEqual(mask.data[249, 300], 0) # center bottom + self.assertEqual(mask.data[200, 249], 1) # center left + self.assertEqual(mask.data[199, 249], 0) # center left + + def test_elliptical_mask_generation_out_of_bounds_top_left(self) -> None: + bounds = Geometry.FloatRect.make(((-0.05, -0.05), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[49, 49], 0) # bottom right + + self.assertEqual(mask.data[49, 0], 1) # center right + self.assertEqual(mask.data[50, 0], 0) # center right + self.assertEqual(mask.data[0, 49], 1) # center bottom + self.assertEqual(mask.data[0, 50], 0) # center bottom + + def test_elliptical_mask_generation_out_of_bounds_center_top(self) -> None: + bounds = Geometry.FloatRect.make(((0.45, -0.05), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[450, 49], 0) # bottom left + self.assertEqual(mask.data[549, 49], 0) # bottom right + + self.assertEqual(mask.data[549, 0], 1) # center right + self.assertEqual(mask.data[550, 0], 0) # center right + self.assertEqual(mask.data[500, 49], 1) # center bottom + self.assertEqual(mask.data[500, 50], 0) # center bottom + self.assertEqual(mask.data[450, 0], 1) # center left + self.assertEqual(mask.data[449, 0], 0) # center left + + def test_elliptical_mask_generation_out_of_bounds_top_right(self) -> None: + bounds = Geometry.FloatRect.make(((0.95, -0.05), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[950, 49], 0) # bottom left + + self.assertEqual(mask.data[999, 49], 1) # center bottom + self.assertEqual(mask.data[999, 50], 0) # center bottom + self.assertEqual(mask.data[950, 0], 1) # center left + self.assertEqual(mask.data[949, 0], 0) # center left + + def test_elliptical_mask_generation_out_of_bounds_center_right(self) -> None: + bounds = Geometry.FloatRect.make(((0.95, 0.45), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[950, 450], 0) # top left + self.assertEqual(mask.data[950, 549], 0) # bottom left + + self.assertEqual(mask.data[999, 450], 1) # center top + self.assertEqual(mask.data[999, 449], 0) # center top + self.assertEqual(mask.data[999, 549], 1) # center bottom + self.assertEqual(mask.data[999, 550], 0) # center bottom + self.assertEqual(mask.data[950, 500], 1) # center left + self.assertEqual(mask.data[949, 550], 0) # center left + + def test_elliptical_mask_generation_out_of_bounds_bottom_right(self) -> None: + bounds = Geometry.FloatRect.make(((0.95, 0.95), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[950, 950], 0) # top left + + self.assertEqual(mask.data[999, 950], 1) # center top + self.assertEqual(mask.data[999, 949], 0) # center top + self.assertEqual(mask.data[950, 999], 1) # center left + self.assertEqual(mask.data[949, 999], 0) # center left + + def test_elliptical_mask_generation_out_of_bound_center_bottom(self) -> None: + bounds = Geometry.FloatRect.make(((0.45, 0.95), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[450, 950], 0) # top left + self.assertEqual(mask.data[549, 950], 0) # top right + + self.assertEqual(mask.data[500, 950], 1) # center top + self.assertEqual(mask.data[500, 949], 0) # center top + self.assertEqual(mask.data[549, 999], 1) # center right + self.assertEqual(mask.data[550, 999], 0) # center right + self.assertEqual(mask.data[450, 999], 1) # center left + self.assertEqual(mask.data[449, 999], 0) # center left + + def test_elliptical_mask_generation_out_of_bounds_bottom_left(self) -> None: + bounds = Geometry.FloatRect.make(((-0.05, 0.95), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[49, 950], 0) # top right + + self.assertEqual(mask.data[0, 950], 1) # center top + self.assertEqual(mask.data[0, 949], 0) # center top + self.assertEqual(mask.data[49, 999], 1) # center right + self.assertEqual(mask.data[50, 999], 0) # center right + + def test_elliptical_mask_generation_out_of_bounds_center_left(self) -> None: + bounds = Geometry.FloatRect.make(((-0.05, 0.45), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertEqual(mask.data[49, 549], 0) # bottom right + self.assertEqual(mask.data[49, 450], 0) # top right + + self.assertEqual(mask.data[0, 450], 1) # center top + self.assertEqual(mask.data[0, 449], 0) # center top + self.assertEqual(mask.data[49, 500], 1) # center right + self.assertEqual(mask.data[50, 500], 0) # center right + self.assertEqual(mask.data[0, 549], 1) # center bottom + self.assertEqual(mask.data[0, 550], 0) # center bottom + + def test_elliptical_mask_generation_out_of_bounds_completely(self) -> None: + bounds = Geometry.FloatRect.make(((1.1, 1.1), (0.1, 0.1))) + mask_xdata = Core.function_make_elliptical_mask((1000, 1000), bounds.center.as_tuple(), bounds.size.as_tuple(), 0) + mask = mask_xdata.data + self.assertTrue(numpy.all(mask == 0)) + + def test_rectangular_mask_generation(self) -> None: + bounds = Geometry.FloatRect.make(((0.2, 0.2), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size,0) + mask = mask_xdata.data + self.assertEqual(mask.data[200, 200], 1) # top left + self.assertEqual(mask.data[199, 199], 0) # top left + self.assertEqual(mask.data[200, 299], 1) # bottom left + self.assertEqual(mask.data[199, 300], 0) # bottom left + self.assertEqual(mask.data[299, 299], 1) # bottom right + self.assertEqual(mask.data[300, 300], 0) # bottom right + self.assertEqual(mask.data[299, 200], 1) # top right + self.assertEqual(mask.data[300, 199], 0) # top right + + self.assertEqual(mask.data[249, 200], 1) # center top + self.assertEqual(mask.data[249, 199], 0) # center top + self.assertEqual(mask.data[299, 249], 1) # center right + self.assertEqual(mask.data[300, 249], 0) # center right + self.assertEqual(mask.data[249, 299], 1) # center bottom + self.assertEqual(mask.data[249, 300], 0) # center bottom + self.assertEqual(mask.data[200, 249], 1) # center left + self.assertEqual(mask.data[199, 249], 0) # center left + + def test_rectangular_mask_generation_out_of_bounds_top_left(self) -> None: + bounds = Geometry.FloatRect.make(((-0.05, -0.05), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size, 0) + mask = mask_xdata.data + self.assertEqual(mask.data[49, 49], 1) # bottom right + self.assertEqual(mask.data[50, 50], 0) # bottom right + + self.assertEqual(mask.data[49, 0], 1) # center right + self.assertEqual(mask.data[50, 0], 0) # center right + self.assertEqual(mask.data[0, 49], 1) # center bottom + self.assertEqual(mask.data[0, 50], 0) # center bottom + + def test_rectangular_mask_generation_out_of_bounds_center_top(self) -> None: + bounds = Geometry.FloatRect.make(((0.45, -0.05), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size, 0) + mask = mask_xdata.data + self.assertEqual(mask.data[450, 49], 1) # bottom left + self.assertEqual(mask.data[449, 50], 0) # bottom left + self.assertEqual(mask.data[549, 49], 1) # bottom right + self.assertEqual(mask.data[550, 50], 0) # bottom right + + self.assertEqual(mask.data[549, 0], 1) # center right + self.assertEqual(mask.data[550, 0], 0) # center right + self.assertEqual(mask.data[500, 49], 1) # center bottom + self.assertEqual(mask.data[500, 50], 0) # center bottom + self.assertEqual(mask.data[450, 0], 1) # center left + self.assertEqual(mask.data[449, 0], 0) # center left + + def test_rectangular_mask_generation_out_of_bounds_top_right(self) -> None: + bounds = Geometry.FloatRect.make(((0.95, -0.05), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size,0) + mask = mask_xdata.data + self.assertEqual(mask.data[950, 49], 1) # bottom left + self.assertEqual(mask.data[949, 50], 0) # bottom left + + self.assertEqual(mask.data[999, 49], 1) # center bottom + self.assertEqual(mask.data[999, 50], 0) # center bottom + self.assertEqual(mask.data[950, 0], 1) # center left + self.assertEqual(mask.data[949, 0], 0) # center left + + def test_rectangular_mask_generation_out_of_bounds_center_right(self) -> None: + bounds = Geometry.FloatRect.make(((0.95, 0.45), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size, 0) + mask = mask_xdata.data + self.assertEqual(mask.data[950, 450], 1) # top left + self.assertEqual(mask.data[949, 449], 0) # top left + self.assertEqual(mask.data[950, 549], 1) # bottom left + self.assertEqual(mask.data[950, 550], 0) # bottom left + + self.assertEqual(mask.data[999, 450], 1) # center top + self.assertEqual(mask.data[999, 449], 0) # center top + self.assertEqual(mask.data[999, 549], 1) # center bottom + self.assertEqual(mask.data[999, 550], 0) # center bottom + self.assertEqual(mask.data[950, 500], 1) # center left + self.assertEqual(mask.data[949, 500], 0) # center left + + def test_rectangular_mask_generation_out_of_bounds_bottom_right(self) -> None: + bounds = Geometry.FloatRect.make(((0.95, 0.95), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size,0) + mask = mask_xdata.data + self.assertEqual(mask.data[950, 950], 1) # top left + self.assertEqual(mask.data[949, 949], 0) # top left + + self.assertEqual(mask.data[999, 950], 1) # center top + self.assertEqual(mask.data[999, 949], 0) # center top + self.assertEqual(mask.data[950, 999], 1) # center left + self.assertEqual(mask.data[949, 999], 0) # center left + + def test_rectangular_mask_generation_out_of_bound_center_bottom(self) -> None: + bounds = Geometry.FloatRect.make(((0.45, 0.95), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size,0) + mask = mask_xdata.data + self.assertEqual(mask.data[450, 950], 1) # top left + self.assertEqual(mask.data[449, 949], 0) # top left + self.assertEqual(mask.data[549, 950], 1) # top right + self.assertEqual(mask.data[550, 949], 0) # top right + + self.assertEqual(mask.data[500, 950], 1) # center top + self.assertEqual(mask.data[500, 949], 0) # center top + self.assertEqual(mask.data[549, 999], 1) # center right + self.assertEqual(mask.data[550, 999], 0) # center right + self.assertEqual(mask.data[450, 999], 1) # center left + self.assertEqual(mask.data[449, 999], 0) # center left + + def test_rectangular_mask_generation_out_of_bounds_bottom_left(self) -> None: + bounds = Geometry.FloatRect.make(((-0.05, 0.95), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size,0) + mask = mask_xdata.data + self.assertEqual(mask.data[49, 950], 1) # top right + self.assertEqual(mask.data[50, 949], 0) # top right + + self.assertEqual(mask.data[0, 950], 1) # center top + self.assertEqual(mask.data[0, 949], 0) # center top + self.assertEqual(mask.data[49, 999], 1) # center right + self.assertEqual(mask.data[50, 999], 0) # center right + + def test_rectangular_mask_generation_out_of_bounds_center_left(self) -> None: + bounds = Geometry.FloatRect.make(((-0.05, 0.45), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size,0) + mask = mask_xdata.data + self.assertEqual(mask.data[49, 549], 1) # bottom right + self.assertEqual(mask.data[50, 550], 0) # bottom right + self.assertEqual(mask.data[49, 450], 1) # top right + self.assertEqual(mask.data[50, 449], 0) # top right + + self.assertEqual(mask.data[0, 450], 1) # center top + self.assertEqual(mask.data[0, 449], 0) # center top + self.assertEqual(mask.data[49, 500], 1) # center right + self.assertEqual(mask.data[50, 500], 0) # center right + self.assertEqual(mask.data[0, 549], 1) # center bottom + self.assertEqual(mask.data[0, 550], 0) # center bottom + + def test_rectangular_mask_generation_out_of_bounds_completely(self) -> None: + bounds = Geometry.FloatRect.make(((1.1, 1.1), (0.1, 0.1))) + mask_xdata = Core.function_make_rectangular_mask((1000, 1000), bounds.center, bounds.size,0) + mask = mask_xdata.data + self.assertTrue(numpy.all(mask == 0)) + if __name__ == '__main__': logging.getLogger().setLevel(logging.DEBUG)