This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
YF-IPS: a low-cost indoor positioning system for tracking 1–100 robots in a ~5×5 m area at ≥10 fps, returning (id, x, y, yaw). Vision-based (webcam) with two detection modes — AprilTag and reference-image (ORB). Early-stage research code.
Package management uses uv. Dependencies live in pyproject.toml (requirements.txt is kept for reference but no longer authoritative).
uv sync # create .venv + install deps
uv run python -m yfips.calibration # chessboard calibration → config.json
uv run python -m yfips.detection --mode apriltag # default mode
uv run python -m yfips.detection --mode image # reference-image mode
uv run pytest # unit tests
uv run ruff check . # lintMode can also be set via "mode" in config.json. Add deps with uv add <pkg> (runtime) or uv add --group dev <pkg> (dev).
Inside the window: double-click 4 world corners in the order of world_corners_m (default (0,0), (5,0), (5,5), (0,5) m) to compute and persist the image→world homography. 5th click resets. ESC quits.
All state flows through config.json (at repo root). Source lives under src/yfips/ as an installable package — run modules with python -m yfips.<name>.
src/yfips/config.py— load/saveconfig.json. Holds camera intrinsics, distortion, world/image corners, UDP settings, mode, references dir.src/yfips/calibration.py— OpenCV chessboard calibration. Readsimages/calibration_*.jpg(absolute-path glob; CWD-independent). Writescamera_matrix+dist_coeffsintoconfig.json.src/yfips/detection.py— main realtime loop.- Builds a detector based on
mode(apriltaguses theapriltaglib;imageusesImageRefDetector). - If intrinsics are present and
undistort=true, precomputes rectify maps (cv2.initUndistortRectifyMap) and remaps every frame. - Mouse double-click collects 4 world-corner pixels →
cv2.findHomography→ image→world homography, persisted. - Per detection: center + a "forward" image point are mapped through the homography; yaw =
atan2of the world-frame forward vector. Smoothed byEMATrackerper id before publish. - Publishes
{id, x, y, yaw, t}JSON over UDP (default127.0.0.1:9999) and optionally as a ROS 2 string topic.
- Builds a detector based on
src/yfips/image_detector.py—ImageRefDetector: ORB + BFMatcher/FLANN + RANSAC homography. Loads references fromreferences/<id>.{png,jpg}where the filename stem is the integer robot id. Emits the same{id, center, forward, corners}shape as the AprilTag adapter so downstream world-transform code is shared. Setimage_mode.use_flann=truefor LSH-based matching at scale.src/yfips/tracker.py—EMATracker: per-id exponential-moving-average smoothing of(x, y, yaw); yaw smoothed via unit vector to handle wrap-around. Disable viatracker.enabled=false.src/yfips/ros_publisher.py— optional ROS 2 publisher (std_msgs/String JSON). No-ops ifrclpyisn't installed. Enable viaros.enabled=true.src/yfips/gui.py— matplotlib live visualizer; listens to the UDP stream and plots each tracked robot as an arrow on the world plane. Run in a second terminal:uv run python -m yfips.gui.tests/— pytest unit tests over pure-logic modules (trackers, homography, guards).
UDP listener (debug):
python3 -c "import socket;s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM);s.bind(('127.0.0.1',9999))
while True: print(s.recvfrom(4096)[0].decode())"apriltagpackage name on PyPI varies by platform (swatbotics binding).- Running
yfips.calibrationauto-clears any previously-savedimage_corners_px, because the old pixel coordinates refer to an image rectified with the old intrinsics. Re-click the 4 world corners after calibration. - Camera index, resolution and fps default to 0 / 640×480 / 60 fps but live in
config.json'scamerablock; CLI flags--camera-index --width --height --fpsoverride at runtime. - Image mode cost scales linearly with number of references; for ≳50 robots swap BFMatcher for FLANN.
- World positioning assumes robots move on a plane; tall tags/robots get parallax error even after undistortion.
rosmode requires a ROS 2 install (Humble+) withrclpyon PYTHONPATH; otherwise it no-ops with a warning.- Toggling
undistortafter clicking world corners still misaligns them — only a recalibration auto-invalidates them. If you flipundistortmanually, re-click.