mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 05:00:08 +02:00
Quick check: put the pointer on the dot, and one limit for learning
After the quick check's capture, in gaze mode, the dot stays where it is in the
room (the panel's new "lock") and the gaze has the pointer again. You put the
pointer on the dot as you'd correct a click, with the mouse or Meta+J and the
head. That click clicks nothing: the helper sends it as "calverify", learned
whatever its size. Over POINTER_GAZE_NUDGE_MAX, the capture runs again, up to
3 times. A right click or Meta+K skips it.
POINTER_GAZE_NUDGE_MAX is now the one limit: 15 degrees by default (was 8),
and ft-gazed's own limits (8 for SteamVR's tracker, 25 for ours) follow it. A
keyboard correction past it isn't learned but opens a quick check ("recheck"),
in place of the 30 degree keyboard limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cbad7bc470
commit
362968198b
9 files changed
+180
-55
No files matched your search
+21
-13
@@ -44,9 +44,11 @@ click, it sends "lesson <raw yaw> <raw pitch> <true yaw> <true pitch>": where th
|
||||
was when the mouse took over, and where the pointer was when you clicked (you were looking
|
||||
there). The gap is the tracker's error there. The raw gaze is the one sent, so it also says
|
||||
when that look was (the history of what was sent), and so what each eye read then:
|
||||
- SteamVR: each eye learns its own error, unless the gaze was more than LESSON_MAX degrees
|
||||
- SteamVR: each eye learns its own error, unless the gaze was more than
|
||||
POINTER_GAZE_NUDGE_MAX degrees (frametop.conf, 15 by default; the helper's limit too)
|
||||
past the correction (then it wasn't a nudge onto what you looked at);
|
||||
- our tracker: the look goes to it as a click ("click T YAW PITCH" on @ft_eyes), as the
|
||||
- our tracker: unless it was more than POINTER_GAZE_NUDGE_MAX off, the look goes to it as
|
||||
a click ("click T YAW PITCH" on @ft_eyes), as the
|
||||
probe's clicks do, and it learns how far the headset has moved on your face. That's
|
||||
what it gets wrong, and after the headset was off, the first click resets it;
|
||||
- either way, how far off each eye was (before the lesson taught it anything) goes to the
|
||||
@@ -70,6 +72,8 @@ tracker's shared memory read-only.
|
||||
|
||||
Control socket: abstract unix datagram "@ft_gazed":
|
||||
lesson <rhy> <rhp> <thy> <thp> from the pointer helper (see above)
|
||||
calverify <rhy> <rhp> <thy> <thp> the same, the quick check's second step (gazecheck.py)
|
||||
recheck <deg> a keyboard correction past POINTER_GAZE_NUDGE_MAX: a quick check
|
||||
status reply: one JSON object
|
||||
forget drop what the lessons taught (the calibration stays)
|
||||
reload read calibration.json and the settings again
|
||||
@@ -122,8 +126,7 @@ SOURCES = ("action", "mmap1", "mmap2", "left", "right") # the ones with a calib
|
||||
SIDES = ("left", "right") # ft-gaze's order, and the sources for each eye alone
|
||||
TRACKERS = ("steam", "own")
|
||||
BIASES = ("auto", "left", "right")
|
||||
LESSON_MAX = 8.0 # degrees past the correction
|
||||
OWN_LESSON_MAX = 25.0 # our tracker: after the headset was off, its first clicks can be 10-17 off
|
||||
NUDGE_MAX = 15.0 # degrees: POINTER_GAZE_NUDGE_MAX's default, the largest lesson taken
|
||||
HISTORY = 12.0 # seconds of the gaze sent, to find a lesson's look (the helper sends it up to 10 s later)
|
||||
LOOK = 0.3 # seconds of samples before that moment make the look (the probe's fixation)
|
||||
RETRY = 3.0 # seconds before starting ft-gaze again
|
||||
@@ -150,7 +153,7 @@ def log(msg):
|
||||
|
||||
|
||||
def read_settings():
|
||||
"""(tracker, eye bias) from frametop.conf, defaults for anything missing or unknown."""
|
||||
"""(tracker, eye bias, nudge max) from frametop.conf, defaults for anything missing or unknown."""
|
||||
conf = {}
|
||||
try:
|
||||
for line in CONF.read_text().splitlines():
|
||||
@@ -162,7 +165,11 @@ def read_settings():
|
||||
pass
|
||||
tracker = conf.get("GAZE_TRACKER", "steam")
|
||||
bias = conf.get("GAZE_EYE", "auto")
|
||||
return tracker if tracker in TRACKERS else "steam", bias if bias in BIASES else "auto"
|
||||
try:
|
||||
nudge = min(max(float(conf.get("POINTER_GAZE_NUDGE_MAX", NUDGE_MAX)), 1.0), 30.0) # the helper's range
|
||||
except ValueError:
|
||||
nudge = NUDGE_MAX
|
||||
return tracker if tracker in TRACKERS else "steam", bias if bias in BIASES else "auto", nudge
|
||||
|
||||
|
||||
def mtime(path):
|
||||
@@ -177,7 +184,7 @@ class Service:
|
||||
self.override, self.verbose, self.to = source, verbose, to
|
||||
self.source = source or "mmap1" # the older path's source
|
||||
STATE.mkdir(parents=True, exist_ok=True)
|
||||
self.tracker, self.bias = read_settings()
|
||||
self.tracker, self.bias, self.nudge_max = read_settings()
|
||||
self.conf_mtime = mtime(CONF)
|
||||
self.models = {name: Correction() for name in SOURCES}
|
||||
self.mode = DEFAULT_MODEL
|
||||
@@ -238,7 +245,7 @@ class Service:
|
||||
|
||||
def load_settings(self):
|
||||
self.conf_mtime = mtime(CONF)
|
||||
tracker, bias = read_settings()
|
||||
tracker, bias, self.nudge_max = read_settings()
|
||||
if (tracker, bias) != (self.tracker, self.bias):
|
||||
log(f"tracker {tracker}, eye bias {bias}" + (f" (--source {self.override} wins)" if self.override else ""))
|
||||
self.tracker, self.bias = tracker, bias
|
||||
@@ -313,16 +320,18 @@ class Service:
|
||||
eyes.append((statistics.median(e[0] for e in seen), statistics.median(e[1] for e in seen)) if seen else None)
|
||||
return t, eyes
|
||||
|
||||
def lesson(self, rhy, rhp, thy, thp):
|
||||
def lesson(self, rhy, rhp, thy, thp, limit=True):
|
||||
"""Learn a lesson; limit=False takes it whatever its size (the quick check's own)."""
|
||||
kind = self.kind
|
||||
limit = self.nudge_max if limit else math.inf
|
||||
rec = {"time": time.time(), "kind": kind, "raw": [rhy, rhp], "true": [thy, thp], "wear": self.steam.worn()}
|
||||
if kind == "source":
|
||||
dy, dp = thy - rhy, thp - rhp # the whole error there
|
||||
cy, cp = self.correction(self.source, rhy, rhp)
|
||||
left = math.hypot(dy - cy, dp - cp)
|
||||
rec.update(source=self.source, model=self.mode, correction=[cy, cp], lesson_deg=left)
|
||||
if left > LESSON_MAX:
|
||||
rec["refused"] = f"more than {LESSON_MAX} deg past the correction"
|
||||
if left > limit:
|
||||
rec["refused"] = f"more than {limit} deg past the correction"
|
||||
else:
|
||||
self.lives[self.source].add({"time": rec["time"], "hy": rhy, "hp": rhp, "dy": dy, "dp": dp,
|
||||
"wy": 1.0, "wp": 1.0, "how": "pointer"}, self.models[self.source], self.mode)
|
||||
@@ -333,7 +342,6 @@ class Service:
|
||||
weights = self.weights[self.tracker if kind == "own" else "steam"]
|
||||
rec.update(tracker=self.tracker if kind == "own" else "steam", bias=self.bias, lesson_deg=left, look_t=t,
|
||||
eyes=eyes, weights=[round(w, 3) for w in weights.weights()])
|
||||
limit = OWN_LESSON_MAX if kind == "own" else LESSON_MAX
|
||||
if t is None:
|
||||
rec["refused"] = "that gaze isn't in the last few seconds sent"
|
||||
elif left > limit:
|
||||
@@ -667,7 +675,7 @@ class Service:
|
||||
elif words[:1] == ["forget"]:
|
||||
self.forget_lessons()
|
||||
reply = "ok"
|
||||
elif words[:1] and words[0] in ("quickcal", "calibrate", "calaccept", "calquit"):
|
||||
elif words[:1] and words[0] in ("quickcal", "calibrate", "calaccept", "calquit", "calverify", "recheck"):
|
||||
reply = self.checks.command(words)
|
||||
elif words[:1] == ["eyes"] and len(words) == 2:
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user