File size: 5,394 Bytes
9f084b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
"""Tests for geometry/bbox.py — pure bbox operations."""

from __future__ import annotations

import pytest
from src.app.geometry import bbox


class TestBasicProperties:
    def test_x2(self) -> None:
        assert bbox.x2((10, 20, 100, 50)) == 110

    def test_y2(self) -> None:
        assert bbox.y2((10, 20, 100, 50)) == 70

    def test_area(self) -> None:
        assert bbox.area((0, 0, 10, 20)) == 200

    def test_center(self) -> None:
        cx, cy = bbox.center((100, 200, 300, 50))
        assert cx == 250.0
        assert cy == 225.0


class TestContains:
    def test_fully_inside(self) -> None:
        outer = (0, 0, 100, 100)
        inner = (10, 10, 20, 20)
        assert bbox.contains(outer, inner) is True

    def test_same_box(self) -> None:
        b = (10, 20, 30, 40)
        assert bbox.contains(b, b) is True

    def test_exceeds_right(self) -> None:
        outer = (0, 0, 100, 100)
        inner = (80, 10, 30, 20)  # x2 = 110 > 100
        assert bbox.contains(outer, inner) is False

    def test_exceeds_bottom(self) -> None:
        outer = (0, 0, 100, 100)
        inner = (10, 85, 20, 20)  # y2 = 105 > 100
        assert bbox.contains(outer, inner) is False

    def test_tolerance_allows_small_overflow(self) -> None:
        outer = (0, 0, 100, 100)
        inner = (0, 0, 103, 100)  # 3px overflow on right
        assert bbox.contains(outer, inner, tolerance=5) is True
        assert bbox.contains(outer, inner, tolerance=2) is False

    def test_tolerance_on_all_sides(self) -> None:
        outer = (10, 10, 80, 80)
        inner = (7, 7, 86, 86)  # 3px overflow on each side
        assert bbox.contains(outer, inner, tolerance=3) is True
        assert bbox.contains(outer, inner, tolerance=2) is False


class TestIntersects:
    def test_overlapping(self) -> None:
        a = (0, 0, 50, 50)
        b = (25, 25, 50, 50)
        assert bbox.intersects(a, b) is True

    def test_not_overlapping(self) -> None:
        a = (0, 0, 10, 10)
        b = (20, 20, 10, 10)
        assert bbox.intersects(a, b) is False

    def test_touching_edge_not_overlapping(self) -> None:
        a = (0, 0, 10, 10)
        b = (10, 0, 10, 10)  # touching at x=10
        assert bbox.intersects(a, b) is False

    def test_contained(self) -> None:
        outer = (0, 0, 100, 100)
        inner = (10, 10, 20, 20)
        assert bbox.intersects(outer, inner) is True


class TestIntersection:
    def test_overlap(self) -> None:
        a = (0, 0, 50, 50)
        b = (25, 25, 50, 50)
        result = bbox.intersection(a, b)
        assert result == (25, 25, 25, 25)

    def test_no_overlap(self) -> None:
        a = (0, 0, 10, 10)
        b = (20, 20, 10, 10)
        assert bbox.intersection(a, b) is None

    def test_contained(self) -> None:
        outer = (0, 0, 100, 100)
        inner = (10, 10, 20, 20)
        result = bbox.intersection(outer, inner)
        assert result == inner


class TestUnion:
    def test_adjacent(self) -> None:
        a = (0, 0, 10, 10)
        b = (10, 0, 10, 10)
        result = bbox.union(a, b)
        assert result == (0, 0, 20, 10)

    def test_overlapping(self) -> None:
        a = (0, 0, 50, 50)
        b = (25, 25, 50, 50)
        result = bbox.union(a, b)
        assert result == (0, 0, 75, 75)

    def test_contained(self) -> None:
        outer = (0, 0, 100, 100)
        inner = (10, 10, 20, 20)
        assert bbox.union(outer, inner) == outer


class TestUnionAll:
    def test_multiple(self) -> None:
        bboxes = [(0, 0, 10, 10), (50, 50, 10, 10), (25, 25, 10, 10)]
        result = bbox.union_all(bboxes)
        assert result == (0, 0, 60, 60)

    def test_single(self) -> None:
        assert bbox.union_all([(5, 5, 10, 10)]) == (5, 5, 10, 10)

    def test_empty_raises(self) -> None:
        with pytest.raises(ValueError, match="empty"):
            bbox.union_all([])


class TestExpand:
    def test_normal(self) -> None:
        result = bbox.expand((10, 20, 100, 50), 5)
        assert result == (5, 15, 110, 60)

    def test_clamp_to_zero(self) -> None:
        result = bbox.expand((2, 3, 10, 10), 10)
        assert result[0] == 0
        assert result[1] == 0

    def test_min_dimension(self) -> None:
        result = bbox.expand((10, 10, 10, 10), -100)
        assert result[2] >= 1
        assert result[3] >= 1


class TestIou:
    def test_perfect_overlap(self) -> None:
        b = (0, 0, 10, 10)
        assert bbox.iou(b, b) == pytest.approx(1.0)

    def test_no_overlap(self) -> None:
        a = (0, 0, 10, 10)
        b = (20, 20, 10, 10)
        assert bbox.iou(a, b) == 0.0

    def test_partial_overlap(self) -> None:
        a = (0, 0, 10, 10)
        b = (5, 5, 10, 10)
        # intersection = (5,5,5,5) area=25, union = 100+100-25=175
        assert bbox.iou(a, b) == pytest.approx(25 / 175)


class TestOverlapRatio:
    def test_fully_inside(self) -> None:
        inner = (10, 10, 20, 20)
        outer = (0, 0, 100, 100)
        assert bbox.overlap_ratio(inner, outer) == pytest.approx(1.0)

    def test_no_overlap(self) -> None:
        a = (0, 0, 10, 10)
        b = (20, 20, 10, 10)
        assert bbox.overlap_ratio(a, b) == 0.0

    def test_half_overlap(self) -> None:
        a = (0, 0, 10, 10)
        b = (5, 0, 10, 10)
        # a ∩ b = (5,0,5,10) area=50, a area=100
        assert bbox.overlap_ratio(a, b) == pytest.approx(0.5)