mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 01:00:06 +02:00
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>
This commit is contained in:
1 parent
ea483f7ce3
commit
8321a99121
3 files changed
+23
-2
No files matched your search
+3
-2
@@ -57,11 +57,12 @@ Test hooks:
|
||||
poses.jsonl head and controller poses from ft-handpanel (below)
|
||||
take.json {"section", "title", "started_ns", "ended_ns", "status": "complete"|"stopped"|"skipped",
|
||||
"deleted": [[from_ns, to_ns], ...], "notes": "",
|
||||
"parts": {"sets.bin": {"names_swapped": false}, "sets-2.bin": {...}}}
|
||||
"parts": {"sets.bin": {"names_swapped": false}, "sets-2.bin": {...}},
|
||||
"clock": [[mono_ns, raw_minus_mono_ns], ...]}
|
||||
exports/<session>/ what export writes (below)
|
||||
```
|
||||
|
||||
All `_ns` times are CLOCK_MONOTONIC nanoseconds, the clock of `dqbuf_ns` in sets.bin and of `capture_ns` in the hands file. sets.bin's `capture_ns` is the camera clock (CLOCK_MONOTONIC_RAW).
|
||||
All `_ns` times are CLOCK_MONOTONIC nanoseconds, the clock of `dqbuf_ns` in sets.bin and of `capture_ns` in the hands file. sets.bin's `capture_ns` is the camera clock (CLOCK_MONOTONIC_RAW). The two drift apart with NTP's corrections: on 2026-10-03 RAW ran 0.80 s ahead, gaining about 10 ppm. take.json `"clock"` samples the difference (RAW minus MONOTONIC, as ft-hands' `raw_minus_mono_ns()`) when each part starts and stops, so a set's exposure time on the poses' clock is `capture_ns - raw_minus_mono_ns`, interpolated between samples. `dqbuf_ns` is on the right clock already, but a few ms after the exposure. Takes recorded before 2026-10-03 have no `"clock"`.
|
||||
|
||||
### profile.json
|
||||
|
||||
|
||||
@@ -99,6 +99,18 @@ def mono_ns():
|
||||
return time.clock_gettime_ns(time.CLOCK_MONOTONIC)
|
||||
|
||||
|
||||
def clock_sample():
|
||||
"""[CLOCK_MONOTONIC ns, CLOCK_MONOTONIC_RAW - CLOCK_MONOTONIC ns], as ft-hands' raw_minus_mono_ns().
|
||||
sets.bin's capture_ns is on the RAW clock and everything else on MONOTONIC; the two drift
|
||||
apart with NTP's corrections (0.8 s apart and ~10 ppm on 2026-10-03), so take.json keeps
|
||||
samples to turn capture_ns into MONOTONIC: capture_ns - offset, interpolated by time."""
|
||||
a = time.clock_gettime_ns(time.CLOCK_MONOTONIC)
|
||||
r = time.clock_gettime_ns(time.CLOCK_MONOTONIC_RAW)
|
||||
b = time.clock_gettime_ns(time.CLOCK_MONOTONIC)
|
||||
mid = (a + b) // 2
|
||||
return [mid, r - mid]
|
||||
|
||||
|
||||
def run_dir():
|
||||
return "/run/user/%d/frametop-hands" % os.getuid()
|
||||
|
||||
@@ -2010,6 +2022,7 @@ class Session:
|
||||
def _start_recording(self):
|
||||
t = self._take
|
||||
t["part"] += 1
|
||||
t["json"].setdefault("clock", []).append(clock_sample())
|
||||
if not self.dry_run:
|
||||
# a safety net only: the session ends the recording itself
|
||||
remaining = section_seconds(t["section"], worst=True, auto=self.auto) / self.speed
|
||||
@@ -2031,6 +2044,7 @@ class Session:
|
||||
self._log("recording part %d ended (%s)" % (rec.part, code))
|
||||
if self._take: # how this part's side cameras are named (sides.py)
|
||||
self._take["json"].setdefault("parts", {})[rec.file] = {"names_swapped": rec.names_swapped}
|
||||
self._take["json"].setdefault("clock", []).append(clock_sample())
|
||||
write_json(os.path.join(self._take["dir"], "take.json"), self._take["json"])
|
||||
|
||||
def _end_take(self, status):
|
||||
|
||||
@@ -89,6 +89,12 @@ class SessionTest(SessionBase):
|
||||
self.assertGreater(ev[2]["t"] - ev[1]["t"], 0.25e9)
|
||||
with open(os.path.join(s.session_dir, "session.json")) as f:
|
||||
self.assertEqual(json.load(f)["mode"], "step")
|
||||
# a clock sample as each part starts (2 steps: 2 parts), for capture_ns (RAW) -> MONOTONIC
|
||||
with open(os.path.join(s.session_dir, "takes", "01-poses", "take.json")) as f:
|
||||
clock = json.load(f)["clock"]
|
||||
self.assertEqual(len(clock), 2)
|
||||
self.assertTrue(all(len(c) == 2 and all(isinstance(v, int) for v in c) for c in clock))
|
||||
self.assertLess(clock[0][0], clock[1][0])
|
||||
# the panel: Ready?, then the countdown, then the section's word for the hold, the diagram
|
||||
self.assertIn("panel: action " + session.READY_TEXT, self.panel)
|
||||
self.assertLess(self.panel.index("panel: big 3"), self.panel.index("panel: big 1"))
|
||||
|
||||
Reference in new issue
Block a user