diff --git a/src/eyetrax/calibration/common.py b/src/eyetrax/calibration/common.py index d82835c..639d93a 100644 --- a/src/eyetrax/calibration/common.py +++ b/src/eyetrax/calibration/common.py @@ -54,3 +54,61 @@ def wait_for_face_and_countdown(cap, gaze_estimator, sw, sh, dur: int = 2) -> bo cv2.imshow("Calibration", canvas) if cv2.waitKey(1) == 27: return False + + +def _pulse_and_capture( + gaze_estimator, + cap, + pts, + sw: int, + sh: int, + pulse_d: float = 1.0, + cd_d: float = 1.0, +): + """ + Shared pulse-and-capture loop for each calibration point. + """ + feats, targs = [], [] + + for x, y in pts: + # pulse + ps = time.time() + final_radius = 20 + while True: + e = time.time() - ps + if e > pulse_d: + break + ok, frame = cap.read() + if not ok: + continue + canvas = np.zeros((sh, sw, 3), dtype=np.uint8) + radius = 15 + int(15 * abs(np.sin(2 * np.pi * e))) + final_radius = radius + cv2.circle(canvas, (x, y), radius, (0, 255, 0), -1) + cv2.imshow("Calibration", canvas) + if cv2.waitKey(1) == 27: + return None + # capture + cs = time.time() + while True: + e = time.time() - cs + if e > cd_d: + break + ok, frame = cap.read() + if not ok: + continue + canvas = np.zeros((sh, sw, 3), dtype=np.uint8) + cv2.circle(canvas, (x, y), final_radius, (0, 255, 0), -1) + t = e / cd_d + ease = t * t * (3 - 2 * t) + ang = 360 * (1 - ease) + cv2.ellipse(canvas, (x, y), (40, 40), 0, -90, -90 + ang, (255, 255, 255), 4) + cv2.imshow("Calibration", canvas) + if cv2.waitKey(1) == 27: + return None + ft, blink = gaze_estimator.extract_features(frame) + if ft is not None and not blink: + feats.append(ft) + targs.append([x, y]) + + return feats, targs diff --git a/src/eyetrax/calibration/five_point.py b/src/eyetrax/calibration/five_point.py index 0133335..50f953b 100644 --- a/src/eyetrax/calibration/five_point.py +++ b/src/eyetrax/calibration/five_point.py @@ -1,14 +1,13 @@ -import time import cv2 import numpy as np from eyetrax.utils.screen import get_screen_size -from eyetrax.calibration.common import wait_for_face_and_countdown +from eyetrax.calibration.common import wait_for_face_and_countdown, _pulse_and_capture def run_5_point_calibration(gaze_estimator, camera_index: int = 0): """ - Faster five-point calibration + Faster five-point calibration. """ sw, sh = get_screen_size() @@ -23,55 +22,11 @@ def run_5_point_calibration(gaze_estimator, camera_index: int = 0): 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] - feats, targs = [], [] - pulse_d, cd_d = 1.0, 1.0 - - for _ in range(1): - for x, y in pts: - ps = time.time() - final_radius = 20 - while True: - e = time.time() - ps - if e > pulse_d: - break - r, f = cap.read() - if not r: - continue - c = np.zeros((sh, sw, 3), dtype=np.uint8) - radius = 15 + int(15 * abs(np.sin(2 * np.pi * e))) - final_radius = radius - cv2.circle(c, (x, y), radius, (0, 255, 0), -1) - cv2.imshow("Calibration", c) - if cv2.waitKey(1) == 27: - cap.release() - cv2.destroyAllWindows() - return - - cs = time.time() - while True: - e = time.time() - cs - if e > cd_d: - break - r, f = cap.read() - if not r: - continue - c = np.zeros((sh, sw, 3), dtype=np.uint8) - cv2.circle(c, (x, y), final_radius, (0, 255, 0), -1) - t = e / cd_d - ease = t * t * (3 - 2 * t) - ang = 360 * (1 - ease) - cv2.ellipse(c, (x, y), (40, 40), 0, -90, -90 + ang, (255, 255, 255), 4) - cv2.imshow("Calibration", c) - if cv2.waitKey(1) == 27: - cap.release() - cv2.destroyAllWindows() - return - ft, blink = gaze_estimator.extract_features(f) - if ft is not None and not blink: - feats.append(ft) - targs.append([x, y]) - + res = _pulse_and_capture(gaze_estimator, cap, pts, sw, sh) cap.release() cv2.destroyAllWindows() + if res is None: + return + feats, targs = res if feats: gaze_estimator.train(np.array(feats), np.array(targs)) diff --git a/src/eyetrax/calibration/nine_point.py b/src/eyetrax/calibration/nine_point.py index e870058..c3126d9 100644 --- a/src/eyetrax/calibration/nine_point.py +++ b/src/eyetrax/calibration/nine_point.py @@ -1,14 +1,13 @@ -import time import cv2 import numpy as np from eyetrax.utils.screen import get_screen_size -from eyetrax.calibration.common import wait_for_face_and_countdown +from eyetrax.calibration.common import wait_for_face_and_countdown, _pulse_and_capture def run_9_point_calibration(gaze_estimator, camera_index: int = 0): """ - Standard nine‑point calibration + Standard nine-point calibration """ sw, sh = get_screen_size() @@ -20,58 +19,24 @@ def run_9_point_calibration(gaze_estimator, camera_index: int = 0): 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), (1, 0), (0, 1), (2, 1), (1, 2)] + order = [ + (1, 1), + (0, 0), + (2, 0), + (0, 2), + (2, 2), + (1, 0), + (0, 1), + (2, 1), + (1, 2), + ] pts = [(mx + int(c * (gw / 2)), my + int(r * (gh / 2))) for (r, c) in order] - feats, targs = [], [] - pulse_d, cd_d = 1.0, 1.0 - - for _ in range(1): - for x, y in pts: - ps = time.time() - final_radius = 20 - while True: - e = time.time() - ps - if e > pulse_d: - break - r, f = cap.read() - if not r: - continue - c = np.zeros((sh, sw, 3), dtype=np.uint8) - radius = 15 + int(15 * abs(np.sin(2 * np.pi * e))) - final_radius = radius - cv2.circle(c, (x, y), radius, (0, 255, 0), -1) - cv2.imshow("Calibration", c) - if cv2.waitKey(1) == 27: - cap.release() - cv2.destroyAllWindows() - return - - cs = time.time() - while True: - e = time.time() - cs - if e > cd_d: - break - r, f = cap.read() - if not r: - continue - c = np.zeros((sh, sw, 3), dtype=np.uint8) - cv2.circle(c, (x, y), final_radius, (0, 255, 0), -1) - t = e / cd_d - ease = t * t * (3 - 2 * t) - ang = 360 * (1 - ease) - cv2.ellipse(c, (x, y), (40, 40), 0, -90, -90 + ang, (255, 255, 255), 4) - cv2.imshow("Calibration", c) - if cv2.waitKey(1) == 27: - cap.release() - cv2.destroyAllWindows() - return - ft, blink = gaze_estimator.extract_features(f) - if ft is not None and not blink: - feats.append(ft) - targs.append([x, y]) - + res = _pulse_and_capture(gaze_estimator, cap, pts, sw, sh) cap.release() cv2.destroyAllWindows() + if res is None: + return + feats, targs = res if feats: gaze_estimator.train(np.array(feats), np.array(targs))