Skip to content

Calibration algorithm

Sibo Wang-Chen edited this page May 26, 2026 · 4 revisions

Arena registration algorithm

Let $x_p, y_p$ be the physical coordinates of a point in the recording area in mm; let $x_t, y_t$ be the positions of the translation stages in mm; let $r_b, c_b$ be the row and column coordinates of the aforementioned point as captured by the behavior camera in pixels.

The registration step establishes two mappings for the behavior camera:

  1. Stage + pixel position → physical position: $f_b:\ (x_t, y_t, r_b, c_b) \rightarrow (x_p, y_p)$
  2. Stage + physical position → pixel position: $g_b:\ (x_t, y_t, x_p, y_p) \rightarrow (r_b, c_b)$

Strategy

  1. Arena spec. The arena PDF (arena146_spec.pdf) encodes the physical (arena-space) positions of all AprilTag corners and the DataMatrix barcode center, in mm. ArenaConfig parses this and writes metadata.yaml.

  2. Mapping board. make_arena146_config.py generates a printable PDF (mapping_board.pdf) with AprilTag markers at the locations encoded in the spec, plus a DataMatrix barcode encoding an 8-character checksum of the full spec. The board is placed in the arena during the scan.

  3. Registration scan. run-arena-registration-scan performs the following:

    • Decodes the DataMatrix to verify the correct board is in place (checksum must match metadata.yaml).
    • Computes the stage-to-arena offset from the stage position when the camera is centred on the DataMatrix.
    • Moves the stage to each AprilTag's centre, acquires 10 consecutive frames, and records the stage position. Output: mapping_scan/apriltag<id>_img<i>.jpg and apriltag_stage_positions.csv.
  4. Detection. fit-arena-registration detects AprilTag corners in each image using pupil_apriltags. Each detected corner gives a tuple $(x_t, y_t, r_b, c_b, x_p, y_p)$ where $(x_p, y_p)$ is known from metadata.yaml.

  5. Outlier rejection. Within-burst outliers (the 10 frames per tag are near-identical because the stage is stationary) are removed by MAD filtering per (tag, corner) group. Overall outliers are then removed by RANSAC.

  6. Linear fit. We assume the mapping is affine linear. For $f_b$, we fit $A_{f_b} \in \mathbb{R}^{2 \times 5}$ such that:

$$\begin{pmatrix} x_p \ y_p \end{pmatrix} = A_{f_b} \begin{pmatrix} x_t \ y_t \ c_b \ r_b \ 1 \end{pmatrix}$$

Two separate RANSAC regressors are fit (one per axis); only combined inliers are used for the final least-squares estimate.

  1. Inverse. $g_b$ (stage + physical → pixel) is derived analytically by partially inverting $A_{f_b}$ (the pixel block is $2 \times 2$ and thus invertible). Both matrices are saved in calibration_result.yaml in the YAML schema read by CalibrationParams in the C++ recorder.

Quality targets

A good registration fit has:

  • RMSE ≲ 0.1 mm (euclidean)
  • R² ≈ 1.0 on both axes
  • Very few RANSAC outliers (< 5%)

Check model/diagnostics.png after fitting to visually verify the residual distribution and spatial pattern.

Differences from the old ArUco calibration

The previous pipeline used an ArUco board scanned on a dense grid, detected with OpenCV, and fitted per-camera. The current pipeline uses:

  • A sparse set of AprilTag markers (8 for arena146) at known positions in the PDF spec.
  • pupil_apriltags for detection (more robust than cv2.aruco for tag16h5).
  • Per-burst MAD filtering to exploit the repeated measurements per tag.
  • A DataMatrix checksum to guard against mismatched boards.
  • A single behavior-camera model (the muscle camera is aligned via a separate homography calibration if needed).

Clone this wiki locally