Skip to content

Commit d245f0a

Browse files
Villtordeir17846
andauthored
add rectangle class (#1815)
Co-authored-by: eir17846 <victor.rogalev@diamond.ac.uk>
1 parent 905cd1f commit d245f0a

File tree

3 files changed

+129
-1
lines changed

3 files changed

+129
-1
lines changed

src/dodal/common/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .coordination import group_uuid, inject
22
from .enums import EnabledDisabledUpper, InOutUpper, OnOffUpper
3-
from .maths import in_micros, step_to_num
3+
from .maths import Rectangle2D, in_micros, step_to_num
44
from .types import MsgGenerator, PlanGenerator
55

66
__all__ = [
@@ -13,4 +13,5 @@
1313
"MsgGenerator",
1414
"PlanGenerator",
1515
"step_to_num",
16+
"Rectangle2D",
1617
]

src/dodal/common/maths.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,83 @@ def in_micros(t: float) -> int:
4848
if t < 0:
4949
raise ValueError(f"Expected a positive time in seconds, got {t!r}")
5050
return int(np.ceil(t * 1e6))
51+
52+
53+
class Rectangle2D:
54+
"""
55+
A 2D rectangle defined by two opposite corners.
56+
57+
This class represents a rectangle in 2D space using two points: (x1, y1) and (x2, y2).
58+
It provides methods to query rectangle properties and check point containment.
59+
60+
Attributes:
61+
x1 (float): The x-coordinate of the first corner.
62+
y1 (float): The y-coordinate of the first corner.
63+
x2 (float): The x-coordinate of the second corner.
64+
y2 (float): The y-coordinate of the second corner.
65+
"""
66+
67+
def __init__(self, x1: float, y1: float, x2: float, y2: float):
68+
"""
69+
Initialize a Rectangle2D with two corner points.
70+
71+
Args:
72+
x1 (float): The x-coordinate of the first corner.
73+
y1 (float): The y-coordinate of the first corner.
74+
x2 (float): The x-coordinate of the second corner.
75+
y2 (float): The y-coordinate of the second corner.
76+
"""
77+
self.x1, self.y1 = x1, y1
78+
self.x2, self.y2 = x2, y2
79+
80+
def get_max_x(self) -> float:
81+
"""
82+
Get the maximum x-coordinate of the rectangle.
83+
84+
Returns:
85+
float: The larger of the two x-coordinates (x1, x2).
86+
"""
87+
return max(self.x1, self.x2)
88+
89+
def get_min_x(self) -> float:
90+
"""
91+
Get the minimum x-coordinate of the rectangle.
92+
93+
Returns:
94+
float: The smaller of the two x-coordinates (x1, x2).
95+
"""
96+
return min(self.x1, self.x2)
97+
98+
def get_max_y(self) -> float:
99+
"""
100+
Get the maximum y-coordinate of the rectangle.
101+
102+
Returns:
103+
float: The larger of the two y-coordinates (y1, y2).
104+
"""
105+
return max(self.y1, self.y2)
106+
107+
def get_min_y(self) -> float:
108+
"""
109+
Get the minimum y-coordinate of the rectangle.
110+
111+
Returns:
112+
float: The smaller of the two y-coordinates (y1, y2).
113+
"""
114+
return min(self.y1, self.y2)
115+
116+
def contains(self, x: float, y: float) -> bool:
117+
"""
118+
Check if a point is contained within the rectangle.
119+
120+
Args:
121+
x (float): The x-coordinate of the point.
122+
y (float): The y-coordinate of the point.
123+
124+
Returns:
125+
bool: True if the point is within the rectangle bounds, False otherwise.
126+
"""
127+
return (
128+
self.get_min_x() <= x <= self.get_max_x()
129+
and self.get_min_y() <= y <= self.get_max_y()
130+
)

tests/common/test_maths.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from dodal.common import in_micros, step_to_num
4+
from dodal.common.maths import Rectangle2D
45

56

67
@pytest.mark.parametrize(
@@ -61,3 +62,49 @@ def test_step_to_num(
6162
assert actual_start == start
6263
assert actual_stop == truncated_stop
6364
assert num == expected_num
65+
66+
67+
@pytest.mark.parametrize(
68+
"x_1,y_1,x_2,y_2, inside, outside",
69+
[
70+
(0, 0, 10.0, 10.0, (5, 5), (15, 15)),
71+
(10, 10, 0.0, 0.0, (5, 5), (15, 15)),
72+
(-20, 0.0, 20, 10.0, (0, 5), (25, 15)),
73+
(-20, 10.0, 20, 0.0, (0, 5), (25, 15)),
74+
],
75+
)
76+
def test_rectangle_contains(
77+
x_1: float,
78+
y_1: float,
79+
x_2: float,
80+
y_2: float,
81+
inside: tuple[float, float],
82+
outside: tuple[float, float],
83+
):
84+
rect = Rectangle2D(x_1, y_1, x_2, y_2)
85+
assert rect.contains(*inside)
86+
assert not rect.contains(*outside)
87+
88+
89+
@pytest.mark.parametrize(
90+
"x_1,y_1,x_2,y_2, max_x, max_y, min_x, min_y",
91+
[
92+
(0, 0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0),
93+
(-20, 0.0, 20, 10.0, 20.0, 10.0, -20.0, 0.0),
94+
],
95+
)
96+
def test_rectangle_min_max(
97+
x_1: float,
98+
y_1: float,
99+
x_2: float,
100+
y_2: float,
101+
max_x: float,
102+
max_y: float,
103+
min_x: float,
104+
min_y: float,
105+
):
106+
rect = Rectangle2D(x_1, y_1, x_2, y_2)
107+
assert rect.get_max_x() == max_x
108+
assert rect.get_max_y() == max_y
109+
assert rect.get_min_x() == min_x
110+
assert rect.get_min_y() == min_y

0 commit comments

Comments
 (0)