From d15e4c968412422cc1fa7d64fd6689fb262c8aec Mon Sep 17 00:00:00 2001 From: ck-zhang Date: Wed, 30 Apr 2025 16:07:21 +0800 Subject: [PATCH] Extract grid helper; dedup calibrations --- src/eyetrax/calibration/__init__.py | 3 ++- src/eyetrax/calibration/common.py | 23 +++++++++++++++++++++-- src/eyetrax/calibration/five_point.py | 10 ++++++---- src/eyetrax/calibration/nine_point.py | 10 ++++++---- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/eyetrax/calibration/__init__.py b/src/eyetrax/calibration/__init__.py index cd15519..60db22d 100644 --- a/src/eyetrax/calibration/__init__.py +++ b/src/eyetrax/calibration/__init__.py @@ -1,4 +1,4 @@ -from .common import wait_for_face_and_countdown +from .common import wait_for_face_and_countdown, compute_grid_points from .nine_point import run_9_point_calibration from .five_point import run_5_point_calibration from .lissajous import run_lissajous_calibration @@ -6,6 +6,7 @@ from .fine_tune import fine_tune_kalman_filter __all__ = [ "wait_for_face_and_countdown", + "compute_grid_points", "run_9_point_calibration", "run_5_point_calibration", "run_lissajous_calibration", diff --git a/src/eyetrax/calibration/common.py b/src/eyetrax/calibration/common.py index 639d93a..b079db0 100644 --- a/src/eyetrax/calibration/common.py +++ b/src/eyetrax/calibration/common.py @@ -3,9 +3,28 @@ import cv2 import numpy as np +def compute_grid_points(order, sw: int, sh: int, margin_ratio: float = 0.10): + """ + Translate grid (row, col) indices into absolute pixel locations + """ + if not order: + return [] + + max_r = max(r for r, _ in order) + max_c = max(c for _, c in order) + + mx, my = int(sw * margin_ratio), int(sh * margin_ratio) + gw, gh = sw - 2 * mx, sh - 2 * my + + step_x = 0 if max_c == 0 else gw / max_c + step_y = 0 if max_r == 0 else gh / max_r + + return [(mx + int(c * step_x), my + int(r * step_y)) for r, c in order] + + def wait_for_face_and_countdown(cap, gaze_estimator, sw, sh, dur: int = 2) -> bool: """ - Waits for a face to be detected (not blinking), then shows a countdown ellipse. + Waits for a face to be detected (not blinking), then shows a countdown ellipse """ cv2.namedWindow("Calibration", cv2.WND_PROP_FULLSCREEN) cv2.setWindowProperty("Calibration", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) @@ -66,7 +85,7 @@ def _pulse_and_capture( cd_d: float = 1.0, ): """ - Shared pulse-and-capture loop for each calibration point. + Shared pulse-and-capture loop for each calibration point """ feats, targs = [], [] diff --git a/src/eyetrax/calibration/five_point.py b/src/eyetrax/calibration/five_point.py index 50f953b..36cbce7 100644 --- a/src/eyetrax/calibration/five_point.py +++ b/src/eyetrax/calibration/five_point.py @@ -2,7 +2,11 @@ import cv2 import numpy as np from eyetrax.utils.screen import get_screen_size -from eyetrax.calibration.common import wait_for_face_and_countdown, _pulse_and_capture +from eyetrax.calibration.common import ( + wait_for_face_and_countdown, + _pulse_and_capture, + compute_grid_points, +) def run_5_point_calibration(gaze_estimator, camera_index: int = 0): @@ -17,10 +21,8 @@ def run_5_point_calibration(gaze_estimator, camera_index: int = 0): cv2.destroyAllWindows() return - mx, my = int(sw * 0.1), int(sh * 0.1) - gw, gh = sw - 2 * mx, sh - 2 * my order = [(1, 1), (0, 0), (2, 0), (0, 2), (2, 2)] - pts = [(mx + int(c * (gw / 2)), my + int(r * (gh / 2))) for (r, c) in order] + pts = compute_grid_points(order, sw, sh) res = _pulse_and_capture(gaze_estimator, cap, pts, sw, sh) cap.release() diff --git a/src/eyetrax/calibration/nine_point.py b/src/eyetrax/calibration/nine_point.py index c3126d9..fbff1f4 100644 --- a/src/eyetrax/calibration/nine_point.py +++ b/src/eyetrax/calibration/nine_point.py @@ -2,7 +2,11 @@ import cv2 import numpy as np from eyetrax.utils.screen import get_screen_size -from eyetrax.calibration.common import wait_for_face_and_countdown, _pulse_and_capture +from eyetrax.calibration.common import ( + wait_for_face_and_countdown, + _pulse_and_capture, + compute_grid_points, +) def run_9_point_calibration(gaze_estimator, camera_index: int = 0): @@ -17,8 +21,6 @@ def run_9_point_calibration(gaze_estimator, camera_index: int = 0): cv2.destroyAllWindows() return - mx, my = int(sw * 0.1), int(sh * 0.1) - gw, gh = sw - 2 * mx, sh - 2 * my order = [ (1, 1), (0, 0), @@ -30,7 +32,7 @@ def run_9_point_calibration(gaze_estimator, camera_index: int = 0): (2, 1), (1, 2), ] - pts = [(mx + int(c * (gw / 2)), my + int(r * (gh / 2))) for (r, c) in order] + pts = compute_grid_points(order, sw, sh) res = _pulse_and_capture(gaze_estimator, cap, pts, sw, sh) cap.release()