"""Library for overlap detection in spectrograms to control co-channel interference.
This module provides classes and functions to define 2D coordinates and axis-aligned
rectangles, and to detect overlaps between rectangles using line-segment intersection
and containment tests.
"""
# class object to contain (x, y) coordinates
[docs]
class Coordinate:
"""Represents a point in 2D space with x and y coordinates.
Attributes:
x (float): X-coordinate of the point.
y (float): Y-coordinate of the point.
"""
[docs]
def __init__(self, x: float, y: float):
"""Initialize a Coordinate.
Args:
x (float): X-coordinate.
y (float): Y-coordinate.
"""
self.x = x
self.y = y
[docs]
def __str__(self) -> str:
"""Return a human-readable string representation of the coordinate.
Returns:
str: Formatted as 'x = {x}, y = {y}'.
"""
return f"x = {self.x}, y = {self.y}"
# represents a rectangle shape with four vertices, each a Coordinate
[docs]
class Rectangle:
"""Represents an axis-aligned rectangle defined by two opposite corners.
The rectangle is built from a lower-left and an upper-right corner,
from which the other two corners are inferred.
Attributes:
coord_lower_left (Coordinate): Lower-left corner.
coord_upper_right (Coordinate): Upper-right corner.
coord_upper_left (Coordinate): Upper-left corner.
coord_lower_right (Coordinate): Lower-right corner.
"""
[docs]
def __init__(self, lower_coord: Coordinate, upper_coord: Coordinate):
"""Initialize a Rectangle from two corner coordinates.
Args:
lower_coord (Coordinate): Lower-left corner of the rectangle.
upper_coord (Coordinate): Upper-right corner of the rectangle.
"""
self.coord_lower_left = lower_coord
self.coord_upper_right = upper_coord
self.coord_upper_left = Coordinate(
self.coord_lower_left.x, self.coord_upper_right.y
)
self.coord_lower_right = Coordinate(
self.coord_upper_right.x, self.coord_lower_left.y
)
# function used in determining if lines intersect
# based on the counter-clockwise test algorithm
[docs]
def counter_clock_wise(a: Coordinate, b: Coordinate, c: Coordinate) -> bool:
"""Determine if three points a, b, c are in counter-clockwise order.
Args:
a (Coordinate): First point.
b (Coordinate): Second point.
c (Coordinate): Third point.
Returns:
bool: True if the sequence (a → b → c) is counter-clockwise.
"""
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x)
# determine if two line segments (AB and CD) intersect
[docs]
def line_intersection(
a: Coordinate, b: Coordinate, c: Coordinate, d: Coordinate
) -> bool:
"""Check if the line segments AB and CD intersect.
Uses the counter-clockwise orientation test.
Args:
a (Coordinate): First endpoint of segment AB.
b (Coordinate): Second endpoint of segment AB.
c (Coordinate): First endpoint of segment CD.
d (Coordinate): Second endpoint of segment CD.
Returns:
bool: True if segments AB and CD intersect.
"""
return counter_clock_wise(a, c, d) != counter_clock_wise(
b, c, d
) and counter_clock_wise(a, b, c) != counter_clock_wise(a, b, d)
# determine if a point lies within the 1D interval [left, right]
[docs]
def is_within_range(
test_coord_x: float, rectangle_left_x: float, rectangle_right_x: float
) -> bool:
"""Check if a coordinate lies within a closed interval on the x-axis.
Args:
test_coord_x (float): The x-value to test.
rectangle_left_x (float): Lower bound of the interval.
rectangle_right_x (float): Upper bound of the interval.
Returns:
bool: True if rectangle_left_x <= test_coord_x <= rectangle_right_x.
"""
return rectangle_left_x <= test_coord_x <= rectangle_right_x
# determine if a rectangle corner lies inside another rectangle
[docs]
def is_corner_in_rectangle(corner_coord: Coordinate, reference_box: Rectangle) -> bool:
"""Check if a corner point is within the bounds of a reference rectangle.
Args:
corner_coord (Coordinate): The corner to test.
reference_box (Rectangle): The rectangle in which to test containment.
Returns:
bool: True if the corner is inside reference_box (including edges).
"""
x_inside = is_within_range(
corner_coord.x,
reference_box.coord_lower_left.x,
reference_box.coord_lower_right.x,
)
y_inside = is_within_range(
corner_coord.y,
reference_box.coord_lower_left.y,
reference_box.coord_upper_left.y,
)
return x_inside and y_inside
# determine if one rectangle is entirely within another
[docs]
def is_rectangle_inside_rectangle(
rectangle_1: Rectangle, rectangle_2: Rectangle
) -> bool:
"""Check if rectangle_1 is completely inside rectangle_2.
Tests whether all four corners of rectangle_1 lie within rectangle_2.
Args:
rectangle_1 (Rectangle): The inner rectangle to test.
rectangle_2 (Rectangle): The outer rectangle to test against.
Returns:
bool: True if rectangle_1 is fully contained in rectangle_2.
"""
corners = [
rectangle_1.coord_lower_left,
rectangle_1.coord_upper_left,
rectangle_1.coord_upper_right,
rectangle_1.coord_lower_right,
]
return all(is_corner_in_rectangle(c, rectangle_2) for c in corners)
# determine if two rectangles have any overlap
[docs]
def is_rectangle_overlap(rectangle_a: Rectangle, rectangle_b: Rectangle) -> bool:
"""Check if two rectangles overlap by intersection or containment.
Overlap occurs if:
1. Any side of rectangle_a intersects any side of rectangle_b.
2. One rectangle is fully contained within the other.
Args:
rectangle_a (Rectangle): First rectangle.
rectangle_b (Rectangle): Second rectangle.
Returns:
bool: True if the rectangles overlap.
"""
# all side-pairs of rectangle a and b
a_sides = [
(rectangle_a.coord_lower_left, rectangle_a.coord_lower_right),
(rectangle_a.coord_lower_left, rectangle_a.coord_upper_left),
(rectangle_a.coord_upper_left, rectangle_a.coord_upper_right),
(rectangle_a.coord_upper_right, rectangle_a.coord_lower_right),
]
b_sides = [
(rectangle_b.coord_lower_left, rectangle_b.coord_lower_right),
(rectangle_b.coord_lower_left, rectangle_b.coord_upper_left),
(rectangle_b.coord_upper_left, rectangle_b.coord_upper_right),
(rectangle_b.coord_upper_right, rectangle_b.coord_lower_right),
]
# check for any side intersection
for a1, a2 in a_sides:
for b1, b2 in b_sides:
if line_intersection(a1, a2, b1, b2):
return True
# check for full containment either way
a_inside_b = is_rectangle_inside_rectangle(rectangle_a, rectangle_b)
b_inside_a = is_rectangle_inside_rectangle(rectangle_b, rectangle_a)
return a_inside_b or b_inside_a