monitor selection, model prototype and 2 more recordings
This commit is contained in:
+46
-23
@@ -16,18 +16,16 @@ import time
|
||||
import tkinter as tk
|
||||
from collections import deque
|
||||
|
||||
from monitor_utils import format_monitor_list
|
||||
from show_apriltag_surface import SurfaceTagWindow, enable_windows_dpi_awareness, validate_args
|
||||
from visualize_surface_gaze import GazeSample, SurfaceReceiver
|
||||
|
||||
|
||||
SCREEN_WIDTH = 2560
|
||||
SCREEN_HEIGHT = 1440
|
||||
SCREEN_DIAGONAL_IN = 31.5
|
||||
DEFAULT_VIEWING_DISTANCE_CM = 60.0
|
||||
TARGET_RADIUS = 80
|
||||
HIT_RADIUS = 140
|
||||
SAFE_LEFT, SAFE_RIGHT = 260, SCREEN_WIDTH - 260
|
||||
SAFE_TOP, SAFE_BOTTOM = 180, SCREEN_HEIGHT - 180
|
||||
SAFE_MARGIN_X = 260
|
||||
SAFE_MARGIN_Y = 180
|
||||
DISPERSION_WINDOW = 5
|
||||
MAX_CONTINUOUS_GAP_S = 0.5
|
||||
|
||||
@@ -38,8 +36,9 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--port", type=int, default=50020)
|
||||
parser.add_argument("--surface", default="screen")
|
||||
parser.add_argument("--confidence", type=float, default=0.9)
|
||||
parser.add_argument("--screen-diagonal", type=float, default=SCREEN_DIAGONAL_IN,
|
||||
help="physical screen diagonal in inches (default: 31.5)")
|
||||
parser.add_argument("--screen-diagonal", type=float, default=None,
|
||||
help="physical screen diagonal in inches "
|
||||
"(default: auto-detected from the selected monitor)")
|
||||
parser.add_argument("--viewing-distance", type=float, default=DEFAULT_VIEWING_DISTANCE_CM,
|
||||
help="eye-to-screen distance in cm (default: 60)")
|
||||
parser.add_argument("--subject-id", default="anon",
|
||||
@@ -57,6 +56,11 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--margin", type=int, default=0)
|
||||
parser.add_argument("--background", default="#10141c")
|
||||
parser.add_argument("--windowed", action="store_true")
|
||||
parser.add_argument("--monitor", type=int, default=None,
|
||||
help="Windows display number to use, e.g. 2 for \\\\.\\DISPLAY2 "
|
||||
"(default: primary)")
|
||||
parser.add_argument("--list-monitors", action="store_true",
|
||||
help="print detected monitors (id, resolution, position, diagonal) and exit")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -66,6 +70,21 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
def __init__(self, root: tk.Tk, args: argparse.Namespace) -> None:
|
||||
super().__init__(root, args)
|
||||
self.args = args
|
||||
self.width = self.monitor.width
|
||||
self.height = self.monitor.height
|
||||
self.safe_left, self.safe_right = SAFE_MARGIN_X, self.width - SAFE_MARGIN_X
|
||||
self.safe_top, self.safe_bottom = SAFE_MARGIN_Y, self.height - SAFE_MARGIN_Y
|
||||
self.pursuit_amp_x = min(760, self.width / 2 - SAFE_MARGIN_X)
|
||||
self.pursuit_amp_y = min(390, self.height / 2 - SAFE_MARGIN_Y)
|
||||
if args.screen_diagonal is not None:
|
||||
self.screen_diagonal = args.screen_diagonal
|
||||
elif self.monitor.diagonal_in is not None:
|
||||
self.screen_diagonal = self.monitor.diagonal_in
|
||||
else:
|
||||
raise SystemExit(
|
||||
"error: could not auto-detect the diagonal for this monitor; "
|
||||
"pass --screen-diagonal explicitly"
|
||||
)
|
||||
self.frame_interval_ms = max(1, round(1000 / args.animation_fps))
|
||||
self.messages: queue.Queue = queue.Queue(maxsize=256)
|
||||
self.stop_event = threading.Event()
|
||||
@@ -83,7 +102,7 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
self.trial = 0
|
||||
self.trial_started = 0.0
|
||||
self.block_started = 0.0
|
||||
self.target = (SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
|
||||
self.target = (self.width / 2, self.height / 2)
|
||||
self.distractor: tuple[float, float] | None = None
|
||||
self.correct_color = "#39e681"
|
||||
self.hit_started: float | None = None
|
||||
@@ -92,8 +111,8 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
self.hits = 0
|
||||
self.samples = 0
|
||||
self.summary_lines: list[str] = []
|
||||
diagonal_px = math.hypot(SCREEN_WIDTH, SCREEN_HEIGHT)
|
||||
self.cm_per_px = args.screen_diagonal * 2.54 / diagonal_px
|
||||
diagonal_px = math.hypot(self.width, self.height)
|
||||
self.cm_per_px = self.screen_diagonal * 2.54 / diagonal_px
|
||||
|
||||
self.history: deque[tuple[float, float, float]] = deque(maxlen=DISPERSION_WINDOW)
|
||||
self.prev_sample: tuple[float, float, float] | None = None
|
||||
@@ -119,13 +138,14 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
"session_id": self.session_id,
|
||||
"session_tag": args.session_tag,
|
||||
"recorded_at": datetime.now().isoformat(timespec="seconds"),
|
||||
"screen_diagonal_in": args.screen_diagonal,
|
||||
"monitor_id": self.monitor.display_id,
|
||||
"screen_diagonal_in": self.screen_diagonal,
|
||||
"viewing_distance_cm": args.viewing_distance,
|
||||
"confidence_threshold": args.confidence,
|
||||
"rounds": args.rounds,
|
||||
"animation_fps": args.animation_fps,
|
||||
"screen_width_px": SCREEN_WIDTH,
|
||||
"screen_height_px": SCREEN_HEIGHT,
|
||||
"screen_width_px": self.width,
|
||||
"screen_height_px": self.height,
|
||||
"result_csv": self.result_path.name,
|
||||
}, indent=2), encoding="utf-8")
|
||||
|
||||
@@ -152,10 +172,10 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
root.after(self.frame_interval_ms, self.tick)
|
||||
|
||||
def sx(self, x: float) -> float:
|
||||
return x / SCREEN_WIDTH * self.canvas.winfo_width()
|
||||
return x / self.width * self.canvas.winfo_width()
|
||||
|
||||
def sy(self, y: float) -> float:
|
||||
return y / SCREEN_HEIGHT * self.canvas.winfo_height()
|
||||
return y / self.height * self.canvas.winfo_height()
|
||||
|
||||
def draw(self, event: tk.Event) -> None:
|
||||
super().draw(event)
|
||||
@@ -208,7 +228,7 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
self.new_trial()
|
||||
|
||||
def random_target(self) -> tuple[float, float]:
|
||||
return self.rng.uniform(SAFE_LEFT, SAFE_RIGHT), self.rng.uniform(SAFE_TOP, SAFE_BOTTOM)
|
||||
return self.rng.uniform(self.safe_left, self.safe_right), self.rng.uniform(self.safe_top, self.safe_bottom)
|
||||
|
||||
def new_trial(self) -> None:
|
||||
self.trial_started = time.monotonic()
|
||||
@@ -246,8 +266,8 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
|
||||
def position_circle(self, item: int, point: tuple[float, float], radius: int) -> None:
|
||||
x, y = self.sx(point[0]), self.sy(point[1])
|
||||
rx = radius / SCREEN_WIDTH * self.canvas.winfo_width()
|
||||
ry = radius / SCREEN_HEIGHT * self.canvas.winfo_height()
|
||||
rx = radius / self.width * self.canvas.winfo_width()
|
||||
ry = radius / self.height * self.canvas.winfo_height()
|
||||
self.canvas.coords(item, x - rx, y - ry, x + rx, y + ry)
|
||||
|
||||
def position_shapes(self) -> None:
|
||||
@@ -273,8 +293,8 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
if kind == "gaze":
|
||||
sample: GazeSample = value # type: ignore[assignment]
|
||||
gaze = (
|
||||
sample.x_norm * SCREEN_WIDTH,
|
||||
(1.0 - sample.y_norm) * SCREEN_HEIGHT,
|
||||
sample.x_norm * self.width,
|
||||
(1.0 - sample.y_norm) * self.height,
|
||||
)
|
||||
samples.append((sample, gaze))
|
||||
else:
|
||||
@@ -357,8 +377,8 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
if self.block == "pursuit":
|
||||
phase = elapsed / 10.0 * math.tau
|
||||
self.target = (
|
||||
SCREEN_WIDTH / 2 + 760 * math.sin(phase),
|
||||
SCREEN_HEIGHT / 2 + 390 * math.sin(phase * 2),
|
||||
self.width / 2 + self.pursuit_amp_x * math.sin(phase),
|
||||
self.height / 2 + self.pursuit_amp_y * math.sin(phase * 2),
|
||||
)
|
||||
self.position_shapes()
|
||||
for sample, gaze in gaze_samples:
|
||||
@@ -414,10 +434,13 @@ class ExerciseWindow(SurfaceTagWindow):
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
if args.list_monitors:
|
||||
print(format_monitor_list())
|
||||
return
|
||||
validate_args(args)
|
||||
if not 0 <= args.confidence <= 1:
|
||||
raise SystemExit("error: --confidence must be between 0 and 1")
|
||||
if args.screen_diagonal <= 0:
|
||||
if args.screen_diagonal is not None and args.screen_diagonal <= 0:
|
||||
raise SystemExit("error: --screen-diagonal must be greater than 0")
|
||||
if args.viewing_distance <= 0:
|
||||
raise SystemExit("error: --viewing-distance must be greater than 0")
|
||||
|
||||
Reference in New Issue
Block a user