import copy import unittest import numpy as np from PIL import Image from geometry_grounding import ground_parts, pixel from normalize_reference import prepare_rgba def plan(box=None, points=None): return {'parts': [{'id': 'hand_left', 'semantic': 'screen-left hand', 'visibility': 'visible', 'bbox_norm_xyxy': box or [0, 0, 1, 1], 'positive_points_norm': points or [], 'pivot_norm': [0, 0]}]} class GeometryTests(unittest.TestCase): def setUp(self): self.rgba = np.zeros((20, 30, 4), dtype=np.uint8) self.rgba[5:15, 10:20] = [20, 40, 60, 255] self.image = Image.fromarray(self.rgba) def test_existing_alpha_and_visible_pixels_unchanged(self): self.rgba[6, 12] = [255, 255, 255, 128] image, meta = prepare_rgba(Image.fromarray(self.rgba)) np.testing.assert_array_equal(image, self.rgba) self.assertEqual(meta['method'], 'existing_alpha') def test_only_border_connected_white_is_removed(self): rgb = np.full((20, 20, 3), 255, dtype=np.uint8) rgb[4:16, 4:16] = 20 rgb[7:13, 7:13] = 255 prepared, meta = prepare_rgba(Image.fromarray(rgb)) data = np.array(prepared) self.assertEqual(data[0, 0, 3], 0) self.assertEqual(data[8, 8, 3], 255) np.testing.assert_array_equal(data[:, :, :3], rgb) self.assertEqual(meta['method'], 'border_connected_white') self.assertEqual(prepared.size, (20, 20)) def test_colored_opaque_and_blank_inputs_rejected(self): for color in [(0, 30, 80, 255), (255, 255, 255, 255), (0, 0, 0, 0)]: with self.assertRaises(ValueError): prepare_rgba(Image.new('RGBA', (10, 10), color)) def test_invalid_model_points_replaced_without_changing_pivot(self): data = plan(points=[[0, 0], [1, 1], [.4, .4]]) original = copy.deepcopy(data) report, prompts = ground_parts(self.image, data) self.assertEqual(report['summary']['invalid_model_points'], 2) self.assertEqual(report['summary']['background_model_points'], 2) candidate = report['parts'][0]['candidate_points'][0] x, y = candidate['point_px'] self.assertGreater(self.rgba[y, x, 3], 0) self.assertGreaterEqual(candidate['distance_to_roi_boundary_px'], 5) self.assertEqual(pixel(candidate['point_norm'], 30, 20), (x, y)) self.assertFalse(prompts['parts'][0]['semantic_verified']) self.assertEqual(prompts['parts'][0]['negative_points_px'], []) self.assertEqual(data, original) self.assertEqual(report['parts'][0]['model_positive_points'][2]['status'], 'UNTRUSTED_SEED') def test_bbox_with_no_foreground_has_no_seed(self): report, prompts = ground_parts(self.image, plan([0, 0, .2, .2])) self.assertEqual(report['summary']['blocked_parts'], 1) self.assertEqual(prompts['parts'][0]['positive_points_px'], []) self.assertIn('EMPTY_FOREGROUND_ROI', report['parts'][0]['warnings']) def test_solid_roi_has_finite_interior_distance(self): report, _ = ground_parts(self.image, plan([10/30, 5/20, 20/30, 15/20])) part = report['parts'][0] self.assertEqual(part['bbox_foreground_ratio'], 1) self.assertEqual(part['candidate_points'][0]['distance_to_roi_boundary_px'], 5) def test_disconnected_roi_remains_semantically_unverified(self): self.rgba[2:4, 2:4] = [255, 0, 0, 255] report, _ = ground_parts(Image.fromarray(self.rgba), plan()) self.assertEqual(report['parts'][0]['foreground_components'], 2) self.assertIn('MULTIPLE_FOREGROUND_COMPONENTS', report['parts'][0]['warnings']) self.assertFalse(report['parts'][0]['semantic_verified']) def test_opaque_image_does_not_validate_every_point(self): report, prompts = ground_parts(Image.new('RGBA', (30, 20), 'black'), plan(points=[[.5, .5]])) self.assertEqual(report['parts'][0]['status'], 'blocked') self.assertEqual(prompts['parts'][0]['positive_points_px'], []) def test_subpixel_roi_without_pixel_center_is_blocked(self): report, _ = ground_parts(self.image, plan([.4, .4, .401, .401])) self.assertEqual(report['parts'][0]['status'], 'blocked') self.assertIn('NO_PIXEL_CENTER_IN_ROI', report['parts'][0]['warnings']) def test_hidden_part_never_gets_foreground_seed(self): data = plan() data['parts'][0].update(visibility='hidden', bbox_norm_xyxy=None) report, prompts = ground_parts(self.image, data) self.assertEqual(report['parts'][0]['status'], 'hidden') self.assertEqual(prompts['parts'][0]['positive_points_px'], []) if __name__ == '__main__': unittest.main()