""" Tests for the shelf map: registration, merging, and evidence pooling. The sign convention is tested explicitly, because getting it backwards is silent: the map still produces boxes, they just land in the wrong place, and a shelf of ~60 facings becomes 857. That happened during development. """ from __future__ import annotations import os import sys import unittest import numpy as np current_dir = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.dirname(current_dir) if os.path.exists(os.path.join(project_root, "stock")): os.chdir(project_root) if current_dir not in sys.path: sys.path.insert(0, current_dir) from shelf_map import Facing, ShelfMap, _iou # noqa: E402 def a_frame(shift_x=0, shift_y=0, size=(160, 240)): """A textured frame, optionally a shifted copy of another.""" height, width = size rng = np.random.RandomState(7) base = rng.randint(0, 255, (height * 2, width * 2, 3), dtype=np.uint8) return base[height - shift_y:height * 2 - shift_y, width - shift_x:width * 2 - shift_x] def a_track(track_id, box, sku=None, score=None, tag_sku=None): track = {"track_id": track_id, "box": list(box)} if sku: track["identity"] = {"state": "recognized", "sku_id": sku, "score": score or 1.0} else: track["identity"] = {"state": "unconfirmed"} if tag_sku: track["tag_identity"] = {"sku_id": tag_sku, "score": 0.7} return track class IouTests(unittest.TestCase): def test_overlap_maths(self): self.assertAlmostEqual(_iou([0, 0, 10, 10], [0, 0, 10, 10]), 1.0) self.assertEqual(_iou([0, 0, 10, 10], [20, 20, 30, 30]), 0.0) class RegistrationTests(unittest.TestCase): def test_the_frame_origin_moves_opposite_to_the_content(self): """The sign that matters: content moving left means the camera moved right. Getting this backwards walks the map the wrong way. It is silent - boxes still appear - and it turned a shelf of ~60 facings into 857. """ shelf = ShelfMap() shelf.register(a_frame()) shift = shelf.register(a_frame(shift_x=10)) # content moved right 10 self.assertIsNotNone(shift) self.assertAlmostEqual(shift[0], 10, delta=1.0) # Content moving right means the camera moved LEFT, so the frame's origin # sits 10 px further back along the shelf. shelf.update(0, a_frame(), [a_track(1, [0, 0, 20, 20])]) offset_before = list(shelf._offset) shelf.update(1, a_frame(shift_x=10), [a_track(2, [0, 0, 20, 20])]) self.assertLess(shelf._offset[0], offset_before[0] + 1, "the offset must follow the camera, not the content") def test_a_first_frame_has_no_shift(self): shelf = ShelfMap() self.assertEqual(shelf.register(a_frame()), (0.0, 0.0)) class MergingTests(unittest.TestCase): def test_a_facing_seen_twice_is_one_facing(self): shelf = ShelfMap() shelf.update(0, a_frame(), [a_track(1, [10, 10, 110, 200])]) shelf.update(1, a_frame(), [a_track(2, [12, 11, 112, 201])]) self.assertEqual(len(shelf.facings()), 1) self.assertEqual(shelf.stats()["merges"], 1) def test_two_neighbouring_facings_stay_separate(self): shelf = ShelfMap() shelf.update(0, a_frame(), [a_track(1, [10, 10, 100, 200]), a_track(2, [110, 10, 200, 200])]) self.assertEqual(len(shelf.facings()), 2) def test_identity_votes_pool_across_track_ids(self): """The point of the map: one facing, many transient track ids, one answer.""" shelf = ShelfMap() for index, (track_id, sku) in enumerate([(1, "sku_a"), (2, "sku_a"), (3, "sku_a"), (4, "sku_b")]): shelf.update(index, a_frame(), [a_track(track_id, [10, 10, 110, 200], sku=sku)]) facing = shelf.facings()[0] self.assertEqual(facing.observations, 4) self.assertEqual(len(facing.seen_track_ids), 4, "four tracks, one facing") sku_id, agreement = facing.best_identity() self.assertEqual(sku_id, "sku_a") self.assertAlmostEqual(agreement, 0.75) def test_tag_evidence_pools_separately_from_packaging(self): shelf = ShelfMap() shelf.update(0, a_frame(), [a_track(1, [10, 10, 110, 200], sku="sku_a", tag_sku="sku_a")]) shelf.update(1, a_frame(), [a_track(2, [10, 10, 110, 200], tag_sku="sku_a")]) facing = shelf.facings()[0] self.assertEqual(facing.best_identity()[0], "sku_a") self.assertEqual(facing.best_tag_identity()[0], "sku_a") self.assertEqual(len(facing.tag_readings), 0, "no readable tag payload was attached") class PayloadTests(unittest.TestCase): def test_a_facing_reports_its_pooled_evidence(self): facing = Facing(facing_id="face_1", shelf_box=[0, 0, 10, 10]) facing.vote_identity("sku_a", 2.0) facing.vote_identity("sku_b", 1.0) payload = facing.as_payload() self.assertEqual(payload["sku_id"], "sku_a") self.assertAlmostEqual(payload["identity_agreement"], 2 / 3, places=3) self.assertIsNone(payload["tag_sku_id"]) if __name__ == "__main__": unittest.main()