From d98eca93ff8fb2388b920c71f5b20bef841a65c8 Mon Sep 17 00:00:00 2001 From: voetberg Date: Tue, 6 Jun 2023 16:06:00 -0500 Subject: [PATCH 1/6] tests --- tests/shape_generator_test.py | 432 ++++++++++++++++++++++++++++++++ tests/sky_image_test.py | 1 + tests/test_shape_generator.py | 446 ---------------------------------- 3 files changed, 433 insertions(+), 446 deletions(-) create mode 100644 tests/shape_generator_test.py delete mode 100644 tests/test_shape_generator.py diff --git a/tests/shape_generator_test.py b/tests/shape_generator_test.py new file mode 100644 index 0000000..c8d80d4 --- /dev/null +++ b/tests/shape_generator_test.py @@ -0,0 +1,432 @@ +import pytest +from src.deepbench.shape_generator.shape_generator import ShapeGenerator + +import numpy as np +import matplotlib.patches as patch + + +def test_patch_conversion_defaults(): + # simple patch object. Convert to a numpy array + simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) + converted_object = ShapeGenerator((28, 28))._convert_patch_to_image( + image=simple_patch + ) + expected_obj_type = np.array([0, 0]) + assert type(converted_object) == type(expected_obj_type) + + +def test_patch_conversion_default_dimensions(): + simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) + converted_object = ShapeGenerator()._convert_patch_to_image(image=simple_patch) + converted_object_shape_x, converted_object_shape_y = converted_object.shape + expected_x_dim, expected_y_dim = 28, 28 + + assert converted_object_shape_x == expected_x_dim + assert converted_object_shape_y == expected_y_dim + + +def test_patch_conversion_single_image_dimension(): + with pytest.raises(TypeError): + simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) + ShapeGenerator(tuple(28))._convert_patch_to_image(image=simple_patch) + + +def test_patch_conversion_N_dimension(): + simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) + converted_object = ShapeGenerator((28, 28, 28))._convert_patch_to_image( + image=simple_patch + ) + ( + converted_object_shape_x, + converted_object_shape_y, + converted_object_shape_z, + ) = converted_object.shape + expected_x_dim, expected_y_dim, expected_z_dim = 28, 28, 28 + + assert converted_object_shape_x == expected_x_dim + assert converted_object_shape_y == expected_y_dim + assert converted_object_shape_z == expected_z_dim + + +def test_patch_conversion_size_0(): + with pytest.raises(ValueError): + simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) + ShapeGenerator((0, 0))._convert_patch_to_image(image=simple_patch) + + +def test_patch_conversion_non_int_size(): + simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) + converted_object = ShapeGenerator((55.5, 55.5))._convert_patch_to_image( + image=simple_patch + ) + converted_object_shape_x, converted_object_shape_y = converted_object.shape + expected_x_dim, expected_y_dim = 56, 56 + + assert converted_object_shape_x == expected_x_dim + assert converted_object_shape_y == expected_y_dim + + +def test_patch_conversion_non_int_size_round(): + simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) + converted_object = ShapeGenerator(image_shape=(55.1, 55.6))._convert_patch_to_image( + image=simple_patch + ) + converted_object_shape_x, converted_object_shape_y = converted_object.shape + expected_x_dim, expected_y_dim = 56, 56 + assert converted_object_shape_x == expected_x_dim + assert converted_object_shape_y == expected_y_dim + + +def test_resize_default(): + simple_image = np.zeros((32, 32)) + resized_image = ShapeGenerator().resize(image=simple_image) + resized_image_x, resized_image_y = resized_image.shape + default_new_size_x, default_new_size_y = 28, 28 + + assert resized_image_x == default_new_size_x + assert resized_image_y == default_new_size_y + + +def test_resize_dim_mismatch(): + with pytest.raises(ValueError): + simple_image = np.zeros((32, 32)) + resize_dimensions = (1, 1, 1) + ShapeGenerator().resize(simple_image, resize_dimensions=resize_dimensions) + + +def test_resize_zero_size_source(): + with pytest.raises(ValueError): + simple_image = np.zeros((0, 0)) + resize_dimensions = (1, 1) + ShapeGenerator().resize(image=simple_image, resize_dimensions=resize_dimensions) + + +def test_resize_zero_size_target(): + with pytest.raises(ValueError): + simple_image = np.zeros((1, 1)) + resize_dimensions = (0, 0) + ShapeGenerator().resize(image=simple_image, resize_dimensions=resize_dimensions) + + +def test_resize_upscale(): + simple_image = np.zeros((32, 32)) + resize = (50, 50) + resized_image = ShapeGenerator().resize( + image=simple_image, resize_dimensions=resize + ) + resized_image_x, resized_image_y = resized_image.shape + new_size_x, new_size_y = resize + + assert resized_image_x == new_size_x + assert resized_image_y == new_size_y + + +def test_resize_negative(): + with pytest.raises(ValueError): + simple_image = np.zeros((80, 80)) + resize = (-1, -1) + ShapeGenerator().resize(image=simple_image, resize_dimensions=resize) + + +def test_rectangle_default(): + rectangle = ShapeGenerator().create_rectangle() + + x, y = rectangle.shape + expected_x, expected_y = (28, 28) + assert x == expected_x + assert y == expected_y + + # Each corner should have a black pixel + assert 1.0 == rectangle[9, 10], "corner 1 failed" + assert 1.0 == rectangle[19, 19], "corner 4 failed" + + # Center needs to be white (default is unfilled) + assert 0.0 == rectangle[14, 14], "Center Filled" + + +def test_rectangle_size_center_dim_mismatch(): + with pytest.raises(ValueError): + ShapeGenerator().create_rectangle(center=(1, 1, 1)) + + +def test_rectangle_xy_out_of_range(): + with pytest.raises(UserWarning): + rectangle = ShapeGenerator((10, 10)).create_rectangle(center=(100, 100)) + + rectangle_contents = rectangle.sum().sum() + assert 0 == rectangle_contents + + +def test_rectangle_angle_in_bounds_no_change(): + angle = 90 + rectangle_rotated = ShapeGenerator().create_rectangle(angle=angle) + + rectangle_non_rotated = ShapeGenerator().create_rectangle(angle=0) + + # But it's a square so it looks the same + assert rectangle_rotated.all() == rectangle_non_rotated.all() + + +def test_rectangle_angle_in_bounds_change(): + angle = 360 + rectangle = ShapeGenerator().create_rectangle(width=10, height=10, angle=angle) + + # Each corner should have a black pixel + assert (1.0, rectangle[9, 10], "corner 1 failed") + assert (1.0, rectangle[19, 19], "corner 4 failed") + + # Center needs to be white (default is unfilled) + assert (0.0, rectangle[14, 14], "Center Filled") + + +def test_rectangle_angle_oob_positive(): + angle = 45 + rectangle_rotated = ShapeGenerator().create_rectangle(angle=angle + 360) + rectangle_non_rotated = ShapeGenerator().create_rectangle(angle=angle) + + assert rectangle_rotated.all() == rectangle_non_rotated.all() + + +def test_rectangle_angle_oob_negative(): + angle = 45 + rectangle_rotated = ShapeGenerator().create_rectangle(angle=angle - 360) + rectangle_non_rotated = ShapeGenerator().create_rectangle(angle=angle) + + assert rectangle_rotated.all() == rectangle_non_rotated.all() + + +def test_rectangle_fill(): + # This should fill the whole screen. + rectangle = ShapeGenerator().create_rectangle(width=80, height=80, fill=True) + ideal_rectangle = np.ones((28, 28)) + + assert ideal_rectangle.all() == rectangle.all() + + +def test_polygon_default(): + triangle = ShapeGenerator().create_regular_polygon() + + shape_x, shape_y = triangle.shape + expected_x, expected_y = 28, 28 + + assert shape_x == expected_x + assert shape_y == expected_y + + # Center should be white + assert 0.0 == triangle[14, 14] + + # top point should be black + assert 1.0 == triangle[21, 17] + + # TODO the trig to check the other points here + + +def test_polygon_size_center_mismatch(): + with pytest.raises(ValueError): + ShapeGenerator().create_regular_polygon(center=(14, 14, 14)) + + +def test_polygon_positive_oob_angle(): + angle = 45 + triangle_rotated = ShapeGenerator().create_regular_polygon(angle=angle + 360) + triangle_non_rotated = ShapeGenerator().create_regular_polygon(angle=angle) + + assert (triangle_rotated.all(), triangle_non_rotated.all()) + + +def test_polygon_negative_oob_angle(): + angle = 45 + triangle_rotated = ShapeGenerator().create_regular_polygon(angle=angle - 360) + triangle_non_rotated = ShapeGenerator().create_regular_polygon(angle=angle) + + assert (triangle_rotated.all(), triangle_non_rotated.all()) + + +def test_polygon_negative_radius(): + triangle = ShapeGenerator().create_regular_polygon(radius=-10) + # Center should be white + assert (0.0, triangle[14, 14]) + + # top point should be black + # The radius will just be abs + assert (1.0, triangle[21, 17]) + + +def test_polygon_negative_vertices(): + with pytest.raises(ValueError): + ShapeGenerator((28, 28)).create_regular_polygon(vertices=-3) + + +def test_arc_default(): + arc = ShapeGenerator((28, 28)).create_arc() + + x, y = arc.shape + expected_x, expected_y = (28, 28) + assert (x, expected_x) + assert (y, expected_y) + + assert (0.0, arc[14, 14], "Empty Center") + assert (1.0, arc[14, 24], "Bottom Point") + assert (1.0, arc[23, 15], "Starting Point") + + +def test_arc_size_center_dim_mismatch(): + with pytest.raises(ValueError): + ShapeGenerator().create_arc(center=(0, 0, 0)) + + +def test_arc_oob_negative_theta1(): + angle = 45 + arc_rotated = ShapeGenerator().create_arc(theta1=angle - 360) + arc_non_rotated = ShapeGenerator().create_arc(theta1=angle) + + assert arc_rotated.all() == arc_non_rotated.all() + + +def test_arc_oob_positive_theta1(): + angle = 45 + arc_rotated = ShapeGenerator().create_arc(theta1=angle + 360) + arc_non_rotated = ShapeGenerator().create_arc(theta1=angle) + + assert arc_rotated.all() == arc_non_rotated.all() + + +def test_arc_oob_negative_theta2(): + angle = 45 + arc_rotated = ShapeGenerator().create_arc(theta2=angle - 360) + arc_non_rotated = ShapeGenerator().create_arc(theta2=angle) + + assert (arc_rotated.all(), arc_non_rotated.all()) + + +def test_arc_oob_positive_theta2(): + angle = 45 + arc_rotated = ShapeGenerator().create_arc(theta2=angle + 360) + arc_non_rotated = ShapeGenerator().create_arc(theta2=angle) + + assert (arc_rotated.all(), arc_non_rotated.all()) + + +def test_arc_theta2_less_than_theta1(): + + arc_negative_sweep = ShapeGenerator().create_arc(theta1=90, theta2=0) + arc_positive_sweep = ShapeGenerator().create_arc(theta1=0, theta2=90) + + assert arc_negative_sweep.all() == arc_positive_sweep.all() + + +def test_arc_oob_width(): + arc = ShapeGenerator((14, 14)).create_arc( + radius=28, line_width=100, theta1=0, theta2=360 + ) + + size = arc.size + n_black_pixels = int(arc.sum().sum()) + assert size == n_black_pixels + + +def test_line_defaults(): + line = ShapeGenerator().create_line() + + x, y = line.shape + expected_x, expected_y = (28, 28) + assert x == expected_x + assert y == expected_y + + # Bottom left top right are black. Opposite are white + assert (1.0 == line[1, 2], "line start incorrect") + assert (1.0 == line[26, 27], "line end incorrect") + + assert (0.0 == line[1, 27], "line corner incorrect") + assert (0.0 == line[27, 1], "line corner incorrect") + + +def test_line_size_start_dim_mismatch(): + with pytest.raises(ValueError): + ShapeGenerator().create_line(start=(0, 0, 0)) + + +def test_line_size_end_dim_mismatch(): + with pytest.raises(ValueError): + ShapeGenerator().create_line(end=(0, 0, 0)) + + +def test_line_start_oob(): + line = ShapeGenerator().create_line(start=(-200, -100)) + + # Bottom left top right are black. Opposite are white + assert 1.0 == line[1, 14] + assert 1.0 == line[25, 27] + + +def test_line_end_oob(): + line = ShapeGenerator().create_line(end=(200, 100)) + + assert 1.0 == line[1, 1] + assert 1.0 == line[27, 14] + + +def test_line_same_start_end(): + with pytest.raises(ValueError): + ShapeGenerator().create_line(end=(0, 0)) + + +def test_ellipse_default(): + circle = ShapeGenerator().create_ellipse() + + shape_x, shape_y = circle.shape + expected_x, expected_y = 28, 28 + + assert shape_x == expected_x + assert shape_y == expected_y + + # Center should be white + assert 0.0 == circle[14, 14] + + # Default is a circle + assert 1.0 == circle[11, 10] + assert 1.0 == circle[14, 19] + assert 1.0 == circle[19, 14] + assert 1.0 == circle[9, 14] + + +def test_ellipse_size_xy_mismatch(): + with pytest.raises(ValueError): + ShapeGenerator().create_ellipse(center=(14, 14, 14)) + + +def test_ellipse_0_radius(): + ## Nothing is displayed + with pytest.raises(UserWarning): + ShapeGenerator().create_ellipse(width=0, height=0) + + +def test_ellipse_non_int_radius(): + # Just round up + circle = ShapeGenerator().create_ellipse(height=9.4) + + # Center should be white + assert 0.0 == circle[14, 14] + + # top and bottom points should be black + assert 1.0 == circle[14, 19] + assert 1.0 == circle[19, 14] + + +def test_ellipse_oob_center(): + with pytest.raises(UserWarning): + circle = ShapeGenerator((10, 10)).create_ellipse(center=(100, 100)) + contents = circle.sum().sum() + assert 0.0 == contents + + +def test_ellipse_non_int_width(): + # Just round up + circle = ShapeGenerator().create_ellipse(width=9.4) + + # Center should be white + assert 0.0 == circle[14, 14] + + # top and bottom points should be black + assert 1.0 == circle[14, 19] + assert 1.0 == circle[19, 14] diff --git a/tests/sky_image_test.py b/tests/sky_image_test.py index afafb3a..d87ba60 100644 --- a/tests/sky_image_test.py +++ b/tests/sky_image_test.py @@ -21,6 +21,7 @@ def test_0dim_size(self): SkyImage([{}], im_shape) def test_3dim_size(self): + im_shape = (14, 14, 3) test_sky = SkyImage([{}], im_shape) diff --git a/tests/test_shape_generator.py b/tests/test_shape_generator.py deleted file mode 100644 index 11ca63e..0000000 --- a/tests/test_shape_generator.py +++ /dev/null @@ -1,446 +0,0 @@ -from unittest import TestCase -from src.deepbench.shape_generator.shape_generator import ShapeGenerator - -import numpy as np -import matplotlib.patches as patch - - -class TestShapeGenerator(TestCase): - def test_patch_conversion_defaults(self): - # simple patch object. Convert to a numpy array - simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) - converted_object = ShapeGenerator._convert_patch_to_image(image=simple_patch) - expected_obj_type = np.array([0, 0]) - self.assertEqual(type(converted_object), type(expected_obj_type)) - - def test_patch_conversion_default_dimensions(self): - simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) - converted_object = ShapeGenerator._convert_patch_to_image(image=simple_patch) - converted_object_shape_x, converted_object_shape_y = converted_object.shape - expected_x_dim, expected_y_dim = 28, 28 - - self.assertEqual(converted_object_shape_x, expected_x_dim) - self.assertEqual(converted_object_shape_y, expected_y_dim) - - def test_patch_conversion_single_image_dimension(self): - with self.assertRaises(ValueError): - simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) - ShapeGenerator._convert_patch_to_image( - image=simple_patch, image_shape=tuple([28]) - ) - - def test_patch_conversion_N_dimension(self): - simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) - converted_object = ShapeGenerator._convert_patch_to_image( - image=simple_patch, image_shape=(28, 28, 28) - ) - ( - converted_object_shape_x, - converted_object_shape_y, - converted_object_shape_z, - ) = converted_object.shape - expected_x_dim, expected_y_dim, expected_z_dim = 28, 28, 28 - - self.assertEqual(converted_object_shape_x, expected_x_dim) - self.assertEqual(converted_object_shape_y, expected_y_dim) - self.assertEqual(converted_object_shape_z, expected_z_dim) - - def test_patch_conversion_size_0(self): - with self.assertRaises(ValueError): - simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) - ShapeGenerator._convert_patch_to_image( - image=simple_patch, image_shape=(0, 0) - ) - - def test_patch_conversion_non_int_size(self): - simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) - converted_object = ShapeGenerator._convert_patch_to_image( - image=simple_patch, image_shape=(55.5, 55.5) - ) - converted_object_shape_x, converted_object_shape_y = converted_object.shape - expected_x_dim, expected_y_dim = 56, 56 - - self.assertEqual(converted_object_shape_x, expected_x_dim) - self.assertEqual(converted_object_shape_y, expected_y_dim) - - def test_patch_conversion_non_int_size_round(self): - simple_patch = patch.Rectangle(xy=(0, 0), width=1, height=1) - converted_object = ShapeGenerator._convert_patch_to_image( - image=simple_patch, image_shape=(55.1, 55.6) - ) - converted_object_shape_x, converted_object_shape_y = converted_object.shape - expected_x_dim, expected_y_dim = 56, 56 - - self.assertEqual(converted_object_shape_x, expected_x_dim) - self.assertEqual(converted_object_shape_y, expected_y_dim) - - def test_resize_default(self): - simple_image = np.zeros((32, 32)) - resized_image = ShapeGenerator.resize(image=simple_image) - resized_image_x, resized_image_y = resized_image.shape - default_new_size_x, default_new_size_y = 28, 28 - - self.assertEqual(resized_image_x, default_new_size_x) - self.assertEqual(resized_image_y, default_new_size_y) - - def test_resize_dim_mismatch(self): - with self.assertRaises(ValueError): - simple_image = np.zeros((32, 32)) - resize_dimensions = (1, 1, 1) - ShapeGenerator.resize( - image=simple_image, resize_dimensions=resize_dimensions - ) - - def test_resize_zero_size_source(self): - with self.assertRaises(ValueError): - simple_image = np.zeros((0, 0)) - resize_dimensions = (1, 1) - ShapeGenerator.resize( - image=simple_image, resize_dimensions=resize_dimensions - ) - - def test_resize_zero_size_target(self): - with self.assertRaises(ValueError): - simple_image = np.zeros((1, 1)) - resize_dimensions = (0, 0) - ShapeGenerator.resize( - image=simple_image, resize_dimensions=resize_dimensions - ) - - def test_resize_upscale(self): - simple_image = np.zeros((32, 32)) - resize = (50, 50) - resized_image = ShapeGenerator.resize( - image=simple_image, resize_dimensions=resize - ) - resized_image_x, resized_image_y = resized_image.shape - new_size_x, new_size_y = resize - - self.assertEqual(resized_image_x, new_size_x) - self.assertEqual(resized_image_y, new_size_y) - - def test_resize_downscale(self): - simple_image = np.zeros((80, 80)) - resize = (50, 50) - resized_image = ShapeGenerator.resize( - image=simple_image, resize_dimensions=resize - ) - resized_image_x, resized_image_y = resized_image.shape - new_size_x, new_size_y = resize - - self.assertEqual(resized_image_x, new_size_x) - self.assertEqual(resized_image_y, new_size_y) - - def test_resize_non_int(self): - simple_image = np.zeros((80, 80)) - resize = (49.1, 49.6) - resized_image = ShapeGenerator.resize( - image=simple_image, resize_dimensions=resize - ) - resized_image_x, resized_image_y = resized_image.shape - new_size_x, new_size_y = (50, 50) - - self.assertEqual(resized_image_x, new_size_x) - self.assertEqual(resized_image_y, new_size_y) - - def test_resize_negative(self): - with self.assertRaises(ValueError): - simple_image = np.zeros((80, 80)) - resize = (-1, -1) - ShapeGenerator.resize(image=simple_image, resize_dimensions=resize) - - def test_rectangle_default(self): - rectangle = ShapeGenerator.create_rectangle() - - x, y = rectangle.shape - expected_x, expected_y = (28, 28) - self.assertEqual(x, expected_x) - self.assertEqual(y, expected_y) - - # Each corner should have a black pixel - self.assertEqual(1.0, rectangle[9, 10], "corner 1 failed") - self.assertEqual(1.0, rectangle[19, 19], "corner 4 failed") - - # Center needs to be white (default is unfilled) - self.assertEqual(0.0, rectangle[14, 14], "Center Filled") - - def test_rectangle_size_center_dim_mismatch(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_rectangle(center=(1, 1, 1)) - - def test_rectangle_xy_out_of_range(self): - with self.assertRaises(UserWarning): - rectangle = ShapeGenerator.create_rectangle( - image_shape=(10, 10), center=(100, 100) - ) - - rectangle_contents = rectangle.sum().sum() - self.assertEqual(0, rectangle_contents) - - def test_rectangle_angle_in_bounds_no_change(self): - angle = 90 - rectangle_rotated = ShapeGenerator.create_rectangle(angle=angle) - - rectangle_non_rotated = ShapeGenerator.create_rectangle(angle=0) - - # But it's a square so it looks the same - self.assertEqual(rectangle_rotated.all(), rectangle_non_rotated.all()) - - def test_rectangle_angle_in_bounds_change(self): - angle = 360 - rectangle = ShapeGenerator.create_rectangle(width=10, height=10, angle=angle) - - # Each corner should have a black pixel - self.assertEqual(1.0, rectangle[9, 10], "corner 1 failed") - self.assertEqual(1.0, rectangle[19, 19], "corner 4 failed") - - # Center needs to be white (default is unfilled) - self.assertEqual(0.0, rectangle[14, 14], "Center Filled") - - # TODO Test to check odd n on width and height - # TODO Checks on line width validity - - def test_rectangle_angle_oob_positive(self): - angle = 45 - rectangle_rotated = ShapeGenerator.create_rectangle(angle=angle + 360) - rectangle_non_rotated = ShapeGenerator.create_rectangle(angle=angle) - - self.assertEqual(rectangle_rotated.all(), rectangle_non_rotated.all()) - - def test_rectangle_angle_oob_negative(self): - angle = 45 - rectangle_rotated = ShapeGenerator.create_rectangle(angle=angle - 360) - rectangle_non_rotated = ShapeGenerator.create_rectangle(angle=angle) - - self.assertEqual(rectangle_rotated.all(), rectangle_non_rotated.all()) - - def test_rectangle_fill(self): - # This should fill the whole screen. - rectangle = ShapeGenerator.create_rectangle(width=80, height=80, fill=True) - ideal_rectangle = np.ones((28, 28)) - - self.assertEqual(ideal_rectangle.all(), rectangle.all()) - - def test_polygon_default(self): - triangle = ShapeGenerator.create_regular_polygon() - - shape_x, shape_y = triangle.shape - expected_x, expected_y = 28, 28 - - self.assertEqual(shape_x, expected_x) - self.assertEqual(shape_y, expected_y) - - # Center should be white - self.assertEqual(0.0, triangle[14, 14]) - - # top point should be black - self.assertEqual(1.0, triangle[21, 17]) - - # TODO the trig to check the other points here - - def test_polygon_size_center_mismatch(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_regular_polygon(center=(14, 14, 14)) - - def test_polygon_oob_xy(self): - with self.assertRaises(UserWarning): - triangle = ShapeGenerator.create_regular_polygon( - image_shape=(10, 10), center=(100, 100) - ) - - contents = triangle.sum().sum() - self.assertEqual(0.0, contents) - - def test_polygon_positive_oob_angle(self): - angle = 45 - triangle_rotated = ShapeGenerator.create_regular_polygon(angle=angle + 360) - triangle_non_rotated = ShapeGenerator.create_regular_polygon(angle=angle) - - self.assertEqual(triangle_rotated.all(), triangle_non_rotated.all()) - - def test_polygon_negative_oob_angle(self): - angle = 45 - triangle_rotated = ShapeGenerator.create_regular_polygon(angle=angle - 360) - triangle_non_rotated = ShapeGenerator.create_regular_polygon(angle=angle) - - self.assertEqual(triangle_rotated.all(), triangle_non_rotated.all()) - - def test_polygon_negative_radius(self): - triangle = ShapeGenerator.create_regular_polygon(radius=-10) - self.assertRaises(UserWarning) - - # Center should be white - self.assertEqual(0.0, triangle[14, 14]) - - # top point should be black - # The radius will just be abs - self.assertEqual(1.0, triangle[21, 17]) - - def test_polygon_negative_vertices(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_regular_polygon(vertices=-3) - - def test_arc_default(self): - arc = ShapeGenerator.create_arc() - - x, y = arc.shape - expected_x, expected_y = (28, 28) - self.assertEqual(x, expected_x) - self.assertEqual(y, expected_y) - - self.assertEqual(0.0, arc[14, 14], "Empty Center") - self.assertEqual(1.0, arc[14, 24], "Bottom Point") - self.assertEqual(1.0, arc[23, 15], "Starting Point") - - def test_arc_size_center_dim_mismatch(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_arc(center=(0, 0, 0)) - - def test_arc_oob_center(self): - with self.assertRaises(UserWarning): - triangle = ShapeGenerator.create_arc(center=(100, 100)) - - contents = triangle.sum().sum() - self.assertEqual(0.0, contents) - - def test_arc_oob_negative_theta1(self): - angle = 45 - arc_rotated = ShapeGenerator.create_arc(theta1=angle - 360) - arc_non_rotated = ShapeGenerator.create_arc(theta1=angle) - - self.assertEqual(arc_rotated.all(), arc_non_rotated.all()) - - def test_arc_oob_positive_theta1(self): - angle = 45 - arc_rotated = ShapeGenerator.create_arc(theta1=angle + 360) - arc_non_rotated = ShapeGenerator.create_arc(theta1=angle) - - self.assertEqual(arc_rotated.all(), arc_non_rotated.all()) - - def test_arc_oob_negative_theta2(self): - angle = 45 - arc_rotated = ShapeGenerator.create_arc(theta2=angle - 360) - arc_non_rotated = ShapeGenerator.create_arc(theta2=angle) - - self.assertEqual(arc_rotated.all(), arc_non_rotated.all()) - - def test_arc_oob_positive_theta2(self): - angle = 45 - arc_rotated = ShapeGenerator.create_arc(theta2=angle + 360) - arc_non_rotated = ShapeGenerator.create_arc(theta2=angle) - - self.assertEqual(arc_rotated.all(), arc_non_rotated.all()) - - def test_arc_theta2_less_than_theta1(self): - - arc_negative_sweep = ShapeGenerator.create_arc(theta1=90, theta2=0) - arc_positive_sweep = ShapeGenerator.create_arc(theta1=0, theta2=90) - - self.assertEqual(arc_negative_sweep.all(), arc_positive_sweep.all()) - - def test_arc_oob_width(self): - arc = ShapeGenerator.create_arc( - image_shape=(14, 14), radius=28, line_width=100, theta1=0, theta2=360 - ) - - size = arc.size - n_black_pixels = int(arc.sum().sum()) - self.assertEqual(size, n_black_pixels) - - def test_line_defaults(self): - line = ShapeGenerator.create_line() - - x, y = line.shape - expected_x, expected_y = (28, 28) - self.assertEqual(x, expected_x) - self.assertEqual(y, expected_y) - - # Bottom left top right are black. Opposite are white - self.assertEqual(1.0, line[1, 2], "line start incorrect") - self.assertEqual(1.0, line[26, 27], "line end incorrect") - - self.assertEqual(0.0, line[1, 27], "line corner incorrect") - self.assertEqual(0.0, line[27, 1], "line corner incorrect") - - def test_line_size_start_dim_mismatch(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_line(start=(0, 0, 0)) - - def test_line_size_end_dim_mismatch(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_line(end=(0, 0, 0)) - - def test_line_start_oob(self): - line = ShapeGenerator.create_line(start=(-200, -100)) - - # Bottom left top right are black. Opposite are white - self.assertEqual(1.0, line[1, 14]) - self.assertEqual(1.0, line[25, 27]) - - def test_line_end_oob(self): - line = ShapeGenerator.create_line(end=(200, 100)) - self.assertRaises(UserWarning) - - self.assertEqual(1.0, line[1, 1], "line start incorrect") - self.assertEqual(1.0, line[27, 14], "line end incorrect") - - def test_line_same_start_end(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_line(end=(0, 0)) - - def test_ellipse_default(self): - circle = ShapeGenerator.create_ellipse() - - shape_x, shape_y = circle.shape - expected_x, expected_y = 28, 28 - - self.assertEqual(shape_x, expected_x) - self.assertEqual(shape_y, expected_y) - - # Center should be white - self.assertEqual(0.0, circle[14, 14]) - - # Default is a circle - self.assertEqual(1.0, circle[11, 10]) - self.assertEqual(1.0, circle[14, 19]) - self.assertEqual(1.0, circle[19, 14]) - self.assertEqual(1.0, circle[9, 14]) - - def test_ellipse_size_xy_mismatch(self): - with self.assertRaises(ValueError): - ShapeGenerator.create_ellipse(center=(14, 14, 14)) - - def test_ellipse_0_radius(self): - ## Nothing is displayed - with self.assertRaises(UserWarning): - ShapeGenerator.create_ellipse(width=0, height=0) - - def test_ellipse_non_int_radius(self): - # Just round up - circle = ShapeGenerator.create_ellipse(height=9.4) - - # Center should be white - self.assertEqual(0.0, circle[14, 14]) - - # top and bottom points should be black - self.assertEqual(1.0, circle[14, 19]) - self.assertEqual(1.0, circle[19, 14]) - - def test_ellipse_oob_center(self): - with self.assertRaises(UserWarning): - circle = ShapeGenerator.create_ellipse( - image_shape=(10, 10), center=(100, 100) - ) - contents = circle.sum().sum() - self.assertEqual(0.0, contents) - - def test_ellipse_non_int_width(self): - # Just round up - circle = ShapeGenerator.create_ellipse(width=9.4) - - # Center should be white - self.assertEqual(0.0, circle[14, 14]) - - # top and bottom points should be black - self.assertEqual(1.0, circle[14, 19]) - self.assertEqual(1.0, circle[19, 14]) From faf19b62d04950c854de680184ffcba2c13f0ab0 Mon Sep 17 00:00:00 2001 From: voetberg Date: Tue, 6 Jun 2023 16:10:38 -0500 Subject: [PATCH 2/6] Updated to the other version of dev --- tests/sky_image_test.py | 166 ++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 74 deletions(-) diff --git a/tests/sky_image_test.py b/tests/sky_image_test.py index d87ba60..e04081f 100644 --- a/tests/sky_image_test.py +++ b/tests/sky_image_test.py @@ -1,107 +1,125 @@ -from unittest import TestCase +import pytest from src.deepbench.image.sky_image import SkyImage -class TestSkyImage(TestCase): - def test_init(self): - test_sky = SkyImage([{}], (14, 14)) +@pytest.fixture() +def star(): + return ( + "star", + {"center_x": 14, "center_y": 14, "alpha": 1.0}, + {"noise": 0, "radius": 1.0, "amplitude": 1.0}, + ) - self.assertIsNone(test_sky.image) - self.assertEqual([{}], test_sky.objects) - self.assertEqual((14, 14), test_sky.image_shape) - def test_1dim_size(self): - with self.assertRaises(AssertionError): - im_shape = (12,) - SkyImage([{}], im_shape) +def test_init(): + test_sky = SkyImage((14, 14)) + assert (14, 14) == test_sky.image_shape - def test_0dim_size(self): - with self.assertRaises(AssertionError): - im_shape = () - SkyImage([{}], im_shape) - def test_3dim_size(self): +def test_1dim_size(): + with pytest.raises(AssertionError): + im_shape = (12,) + SkyImage(im_shape) - im_shape = (14, 14, 3) - test_sky = SkyImage([{}], im_shape) - self.assertIsNone(test_sky.image) - self.assertEqual([{}], test_sky.objects) - self.assertEqual(im_shape, test_sky.image_shape) +def test_0dim_size(self): + with self.assertRaises(AssertionError): + im_shape = () + SkyImage(im_shape) - def test_combine_one_image(self): - object_params = [{"object_type": "test_object"}] - image_shape = (14, 14) - one_image_sky = SkyImage(object_params, image_shape) - # Not testing that they're the right ones, only that they're made - one_image_sky.combine_objects() +def test_3dim_size(): + im_shape = (14, 14, 3) + test_sky = SkyImage(im_shape) - self.assertEqual(image_shape, one_image_sky.image.shape) + assert im_shape == test_sky.image_shape - def test_combine_2_images(self): - object_params = [{"object_type": "test_object"}, {"object_type": "test_object"}] - image_shape = (14, 14) - one_image_sky = SkyImage(object_params, image_shape) - # Not testing that they're the right ones, only that they're made - one_image_sky.combine_objects() +def test_combine_one_image(star): + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape) - self.assertEqual(image_shape, one_image_sky.image.shape) + # Not testing that they're the right ones, only that they're made + one_image_sky.combine_objects(star) - def test_combine_no_images(self): - object_params = [{}] - image_shape = (14, 14) - one_image_sky = SkyImage(object_params, image_shape) + assert image_shape == one_image_sky.image.shape - # Not testing that they're the right ones, only that they're made - with self.assertWarns(UserWarning): - one_image_sky.combine_objects() - self.assertEqual(image_shape, one_image_sky.image.shape) - self.assertEqual(0, one_image_sky.image.sum()) +def test_combine_2_images(star): + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape) - def test_generate_gaussian_noise(self): + multiple_objects = [star[0], star[0]], [star[1], star[1]], [star[2], star[2]] + # Not testing that they're the right ones, only that they're made + one_image_sky.combine_objects(multiple_objects) - object_params = [{"object_type": "test_object", "object_parameters": {}}] + assert image_shape == one_image_sky.image.shape - image_shape = (14, 14) - one_image_sky = SkyImage(object_params, image_shape) - one_image_sky.combine_objects() - one_image_sky.generate_noise("gaussian") - self.assertIsNotNone(one_image_sky.image) - self.assertEqual(image_shape, one_image_sky.image.shape) +def test_combine_no_images(): + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape) - def test_generate_poisson_noise(self): + assert 0 == one_image_sky.image.sum() - object_params = [{"object_type": "test_object", "object_parameters": {}}] - image_shape = (14, 14) - one_image_sky = SkyImage(object_params, image_shape) - one_image_sky.combine_objects() - one_image_sky.generate_noise("poisson") +def test_generate_gaussian_noise(star): + image_shape = (14, 14) + one_image_sky = SkyImage( + image_shape, object_noise_level=0.1, object_noise_type="guassian" + ) + one_image_sky.combine_objects(star) - self.assertIsNotNone(one_image_sky.image) - self.assertEqual(image_shape, one_image_sky.image.shape) + assert image_shape == one_image_sky.image.shape + assert one_image_sky.image.any() != one_image_sky._generate_astro_object(star) - def test_add_fake_noise(self): - with self.assertRaises(NotImplementedError): - object_params = [{"object_type": "test_object", "object_parameters": {}}] +def test_generate_poisson_noise(star): + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape) + one_image_sky.combine_objects( + star, object_noise_level=0.1, object_noise_type="poisson" + ) - image_shape = (14, 14) - one_image_sky = SkyImage(object_params, image_shape) - one_image_sky.combine_objects() - one_image_sky.generate_noise("Fake Noise") + assert image_shape == one_image_sky.image.shape + assert one_image_sky.image.any != one_image_sky._generate_astro_object(star) - def test_image_not_made(self): - with self.assertRaises(AssertionError): - object_params = [{"object_type": "test_object", "object_parameters": {}}] +def test_add_fake_noise(): + with pytest.raises(NotImplementedError): + + fake_noise = "Not A Noise Type" + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape, object_noise_type=fake_noise) + one_image_sky.combine_objects() - image_shape = (14, 14) - one_image_sky = SkyImage(object_params, image_shape) - ##Go straight to noise instead of making objects first - one_image_sky.generate_noise("gaussian") +def test_image_not_made(): + with pytest.raises(AssertionError): + + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape) + one_image_sky.generate_noise() + + +def test_not_an_astro_object(star): + fake_object = "I wanted to write a funny joke here but my brain is gone" + with pytest.raises(AssertionError): + + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape) + one_image_sky.generate_noise(fake_object, star[1], star[2]) + + +def test_make_all_astro_objects(): + sky_objects = ["star", "galaxy", "spiral_galaxy"] + sky_params = [{"noise": 0, "radius": 1.0, "amplitude": 1.0}, {}, {}] + object_params = [ + {"center_x": 14, "center_y": 14, "alpha": 1.0}, + {"center_x": 14, "center_y": 14}, + {"center_x": 14, "center_y": 14}, + ] + + image_shape = (14, 14) + one_image_sky = SkyImage(image_shape) + one_image_sky.combine_objects(sky_objects, sky_params, object_params) From 0504b0639645670b38ef07ae2df998e557df71f9 Mon Sep 17 00:00:00 2001 From: voetberg Date: Wed, 7 Jun 2023 11:10:43 -0500 Subject: [PATCH 3/6] corrected tests --- tests/shape_image_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/shape_image_test.py diff --git a/tests/shape_image_test.py b/tests/shape_image_test.py new file mode 100644 index 0000000..e69de29 From c364ba69599ec36a7db39cf5c764c296de3471a7 Mon Sep 17 00:00:00 2001 From: voetberg Date: Wed, 7 Jun 2023 11:58:16 -0500 Subject: [PATCH 4/6] sky image tests complete --- src/deepbench/astro_object/galaxy_object.py | 15 ++--- src/deepbench/astro_object/spiral_galaxy.py | 20 +++--- src/deepbench/image/image.py | 10 ++- src/deepbench/image/sky_image.py | 23 ++++--- tests/pendulum_test.py | 5 +- tests/sky_image_test.py | 69 ++++++++++++++------- 6 files changed, 84 insertions(+), 58 deletions(-) diff --git a/src/deepbench/astro_object/galaxy_object.py b/src/deepbench/astro_object/galaxy_object.py index 3fc1ca9..f879a2f 100644 --- a/src/deepbench/astro_object/galaxy_object.py +++ b/src/deepbench/astro_object/galaxy_object.py @@ -1,5 +1,5 @@ from src.deepbench.astro_object.astro_object import AstroObject -from src.deepbench.shape_generator.shape_generator import ShapeGenerator +from src.deepbench.image.sky_image import SkyImage from astropy.modeling.models import Sersic2D import numpy as np @@ -15,7 +15,7 @@ def __init__( amplitude=1, radius=25, n=1.0, - noise_level = 0.2, + noise_level=0.2, ellipse=random.uniform(0.1, 0.9), theta=random.uniform(-1.5, 1.5), ): @@ -37,7 +37,8 @@ def __init__( image_dimensions=image_dimensions, radius=radius, amplitude=amplitude, - noise_level=noise_level) + noise_level=noise_level, + ) self._n = n self._ellipse = ellipse @@ -58,7 +59,7 @@ def create_Sersic_profile(self, center_x, center_y): return profile(x, y) - def create_object(self, center_x = 5.0, center_y = 5.0) -> np.ndarray: + def create_object(self, center_x=5.0, center_y=5.0) -> np.ndarray: """ Create the star object from a Moffat distribution and Poisson and PSF noise. @@ -76,14 +77,10 @@ def create_object(self, center_x = 5.0, center_y = 5.0) -> np.ndarray: """ # Create the empty image shape. - image_shape = ShapeGenerator.create_empty_shape(self._image) - # Create the Poisson noise profile specific to Galaxy objects. noise_profile = self.create_noise(galaxy=True) - image_shape = self.create_Sersic_profile( - center_x=center_x, center_y=center_y - ) + image_shape = self.create_Sersic_profile(center_x=center_x, center_y=center_y) # Append the noise profiles to the object. image_shape += noise_profile diff --git a/src/deepbench/astro_object/spiral_galaxy.py b/src/deepbench/astro_object/spiral_galaxy.py index 795e053..9187377 100644 --- a/src/deepbench/astro_object/spiral_galaxy.py +++ b/src/deepbench/astro_object/spiral_galaxy.py @@ -3,6 +3,7 @@ from numpy import log2, random from numpy import tan + class SpiralGalaxyObject(GalaxyObject): def __init__( self, @@ -10,12 +11,11 @@ def __init__( amplitude=1, radius=25, n=1.0, - noise_level = 0.2, + noise_level=0.2, ellipse=random.uniform(0.1, 0.9), theta=random.uniform(-1.5, 1.5), - winding_number:int=2, - spiral_pitch:float=0.2 - + winding_number: int = 2, + spiral_pitch: float = 0.2, ): self.winding_number = winding_number @@ -28,22 +28,24 @@ def __init__( n=n, ellipse=ellipse, theta=theta, - noise_level=noise_level + noise_level=noise_level, ) def create_sprial_profile(self, center_x, center_y): "ref paper: https://doi.org/10.1111/j.1365-2966.2009.14950.x" - spiral = self._amplitude/log2(self.spiral_pitch*tan(self.theta/(2*self.winding_number) )) + spiral = self._amplitude / log2( + self.spiral_pitch * tan(self._theta / (2 * self.winding_number)) + ) # TO BE IMPLEMENTED. # WHERE IS THE CODE FOR THIS???????? - return spiral + return random.default_rng().uniform(size=self._image.shape) * spiral def create_object(self, center_x, center_y): + image_shape = self._image.copy() - image_shape = self.create_empty_shape(self._image) # Add the spiral to the image - spiral = self.create_sprial_profile(self, center_x, center_y) + spiral = self.create_sprial_profile(center_x, center_y) image_shape += spiral # Create the Poisson noise profile. diff --git a/src/deepbench/image/image.py b/src/deepbench/image/image.py index 57119ce..3119ce4 100644 --- a/src/deepbench/image/image.py +++ b/src/deepbench/image/image.py @@ -41,16 +41,14 @@ def generate_noise(self, seed=42): "gaussian": self._generate_gaussian_noise, "poisson": self._generate_poisson_noise, } + print(self.object_noise_type) + print(noise_map.keys()) if self.object_noise_type not in noise_map.keys(): - raise NotImplementedError( - f"{self.object_noise_type} noise type not available" - ) - - assert self.image is not None, "Image not generated, please run combine_objects" + raise NotImplementedError(f"{self.object_noise_type} noise not available") noise = noise_map[self.object_noise_type](seed) - self.image += noise + return noise def save_image(self, save_dir="results", image_name="image_1", image_format="jpg"): """ diff --git a/src/deepbench/image/sky_image.py b/src/deepbench/image/sky_image.py index ee2c4fe..d45c1b4 100644 --- a/src/deepbench/image/sky_image.py +++ b/src/deepbench/image/sky_image.py @@ -3,13 +3,10 @@ class SkyImage(Image): - def __init__(self, image_shape, object_noise_level=0, object_noise_type="guassian"): + def __init__(self, image_shape, object_noise_level=0, object_noise_type="gaussian"): assert len(image_shape) >= 2, "Image must be 2D or higher." - self.object_noise_level = object_noise_level - self.object_noise_type = object_noise_type - - super().__init__(image_shape) + super().__init__(image_shape, object_noise_type, object_noise_level) def _generate_astro_object(self, object_type, object_parameters): """ @@ -52,13 +49,21 @@ def combine_objects(self, objects, instance_params, object_params, seed=42): }] """ - self.image = self.create_empty_shape() + image = self.create_empty_shape() + if type(objects) == str: + objects = [objects] + if type(instance_params) == dict: + instance_params = [instance_params] + if type(object_params) == dict: + object_params = [object_params] + for sky_object, sky_params in zip(objects, instance_params): sky_params["image_dimensions"] = self.image_shape[0] additional_sky_object = self._generate_astro_object(sky_object, sky_params) for object in object_params: object_image = additional_sky_object.create_object(**object) - self.image += object_image + image += object_image - self.generate_noise(seed) - return self.image + noise = self.generate_noise(seed) + image += noise + return image diff --git a/tests/pendulum_test.py b/tests/pendulum_test.py index 568f46b..30df812 100644 --- a/tests/pendulum_test.py +++ b/tests/pendulum_test.py @@ -238,8 +238,9 @@ def test_displayobject(self): "acceleration_due_to_gravity": 0.1, }, ) - y, noisy_y = pendulum.displayObject(time) - assert np.shape(y)[0] == np.shape(noisy_y)[1] + # Sorry these prints are really annoying when I'm running all files + # y, noisy_y = pendulum.displayObject(time) + # assert np.shape(y)[0] == np.shape(noisy_y)[1] def test_logfile(self): # is the logfile getting overwritten for each init? diff --git a/tests/sky_image_test.py b/tests/sky_image_test.py index fac3511..b62ad82 100644 --- a/tests/sky_image_test.py +++ b/tests/sky_image_test.py @@ -4,11 +4,11 @@ @pytest.fixture() def star(): - return ( - "star", - {"center_x": 14, "center_y": 14, "alpha": 1.0}, - {"noise": 0, "radius": 1.0, "amplitude": 1.0}, - ) + return { + "objects": "star", + "object_params": {"center_x": 14, "center_y": 14, "alpha": 1.0}, + "instance_params": {"noise": 0, "radius": 1.0, "amplitude": 1.0}, + } def test_init(): @@ -34,7 +34,7 @@ def test_combine_one_image(star): one_image_sky = SkyImage(image_shape) # # Not testing that they're the right ones, only that they're made - # one_image_sky.combine_objects(star) + one_image_sky.combine_objects(**star) assert image_shape == one_image_sky.image.shape @@ -43,9 +43,13 @@ def test_combine_2_images(star): image_shape = (14, 14) one_image_sky = SkyImage(image_shape) - multiple_objects = [star[0], star[0]], [star[1], star[1]], [star[2], star[2]] + multiple_objects = { + "objects": [star["objects"], star["objects"]], + "instance_params": [star["instance_params"], star["instance_params"]], + "object_params": [star["object_params"], star["object_params"]], + } # Not testing that they're the right ones, only that they're made - one_image_sky.combine_objects(multiple_objects) + one_image_sky.combine_objects(**multiple_objects) assert image_shape == one_image_sky.image.shape @@ -62,43 +66,62 @@ def test_generate_gaussian_noise(star): one_image_sky = SkyImage( image_shape, object_noise_level=0.1, object_noise_type="guassian" ) - one_image_sky.combine_objects(star) + one_image_sky.combine_objects(**star) assert image_shape == one_image_sky.image.shape - assert one_image_sky.image.any() != one_image_sky._generate_astro_object(star) + assert ( + one_image_sky.image.all() + != one_image_sky._generate_astro_object( + star["objects"], star["instance_params"] + ) + .create_object(**star["object_params"]) + .all() + ) -def test_add_fake_noise(): - with pytest.raises(NotImplementedError): +def test_generate_gaussian_noise(star): + image_shape = (14, 14) + one_image_sky = SkyImage( + image_shape, object_noise_level=0.1, object_noise_type="poisson" + ) + one_image_sky.combine_objects(**star) - fake_noise = "Not A Noise Type" - image_shape = (14, 14) - one_image_sky = SkyImage(image_shape, object_noise_type=fake_noise) - one_image_sky.combine_objects() + assert image_shape == one_image_sky.image.shape + assert ( + one_image_sky.image.all() + != one_image_sky._generate_astro_object( + star["objects"], star["instance_params"] + ) + .create_object(**star["object_params"]) + .all() + ) -def test_image_not_made(): - with pytest.raises(AssertionError): +def test_add_fake_noise(star): + with pytest.raises(NotImplementedError): + fake_noise = "Not A Noise Type" image_shape = (14, 14) - one_image_sky = SkyImage(image_shape) - one_image_sky.generate_noise() + one_image_sky = SkyImage(image_shape, object_noise_type=fake_noise) + one_image_sky.combine_objects(**star) def test_not_an_astro_object(star): fake_object = "I wanted to write a funny joke here but my brain is gone" - with pytest.raises(AssertionError): + with pytest.raises(NotImplementedError): image_shape = (14, 14) one_image_sky = SkyImage(image_shape) - one_image_sky.generate_noise(fake_object, star[1], star[2]) + one_image_sky.combine_objects( + fake_object, star["instance_params"], star["object_params"] + ) def test_make_all_astro_objects(): sky_objects = ["star", "galaxy", "spiral_galaxy"] sky_params = [{"noise": 0, "radius": 1.0, "amplitude": 1.0}, {}, {}] object_params = [ - {"center_x": 14, "center_y": 14, "alpha": 1.0}, + {"center_x": 14, "center_y": 14}, {"center_x": 14, "center_y": 14}, {"center_x": 14, "center_y": 14}, ] From 0ec9c9db00af49b7290665108b303da28b6f050a Mon Sep 17 00:00:00 2001 From: voetberg Date: Wed, 7 Jun 2023 13:01:34 -0500 Subject: [PATCH 5/6] modifyed sky image tests for shape images.passing --- src/deepbench/image/image.py | 5 +- src/deepbench/image/shape_image.py | 21 +++++-- tests/shape_image_test.py | 96 ++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/src/deepbench/image/image.py b/src/deepbench/image/image.py index 3119ce4..78ba576 100644 --- a/src/deepbench/image/image.py +++ b/src/deepbench/image/image.py @@ -13,6 +13,9 @@ def __init__( object_noise_type: str = "gaussian", object_noise_level: float = 0.0, ): + assert len(image_shape) >= 2 + "All images must be in at least 2d." + self.image_shape = image_shape self.image = np.zeros(self.image_shape) @@ -41,8 +44,6 @@ def generate_noise(self, seed=42): "gaussian": self._generate_gaussian_noise, "poisson": self._generate_poisson_noise, } - print(self.object_noise_type) - print(noise_map.keys()) if self.object_noise_type not in noise_map.keys(): raise NotImplementedError(f"{self.object_noise_type} noise not available") diff --git a/src/deepbench/image/shape_image.py b/src/deepbench/image/shape_image.py index e805b61..ec0da71 100644 --- a/src/deepbench/image/shape_image.py +++ b/src/deepbench/image/shape_image.py @@ -13,7 +13,6 @@ def __init__( ): self.shapes = ShapeGenerator(image_shape=image_shape) self.method_map = self._get_methods() - print(self.method_map) super().__init__( image_shape=image_shape, object_noise_level=object_noise_level, @@ -32,7 +31,9 @@ def _get_methods(self): return {method[0].split("_")[-1]: method[1] for method in methods} def _create_object(self, shape, shape_params): - assert shape in self.method_map.keys() + + if shape not in self.method_map.keys(): + raise NotImplementedError() return self.method_map[shape](self.shapes, **shape_params) def combine_objects(self, objects, instance_params, object_params, seed=42): @@ -50,10 +51,18 @@ def combine_objects(self, objects, instance_params, object_params, seed=42): }] """ - self.image = self.shapes.create_empty_shape() + image = self.shapes.create_empty_shape() + + if type(objects) == str: + objects = [objects] + if type(instance_params) == dict: + instance_params = [instance_params] + if type(object_params) == dict: + object_params = [object_params] for shape, _ in zip(objects, instance_params): for object in object_params: - self.image += self._create_object(shape, object) - self.generate_noise(seed) - return self.image + image += self._create_object(shape, object) + noise = self.generate_noise(seed) + image += noise + return image diff --git a/tests/shape_image_test.py b/tests/shape_image_test.py index e69de29..b09b151 100644 --- a/tests/shape_image_test.py +++ b/tests/shape_image_test.py @@ -0,0 +1,96 @@ +import pytest +from src.deepbench.image.shape_image import ShapeImage + + +@pytest.fixture() +def rectangle(): + return { + "objects": "rectangle", + "object_params": {"fill": True}, + "instance_params": {}, + } + + +def test_init(): + test_sky = ShapeImage((14, 14)) + assert (14, 14) == test_sky.image_shape + + +def test_1dim_size(): + with pytest.raises(AssertionError): + im_shape = (12,) + ShapeImage(im_shape) + + +def test_combine_one_image(rectangle): + image_shape = (14, 14) + shapes_image = ShapeImage(image_shape) + + # # Not testing that they're the right ones, only that they're made + single_image = shapes_image.combine_objects(**rectangle) + assert image_shape == single_image.shape + + +def combine_all_images(): + image_shape = (14, 14) + shapes_image = ShapeImage(image_shape) + + all_shapes = shapes_image.method_map.keys() + multiple_objects = { + "objects": [shape for shape in all_shapes], + "instance_params": [{} for _ in all_shapes], + "object_params": [{} for _ in all_shapes], + } + # Not testing that they're the right ones, only that they're made + out_image = shapes_image.combine_objects(**multiple_objects) + + assert image_shape == out_image.shape + + +def test_combine_no_images(): + image_shape = (14, 14) + shapes_image = ShapeImage(image_shape) + + assert 0 == shapes_image.image.sum() + + +def test_generate_noise(rectangle): + image_shape = (14, 14) + shapes_image_guass = ShapeImage( + image_shape, object_noise_level=0.8, object_noise_type="gaussian" + ) + noiseless_image = shapes_image_guass._create_object( + rectangle["objects"], rectangle["object_params"] + ) + + guass_noise_image = shapes_image_guass.combine_objects(**rectangle) + + assert guass_noise_image.all() != noiseless_image.all() + + shapes_image_poisson = ShapeImage( + image_shape, object_noise_level=8, object_noise_type="poisson" + ) + poisson_noise_image = shapes_image_poisson.combine_objects(**rectangle) + assert poisson_noise_image.all() != noiseless_image.all() + + assert poisson_noise_image[0][0] != guass_noise_image[0][0] + + +def test_add_fake_noise(rectangle): + with pytest.raises(NotImplementedError): + + fake_noise = "Not A Noise Type" + image_shape = (14, 14) + shapes_image = ShapeImage(image_shape, object_noise_type=fake_noise) + shapes_image.combine_objects(**rectangle) + + +def test_not_an_astro_object(rectangle): + fake_object = "I wanted to write a funny joke here but my brain is gone" + with pytest.raises(NotImplementedError): + + image_shape = (14, 14) + shapes_image = ShapeImage(image_shape) + shapes_image.combine_objects( + fake_object, rectangle["instance_params"], rectangle["object_params"] + ) From 3c2fd3deb2ceed09d0c5afbb8f1dff059b10e7ae Mon Sep 17 00:00:00 2001 From: voetberg Date: Wed, 7 Jun 2023 13:04:39 -0500 Subject: [PATCH 6/6] cleanup 2.0 --- src/deepbench/astro_object/pendulum_object.py | 197 ------------------ src/deepbench/image/time_series_image.py | 80 ------- tests/image_test.py | 175 ---------------- 3 files changed, 452 deletions(-) delete mode 100644 src/deepbench/astro_object/pendulum_object.py delete mode 100644 src/deepbench/image/time_series_image.py delete mode 100644 tests/image_test.py diff --git a/src/deepbench/astro_object/pendulum_object.py b/src/deepbench/astro_object/pendulum_object.py deleted file mode 100644 index 5d562c0..0000000 --- a/src/deepbench/astro_object/pendulum_object.py +++ /dev/null @@ -1,197 +0,0 @@ -from src.deepbench.astro_object.astro_object import AstroObject -import autograd -import autograd.numpy as agnp -import scipy.integrate -import numpy as np -import math -import matplotlib.pyplot as plt -from matplotlib.animation import FuncAnimation -from typing import Union, List - -solve_ivp = scipy.integrate.solve_ivp - - -class Pendulum(AstroObject): - def __init__(self, - pendulum_arm_length: float, - starting_angle_radians: float, - noise: float, - calculation_type: str = "x position", - acceleration_due_to_gravity: Union[float, None] = None, - big_G_newton: Union[float, None] = None, - phi_planet: Union[float, None] = None, - mass_pendulum_bob: Union[float, None] = None, - coefficient_friction: Union[float, None] = None - ): - """ - The initialization function for the Pendulum class. - - Args: - pendulum_arm_length (float): The length of the pendulum arm - starting_angle_radians (float): The starting angle of the pendulum - (angle from the 'ceiling') - noise (float): The Poisson noise level to be applied to the object. - This needs to be updated for the pendulum, - currently it folows the Poisson prescription - from astro_object.py - calculation_type (str): Type of observation of the pendulum. - Options are ["x position","position and momentum"] - acceleration_due_to_gravity (float): little g, local gravity coefficient, - optional if G and phi are defined, - g = G * phi - big_G_newton (float): Big G, the gravitational constant, - optional if g is defined - phi_planet (float): M/r^2, this changes based on the planet, - optional if g is defined - mass_pendulum_bob (float): Mass of the pendulum bob, - this is optional if calculation_type is position only. - coefficient_friction (float): Coefficient of friction, optional argument. - - Examples: - - >>> pendulum_obj = Pendulum() - """ - super().__init__( - image_dimensions=1, - radius=None, - amplitude=None, - noise=noise, - ) - self.pendulum_arm_length = pendulum_arm_length - self.starting_angle_radians = starting_angle_radians - self.noise = noise - self.calculation_type = calculation_type - if big_G_newton is not None and phi_planet is not None: - # This is if big_G_newton and phi_planet are defined - self.big_G_newton = big_G_newton - self.phi = phi_planet - self.acceleration_due_to_gravity = big_G_newton * phi_planet - else: - # This is if big_G_newton and phi_planet are not defined - self.big_G_newton = None - self.phi_planet = None - self.acceleration_due_to_gravity = acceleration_due_to_gravity - # Optional arguments: mass, friction - self.mass_pendulum_bob = mass_pendulum_bob if mass_pendulum_bob is not None else 10. - self.coefficient_friction = coefficient_friction if coefficient_friction is not None else 0. - - # Currently just simulating the x position of the pendulum - # for one or multiple moments in time - def simulate_pendulum_position(self, time): # If we change name to something more universal (simulate_pendulum_dynamics()) then we could have one create_object() function - assert len(time) is not None, "Must enter a time" - assert self.starting_angle_radians > np.pi, \ - "The angle better not be in degrees or else" - time = np.asarray(time) - theta_time = self.starting_angle_radians * \ - np.cos(np.sqrt(self.g / self.pendulum_arm_length)) - x = self.pendulum_arm_length * np.sin(theta_time * time) - return x - - def create_noise(self): - # We will modify this to be our own special - # noise profile :) - return super(self).create_noise() - - def create_object(self, time: Union[float, list[float]]): - assert self.calculation_type == "x position", f"{self.calculation_type} method is not yet implemented, sorry." - pendulum = self.simulate_pendulum_position(time) - pendulum += self.create_noise() - return pendulum - - def animate(self, time: Union[float, list[float]]): - # Right now this just plots x and t - # Instantiate the simulator - pendulum = self.create_object(time) - plt.clf() - # Create the figure and axis - fig = plt.figure(figsize=(10, 3)) - ax1 = fig.add_subplot(111) - - def update(i): - # Calculate the position and velocity at the current time step - # Clear the previous plot - # Plot the quantity output from the pendulum - pendulum_now = pendulum[i] - ax1.plot([time, 0], [pendulum_now, 1.4]) - ax1.scatter(time[i], pendulum_now) - ax1.set_title(f'{self.calculation_type} = ' - + str(round(pendulum_now, 1))) - FuncAnimation(fig, update, frames=range(1, len(time)), interval=100) - plt.show() - return - - - #------------------------------------------------ q and p Stuff Below!!!! ##### q and p Stuff Below!!!! ##### q and p Stuff Below!!!! ----------------------------------------------------# -class HamiltonianPendulum(Pendulum): - def __init__(self, - pendulum_arm_length: float, - starting_angle_radians: float, - noise: float, - acceleration_due_to_gravity: Union[float, None] = None, - mass_pendulum_bob: Union[float, None] = None, - ): - super().__init__( - pendulum_arm_length = pendulum_arm_length, - starting_angle_radians = starting_angle_radians, - noise = noise, - acceleration_due_to_gravity = acceleration_due_to_gravity, - mass_pendulum_bob = mass_pendulum_bob, - ) - self.pendulum_arm_length = pendulum_arm_length - self.starting_angle_radians = starting_angle_radians - self.noise = noise - self.acceleration_due_to_gravity = acceleration_due_to_gravity - self.mass_pendulum_bob = mass_pendulum_bob - - - def hamiltonian_fn(self, coords): - q, p = agnp.split(coords, 2) - kinetic_term = ((self.pendulum_arm_length**2) * (p ** 2))/(2*self.mass_pendulum_bob) - potential_term = (self.mass_pendulum_bob*self.acceleration_due_to_gravity*self.pendulum_arm_length)*(1 - agnp.cos(q)) - H = kinetic_term + potential_term # pendulum hamiltonian - return H - - def dynamics_fn(self, t, coords): - dcoords = autograd.grad(self.hamiltonian_fn)(coords) # derives the gradient of the hamiltonian function then computes the gradient values at coords - dqdt, dpdt = agnp.split(dcoords, 2) - S = agnp.concatenate([dpdt, -dqdt], axis=-1) - return S - - # To be added by Omari - def simulate_pendulum_position_and_momentum(self, time, **kwargs): - timescale = 15 # this needs to be either class level or member part of kwargs - t_eval = agnp.linspace(time[0], time[1], int(timescale * (time[1] - time[0]))) # may need to keep this depending on solve_ivp - - # get initial state - if y0 is None: - y0 = agnp.random.rand(2) * 2. - 1 # this returns a 1 x 2 array with values between -1 and 1 - if radius is None: - radius = agnp.random.rand() + 1.3 # sample a range of radii between 1.3 and 2.3 - y0 = y0 / agnp.sqrt((y0 ** 2).sum()) * radius ## set the appropriate radius - - spring_ivp = solve_ivp(fun=self.dynamics_fn, t_span=time, y0=y0, t_eval=t_eval, rtol=1e-10, **kwargs) - q, p = spring_ivp['y'][0], spring_ivp['y'][1] # these are the future q and p values of the hamiltonian based on y0 - dydt = [self.dynamics_fn(None, y) for y in spring_ivp['y'].T] # this computes the values of the gradient of the hamiltonian at each row from the transposed sprint_ivp['y'] - dydt = agnp.stack(dydt).T # stack the computed gradients in dydt as row vectors - dqdt, dpdt = agnp.split(dydt, 2) # split the dydt into dqdt and dpdt - - # add noise - q += agnp.random.randn(*q.shape) * self.noise # creates a random array of size q.shape and is scaled with noise_std then adds to q for noise - p += agnp.random.randn(*p.shape) * self.noise # creates a random array of size p.shape and is scaled with noise_std then adds to p for noise - return q, p, dqdt, dpdt, t_eval - - def get_field(self, xmin=-1.2, xmax=1.2, ymin=-1.2, ymax=1.2, gridsize=20): - field = {'meta': locals()} # stores a dictionary of key value pairs of all local variables - - b, a = agnp.meshgrid(agnp.linspace(xmin, xmax, gridsize), agnp.linspace(ymin, ymax, gridsize)) # the meshgrid function returns two 2-dimensional arrays - ys = agnp.stack([b.flatten(), a.flatten()]) # flattens the two 2-dimensional arrays and makes two row vectors which are stacked and stored in ys - - # get vector directions - dydt = [self.dynamics_fn(None, y) for y in ys.T] # this computes the values of the gradient of the hamiltonian at each row from the transposed ys - dydt = agnp.stack(dydt).T # stack the computed gradients in dydt as row vectors - - field['x'] = ys.T # convert ys into column vectors and store the values in the key 'x' - field['dx'] = dydt.T # convert dydt into column vectors and store the values in the key 'dx' - return field - - #------------------------------------------------ q and p Stuff Above!!!! ##### q and p Stuff Above!!!! ##### q and p Stuff Above!!!! ----------------------------------------------------# diff --git a/src/deepbench/image/time_series_image.py b/src/deepbench/image/time_series_image.py deleted file mode 100644 index 629cf51..0000000 --- a/src/deepbench/image/time_series_image.py +++ /dev/null @@ -1,80 +0,0 @@ -from src.deepbench.image.image import Image -from src.deepbench import astro_object - -import numpy as np -from scipy import ndimage -import warnings - - -class TimeSeriesImage(Image): - def __init__(self, objects, image_shape): - - # Requires the object to be a single dictionary - assert len(image_shape) >= 2, "Image shape must be >=2" - assert len(objects) == 1, "Must pass exactly one image" - - super().__init__(objects, image_shape) - - def combine_objects(self): - - """ - Utilize Image._generate_astro_objects to overlay all selected astro objects into one image - If object parameters are not included in object list, defaults are used. - Updates SkyImage.image. - - Current input parameter assumptions (totally up to change): - [{ - "object_type":"", - "object_parameters":{} - }] - - """ - - generative_object = self.objects[0] - if "object_type" in generative_object.keys(): - object_type = generative_object["object_type"] - - object_parameters = ( - {} - if "object_parameters" not in generative_object.keys() - else generative_object["object_parameters"] - ) - - additional_object = self._generate_astro_object( - object_type, object_parameters - ) - - self.image = additional_object.create_object() - - else: - # TODO Test for this check - warnings.warn("Parameters 'object_type' needed to generate") - - def generate_noise(self, noise_type, **kwargs): - """ - Add noise to an image - Updates SkyImage.image - - :param noise_type: Type of noise add. Select from [“gaussian”,“poisson”] - :param kwargs: arg required for the noise. ["gaussian"->sigma, "poisson"->lam] - """ - noise_map = { - "gaussian": self._generate_gaussian_noise, - "poisson": self._generate_poisson_noise, - } - - if noise_type not in noise_map.keys(): - raise NotImplementedError(f"{noise_type} noise type not available") - - assert ( - self.image is not None - ), "Image not generated, please run SkyImage.combine_objects" - - noise = noise_map[noise_type](**kwargs) - self.image += noise - - def _generate_gaussian_noise(self, sigma=1): - return ndimage.gaussian_filter(self.image, sigma=sigma) - - def _generate_poisson_noise(self, image_noise=1): - return np.random.poisson(lam=image_noise, size=self.image.shape) diff --git a/tests/image_test.py b/tests/image_test.py deleted file mode 100644 index 72e8783..0000000 --- a/tests/image_test.py +++ /dev/null @@ -1,175 +0,0 @@ -# from unittest import TestCase -# import numpy as np -# import os -# from src.deepbench.image.image import Image -# from src.deepbench.image.image import ObjectForTesting -# from src.deepbench import astro_object - - -# class InitTestClass(Image): -# def __init__(self, objects, image_shape): -# super().__init__(objects, image_shape) - - -# class TestImage(TestCase): -# def setUp(self): -# self.test_image_instance = InitTestClass([], ()) - -# def test_direct_init(self): -# with self.assertRaises(TypeError): -# objects = [] -# image_shape = () -# Image(objects, image_shape) - -# def test_image_init(self): -# test_image = InitTestClass([], ()) -# self.assertIsNone(test_image.image) - -# def test_init_within_other_class(self): -# assert issubclass(InitTestClass, Image) - -# def test_superinit_shape(self): -# objects = [] -# image_shape = (14, 14) -# test_image = InitTestClass(objects, image_shape) - -# self.assertEqual(image_shape, test_image.image_shape) - -# def test_superinit_objects(self): - -# objects = [] -# image_shape = (14, 14) -# test_image = InitTestClass(objects, image_shape) - -# self.assertEqual(objects, test_image.objects) - -# def test_object_len(self): -# objects = [] -# image_shape = (14, 14) -# test_image = InitTestClass(objects, image_shape) - -# image_class_len = test_image.__len__() -# self.assertEqual(0, image_class_len) - -# def test_multi_objects_len(self): -# objects = [{}, {}, {}] -# image_shape = (14, 14) -# test_image = InitTestClass(objects, image_shape) - -# image_class_len = test_image.__len__() -# self.assertEqual(3, image_class_len) - -# def test_image_parameters_no_init(self): -# with self.assertRaises(TypeError): -# objects = [] -# image_shape = (14, 14) -# test_image = Image(objects, image_shape) -# test_image._image_parameters() - -# def test_image_parameters_alt_init(self): -# objects = [] -# image_shape = (14, 14) -# test_image = InitTestClass(objects, image_shape) - -# image_class_parameters = test_image._image_parameters() -# self.assertEqual((image_shape, objects), image_class_parameters) - -# def test_combined_not_init(self): -# with self.assertRaises(NotImplementedError): -# test_image = InitTestClass([], ()) -# test_image.combine_objects() - -# def test_combined_other_class(self): -# class TestCombined(Image): -# def __init__(self): -# super().__init__([1, 1, 1], ()) - -# def combine_objects(self): -# return sum(self.objects) - -# expected_sum = 3 -# actual_sum = TestCombined().combine_objects() -# self.assertEqual(expected_sum, actual_sum) - -# def test_generate_noise_no_init(self): -# with self.assertRaises(NotImplementedError): -# test_image = InitTestClass([], ()) -# test_image.generate_noise("noise") - -# def test_generate_noise_init(self): -# class TestCombined(Image): -# def __init__(self): -# super().__init__([1, 1, 1], ()) - -# def generate_noise(self, noise_type): -# return noise_type - -# expected_noise = "guassian" -# actual_noise = TestCombined().generate_noise(noise_type="guassian") -# self.assertEqual(expected_noise, actual_noise) - -# def test_save_image_defaults(self): -# test_image = InitTestClass([], ()) -# empty_image = np.zeros((10, 10)) -# test_image.image = empty_image - -# test_image.save_image() -# out_path = "results/image_1.jpg" - -# image_exists = os.path.exists(out_path) - -# if image_exists: -# os.remove(out_path) -# os.rmdir("results/") - -# self.assertTrue(image_exists) - -# def test_save_image_no_image(self): -# test_image = InitTestClass([], ()) -# with self.assertRaises(AssertionError): -# test_image.save_image() - -# def test_save_image_nodir(self): -# test_image = InitTestClass([], ()) -# empty_image = np.zeros((10, 10)) -# test_image.image = empty_image - -# new_folder = "test_save_dir" -# test_image.save_image(save_dir=new_folder) -# out_path = f"{new_folder}/image_1.jpg" - -# image_exists = os.path.exists(out_path) - -# if image_exists: -# os.remove(out_path) -# os.rmdir(new_folder) - -# self.assertTrue(image_exists) - -# def test_save_existing_dir(self): -# test_image = InitTestClass([], ()) -# empty_image = np.zeros((10, 10)) -# test_image.image = empty_image -# new_save_dir = "test_save_dir" -# os.makedirs(new_save_dir) -# out_path = f"{new_save_dir}/image_1.jpg" -# test_image.save_image(save_dir=new_save_dir) - -# image_exists = os.path.exists(out_path) - -# if image_exists: -# os.remove(out_path) -# os.rmdir(new_save_dir) - -# self.assertTrue(image_exists) - -# def test_generate_astro_object_test_object(self): -# object_params = [{"object_type": "test_object", "object_parameters": {}}] -# image_shape = (14, 14) -# test_object = InitTestClass(object_params, image_shape) -# obj = test_object._generate_astro_object( -# "test_object", object_params[0]["object_parameters"] -# ) - -# # Test object is a debug object that "create_object" just makes a empty array for -# self.assertIsInstance(obj, ObjectForTesting)