#!/usr/bin/env python3 """ check_robot_marker_uniqueness.py Standalone checker for duplicate / invalid marker IDs in robot.json. Use this before running bundle adjustment: python check_robot_marker_uniqueness.py -robot robot.json Exit codes: 0 = no duplicates found 1 = duplicates or structural problems found """ from __future__ import annotations import argparse import json import os import sys from collections import defaultdict from typing import Any, Dict, List, Tuple def resolve_path(path: str) -> str: path = os.path.expanduser(path) if os.path.isabs(path): return path return os.path.abspath(path) def load_json(path: str) -> Dict[str, Any]: with open(resolve_path(path), "r", encoding="utf-8") as f: return json.load(f) def main() -> None: parser = argparse.ArgumentParser(description="Check marker ID uniqueness in robot.json") parser.add_argument("-robot", "--robot", default="../../data/robot/robot.json", help="Path to robot.json") parser.add_argument( "--strict", action="store_true", help="Return exit code 1 if any duplicate or invalid marker is found", ) args = parser.parse_args() robot = load_json(args.robot) links = robot.get("links", {}) or {} id_locations: Dict[int, List[Tuple[str, int, str]]] = defaultdict(list) invalid_entries: List[str] = [] total_markers = 0 for link_name, link_data in links.items(): markers = link_data.get("markers", []) or [] for idx, marker in enumerate(markers): total_markers += 1 marker_id = marker.get("id", None) if marker_id is None: invalid_entries.append(f"link='{link_name}' marker_index={idx}: missing id") continue try: marker_id_int = int(marker_id) except Exception: invalid_entries.append(f"link='{link_name}' marker_index={idx}: non-integer id={marker_id!r}") continue name = str(marker.get("name", f"marker_{marker_id_int}")) id_locations[marker_id_int].append((link_name, idx, name)) pos = marker.get("position", None) if pos is None or not isinstance(pos, list) or len(pos) != 3: invalid_entries.append( f"link='{link_name}' marker_id={marker_id_int}: invalid/missing 3D position" ) duplicates = {mid: locs for mid, locs in id_locations.items() if len(locs) > 1} print(f"[INFO] Total marker entries: {total_markers}") print(f"[INFO] Unique marker IDs : {len(id_locations)}") print(f"[INFO] Duplicate IDs : {len(duplicates)}") print(f"[INFO] Invalid entries : {len(invalid_entries)}") if duplicates: print("\n[DUPLICATES]") for mid in sorted(duplicates.keys()): print(f" marker_id={mid}") for link_name, idx, name in duplicates[mid]: print(f" - link='{link_name}' marker_index={idx} name='{name}'") if invalid_entries: print("\n[INVALID ENTRIES]") for item in invalid_entries: print(f" - {item}") if duplicates or invalid_entries: print("\n[WARN] robot.json is not clean for deterministic constraint generation.") if args.strict: sys.exit(1) else: print("\n[OK] No duplicate marker IDs found.") if __name__ == "__main__": main()