Download test_window_postprocess.py from constructelligence/painting-vision-robotics-kit: direct link, hf CLI and curl.
- Browser
- Download file 4.06 kB
-
https://huggingface.co/constructelligence/painting-vision-robotics-kit/resolve/main/test_window_postprocess.py
- Command line
-
hf download hf://constructelligence/painting-vision-robotics-kit/test_window_postprocess.py
-
curl -L -o test_window_postprocess.py https://huggingface.co/constructelligence/painting-vision-robotics-kit/resolve/main/test_window_postprocess.py
4.06 kB
| #!/usr/bin/env python3 | |
| """Regression tests for window_postprocess (numpy/scipy only). | |
| Run with ``python3 test_window_postprocess.py``. These pin the behaviours that | |
| matter for painting safety: merging mullion-split panes, fitting an oriented | |
| rectangle at any angle, filtering specks and non-window shapes, and rasterising | |
| a clean keep-out. | |
| """ | |
| import numpy as np | |
| from PIL import Image, ImageDraw | |
| import window_postprocess as wp | |
| def rotated_rectangle(height, width, center, angle_deg, side_w, side_h): | |
| canvas = Image.new("L", (width, height), 0) | |
| draw = ImageDraw.Draw(canvas) | |
| theta = np.radians(angle_deg) | |
| axes = np.array([[np.cos(theta), np.sin(theta)], [-np.sin(theta), np.cos(theta)]]) | |
| half = np.array([side_w, side_h]) / 2.0 | |
| corners = [] | |
| for sx, sy in ((-1, -1), (1, -1), (1, 1), (-1, 1)): | |
| point = np.array(center) + np.array([sx, sy]) * half @ axes | |
| corners.append(tuple(point)) | |
| draw.polygon(corners, fill=255) | |
| return np.asarray(canvas) > 0 | |
| def test_minimum_area_rectangle_recovers_orientation(): | |
| for angle in (0, 30, 45, 75): | |
| mask = rotated_rectangle(400, 400, (200, 200), angle, 140, 60) | |
| ys, xs = np.nonzero(mask) | |
| corners, width, height, fitted = wp.minimum_area_rectangle(np.c_[xs, ys]) | |
| long_side, short_side = max(width, height), min(width, height) | |
| assert abs(long_side - 140) < 8, (angle, width, height) | |
| assert abs(short_side - 60) < 8, (angle, width, height) | |
| # Angle is only defined modulo 90 degrees for an axis-aligned box. | |
| assert min(abs((fitted - angle) % 90), abs((fitted - angle) % 90 - 90)) < 6, (angle, fitted) | |
| def test_merge_fragments_joins_mullion_panes_only(): | |
| mask = np.zeros((120, 160), bool) | |
| mask[30:90, 10:60] = True # left pane | |
| mask[30:90, 66:120] = True # right pane, 6px gap | |
| merged = wp.merge_fragments(mask, gap=8) | |
| assert merged.max() == 1, "panes within the gap should merge" | |
| far = mask.copy() | |
| far[30:90, 140:159] = True # a third pane far away | |
| assert wp.merge_fragments(far, gap=8).max() == 2 | |
| def test_window_instances_merges_and_filters(): | |
| mask = np.zeros((200, 200), bool) | |
| mask[50:110, 40:80] = True | |
| mask[50:110, 84:130] = True # mullion gap of 4px | |
| mask[10:12, 10:12] = True # stray speck | |
| instances, labels = wp.window_instances(mask, min_area=64, merge_gap=8, min_fill=0.5, max_aspect=8.0) | |
| assert len(instances) == 1, instances | |
| window = instances[0] | |
| assert window["area_pixels"] > 5000 | |
| assert window["fill_ratio"] > 0.85 | |
| assert window["bbox_xyxy"][0] == 40 and window["bbox_xyxy"][2] == 129 | |
| def test_window_instances_rejects_elongated_shape(): | |
| mask = np.zeros((200, 400), bool) | |
| mask[95:105, 10:390] = True # a long thin stripe, aspect ~38 | |
| instances, _ = wp.window_instances(mask, min_area=64, merge_gap=0, max_aspect=8.0) | |
| assert instances == [] | |
| def test_regularized_mask_fills_mullion_gap(): | |
| mask = np.zeros((120, 200), bool) | |
| mask[30:90, 20:80] = True | |
| mask[30:90, 90:150] = True # 10px gap | |
| instances, _ = wp.window_instances(mask, min_area=64, merge_gap=14, min_fill=0.5) | |
| assert len(instances) == 1 | |
| regular = wp.regularized_mask(instances, mask.shape) | |
| # The fitted rectangle should cover the gap the raw mask left empty. | |
| assert regular[60, 85], "fitted window should fill the mullion gap" | |
| assert int(regular.sum()) > int(mask.sum()) | |
| def test_window_metrics(): | |
| target = np.zeros((50, 50), bool) | |
| target[10:40, 10:40] = True | |
| assert wp.window_metrics(target, target)["iou"] == 1.0 | |
| disjoint = np.zeros_like(target) | |
| disjoint[0:5, 0:5] = True | |
| result = wp.window_metrics(disjoint, target) | |
| assert result["precision"] == 0.0 and result["recall"] == 0.0 and result["iou"] == 0.0 | |
| def main(): | |
| tests = [value for name, value in sorted(globals().items()) if name.startswith("test_")] | |
| for test in tests: | |
| test() | |
| print(f"ok {test.__name__}") | |
| print(f"{len(tests)} tests passed") | |
| if __name__ == "__main__": | |
| main() | |