From d9d49534569c0c5a707069e360d89c98e7d2643b Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Mon, 23 Sep 2024 16:15:34 +0800 Subject: [PATCH 1/3] Add club target shape. --- src/data_morph/shapes/factory.py | 1 + src/data_morph/shapes/points.py | 83 ++++++++++++++++++++++++++++++++ tests/shapes/test_points.py | 17 +++++++ 3 files changed, 101 insertions(+) diff --git a/src/data_morph/shapes/factory.py b/src/data_morph/shapes/factory.py index 2b2db2af..75623e2a 100644 --- a/src/data_morph/shapes/factory.py +++ b/src/data_morph/shapes/factory.py @@ -53,6 +53,7 @@ class ShapeFactory: 'rectangle': polygons.Rectangle, 'rings': circles.Rings, 'star': polygons.Star, + 'club': points.Club, } AVAILABLE_SHAPES: list[str] = sorted(_SHAPE_MAPPING.keys()) diff --git a/src/data_morph/shapes/points.py b/src/data_morph/shapes/points.py index 158be2a8..392b06de 100644 --- a/src/data_morph/shapes/points.py +++ b/src/data_morph/shapes/points.py @@ -304,3 +304,86 @@ def distance(self, x: Number, y: Number) -> int: Always returns 0 to allow for scattering of the points. """ return 0 + + +class Club(PointCollection): + """ + Class for the club shape. + + .. plot:: + :scale: 75 + :caption: + This shape is generated using the panda dataset. + + from data_morph.data.loader import DataLoader + from data_morph.shapes.points import Club + + _ = Club(DataLoader.load_dataset('panda')).plot() + + Parameters + ---------- + dataset : Dataset + The starting dataset to morph into other shapes. + """ + + def __init__(self, dataset: Dataset) -> None: + x_bounds = dataset.data_bounds.x_bounds + y_bounds = dataset.data_bounds.y_bounds + + x_shift = sum(x_bounds) / 2 + y_shift = sum(y_bounds) / 2 + scale_factor = x_bounds.range / 75 + + # params for lobes + r = 15 * scale_factor + top_lobe_y_offset = 18 * scale_factor + bottom_lobes_x_offset = 15 * scale_factor + bottom_lobes_y_offset = 9 * scale_factor + + t = np.linspace(0, (2 - 1 / 3) * np.pi, num=30) + + # top lobe + angle_offset = -1 / 3 * np.pi + x_top = r * np.cos(t + angle_offset) + y_top = r * np.sin(t + angle_offset) + top_lobe_y_offset + + # bottom left lobe + angle_offset = 1 / 3 * np.pi + x_bottom_left = r * np.cos(t + angle_offset) - bottom_lobes_x_offset + y_bottom_left = r * np.sin(t + angle_offset) - bottom_lobes_y_offset + + # bottom right lobe + angle_offset = np.pi + x_bottom_right = r * np.cos(t + angle_offset) + bottom_lobes_x_offset + y_bottom_right = r * np.sin(t + angle_offset) - bottom_lobes_y_offset + + x_lobes = [x_top, x_bottom_left, x_bottom_right] + y_lobes = [y_top, y_bottom_left, y_bottom_right] + + # params for the stem + stem_x_offset = 8 * scale_factor + stem_y_offset = 34 * scale_factor + stem_scaler = 0.35 / scale_factor + stem_x_pad = 1.5 * scale_factor + + # stem bottom + x_line = np.linspace(-stem_x_offset, stem_x_offset, num=8) + y_line = np.repeat(-stem_y_offset, 8) + + # left part of the stem + t_left = np.linspace(-(stem_x_offset - stem_x_pad), -stem_x_pad, num=6) + x_left = t_left + y_left = stem_scaler * np.power(t_left + stem_x_offset, 2) - stem_y_offset + + # right part of the stem + t_right = np.linspace(stem_x_pad, stem_x_offset - stem_x_pad, num=6) + x_right = t_right + y_right = stem_scaler * np.power(t_right - stem_x_offset, 2) - stem_y_offset + + x_stem = [x_line, x_left, x_right] + y_stem = [y_line, y_left, y_right] + + xs = x_shift + np.concatenate(x_lobes + x_stem) + ys = y_shift + np.concatenate(y_lobes + y_stem) + + super().__init__(*np.stack([xs, ys], axis=1)) diff --git a/tests/shapes/test_points.py b/tests/shapes/test_points.py index feb38a47..f156f836 100644 --- a/tests/shapes/test_points.py +++ b/tests/shapes/test_points.py @@ -142,3 +142,20 @@ class TestUpParabola(ParabolaTestBase): positive_quadratic_term = True x_index = 0 y_index = 1 + + +class TestClub(PointsModuleTestBase): + """Test the Club class.""" + + shape_name = 'club' + distance_test_cases = [ + [(19.639387, 73.783711), 0.0], # top lobe + [(12.730310, 60.295844), 0.0], # bottom left lobe + [(27.630301, 60.920443), 0.0], # bottom right lobe + [(20.304761, 55.933333), 0.0], # top of stem + [(18.8, 57.076666), 0.0], # left part of stem + [(20.933333, 57.823333), 0.0], # right part of stem + [(0, 0), 58.717591], + [(20, 50), 5.941155], + [(10, 80), 10.288055], + ] From e14f28e5470cff2ea164414ac4f2bb3681eeab21 Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Mon, 23 Sep 2024 16:56:32 +0800 Subject: [PATCH 2/3] Use the smaller axis for scale factor of club shape. --- src/data_morph/shapes/points.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data_morph/shapes/points.py b/src/data_morph/shapes/points.py index 392b06de..b3faf347 100644 --- a/src/data_morph/shapes/points.py +++ b/src/data_morph/shapes/points.py @@ -332,7 +332,7 @@ def __init__(self, dataset: Dataset) -> None: x_shift = sum(x_bounds) / 2 y_shift = sum(y_bounds) / 2 - scale_factor = x_bounds.range / 75 + scale_factor = min(x_bounds.range, y_bounds.range) / 75 # params for lobes r = 15 * scale_factor From 0e641cbe17554f95d6a7f4f72b09ac738bc83c46 Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Sun, 29 Sep 2024 21:31:10 +0800 Subject: [PATCH 3/3] Rename and simplify --- src/data_morph/shapes/points.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/data_morph/shapes/points.py b/src/data_morph/shapes/points.py index b3faf347..aaeaea25 100644 --- a/src/data_morph/shapes/points.py +++ b/src/data_morph/shapes/points.py @@ -335,7 +335,7 @@ def __init__(self, dataset: Dataset) -> None: scale_factor = min(x_bounds.range, y_bounds.range) / 75 # params for lobes - r = 15 * scale_factor + radius = 15 * scale_factor top_lobe_y_offset = 18 * scale_factor bottom_lobes_x_offset = 15 * scale_factor bottom_lobes_y_offset = 9 * scale_factor @@ -344,18 +344,18 @@ def __init__(self, dataset: Dataset) -> None: # top lobe angle_offset = -1 / 3 * np.pi - x_top = r * np.cos(t + angle_offset) - y_top = r * np.sin(t + angle_offset) + top_lobe_y_offset + x_top = radius * np.cos(t + angle_offset) + y_top = radius * np.sin(t + angle_offset) + top_lobe_y_offset # bottom left lobe angle_offset = 1 / 3 * np.pi - x_bottom_left = r * np.cos(t + angle_offset) - bottom_lobes_x_offset - y_bottom_left = r * np.sin(t + angle_offset) - bottom_lobes_y_offset + x_bottom_left = radius * np.cos(t + angle_offset) - bottom_lobes_x_offset + y_bottom_left = radius * np.sin(t + angle_offset) - bottom_lobes_y_offset # bottom right lobe angle_offset = np.pi - x_bottom_right = r * np.cos(t + angle_offset) + bottom_lobes_x_offset - y_bottom_right = r * np.sin(t + angle_offset) - bottom_lobes_y_offset + x_bottom_right = radius * np.cos(t + angle_offset) + bottom_lobes_x_offset + y_bottom_right = radius * np.sin(t + angle_offset) - bottom_lobes_y_offset x_lobes = [x_top, x_bottom_left, x_bottom_right] y_lobes = [y_top, y_bottom_left, y_bottom_right] @@ -371,14 +371,12 @@ def __init__(self, dataset: Dataset) -> None: y_line = np.repeat(-stem_y_offset, 8) # left part of the stem - t_left = np.linspace(-(stem_x_offset - stem_x_pad), -stem_x_pad, num=6) - x_left = t_left - y_left = stem_scaler * np.power(t_left + stem_x_offset, 2) - stem_y_offset + x_left = np.linspace(-(stem_x_offset - stem_x_pad), -stem_x_pad, num=6) + y_left = stem_scaler * np.power(x_left + stem_x_offset, 2) - stem_y_offset # right part of the stem - t_right = np.linspace(stem_x_pad, stem_x_offset - stem_x_pad, num=6) - x_right = t_right - y_right = stem_scaler * np.power(t_right - stem_x_offset, 2) - stem_y_offset + x_right = np.linspace(stem_x_pad, stem_x_offset - stem_x_pad, num=6) + y_right = stem_scaler * np.power(x_right - stem_x_offset, 2) - stem_y_offset x_stem = [x_line, x_left, x_right] y_stem = [y_line, y_left, y_right]