mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 09:00:35 +02:00
fix(tracking): reduce eye images while capturing, with a backlog cap
The capture tool's stdout is block-buffered, so waiting for its directory name left every image on disk until the capture ended (3,554 PNGs in a 20 s run on the Frame). Detect the new capture directory instead, decode in three worker processes, and stop the capture if more than 900 images wait. On the Frame a 30 s run now peaks at 7 images on disk. Part of #27 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
fa49fab5bf
commit
42ec68ed51
3 files changed
+95
-23
No files matched your search
+26
-4
@@ -125,7 +125,7 @@ class FakeCaptureTool:
|
||||
stdout.flush()
|
||||
self.returncode = 1
|
||||
return self
|
||||
stdout.write(f"Writing capture to: {self.directory}\nCapturing images...\n")
|
||||
# Real output is block-buffered until exit, so nothing is printed here.
|
||||
stdout.flush()
|
||||
self.directory.mkdir()
|
||||
return self
|
||||
@@ -156,6 +156,7 @@ class CaptureLifecycle(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.root = Path(tempfile.mkdtemp())
|
||||
self.directory = self.root / "etcalib_test"
|
||||
(self.root / "etcalib_older").mkdir() # an earlier capture is not ours
|
||||
self.loaded = []
|
||||
|
||||
def tearDown(self):
|
||||
@@ -170,9 +171,8 @@ class CaptureLifecycle(unittest.TestCase):
|
||||
def capture(self, tool):
|
||||
class TestCapture(pulse.Capture):
|
||||
# A temporary directory stands in for /tmp/etcalib_*.
|
||||
WRITING = __import__("re").compile(r"Writing capture to: (\S+)")
|
||||
PREFIX = str(self.root)
|
||||
capture = TestCapture(20, runner=tool, loader=self.loader)
|
||||
PREFIX = str(self.root / "etcalib_")
|
||||
capture = TestCapture(20, runner=tool, loader=self.loader, workers=0)
|
||||
with patch.object(pulse.time, "sleep", lambda s: None):
|
||||
return capture, capture.run()
|
||||
|
||||
@@ -181,6 +181,7 @@ class CaptureLifecycle(unittest.TestCase):
|
||||
capture, frames = self.capture(tool)
|
||||
self.assertEqual(sorted(self.loaded), sorted(f"{e}_{i}.png" for e in ("left", "right") for i in range(6)))
|
||||
self.assertFalse(self.directory.exists()) # images and metadata removed
|
||||
self.assertTrue((self.root / "etcalib_older").exists())
|
||||
self.assertEqual(len(frames), 10) # frame 2 invalid in both eyes
|
||||
self.assertEqual(tool.command[-2:], ["--calib", "20"])
|
||||
self.assertTrue(all(isinstance(t, float) and eye in ("left", "right") for t, eye, _ in frames))
|
||||
@@ -191,6 +192,27 @@ class CaptureLifecycle(unittest.TestCase):
|
||||
self.capture(tool)
|
||||
self.assertIn("cameras unavailable", str(raised.exception))
|
||||
|
||||
def test_backlog_stops_capture_and_removes_images(self):
|
||||
class Stalled(FakeCaptureTool):
|
||||
def poll(self):
|
||||
for i in range(20):
|
||||
for eye in ("left", "right"):
|
||||
(self.directory / f"{eye}_{self.polls * 20 + i}.png").write_bytes(b"png")
|
||||
self.polls += 1
|
||||
return None
|
||||
tool = Stalled(self.directory)
|
||||
class TestCapture(pulse.Capture):
|
||||
PREFIX = str(self.root / "etcalib_")
|
||||
MAX_BACKLOG = 50
|
||||
capture = TestCapture(20, runner=tool, loader=self.loader, workers=0)
|
||||
capture.reduce = lambda final=False, original=capture.reduce: (
|
||||
original(final) if tool.polls > 3 else None) # reduction stalls
|
||||
with patch.object(pulse.time, "sleep", lambda s: None), self.assertRaises(RuntimeError) as raised:
|
||||
capture.run()
|
||||
self.assertIn("fell behind", str(raised.exception))
|
||||
self.assertEqual(tool.returncode, -15) # capture tool stopped
|
||||
self.assertFalse(self.directory.exists()) # no image left behind
|
||||
|
||||
def test_only_removes_capture_directories(self):
|
||||
capture = pulse.Capture(20)
|
||||
capture.directory = self.root
|
||||
|
||||
Reference in new issue
Block a user