File size: 3,043 Bytes
37e3d5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""Tests for Plan 1.3 §4 Blum-medial-axis lathe-profile derivation (Phase 2)."""

from __future__ import annotations

import sys
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "stage2_spec"))

from derive_geometry import derive_lathe_profile  # noqa: E402


def circle_mask(size: int, r: float):
    c = (size - 1) / 2.0
    return [((x - c) ** 2 + (y - c) ** 2) <= r * r for y in range(size) for x in range(size)]


def rect_mask(w: int, h: int, mx: int):
    # a vertical bar of constant half-width mx, centered
    cx = w // 2
    return [abs(x - cx) <= mx for y in range(h) for x in range(w)]


def triangle_mask(w: int, h: int):
    # point at top (y=0), widening to full at bottom (y=h-1)
    cx = w // 2
    mask = []
    for y in range(h):
        half = int((y / (h - 1)) * (cx - 1))
        for x in range(w):
            mask.append(abs(x - cx) <= half)
    return mask


class LatheProfileTest(unittest.TestCase):
    def test_circle_radius_peaks_in_middle(self):
        size = 64
        mask = circle_mask(size, 28)
        prof = derive_lathe_profile(mask, size, size, samples=15)
        radii = [p[0] for p in prof["points"]]
        mid = radii[len(radii) // 2]
        self.assertGreater(mid, radii[0])
        self.assertGreater(mid, radii[-1])
        self.assertLess(radii[0], 0.15)  # near the poles the radius is ~0
        self.assertLess(radii[-1], 0.15)

    def test_rectangle_radius_roughly_constant(self):
        w, h = 40, 80
        mask = rect_mask(w, h, 8)
        prof = derive_lathe_profile(mask, w, h, samples=15)
        # drop first/last (endpoints can clip) and check the middle band is stable
        mids = [p[0] for p in prof["points"][3:-3]]
        self.assertTrue(mids)
        self.assertLess(max(mids) - min(mids), 0.03, mids)

    def test_triangle_radius_increases_top_to_bottom(self):
        w, h = 60, 80
        mask = triangle_mask(w, h)
        prof = derive_lathe_profile(mask, w, h, samples=15)
        radii = [p[0] for p in prof["points"]]
        # top (axisPos -0.5) narrow → bottom (+0.5) wide
        self.assertLess(radii[0], radii[-1])
        self.assertEqual(prof["axis"], "vertical")

    def test_axis_picks_longer_dimension(self):
        # a subject whose bbox is WIDER than tall → horizontal revolve axis.
        w, h = 80, 40
        cy = h // 2
        mask = [abs(y - cy) <= 6 for y in range(h) for x in range(w)]  # wide horizontal bar
        prof = derive_lathe_profile(mask, w, h, samples=10)
        self.assertEqual(prof["axis"], "horizontal")

    def test_axis_positions_span_normalized_range(self):
        size = 40
        mask = circle_mask(size, 16)
        prof = derive_lathe_profile(mask, size, size, samples=9)
        positions = [p[1] for p in prof["points"]]
        self.assertAlmostEqual(positions[0], -0.5, delta=0.05)
        self.assertAlmostEqual(positions[-1], 0.5, delta=0.05)


if __name__ == "__main__":
    unittest.main(verbosity=2)