38 Commits
Author SHA1 Message Date
DeeJanuz 67a3c02e31 ft-camd: map a camera whose dark frames are all zeros
Through the ISP (no colour module), the near-black exposures come out
all zeros, identical every time, so their dequeues change no buffer and
ft-camd never finished learning the side cameras' buffers ("can't tell
which buffers are this camera's yet"): the side pair published nothing.
Such an index is now left unmapped and its frames counted as dark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 89ec1608d9)
2026-10-05 13:55:18 -06:00
DeeJanuz 662919babf camcheck: no nested parentheses in the missing cameras' message
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 917ef9d29c)
2026-10-05 13:55:17 -06:00
DeeJanuz 98bd6ce21c Hand recorder: headsets without the colour module
Without the Arcturus module, XRService runs the side cameras through the
ISP ("ISP enabled for tracking cameras (main VFE available)"): NV12 on
vfe0 and vfe1, pitch 1152. ft-camd published only grey cameras, so it
dropped them, and the recorder stopped at "ft-camd publishes only 2 of 4
mono cameras" whatever was restarted.

- ft-camd reads a tracking camera's NV12 luma as its grey image.
- ft-hands and check_sides name the cameras by XRService's numbering in
  its log (TrackingCameraInit index 0-3), not by capture pipe, which
  moves with the module; a camera whose size isn't its calibration's is
  left out. sides.json says which device each camera was.
- camcheck reports the camera map, the ISP routing and which cameras
  ft-camd is missing; the recorder says so instead of "restart it",
  restarts its own ft-camd once when that's the only problem, and keeps
  the map in session.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4db5266cf7)
2026-10-05 13:55:17 -06:00
DeeJanuzandClaude Opus 5.5 2351cec310 hand recorder: final consent text (2026-10-03), residency check, installer
Consent 2026-10-03, after a non-lawyer review: who runs this and how to reach
them, the dataset is public (Hugging Face, possibly abroad), the Hugging Face
username shows next to the contributor id, purposes (no identification), safety,
the maintainer grant passes to whoever maintains Frametop next, withdrawal
before and after merge, rights such as the GDPR's, and what a new version means.
Residents of Illinois, Texas and Washington can't take part for now (biometric
privacy laws): a third checkbox, profile consent.region_ok, checked by
validate.py from this consent version on. The DRAFT banners are gone, so uploads
no longer need FT_HANDREC_ALLOW_UPLOAD.

hands/rec/install.sh installs the recorder on a Frame with Frametop: container
packages, hand tracking and panel builds, ft-camd's capabilities, menu entry.

test_qml_backend also checks each call's argument count against the slots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:35:18 -06:00
DeeJanuzandClaude Opus 5.5 1eb120e6b1 hand recorder export: no controller poses without controllers, nothing in deleted ranges
When the checklist says no controllers, exported poses.jsonl has left and right
null and feedback lines carry no controller state: controllers left switched on
still get tracked (one wandered 2 m in a real session) and would read as the
hands' ground truth. Poses and live-tracker feedback inside deleted ranges are
left out too, as the images there are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:24:39 -06:00
DeeJanuzandClaude Opus 5.5 8321a99121 hand recorder: take.json keeps camera clock samples
sets.bin's capture_ns is CLOCK_MONOTONIC_RAW; poses.jsonl and prompts.jsonl are
CLOCK_MONOTONIC. On 2026-10-03 the two were 0.80 s apart during a session and
1.11 s apart five hours later, so images can't be paired with poses by
capture_ns. take.json now samples RAW minus MONOTONIC as each recording part
starts and stops (as ft-hands' raw_minus_mono_ns), so readers can put each
exposure on the poses' clock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 13:32:32 -06:00
DeeJanuzandClaude Opus 5.5 ea483f7ce3 hand recorder: log in from the Upload page, three-step page, no terminal
The Upload page is now three numbered steps: choose the export, log in to
Hugging Face, upload. Log in runs hub.py login, huggingface_hub's browser
login (OAuth device code, as hf auth login does): the link opens in the
browser and the page shows the code to enter, with Copy code and Cancel.
hub.py saves the token; the window never sees one, and nobody pastes one.

The terminal upload and the login command are gone from the page, and
UPLOAD.md is now a short 'About uploading' under the steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 13:01:08 -06:00
DeeJanuzandClaude Opus 5.5 e62ebb8669 hand recorder: login command works from Frametop's Konsole
Frametop's Konsole sets XDG_RUNTIME_DIR=/run/user/UID/frametop, where podman finds
no container state, so 'distrobox enter dev -- hf auth login' failed with a crun
error. The command the Upload page shows now sets the real runtime folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:48:17 -06:00
DeeJanuzandClaude Opus 5.5 8dbcdecf90 Hands: fix Export doing nothing, and show it's busy at once
af2ea7c put _stay_awake between exportSession and its @Slot, so the
window's Export button called a method QML couldn't see. A new test checks
every backend call in main.qml against Backend's slots and properties.
Export and Upload now say Exporting…/Uploading… with a spinner the moment
they're pressed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:29:04 -06:00
DeeJanuzandClaude Opus 5.5 af2ea7c0ee Hands: upload from the headset, then plug in and leave it
Export and upload are done in the headset now: the export page only notes
that VR may stutter a little. Upload opens the pull request first (a
draft) and shows its link, telling the person to plug in the headset and
leave it until it says Uploaded; the files then go to refs/pr/N, and the
pull request is marked open at the end. A retry of the same export goes on
in the same pull request. While an export or upload runs, a host unit holds
a logind sleep inhibitor so the Frame stays awake with the headset off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:20:00 -06:00
DeeJanuzandClaude Opus 5.5 30e02e9db7 Hands: push steps say to follow the hollow circle, not the blue dot
The dot is the current tracker's distance guess, often wrong; the ring is
where the hands should be.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:59:39 -06:00
DeeJanuzandClaude Opus 5.5 a05e500d30 Hands: the recorder's host commands run from the home folder
host-spawn starts a host command in the caller's folder. Started from /tmp,
the app's folder in the container is /run/host/tmp, which the host doesn't
have, so starting ft-camd (and every other host command) exited 127.
host_command now runs from home (env -C), and the launcher cds there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:45:40 -06:00
DeeJanuzandClaude Opus 5.5 fda6302bd3 Hands: the recorder measures the light itself
The checklist page measures the light when it opens, starting ft-camd if
nothing runs it (and stopping it on quit), instead of saying the cameras
aren't running. The round's lighting defaults to what the cameras measure:
daylight or indoor, from the mono cameras' ambient infrared. Lamps give off
little infrared, so dim and normal rooms read alike; picking dim, room or
daylight still overrides it. session.json gets source, measured and
ambient_ir.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:37:54 -06:00
DeeJanuzandClaude Opus 5.5 a533bb973a Hands: ft-hands works out which side camera is which
ft-camd tells the side cameras apart by XRService's buffer allocation order,
which some XRService starts reverse; both of 2026-10-02's starts did, so the
cutouts missed the hands. HANDS_SWAP_SIDES=auto (the default) has ft-hands
vote from hands seen in both side cameras: the landmark rays meet in front
of both cameras only under the right naming. While undecided it probes the
exchanged naming with the landmark model. It decides in about 2 s of hands
(right on all 7 recordings replayed), swaps the views in place, and publishes
sides.json. 0 and 1 still force it, with a warning when the hands disagree.

Recordings carry each part's naming and the session's decision; review,
export, validate and ft-handreplay put the names right, and takes.py sides
records a decision by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:24:13 -06:00
DeeJanuzandClaude Opus 5.5 97b7fc837c Hands: shorter recording sessions with pose sweeps
The second real session took 16 minutes, half of it 36 still poses. Labels
come from the auto-labeller, so what matters is variety, not clean holds.
A sweep step shows a strip of pose pictures and lights one every 4 s while
the hands move slowly near and far; each cue is a prompt event with
"cue": true. The core session is now 15 steps, about 5 minutes recorded.

The pose groups, the one-hand sweeps' groups and the cue order are shuffled
per session, seeded from its id and saved in session.json. A quick round
(--quick, or the checklist's choice) is about 2 minutes for extra lighting.
Touch the dot has 6 dots, the push sections two heights.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:30:50 -06:00
DeeJanuzandClaude Opus 5.5 3ebae6a88f Hands: check the tracking cameras before recording, and watch for losing them
After the headset wakes, XRService sometimes fails to load the colour module's
VCINT FPGA image; then only the two side cameras run, without the IR light,
and the tracker finds no hands. hands/camcheck.py reads XRService's log, the
video nodes it holds and ft-camd's ring, and says ok, degraded or unknown.

The recorder won't start while degraded (--ignore-cameras overrides it), offers
a confirmed SteamVR restart, and stops the first hand-size step when the
tracker sees no hand at all. ft-camwatch (unit file only, not enabled) follows
the log, notifies, and with CAMWATCH_AUTO_RESTART=1 restarts SteamVR when the
headset isn't worn and nothing else uses VR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:12:16 -06:00
DeeJanuzandClaude Opus 5.5 79eb251d6c Hands: the headset button as Next, clearer push steps
The headset's right-side click button (KEY_SELECT on gpio-keys, read without
a grab) now works the session: Next while a step waits, pause during a hold,
resume while paused. With no mouse connected the hints lead with it.

The push sections say plainly to push straight out from the headset and pull
back, with a side-view picture of the head, the headset and the arrow, and
the bar's ends read "At your chest" and "Arm out". The bar labels are sent
as one field, so labels with spaces no longer split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 17:19:16 -06:00
DeeJanuzandClaude Opus 5.5 35196e2955 Hands: step mode and pose pictures for the hand recorder
The first real session moved on every 5 s with text only, too fast to follow.
Each step now waits for Next (Space or the window's button), counts down 3-2-1
while recording, then holds. P pauses, R redoes a step, S skips a section;
"Advance by itself" (--auto) keeps the old timed flow. Nothing records while
a step waits: each step is its own recording part.

The panel and the window show a picture of each pose (hands/rec/poses,
generated by make_poses.py from a parametric hand, MIT) and a diagram of where
to hold the hands and how far out. prompts.jsonl gains ready, wait and redo
events; session.json gains mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:34:45 -06:00
DeeJanuzandClaude Opus 5.5 1fb6e7a38e Hands: upload from the hand recorder's window, export checks, a rehearsal
hands/rec/validate.py (standard library; Linux and Windows, Python 3.12+)
checks an export before upload and when it's received: SHA256SUMS, an
allow-list of files, the manifest's schema and keys, the consent version,
a uuid4 contributor, no identifying fields in calibration.json or
device.json, every sets.bin.zst decompressed to its end as a stream with
each FHSET01 header checked against the manifest, jsonl lines, the total
size. It decompresses with compression.zstd, zstandard or the zstd
program. validate.py DIR [--json].

hands/rec/hub.py uploads an export with huggingface_hub, as a pull
request to contributions/<contributor>/<session>: validate first, refuse
a repeat of the same export, check the login (whoami) and access
(auth_check), upload_folder(create_pr=True) with the manifest summary as
the description, then record the PR under "uploads" in session.json.
Errors are explained (terms not accepted, not found, 401/403, network).
--dry-run makes no network calls. FT_HANDREC_DATASET overrides
HF_DATASET (DeeJanuz/frametop-hands); while the texts are drafts a real
upload needs FT_HANDREC_ALLOW_UPLOAD=1.

The Upload page shows the login with "Check again" and how to run
hf auth login in a terminal (the token never enters the window), then
Upload with a phase, progress and Cancel (hub.py as a child process), the
PR link, and a warning for an export uploaded before. The manual
command stays as the fallback. ft-handrec --hub-dry-run.

session.py also saves device.json: cv.cad_from_cal and head from
/persist/device_config.json, the labeller's shape, nothing identifying;
export copies it. Session ids with a -N suffix are accepted everywhere.

hands/rec/rehearse.sh runs it all without the headset: ft-ringplay plays
30 s of a capture into a ring, session.py records a short test script
with ft-handpanel --no-vr and a tracker, then export, validate and a
dry-run upload (--repo ID uploads for real). It runs in one frame-job
scope, deletes its data and stops its processes, also on Ctrl+C.

hands/rec/tests/test_validate.py covers good and broken exports and hub.py
without the network. The dev container gains python3-huggingface-hub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:53:53 -06:00
DeeJanuzandClaude Opus 5.5 27b731878c Hands: the hand recorder's window, review and export
hands/rec/ft_handrec.py + main.qml (Kirigami, dev container; host launcher
hands/rec/ft-handrec): consent (CONSENT.md, asked again when its version
changes; profile.json with a random contributor id), the before-you-start
checklist with the lighting and free-space checks, the session controls
(Space pauses, Esc stops), review with a frame-set viewer that deletes
ranges, takes and sessions, export with progress and cancel (warns while
the headset is worn), and the upload page (UPLOAD.md, the
huggingface-cli command; HF_DATASET is a placeholder). --dry-run runs
sessions without processes, for testing.

hands/rec/takes.py (standard library): indexes sets.bin and sets-N.bin
without reading pixels, reads one set's cameras, keeps deleted ranges in
take.json, and exports: deleted sets left out, zstd -10 -T2 at nice 19,
manifest.json and SHA256SUMS, nothing left behind on cancel.

CONSENT.md and UPLOAD.md are drafts pending a legal review; the window
says contributions aren't open yet. The dev container gains zstd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:50:22 -06:00
DeeJanuzandClaude Opus 5.5 3b2f065c85 Hands: the hand recorder's session runner and guided script
hands/rec/session.py runs a recording session from script.json: it starts
ft-camd and a tracking ft-hands as transient units only if they aren't
running, records each section as one take (ft-hands --record-only at
10 sets/s, a new sets-N.bin after each pause), drives ft-handpanel, and
writes session.json, calibration.json (identifying fields removed),
prompts.jsonl and take.json. Feedback comes from the live hands file and,
in the controller sections, from the panel's device poll. It also runs
from the command line (--dry-run, --speed, --ring, --no-start).

hands/rec/script.json: 11 sections, about 9 minutes without the object
and controller sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:48:59 -06:00
DeeJanuzandClaude Opus 5.5 c6531148d7 Hands: the recorder's worn check goes by the panel's backlight, as frame-job does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:36:17 -06:00
DeeJanuzandClaude Opus 5.5 7b80592665 Hands: ft-handpanel, the hand recorder's headset panel
A head-locked SteamVR overlay for the hand recorder (hands/rec/DESIGN.md):
1.2 m ahead, 12 degrees up, 36 degrees wide, drawn with stb_truetype
into three shared DMA-BUFs as ft-gazepanel does. It shows the title,
step, wrapped instruction, note, countdown, hand chips, near/far bar
and a "Paused" cover, driven over @ft_handpanel.

It also places the touch target, a 2 cm dot in its own overlay fixed
in the room where the head was at the first command for that point,
and logs head and controller poses to poses.jsonl at 250 Hz from a
thread of its own. Both threads take one lock around OpenVR calls.

--no-vr prints each picture's state to stdout (and --dump writes the
pictures), for testing without a headset. hands/rec/build.sh builds it
in the dev container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:35:43 -06:00
DeeJanuzandClaude Opus 5.5 49b8e050db Hands: --record-hz, and the hand recorder's design (hands/rec/DESIGN.md)
ft-hands --record-hz N records at most N frame sets a second, for the hand recorder (10).
DESIGN.md lays out the recorder: the headset panel, the session runner and its script,
the files, review and export, consent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:13 -06:00
DeeJanuzandClaude Opus 5.5 ac142f6e4a ft-cutouts status: only the current run's tracker lines
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:26:59 -06:00
DeeJanuzandClaude Opus 5.5 b417534425 Hands: ft-cutouts, the hand cutouts without pinches and grips
hands/ft-cutouts on|off|status starts ft-camd and ft-hands as transient
user units with ft-hands' new --no-gestures: hands are published for
ft-screens' cutouts, but no pinch or grip is detected, so nothing clicks
or drags and a closing hand doesn't raise the tracking rate. It needs a
build and ft-camd's capabilities, not hands/run.sh install. Its units
conflict with ft-handsctl's, so each stops the other, and they stop with
SteamVR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:22:52 -06:00
DeeJanuzandClaude Opus 5.5 7bcda94267 Defer hand tracking; the README leads with what Frametop does now
- install.sh no longer offers hand tracking (it's heavy on the CPU and
  needs more work); hands/ still builds and installs by hand
- the build container drops python3-opencv and python3-numpy, which
  only hand tracking's tools used: Fedora's OpenCV pulls in over a GB
- README: a new opening and feature list, the Use section by topic
  (screens, mouse, floating windows, profiles, gaze), and new known
  limitations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:20:39 -06:00
DeeJanuzandClaude Opus 5.5 0280e7441d Installers: ask for sudo in the terminal, and cope with SteamVR off
- frame_sudo (scripts/_env.sh) replaces three copies of sudo_run. On
  the Frame it asks in the terminal even when stdin isn't one, which
  install.sh's steps never had: saying yes to the Bluetooth fixes or
  hand tracking stopped the install with "no terminal for sudo".
  From a PC it asks through ssh -t when .env has no password.
- start_with_steamvr: the pointer, power, and gaze services restart
  when SteamVR runs (a re-install runs the new code) and are left to
  start with it when it doesn't, instead of failing the install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:04:49 -06:00
DeeJanuzandClaude Opus 5.5 12c42cd455 Bring the docs up to date with the code
- floating-windows.md and profiles.md describe what's built, with what
  isn't listed as such; the plans, phases, and branch notes are gone
- hands-migration.md is gone: the move is done; its open items are in
  hands/README.md's Known issues
- reference.md: Layout & profiles, every action, key combinations,
  floating windows, the gaze pointer, and hand tracking as they are
- design.md gets the KWin findings from floating-windows.md
- README, gaze/README, AGENTS, hazards, gaze-controllers, and the
  example config catch up with gaze, hands, and the stuck-key fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:47:53 -06:00
DeeJanuzandClaude Opus 5.5 0ac2b10cd7 Remove leftovers before a release
- gaze/tracker/lab/eyes_track.py: nothing used it
- Input Settings: the pointer role backend, whose page was removed
- ft-screens: no log line for every floating-window resize
- hands: ft-hands --help gives the palm-down default (1: off), the
  uninstall removes ft-handsctl's link, .frame-job is ignored
- Display Settings: "Save as profile…", not "Save current arrangement"
- two stale comments (ft-pointer's grabprobe, ft-gaze)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:47:53 -06:00
DeeJanuzandClaude Opus 5.5 c2739cb0df Hands: park hand tracking behind ft-handsctl
Hand tracking no longer starts with SteamVR: hands/run.sh install leaves
the units disabled and links hands/ft-handsctl into ~/.local/bin, which
turns it on and off (on | off | status | log | cutouts on|off | gestures).
With it on, hands show through the screens; pinches and grips move the
pointer only with POINTER_HANDS=1, now off by default. ft-camd's service
runs the mono cameras only: while the headset is worn, the colour module
writes just a half-size image into the top-left quarter of its buffers,
which ft-camd can't use yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:08:54 -06:00
DeeJanuzandClaude Opus 5.5 5ca4160a7e Hands: fixes from the second headset test (pinches)
- The palm-down filter is off by default: the user's deliberate pinches,
  hand raised, read 0.90-0.99, like typing.
- Typing is told apart by the keyboard instead: the input relay sends the
  pointer helper "typing" on key presses, and it takes no pinch within
  POINTER_PINCH_TYPING (1 s) of one.
- Grips are still held to hands raised (POINTER_GRIP_BELOW, 0.35 m below
  the eyes); pinches aren't, since the user's own sat 0.35-0.45 m below,
  elbow resting.
- A grip doesn't begin with the thumb on the index tip: that's a pinch
  with the other fingers curled, which was taken for a grip.
- A pinch's point is the index and middle knuckles: the tips' midpoint
  moved 1-2 cm as the pinch opened, dragging every release off its press.
- ft-hands --gesture-log prints what the detectors measure, 10 times a
  second.

Pinches still aren't reliable enough to use; hand tracking is parked for
now in favour of the controllers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:07:58 -06:00
DeeJanuzandClaude Opus 5.5 50c14545ed Hands: pick the cameras by the light, and detect grips
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>
2026-09-30 14:31:17 -06:00
DeeJanuzandClaude Opus 5.5 40a2f39b2a ft-camd: keep colour trouble away from the mono cameras, and idle colour
A colour camera probes at most 4 buffers a frame (each probe syncs a
~9 MB buffer's cache, which made the mono cameras miss frames), and one
that goes stale twice in a row is paused (10 s, doubling to 160 s) and
learned again instead of ft-camd exiting. The colour cameras run at 2 fps
until a reader asks for more in frametop-hands/color-fps, which saves
most of their decoding while only their brightness is needed. Each mono
camera's latest near-black frame's mean goes in the ring (dark_mean), a
measure of the room's IR light. The service now starts with --with-color;
HANDS_CAMERAS=mono leaves the colour cameras out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:31:16 -06:00
DeeJanuzandClaude Opus 5.5 e2abaa06b6 Hands: fixes from the first headset test
- The runtime files move to /run/user/UID/frametop-hands/: the desktop
  session deletes /run/user/UID/frametop at every start.
- The cutout copy shader runs at highp: mediump (16-bit on Adreno)
  stepped 1.7 texels across a 3440-pixel screen.
- One hand no longer pinches both sides after its left/right call flips
  mid-pinch, and --pinch-palm-down (0.6) holds back pinches with the palm
  facing down (typing on a lap keyboard).
- ft-camd judges a colour frame fresh by its luma rows only, and logs
  per-buffer changes at stale colour frames with FT_CAMD_DEBUG=1.
- hands/run.sh caps skips the setcap when ft-camd already has them.
- The replay tool dumps poses (--poses), and its pinch events carry the
  hand id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:00:54 -06:00
DeeJanuzandClaude Opus 5.5 499035216c Make hand tracking a Frametop component
- Programs: ft-camd (the camera broker), ft-hands (the tracker), and
  ft-handreplay and ft-ringplay for recordings, built by hands/build.sh
  into hands/build/ with one Makefile. The first build fetches ncnn at
  frame-hands' pinned tag and builds it with the same options.
- ft-camd gets its privileges from file capabilities (CAP_SYS_PTRACE,
  CAP_PERFMON, CAP_DAC_READ_SEARCH) that hands/run.sh install sets with
  sudo, and drops them once set up. It still works under sudo. It runs
  on the host, linked statically, as frametop-camd.service. ft-hands
  runs in the dev container as frametop-hands.service. Both start and
  stop with SteamVR.
- Files move to /run/user/UID/frametop/ (cam-ring, hands, gestures),
  not $XDG_RUNTIME_DIR, which a terminal in the Frametop desktop has
  its own of. SIGUSR1 recordings go to ~/.local/share/frametop/hands.
- The calibration is read through /run/host in the container.
- Settings: HANDS_SWAP_SIDES and HANDS_CPUS in frametop.conf.
- install.sh offers hand tracking as an optional last step.
- The container gets jsoncpp-devel, glibc-static, and NumPy and OpenCV
  for the Python tools.
- tools/ring.py reads the ring, and models/NOTICE credits the
  Apache-2.0 models.

Checked: ft-handreplay gives identical summaries and byte-identical
depth dumps to frame-hands' fh-replay on both 2026-09-29 recordings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:15:07 -06:00
DeeJanuzandClaude Opus 5.5 3e3d31c728 Lay out hands/ like Frametop's other components
trackd/ becomes track/, and the calibration helper the Python tools
import moves from the prototype's folder into tools/. The model
development tools (nettest and the scripts that compare it with the
Python models or cut int8 calibration crops) stay in frame-hands with
the prototype they need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:01:10 -06:00
DeeJanuzandClaude Opus 5.5 1a76d1560b Bring in frame-hands' hand tracking under hands/
The history of frame-hands (~/Desktop/Projects/frame-hands on the
Frame), filtered to what moves: the camera broker (camd/), the tracker
and its offline tools (trackd/), the shared file layouts (include/), the
ncnn models, the analysis tools, and the calibration and model helpers
they import from the Python prototype. The reverse-engineering notes,
probes, camprobe, and the rest of the prototype stay in frame-hands.
Unchanged here: the renames to ft- names and Frametop paths follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:59:32 -06:00