mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 07:00:14 +02:00
ft-hands tracks with the mono IR cameras in dim light and with every camera (or the colour pair, HANDS_BRIGHT) in bright light, going by the colour frames' mean brightness with hysteresis and a 2 s hold (HANDS_CAMERAS=auto, the default; mono, color and all fix it). Colour frames are placed on the mono cameras' clock by their dequeue time, and a view in a camera a step lacks waits for that camera's next frame. A grip (a closed hand) is a second gesture next to the pinch, in version 2 of the gestures file: every fingertip curled toward the wrist, beginning only on a hand seen open within a second and held up in front. On the 2026-09-30 lit recording that leaves 6 false grips of 14, all with the hands on the desk; pinch counts are unchanged. ft-handreplay logs grips and finger curl, watch_gestures.py shows them, and tools/cut_sets.py copies a few sets out of a recording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
4.4 KiB
Python
96 lines
4.4 KiB
Python
"""Watch the gestures ft-hands publishes, live: pinches and grips, begins, ends, and drags.
|
|
|
|
usage: python3 tools/watch_gestures.py [--every S] [--distance]
|
|
|
|
Prints a line when a pinch or a grip (a closed hand) begins or ends on either hand. It goes
|
|
by the counters, so a quick tap between two reads still shows. While one is held, every
|
|
--every seconds (default 0.1) it prints how far its point has moved since it began, in the
|
|
head frame (turning your head moves it too; a real consumer turns both points into the room
|
|
first, see include/fh_gestures.h). --distance also prints each hand's thumb-to-index
|
|
distance and finger curl, to see how close a gesture comes to the thresholds.
|
|
Version 1 files (pinches only) still work.
|
|
"""
|
|
import argparse
|
|
import mmap
|
|
import os
|
|
import struct
|
|
import time
|
|
|
|
HDR = struct.Struct('<8sIIQQQffff8x') # 64 bytes
|
|
SLOT = struct.Struct('<IIIIQQff3f3f') # 64 bytes
|
|
TRACKED, DOWN, LOST = 1, 2, 4
|
|
SIDES = ('left ', 'right')
|
|
KINDS = ('pinch', 'grip ')
|
|
|
|
|
|
def path():
|
|
return '/run/user/%d/frametop-hands/gestures' % os.getuid()
|
|
|
|
|
|
def read(m):
|
|
"""(header, [[pinch left, right], [grip left, right]]) under the sequence lock, or None
|
|
if it's being written. Version 1 has no grips: they read as all zero."""
|
|
for _ in range(10):
|
|
s1 = struct.unpack_from('<Q', m, 16)[0]
|
|
if s1 % 2 == 0:
|
|
h = HDR.unpack_from(m, 0)
|
|
kinds = 2 if h[1] >= 2 and len(m) >= HDR.size + 4 * SLOT.size else 1
|
|
g = [[SLOT.unpack_from(m, HDR.size + (k * 2 + s) * SLOT.size) for s in range(2)] for k in range(kinds)]
|
|
if kinds == 1:
|
|
g.append([(0,) * 14, (0,) * 14])
|
|
if struct.unpack_from('<Q', m, 16)[0] == s1:
|
|
return h, g
|
|
time.sleep(0.0005)
|
|
return None
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('--every', type=float, default=0.1, help='seconds between drag lines while held')
|
|
ap.add_argument('--distance', action='store_true', help="print each hand's pinch distance and finger curl")
|
|
a = ap.parse_args()
|
|
with open(path(), 'rb') as f:
|
|
m = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
|
|
first = read(m)
|
|
if first is None or first[0][0] != b'FHGEST01':
|
|
raise SystemExit('%s is not an ft-hands gestures file' % path())
|
|
h, g = first
|
|
print('version %d; thresholds: pinch begins under %.3f m, ends over %.3f m; grip begins with every finger '
|
|
'curled under %.2f, ends over %.2f' % (h[1], h[6], h[7], h[8], h[9]))
|
|
seen = [[(q[2], q[3]) for q in kind] for kind in g] # begins, ends
|
|
last_drag = last_dist = 0.0
|
|
while True:
|
|
got = read(m)
|
|
if got:
|
|
h, g = got
|
|
now = time.monotonic()
|
|
for k, kind in enumerate(g):
|
|
for s, q in enumerate(kind):
|
|
flags, hand, begins, ends, begin_ns, end_ns, dist, strength = q[:8]
|
|
point, begin_point = q[8:11], q[11:14]
|
|
if begins != seen[k][s][0]:
|
|
print('%s %s BEGIN (#%d, hand %d) at %+.3f %+.3f %+.3f %s %.3f' %
|
|
(SIDES[s], KINDS[k], begins, hand, *begin_point, 'curl' if k else 'd', dist), flush=True)
|
|
if ends != seen[k][s][1]:
|
|
held = (end_ns - begin_ns) / 1e9 if end_ns >= begin_ns else 0
|
|
print('%s %s %s after %.2f s' % (SIDES[s], KINDS[k], 'LOST' if flags & LOST else 'END', held),
|
|
flush=True)
|
|
seen[k][s] = (begins, ends)
|
|
if flags & DOWN and now - last_drag >= a.every:
|
|
d = [point[i] - begin_point[i] for i in range(3)]
|
|
print('%s %s drag %+6.1f %+6.1f %+6.1f mm (%.0f mm)' %
|
|
(SIDES[s], KINDS[k], *(1000 * x for x in d), 1000 * sum(x * x for x in d) ** 0.5),
|
|
flush=True)
|
|
if any(q[0] & DOWN for kind in g for q in kind) and now - last_drag >= a.every:
|
|
last_drag = now
|
|
if a.distance and now - last_dist >= 0.2:
|
|
last_dist = now
|
|
print(' ' + ' '.join(
|
|
'%s %s' % (SIDES[s].strip(), 'd %.3f curl %.2f' % (g[0][s][6], g[1][s][6]) if g[0][s][0] & TRACKED
|
|
else '-') for s in range(2)), flush=True)
|
|
time.sleep(0.005)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|