From acfd272450a7ecdf833983dfacf52e7c04222687 Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Thu, 9 Jul 2026 11:11:09 +0200 Subject: [PATCH 01/17] Build processors inside DLC worker when needed Add deferred processor construction via `ProcessorSpec` so processors that require worker-thread context can be built in `DLCLiveProcessor._worker_loop` instead of the GUI thread. Update both main window and service configuration paths to choose between immediate instantiation and worker-side specs, add lifecycle/context logging helpers, and improve processor shutdown/reset cleanup by calling processor `stop()` when appropriate. Also adjust DLC logging defaults to reduce timing noise while enabling targeted lifecycle diagnostics. --- dlclivegui/config.py | 2 + dlclivegui/gui/main_window.py | 94 +++++++++++----- dlclivegui/processors/dlc_processor_socket.py | 5 + dlclivegui/processors/processor_utils.py | 60 ++++++++++ dlclivegui/services/dlc_processor.py | 106 ++++++++++++++++-- 5 files changed, 231 insertions(+), 36 deletions(-) diff --git a/dlclivegui/config.py b/dlclivegui/config.py index ff57b1552..c9628ca97 100644 --- a/dlclivegui/config.py +++ b/dlclivegui/config.py @@ -39,6 +39,8 @@ DLC_DO_LOG_TIMING: bool = False ### Trigger debug logging DEBUG_TRIGGER_LOGS = False +### Extra logs for DLC lifecycle (model loading, etc) +DLC_LIFECYCLE_EXTRA_LOGS: bool = True # MAIN_WINDOW_DO_LOG_TIMING: bool = False #### Backends BASLER_DO_LOG_TIMING: bool = False diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index 3eacab0da..9d98936f8 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -66,8 +66,11 @@ ) from ..processors.processor_utils import ( + create_spec_from_scan, default_processors_dir, instantiate_from_scan, + log_processor_context, + processor_builds_in_worker, scan_processor_folder, scan_processor_package, ) @@ -937,7 +940,7 @@ def _apply_config(self, config: ApplicationSettings, *, restore_local_prefs: boo color_ui.set_bbox_combo_from_bgr(self.bbox_color_combo, self._bbox_color) # Processor - ## Allow processor control checkbox state + ## Use custom processor checkbox state if hasattr(self, "use_custom_proc_checkbox"): self.use_custom_proc_checkbox.setChecked(self._settings_store.get_processor_control_enabled(default=False)) @@ -2034,34 +2037,77 @@ def _stop_preview(self) -> None: def _configure_dlc(self) -> bool: try: settings = self._dlc_settings_from_ui() - except (ValueError, RuntimeError, json.JSONDecodeError) as exc: - self._show_error(f"Invalid DLCLive settings: {exc}") + except ( + ValueError, + RuntimeError, + json.JSONDecodeError, + ) as exc: + self._show_error( + f"Invalid DLCLive settings: {exc}" + ) return False + if not settings.model_path: - self._show_error("Please select a DLCLive model before starting inference.") + self._show_error( + "Please select a DLCLive model before " + "starting inference." + ) return False - # Instantiate processor if selected processor = None + processor_spec = None selected_key = self.processor_combo.currentData() self._settings_store.set_processor_key(selected_key) if self._custom_processor_enabled(): try: - processor = instantiate_from_scan( - self._scanned_processors, - selected_key, + processor_info = self._scanned_processors[ + selected_key + ] + processor_class = processor_info["class"] + processor_name = processor_info.get( + "name", + processor_class.__name__, ) - processor_name = self._scanned_processors[selected_key]["name"] + + if processor_builds_in_worker( + processor_class + ): + processor_spec = create_spec_from_scan( + self._scanned_processors, + selected_key, + ) + + log_processor_context( + "MainWindow._configure_dlc - " + f"SPEC: {processor_class.__name__}", + logger, + ) + else: + processor = instantiate_from_scan( + self._scanned_processors, + selected_key, + ) + + log_processor_context( + "MainWindow._configure_dlc - " + f"INSTANCE: {type(processor).__name__}", + logger, + ) + self.statusBar().showMessage( f"Loaded processor: {processor_name}", 3000, ) + except Exception as exc: - error_msg = f"Failed to instantiate processor: {exc}" + error_msg = ( + "Failed to configure processor: " + f"{exc}" + ) self._show_error(error_msg) - logger.error(error_msg) + logger.exception(error_msg) return False elif selected_key is not None: @@ -2070,8 +2116,14 @@ def _configure_dlc(self) -> bool: 3000, ) - self._dlc.configure(settings, processor=processor) - self._model_path_store.save_if_valid(settings.model_path) + self._dlc.configure( + settings, + processor=processor, + processor_spec=processor_spec, + ) + self._model_path_store.save_if_valid( + settings.model_path + ) return True def _update_inference_buttons(self) -> None: @@ -2205,20 +2257,6 @@ def _update_metrics(self) -> None: else: self.recording_stats_label.setText(self._last_recorder_summary) - def _on_processor_selection_changed( - self, - _index: int, - ) -> None: - """Enable custom processing when a processor is selected.""" - has_selection = self.processor_combo.currentData() is not None - self.processor_toggle_row.setVisible(has_selection) - - self.use_custom_proc_checkbox.blockSignals(True) - self.use_custom_proc_checkbox.setChecked(has_selection) - self.use_custom_proc_checkbox.blockSignals(False) - - self._update_processor_status() - def _update_processor_status(self) -> None: """Update processor connection and recording status, handle auto-recording.""" if not self._custom_processor_enabled(): @@ -2562,7 +2600,7 @@ def closeEvent(self, event: QCloseEvent) -> None: # pragma: no cover - GUI beha # Remember processor-control checkbox state on exit if hasattr(self, "use_custom_proc_checkbox"): - self._settings_store.set_processor_control_enabled(self.use_custom_proc_checkbox.isChecked()) + self._settings_store.set_processor_control_enabled(self._custom_processor_enabled()) if hasattr(self, "filename_edit"): self._settings_store.set_rec_filename(self.filename_edit.text().strip()) diff --git a/dlclivegui/processors/dlc_processor_socket.py b/dlclivegui/processors/dlc_processor_socket.py index 6a91ef1a3..1ab244f5b 100644 --- a/dlclivegui/processors/dlc_processor_socket.py +++ b/dlclivegui/processors/dlc_processor_socket.py @@ -38,6 +38,11 @@ class BaseProcessorSocket(Processor): PROCESSOR_PARAMS = {} PROCESSOR_DISCOVERABLE = False # base class, not intended to be an example processor + # Experimental: + # Socket/Teensy/Unity processors often start threads, sockets, serial ports, etc. + # Build them inside the DLCLive worker to match legacy Tk GUI behavior. + PROCESSOR_BUILD_IN_WORKER = False + def __init__( self, bind=None, diff --git a/dlclivegui/processors/processor_utils.py b/dlclivegui/processors/processor_utils.py index e47dbe2f8..879bab0df 100644 --- a/dlclivegui/processors/processor_utils.py +++ b/dlclivegui/processors/processor_utils.py @@ -5,13 +5,32 @@ import logging import pkgutil import sys +from dataclasses import dataclass, field from importlib import import_module from importlib.resources import as_file, files from pathlib import Path +from typing import Any + +from dlclivegui.config import DLC_LIFECYCLE_EXTRA_LOGS logger = logging.getLogger(__name__) +@dataclass +class ProcessorSpec: + cls: type + kwargs: dict[str, Any] = field(default_factory=dict) + + @property + def name(self) -> str: + return getattr(self.cls, "PROCESSOR_NAME", self.cls.__name__) + + def build(self) -> Any: + """Instantiate the processor class with the provided kwargs.""" + log_processor_context(f"ProcessorSpec.build: {self.name} with kwargs={self.kwargs}", logger) + return self.cls(**self.kwargs) + + def default_processors_dir() -> str: with as_file(files("dlclivegui").joinpath("processors")) as path: return str(path) @@ -202,6 +221,28 @@ def load_processors_from_file(file_path: str | Path) -> dict[str, dict]: return {} +def create_spec_from_scan(processors_dict, processor_key, **kwargs) -> ProcessorSpec: + """Create a ProcessorSpec from scan_processor_folder results, without instantiating the processor yet.""" + if processor_key not in processors_dict: + available = ", ".join(processors_dict.keys()) + raise ValueError(f"Unknown processor '{processor_key}'. Available: {available}") + + processor_info = processors_dict[processor_key] + processor_class = processor_info["class"] + return ProcessorSpec(cls=processor_class, kwargs=kwargs) + + +def processor_builds_in_worker(processor_class: type) -> bool: + """ + Return True if this processor class requests construction inside DLCLiveWorker. + + Processors opt in by defining: + + PROCESSOR_BUILD_IN_WORKER = True + """ + return bool(getattr(processor_class, "PROCESSOR_BUILD_IN_WORKER", False)) + + def instantiate_from_scan(processors_dict: dict[str, dict], processor_key: str, **kwargs): """ Instantiate a processor from scan_processor_folder results. @@ -246,3 +287,22 @@ def display_processor_info(processors): print(f" - {param_name} ({param_info['type']})") print(f" Default: {param_info['default']}") print(f" {param_info['description']}") + + +def log_processor_context(label: str, custom_logger: logging.Logger = logger): + if not DLC_LIFECYCLE_EXTRA_LOGS: + return + + import multiprocessing as mp + import os + import threading + import time + + custom_logger.info( + "[CUSTOM PROCESSOR] %s | pid=%s process=%s thread=%s time=%.6f", + label, + os.getpid(), + mp.current_process().name, + threading.current_thread().name, + time.time(), + ) diff --git a/dlclivegui/services/dlc_processor.py b/dlclivegui/services/dlc_processor.py index ec92461e7..e51be7500 100644 --- a/dlclivegui/services/dlc_processor.py +++ b/dlclivegui/services/dlc_processor.py @@ -17,7 +17,13 @@ from PySide6.QtCore import QObject, Signal from dlclivegui.config import DLC_DO_LOG_TIMING, DLCProcessorSettings, ModelType -from dlclivegui.processors.processor_utils import instantiate_from_scan +from dlclivegui.processors.dlc_processor_socket import BaseProcessorSocket +from dlclivegui.processors.processor_utils import ( + ProcessorSpec, + create_spec_from_scan, + instantiate_from_scan, + log_processor_context, +) from dlclivegui.temp import Engine # type: ignore # TODO use main package enum when released from dlclivegui.utils.stats import WorkerTimingStats from dlclivegui.utils.utils import format_thread_stack @@ -156,6 +162,8 @@ def __init__(self) -> None: self._settings = DLCProcessorSettings() self._dlc: Any | None = None self._processor: Any | None = None + self._processor_spec: ProcessorSpec | None = None + self.processor_built_from_spec = False # Worker thread and queue self._queue: queue.Queue[Any] | None = None self._worker_thread: threading.Thread | None = None @@ -200,15 +208,27 @@ def __init__(self) -> None: def get_model_backend(model_path: str) -> Engine: return Engine.from_model_path(model_path) - def configure(self, settings: DLCProcessorSettings, processor: Any | None = None) -> None: + def configure( + self, settings: DLCProcessorSettings, processor: Any | None = None, processor_spec: ProcessorSpec | None = None + ) -> None: with self._lifecycle_lock: if self._state != WorkerState.STOPPED: raise RuntimeError("Cannot configure DLCLiveProcessor while it is running. Please stop it first.") + if processor is not None and processor_spec is not None: + raise ValueError( + "Cannot provide both a processor instance and a processor_spec. Please provide only one." + ) self._settings = settings self._processor = processor + self._processor_spec = processor_spec + self.processor_built_from_spec = False - def reset(self) -> None: + def reset(self, reset_processor_plugin: bool = False) -> None: """Stop the worker thread and drop the current DLCLive instance.""" + had_runtime = ( + self._worker_thread is not None or self._dlc is not None or self._initialized or reset_processor_plugin + ) + stopped = self._stop_worker() if not stopped: with self._lifecycle_lock: @@ -217,6 +237,10 @@ def reset(self) -> None: "Reset requested but worker thread is still alive; skipping DLCLive reset to avoid potential issues." ) return + + if had_runtime and self._processor is not None: + self._cleanup_processor() + self._dlc = None self._initialized = False with self._stats_lock: @@ -232,10 +256,28 @@ def reset(self) -> None: self._gpu_inference_times.clear() self._processor_overhead_times.clear() + def _cleanup_processor(self) -> None: + proc = self._processor + if proc is None: + return + + stop = getattr(proc, "stop", None) + if callable(stop): + try: + log_processor_context(f"Stopping processor: {type(proc).__name__}", logger) + stop() + except Exception: + logger.exception("Failed to stop processor cleanly") + + self._processor = None + self._processor_built_from_spec = False + def shutdown(self) -> None: stopped = self._stop_worker() if not stopped: with self._lifecycle_lock: + if self._processor is not None: + self._cleanup_processor() self._pending_reset = True logger.warning( "Shutdown requested but worker thread is still alive; DLCLive instance may not be fully released." @@ -458,6 +500,7 @@ def _start_worker_locked(self, init_frame: np.ndarray, init_timestamp: float) -> self._queue = None self._stop_event.clear() self._state = WorkerState.STARTING + log_processor_context("Starting DLCLive worker thread", logger) self._worker_thread = threading.Thread( target=self._worker_loop, args=(init_frame, init_timestamp), @@ -590,6 +633,7 @@ def _process_frame( """ if self._dlc is None: raise RuntimeError("DLCLive instance is not initialized.") + # log_processor_context(f"DLCLiveProcessor._process_frame: timestamp={timestamp:.6f}", logger) # Time GPU inference (and processor overhead when present) with self._worker_timing.measure("DLC.prepare_frame"): frame = self._prepare_input_frame(frame) @@ -653,10 +697,11 @@ def _process_frame( def _worker_loop(self, init_frame: np.ndarray, init_timestamp: float) -> None: try: - # -------- Initialization (unchanged) -------- + # -------- Initialization -------- if not self._settings.model_path: raise RuntimeError("No DLCLive model path configured.") + log_processor_context("DLCLiveProcessor._worker_loop", logger) with self._worker_timing.measure("DLC.build_options"): dyn = self._settings.dynamic if not isinstance(dyn, (list, tuple)) or len(dyn) != 3: @@ -666,10 +711,33 @@ def _worker_loop(self, init_frame: np.ndarray, init_timestamp: float) -> None: raise RuntimeError("Invalid dynamic crop settings format.") from e enabled, margin, max_missing = dyn + custom_proc = None + + if self._processor is not None: + custom_proc = self._processor + log_processor_context( + f"Using existing processor instance: {type(custom_proc).__name__}", + logger, + ) + + elif self._processor_spec is not None: + log_processor_context( + f"Building processor from spec: {self._processor_spec.name}", + logger, + ) + custom_proc = self._processor_spec.build() + + with self._lifecycle_lock: + self._processor = custom_proc + self._processor_built_from_spec = True + + else: + custom_proc = None + options = { "model_path": self._settings.model_path, "model_type": self._settings.model_type, - "processor": self._processor, + "processor": custom_proc, "dynamic": [enabled, margin, max_missing], "resize": self._settings.resize, "precision": self._settings.precision, @@ -697,6 +765,7 @@ def _worker_loop(self, init_frame: np.ndarray, init_timestamp: float) -> None: ) with self._worker_timing.measure("DLC.construct"): self._dlc = DLCLive(**options) + log_processor_context("DLCLive instance constructed", logger) self._worker_timing.maybe_log() except Exception as exc: self._worker_timing.note_error() @@ -724,6 +793,7 @@ def _worker_loop(self, init_frame: np.ndarray, init_timestamp: float) -> None: # First inference to initialize with self._worker_timing.measure("DLC.init_inference"): self._dlc.init_inference(init_frame) + log_processor_context("DLCLive init_inference completed", logger) self._debug_log_dlc_runner_device() self._worker_timing.note_frame() @@ -871,13 +941,33 @@ def configure(self, settings: DLCProcessorSettings, scanned_processors: dict, se raise RuntimeError("Cannot configure DLCLiveProcessor while it is running. Please stop it first.") processor = None + processor_spec = None + if selected_key is not None and scanned_processors: try: - processor = instantiate_from_scan(scanned_processors, selected_key) + processor_info = scanned_processors[selected_key] + processor_class = processor_info["class"] + + if BaseProcessorSocket.do_build_in_worker(processor_class): + processor_spec = create_spec_from_scan(scanned_processors, selected_key) + + log_processor_context( + f"DLCLiveProcessor.configure - SPEC: {processor_class.__name__}", + logger, + ) + else: + processor = instantiate_from_scan(scanned_processors, selected_key) + + log_processor_context( + f"DLCLiveProcessor.configure - INSTANCE: {type(processor).__name__}", + logger, + ) + except Exception as exc: - logger.error("Failed to instantiate processor: %s", exc) + logger.error("Failed to configure processor: %s", exc, exc_info=True) return False - self._proc.configure(settings, processor=processor) + + self._proc.configure(settings, processor=processor, processor_spec=processor_spec) return True def start(self): From dc1446219b6346545dbce513e0c2b7b013261880 Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Thu, 9 Jul 2026 11:11:55 +0200 Subject: [PATCH 02/17] Add mock proc test fiels --- .../custom/mock_socket_processor.py | 532 ++ .../custom/mock_unity_socket_client.ipynb | 4609 +++++++++++++++++ 2 files changed, 5141 insertions(+) create mode 100644 dlclivegui/processors/custom/mock_socket_processor.py create mode 100644 dlclivegui/processors/custom/mock_unity_socket_client.ipynb diff --git a/dlclivegui/processors/custom/mock_socket_processor.py b/dlclivegui/processors/custom/mock_socket_processor.py new file mode 100644 index 000000000..788bde3df --- /dev/null +++ b/dlclivegui/processors/custom/mock_socket_processor.py @@ -0,0 +1,532 @@ +"""Standalone mock DLC processor plugin with socket listener support. + +This fixture intentionally avoids importing dlclive, dlclivegui, Teensy, serial, +NumPy, or project-specific processor base classes. + +It is designed to test two separate concerns without mixing them: + +1. GUI processor plugin discovery/configuration + - Exposes PROCESSOR_* metadata. + - Exposes PROCESSOR_BUILD_IN_WORKER = True. + - Exposes get_available_processors(), which your loader can consume without + requiring this class to inherit from dlclive.processor.Processor. + +2. Runtime socket listener behavior + - Starts a multiprocessing.connection.Listener. + - Accepts one or more clients on a background thread. + - Receives simple command dictionaries from clients. + - Broadcasts mock pose payloads to connected clients from process(). + +It does NOT mock Teensy serial acquisition. For the listener send/receive tests, +Teensy is not required: the Teensy path is a separate serial-reader concern. +""" + +from __future__ import annotations + +import logging +import pickle +import sys +import time +from collections import deque +from multiprocessing.connection import Client, Listener +from pathlib import Path +from threading import Event, Lock, Thread +from typing import Any + +logger = logging.getLogger(__name__) + +IP_ADDRESS = "127.0.0.1" +PORT = 6000 + + +class MockSocketProcessor: + """Standalone socket-based mock processor for tests. + + This intentionally reimplements the core listener/thread/control behavior + instead of inheriting from project classes. It is suitable for fixture usage + where external dependencies such as Teensy, serial, dlclive, or dlclivegui + should not be imported. + + Expected test usage: + + proc = MockSocketProcessor(bind=("127.0.0.1", free_port())) + conn = Client(proc.address, authkey=proc.authkey) + conn.send({"cmd": "ping"}) + assert conn.recv()["type"] == "pong" + proc.process([[1, 2, 0.9]]) + assert conn.recv()["type"] == "pose" + proc.stop() + + Notes: + - `process()` accepts any pose-like Python object. It does not validate + DLC shape because this mock is for socket lifecycle tests, not pose + validation tests. + - Payloads are sent through multiprocessing.connection, matching the + style used by the legacy socket processors. + """ + + PROCESSOR_NAME = "Mock Socket Processor" + PROCESSOR_DESCRIPTION = "Standalone mock socket processor without Teensy or DLCLive imports." + PROCESSOR_BUILD_IN_WORKER = False + PROCESSOR_PARAMS = { + "bind": { + "type": "tuple", + "default": (IP_ADDRESS, PORT), + "description": "Server bind address. Use port 0 to request an ephemeral port.", + }, + "authkey": { + "type": "bytes", + "default": b"secret password", + "description": "Authentication key for multiprocessing.connection clients.", + }, + "start_server": { + "type": "bool", + "default": True, + "description": "Whether to start the listener in __init__.", + }, + "socket_timeout": { + "type": "float", + "default": 0.05, + "description": "Accept-loop timeout in seconds.", + }, + "save_original": { + "type": "bool", + "default": False, + "description": "Whether to store raw pose payloads while recording.", + }, + } + + def __init__( + self, + bind: tuple[str, int] = (IP_ADDRESS, PORT), + authkey: bytes = b"secret password", + *, + start_server: bool = True, + socket_timeout: float = 0.05, + save_original: bool = False, + ) -> None: + self.address = bind + self.authkey = authkey + self._socket_timeout = float(socket_timeout) + self.save_original = bool(save_original) + + # Runtime listener/client state. + self.listener: Listener | None = None + self.conns: set[Any] = set() + self._conns_lock = Lock() + self._stop = Event() + self._accept_thread: Thread | None = None + self._rx_threads: set[Thread] = set() + + # Recording/control state compatible with socket-processor expectations. + self._recording = Event() + self._vid_recording = Event() + self._session_name = "test_session" + self.filename: str | None = None + + # Minimal data buffers for save/get_data tests. + self.start_time = time.time() + self.time_stamp = deque() + self.step = deque() + self.frame_time = deque() + self.pose_time = deque() + self.original_pose = deque() if self.save_original else None + self.received_commands = deque() + self.broadcast_count = 0 + self.curr_step = 0 + + if start_server: + self.start_server(bind, authkey=authkey, timeout=self._socket_timeout) + + # ------------------------------------------------------------------ + # Properties matching the real socket processors + # ------------------------------------------------------------------ + @property + def recording(self) -> bool: + return self._recording.is_set() + + @property + def video_recording(self) -> bool: + return self._vid_recording.is_set() + + @property + def session_name(self) -> str: + return self._session_name + + @session_name.setter + def session_name(self, name: str) -> None: + self._session_name = str(name) + self.filename = f"{self._session_name}_mock_processor_data.pkl" + + # ------------------------------------------------------------------ + # Listener lifecycle + # ------------------------------------------------------------------ + def start_server( + self, + bind: tuple[str, int] | None = None, + authkey: bytes | None = None, + *, + timeout: float | None = None, + ) -> None: + """Start the socket listener if it is not already running.""" + if self.listener is not None: + return + + if bind is not None: + self.address = bind + if authkey is not None: + self.authkey = authkey + if timeout is not None: + self._socket_timeout = float(timeout) + + self._stop.clear() + self.listener = Listener(self.address, authkey=self.authkey) + + # If bind used port 0, update address to the actual ephemeral port. + self.address = self._actual_listener_address(self.listener, fallback=self.address) + + self._set_listener_timeout(self.listener, self._socket_timeout) + + self._accept_thread = Thread(target=self._accept_loop, name="MockSocketProcessorAccept", daemon=True) + self._accept_thread.start() + logger.info("MockSocketProcessor listening on %s:%s", self.address[0], self.address[1]) + + @staticmethod + def _actual_listener_address(listener: Listener, fallback: tuple[str, int]) -> tuple[str, int]: + """Best-effort extraction of the actual listener address.""" + try: + raw = getattr(listener, "_listener", None) + sock = getattr(raw, "_socket", None) + if sock is not None: + addr = sock.getsockname() + return (str(addr[0]), int(addr[1])) + except Exception: + pass + try: + addr = listener.address + return (str(addr[0]), int(addr[1])) + except Exception: + return fallback + + @staticmethod + def _set_listener_timeout(listener: Listener, timeout: float) -> None: + """Set accept timeout on CPython listener internals, best effort.""" + raw = getattr(listener, "_listener", None) + for candidate in (raw, getattr(raw, "_socket", None)): + try: + if candidate is not None and hasattr(candidate, "settimeout"): + candidate.settimeout(timeout) + return + except Exception: + pass + + def _accept_loop(self) -> None: + while not self._stop.is_set(): + try: + if self.listener is None: + return + conn = self.listener.accept() + except TimeoutError: + continue + except (OSError, EOFError): + if self._stop.is_set(): + break + continue + except Exception: + if self._stop.is_set(): + break + logger.exception("Unexpected accept-loop error") + continue + + with self._conns_lock: + self.conns.add(conn) + + rx = Thread(target=self._rx_loop, args=(conn,), name="MockSocketProcessorRx", daemon=True) + self._rx_threads.add(rx) + rx.start() + logger.info("MockSocketProcessor client connected") + + def _rx_loop(self, conn: Any) -> None: + while not self._stop.is_set(): + try: + if conn.poll(0.05): + msg = conn.recv() + self._handle_client_message(msg, conn=conn) + continue + + if getattr(conn, "closed", False): + break + + except (EOFError, OSError, ConnectionError, BrokenPipeError): + break + except Exception: + logger.exception("Unexpected receive-loop error") + break + + self._close_conn(conn) + + def _close_conn(self, conn: Any) -> None: + try: + conn.close() + except Exception: + pass + with self._conns_lock: + self.conns.discard(conn) + + def stop(self) -> None: + """Stop listener, close clients, and join background threads best-effort.""" + if self._stop.is_set(): + return + + self._stop.set() + + # Wake accept() if needed. + try: + Client(self.address, authkey=self.authkey).close() + except Exception: + pass + + with self._conns_lock: + conns = list(self.conns) + for conn in conns: + self._close_conn(conn) + + try: + if self.listener is not None: + self.listener.close() + except Exception: + pass + self.listener = None + + if self._accept_thread is not None: + self._accept_thread.join(timeout=1.0) + self._accept_thread = None + + for thread in list(self._rx_threads): + try: + thread.join(timeout=0.5) + except Exception: + pass + self._rx_threads.clear() + + if sys.platform.startswith("win"): + time.sleep(0.05) + + close = stop + + def __del__(self) -> None: + try: + self.stop() + except Exception: + pass + + # ------------------------------------------------------------------ + # Client command handling + # ------------------------------------------------------------------ + def _handle_client_message(self, msg: Any, *, conn: Any | None = None) -> None: + self.received_commands.append(msg) + + if not isinstance(msg, dict): + self._send_to(conn, {"type": "error", "error": "message must be a dict"}) + return + + cmd = msg.get("cmd") + + if cmd == "ping": + self._send_to( + conn, + { + "type": "pong", + "timestamp": time.time(), + "session_name": self.session_name, + "recording": self.recording, + "video_recording": self.video_recording, + "clients": self.client_count(), + }, + ) + + elif cmd == "status": + self._send_to(conn, self.status_payload()) + + elif cmd == "set_session_name": + self.session_name = msg.get("session_name", "default_session") + self._send_to(conn, {"type": "ack", "cmd": cmd, "session_name": self.session_name}) + + elif cmd == "start_recording": + self.start_recording() + self._send_to(conn, {"type": "ack", "cmd": cmd, "recording": True}) + + elif cmd == "stop_recording": + self.stop_recording() + self._send_to(conn, {"type": "ack", "cmd": cmd, "recording": False}) + + elif cmd == "save": + file = msg.get("filename", self.filename) + result = self.save(file) + self._send_to(conn, {"type": "ack", "cmd": cmd, "result": result, "filename": file}) + + elif cmd == "close": + self._send_to(conn, {"type": "ack", "cmd": cmd}) + if conn is not None: + self._close_conn(conn) + + else: + self._send_to(conn, {"type": "error", "error": f"unknown cmd: {cmd!r}"}) + + @staticmethod + def _send_to(conn: Any | None, payload: Any) -> bool: + if conn is None: + return False + try: + conn.send(payload) + return True + except Exception: + return False + + def client_count(self) -> int: + with self._conns_lock: + return len(self.conns) + + def status_payload(self) -> dict[str, Any]: + return { + "type": "status", + "session_name": self.session_name, + "recording": self.recording, + "video_recording": self.video_recording, + "clients": self.client_count(), + "steps": self.curr_step, + "broadcast_count": self.broadcast_count, + "address": self.address, + } + + # ------------------------------------------------------------------ + # Recording helpers + # ------------------------------------------------------------------ + def start_recording(self) -> None: + self._recording.set() + self._vid_recording.set() + self._clear_data_queues() + self.curr_step = 0 + + def stop_recording(self) -> None: + self._recording.clear() + self._vid_recording.clear() + + def _clear_data_queues(self) -> None: + self.time_stamp.clear() + self.step.clear() + self.frame_time.clear() + self.pose_time.clear() + if self.original_pose is not None: + self.original_pose.clear() + + # ------------------------------------------------------------------ + # Process/broadcast path + # ------------------------------------------------------------------ + def process(self, pose: Any, **kwargs: Any) -> Any: + """Mock DLCLive processor callback. + + Records minimal metadata when recording is active and broadcasts a simple + pose payload to all connected clients. + """ + now = time.time() + self.curr_step += 1 + + if self.recording: + self.time_stamp.append(now) + self.step.append(self.curr_step) + self.frame_time.append(kwargs.get("frame_time", -1)) + if "pose_time" in kwargs: + self.pose_time.append(kwargs["pose_time"]) + if self.original_pose is not None: + self.original_pose.append(pose) + + payload = { + "type": "pose", + "timestamp": now, + "step": self.curr_step, + "pose": self._make_pickle_safe_pose(pose), + "frame_time": kwargs.get("frame_time", None), + "pose_time": kwargs.get("pose_time", None), + "recording": self.recording, + } + self.broadcast(payload) + return pose + + @staticmethod + def _make_pickle_safe_pose(pose: Any) -> Any: + """Convert common array-likes to socket-safe Python types.""" + tolist = getattr(pose, "tolist", None) + if callable(tolist): + try: + return tolist() + except Exception: + pass + return pose + + def broadcast(self, payload: Any) -> None: + with self._conns_lock: + conns = list(self.conns) + + dead = [] + for conn in conns: + try: + conn.send(payload) + self.broadcast_count += 1 + except Exception: + dead.append(conn) + + for conn in dead: + self._close_conn(conn) + + # ------------------------------------------------------------------ + # Save/get_data helpers + # ------------------------------------------------------------------ + def get_data(self) -> dict[str, Any]: + return { + "start_time": self.start_time, + "session_name": self.session_name, + "time_stamp": list(self.time_stamp), + "step": list(self.step), + "frame_time": list(self.frame_time), + "pose_time": list(self.pose_time), + "recording": self.recording, + "video_recording": self.video_recording, + "received_commands": list(self.received_commands), + "broadcast_count": self.broadcast_count, + } + + def save(self, file: str | Path | None = None) -> int: + if not file: + return 0 + try: + path = Path(file) + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("wb") as fh: + pickle.dump(self.get_data(), fh) + return 1 + except Exception: + logger.exception("MockSocketProcessor save failed") + return -1 + + +# Optional aliases useful in different test styles. +MockPDSocketProcessor = MockSocketProcessor +MockUnitySocketProcessor = MockSocketProcessor + + +def get_available_processors() -> dict[str, dict[str, Any]]: + """Plugin-discovery entrypoint used by dlclivegui.processor_utils. + + This avoids requiring the class to inherit from dlclive.processor.Processor + during tests. The loader path that prefers get_available_processors() can + still discover this processor as a GUI plugin fixture. + """ + return { + "MockSocketProcessor": { + "class": MockSocketProcessor, + "name": getattr(MockSocketProcessor, "PROCESSOR_NAME", "MockSocketProcessor"), + "description": getattr(MockSocketProcessor, "PROCESSOR_DESCRIPTION", ""), + "params": getattr(MockSocketProcessor, "PROCESSOR_PARAMS", {}), + } + } diff --git a/dlclivegui/processors/custom/mock_unity_socket_client.ipynb b/dlclivegui/processors/custom/mock_unity_socket_client.ipynb new file mode 100644 index 000000000..63e4569a1 --- /dev/null +++ b/dlclivegui/processors/custom/mock_unity_socket_client.ipynb @@ -0,0 +1,4609 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7fb27b941602401d91542211134fc71a", + "metadata": {}, + "source": [ + "# Mock Unity Socket Client for DLCLive Processor\n", + "\n", + "This notebook acts as the **Unity-side socket client** for the legacy DLC socket processor.\n", + "\n", + "## Which processor style this targets\n", + "\n", + "The legacy processor chain you showed is:\n", + "\n", + "```text\n", + "dlc_inference_w_pd_sync\n", + " -> dlc_inference_w_pd\n", + " -> MyProcessor_socket\n", + "```\n", + "\n", + "`MyProcessor_socket` opens a `multiprocessing.connection.Listener`, defaulting to:\n", + "\n", + "```python\n", + "(\"127.0.0.1\", 6000)\n", + "authkey=b\"secret password\"\n", + "```\n", + "\n", + "It sends payloads from `process()` shaped like:\n", + "\n", + "```python\n", + "[time.time(), x, y, heading, head_angle, signal]\n", + "```\n", + "\n", + "This notebook connects as the **client** and tries to catch those pose/kinematics packets.\n", + "\n", + "## Important ordering note\n", + "\n", + "The legacy `MyProcessor_socket` does **not** have a background accept thread. It accepts a client only when `process()` runs. That means if DLC inference is not producing poses yet, `Client(...)` may block or time out. If that happens, start/continue DLC inference and retry." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "acae54e37e7d407bbb7b55eff062a284", + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "import json\n", + "import queue\n", + "import threading\n", + "import time\n", + "from dataclasses import asdict, dataclass\n", + "from multiprocessing.connection import Client\n", + "from pathlib import Path\n", + "from typing import Any" + ] + }, + { + "cell_type": "markdown", + "id": "9a63283cbaf04dbcab1f6479b197f3a8", + "metadata": {}, + "source": [ + "## Configuration\n", + "\n", + "Adjust these if the processor uses a different port or auth key.\n", + "\n", + "For the legacy processor, the defaults are usually:\n", + "\n", + "```python\n", + "ADDRESS = (\"127.0.0.1\", 6000)\n", + "AUTHKEY = b\"secret password\"\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8dd0d8092fe74a7c96281538738b07e2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Target processor socket: ('127.0.0.1', 6000)\n" + ] + } + ], + "source": [ + "ADDRESS = (\"127.0.0.1\", 6000)\n", + "AUTHKEY = b\"secret password\"\n", + "\n", + "# How long to wait for a connection attempt before considering it failed.\n", + "CONNECT_TIMEOUT_S = 10.0\n", + "\n", + "# How long the receive loop should poll while waiting for new packets.\n", + "POLL_INTERVAL_S = 0.05\n", + "\n", + "print(\"Target processor socket:\", ADDRESS)" + ] + }, + { + "cell_type": "markdown", + "id": "72eea5119410473aa328ad9291626812", + "metadata": {}, + "source": [ + "## Client helpers\n", + "\n", + "`multiprocessing.connection.Client(...)` can block if the server has not called `accept()` yet. To avoid freezing the notebook, `connect_with_timeout()` performs the connection attempt in a background thread." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8edb47106e1a46a883d545849b8ab81b", + "metadata": {}, + "outputs": [], + "source": [ + "class ConnectTimeoutError(TimeoutError):\n", + " pass\n", + "\n", + "\n", + "def connect_with_timeout(address, authkey: bytes, timeout_s: float = 10.0):\n", + " \"\"\"Connect to a multiprocessing.connection.Listener without freezing the notebook forever.\"\"\"\n", + " result_q: queue.Queue[tuple[str, Any]] = queue.Queue(maxsize=1)\n", + "\n", + " def worker():\n", + " try:\n", + " conn = Client(address, authkey=authkey)\n", + " result_q.put((\"ok\", conn))\n", + " except Exception as exc:\n", + " result_q.put((\"error\", exc))\n", + "\n", + " t = threading.Thread(target=worker, name=\"MockUnityConnect\", daemon=True)\n", + " t.start()\n", + " t.join(timeout_s)\n", + "\n", + " if t.is_alive():\n", + " raise ConnectTimeoutError(\n", + " f\"Timed out after {timeout_s:.1f}s while connecting to {address}. \"\n", + " \"For legacy MyProcessor_socket, this can happen if DLC process() has not called listener.accept() yet.\"\n", + " )\n", + "\n", + " status, payload = result_q.get_nowait()\n", + " if status == \"ok\":\n", + " return payload\n", + " raise payload\n", + "\n", + "\n", + "@dataclass\n", + "class LegacyPosePacket:\n", + " timestamp: float\n", + " x: float\n", + " y: float\n", + " heading: float\n", + " head_angle: float\n", + " signal: float\n", + " raw: Any\n", + "\n", + "\n", + "def decode_payload(payload: Any) -> dict[str, Any]:\n", + " \"\"\"Decode either legacy list payloads or newer dict/list mock payloads.\"\"\"\n", + " # Legacy MyProcessor_socket payload:\n", + " # [time.time(), x, y, heading, head_angle, signal]\n", + " if isinstance(payload, list) and len(payload) == 6:\n", + " pkt = LegacyPosePacket(\n", + " timestamp=float(payload[0]),\n", + " x=float(payload[1]),\n", + " y=float(payload[2]),\n", + " heading=float(payload[3]),\n", + " head_angle=float(payload[4]),\n", + " signal=float(payload[5]),\n", + " raw=payload,\n", + " )\n", + " return {\"kind\": \"legacy_pose\", **asdict(pkt)}\n", + "\n", + " # Newer/base mock payloads may be dictionaries.\n", + " if isinstance(payload, dict):\n", + " kind = payload.get(\"type\", \"dict\")\n", + " return {\"kind\": kind, \"raw\": payload}\n", + "\n", + " # Some processors broadcast [timestamp, pose].\n", + " if isinstance(payload, list) and len(payload) == 2:\n", + " return {\"kind\": \"timestamp_pose\", \"timestamp\": payload[0], \"pose\": payload[1], \"raw\": payload}\n", + "\n", + " return {\"kind\": \"unknown\", \"raw\": payload}" + ] + }, + { + "cell_type": "markdown", + "id": "10185d26023b46108eb7d9f57d49d2b3", + "metadata": {}, + "source": [ + "## Connect to the DLC processor socket\n", + "\n", + "Run this cell once the processor has been created and its listener should be available.\n", + "\n", + "If it times out, it likely means either:\n", + "\n", + "1. the processor has not been instantiated yet,\n", + "2. the address/authkey are wrong,\n", + "3. the legacy processor is waiting until `process()` runs before accepting the connection,\n", + "4. another process is using the port." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8763a12b2bbd4a93a75aff182afb95dc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to processor socket: ('127.0.0.1', 6000)\n" + ] + } + ], + "source": [ + "conn = connect_with_timeout(ADDRESS, AUTHKEY, timeout_s=CONNECT_TIMEOUT_S)\n", + "print(\"Connected to processor socket:\", ADDRESS)" + ] + }, + { + "cell_type": "markdown", + "id": "7623eae2785240b9bd12b16a66d81610", + "metadata": {}, + "source": [ + "## Optional: send a ping/status command\n", + "\n", + "Only use this for processors that implement command handling, such as the newer `BaseProcessorSocket` or the standalone mock processor.\n", + "\n", + "The legacy `MyProcessor_socket` does **not** read commands from the client, so skip this cell for that processor." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7cdc8c89c7104fffa095e18ddfef8986", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment only for BaseProcessorSocket-style processors or the standalone mock.\n", + "# conn.send({\"cmd\": \"ping\"})\n", + "# if conn.poll(2.0):\n", + "# print(\"Response:\", conn.recv())\n", + "# else:\n", + "# print(\"No response. This is expected for legacy MyProcessor_socket.\")" + ] + }, + { + "cell_type": "markdown", + "id": "b118ea5561624da68c537baed56e602f", + "metadata": {}, + "source": [ + "## Receive pose packets\n", + "\n", + "This cell listens for up to `duration_s` seconds and prints decoded packets. For legacy `MyProcessor_socket`, you should see `legacy_pose` packets once `process()` is called by DLC inference." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "938c804e27f84196a10c8828c723f798", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.750286,\n", + " \"step\": 12,\n", + " \"pose\": [\n", + " [\n", + " 288.7191162109375,\n", + " 298.63250732421875,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.0001220703125,\n", + " 307.95672607421875,\n", + " 0.973543643951416\n", + " ],\n", + " [\n", + " 297.29254150390625,\n", + " 314.52410888671875,\n", + " 0.6293796896934509\n", + " ],\n", + " [\n", + " 290.9452819824219,\n", + " 310.3515319824219,\n", + " 0.640852153301239\n", + " ],\n", + " [\n", + " 304.86236572265625,\n", + " 313.2810974121094,\n", + " 0.6921122670173645\n", + " ],\n", + " [\n", + " 284.668212890625,\n", + " 266.52752685546875,\n", + " 0.9321146607398987\n", + " ],\n", + " [\n", + " 284.6539001464844,\n", + " 237.49722290039062,\n", + " 0.4642881155014038\n", + " ],\n", + " [\n", + " 298.547607421875,\n", + " 218.42587280273438,\n", + " 0.26315996050834656\n", + " ],\n", + " [\n", + " 182.9195098876953,\n", + " 312.5831604003906,\n", + " 0.4563772678375244\n", + " ],\n", + " [\n", + " 181.693359375,\n", + " 306.1708068847656,\n", + " 0.39262306690216064\n", + " ],\n", + " [\n", + " 309.70013427734375,\n", + " 273.22198486328125,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.6557922363281,\n", + " 278.49371337890625,\n", + " 0.5969972014427185\n", + " ],\n", + " [\n", + " 362.46343994140625,\n", + " 283.172119140625,\n", + " 0.4280658960342407\n", + " ],\n", + " [\n", + " 185.8195343017578,\n", + " 311.7103271484375,\n", + " 0.27244052290916443\n", + " ],\n", + " [\n", + " 192.61837768554688,\n", + " 305.4747009277344,\n", + " 0.39853179454803467\n", + " ],\n", + " [\n", + " 355.7668151855469,\n", + " 339.8248596191406,\n", + " 0.40097054839134216\n", + " ],\n", + " [\n", + " 364.0209655761719,\n", + " 341.5860595703125,\n", + " 0.5437613129615784\n", + " ],\n", + " [\n", + " 335.2928771972656,\n", + " 344.40032958984375,\n", + " 0.3498704731464386\n", + " ],\n", + " [\n", + " 331.4139404296875,\n", + " 356.1664123535156,\n", + " 0.4611521065235138\n", + " ],\n", + " [\n", + " 366.796142578125,\n", + " 344.254150390625,\n", + " 0.47546425461769104\n", + " ],\n", + " [\n", + " 270.3355712890625,\n", + " 358.1495666503906,\n", + " 0.4529607594013214\n", + " ],\n", + " [\n", + " 379.02679443359375,\n", + " 356.05645751953125,\n", + " 0.4878201186656952\n", + " ],\n", + " [\n", + " 268.0301818847656,\n", + " 359.48583984375,\n", + " 0.366856187582016\n", + " ],\n", + " [\n", + " 599.5546264648438,\n", + " 377.7467956542969,\n", + " 0.564258873462677\n", + " ],\n", + " [\n", + " 185.41236877441406,\n", + " 207.2476043701172,\n", + " 0.3415561616420746\n", + " ],\n", + " [\n", + " 518.7420043945312,\n", + " 391.21240234375,\n", + " 0.3360799551010132\n", + " ],\n", + " [\n", + " 516.0563354492188,\n", + " 429.2253723144531,\n", + " 0.7114025354385376\n", + " ],\n", + " [\n", + " 164.67864990234375,\n", + " 212.08599853515625,\n", + " 0.22173050045967102\n", + " ],\n", + " [\n", + " 508.5932312011719,\n", + " 391.6039123535156,\n", + " 0.30660656094551086\n", + " ],\n", + " [\n", + " 511.993896484375,\n", + " 447.9190368652344,\n", + " 0.5064514875411987\n", + " ],\n", + " [\n", + " 591.1927490234375,\n", + " 409.2403564453125,\n", + " 0.5136002898216248\n", + " ],\n", + " [\n", + " 106.3792495727539,\n", + " 271.0600891113281,\n", + " 0.14335250854492188\n", + " ],\n", + " [\n", + " 112.1520767211914,\n", + " 279.07525634765625,\n", + " 0.2755976915359497\n", + " ],\n", + " [\n", + " 572.9004516601562,\n", + " 393.2549743652344,\n", + " 0.39857158064842224\n", + " ],\n", + " [\n", + " 144.4106903076172,\n", + " 313.3831787109375,\n", + " 0.27947890758514404\n", + " ],\n", + " [\n", + " 550.626953125,\n", + " 466.50970458984375,\n", + " 0.5572487115859985\n", + " ],\n", + " [\n", + " 466.3507385253906,\n", + " 413.0749206542969,\n", + " 0.39403966069221497\n", + " ],\n", + " [\n", + " 461.47198486328125,\n", + " 400.29541015625,\n", + " 0.3778141140937805\n", + " ],\n", + " [\n", + " 460.13824462890625,\n", + " 403.57171630859375,\n", + " 0.5188782811164856\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.7246952,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.750286\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.78031,\n", + " \"step\": 13,\n", + " \"pose\": [\n", + " [\n", + " 287.8656921386719,\n", + " 298.9954833984375,\n", + " 1.0\n", + " ],\n", + " [\n", + " 292.98870849609375,\n", + " 308.14825439453125,\n", + " 0.9376083016395569\n", + " ],\n", + " [\n", + " 297.0699462890625,\n", + " 315.7899169921875,\n", + " 0.605503499507904\n", + " ],\n", + " [\n", + " 290.7883605957031,\n", + " 311.0892333984375,\n", + " 0.653724193572998\n", + " ],\n", + " [\n", + " 304.9418029785156,\n", + " 314.27423095703125,\n", + " 0.6581617593765259\n", + " ],\n", + " [\n", + " 284.5467224121094,\n", + " 266.99755859375,\n", + " 0.9386962056159973\n", + " ],\n", + " [\n", + " 285.4912109375,\n", + " 236.9186248779297,\n", + " 0.48566538095474243\n", + " ],\n", + " [\n", + " 282.66357421875,\n", + " 236.26953125,\n", + " 0.2793366611003876\n", + " ],\n", + " [\n", + " 182.8148193359375,\n", + " 314.7862548828125,\n", + " 0.4555658996105194\n", + " ],\n", + " [\n", + " 183.0238037109375,\n", + " 309.2024230957031,\n", + " 0.3395337760448456\n", + " ],\n", + " [\n", + " 310.2979431152344,\n", + " 272.9541015625,\n", + " 1.0\n", + " ],\n", + " [\n", + " 362.39019775390625,\n", + " 278.04864501953125,\n", + " 0.7049410343170166\n", + " ],\n", + " [\n", + " 362.625,\n", + " 281.328369140625,\n", + " 0.4549161195755005\n", + " ],\n", + " [\n", + " 357.58203125,\n", + " 282.10272216796875,\n", + " 0.26718243956565857\n", + " ],\n", + " [\n", + " 194.67514038085938,\n", + " 306.698974609375,\n", + " 0.45219412446022034\n", + " ],\n", + " [\n", + " 355.5292663574219,\n", + " 339.20318603515625,\n", + " 0.41419631242752075\n", + " ],\n", + " [\n", + " 367.3896179199219,\n", + " 340.3309326171875,\n", + " 0.5223292112350464\n", + " ],\n", + " [\n", + " 335.11529541015625,\n", + " 343.81201171875,\n", + " 0.347744345664978\n", + " ],\n", + " [\n", + " 332.03765869140625,\n", + " 356.7191162109375,\n", + " 0.4733559191226959\n", + " ],\n", + " [\n", + " 367.4532470703125,\n", + " 344.79302978515625,\n", + " 0.4827011823654175\n", + " ],\n", + " [\n", + " 270.3110046386719,\n", + " 357.83343505859375,\n", + " 0.4700448215007782\n", + " ],\n", + " [\n", + " 378.0578308105469,\n", + " 356.4914855957031,\n", + " 0.4333026707172394\n", + " ],\n", + " [\n", + " 267.2149963378906,\n", + " 359.4980773925781,\n", + " 0.39140474796295166\n", + " ],\n", + " [\n", + " 596.2949829101562,\n", + " 378.6457824707031,\n", + " 0.5882202386856079\n", + " ],\n", + " [\n", + " 185.2808837890625,\n", + " 208.80474853515625,\n", + " 0.4029167890548706\n", + " ],\n", + " [\n", + " 563.7208862304688,\n", + " 387.8863220214844,\n", + " 0.390456885099411\n", + " ],\n", + " [\n", + " 519.1982421875,\n", + " 433.89654541015625,\n", + " 0.5289698839187622\n", + " ],\n", + " [\n", + " 442.9023132324219,\n", + " 356.9894714355469,\n", + " 0.2825982868671417\n", + " ],\n", + " [\n", + " 568.7670288085938,\n", + " 195.95079040527344,\n", + " 0.43732380867004395\n", + " ],\n", + " [\n", + " 512.254638671875,\n", + " 449.7223815917969,\n", + " 0.6101775169372559\n", + " ],\n", + " [\n", + " 591.0804443359375,\n", + " 409.246337890625,\n", + " 0.5442432761192322\n", + " ],\n", + " [\n", + " 470.5218200683594,\n", + " 402.7597961425781,\n", + " 0.1880091279745102\n", + " ],\n", + " [\n", + " 107.73304748535156,\n", + " 270.0023193359375,\n", + " 0.14866414666175842\n", + " ],\n", + " [\n", + " 572.8255615234375,\n", + " 393.6127624511719,\n", + " 0.5601691603660583\n", + " ],\n", + " [\n", + " 481.23486328125,\n", + " 431.833984375,\n", + " 0.2556696832180023\n", + " ],\n", + " [\n", + " 550.4242553710938,\n", + " 467.0722961425781,\n", + " 0.4299638867378235\n", + " ],\n", + " [\n", + " 466.45404052734375,\n", + " 412.8644714355469,\n", + " 0.38715386390686035\n", + " ],\n", + " [\n", + " 462.144775390625,\n", + " 401.0147705078125,\n", + " 0.3404390513896942\n", + " ],\n", + " [\n", + " 459.4046325683594,\n", + " 403.1348571777344,\n", + " 0.4926401376724243\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.7554483,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.78031\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.821648,\n", + " \"step\": 14,\n", + " \"pose\": [\n", + " [\n", + " 288.8023681640625,\n", + " 298.86077880859375,\n", + " 1.0\n", + " ],\n", + " [\n", + " 292.8272399902344,\n", + " 308.04949951171875,\n", + " 0.9570764899253845\n", + " ],\n", + " [\n", + " 296.77056884765625,\n", + " 315.70367431640625,\n", + " 0.631300687789917\n", + " ],\n", + " [\n", + " 289.9299621582031,\n", + " 310.9530944824219,\n", + " 0.6520562171936035\n", + " ],\n", + " [\n", + " 305.0509338378906,\n", + " 313.7876892089844,\n", + " 0.7196981310844421\n", + " ],\n", + " [\n", + " 284.6484680175781,\n", + " 266.7134704589844,\n", + " 0.9353039860725403\n", + " ],\n", + " [\n", + " 285.0174560546875,\n", + " 238.79736328125,\n", + " 0.4836384952068329\n", + " ],\n", + " [\n", + " 154.6018829345703,\n", + " 315.4358215332031,\n", + " 0.2590964734554291\n", + " ],\n", + " [\n", + " 336.0355529785156,\n", + " 466.2019348144531,\n", + " 0.37860575318336487\n", + " ],\n", + " [\n", + " 181.9645233154297,\n", + " 308.6495361328125,\n", + " 0.3605335056781769\n", + " ],\n", + " [\n", + " 309.65972900390625,\n", + " 273.17425537109375,\n", + " 1.0\n", + " ],\n", + " [\n", + " 362.54388427734375,\n", + " 277.6341857910156,\n", + " 0.5562496781349182\n", + " ],\n", + " [\n", + " 361.87298583984375,\n", + " 282.2453308105469,\n", + " 0.477169394493103\n", + " ],\n", + " [\n", + " 358.46246337890625,\n", + " 282.86480712890625,\n", + " 0.2796511948108673\n", + " ],\n", + " [\n", + " 189.29440307617188,\n", + " 305.3114929199219,\n", + " 0.41088035702705383\n", + " ],\n", + " [\n", + " 355.5824890136719,\n", + " 341.4902648925781,\n", + " 0.3962235450744629\n", + " ],\n", + " [\n", + " 363.149658203125,\n", + " 340.7292785644531,\n", + " 0.48571205139160156\n", + " ],\n", + " [\n", + " 332.39642333984375,\n", + " 340.5425720214844,\n", + " 0.32239603996276855\n", + " ],\n", + " [\n", + " 331.2975769042969,\n", + " 355.4726257324219,\n", + " 0.4472481310367584\n", + " ],\n", + " [\n", + " 364.4246520996094,\n", + " 346.6827087402344,\n", + " 0.4476216733455658\n", + " ],\n", + " [\n", + " 269.8668212890625,\n", + " 359.0860900878906,\n", + " 0.43882080912590027\n", + " ],\n", + " [\n", + " 379.4068298339844,\n", + " 356.64239501953125,\n", + " 0.4417465329170227\n", + " ],\n", + " [\n", + " 266.974853515625,\n", + " 360.31402587890625,\n", + " 0.33691221475601196\n", + " ],\n", + " [\n", + " 596.21826171875,\n", + " 377.820068359375,\n", + " 0.5398204922676086\n", + " ],\n", + " [\n", + " 183.42034912109375,\n", + " 208.78756713867188,\n", + " 0.34466955065727234\n", + " ],\n", + " [\n", + " 564.060791015625,\n", + " 388.1324157714844,\n", + " 0.4286687970161438\n", + " ],\n", + " [\n", + " 517.4417724609375,\n", + " 430.2832336425781,\n", + " 0.5827439427375793\n", + " ],\n", + " [\n", + " 169.510009765625,\n", + " 212.77224731445312,\n", + " 0.23520678281784058\n", + " ],\n", + " [\n", + " 509.5054016113281,\n", + " 390.251708984375,\n", + " 0.2855446934700012\n", + " ],\n", + " [\n", + " 511.0235290527344,\n", + " 447.9842224121094,\n", + " 0.5595549941062927\n", + " ],\n", + " [\n", + " 591.0059204101562,\n", + " 408.37109375,\n", + " 0.5015893578529358\n", + " ],\n", + " [\n", + " 475.4132385253906,\n", + " 422.1249084472656,\n", + " 0.19282308220863342\n", + " ],\n", + " [\n", + " 110.6484146118164,\n", + " 276.17138671875,\n", + " 0.3112924098968506\n", + " ],\n", + " [\n", + " 572.998046875,\n", + " 393.3849182128906,\n", + " 0.4534406363964081\n", + " ],\n", + " [\n", + " 481.1611022949219,\n", + " 432.43438720703125,\n", + " 0.2341541200876236\n", + " ],\n", + " [\n", + " 551.3787231445312,\n", + " 466.6352844238281,\n", + " 0.5391212105751038\n", + " ],\n", + " [\n", + " 467.7240905761719,\n", + " 412.1317138671875,\n", + " 0.3793080151081085\n", + " ],\n", + " [\n", + " 465.2247619628906,\n", + " 409.2422180175781,\n", + " 0.33666175603866577\n", + " ],\n", + " [\n", + " 460.2418212890625,\n", + " 403.50830078125,\n", + " 0.42937344312667847\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.7889726,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.821648\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.851803,\n", + " \"step\": 15,\n", + " \"pose\": [\n", + " [\n", + " 288.8399963378906,\n", + " 298.389892578125,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.063232421875,\n", + " 307.6488952636719,\n", + " 0.9409026503562927\n", + " ],\n", + " [\n", + " 296.73828125,\n", + " 314.7473449707031,\n", + " 0.6393375992774963\n", + " ],\n", + " [\n", + " 290.7121887207031,\n", + " 310.599609375,\n", + " 0.6521811485290527\n", + " ],\n", + " [\n", + " 304.9431457519531,\n", + " 311.9217224121094,\n", + " 0.7004058957099915\n", + " ],\n", + " [\n", + " 284.689697265625,\n", + " 267.53924560546875,\n", + " 0.9271823167800903\n", + " ],\n", + " [\n", + " 182.9373779296875,\n", + " 313.93133544921875,\n", + " 0.5212528109550476\n", + " ],\n", + " [\n", + " 153.7313690185547,\n", + " 313.92779541015625,\n", + " 0.3010638356208801\n", + " ],\n", + " [\n", + " 181.32296752929688,\n", + " 312.8819580078125,\n", + " 0.5419734120368958\n", + " ],\n", + " [\n", + " 183.34437561035156,\n", + " 306.7300720214844,\n", + " 0.37734419107437134\n", + " ],\n", + " [\n", + " 309.9617004394531,\n", + " 273.3157653808594,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.39080810546875,\n", + " 277.68914794921875,\n", + " 0.7047985792160034\n", + " ],\n", + " [\n", + " 362.3508605957031,\n", + " 282.02313232421875,\n", + " 0.4799898564815521\n", + " ],\n", + " [\n", + " 179.46484375,\n", + " 315.53436279296875,\n", + " 0.3219224810600281\n", + " ],\n", + " [\n", + " 193.1734161376953,\n", + " 304.5758056640625,\n", + " 0.4651084244251251\n", + " ],\n", + " [\n", + " 355.8511047363281,\n", + " 340.7768859863281,\n", + " 0.42659905552864075\n", + " ],\n", + " [\n", + " 364.3086242675781,\n", + " 341.3064270019531,\n", + " 0.5809048414230347\n", + " ],\n", + " [\n", + " 310.90179443359375,\n", + " 347.79620361328125,\n", + " 0.37011390924453735\n", + " ],\n", + " [\n", + " 329.7757263183594,\n", + " 356.66290283203125,\n", + " 0.5357598066329956\n", + " ],\n", + " [\n", + " 369.3732604980469,\n", + " 347.80084228515625,\n", + " 0.5102704167366028\n", + " ],\n", + " [\n", + " 269.4798583984375,\n", + " 358.50128173828125,\n", + " 0.36626455187797546\n", + " ],\n", + " [\n", + " 379.74285888671875,\n", + " 356.38800048828125,\n", + " 0.4842686653137207\n", + " ],\n", + " [\n", + " 265.9881286621094,\n", + " 360.00592041015625,\n", + " 0.27803361415863037\n", + " ],\n", + " [\n", + " 596.4786376953125,\n", + " 379.2182922363281,\n", + " 0.5961218476295471\n", + " ],\n", + " [\n", + " 254.6011505126953,\n", + " 382.6790466308594,\n", + " 0.4003625512123108\n", + " ],\n", + " [\n", + " 563.052001953125,\n", + " 386.73590087890625,\n", + " 0.36300426721572876\n", + " ],\n", + " [\n", + " 517.0165405273438,\n", + " 430.59613037109375,\n", + " 0.5643782019615173\n", + " ],\n", + " [\n", + " 164.36508178710938,\n", + " 213.65438842773438,\n", + " 0.2149515450000763\n", + " ],\n", + " [\n", + " 509.01336669921875,\n", + " 392.5158996582031,\n", + " 0.23822596669197083\n", + " ],\n", + " [\n", + " 510.94085693359375,\n", + " 449.33477783203125,\n", + " 0.6286779642105103\n", + " ],\n", + " [\n", + " 590.7813110351562,\n", + " 409.1065368652344,\n", + " 0.4439380168914795\n", + " ],\n", + " [\n", + " 463.3310546875,\n", + " 401.2489318847656,\n", + " 0.1755678504705429\n", + " ],\n", + " [\n", + " 109.76718139648438,\n", + " 271.8696594238281,\n", + " 0.2973553240299225\n", + " ],\n", + " [\n", + " 572.5289306640625,\n", + " 393.1651916503906,\n", + " 0.5296833515167236\n", + " ],\n", + " [\n", + " 480.9592590332031,\n", + " 432.1273193359375,\n", + " 0.21754036843776703\n", + " ],\n", + " [\n", + " 550.6238403320312,\n", + " 466.08721923828125,\n", + " 0.5377715229988098\n", + " ],\n", + " [\n", + " 468.291015625,\n", + " 413.2549133300781,\n", + " 0.3708413243293762\n", + " ],\n", + " [\n", + " 136.44790649414062,\n", + " 171.77438354492188,\n", + " 0.3511451184749603\n", + " ],\n", + " [\n", + " 460.23358154296875,\n", + " 404.2395935058594,\n", + " 0.47236377000808716\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.8206406,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.8528142\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.8770585,\n", + " \"step\": 16,\n", + " \"pose\": [\n", + " [\n", + " 289.1402282714844,\n", + " 299.0330810546875,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.5198974609375,\n", + " 308.2564392089844,\n", + " 0.9859002232551575\n", + " ],\n", + " [\n", + " 297.6006164550781,\n", + " 314.6565856933594,\n", + " 0.6514089107513428\n", + " ],\n", + " [\n", + " 290.924072265625,\n", + " 311.3872985839844,\n", + " 0.6389972567558289\n", + " ],\n", + " [\n", + " 304.9306945800781,\n", + " 313.3708801269531,\n", + " 0.7217590808868408\n", + " ],\n", + " [\n", + " 284.5622863769531,\n", + " 267.4127502441406,\n", + " 0.9266435503959656\n", + " ],\n", + " [\n", + " 184.82669067382812,\n", + " 312.5155334472656,\n", + " 0.5616798400878906\n", + " ],\n", + " [\n", + " 154.43678283691406,\n", + " 314.49652099609375,\n", + " 0.24991558492183685\n", + " ],\n", + " [\n", + " 182.59487915039062,\n", + " 312.3272705078125,\n", + " 0.592433512210846\n", + " ],\n", + " [\n", + " 182.8374786376953,\n", + " 306.06494140625,\n", + " 0.419148325920105\n", + " ],\n", + " [\n", + " 310.11920166015625,\n", + " 273.36737060546875,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.7231140136719,\n", + " 277.9724426269531,\n", + " 0.5769408345222473\n", + " ],\n", + " [\n", + " 362.3317565917969,\n", + " 282.26922607421875,\n", + " 0.43514740467071533\n", + " ],\n", + " [\n", + " 184.4752655029297,\n", + " 311.1407470703125,\n", + " 0.38407573103904724\n", + " ],\n", + " [\n", + " 187.931640625,\n", + " 302.62933349609375,\n", + " 0.4633309543132782\n", + " ],\n", + " [\n", + " 356.0732421875,\n", + " 340.64599609375,\n", + " 0.4025139808654785\n", + " ],\n", + " [\n", + " 362.9293212890625,\n", + " 341.8232727050781,\n", + " 0.5162671208381653\n", + " ],\n", + " [\n", + " 309.4438781738281,\n", + " 345.9781799316406,\n", + " 0.33583828806877136\n", + " ],\n", + " [\n", + " 331.2337341308594,\n", + " 355.5598449707031,\n", + " 0.4219924509525299\n", + " ],\n", + " [\n", + " 366.71087646484375,\n", + " 345.36962890625,\n", + " 0.4366249144077301\n", + " ],\n", + " [\n", + " 269.210693359375,\n", + " 358.0929260253906,\n", + " 0.3690962493419647\n", + " ],\n", + " [\n", + " 424.5083923339844,\n", + " 376.5726013183594,\n", + " 0.42966294288635254\n", + " ],\n", + " [\n", + " 303.0454406738281,\n", + " 218.3123779296875,\n", + " 0.31208333373069763\n", + " ],\n", + " [\n", + " 598.5891723632812,\n", + " 378.4424133300781,\n", + " 0.5889440178871155\n", + " ],\n", + " [\n", + " 197.69949340820312,\n", + " 217.56845092773438,\n", + " 0.3414533734321594\n", + " ],\n", + " [\n", + " 563.177734375,\n", + " 386.8681945800781,\n", + " 0.39073020219802856\n", + " ],\n", + " [\n", + " 516.6555786132812,\n", + " 428.7958679199219,\n", + " 0.5345339179039001\n", + " ],\n", + " [\n", + " 442.6798095703125,\n", + " 357.461181640625,\n", + " 0.23529931902885437\n", + " ],\n", + " [\n", + " 507.02862548828125,\n", + " 392.94842529296875,\n", + " 0.26026907563209534\n", + " ],\n", + " [\n", + " 511.629150390625,\n", + " 447.88116455078125,\n", + " 0.5966296792030334\n", + " ],\n", + " [\n", + " 592.001708984375,\n", + " 408.8056945800781,\n", + " 0.5580258965492249\n", + " ],\n", + " [\n", + " 463.76220703125,\n", + " 402.1715087890625,\n", + " 0.16743294894695282\n", + " ],\n", + " [\n", + " 111.18097686767578,\n", + " 276.2333679199219,\n", + " 0.2950418293476105\n", + " ],\n", + " [\n", + " 573.3688354492188,\n", + " 393.168701171875,\n", + " 0.4543166756629944\n", + " ],\n", + " [\n", + " 481.0332946777344,\n", + " 433.19580078125,\n", + " 0.2525552809238434\n", + " ],\n", + " [\n", + " 551.67578125,\n", + " 466.51300048828125,\n", + " 0.48711535334587097\n", + " ],\n", + " [\n", + " 468.5693054199219,\n", + " 413.4539489746094,\n", + " 0.38309159874916077\n", + " ],\n", + " [\n", + " 135.75698852539062,\n", + " 170.189697265625,\n", + " 0.30588316917419434\n", + " ],\n", + " [\n", + " 459.2412109375,\n", + " 403.5185852050781,\n", + " 0.3879672586917877\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.8528142,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.8770585\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.9255464,\n", + " \"step\": 17,\n", + " \"pose\": [\n", + " [\n", + " 289.01116943359375,\n", + " 299.0413818359375,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.1086730957031,\n", + " 308.51885986328125,\n", + " 0.95180344581604\n", + " ],\n", + " [\n", + " 296.3152770996094,\n", + " 316.4967956542969,\n", + " 0.5928277373313904\n", + " ],\n", + " [\n", + " 290.5437927246094,\n", + " 311.6708679199219,\n", + " 0.6084922552108765\n", + " ],\n", + " [\n", + " 306.17803955078125,\n", + " 313.6896057128906,\n", + " 0.7187818288803101\n", + " ],\n", + " [\n", + " 284.83319091796875,\n", + " 267.0993957519531,\n", + " 0.9082852602005005\n", + " ],\n", + " [\n", + " 285.2369384765625,\n", + " 239.99192810058594,\n", + " 0.4807704985141754\n", + " ],\n", + " [\n", + " 281.5351257324219,\n", + " 244.12281799316406,\n", + " 0.25091421604156494\n", + " ],\n", + " [\n", + " 183.10739135742188,\n", + " 313.957275390625,\n", + " 0.47192785143852234\n", + " ],\n", + " [\n", + " 182.70115661621094,\n", + " 306.3948974609375,\n", + " 0.3859936594963074\n", + " ],\n", + " [\n", + " 310.44354248046875,\n", + " 273.25341796875,\n", + " 1.0\n", + " ],\n", + " [\n", + " 362.1278076171875,\n", + " 276.0966796875,\n", + " 0.66349196434021\n", + " ],\n", + " [\n", + " 363.0843200683594,\n", + " 282.9954528808594,\n", + " 0.44170665740966797\n", + " ],\n", + " [\n", + " 182.47335815429688,\n", + " 311.0877685546875,\n", + " 0.2967713475227356\n", + " ],\n", + " [\n", + " 188.00137329101562,\n", + " 304.48931884765625,\n", + " 0.45905861258506775\n", + " ],\n", + " [\n", + " 354.645263671875,\n", + " 339.31219482421875,\n", + " 0.3976733684539795\n", + " ],\n", + " [\n", + " 363.1065979003906,\n", + " 339.9534606933594,\n", + " 0.5860384702682495\n", + " ],\n", + " [\n", + " 316.1658935546875,\n", + " 342.28369140625,\n", + " 0.3572511076927185\n", + " ],\n", + " [\n", + " 331.590087890625,\n", + " 355.1735534667969,\n", + " 0.4502539336681366\n", + " ],\n", + " [\n", + " 363.5017395019531,\n", + " 344.60235595703125,\n", + " 0.5015437006950378\n", + " ],\n", + " [\n", + " 268.5982666015625,\n", + " 358.1197204589844,\n", + " 0.36430323123931885\n", + " ],\n", + " [\n", + " 378.5502014160156,\n", + " 355.8092041015625,\n", + " 0.42007115483283997\n", + " ],\n", + " [\n", + " 303.5655517578125,\n", + " 217.58612060546875,\n", + " 0.33906295895576477\n", + " ],\n", + " [\n", + " 596.765380859375,\n", + " 379.427001953125,\n", + " 0.5411680340766907\n", + " ],\n", + " [\n", + " 199.35903930664062,\n", + " 222.4739990234375,\n", + " 0.29007023572921753\n", + " ],\n", + " [\n", + " 562.4085083007812,\n", + " 386.65509033203125,\n", + " 0.3170825242996216\n", + " ],\n", + " [\n", + " 517.340087890625,\n", + " 430.5169677734375,\n", + " 0.6213215589523315\n", + " ],\n", + " [\n", + " 441.74444580078125,\n", + " 357.04644775390625,\n", + " 0.2545863389968872\n", + " ],\n", + " [\n", + " 503.5122375488281,\n", + " 393.9283447265625,\n", + " 0.30190107226371765\n", + " ],\n", + " [\n", + " 510.5965576171875,\n", + " 450.106201171875,\n", + " 0.5517356991767883\n", + " ],\n", + " [\n", + " 591.1541748046875,\n", + " 408.65826416015625,\n", + " 0.49750760197639465\n", + " ],\n", + " [\n", + " 475.8544921875,\n", + " 422.05078125,\n", + " 0.1733374446630478\n", + " ],\n", + " [\n", + " 109.9720687866211,\n", + " 271.4387512207031,\n", + " 0.2799350619316101\n", + " ],\n", + " [\n", + " 572.795166015625,\n", + " 393.3072204589844,\n", + " 0.3979363441467285\n", + " ],\n", + " [\n", + " 479.5787353515625,\n", + " 432.65380859375,\n", + " 0.22706195712089539\n", + " ],\n", + " [\n", + " 550.501708984375,\n", + " 467.0165100097656,\n", + " 0.4965691566467285\n", + " ],\n", + " [\n", + " 468.164306640625,\n", + " 412.325927734375,\n", + " 0.3681727349758148\n", + " ],\n", + " [\n", + " 461.05029296875,\n", + " 400.1574401855469,\n", + " 0.36365073919296265\n", + " ],\n", + " [\n", + " 460.8140563964844,\n", + " 402.9066467285156,\n", + " 0.4981914758682251\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.8993094,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.9255464\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.9609218,\n", + " \"step\": 18,\n", + " \"pose\": [\n", + " [\n", + " 288.9327392578125,\n", + " 299.13629150390625,\n", + " 1.0\n", + " ],\n", + " [\n", + " 292.8038330078125,\n", + " 308.6182861328125,\n", + " 0.965558648109436\n", + " ],\n", + " [\n", + " 297.1647644042969,\n", + " 315.5074157714844,\n", + " 0.6364358067512512\n", + " ],\n", + " [\n", + " 290.55609130859375,\n", + " 311.7894287109375,\n", + " 0.6080928444862366\n", + " ],\n", + " [\n", + " 305.4518127441406,\n", + " 313.89239501953125,\n", + " 0.7770905494689941\n", + " ],\n", + " [\n", + " 284.95733642578125,\n", + " 267.11175537109375,\n", + " 0.9243097305297852\n", + " ],\n", + " [\n", + " 285.8481750488281,\n", + " 238.2171630859375,\n", + " 0.49581968784332275\n", + " ],\n", + " [\n", + " 281.67034912109375,\n", + " 238.3629608154297,\n", + " 0.2495463341474533\n", + " ],\n", + " [\n", + " 181.5650177001953,\n", + " 314.2547912597656,\n", + " 0.5168197751045227\n", + " ],\n", + " [\n", + " 182.05751037597656,\n", + " 306.9068908691406,\n", + " 0.36109668016433716\n", + " ],\n", + " [\n", + " 310.2335205078125,\n", + " 273.6082763671875,\n", + " 1.0\n", + " ],\n", + " [\n", + " 362.219482421875,\n", + " 276.6759948730469,\n", + " 0.618270754814148\n", + " ],\n", + " [\n", + " 362.4667663574219,\n", + " 280.8567199707031,\n", + " 0.40474116802215576\n", + " ],\n", + " [\n", + " 184.80101013183594,\n", + " 316.924560546875,\n", + " 0.3415667712688446\n", + " ],\n", + " [\n", + " 189.2113800048828,\n", + " 305.0552062988281,\n", + " 0.44207993149757385\n", + " ],\n", + " [\n", + " 354.8471984863281,\n", + " 341.1282043457031,\n", + " 0.4782800078392029\n", + " ],\n", + " [\n", + " 363.4027404785156,\n", + " 341.2732238769531,\n", + " 0.518268346786499\n", + " ],\n", + " [\n", + " 316.40673828125,\n", + " 342.68695068359375,\n", + " 0.3713846504688263\n", + " ],\n", + " [\n", + " 330.7171630859375,\n", + " 355.67803955078125,\n", + " 0.46434029936790466\n", + " ],\n", + " [\n", + " 367.51568603515625,\n", + " 344.2020568847656,\n", + " 0.4221855401992798\n", + " ],\n", + " [\n", + " 269.15869140625,\n", + " 357.6059265136719,\n", + " 0.43143007159233093\n", + " ],\n", + " [\n", + " 377.92431640625,\n", + " 355.83026123046875,\n", + " 0.42217832803726196\n", + " ],\n", + " [\n", + " 266.35400390625,\n", + " 359.1109313964844,\n", + " 0.36265674233436584\n", + " ],\n", + " [\n", + " 598.98876953125,\n", + " 378.3539123535156,\n", + " 0.5779925584793091\n", + " ],\n", + " [\n", + " 521.8517456054688,\n", + " 317.60223388671875,\n", + " 0.3474227786064148\n", + " ],\n", + " [\n", + " 563.2191162109375,\n", + " 386.7420654296875,\n", + " 0.3701111972332001\n", + " ],\n", + " [\n", + " 516.5422973632812,\n", + " 430.6274108886719,\n", + " 0.5754683613777161\n", + " ],\n", + " [\n", + " 566.955078125,\n", + " 197.94085693359375,\n", + " 0.35363760590553284\n", + " ],\n", + " [\n", + " 573.0870971679688,\n", + " 198.2352294921875,\n", + " 0.5906892418861389\n", + " ],\n", + " [\n", + " 511.6949157714844,\n", + " 449.3252258300781,\n", + " 0.6369988322257996\n", + " ],\n", + " [\n", + " 591.0794067382812,\n", + " 408.18438720703125,\n", + " 0.5102192759513855\n", + " ],\n", + " [\n", + " 508.6004638671875,\n", + " 381.352783203125,\n", + " 0.2138703316450119\n", + " ],\n", + " [\n", + " 109.30960083007812,\n", + " 272.1946716308594,\n", + " 0.336846262216568\n", + " ],\n", + " [\n", + " 572.595703125,\n", + " 393.81610107421875,\n", + " 0.48220178484916687\n", + " ],\n", + " [\n", + " 482.5830383300781,\n", + " 431.64202880859375,\n", + " 0.18888558447360992\n", + " ],\n", + " [\n", + " 549.8436279296875,\n", + " 465.8060302734375,\n", + " 0.6060653924942017\n", + " ],\n", + " [\n", + " 470.1370544433594,\n", + " 413.1282653808594,\n", + " 0.4226745069026947\n", + " ],\n", + " [\n", + " 134.86871337890625,\n", + " 172.37551879882812,\n", + " 0.34861159324645996\n", + " ],\n", + " [\n", + " 460.8798522949219,\n", + " 403.1666564941406,\n", + " 0.4525850713253021\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.933552,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.9609218\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588105.996339,\n", + " \"step\": 19,\n", + " \"pose\": [\n", + " [\n", + " 289.3584289550781,\n", + " 299.6400451660156,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.6114807128906,\n", + " 308.7494201660156,\n", + " 0.9362009763717651\n", + " ],\n", + " [\n", + " 296.5362548828125,\n", + " 315.8563537597656,\n", + " 0.6216031908988953\n", + " ],\n", + " [\n", + " 290.3965759277344,\n", + " 311.8982849121094,\n", + " 0.5908151865005493\n", + " ],\n", + " [\n", + " 305.3473205566406,\n", + " 314.0240783691406,\n", + " 0.6830025911331177\n", + " ],\n", + " [\n", + " 284.38623046875,\n", + " 267.3375244140625,\n", + " 0.938642144203186\n", + " ],\n", + " [\n", + " 183.94895935058594,\n", + " 312.9803771972656,\n", + " 0.5159981846809387\n", + " ],\n", + " [\n", + " 153.8936767578125,\n", + " 314.8314514160156,\n", + " 0.26622244715690613\n", + " ],\n", + " [\n", + " 181.40602111816406,\n", + " 314.3370056152344,\n", + " 0.5327624082565308\n", + " ],\n", + " [\n", + " 182.94410705566406,\n", + " 307.2097473144531,\n", + " 0.4217340648174286\n", + " ],\n", + " [\n", + " 310.5429992675781,\n", + " 273.63134765625,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.03759765625,\n", + " 275.5606994628906,\n", + " 0.6987367868423462\n", + " ],\n", + " [\n", + " 363.2841491699219,\n", + " 282.46881103515625,\n", + " 0.45098239183425903\n", + " ],\n", + " [\n", + " 183.90602111816406,\n", + " 316.1698913574219,\n", + " 0.39084556698799133\n", + " ],\n", + " [\n", + " 193.3860321044922,\n", + " 305.0615234375,\n", + " 0.507167398929596\n", + " ],\n", + " [\n", + " 355.57421875,\n", + " 341.30316162109375,\n", + " 0.4452424645423889\n", + " ],\n", + " [\n", + " 363.03021240234375,\n", + " 340.71435546875,\n", + " 0.5201398730278015\n", + " ],\n", + " [\n", + " 317.2238464355469,\n", + " 345.26666259765625,\n", + " 0.34468573331832886\n", + " ],\n", + " [\n", + " 332.4403991699219,\n", + " 356.6281433105469,\n", + " 0.46254584193229675\n", + " ],\n", + " [\n", + " 363.12640380859375,\n", + " 345.547119140625,\n", + " 0.42061465978622437\n", + " ],\n", + " [\n", + " 269.6493225097656,\n", + " 357.64892578125,\n", + " 0.4019133448600769\n", + " ],\n", + " [\n", + " 378.178466796875,\n", + " 356.5284729003906,\n", + " 0.4181462824344635\n", + " ],\n", + " [\n", + " 266.38238525390625,\n", + " 358.6065368652344,\n", + " 0.33007708191871643\n", + " ],\n", + " [\n", + " 599.5531005859375,\n", + " 377.76666259765625,\n", + " 0.629446804523468\n", + " ],\n", + " [\n", + " 184.41952514648438,\n", + " 208.114013671875,\n", + " 0.4009157419204712\n", + " ],\n", + " [\n", + " 519.3410034179688,\n", + " 390.22406005859375,\n", + " 0.3750861883163452\n", + " ],\n", + " [\n", + " 516.8319702148438,\n", + " 428.9804382324219,\n", + " 0.657153844833374\n", + " ],\n", + " [\n", + " 567.7550659179688,\n", + " 193.29896545410156,\n", + " 0.34581509232521057\n", + " ],\n", + " [\n", + " 509.476806640625,\n", + " 386.754150390625,\n", + " 0.3405891954898834\n", + " ],\n", + " [\n", + " 516.4321899414062,\n", + " 443.82684326171875,\n", + " 0.5484656095504761\n", + " ],\n", + " [\n", + " 594.040283203125,\n", + " 409.7172546386719,\n", + " 0.5296698212623596\n", + " ],\n", + " [\n", + " 508.0539855957031,\n", + " 380.4469299316406,\n", + " 0.20867124199867249\n", + " ],\n", + " [\n", + " 115.30363464355469,\n", + " 228.87652587890625,\n", + " 0.15687808394432068\n", + " ],\n", + " [\n", + " 573.0440063476562,\n", + " 393.09100341796875,\n", + " 0.39379948377609253\n", + " ],\n", + " [\n", + " 480.1328125,\n", + " 431.7356262207031,\n", + " 0.2691611051559448\n", + " ],\n", + " [\n", + " 550.718505859375,\n", + " 466.6239318847656,\n", + " 0.5681474804878235\n", + " ],\n", + " [\n", + " 469.20660400390625,\n", + " 413.4725036621094,\n", + " 0.39115187525749207\n", + " ],\n", + " [\n", + " 133.34088134765625,\n", + " 172.20274353027344,\n", + " 0.3368547558784485\n", + " ],\n", + " [\n", + " 459.3551025390625,\n", + " 403.5044250488281,\n", + " 0.45792460441589355\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.9642372,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588105.996339\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.0221846,\n", + " \"step\": 20,\n", + " \"pose\": [\n", + " [\n", + " 288.93731689453125,\n", + " 298.9955749511719,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.2713928222656,\n", + " 308.37408447265625,\n", + " 0.943423330783844\n", + " ],\n", + " [\n", + " 296.5384826660156,\n", + " 315.9530944824219,\n", + " 0.603498101234436\n", + " ],\n", + " [\n", + " 290.7826843261719,\n", + " 311.4992980957031,\n", + " 0.6115655899047852\n", + " ],\n", + " [\n", + " 304.9278259277344,\n", + " 313.83294677734375,\n", + " 0.7098742127418518\n", + " ],\n", + " [\n", + " 284.4320373535156,\n", + " 267.431884765625,\n", + " 0.9166838526725769\n", + " ],\n", + " [\n", + " 287.8583984375,\n", + " 231.46864318847656,\n", + " 0.4841695725917816\n", + " ],\n", + " [\n", + " 294.63043212890625,\n", + " 218.80035400390625,\n", + " 0.29108670353889465\n", + " ],\n", + " [\n", + " 181.39418029785156,\n", + " 314.2521057128906,\n", + " 0.5189660787582397\n", + " ],\n", + " [\n", + " 304.42047119140625,\n", + " 209.36367797851562,\n", + " 0.40350794792175293\n", + " ],\n", + " [\n", + " 310.4044494628906,\n", + " 273.7499694824219,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.916259765625,\n", + " 275.2131042480469,\n", + " 0.6535776853561401\n", + " ],\n", + " [\n", + " 364.2809753417969,\n", + " 282.39776611328125,\n", + " 0.40148383378982544\n", + " ],\n", + " [\n", + " 180.97071838378906,\n", + " 317.89715576171875,\n", + " 0.3581520915031433\n", + " ],\n", + " [\n", + " 187.54930114746094,\n", + " 302.6390686035156,\n", + " 0.4738801419734955\n", + " ],\n", + " [\n", + " 354.7167663574219,\n", + " 341.0120849609375,\n", + " 0.38925886154174805\n", + " ],\n", + " [\n", + " 362.65179443359375,\n", + " 341.0782775878906,\n", + " 0.5246103405952454\n", + " ],\n", + " [\n", + " 333.5454406738281,\n", + " 342.33709716796875,\n", + " 0.3726454973220825\n", + " ],\n", + " [\n", + " 331.2152099609375,\n", + " 355.3799133300781,\n", + " 0.5273229479789734\n", + " ],\n", + " [\n", + " 292.8047790527344,\n", + " 357.6714172363281,\n", + " 0.4529617428779602\n", + " ],\n", + " [\n", + " 269.3791198730469,\n", + " 357.7329406738281,\n", + " 0.33708542585372925\n", + " ],\n", + " [\n", + " 413.47406005859375,\n", + " 371.240478515625,\n", + " 0.38202741742134094\n", + " ],\n", + " [\n", + " 266.7149658203125,\n", + " 358.42596435546875,\n", + " 0.255717396736145\n", + " ],\n", + " [\n", + " 599.5459594726562,\n", + " 377.59088134765625,\n", + " 0.6171793937683105\n", + " ],\n", + " [\n", + " 182.58990478515625,\n", + " 208.6776580810547,\n", + " 0.3807677626609802\n", + " ],\n", + " [\n", + " 563.1406860351562,\n", + " 387.25885009765625,\n", + " 0.4175715744495392\n", + " ],\n", + " [\n", + " 517.3438110351562,\n", + " 431.8337097167969,\n", + " 0.5834364295005798\n", + " ],\n", + " [\n", + " 571.3803100585938,\n", + " 186.68905639648438,\n", + " 0.3315950930118561\n", + " ],\n", + " [\n", + " 572.1192016601562,\n", + " 190.96864318847656,\n", + " 0.44181355834007263\n", + " ],\n", + " [\n", + " 513.9149169921875,\n", + " 444.82080078125,\n", + " 0.5983200073242188\n", + " ],\n", + " [\n", + " 590.0543212890625,\n", + " 409.4712219238281,\n", + " 0.48646411299705505\n", + " ],\n", + " [\n", + " 474.74908447265625,\n", + " 422.23394775390625,\n", + " 0.2016405165195465\n", + " ],\n", + " [\n", + " 108.44601440429688,\n", + " 270.20269775390625,\n", + " 0.18030454218387604\n", + " ],\n", + " [\n", + " 572.8609008789062,\n", + " 393.09124755859375,\n", + " 0.4335196018218994\n", + " ],\n", + " [\n", + " 479.59942626953125,\n", + " 431.8648376464844,\n", + " 0.26634445786476135\n", + " ],\n", + " [\n", + " 551.6661376953125,\n", + " 466.70135498046875,\n", + " 0.5170703530311584\n", + " ],\n", + " [\n", + " 468.4554443359375,\n", + " 413.3636779785156,\n", + " 0.391674280166626\n", + " ],\n", + " [\n", + " 135.02671813964844,\n", + " 171.04122924804688,\n", + " 0.3395681083202362\n", + " ],\n", + " [\n", + " 460.0732727050781,\n", + " 402.4387512207031,\n", + " 0.43441158533096313\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588105.9977791,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.0241945\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.0568671,\n", + " \"step\": 21,\n", + " \"pose\": [\n", + " [\n", + " 289.0395202636719,\n", + " 299.1026611328125,\n", + " 1.0\n", + " ],\n", + " [\n", + " 294.0700378417969,\n", + " 308.541015625,\n", + " 0.9578058123588562\n", + " ],\n", + " [\n", + " 296.70440673828125,\n", + " 315.8837890625,\n", + " 0.6122352480888367\n", + " ],\n", + " [\n", + " 291.8658752441406,\n", + " 311.9002685546875,\n", + " 0.5979498624801636\n", + " ],\n", + " [\n", + " 306.3223876953125,\n", + " 312.81304931640625,\n", + " 0.7840145826339722\n", + " ],\n", + " [\n", + " 284.8127136230469,\n", + " 267.5986328125,\n", + " 0.9234873652458191\n", + " ],\n", + " [\n", + " 295.66680908203125,\n", + " 222.97425842285156,\n", + " 0.5263190865516663\n", + " ],\n", + " [\n", + " 296.760009765625,\n", + " 216.06723022460938,\n", + " 0.33805859088897705\n", + " ],\n", + " [\n", + " 182.27871704101562,\n", + " 313.2104187011719,\n", + " 0.4452279508113861\n", + " ],\n", + " [\n", + " 302.8914794921875,\n", + " 207.4759521484375,\n", + " 0.4147292673587799\n", + " ],\n", + " [\n", + " 310.67901611328125,\n", + " 273.2028503417969,\n", + " 1.0\n", + " ],\n", + " [\n", + " 364.8524169921875,\n", + " 274.2853088378906,\n", + " 0.6022342443466187\n", + " ],\n", + " [\n", + " 363.9833679199219,\n", + " 282.55401611328125,\n", + " 0.4224339723587036\n", + " ],\n", + " [\n", + " 181.81272888183594,\n", + " 307.6803283691406,\n", + " 0.26984354853630066\n", + " ],\n", + " [\n", + " 194.29331970214844,\n", + " 306.2869873046875,\n", + " 0.40715906023979187\n", + " ],\n", + " [\n", + " 351.9360046386719,\n", + " 343.8916015625,\n", + " 0.3862766921520233\n", + " ],\n", + " [\n", + " 363.7510986328125,\n", + " 342.71826171875,\n", + " 0.5607332587242126\n", + " ],\n", + " [\n", + " 333.4976501464844,\n", + " 343.6668701171875,\n", + " 0.35671743750572205\n", + " ],\n", + " [\n", + " 332.6026916503906,\n", + " 355.7337341308594,\n", + " 0.49907931685447693\n", + " ],\n", + " [\n", + " 281.2688903808594,\n", + " 357.4167175292969,\n", + " 0.5524068474769592\n", + " ],\n", + " [\n", + " 270.1759338378906,\n", + " 357.4426574707031,\n", + " 0.47422295808792114\n", + " ],\n", + " [\n", + " 379.6234436035156,\n", + " 356.0726318359375,\n", + " 0.4222455620765686\n", + " ],\n", + " [\n", + " 266.31121826171875,\n", + " 358.93865966796875,\n", + " 0.4061802625656128\n", + " ],\n", + " [\n", + " 598.6159057617188,\n", + " 377.72808837890625,\n", + " 0.5579972863197327\n", + " ],\n", + " [\n", + " 522.2592163085938,\n", + " 318.6231994628906,\n", + " 0.3378920257091522\n", + " ],\n", + " [\n", + " 518.2576293945312,\n", + " 390.44976806640625,\n", + " 0.41228482127189636\n", + " ],\n", + " [\n", + " 515.7354736328125,\n", + " 429.0234069824219,\n", + " 0.5634108185768127\n", + " ],\n", + " [\n", + " 443.417724609375,\n", + " 356.7914733886719,\n", + " 0.26663434505462646\n", + " ],\n", + " [\n", + " 507.91925048828125,\n", + " 388.6474304199219,\n", + " 0.35248884558677673\n", + " ],\n", + " [\n", + " 512.753173828125,\n", + " 449.7189636230469,\n", + " 0.578186571598053\n", + " ],\n", + " [\n", + " 581.30078125,\n", + " 414.9436340332031,\n", + " 0.4276328682899475\n", + " ],\n", + " [\n", + " 463.5183410644531,\n", + " 402.17388916015625,\n", + " 0.13629432022571564\n", + " ],\n", + " [\n", + " 111.8991470336914,\n", + " 278.3125915527344,\n", + " 0.151170015335083\n", + " ],\n", + " [\n", + " 575.8933715820312,\n", + " 398.4580993652344,\n", + " 0.40502646565437317\n", + " ],\n", + " [\n", + " 143.69412231445312,\n", + " 345.78521728515625,\n", + " 0.24476872384548187\n", + " ],\n", + " [\n", + " 143.1280517578125,\n", + " 373.794189453125,\n", + " 0.4513229429721832\n", + " ],\n", + " [\n", + " 468.4044189453125,\n", + " 411.8726806640625,\n", + " 0.37500157952308655\n", + " ],\n", + " [\n", + " 462.43206787109375,\n", + " 399.8568115234375,\n", + " 0.3841189444065094\n", + " ],\n", + " [\n", + " 461.3127136230469,\n", + " 403.23052978515625,\n", + " 0.5030932426452637\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.0300262,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.0578823\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.0853903,\n", + " \"step\": 22,\n", + " \"pose\": [\n", + " [\n", + " 289.0032653808594,\n", + " 299.5898132324219,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.6610412597656,\n", + " 308.364501953125,\n", + " 0.9171743392944336\n", + " ],\n", + " [\n", + " 297.53411865234375,\n", + " 315.0213928222656,\n", + " 0.5967923998832703\n", + " ],\n", + " [\n", + " 291.45599365234375,\n", + " 310.9009094238281,\n", + " 0.6069502234458923\n", + " ],\n", + " [\n", + " 305.15667724609375,\n", + " 312.3479919433594,\n", + " 0.7363993525505066\n", + " ],\n", + " [\n", + " 284.5779724121094,\n", + " 267.80059814453125,\n", + " 0.8958398699760437\n", + " ],\n", + " [\n", + " 287.5237121582031,\n", + " 231.7471923828125,\n", + " 0.5256384611129761\n", + " ],\n", + " [\n", + " 297.3427734375,\n", + " 217.41281127929688,\n", + " 0.32152754068374634\n", + " ],\n", + " [\n", + " 182.88433837890625,\n", + " 314.53692626953125,\n", + " 0.489521861076355\n", + " ],\n", + " [\n", + " 303.3013916015625,\n", + " 209.451171875,\n", + " 0.42159581184387207\n", + " ],\n", + " [\n", + " 310.30352783203125,\n", + " 273.47393798828125,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.04669189453125,\n", + " 275.4956970214844,\n", + " 0.6113792061805725\n", + " ],\n", + " [\n", + " 364.1795959472656,\n", + " 282.3038024902344,\n", + " 0.44920432567596436\n", + " ],\n", + " [\n", + " 185.60179138183594,\n", + " 312.93963623046875,\n", + " 0.2924720048904419\n", + " ],\n", + " [\n", + " 188.79063415527344,\n", + " 306.9405822753906,\n", + " 0.4584471881389618\n", + " ],\n", + " [\n", + " 355.7679443359375,\n", + " 340.5166320800781,\n", + " 0.4532307982444763\n", + " ],\n", + " [\n", + " 362.6515197753906,\n", + " 342.4863586425781,\n", + " 0.5490198731422424\n", + " ],\n", + " [\n", + " 334.1615905761719,\n", + " 344.5025939941406,\n", + " 0.33372119069099426\n", + " ],\n", + " [\n", + " 331.80181884765625,\n", + " 355.52691650390625,\n", + " 0.4720294773578644\n", + " ],\n", + " [\n", + " 290.5385437011719,\n", + " 358.4304504394531,\n", + " 0.4409693479537964\n", + " ],\n", + " [\n", + " 270.8581237792969,\n", + " 356.5791931152344,\n", + " 0.4198722839355469\n", + " ],\n", + " [\n", + " 424.2137145996094,\n", + " 375.2749328613281,\n", + " 0.4370681345462799\n", + " ],\n", + " [\n", + " 268.27886962890625,\n", + " 357.68585205078125,\n", + " 0.36227190494537354\n", + " ],\n", + " [\n", + " 599.3430786132812,\n", + " 377.6954650878906,\n", + " 0.5684486031532288\n", + " ],\n", + " [\n", + " 520.1165771484375,\n", + " 318.81927490234375,\n", + " 0.3776821196079254\n", + " ],\n", + " [\n", + " 562.5358276367188,\n", + " 387.04693603515625,\n", + " 0.3308621942996979\n", + " ],\n", + " [\n", + " 516.3358154296875,\n", + " 429.0692443847656,\n", + " 0.5608440637588501\n", + " ],\n", + " [\n", + " 566.9133911132812,\n", + " 193.52452087402344,\n", + " 0.33105629682540894\n", + " ],\n", + " [\n", + " 570.7525634765625,\n", + " 196.58692932128906,\n", + " 0.4755719006061554\n", + " ],\n", + " [\n", + " 512.5366821289062,\n", + " 448.55328369140625,\n", + " 0.5968109369277954\n", + " ],\n", + " [\n", + " 590.8397216796875,\n", + " 408.89471435546875,\n", + " 0.4935222268104553\n", + " ],\n", + " [\n", + " 110.8057632446289,\n", + " 271.4251403808594,\n", + " 0.16251139342784882\n", + " ],\n", + " [\n", + " 113.5406494140625,\n", + " 279.87841796875,\n", + " 0.38004982471466064\n", + " ],\n", + " [\n", + " 576.237548828125,\n", + " 397.9443664550781,\n", + " 0.3451719284057617\n", + " ],\n", + " [\n", + " 144.29734802246094,\n", + " 315.7247009277344,\n", + " 0.25869646668434143\n", + " ],\n", + " [\n", + " 549.8232421875,\n", + " 465.3925476074219,\n", + " 0.483270525932312\n", + " ],\n", + " [\n", + " 141.22862243652344,\n", + " 190.88340759277344,\n", + " 0.3440597653388977\n", + " ],\n", + " [\n", + " 462.27349853515625,\n", + " 407.5166931152344,\n", + " 0.39141225814819336\n", + " ],\n", + " [\n", + " 459.8392333984375,\n", + " 402.88421630859375,\n", + " 0.48188316822052\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.0603576,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.0853903\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.113898,\n", + " \"step\": 23,\n", + " \"pose\": [\n", + " [\n", + " 289.3703918457031,\n", + " 299.01654052734375,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.63714599609375,\n", + " 308.040771484375,\n", + " 0.9116361141204834\n", + " ],\n", + " [\n", + " 297.54144287109375,\n", + " 314.6742248535156,\n", + " 0.5906339287757874\n", + " ],\n", + " [\n", + " 292.0235290527344,\n", + " 310.41552734375,\n", + " 0.6293807029724121\n", + " ],\n", + " [\n", + " 305.7689514160156,\n", + " 312.5812072753906,\n", + " 0.6732482314109802\n", + " ],\n", + " [\n", + " 284.58123779296875,\n", + " 267.2427673339844,\n", + " 0.9067515134811401\n", + " ],\n", + " [\n", + " 183.6278076171875,\n", + " 312.8146667480469,\n", + " 0.5189109444618225\n", + " ],\n", + " [\n", + " 373.2416076660156,\n", + " 300.8122863769531,\n", + " 0.3220520615577698\n", + " ],\n", + " [\n", + " 182.9404754638672,\n", + " 312.4514465332031,\n", + " 0.5909151434898376\n", + " ],\n", + " [\n", + " 303.0302429199219,\n", + " 210.0631561279297,\n", + " 0.4749321937561035\n", + " ],\n", + " [\n", + " 310.4212951660156,\n", + " 273.31060791015625,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.5233459472656,\n", + " 275.4824523925781,\n", + " 0.5813355445861816\n", + " ],\n", + " [\n", + " 363.8905029296875,\n", + " 281.9700622558594,\n", + " 0.42637211084365845\n", + " ],\n", + " [\n", + " 185.95431518554688,\n", + " 311.6904296875,\n", + " 0.34200912714004517\n", + " ],\n", + " [\n", + " 192.42385864257812,\n", + " 305.3067932128906,\n", + " 0.4337136447429657\n", + " ],\n", + " [\n", + " 355.9394836425781,\n", + " 340.25689697265625,\n", + " 0.43801149725914\n", + " ],\n", + " [\n", + " 363.40325927734375,\n", + " 341.9284362792969,\n", + " 0.5555760264396667\n", + " ],\n", + " [\n", + " 314.46746826171875,\n", + " 342.5721130371094,\n", + " 0.35660916566848755\n", + " ],\n", + " [\n", + " 339.87835693359375,\n", + " 359.97998046875,\n", + " 0.4949004054069519\n", + " ],\n", + " [\n", + " 290.6855773925781,\n", + " 358.2529602050781,\n", + " 0.4353252649307251\n", + " ],\n", + " [\n", + " 270.9769287109375,\n", + " 356.5190124511719,\n", + " 0.40807199478149414\n", + " ],\n", + " [\n", + " 424.1128845214844,\n", + " 374.64202880859375,\n", + " 0.42985209822654724\n", + " ],\n", + " [\n", + " 267.4349670410156,\n", + " 357.9966125488281,\n", + " 0.3516447842121124\n", + " ],\n", + " [\n", + " 597.1614990234375,\n", + " 379.19207763671875,\n", + " 0.5612181425094604\n", + " ],\n", + " [\n", + " 200.91233825683594,\n", + " 229.2719268798828,\n", + " 0.3809609115123749\n", + " ],\n", + " [\n", + " 563.5918579101562,\n", + " 387.2767028808594,\n", + " 0.3711811602115631\n", + " ],\n", + " [\n", + " 516.4181518554688,\n", + " 428.2336120605469,\n", + " 0.6263866424560547\n", + " ],\n", + " [\n", + " 570.1748046875,\n", + " 189.33961486816406,\n", + " 0.3523023724555969\n", + " ],\n", + " [\n", + " 569.3735961914062,\n", + " 194.79763793945312,\n", + " 0.36297234892845154\n", + " ],\n", + " [\n", + " 512.5416259765625,\n", + " 448.44085693359375,\n", + " 0.5241063833236694\n", + " ],\n", + " [\n", + " 155.5992889404297,\n", + " 363.5351867675781,\n", + " 0.46883660554885864\n", + " ],\n", + " [\n", + " 508.84210205078125,\n", + " 377.6853332519531,\n", + " 0.1948014795780182\n", + " ],\n", + " [\n", + " 112.65641021728516,\n", + " 278.4239501953125,\n", + " 0.18293216824531555\n", + " ],\n", + " [\n", + " 572.6771850585938,\n", + " 392.94891357421875,\n", + " 0.39113080501556396\n", + " ],\n", + " [\n", + " 481.0816650390625,\n", + " 432.840087890625,\n", + " 0.22011691331863403\n", + " ],\n", + " [\n", + " 145.10130310058594,\n", + " 373.3096618652344,\n", + " 0.4219147264957428\n", + " ],\n", + " [\n", + " 468.0207824707031,\n", + " 413.1219787597656,\n", + " 0.3619706630706787\n", + " ],\n", + " [\n", + " 136.87318420410156,\n", + " 172.54913330078125,\n", + " 0.3659115135669708\n", + " ],\n", + " [\n", + " 459.65655517578125,\n", + " 403.489013671875,\n", + " 0.46539226174354553\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.0933394,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.113898\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.1507206,\n", + " \"step\": 24,\n", + " \"pose\": [\n", + " [\n", + " 289.14605712890625,\n", + " 298.5685729980469,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.7758483886719,\n", + " 308.03326416015625,\n", + " 0.9337582588195801\n", + " ],\n", + " [\n", + " 297.73248291015625,\n", + " 315.2346496582031,\n", + " 0.6327982544898987\n", + " ],\n", + " [\n", + " 291.4134216308594,\n", + " 311.653564453125,\n", + " 0.609774649143219\n", + " ],\n", + " [\n", + " 305.1743469238281,\n", + " 313.22369384765625,\n", + " 0.7303615808486938\n", + " ],\n", + " [\n", + " 285.11077880859375,\n", + " 267.58795166015625,\n", + " 0.9044018387794495\n", + " ],\n", + " [\n", + " 287.801025390625,\n", + " 230.8278045654297,\n", + " 0.5132544636726379\n", + " ],\n", + " [\n", + " 294.9792785644531,\n", + " 217.2659912109375,\n", + " 0.3110557496547699\n", + " ],\n", + " [\n", + " 182.70785522460938,\n", + " 312.7431640625,\n", + " 0.4303653836250305\n", + " ],\n", + " [\n", + " 302.67926025390625,\n", + " 209.3670196533203,\n", + " 0.3863958716392517\n", + " ],\n", + " [\n", + " 310.23931884765625,\n", + " 273.41265869140625,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.62945556640625,\n", + " 276.4300842285156,\n", + " 0.6256653070449829\n", + " ],\n", + " [\n", + " 363.4427185058594,\n", + " 281.51226806640625,\n", + " 0.4842453896999359\n", + " ],\n", + " [\n", + " 357.33184814453125,\n", + " 280.7920227050781,\n", + " 0.27625176310539246\n", + " ],\n", + " [\n", + " 194.59628295898438,\n", + " 306.9119873046875,\n", + " 0.41847968101501465\n", + " ],\n", + " [\n", + " 355.71160888671875,\n", + " 338.88134765625,\n", + " 0.44307053089141846\n", + " ],\n", + " [\n", + " 362.9523010253906,\n", + " 339.22442626953125,\n", + " 0.6132069230079651\n", + " ],\n", + " [\n", + " 317.2615051269531,\n", + " 345.85675048828125,\n", + " 0.35067668557167053\n", + " ],\n", + " [\n", + " 331.7022705078125,\n", + " 355.4823303222656,\n", + " 0.45521220564842224\n", + " ],\n", + " [\n", + " 363.580810546875,\n", + " 345.0674743652344,\n", + " 0.4948749542236328\n", + " ],\n", + " [\n", + " 269.8246765136719,\n", + " 356.2699279785156,\n", + " 0.5052472949028015\n", + " ],\n", + " [\n", + " 423.73480224609375,\n", + " 374.68438720703125,\n", + " 0.4521806836128235\n", + " ],\n", + " [\n", + " 266.7372131347656,\n", + " 357.82562255859375,\n", + " 0.44915807247161865\n", + " ],\n", + " [\n", + " 598.9044189453125,\n", + " 377.9191589355469,\n", + " 0.5668219923973083\n", + " ],\n", + " [\n", + " 185.02847290039062,\n", + " 209.2593536376953,\n", + " 0.29630619287490845\n", + " ],\n", + " [\n", + " 232.27960205078125,\n", + " 424.1368408203125,\n", + " 0.35069289803504944\n", + " ],\n", + " [\n", + " 516.3990478515625,\n", + " 429.19805908203125,\n", + " 0.6708835959434509\n", + " ],\n", + " [\n", + " 443.72869873046875,\n", + " 355.66790771484375,\n", + " 0.18464210629463196\n", + " ],\n", + " [\n", + " 509.17938232421875,\n", + " 388.7170104980469,\n", + " 0.4232410788536072\n", + " ],\n", + " [\n", + " 513.5040893554688,\n", + " 449.72918701171875,\n", + " 0.5325906872749329\n", + " ],\n", + " [\n", + " 583.7595825195312,\n", + " 413.1138916015625,\n", + " 0.4442688822746277\n", + " ],\n", + " [\n", + " 115.84835052490234,\n", + " 221.41104125976562,\n", + " 0.1638604700565338\n", + " ],\n", + " [\n", + " 116.47493743896484,\n", + " 283.7770690917969,\n", + " 0.2362251728773117\n", + " ],\n", + " [\n", + " 576.6185913085938,\n", + " 399.1842041015625,\n", + " 0.4103768765926361\n", + " ],\n", + " [\n", + " 144.26768493652344,\n", + " 343.4598693847656,\n", + " 0.2693617641925812\n", + " ],\n", + " [\n", + " 550.6482543945312,\n", + " 465.9261474609375,\n", + " 0.52559894323349\n", + " ],\n", + " [\n", + " 467.72491455078125,\n", + " 412.5487365722656,\n", + " 0.3213626742362976\n", + " ],\n", + " [\n", + " 466.04193115234375,\n", + " 410.77960205078125,\n", + " 0.32539424300193787\n", + " ],\n", + " [\n", + " 459.31622314453125,\n", + " 402.8010559082031,\n", + " 0.4295216500759125\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.1260552,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.1507206\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.1780674,\n", + " \"step\": 25,\n", + " \"pose\": [\n", + " [\n", + " 289.6149597167969,\n", + " 299.89166259765625,\n", + " 1.0\n", + " ],\n", + " [\n", + " 294.52008056640625,\n", + " 308.8827209472656,\n", + " 0.9231247305870056\n", + " ],\n", + " [\n", + " 298.28369140625,\n", + " 316.6026916503906,\n", + " 0.6108699440956116\n", + " ],\n", + " [\n", + " 292.314697265625,\n", + " 312.0814514160156,\n", + " 0.5867233276367188\n", + " ],\n", + " [\n", + " 306.8653564453125,\n", + " 313.03192138671875,\n", + " 0.7602759599685669\n", + " ],\n", + " [\n", + " 284.97998046875,\n", + " 268.31109619140625,\n", + " 0.9025271534919739\n", + " ],\n", + " [\n", + " 285.36627197265625,\n", + " 239.4760284423828,\n", + " 0.45559558272361755\n", + " ],\n", + " [\n", + " 278.97967529296875,\n", + " 251.67454528808594,\n", + " 0.3040803074836731\n", + " ],\n", + " [\n", + " 182.5161590576172,\n", + " 314.0823974609375,\n", + " 0.487441748380661\n", + " ],\n", + " [\n", + " 303.5965881347656,\n", + " 209.8799591064453,\n", + " 0.39406585693359375\n", + " ],\n", + " [\n", + " 310.803466796875,\n", + " 273.83941650390625,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.4359130859375,\n", + " 276.5120849609375,\n", + " 0.5850675106048584\n", + " ],\n", + " [\n", + " 364.39306640625,\n", + " 281.8566589355469,\n", + " 0.43770086765289307\n", + " ],\n", + " [\n", + " 184.96409606933594,\n", + " 312.2420349121094,\n", + " 0.32801908254623413\n", + " ],\n", + " [\n", + " 188.65463256835938,\n", + " 306.26580810546875,\n", + " 0.44451019167900085\n", + " ],\n", + " [\n", + " 356.0030517578125,\n", + " 340.06390380859375,\n", + " 0.393046498298645\n", + " ],\n", + " [\n", + " 361.8909606933594,\n", + " 340.0927429199219,\n", + " 0.5161962509155273\n", + " ],\n", + " [\n", + " 315.6810607910156,\n", + " 343.1770935058594,\n", + " 0.37498360872268677\n", + " ],\n", + " [\n", + " 330.904052734375,\n", + " 355.0949401855469,\n", + " 0.5339792370796204\n", + " ],\n", + " [\n", + " 367.3807067871094,\n", + " 343.9208679199219,\n", + " 0.41494372487068176\n", + " ],\n", + " [\n", + " 270.63043212890625,\n", + " 356.5071716308594,\n", + " 0.39088016748428345\n", + " ],\n", + " [\n", + " 424.5980529785156,\n", + " 377.47625732421875,\n", + " 0.40465661883354187\n", + " ],\n", + " [\n", + " 267.8541259765625,\n", + " 358.0108642578125,\n", + " 0.33039915561676025\n", + " ],\n", + " [\n", + " 599.1412353515625,\n", + " 378.35345458984375,\n", + " 0.5644627213478088\n", + " ],\n", + " [\n", + " 184.03726196289062,\n", + " 209.42005920410156,\n", + " 0.459592342376709\n", + " ],\n", + " [\n", + " 511.7866516113281,\n", + " 408.0044860839844,\n", + " 0.3771219551563263\n", + " ],\n", + " [\n", + " 516.994873046875,\n", + " 430.9440612792969,\n", + " 0.5924205780029297\n", + " ],\n", + " [\n", + " 443.1100158691406,\n", + " 355.68621826171875,\n", + " 0.2457418441772461\n", + " ],\n", + " [\n", + " 499.76837158203125,\n", + " 422.5597839355469,\n", + " 0.24084235727787018\n", + " ],\n", + " [\n", + " 511.1026611328125,\n", + " 445.35107421875,\n", + " 0.5383387804031372\n", + " ],\n", + " [\n", + " 589.8642578125,\n", + " 408.1378173828125,\n", + " 0.456050306558609\n", + " ],\n", + " [\n", + " 507.7047119140625,\n", + " 379.2082214355469,\n", + " 0.16193071007728577\n", + " ],\n", + " [\n", + " 112.13590240478516,\n", + " 276.6860656738281,\n", + " 0.282148540019989\n", + " ],\n", + " [\n", + " 573.1347045898438,\n", + " 393.6588439941406,\n", + " 0.4297298192977905\n", + " ],\n", + " [\n", + " 482.9963073730469,\n", + " 432.75811767578125,\n", + " 0.2714385986328125\n", + " ],\n", + " [\n", + " 550.9261474609375,\n", + " 467.3409118652344,\n", + " 0.5166769623756409\n", + " ],\n", + " [\n", + " 468.4465026855469,\n", + " 413.2013244628906,\n", + " 0.42883676290512085\n", + " ],\n", + " [\n", + " 133.96446228027344,\n", + " 171.50291442871094,\n", + " 0.35597431659698486\n", + " ],\n", + " [\n", + " 465.5216369628906,\n", + " 407.3671875,\n", + " 0.4744454324245453\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.1553848,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.1780674\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.2132318,\n", + " \"step\": 26,\n", + " \"pose\": [\n", + " [\n", + " 289.6811218261719,\n", + " 299.3060607910156,\n", + " 1.0\n", + " ],\n", + " [\n", + " 294.1546630859375,\n", + " 308.6109924316406,\n", + " 0.9218483567237854\n", + " ],\n", + " [\n", + " 297.23516845703125,\n", + " 316.22552490234375,\n", + " 0.5849137902259827\n", + " ],\n", + " [\n", + " 292.8453369140625,\n", + " 312.3377990722656,\n", + " 0.5977075695991516\n", + " ],\n", + " [\n", + " 306.42193603515625,\n", + " 313.16546630859375,\n", + " 0.73295658826828\n", + " ],\n", + " [\n", + " 285.2742919921875,\n", + " 268.0978698730469,\n", + " 0.8919780254364014\n", + " ],\n", + " [\n", + " 285.22930908203125,\n", + " 240.1131134033203,\n", + " 0.48513516783714294\n", + " ],\n", + " [\n", + " 278.72900390625,\n", + " 252.45925903320312,\n", + " 0.30949145555496216\n", + " ],\n", + " [\n", + " 182.2201690673828,\n", + " 313.8691711425781,\n", + " 0.4198264181613922\n", + " ],\n", + " [\n", + " 302.0194091796875,\n", + " 209.4342803955078,\n", + " 0.3796064555644989\n", + " ],\n", + " [\n", + " 311.20037841796875,\n", + " 273.72052001953125,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.5647888183594,\n", + " 275.8450622558594,\n", + " 0.551364004611969\n", + " ],\n", + " [\n", + " 361.8849792480469,\n", + " 280.7410583496094,\n", + " 0.4334920346736908\n", + " ],\n", + " [\n", + " 185.7280731201172,\n", + " 312.75677490234375,\n", + " 0.29518699645996094\n", + " ],\n", + " [\n", + " 188.90249633789062,\n", + " 306.63177490234375,\n", + " 0.43662378191947937\n", + " ],\n", + " [\n", + " 355.60833740234375,\n", + " 340.0720520019531,\n", + " 0.4073914885520935\n", + " ],\n", + " [\n", + " 362.4900817871094,\n", + " 339.8585205078125,\n", + " 0.5256603956222534\n", + " ],\n", + " [\n", + " 310.6485900878906,\n", + " 346.7264404296875,\n", + " 0.3758726119995117\n", + " ],\n", + " [\n", + " 331.7028503417969,\n", + " 357.2618408203125,\n", + " 0.5182921886444092\n", + " ],\n", + " [\n", + " 282.8375244140625,\n", + " 356.63385009765625,\n", + " 0.47025105357170105\n", + " ],\n", + " [\n", + " 271.1107482910156,\n", + " 356.7911376953125,\n", + " 0.49355366826057434\n", + " ],\n", + " [\n", + " 424.5859680175781,\n", + " 375.9132080078125,\n", + " 0.3878205716609955\n", + " ],\n", + " [\n", + " 267.3672180175781,\n", + " 358.4584655761719,\n", + " 0.4485253393650055\n", + " ],\n", + " [\n", + " 598.8714599609375,\n", + " 377.88165283203125,\n", + " 0.5623369216918945\n", + " ],\n", + " [\n", + " 184.80422973632812,\n", + " 207.90528869628906,\n", + " 0.4335136115550995\n", + " ],\n", + " [\n", + " 513.0913696289062,\n", + " 407.55938720703125,\n", + " 0.34691154956817627\n", + " ],\n", + " [\n", + " 517.757080078125,\n", + " 432.6747131347656,\n", + " 0.5728744864463806\n", + " ],\n", + " [\n", + " 163.63232421875,\n", + " 212.3854217529297,\n", + " 0.32033267617225647\n", + " ],\n", + " [\n", + " 570.7784423828125,\n", + " 193.9116973876953,\n", + " 0.40288540720939636\n", + " ],\n", + " [\n", + " 513.4071655273438,\n", + " 445.5836486816406,\n", + " 0.6099948883056641\n", + " ],\n", + " [\n", + " 590.9609985351562,\n", + " 408.857421875,\n", + " 0.4939699172973633\n", + " ],\n", + " [\n", + " 476.909423828125,\n", + " 420.9211120605469,\n", + " 0.17517627775669098\n", + " ],\n", + " [\n", + " 111.49516296386719,\n", + " 278.0699462890625,\n", + " 0.20062971115112305\n", + " ],\n", + " [\n", + " 572.727294921875,\n", + " 393.2834167480469,\n", + " 0.39421916007995605\n", + " ],\n", + " [\n", + " 481.32647705078125,\n", + " 431.9410400390625,\n", + " 0.2601860463619232\n", + " ],\n", + " [\n", + " 511.23797607421875,\n", + " 458.18768310546875,\n", + " 0.5182275772094727\n", + " ],\n", + " [\n", + " 468.54132080078125,\n", + " 413.6605529785156,\n", + " 0.40847253799438477\n", + " ],\n", + " [\n", + " 464.9053649902344,\n", + " 410.059326171875,\n", + " 0.35141322016716003\n", + " ],\n", + " [\n", + " 459.9055480957031,\n", + " 403.2681579589844,\n", + " 0.4966220259666443\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.1875935,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.2132318\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.2483606,\n", + " \"step\": 27,\n", + " \"pose\": [\n", + " [\n", + " 289.65423583984375,\n", + " 298.561767578125,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.9566955566406,\n", + " 308.3065490722656,\n", + " 0.9470576047897339\n", + " ],\n", + " [\n", + " 297.505859375,\n", + " 314.407958984375,\n", + " 0.6088659763336182\n", + " ],\n", + " [\n", + " 291.20574951171875,\n", + " 310.8966064453125,\n", + " 0.607652485370636\n", + " ],\n", + " [\n", + " 306.73492431640625,\n", + " 312.7744445800781,\n", + " 0.7566508054733276\n", + " ],\n", + " [\n", + " 285.1037902832031,\n", + " 267.3839416503906,\n", + " 0.906174898147583\n", + " ],\n", + " [\n", + " 185.3070526123047,\n", + " 311.79327392578125,\n", + " 0.5088176727294922\n", + " ],\n", + " [\n", + " 154.76300048828125,\n", + " 314.7828674316406,\n", + " 0.29141828417778015\n", + " ],\n", + " [\n", + " 182.6195068359375,\n", + " 312.42791748046875,\n", + " 0.5560340285301208\n", + " ],\n", + " [\n", + " 304.5721435546875,\n", + " 209.04302978515625,\n", + " 0.4600820243358612\n", + " ],\n", + " [\n", + " 310.9137878417969,\n", + " 273.2908935546875,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.1856994628906,\n", + " 276.47381591796875,\n", + " 0.5621199011802673\n", + " ],\n", + " [\n", + " 363.20672607421875,\n", + " 281.5467224121094,\n", + " 0.427944153547287\n", + " ],\n", + " [\n", + " 181.0316619873047,\n", + " 311.6895446777344,\n", + " 0.3624846041202545\n", + " ],\n", + " [\n", + " 188.46315002441406,\n", + " 304.592529296875,\n", + " 0.5054963231086731\n", + " ],\n", + " [\n", + " 353.033447265625,\n", + " 342.5238037109375,\n", + " 0.40231525897979736\n", + " ],\n", + " [\n", + " 362.5194091796875,\n", + " 339.60711669921875,\n", + " 0.6008350849151611\n", + " ],\n", + " [\n", + " 317.26544189453125,\n", + " 342.3359069824219,\n", + " 0.3960641920566559\n", + " ],\n", + " [\n", + " 331.4060974121094,\n", + " 356.75445556640625,\n", + " 0.5937938690185547\n", + " ],\n", + " [\n", + " 293.347412109375,\n", + " 357.84515380859375,\n", + " 0.4450031816959381\n", + " ],\n", + " [\n", + " 269.11407470703125,\n", + " 357.3741455078125,\n", + " 0.4057061970233917\n", + " ],\n", + " [\n", + " 378.8157653808594,\n", + " 355.3031921386719,\n", + " 0.39472222328186035\n", + " ],\n", + " [\n", + " 265.99078369140625,\n", + " 358.6902160644531,\n", + " 0.3461160957813263\n", + " ],\n", + " [\n", + " 598.9271850585938,\n", + " 377.77618408203125,\n", + " 0.5730097889900208\n", + " ],\n", + " [\n", + " 184.35238647460938,\n", + " 209.6236572265625,\n", + " 0.3743739724159241\n", + " ],\n", + " [\n", + " 513.5267333984375,\n", + " 407.8452453613281,\n", + " 0.394991397857666\n", + " ],\n", + " [\n", + " 517.43359375,\n", + " 432.714111328125,\n", + " 0.6186484694480896\n", + " ],\n", + " [\n", + " 566.1676635742188,\n", + " 200.06082153320312,\n", + " 0.36133381724357605\n", + " ],\n", + " [\n", + " 570.642333984375,\n", + " 203.29977416992188,\n", + " 0.2848742604255676\n", + " ],\n", + " [\n", + " 513.6658935546875,\n", + " 447.7688903808594,\n", + " 0.566241979598999\n", + " ],\n", + " [\n", + " 590.916259765625,\n", + " 409.0061950683594,\n", + " 0.5227742791175842\n", + " ],\n", + " [\n", + " 476.4045104980469,\n", + " 421.7107849121094,\n", + " 0.23629191517829895\n", + " ],\n", + " [\n", + " 109.81532287597656,\n", + " 272.0874938964844,\n", + " 0.2973298132419586\n", + " ],\n", + " [\n", + " 572.2440185546875,\n", + " 393.4117431640625,\n", + " 0.4230390191078186\n", + " ],\n", + " [\n", + " 481.6334228515625,\n", + " 434.35992431640625,\n", + " 0.20489251613616943\n", + " ],\n", + " [\n", + " 511.52740478515625,\n", + " 458.0361633300781,\n", + " 0.47707173228263855\n", + " ],\n", + " [\n", + " 468.145751953125,\n", + " 413.9035339355469,\n", + " 0.4124965965747833\n", + " ],\n", + " [\n", + " 465.691650390625,\n", + " 411.6900329589844,\n", + " 0.3242393732070923\n", + " ],\n", + " [\n", + " 459.133544921875,\n", + " 403.29656982421875,\n", + " 0.4457142651081085\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.2229764,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.2503667\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.2815137,\n", + " \"step\": 28,\n", + " \"pose\": [\n", + " [\n", + " 289.7947998046875,\n", + " 299.4747314453125,\n", + " 1.0\n", + " ],\n", + " [\n", + " 294.1377868652344,\n", + " 308.9644470214844,\n", + " 0.9098001718521118\n", + " ],\n", + " [\n", + " 297.0553894042969,\n", + " 316.2694396972656,\n", + " 0.5839970111846924\n", + " ],\n", + " [\n", + " 292.7187194824219,\n", + " 312.2520446777344,\n", + " 0.5812930464744568\n", + " ],\n", + " [\n", + " 306.51007080078125,\n", + " 313.4143981933594,\n", + " 0.7014763355255127\n", + " ],\n", + " [\n", + " 285.4446105957031,\n", + " 267.66400146484375,\n", + " 0.885840654373169\n", + " ],\n", + " [\n", + " 287.2601623535156,\n", + " 232.3909912109375,\n", + " 0.47544583678245544\n", + " ],\n", + " [\n", + " 279.16656494140625,\n", + " 250.76365661621094,\n", + " 0.29200607538223267\n", + " ],\n", + " [\n", + " 182.51055908203125,\n", + " 314.830322265625,\n", + " 0.4986189305782318\n", + " ],\n", + " [\n", + " 304.1475524902344,\n", + " 207.91217041015625,\n", + " 0.5152255892753601\n", + " ],\n", + " [\n", + " 311.350830078125,\n", + " 273.5013122558594,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.06219482421875,\n", + " 276.67120361328125,\n", + " 0.5790920853614807\n", + " ],\n", + " [\n", + " 362.5161437988281,\n", + " 283.22723388671875,\n", + " 0.4104999601840973\n", + " ],\n", + " [\n", + " 181.13710021972656,\n", + " 312.584716796875,\n", + " 0.29986241459846497\n", + " ],\n", + " [\n", + " 188.63394165039062,\n", + " 303.74395751953125,\n", + " 0.45568570494651794\n", + " ],\n", + " [\n", + " 357.4764099121094,\n", + " 339.7001953125,\n", + " 0.4089646339416504\n", + " ],\n", + " [\n", + " 362.9626770019531,\n", + " 339.128662109375,\n", + " 0.4946100115776062\n", + " ],\n", + " [\n", + " 311.04779052734375,\n", + " 346.371826171875,\n", + " 0.39371228218078613\n", + " ],\n", + " [\n", + " 339.0541687011719,\n", + " 359.549560546875,\n", + " 0.5586241483688354\n", + " ],\n", + " [\n", + " 283.3900146484375,\n", + " 356.6086120605469,\n", + " 0.4299786686897278\n", + " ],\n", + " [\n", + " 270.4642333984375,\n", + " 357.4127502441406,\n", + " 0.40563082695007324\n", + " ],\n", + " [\n", + " 423.99005126953125,\n", + " 375.4238586425781,\n", + " 0.43916311860084534\n", + " ],\n", + " [\n", + " 268.08233642578125,\n", + " 358.42919921875,\n", + " 0.3316787779331207\n", + " ],\n", + " [\n", + " 598.7156982421875,\n", + " 377.3929138183594,\n", + " 0.5510212182998657\n", + " ],\n", + " [\n", + " 182.943115234375,\n", + " 208.30332946777344,\n", + " 0.44296297430992126\n", + " ],\n", + " [\n", + " 564.0025024414062,\n", + " 386.8399353027344,\n", + " 0.39091676473617554\n", + " ],\n", + " [\n", + " 517.2132568359375,\n", + " 432.1662292480469,\n", + " 0.6276928186416626\n", + " ],\n", + " [\n", + " 570.1396484375,\n", + " 190.4938507080078,\n", + " 0.2375359982252121\n", + " ],\n", + " [\n", + " 492.12896728515625,\n", + " 413.6061096191406,\n", + " 0.3405477702617645\n", + " ],\n", + " [\n", + " 512.7205200195312,\n", + " 450.4252624511719,\n", + " 0.6235648393630981\n", + " ],\n", + " [\n", + " 590.1025390625,\n", + " 409.0594787597656,\n", + " 0.5204903483390808\n", + " ],\n", + " [\n", + " 476.5130310058594,\n", + " 422.0225524902344,\n", + " 0.14199496805667877\n", + " ],\n", + " [\n", + " 110.05477142333984,\n", + " 271.470703125,\n", + " 0.22186370193958282\n", + " ],\n", + " [\n", + " 571.6903686523438,\n", + " 393.01318359375,\n", + " 0.4487222135066986\n", + " ],\n", + " [\n", + " 142.70831298828125,\n", + " 346.6425476074219,\n", + " 0.2570325434207916\n", + " ],\n", + " [\n", + " 511.7834167480469,\n", + " 456.3761901855469,\n", + " 0.5804262161254883\n", + " ],\n", + " [\n", + " 468.8331298828125,\n", + " 413.937744140625,\n", + " 0.36150258779525757\n", + " ],\n", + " [\n", + " 465.01104736328125,\n", + " 410.2015380859375,\n", + " 0.3218684196472168\n", + " ],\n", + " [\n", + " 460.2878723144531,\n", + " 402.61614990234375,\n", + " 0.44286683201789856\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.2539957,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.2815137\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.313648,\n", + " \"step\": 29,\n", + " \"pose\": [\n", + " [\n", + " 290.2019958496094,\n", + " 299.82733154296875,\n", + " 1.0\n", + " ],\n", + " [\n", + " 294.038818359375,\n", + " 309.61590576171875,\n", + " 0.8994060754776001\n", + " ],\n", + " [\n", + " 297.1882019042969,\n", + " 316.2828369140625,\n", + " 0.580872118473053\n", + " ],\n", + " [\n", + " 292.24969482421875,\n", + " 311.94366455078125,\n", + " 0.553147554397583\n", + " ],\n", + " [\n", + " 305.8408508300781,\n", + " 312.88555908203125,\n", + " 0.7357133626937866\n", + " ],\n", + " [\n", + " 285.2379455566406,\n", + " 267.34576416015625,\n", + " 0.8884855508804321\n", + " ],\n", + " [\n", + " 288.9740905761719,\n", + " 231.48561096191406,\n", + " 0.5197221636772156\n", + " ],\n", + " [\n", + " 299.1871032714844,\n", + " 218.01211547851562,\n", + " 0.3429694175720215\n", + " ],\n", + " [\n", + " 181.70339965820312,\n", + " 317.9532165527344,\n", + " 0.46619749069213867\n", + " ],\n", + " [\n", + " 304.53472900390625,\n", + " 208.62942504882812,\n", + " 0.45269933342933655\n", + " ],\n", + " [\n", + " 311.17626953125,\n", + " 274.0134582519531,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.29833984375,\n", + " 278.41729736328125,\n", + " 0.5592008233070374\n", + " ],\n", + " [\n", + " 363.13763427734375,\n", + " 281.8921813964844,\n", + " 0.45556432008743286\n", + " ],\n", + " [\n", + " 357.6162414550781,\n", + " 281.98394775390625,\n", + " 0.30312928557395935\n", + " ],\n", + " [\n", + " 187.75889587402344,\n", + " 305.17547607421875,\n", + " 0.44207459688186646\n", + " ],\n", + " [\n", + " 355.2580261230469,\n", + " 341.6760559082031,\n", + " 0.38897281885147095\n", + " ],\n", + " [\n", + " 363.04852294921875,\n", + " 339.8902893066406,\n", + " 0.5060890316963196\n", + " ],\n", + " [\n", + " 314.8399963378906,\n", + " 341.91680908203125,\n", + " 0.40578165650367737\n", + " ],\n", + " [\n", + " 338.7541198730469,\n", + " 359.4593200683594,\n", + " 0.548823893070221\n", + " ],\n", + " [\n", + " 366.559814453125,\n", + " 343.24029541015625,\n", + " 0.4393633306026459\n", + " ],\n", + " [\n", + " 270.7618103027344,\n", + " 357.1903991699219,\n", + " 0.4302641451358795\n", + " ],\n", + " [\n", + " 379.385498046875,\n", + " 355.3668212890625,\n", + " 0.45166778564453125\n", + " ],\n", + " [\n", + " 268.61431884765625,\n", + " 357.9519348144531,\n", + " 0.36507532000541687\n", + " ],\n", + " [\n", + " 597.203125,\n", + " 379.3829345703125,\n", + " 0.587417721748352\n", + " ],\n", + " [\n", + " 182.3574676513672,\n", + " 208.2609100341797,\n", + " 0.39093875885009766\n", + " ],\n", + " [\n", + " 512.343994140625,\n", + " 407.104736328125,\n", + " 0.3616585433483124\n", + " ],\n", + " [\n", + " 517.728759765625,\n", + " 431.97174072265625,\n", + " 0.5530462265014648\n", + " ],\n", + " [\n", + " 567.7471313476562,\n", + " 195.76194763183594,\n", + " 0.41424036026000977\n", + " ],\n", + " [\n", + " 568.5385131835938,\n", + " 196.24961853027344,\n", + " 0.3196858763694763\n", + " ],\n", + " [\n", + " 512.130615234375,\n", + " 450.2174072265625,\n", + " 0.6944491863250732\n", + " ],\n", + " [\n", + " 154.75747680664062,\n", + " 365.33026123046875,\n", + " 0.46970120072364807\n", + " ],\n", + " [\n", + " 476.37847900390625,\n", + " 421.13104248046875,\n", + " 0.2024911344051361\n", + " ],\n", + " [\n", + " 109.27305603027344,\n", + " 276.56787109375,\n", + " 0.22318890690803528\n", + " ],\n", + " [\n", + " 573.2276611328125,\n", + " 393.43096923828125,\n", + " 0.45069870352745056\n", + " ],\n", + " [\n", + " 144.93081665039062,\n", + " 343.8260803222656,\n", + " 0.26525822281837463\n", + " ],\n", + " [\n", + " 510.0262145996094,\n", + " 457.258544921875,\n", + " 0.6254387497901917\n", + " ],\n", + " [\n", + " 468.2686767578125,\n", + " 412.5702209472656,\n", + " 0.40957126021385193\n", + " ],\n", + " [\n", + " 464.2906188964844,\n", + " 410.6726989746094,\n", + " 0.32647469639778137\n", + " ],\n", + " [\n", + " 460.0201721191406,\n", + " 403.037841796875,\n", + " 0.41243088245391846\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.285935,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.313648\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.3619356,\n", + " \"step\": 30,\n", + " \"pose\": [\n", + " [\n", + " 290.1323547363281,\n", + " 299.7147521972656,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.99139404296875,\n", + " 309.4753112792969,\n", + " 0.8872848749160767\n", + " ],\n", + " [\n", + " 297.3484802246094,\n", + " 315.24835205078125,\n", + " 0.5460883975028992\n", + " ],\n", + " [\n", + " 291.71209716796875,\n", + " 310.8031311035156,\n", + " 0.5654347538948059\n", + " ],\n", + " [\n", + " 305.3967590332031,\n", + " 312.7275085449219,\n", + " 0.7120907306671143\n", + " ],\n", + " [\n", + " 285.19940185546875,\n", + " 267.3405456542969,\n", + " 0.8942697048187256\n", + " ],\n", + " [\n", + " 289.3546447753906,\n", + " 231.18653869628906,\n", + " 0.483479768037796\n", + " ],\n", + " [\n", + " 299.21112060546875,\n", + " 218.4276580810547,\n", + " 0.4001926779747009\n", + " ],\n", + " [\n", + " 181.76585388183594,\n", + " 313.5556945800781,\n", + " 0.46045035123825073\n", + " ],\n", + " [\n", + " 309.8026428222656,\n", + " 210.481689453125,\n", + " 0.5369957089424133\n", + " ],\n", + " [\n", + " 310.97064208984375,\n", + " 273.8341369628906,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.9340515136719,\n", + " 277.03912353515625,\n", + " 0.6095738410949707\n", + " ],\n", + " [\n", + " 362.3292541503906,\n", + " 282.6873474121094,\n", + " 0.5387850999832153\n", + " ],\n", + " [\n", + " 183.47048950195312,\n", + " 311.634521484375,\n", + " 0.30759158730506897\n", + " ],\n", + " [\n", + " 193.97259521484375,\n", + " 306.5625,\n", + " 0.4332640469074249\n", + " ],\n", + " [\n", + " 355.8443908691406,\n", + " 341.6749267578125,\n", + " 0.3820899724960327\n", + " ],\n", + " [\n", + " 362.45452880859375,\n", + " 340.09088134765625,\n", + " 0.493086040019989\n", + " ],\n", + " [\n", + " 315.4454650878906,\n", + " 343.0971984863281,\n", + " 0.3856670558452606\n", + " ],\n", + " [\n", + " 339.348388671875,\n", + " 359.60870361328125,\n", + " 0.55845707654953\n", + " ],\n", + " [\n", + " 284.3851623535156,\n", + " 356.5133972167969,\n", + " 0.4924909174442291\n", + " ],\n", + " [\n", + " 271.4208679199219,\n", + " 357.4637451171875,\n", + " 0.3702590763568878\n", + " ],\n", + " [\n", + " 423.9642028808594,\n", + " 375.77801513671875,\n", + " 0.42082488536834717\n", + " ],\n", + " [\n", + " 269.3265075683594,\n", + " 358.4918212890625,\n", + " 0.3108918368816376\n", + " ],\n", + " [\n", + " 595.7271728515625,\n", + " 378.74505615234375,\n", + " 0.6383695006370544\n", + " ],\n", + " [\n", + " 182.5079803466797,\n", + " 209.4701690673828,\n", + " 0.3769323527812958\n", + " ],\n", + " [\n", + " 563.5107421875,\n", + " 388.12860107421875,\n", + " 0.34879517555236816\n", + " ],\n", + " [\n", + " 516.7620239257812,\n", + " 429.2193908691406,\n", + " 0.6218969225883484\n", + " ],\n", + " [\n", + " 443.3829650878906,\n", + " 357.2695007324219,\n", + " 0.2294222116470337\n", + " ],\n", + " [\n", + " 508.9411926269531,\n", + " 390.8059387207031,\n", + " 0.27742016315460205\n", + " ],\n", + " [\n", + " 513.4525756835938,\n", + " 451.084228515625,\n", + " 0.588313102722168\n", + " ],\n", + " [\n", + " 590.62939453125,\n", + " 409.5919494628906,\n", + " 0.49015718698501587\n", + " ],\n", + " [\n", + " 505.6385498046875,\n", + " 379.8114929199219,\n", + " 0.16522188484668732\n", + " ],\n", + " [\n", + " 108.85206604003906,\n", + " 271.24273681640625,\n", + " 0.2543381452560425\n", + " ],\n", + " [\n", + " 573.4502563476562,\n", + " 393.5893249511719,\n", + " 0.49820849299430847\n", + " ],\n", + " [\n", + " 142.85484313964844,\n", + " 314.2268981933594,\n", + " 0.19713541865348816\n", + " ],\n", + " [\n", + " 549.9910278320312,\n", + " 465.56768798828125,\n", + " 0.5062677264213562\n", + " ],\n", + " [\n", + " 469.6101379394531,\n", + " 412.96905517578125,\n", + " 0.3788171112537384\n", + " ],\n", + " [\n", + " 465.2295837402344,\n", + " 411.72161865234375,\n", + " 0.343596875667572\n", + " ],\n", + " [\n", + " 460.37811279296875,\n", + " 402.3249206542969,\n", + " 0.46701255440711975\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.3327684,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.3619356\n", + "}\n", + "{\n", + " \"kind\": \"pose\",\n", + " \"raw\": {\n", + " \"type\": \"pose\",\n", + " \"timestamp\": 1783588106.3911848,\n", + " \"step\": 31,\n", + " \"pose\": [\n", + " [\n", + " 289.88616943359375,\n", + " 299.245849609375,\n", + " 1.0\n", + " ],\n", + " [\n", + " 293.7210693359375,\n", + " 308.9605407714844,\n", + " 0.8916722536087036\n", + " ],\n", + " [\n", + " 297.2877502441406,\n", + " 315.5369873046875,\n", + " 0.5746937394142151\n", + " ],\n", + " [\n", + " 292.2024230957031,\n", + " 311.0600891113281,\n", + " 0.5595524907112122\n", + " ],\n", + " [\n", + " 305.3230285644531,\n", + " 313.09588623046875,\n", + " 0.7339632511138916\n", + " ],\n", + " [\n", + " 285.2215576171875,\n", + " 266.96453857421875,\n", + " 0.894293487071991\n", + " ],\n", + " [\n", + " 297.3047180175781,\n", + " 221.42364501953125,\n", + " 0.5297285914421082\n", + " ],\n", + " [\n", + " 295.8182067871094,\n", + " 216.30775451660156,\n", + " 0.38408026099205017\n", + " ],\n", + " [\n", + " 183.77606201171875,\n", + " 316.7649841308594,\n", + " 0.4275979697704315\n", + " ],\n", + " [\n", + " 308.4361572265625,\n", + " 210.48973083496094,\n", + " 0.5560570359230042\n", + " ],\n", + " [\n", + " 311.0546875,\n", + " 273.9168395996094,\n", + " 1.0\n", + " ],\n", + " [\n", + " 363.8443298339844,\n", + " 277.56488037109375,\n", + " 0.632280170917511\n", + " ],\n", + " [\n", + " 363.19598388671875,\n", + " 283.2601013183594,\n", + " 0.5504708290100098\n", + " ],\n", + " [\n", + " 360.5338439941406,\n", + " 282.9017028808594,\n", + " 0.3014270067214966\n", + " ],\n", + " [\n", + " 187.5973663330078,\n", + " 304.839111328125,\n", + " 0.4563285708427429\n", + " ],\n", + " [\n", + " 355.62762451171875,\n", + " 341.2684020996094,\n", + " 0.4316878914833069\n", + " ],\n", + " [\n", + " 362.1797180175781,\n", + " 340.63848876953125,\n", + " 0.523749828338623\n", + " ],\n", + " [\n", + " 310.7569580078125,\n", + " 347.2984313964844,\n", + " 0.3826305568218231\n", + " ],\n", + " [\n", + " 338.3757629394531,\n", + " 360.44317626953125,\n", + " 0.5659022927284241\n", + " ],\n", + " [\n", + " 367.23004150390625,\n", + " 344.0614929199219,\n", + " 0.43424880504608154\n", + " ],\n", + " [\n", + " 272.1240234375,\n", + " 355.71612548828125,\n", + " 0.5028250217437744\n", + " ],\n", + " [\n", + " 379.81280517578125,\n", + " 355.9754943847656,\n", + " 0.38737767934799194\n", + " ],\n", + " [\n", + " 268.998046875,\n", + " 357.2021789550781,\n", + " 0.44784843921661377\n", + " ],\n", + " [\n", + " 596.336181640625,\n", + " 379.1740417480469,\n", + " 0.545729398727417\n", + " ],\n", + " [\n", + " 201.0037841796875,\n", + " 228.0155029296875,\n", + " 0.42291054129600525\n", + " ],\n", + " [\n", + " 562.3812866210938,\n", + " 386.2914123535156,\n", + " 0.36510559916496277\n", + " ],\n", + " [\n", + " 517.466796875,\n", + " 433.1764831542969,\n", + " 0.5716246962547302\n", + " ],\n", + " [\n", + " 570.5004272460938,\n", + " 194.27825927734375,\n", + " 0.31970250606536865\n", + " ],\n", + " [\n", + " 572.74462890625,\n", + " 201.8236541748047,\n", + " 0.39004698395729065\n", + " ],\n", + " [\n", + " 513.2064208984375,\n", + " 451.0941162109375,\n", + " 0.6408159732818604\n", + " ],\n", + " [\n", + " 590.6119995117188,\n", + " 408.5152282714844,\n", + " 0.5167672634124756\n", + " ],\n", + " [\n", + " 114.4150619506836,\n", + " 235.0469207763672,\n", + " 0.13695929944515228\n", + " ],\n", + " [\n", + " 112.1463623046875,\n", + " 279.28265380859375,\n", + " 0.23755685985088348\n", + " ],\n", + " [\n", + " 571.991943359375,\n", + " 393.113525390625,\n", + " 0.4135165214538574\n", + " ],\n", + " [\n", + " 144.2115478515625,\n", + " 313.7943115234375,\n", + " 0.23972396552562714\n", + " ],\n", + " [\n", + " 510.1828918457031,\n", + " 456.5469055175781,\n", + " 0.503180205821991\n", + " ],\n", + " [\n", + " 468.22821044921875,\n", + " 413.3734436035156,\n", + " 0.37297725677490234\n", + " ],\n", + " [\n", + " 465.0234069824219,\n", + " 410.9199523925781,\n", + " 0.36548110842704773\n", + " ],\n", + " [\n", + " 465.41192626953125,\n", + " 406.6351623535156,\n", + " 0.4397272765636444\n", + " ]\n", + " ],\n", + " \"frame_time\": 1783588106.3645778,\n", + " \"pose_time\": null,\n", + " \"recording\": false\n", + " },\n", + " \"received_at\": 1783588106.3911848\n", + "}\n", + "Received 20 packet(s).\n" + ] + } + ], + "source": [ + "def receive_packets(conn, duration_s: float = 30.0, max_packets: int | None = None):\n", + " start = time.time()\n", + " packets: list[dict[str, Any]] = []\n", + "\n", + " while time.time() - start < duration_s:\n", + " if conn.poll(POLL_INTERVAL_S):\n", + " payload = conn.recv()\n", + " decoded = decode_payload(payload)\n", + " decoded[\"received_at\"] = time.time()\n", + " packets.append(decoded)\n", + " print(json.dumps(decoded, default=str, indent=2))\n", + "\n", + " if max_packets is not None and len(packets) >= max_packets:\n", + " break\n", + "\n", + " print(f\"Received {len(packets)} packet(s).\")\n", + " return packets\n", + "\n", + "\n", + "packets = receive_packets(conn, duration_s=30.0, max_packets=20)" + ] + }, + { + "cell_type": "markdown", + "id": "504fb2a444614c0babb325280ed9130a", + "metadata": {}, + "source": [ + "## Save captured packets\n", + "\n", + "This is useful if you want to inspect the stream shape after a test run." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "59bbdb311c014d738909a11f9e486628", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Saved 20 packet(s) to C:\\Users\\Cyril A\\Desktop\\Code\\DeepLabCut-live-GUI\\dlclivegui\\processors\\custom\\mock_unity_captured_packets.json\n" + ] + } + ], + "source": [ + "out_path = Path(\"mock_unity_captured_packets.json\")\n", + "out_path.write_text(json.dumps(packets, default=str, indent=2), encoding=\"utf-8\")\n", + "print(\"Saved\", len(packets), \"packet(s) to\", out_path.resolve())" + ] + }, + { + "cell_type": "markdown", + "id": "b43b363d81ae4b689946ece5c682cd59", + "metadata": {}, + "source": [ + "## Close the client connection" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8a65eabff63a45729fe45fb5ade58bdc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connection closed\n" + ] + } + ], + "source": [ + "try:\n", + " conn.close()\n", + " print(\"Connection closed\")\n", + "except Exception as exc:\n", + " print(\"Close failed:\", exc)" + ] + }, + { + "cell_type": "markdown", + "id": "c3933fab20d04ec698c2621248eb3be0", + "metadata": {}, + "source": [ + "## Optional local smoke test with a standalone mock server\n", + "\n", + "Use this only if you want to validate the notebook client logic without running DLCLiveGUI.\n", + "\n", + "This requires `mock_socket_processor.py` to be importable, for example by placing it next to this notebook or adding its directory to `sys.path`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "4dd4641cc4064e0191573fe9c69df29b", + "metadata": {}, + "outputs": [], + "source": [ + "# Optional smoke test. Leave commented unless you have mock_socket_processor.py available.\n", + "#\n", + "# import socket\n", + "# import sys\n", + "# from multiprocessing.connection import Client\n", + "#\n", + "# from mock_socket_processor import MockSocketProcessor\n", + "#\n", + "# def free_port():\n", + "# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", + "# s.bind((\"127.0.0.1\", 0))\n", + "# port = s.getsockname()[1]\n", + "# s.close()\n", + "# return port\n", + "#\n", + "# port = free_port()\n", + "# mock = MockSocketProcessor(bind=(\"127.0.0.1\", port), authkey=AUTHKEY)\n", + "# test_conn = Client(mock.address, authkey=AUTHKEY)\n", + "# test_conn.send({\"cmd\": \"ping\"})\n", + "# print(test_conn.recv())\n", + "# mock.process([[1, 2, 0.9]], frame_time=123.456)\n", + "# print(decode_payload(test_conn.recv()))\n", + "# test_conn.close()\n", + "# mock.stop()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "deeplabcut-live-gui (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From bbd7f8c5e012e07428c53b2ab583575e6cf4dc84 Mon Sep 17 00:00:00 2001 From: Cyril Achard Date: Thu, 9 Jul 2026 16:35:03 +0200 Subject: [PATCH 03/17] Update main_window.py --- dlclivegui/gui/main_window.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index 9d98936f8..49c9d38cb 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -1914,7 +1914,42 @@ def worker(): daemon=True, ).start() + def _save_processor_data_if_available(self) -> None: + """Best-effort generic processor save. + + The GUI intentionally does not pass a path here. This lets custom processors + use their own save_path / filename / internal policy. + + Expected processor contract: + processor.save() -> int | bool | None + + Return values are only logged; failure should not crash the GUI. + """ + processor = getattr(self._dlc, "_processor", None) + + # Fallback in case DLCLive owns the processor but _processor was not updated. + if processor is None: + dlc_obj = getattr(self._dlc, "_dlc", None) + processor = getattr(dlc_obj, "processor", None) if dlc_obj is not None else None + + if processor is None: + logger.debug("Processor save skipped: no processor instance available.") + return + + save = getattr(processor, "save", None) + if not callable(save): + logger.debug("Processor save skipped: processor has no callable save().") + return + + try: + result = save() + logger.info("Processor save() completed with result: %r", result) + except Exception: + logger.exception("Processor save() failed.") + def _on_recording_stopped_async(self) -> None: + self._save_processor_data_if_available() + self._recording_stopping = False self.start_record_button.setEnabled(True) self.stop_record_button.setEnabled(False) From 0d1731c2b7d28491cd4c93f8c3a70a01000c3684 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 10 Jul 2026 11:54:27 +0200 Subject: [PATCH 04/17] Add recording file context and recorder path accessors RecordingManager now captures and exposes a current/last recording file context, including run/session directories plus per-camera video and timestamp sidecar paths, so downstream processor hooks can still resolve finalized files after stop_all(). VideoRecorder adds explicit output_path and timestamp_json_path properties, and timestamp saving now reuses the shared timestamp path accessor. --- dlclivegui/services/recording_manager.py | 72 ++++++++++++++++++++++++ dlclivegui/services/video_recorder.py | 12 +++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/dlclivegui/services/recording_manager.py b/dlclivegui/services/recording_manager.py index 512f76ba1..9dfd68c1f 100644 --- a/dlclivegui/services/recording_manager.py +++ b/dlclivegui/services/recording_manager.py @@ -37,6 +37,9 @@ def __init__(self): self._dispatch_accepting: bool = False self._dispatch_sentinel_enqueued: bool = False + # Utility for operation on latest recording file context (e.g., for processor hooks) + self._last_recording_file_context: dict | None = None + @property def is_active(self) -> bool: with self._lock: @@ -313,6 +316,9 @@ def start_all( self._run_dir = None return None + with self._lock: + self._last_recording_file_context = self._build_recording_file_context_unlocked() + return run_dir def stop_all(self) -> bool: @@ -322,6 +328,11 @@ def stop_all(self) -> bool: with self._lock: recorders = list(self._recorders.items()) + run_dir = self._run_dir + session_dir = self._session_dir + self._last_recording_file_context = self._build_recording_file_context_unlocked( + recorders=recorders, run_dir=run_dir, session_dir=session_dir + ) self._recorders.clear() for cam_id, rec in recorders: @@ -484,3 +495,64 @@ def get_stats_summary(self) -> str: f"backlog {totals['backlog']} | " f"dropped {totals['dropped']}" ) + + def _build_recording_file_context_unlocked( + self, + recorders: list[tuple[str, VideoRecorder]] | None = None, + run_dir: Path | None = None, + session_dir: Path | None = None, + ) -> dict: + """Build a file context for active or recently stopped recorders. + + Must be called with self._lock held if using internal state. + """ + if recorders is None: + recorders = list(self._recorders.items()) + + if run_dir is None: + run_dir = self._run_dir + + if session_dir is None: + session_dir = self._session_dir + + video_files: dict[str, Path] = {} + timestamp_json_files: dict[str, Path] = {} + + for cam_id, recorder in recorders: + video_path = getattr(recorder, "output_path", None) + timestamp_path = getattr(recorder, "timestamp_json_path", None) + + if video_path is not None: + video_files[str(cam_id)] = Path(video_path) + + if timestamp_path is not None: + timestamp_json_files[str(cam_id)] = Path(timestamp_path) + + return { + "run_dir": run_dir, + "session_dir": session_dir, + "video_files": video_files, + "timestamp_json_files": timestamp_json_files, + } + + def get_recording_file_context(self) -> dict: + """Return current or last recording file context. + + This is used by optional custom processors to save compatibility sidecars + next to the videos after RecordingManager.stop_all() has finalized them. + """ + with self._lock: + if self._recorders: + context = self._build_recording_file_context_unlocked() + self._last_recording_file_context = context + return dict(context) + + if self._last_recording_file_context is not None: + return dict(self._last_recording_file_context) + + return { + "run_dir": self._run_dir, + "session_dir": self._session_dir, + "video_files": {}, + "timestamp_json_files": {}, + } diff --git a/dlclivegui/services/video_recorder.py b/dlclivegui/services/video_recorder.py index 3a3c40b2a..8baa98c37 100644 --- a/dlclivegui/services/video_recorder.py +++ b/dlclivegui/services/video_recorder.py @@ -160,6 +160,16 @@ def __init__( def is_running(self) -> bool: return self._writer_thread is not None and self._writer_thread.is_alive() + @property + def output_path(self) -> Path: + """Video output path.""" + return self._output + + @property + def timestamp_json_path(self) -> Path: + """Timestamp JSON sidecar path written by _save_timestamps().""" + return self._output.with_suffix("").with_suffix(self._output.suffix + "_timestamps.json") + def start(self) -> None: if WriteGear is None: raise RuntimeError("vidgear is required for video recording. Install it with 'pip install vidgear'.") @@ -626,7 +636,7 @@ def _save_timestamps(self) -> None: logger.info("No timestamps to save") return - timestamp_file = self._output.with_suffix("").with_suffix(self._output.suffix + "_timestamps.json") + timestamp_file = self.timestamp_json_path try: with self._stats_lock: From e6de71114436b1fc05e58c883b285e96835e787e Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 10 Jul 2026 11:55:06 +0200 Subject: [PATCH 05/17] Add recording lifecycle hooks for processors Wire the main window to notify custom DLC processors when recording starts/stops, with a shared recording context (run dir, filename stem, and file metadata). Refactor processor lookup into a helper and only fall back to generic save() if no stop hook handles persistence. Extend BaseProcessorSocket with recording context/save-path state, start/stop hook methods, and a stop(save=...) option. Update save() to use an explicit path or configured default path, create parent directories, and improve logging for skipped/failed saves. --- dlclivegui/gui/main_window.py | 98 +++++++++++++++++-- dlclivegui/processors/dlc_processor_socket.py | 78 +++++++++++++-- 2 files changed, 162 insertions(+), 14 deletions(-) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index 49c9d38cb..c0710273d 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -1863,6 +1863,7 @@ def _start_multi_camera_recording(self) -> None: if run_dir is None: self._show_error("Failed to start recording.") return + self._notify_processor_recording_started(run_dir) self.multi_camera_controller.set_recording_sink(self._rec_manager.write_frame) self.multi_camera_controller.set_recording_frame_is_enabled(True) @@ -1914,6 +1915,20 @@ def worker(): daemon=True, ).start() + def _get_dlc_processor_instance(self): + """Return the active custom DLC processor instance, if available.""" + processor = getattr(self._dlc, "_processor", None) + + if processor is not None: + return processor + + # Fallback: if DLCLive owns it internally. + dlc_obj = getattr(self._dlc, "_dlc", None) + if dlc_obj is not None: + return getattr(dlc_obj, "processor", None) + + return None + def _save_processor_data_if_available(self) -> None: """Best-effort generic processor save. @@ -1925,12 +1940,7 @@ def _save_processor_data_if_available(self) -> None: Return values are only logged; failure should not crash the GUI. """ - processor = getattr(self._dlc, "_processor", None) - - # Fallback in case DLCLive owns the processor but _processor was not updated. - if processor is None: - dlc_obj = getattr(self._dlc, "_dlc", None) - processor = getattr(dlc_obj, "processor", None) if dlc_obj is not None else None + processor = self._get_dlc_processor_instance() if processor is None: logger.debug("Processor save skipped: no processor instance available.") @@ -1947,8 +1957,82 @@ def _save_processor_data_if_available(self) -> None: except Exception: logger.exception("Processor save() failed.") + def _notify_processor_recording_started(self, run_dir) -> None: + processor = self._get_dlc_processor_instance() + if processor is None: + return + + hook = getattr(processor, "on_recording_started", None) + if not callable(hook): + return + + try: + context = self._build_processor_recording_context(run_dir) + hook(context) + logger.info("Notified processor recording started: %s", context) + except Exception: + logger.exception("Processor on_recording_started hook failed") + + from pathlib import Path + + def _build_processor_recording_context(self, run_dir) -> dict: + run_dir = Path(run_dir) if run_dir is not None else None + + file_context = {} + try: + file_context = self._rec_manager.get_recording_file_context() + except Exception: + logger.exception("Failed to get recording file context from RecordingManager") + file_context = {} + + if run_dir is None: + run_dir = file_context.get("run_dir", None) + if run_dir is not None: + run_dir = Path(run_dir) + + session_name = "" + if hasattr(self, "session_name_edit"): + session_name = self.session_name_edit.text().strip() + + filename = "" + if hasattr(self, "filename_edit"): + filename = self.filename_edit.text().strip() + + filename_stem = Path(filename or session_name or "recording").stem + + ctx = { + "run_dir": run_dir, + "session_name": session_name, + "filename": filename, + "filename_stem": filename_stem, + "processor_base_path": run_dir / filename_stem if run_dir is not None else None, + } + ctx.update(file_context) + return ctx + + def _notify_processor_recording_stopped(self) -> None: + processor = self._get_dlc_processor_instance() + if processor is None: + return False + + hook = getattr(processor, "on_recording_stopped", None) + if not callable(hook): + return False + + try: + run_dir = getattr(self._rec_manager, "run_dir", None) + context = self._build_processor_recording_context(run_dir) + hook(context) + logger.info("Notified processor recording stopped") + return True + except Exception: + logger.exception("Processor on_recording_stopped hook failed") + return False + def _on_recording_stopped_async(self) -> None: - self._save_processor_data_if_available() + handled_by_stop_hook = self._notify_processor_recording_stopped() + if not handled_by_stop_hook: + self._save_processor_data_if_available() self._recording_stopping = False self.start_record_button.setEnabled(True) diff --git a/dlclivegui/processors/dlc_processor_socket.py b/dlclivegui/processors/dlc_processor_socket.py index 1ab244f5b..5c28bc887 100644 --- a/dlclivegui/processors/dlc_processor_socket.py +++ b/dlclivegui/processors/dlc_processor_socket.py @@ -83,6 +83,8 @@ def __init__( self._vid_recording = Event() self.curr_step = 0 self.save_original = save_original + self.recording_context = {} + self.save_path = None # Networking (optional) self.address = bind @@ -262,8 +264,14 @@ def stop_recording(self): # STOP / SHUTDOWN # -------------------------------------------------------------------------------------- - def stop(self): + def stop(self, save: bool = False, file=None): """Gracefully stop listener and clients.""" + if save: + try: + self.save(file) + except Exception: + logger.exception("Failed to save processor data on stop") + if self._stop.is_set(): return @@ -367,23 +375,79 @@ def _clear_data_queues(self): self.original_pose.clear() def save(self, file=None): - if not file: + target = file + + if target is None: + target = getattr(self, "save_path", None) + + if target is None: + logger.warning("Processor save skipped: no file or save_path provided.") return 0 + try: save_dict = self.get_data() - path2save = Path(__file__).parent.parent.parent / "data" / file - path2save.parent.mkdir(parents=True, exist_ok=True) + save_path = Path(target) + save_path.parent.mkdir(parents=True, exist_ok=True) + if self.save_original: original_pose = save_dict.pop("original_pose") - self.save_original_pose(original_pose, save_dict["frame_time"], save_dict["time_stamp"], path2save) - with open(path2save, "wb") as f: + self.save_original_pose( + original_pose, + save_dict["frame_time"], + save_dict["time_stamp"], + save_path, + ) + + with open(save_path, "wb") as f: pickle.dump(save_dict, f) - logger.info(f"Saved data to {path2save}") + + logger.info(f"Saved processor data to {save_path}") return 1 + except Exception as e: logger.error(f"Save failed: {e}") return -1 + def set_recording_context(self, context: dict | None) -> None: + """Set GUI-provided recording context. + + This is intentionally generic. Custom processors may use it to derive + processor-specific output paths. + + Expected keys may include: + run_dir + session_name + filename + filename_stem + processor_base_path + video_files + timestamp_files + """ + self.recording_context = dict(context or {}) + + base_path = self.recording_context.get("processor_base_path") + if base_path is not None: + self.save_path = Path(base_path) + + def set_save_path(self, path) -> None: + """Set default save path used by save() when no file is provided.""" + self.save_path = Path(path) if path is not None else None + + def get_save_path(self): + """Return default save path, if any.""" + return getattr(self, "save_path", None) + + def on_recording_started(self, context: dict) -> None: + """Optional hook called by GUI when recording starts.""" + self.set_recording_context(context) + + def on_recording_stopped(self, context: dict) -> None: + """Optional hook called by GUI when recording stops. + + Base implementation only updates context. Custom processors can override. + """ + self.set_recording_context(context) + def save_original_pose( self, original_pose: np.ndarray, From d5624d5c9efa2c81308930f95f6d66bd761fe8a2 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 10 Jul 2026 11:56:03 +0200 Subject: [PATCH 06/17] Add processor recording-context tests Expand custom processor test coverage around recording context and save-path behavior. This adds a new test module for BaseProcessorSocket and DLCLiveMainWindow recording hook interactions, including optional hook handling and processor lookup paths. The existing base processor tests were also cleaned up to use pytest `tmp_path` for file outputs instead of writing into module data directories, remove manual cleanup blocks, and tighten assertions/formatting for save and recording flows. --- .../custom_processors/test_base_processor.py | 191 +++++------- .../test_processor_rec_context.py | 292 ++++++++++++++++++ 2 files changed, 363 insertions(+), 120 deletions(-) create mode 100644 tests/custom_processors/test_processor_rec_context.py diff --git a/tests/custom_processors/test_base_processor.py b/tests/custom_processors/test_base_processor.py index 8711eec11..cb8d2e7f8 100644 --- a/tests/custom_processors/test_base_processor.py +++ b/tests/custom_processors/test_base_processor.py @@ -1,9 +1,11 @@ -# tests/processors/test_dlc_processor_socket.py +# tests/custom_processors/test_base_processor.py from __future__ import annotations import importlib import pickle from pathlib import Path +import sys +import types import numpy as np import pandas as pd @@ -26,11 +28,6 @@ def example_processor_mod(): return importlib.import_module("dlclivegui.processors.examples") -def _module_data_dir(socket_mod) -> Path: - """Compute the data/ directory where save() writes artifacts.""" - return Path(socket_mod.__file__).parent.parent.parent / "data" - - def _mk_bodyparts(n: int) -> list[str]: return [f"bp{i}" for i in range(n)] @@ -41,7 +38,6 @@ def _mk_pose(n_keypoints: int = 5) -> np.ndarray: Base class does not interpret pose content—only broadcasts/logs it. """ pose = np.zeros((n_keypoints, 3), dtype=float) - # Fill with simple coordinates & confidence for i in range(n_keypoints): pose[i, :] = [10.0 + i, 20.0 + i, 0.9] return pose @@ -53,25 +49,25 @@ def test_base_init_and_stop(socket_mod): and ensure stop() is idempotent. """ BaseProcessorSocket = socket_mod.BaseProcessorSocket - proc = BaseProcessorSocket(bind=("127.0.0.1", 0), use_perf_counter=True, save_original=False) + proc = BaseProcessorSocket( + bind=("127.0.0.1", 0), + use_perf_counter=True, + save_original=False, + ) try: - # Core attributes exist assert hasattr(proc, "listener") assert callable(proc.timing_func) - # perf_counter chosen + import time as _t assert proc.timing_func is _t.perf_counter - # Initial flags & counters assert proc.recording is False assert proc.video_recording is False assert proc.curr_step == 0 assert isinstance(proc.conns, set) finally: - # stop must be safe and idempotent proc.stop() - proc.stop() # second call should be a no-op def test_base_recording_flags_and_session_name(socket_mod): @@ -81,23 +77,19 @@ def test_base_recording_flags_and_session_name(socket_mod): BaseProcessorSocket = socket_mod.BaseProcessorSocket proc = BaseProcessorSocket(bind=("127.0.0.1", 0)) try: - # Start recording proc._handle_client_message({"cmd": "start_recording"}) assert proc.recording is True assert proc.video_recording is True - assert proc.curr_step == 0 # reset + assert proc.curr_step == 0 - # Set a session name proc._handle_client_message({"cmd": "set_session_name", "session_name": "unit_test"}) assert proc.session_name == "unit_test" assert proc.filename == "unit_test_dlc_processor_data.pkl" - # Stop recording proc._handle_client_message({"cmd": "stop_recording"}) assert proc.recording is False assert proc.video_recording is False - # Unknown / invalid messages must not crash proc._handle_client_message(None) proc._handle_client_message({"cmd": "does_not_exist"}) finally: @@ -109,27 +101,27 @@ def test_base_process_without_and_with_recording(socket_mod): BaseProcessorSocket.process() should: - increment curr_step always, - when recording, append time/step/frame_time/pose_time, - - when save_original=True, store copies of pose arrays only while recording. + - when save_original=True, store copies of pose arrays only while recording. """ BaseProcessorSocket = socket_mod.BaseProcessorSocket proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=True) + try: pose = _mk_pose() - # Not recording yet: curr_step increments, no logs appended before_step = proc.curr_step ret = proc.process(pose, frame_time=0.012, pose_time=0.013) + assert ret is pose assert proc.curr_step == before_step + 1 assert len(proc.time_stamp) == 0 assert len(proc.step) == 0 assert len(proc.frame_time) == 0 assert len(proc.pose_time) == 0 - # Raw poses must stay aligned with recorded metadata. + assert proc.original_pose is not None assert len(proc.original_pose) == 0 - # Start recording and push two frames proc._handle_client_message({"cmd": "start_recording"}) for _ in range(2): proc.process(pose, frame_time=0.01, pose_time=0.011) @@ -139,25 +131,23 @@ def test_base_process_without_and_with_recording(socket_mod): assert len(proc.frame_time) == 2 assert len(proc.pose_time) == 2 assert len(proc.original_pose) == 2 + np.testing.assert_allclose(proc.original_pose[0], pose) np.testing.assert_allclose(proc.original_pose[1], pose) - # Data snapshot integrity data = proc.get_data() assert "start_time" in data assert isinstance(data["time_stamp"], np.ndarray) assert isinstance(data["step"], np.ndarray) assert isinstance(data["frame_time"], np.ndarray) - # pose_time can be None if never provided; here it is provided. assert isinstance(data["pose_time"], np.ndarray) - # original_pose is included when save_original=True assert isinstance(data["original_pose"], np.ndarray) finally: proc.stop() -def test_save_ignores_pre_recording_original_pose_frames(socket_mod): +def test_save_ignores_pre_recording_original_pose_frames(socket_mod, tmp_path): """ save_original data must stay aligned with recorded metadata even if process() is called before recording starts. @@ -183,18 +173,16 @@ def test_save_ignores_pre_recording_original_pose_frames(socket_mod): proc.process(pose, frame_time=0.01, pose_time=0.02) proc._handle_client_message({"cmd": "stop_recording"}) - filename = "unit_test_pre_recording_frames.pkl" - ret = proc.save(filename) - assert ret == 1 + pkl_path = tmp_path / "unit_test_pre_recording_frames.pkl" + h5_path = tmp_path / "unit_test_pre_recording_frames_DLC.hdf5" - data_dir = _module_data_dir(socket_mod) - pkl_path = data_dir / filename - h5_path = data_dir / (Path(filename).stem + "_DLC.hdf5") + ret = proc.save(pkl_path) + assert ret == 1 assert pkl_path.exists() assert h5_path.exists() - with open(pkl_path, "rb") as f: + with pkl_path.open("rb") as f: payload = pickle.load(f) assert len(payload["frame_time"]) == 2 @@ -208,11 +196,6 @@ def test_save_ignores_pre_recording_original_pose_frames(socket_mod): finally: proc.stop() - try: - pkl_path.unlink(missing_ok=True) - h5_path.unlink(missing_ok=True) - except Exception: - pass @pytest.mark.parametrize( @@ -223,7 +206,11 @@ def test_save_ignores_pre_recording_original_pose_frames(socket_mod): ], ) def test_subclass_save_ignores_pre_recording_original_pose_frames( - socket_mod, example_processor_mod, class_name, n_keypoints + socket_mod, + example_processor_mod, + class_name, + n_keypoints, + tmp_path, ): """ Concrete processors must keep original_pose aligned with recorded metadata @@ -249,18 +236,16 @@ def test_subclass_save_ignores_pre_recording_original_pose_frames( proc.process(pose, frame_time=0.01, pose_time=0.02) proc._handle_client_message({"cmd": "stop_recording"}) - filename = f"unit_test_{class_name}.pkl" - ret = proc.save(filename) - assert ret == 1 + pkl_path = tmp_path / f"unit_test_{class_name}.pkl" + h5_path = tmp_path / f"unit_test_{class_name}_DLC.hdf5" - data_dir = _module_data_dir(socket_mod) - pkl_path = data_dir / filename - h5_path = data_dir / (Path(filename).stem + "_DLC.hdf5") + ret = proc.save(pkl_path) + assert ret == 1 assert pkl_path.exists() assert h5_path.exists() - with open(pkl_path, "rb") as f: + with pkl_path.open("rb") as f: payload = pickle.load(f) assert len(payload["frame_time"]) == 3 @@ -274,11 +259,6 @@ def test_subclass_save_ignores_pre_recording_original_pose_frames( finally: proc.stop() - try: - pkl_path.unlink(missing_ok=True) - h5_path.unlink(missing_ok=True) - except Exception: - pass def test_base_broadcast_handles_bad_connections(socket_mod): @@ -289,7 +269,6 @@ def test_base_broadcast_handles_bad_connections(socket_mod): class BadConn: def __init__(self): - # Minimal attributes to satisfy _close_conn class Sock: def shutdown(self, *_args, **_kwargs): raise RuntimeError("shutdown fail") @@ -303,7 +282,6 @@ def close(self): raise RuntimeError("close fail") def __hash__(self): - # allow put in a set return id(self) def __eq__(self, other): @@ -311,18 +289,20 @@ def __eq__(self, other): BaseProcessorSocket = socket_mod.BaseProcessorSocket proc = BaseProcessorSocket(bind=("127.0.0.1", 0)) + try: bad = BadConn() proc.conns.add(bad) - # Should not raise + proc.broadcast(["ts", "payload"]) - # bad conn should be discarded + assert bad not in proc.conns + finally: proc.stop() -def test_save_writes_pkl_and_hdf5_with_labels(socket_mod, caplog): +def test_save_writes_pkl_and_hdf5_with_labels(socket_mod, tmp_path, caplog): """ End-to-end save() with save_original=True and a matching dlc_cfg bodypart list. Verifies: @@ -339,56 +319,47 @@ def test_save_writes_pkl_and_hdf5_with_labels(socket_mod, caplog): dlc_cfg = {"metadata": {"bodyparts": bodyparts}} proc.set_dlc_cfg(dlc_cfg) - # create 3 frames pose = _mk_pose(n_keypoints=n_keypoints) + proc._handle_client_message({"cmd": "start_recording"}) for _ in range(3): proc.process(pose, frame_time=0.01, pose_time=0.011) proc._handle_client_message({"cmd": "stop_recording"}) - # deterministic relative filename - filename = "unit_test_session.pkl" - ret = proc.save(filename) - assert ret == 1 + pkl_path = tmp_path / "unit_test_session.pkl" + h5_path = tmp_path / "unit_test_session_DLC.hdf5" - data_dir = _module_data_dir(socket_mod) - pkl_path = data_dir / filename - h5_path = data_dir / (Path(filename).stem + "_DLC.hdf5") + ret = proc.save(pkl_path) + assert ret == 1 assert pkl_path.exists(), f"Missing {pkl_path}" assert h5_path.exists(), f"Missing {h5_path}" - # verify pkl payload - with open(pkl_path, "rb") as f: + with pkl_path.open("rb") as f: payload = pickle.load(f) - assert "original_pose" not in payload # popped out before pickling + assert "original_pose" not in payload assert "dlc_cfg" in payload assert payload["dlc_cfg"] == dlc_cfg - # verify HDF5 contents (skip if tables is not installed) pytest.importorskip("tables") df = pd.read_hdf(h5_path, key="df_with_missing") - # Expect rows == frames + assert df.shape[0] == 3 - # Confirm the labeled columns exist for all bodyparts x (x, y, likelihood) expected_cols = pd.MultiIndex.from_product( [bodyparts, ["x", "y", "likelihood"]], names=["bodyparts", "coords"], ) - # Some pandas versions will allow mixing multiindex + string cols; - # so just check presence of expected label tuples: + for col in expected_cols: assert col in df.columns - # frame_time & pose_time columns are present assert "frame_time" in df.columns assert "pose_time" in df.columns assert list(df["frame_time"]) == [0.01, 0.01, 0.01] assert list(df["pose_time"]) == list(payload["time_stamp"]) - # sanity check values for first row for i, bp in enumerate(bodyparts): assert np.isclose(df[(bp, "x")].iloc[0], 10.0 + i) assert np.isclose(df[(bp, "y")].iloc[0], 20.0 + i) @@ -396,61 +367,45 @@ def test_save_writes_pkl_and_hdf5_with_labels(socket_mod, caplog): finally: proc.stop() - # cleanup - try: - pkl_path.unlink(missing_ok=True) - h5_path.unlink(missing_ok=True) - except Exception: - pass -def test_save_without_dlc_cfg_unlabeled_columns(socket_mod, caplog): +def test_save_without_dlc_cfg_unlabeled_columns(socket_mod, tmp_path, caplog): """ Ensure that without dlc_cfg, save() still writes HDF5 with unlabeled columns - and logs a warning (no crash). + and logs a warning or at least succeeds without crashing. """ BaseProcessorSocket = socket_mod.BaseProcessorSocket proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=True) try: pose = _mk_pose(3) + proc._handle_client_message({"cmd": "start_recording"}) proc.process(pose, frame_time=0.01, pose_time=0.02) proc._handle_client_message({"cmd": "stop_recording"}) - filename = "unit_test_no_dlc_cfg.pkl" - ret = proc.save(filename) - assert ret == 1 + pkl_path = tmp_path / "unit_test_no_dlc_cfg.pkl" + h5_path = tmp_path / "unit_test_no_dlc_cfg_DLC.hdf5" - data_dir = _module_data_dir(socket_mod) - pkl_path = data_dir / filename - h5_path = data_dir / (Path(filename).stem + "_DLC.hdf5") + ret = proc.save(pkl_path) + assert ret == 1 assert pkl_path.exists() assert h5_path.exists() - # Check warning logged - # (Depending on logger config in tests, you may need to set level to capture warnings) + # Depending on logger config in tests, caplog may or may not catch this. [rec for rec in caplog.records if "saving without column labels" in rec.message] - # It's okay if caplog didn't catch it due to logger level; we mainly ensure no crash and files exist. - # Verify HDF5 loads (skip if tables not installed) pytest.importorskip("tables") df = pd.read_hdf(h5_path, key="df_with_missing") - assert df.shape[0] == 1 # 1 frame saved - # Expect unlabeled numeric columns for pose plus "frame_time" and "pose_time" - # We can't rely on a MultiIndex here; just ensure numeric columns exist + + assert df.shape[0] == 1 + numeric_cols = [c for c in df.columns if c not in ("frame_time", "pose_time")] - assert len(numeric_cols) == 3 * 3 # 3 keypoints * 3 coords + assert len(numeric_cols) == 3 * 3 finally: proc.stop() - # cleanup - try: - pkl_path.unlink(missing_ok=True) - h5_path.unlink(missing_ok=True) - except Exception: - pass def test_get_data_includes_dlc_cfg(socket_mod): @@ -459,38 +414,34 @@ def test_get_data_includes_dlc_cfg(socket_mod): """ BaseProcessorSocket = socket_mod.BaseProcessorSocket proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=False) + try: dlc_cfg = {"metadata": {"bodyparts": ["a", "b"]}} proc.set_dlc_cfg(dlc_cfg) + data = proc.get_data() + assert "dlc_cfg" in data assert data["dlc_cfg"] == dlc_cfg + finally: proc.stop() -def test_save_handles_empty_original_pose(socket_mod): +def test_save_handles_empty_original_pose(socket_mod, tmp_path): """ - With save_original=True but no process() calls, save() should not crash. - Depending on pandas behavior, HDF5 should exist with 0 rows or be created successfully. + With save_original=True but no process() calls, save() should not raise. + The exact return value is implementation-dependent for empty data. """ BaseProcessorSocket = socket_mod.BaseProcessorSocket proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=True) + try: - filename = "unit_test_empty_original.pkl" - ret = proc.save(filename) - # If nothing to save, your implementation returns 1 (saved) or could be 0; current code returns 1 - assert ret in (1, 0, -1) # accept current behavior; adjust if you standardize - data_dir = _module_data_dir(socket_mod) - pkl_path = data_dir / filename - h5_path = data_dir / (Path(filename).stem + "_DLC.hdf5") - # pkl exists if ret == 1; hdf5 may or may not depending on your final logic - # Leave assertions lenient; the main check is that no exception bubbles up. + pkl_path = tmp_path / "unit_test_empty_original.pkl" + + ret = proc.save(pkl_path) + + assert ret in (1, 0, -1) + finally: proc.stop() - # cleanup - try: - pkl_path.unlink(missing_ok=True) - h5_path.unlink(missing_ok=True) - except Exception: - pass diff --git a/tests/custom_processors/test_processor_rec_context.py b/tests/custom_processors/test_processor_rec_context.py new file mode 100644 index 000000000..16a1461d9 --- /dev/null +++ b/tests/custom_processors/test_processor_rec_context.py @@ -0,0 +1,292 @@ +# tests/processors/test_processor_recording_context.py +from __future__ import annotations + +import importlib +import pickle +import sys +import types +from types import SimpleNamespace + +import pytest + +# ----------------------------------------------------------------------------- +# Shared fixtures +# ----------------------------------------------------------------------------- + + +def _mock_dlclive(monkeypatch): + """Install a tiny dlclive.processor.Processor mock before importing processors.""" + + class Processor: + def __init__(self, *args, **kwargs): + pass + + def process(self, pose, **kwargs): + return pose + + dlclive_mod = types.ModuleType("dlclive") + processor_mod = types.ModuleType("dlclive.processor") + + dlclive_mod.Processor = Processor + processor_mod.Processor = Processor + + monkeypatch.setitem(sys.modules, "dlclive", dlclive_mod) + monkeypatch.setitem(sys.modules, "dlclive.processor", processor_mod) + + +@pytest.fixture +def socket_mod(monkeypatch): + """Import dlclivegui.processors.dlc_processor_socket with dlclive mocked.""" + _mock_dlclive(monkeypatch) + mod_name = "dlclivegui.processors.dlc_processor_socket" + sys.modules.pop(mod_name, None) + return importlib.import_module(mod_name) + + +class DummyLineEdit: + def __init__(self, value: str): + self._value = value + + def text(self) -> str: + return self._value + + +class HookProcessor: + def __init__(self): + self.started_contexts = [] + self.stopped_contexts = [] + self.save_calls = 0 + + def on_recording_started(self, context): + self.started_contexts.append(context) + + def on_recording_stopped(self, context): + self.stopped_contexts.append(context) + + def save(self): + self.save_calls += 1 + return 1 + + +class SaveOnlyProcessor: + def __init__(self): + self.save_calls = 0 + + def save(self): + self.save_calls += 1 + return 1 + + +# ----------------------------------------------------------------------------- +# BaseProcessorSocket generic recording-context API +# ----------------------------------------------------------------------------- + + +def test_base_processor_recording_context_sets_save_path(socket_mod, tmp_path): + BaseProcessorSocket = socket_mod.BaseProcessorSocket + proc = BaseProcessorSocket(bind=("127.0.0.1", 0)) + + try: + base_path = tmp_path / "MouseA_2026-07-10_1" + context = { + "run_dir": tmp_path, + "session_name": "MouseA", + "filename": "MouseA_2026-07-10_1.avi", + "filename_stem": "MouseA_2026-07-10_1", + "processor_base_path": base_path, + } + + proc.set_recording_context(context) + + assert proc.recording_context == context + assert proc.get_save_path() == base_path + assert proc.save_path == base_path + + finally: + proc.stop() + + +def test_base_processor_recording_hooks_update_context(socket_mod, tmp_path): + BaseProcessorSocket = socket_mod.BaseProcessorSocket + proc = BaseProcessorSocket(bind=("127.0.0.1", 0)) + + try: + started_context = {"processor_base_path": tmp_path / "started"} + stopped_context = {"processor_base_path": tmp_path / "stopped"} + + proc.on_recording_started(started_context) + assert proc.recording_context == started_context + assert proc.get_save_path() == tmp_path / "started" + + proc.on_recording_stopped(stopped_context) + assert proc.recording_context == stopped_context + assert proc.get_save_path() == tmp_path / "stopped" + + finally: + proc.stop() + + +def test_base_processor_save_uses_save_path_when_file_is_none(socket_mod, tmp_path): + BaseProcessorSocket = socket_mod.BaseProcessorSocket + proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=False) + + try: + save_path = tmp_path / "legacy" / "MouseA_2026-07-10_1_PROC" + proc.set_save_path(save_path) + + proc.start_recording() + proc.process([[1.0, 2.0, 0.9]], frame_time=12.34, pose_time=12.35) + proc.stop_recording() + + ret = proc.save() + assert ret == 1 + assert save_path.exists() + + with save_path.open("rb") as f: + payload = pickle.load(f) + + assert "time_stamp" in payload + assert "step" in payload + assert "frame_time" in payload + assert len(payload["frame_time"]) == 1 + + finally: + proc.stop() + + +def test_base_processor_save_writes_to_explicit_absolute_file(socket_mod, tmp_path): + BaseProcessorSocket = socket_mod.BaseProcessorSocket + proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=False) + + try: + explicit_path = tmp_path / "explicit" / "processor_data.pkl" + ret = proc.save(explicit_path) + + assert ret == 1 + assert explicit_path.exists() + + with explicit_path.open("rb") as f: + payload = pickle.load(f) + + assert "start_time" in payload + + finally: + proc.stop() + + +def test_base_processor_save_without_file_or_save_path_returns_zero(socket_mod): + BaseProcessorSocket = socket_mod.BaseProcessorSocket + proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=False) + + try: + proc.save_path = None + assert proc.save() == 0 + finally: + proc.stop() + + +def test_base_processor_stop_save_true_uses_save_path(socket_mod, tmp_path): + BaseProcessorSocket = socket_mod.BaseProcessorSocket + proc = BaseProcessorSocket(bind=("127.0.0.1", 0), save_original=False) + + save_path = tmp_path / "processor_on_stop.pkl" + proc.set_save_path(save_path) + + # stop(save=True) should save, close listener, and be idempotent. + proc.stop(save=True) + assert save_path.exists() + assert proc.listener is None + + proc.stop(save=True) # no crash + + +# ----------------------------------------------------------------------------- +# DLCLiveMainWindow recording-context helpers +# ----------------------------------------------------------------------------- + + +@pytest.fixture +def main_window_cls(): + pytest.importorskip("PySide6") + mod = importlib.import_module("dlclivegui.gui.main_window") + return mod.DLCLiveMainWindow + + +def make_window_shell(main_window_cls, processor=None, run_dir=None): + """Create a DLCLiveMainWindow shell without running QMainWindow.__init__.""" + win = main_window_cls.__new__(main_window_cls) + win._dlc = SimpleNamespace(_processor=processor, _dlc=None) + win._rec_manager = SimpleNamespace(run_dir=run_dir) + win.session_name_edit = DummyLineEdit("MouseA") + win.filename_edit = DummyLineEdit("MouseA_2026-07-10_1.avi") + return win + + +def test_main_window_build_processor_recording_context(main_window_cls, tmp_path): + win = make_window_shell(main_window_cls, run_dir=tmp_path) + + context = win._build_processor_recording_context(tmp_path) + + assert context["run_dir"] == tmp_path + assert context["session_name"] == "MouseA" + assert context["filename"] == "MouseA_2026-07-10_1.avi" + assert context["filename_stem"] == "MouseA_2026-07-10_1" + assert context["processor_base_path"] == tmp_path / "MouseA_2026-07-10_1" + + +def test_main_window_get_processor_instance_prefers_direct_processor(main_window_cls): + processor = HookProcessor() + win = make_window_shell(main_window_cls, processor=processor) + + assert win._get_dlc_processor_instance() is processor + + +def test_main_window_get_processor_instance_falls_back_to_dlclive_processor(main_window_cls): + processor = HookProcessor() + win = main_window_cls.__new__(main_window_cls) + win._dlc = SimpleNamespace(_processor=None, _dlc=SimpleNamespace(processor=processor)) + + assert win._get_dlc_processor_instance() is processor + + +def test_main_window_notify_processor_recording_started_calls_hook(main_window_cls, tmp_path): + processor = HookProcessor() + win = make_window_shell(main_window_cls, processor=processor, run_dir=tmp_path) + + win._notify_processor_recording_started(tmp_path) + + assert len(processor.started_contexts) == 1 + context = processor.started_contexts[0] + assert context["processor_base_path"] == tmp_path / "MouseA_2026-07-10_1" + + +def test_main_window_notify_processor_recording_stopped_calls_hook(main_window_cls, tmp_path): + processor = HookProcessor() + win = make_window_shell(main_window_cls, processor=processor, run_dir=tmp_path) + + win._notify_processor_recording_stopped() + + assert len(processor.stopped_contexts) == 1 + context = processor.stopped_contexts[0] + assert context["processor_base_path"] == tmp_path / "MouseA_2026-07-10_1" + + +def test_main_window_save_processor_data_calls_save(main_window_cls, tmp_path): + processor = SaveOnlyProcessor() + win = make_window_shell(main_window_cls, processor=processor, run_dir=tmp_path) + + win._save_processor_data_if_available() + + assert processor.save_calls == 1 + + +def test_main_window_processor_hooks_are_optional(main_window_cls, tmp_path): + class NoHooks: + pass + + win = make_window_shell(main_window_cls, processor=NoHooks(), run_dir=tmp_path) + + # Optional hooks/save absence should not crash. + win._notify_processor_recording_started(tmp_path) + win._notify_processor_recording_stopped() + win._save_processor_data_if_available() From a478e3af5253ceeb77f88be17a0526c7ac5c3764 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Fri, 10 Jul 2026 16:09:27 +0200 Subject: [PATCH 07/17] Remove custom proc testing folder --- .../custom/mock_socket_processor.py | 532 -- .../custom/mock_unity_socket_client.ipynb | 4609 ----------------- 2 files changed, 5141 deletions(-) delete mode 100644 dlclivegui/processors/custom/mock_socket_processor.py delete mode 100644 dlclivegui/processors/custom/mock_unity_socket_client.ipynb diff --git a/dlclivegui/processors/custom/mock_socket_processor.py b/dlclivegui/processors/custom/mock_socket_processor.py deleted file mode 100644 index 788bde3df..000000000 --- a/dlclivegui/processors/custom/mock_socket_processor.py +++ /dev/null @@ -1,532 +0,0 @@ -"""Standalone mock DLC processor plugin with socket listener support. - -This fixture intentionally avoids importing dlclive, dlclivegui, Teensy, serial, -NumPy, or project-specific processor base classes. - -It is designed to test two separate concerns without mixing them: - -1. GUI processor plugin discovery/configuration - - Exposes PROCESSOR_* metadata. - - Exposes PROCESSOR_BUILD_IN_WORKER = True. - - Exposes get_available_processors(), which your loader can consume without - requiring this class to inherit from dlclive.processor.Processor. - -2. Runtime socket listener behavior - - Starts a multiprocessing.connection.Listener. - - Accepts one or more clients on a background thread. - - Receives simple command dictionaries from clients. - - Broadcasts mock pose payloads to connected clients from process(). - -It does NOT mock Teensy serial acquisition. For the listener send/receive tests, -Teensy is not required: the Teensy path is a separate serial-reader concern. -""" - -from __future__ import annotations - -import logging -import pickle -import sys -import time -from collections import deque -from multiprocessing.connection import Client, Listener -from pathlib import Path -from threading import Event, Lock, Thread -from typing import Any - -logger = logging.getLogger(__name__) - -IP_ADDRESS = "127.0.0.1" -PORT = 6000 - - -class MockSocketProcessor: - """Standalone socket-based mock processor for tests. - - This intentionally reimplements the core listener/thread/control behavior - instead of inheriting from project classes. It is suitable for fixture usage - where external dependencies such as Teensy, serial, dlclive, or dlclivegui - should not be imported. - - Expected test usage: - - proc = MockSocketProcessor(bind=("127.0.0.1", free_port())) - conn = Client(proc.address, authkey=proc.authkey) - conn.send({"cmd": "ping"}) - assert conn.recv()["type"] == "pong" - proc.process([[1, 2, 0.9]]) - assert conn.recv()["type"] == "pose" - proc.stop() - - Notes: - - `process()` accepts any pose-like Python object. It does not validate - DLC shape because this mock is for socket lifecycle tests, not pose - validation tests. - - Payloads are sent through multiprocessing.connection, matching the - style used by the legacy socket processors. - """ - - PROCESSOR_NAME = "Mock Socket Processor" - PROCESSOR_DESCRIPTION = "Standalone mock socket processor without Teensy or DLCLive imports." - PROCESSOR_BUILD_IN_WORKER = False - PROCESSOR_PARAMS = { - "bind": { - "type": "tuple", - "default": (IP_ADDRESS, PORT), - "description": "Server bind address. Use port 0 to request an ephemeral port.", - }, - "authkey": { - "type": "bytes", - "default": b"secret password", - "description": "Authentication key for multiprocessing.connection clients.", - }, - "start_server": { - "type": "bool", - "default": True, - "description": "Whether to start the listener in __init__.", - }, - "socket_timeout": { - "type": "float", - "default": 0.05, - "description": "Accept-loop timeout in seconds.", - }, - "save_original": { - "type": "bool", - "default": False, - "description": "Whether to store raw pose payloads while recording.", - }, - } - - def __init__( - self, - bind: tuple[str, int] = (IP_ADDRESS, PORT), - authkey: bytes = b"secret password", - *, - start_server: bool = True, - socket_timeout: float = 0.05, - save_original: bool = False, - ) -> None: - self.address = bind - self.authkey = authkey - self._socket_timeout = float(socket_timeout) - self.save_original = bool(save_original) - - # Runtime listener/client state. - self.listener: Listener | None = None - self.conns: set[Any] = set() - self._conns_lock = Lock() - self._stop = Event() - self._accept_thread: Thread | None = None - self._rx_threads: set[Thread] = set() - - # Recording/control state compatible with socket-processor expectations. - self._recording = Event() - self._vid_recording = Event() - self._session_name = "test_session" - self.filename: str | None = None - - # Minimal data buffers for save/get_data tests. - self.start_time = time.time() - self.time_stamp = deque() - self.step = deque() - self.frame_time = deque() - self.pose_time = deque() - self.original_pose = deque() if self.save_original else None - self.received_commands = deque() - self.broadcast_count = 0 - self.curr_step = 0 - - if start_server: - self.start_server(bind, authkey=authkey, timeout=self._socket_timeout) - - # ------------------------------------------------------------------ - # Properties matching the real socket processors - # ------------------------------------------------------------------ - @property - def recording(self) -> bool: - return self._recording.is_set() - - @property - def video_recording(self) -> bool: - return self._vid_recording.is_set() - - @property - def session_name(self) -> str: - return self._session_name - - @session_name.setter - def session_name(self, name: str) -> None: - self._session_name = str(name) - self.filename = f"{self._session_name}_mock_processor_data.pkl" - - # ------------------------------------------------------------------ - # Listener lifecycle - # ------------------------------------------------------------------ - def start_server( - self, - bind: tuple[str, int] | None = None, - authkey: bytes | None = None, - *, - timeout: float | None = None, - ) -> None: - """Start the socket listener if it is not already running.""" - if self.listener is not None: - return - - if bind is not None: - self.address = bind - if authkey is not None: - self.authkey = authkey - if timeout is not None: - self._socket_timeout = float(timeout) - - self._stop.clear() - self.listener = Listener(self.address, authkey=self.authkey) - - # If bind used port 0, update address to the actual ephemeral port. - self.address = self._actual_listener_address(self.listener, fallback=self.address) - - self._set_listener_timeout(self.listener, self._socket_timeout) - - self._accept_thread = Thread(target=self._accept_loop, name="MockSocketProcessorAccept", daemon=True) - self._accept_thread.start() - logger.info("MockSocketProcessor listening on %s:%s", self.address[0], self.address[1]) - - @staticmethod - def _actual_listener_address(listener: Listener, fallback: tuple[str, int]) -> tuple[str, int]: - """Best-effort extraction of the actual listener address.""" - try: - raw = getattr(listener, "_listener", None) - sock = getattr(raw, "_socket", None) - if sock is not None: - addr = sock.getsockname() - return (str(addr[0]), int(addr[1])) - except Exception: - pass - try: - addr = listener.address - return (str(addr[0]), int(addr[1])) - except Exception: - return fallback - - @staticmethod - def _set_listener_timeout(listener: Listener, timeout: float) -> None: - """Set accept timeout on CPython listener internals, best effort.""" - raw = getattr(listener, "_listener", None) - for candidate in (raw, getattr(raw, "_socket", None)): - try: - if candidate is not None and hasattr(candidate, "settimeout"): - candidate.settimeout(timeout) - return - except Exception: - pass - - def _accept_loop(self) -> None: - while not self._stop.is_set(): - try: - if self.listener is None: - return - conn = self.listener.accept() - except TimeoutError: - continue - except (OSError, EOFError): - if self._stop.is_set(): - break - continue - except Exception: - if self._stop.is_set(): - break - logger.exception("Unexpected accept-loop error") - continue - - with self._conns_lock: - self.conns.add(conn) - - rx = Thread(target=self._rx_loop, args=(conn,), name="MockSocketProcessorRx", daemon=True) - self._rx_threads.add(rx) - rx.start() - logger.info("MockSocketProcessor client connected") - - def _rx_loop(self, conn: Any) -> None: - while not self._stop.is_set(): - try: - if conn.poll(0.05): - msg = conn.recv() - self._handle_client_message(msg, conn=conn) - continue - - if getattr(conn, "closed", False): - break - - except (EOFError, OSError, ConnectionError, BrokenPipeError): - break - except Exception: - logger.exception("Unexpected receive-loop error") - break - - self._close_conn(conn) - - def _close_conn(self, conn: Any) -> None: - try: - conn.close() - except Exception: - pass - with self._conns_lock: - self.conns.discard(conn) - - def stop(self) -> None: - """Stop listener, close clients, and join background threads best-effort.""" - if self._stop.is_set(): - return - - self._stop.set() - - # Wake accept() if needed. - try: - Client(self.address, authkey=self.authkey).close() - except Exception: - pass - - with self._conns_lock: - conns = list(self.conns) - for conn in conns: - self._close_conn(conn) - - try: - if self.listener is not None: - self.listener.close() - except Exception: - pass - self.listener = None - - if self._accept_thread is not None: - self._accept_thread.join(timeout=1.0) - self._accept_thread = None - - for thread in list(self._rx_threads): - try: - thread.join(timeout=0.5) - except Exception: - pass - self._rx_threads.clear() - - if sys.platform.startswith("win"): - time.sleep(0.05) - - close = stop - - def __del__(self) -> None: - try: - self.stop() - except Exception: - pass - - # ------------------------------------------------------------------ - # Client command handling - # ------------------------------------------------------------------ - def _handle_client_message(self, msg: Any, *, conn: Any | None = None) -> None: - self.received_commands.append(msg) - - if not isinstance(msg, dict): - self._send_to(conn, {"type": "error", "error": "message must be a dict"}) - return - - cmd = msg.get("cmd") - - if cmd == "ping": - self._send_to( - conn, - { - "type": "pong", - "timestamp": time.time(), - "session_name": self.session_name, - "recording": self.recording, - "video_recording": self.video_recording, - "clients": self.client_count(), - }, - ) - - elif cmd == "status": - self._send_to(conn, self.status_payload()) - - elif cmd == "set_session_name": - self.session_name = msg.get("session_name", "default_session") - self._send_to(conn, {"type": "ack", "cmd": cmd, "session_name": self.session_name}) - - elif cmd == "start_recording": - self.start_recording() - self._send_to(conn, {"type": "ack", "cmd": cmd, "recording": True}) - - elif cmd == "stop_recording": - self.stop_recording() - self._send_to(conn, {"type": "ack", "cmd": cmd, "recording": False}) - - elif cmd == "save": - file = msg.get("filename", self.filename) - result = self.save(file) - self._send_to(conn, {"type": "ack", "cmd": cmd, "result": result, "filename": file}) - - elif cmd == "close": - self._send_to(conn, {"type": "ack", "cmd": cmd}) - if conn is not None: - self._close_conn(conn) - - else: - self._send_to(conn, {"type": "error", "error": f"unknown cmd: {cmd!r}"}) - - @staticmethod - def _send_to(conn: Any | None, payload: Any) -> bool: - if conn is None: - return False - try: - conn.send(payload) - return True - except Exception: - return False - - def client_count(self) -> int: - with self._conns_lock: - return len(self.conns) - - def status_payload(self) -> dict[str, Any]: - return { - "type": "status", - "session_name": self.session_name, - "recording": self.recording, - "video_recording": self.video_recording, - "clients": self.client_count(), - "steps": self.curr_step, - "broadcast_count": self.broadcast_count, - "address": self.address, - } - - # ------------------------------------------------------------------ - # Recording helpers - # ------------------------------------------------------------------ - def start_recording(self) -> None: - self._recording.set() - self._vid_recording.set() - self._clear_data_queues() - self.curr_step = 0 - - def stop_recording(self) -> None: - self._recording.clear() - self._vid_recording.clear() - - def _clear_data_queues(self) -> None: - self.time_stamp.clear() - self.step.clear() - self.frame_time.clear() - self.pose_time.clear() - if self.original_pose is not None: - self.original_pose.clear() - - # ------------------------------------------------------------------ - # Process/broadcast path - # ------------------------------------------------------------------ - def process(self, pose: Any, **kwargs: Any) -> Any: - """Mock DLCLive processor callback. - - Records minimal metadata when recording is active and broadcasts a simple - pose payload to all connected clients. - """ - now = time.time() - self.curr_step += 1 - - if self.recording: - self.time_stamp.append(now) - self.step.append(self.curr_step) - self.frame_time.append(kwargs.get("frame_time", -1)) - if "pose_time" in kwargs: - self.pose_time.append(kwargs["pose_time"]) - if self.original_pose is not None: - self.original_pose.append(pose) - - payload = { - "type": "pose", - "timestamp": now, - "step": self.curr_step, - "pose": self._make_pickle_safe_pose(pose), - "frame_time": kwargs.get("frame_time", None), - "pose_time": kwargs.get("pose_time", None), - "recording": self.recording, - } - self.broadcast(payload) - return pose - - @staticmethod - def _make_pickle_safe_pose(pose: Any) -> Any: - """Convert common array-likes to socket-safe Python types.""" - tolist = getattr(pose, "tolist", None) - if callable(tolist): - try: - return tolist() - except Exception: - pass - return pose - - def broadcast(self, payload: Any) -> None: - with self._conns_lock: - conns = list(self.conns) - - dead = [] - for conn in conns: - try: - conn.send(payload) - self.broadcast_count += 1 - except Exception: - dead.append(conn) - - for conn in dead: - self._close_conn(conn) - - # ------------------------------------------------------------------ - # Save/get_data helpers - # ------------------------------------------------------------------ - def get_data(self) -> dict[str, Any]: - return { - "start_time": self.start_time, - "session_name": self.session_name, - "time_stamp": list(self.time_stamp), - "step": list(self.step), - "frame_time": list(self.frame_time), - "pose_time": list(self.pose_time), - "recording": self.recording, - "video_recording": self.video_recording, - "received_commands": list(self.received_commands), - "broadcast_count": self.broadcast_count, - } - - def save(self, file: str | Path | None = None) -> int: - if not file: - return 0 - try: - path = Path(file) - path.parent.mkdir(parents=True, exist_ok=True) - with path.open("wb") as fh: - pickle.dump(self.get_data(), fh) - return 1 - except Exception: - logger.exception("MockSocketProcessor save failed") - return -1 - - -# Optional aliases useful in different test styles. -MockPDSocketProcessor = MockSocketProcessor -MockUnitySocketProcessor = MockSocketProcessor - - -def get_available_processors() -> dict[str, dict[str, Any]]: - """Plugin-discovery entrypoint used by dlclivegui.processor_utils. - - This avoids requiring the class to inherit from dlclive.processor.Processor - during tests. The loader path that prefers get_available_processors() can - still discover this processor as a GUI plugin fixture. - """ - return { - "MockSocketProcessor": { - "class": MockSocketProcessor, - "name": getattr(MockSocketProcessor, "PROCESSOR_NAME", "MockSocketProcessor"), - "description": getattr(MockSocketProcessor, "PROCESSOR_DESCRIPTION", ""), - "params": getattr(MockSocketProcessor, "PROCESSOR_PARAMS", {}), - } - } diff --git a/dlclivegui/processors/custom/mock_unity_socket_client.ipynb b/dlclivegui/processors/custom/mock_unity_socket_client.ipynb deleted file mode 100644 index 63e4569a1..000000000 --- a/dlclivegui/processors/custom/mock_unity_socket_client.ipynb +++ /dev/null @@ -1,4609 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "7fb27b941602401d91542211134fc71a", - "metadata": {}, - "source": [ - "# Mock Unity Socket Client for DLCLive Processor\n", - "\n", - "This notebook acts as the **Unity-side socket client** for the legacy DLC socket processor.\n", - "\n", - "## Which processor style this targets\n", - "\n", - "The legacy processor chain you showed is:\n", - "\n", - "```text\n", - "dlc_inference_w_pd_sync\n", - " -> dlc_inference_w_pd\n", - " -> MyProcessor_socket\n", - "```\n", - "\n", - "`MyProcessor_socket` opens a `multiprocessing.connection.Listener`, defaulting to:\n", - "\n", - "```python\n", - "(\"127.0.0.1\", 6000)\n", - "authkey=b\"secret password\"\n", - "```\n", - "\n", - "It sends payloads from `process()` shaped like:\n", - "\n", - "```python\n", - "[time.time(), x, y, heading, head_angle, signal]\n", - "```\n", - "\n", - "This notebook connects as the **client** and tries to catch those pose/kinematics packets.\n", - "\n", - "## Important ordering note\n", - "\n", - "The legacy `MyProcessor_socket` does **not** have a background accept thread. It accepts a client only when `process()` runs. That means if DLC inference is not producing poses yet, `Client(...)` may block or time out. If that happens, start/continue DLC inference and retry." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "acae54e37e7d407bbb7b55eff062a284", - "metadata": {}, - "outputs": [], - "source": [ - "from __future__ import annotations\n", - "\n", - "import json\n", - "import queue\n", - "import threading\n", - "import time\n", - "from dataclasses import asdict, dataclass\n", - "from multiprocessing.connection import Client\n", - "from pathlib import Path\n", - "from typing import Any" - ] - }, - { - "cell_type": "markdown", - "id": "9a63283cbaf04dbcab1f6479b197f3a8", - "metadata": {}, - "source": [ - "## Configuration\n", - "\n", - "Adjust these if the processor uses a different port or auth key.\n", - "\n", - "For the legacy processor, the defaults are usually:\n", - "\n", - "```python\n", - "ADDRESS = (\"127.0.0.1\", 6000)\n", - "AUTHKEY = b\"secret password\"\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "8dd0d8092fe74a7c96281538738b07e2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Target processor socket: ('127.0.0.1', 6000)\n" - ] - } - ], - "source": [ - "ADDRESS = (\"127.0.0.1\", 6000)\n", - "AUTHKEY = b\"secret password\"\n", - "\n", - "# How long to wait for a connection attempt before considering it failed.\n", - "CONNECT_TIMEOUT_S = 10.0\n", - "\n", - "# How long the receive loop should poll while waiting for new packets.\n", - "POLL_INTERVAL_S = 0.05\n", - "\n", - "print(\"Target processor socket:\", ADDRESS)" - ] - }, - { - "cell_type": "markdown", - "id": "72eea5119410473aa328ad9291626812", - "metadata": {}, - "source": [ - "## Client helpers\n", - "\n", - "`multiprocessing.connection.Client(...)` can block if the server has not called `accept()` yet. To avoid freezing the notebook, `connect_with_timeout()` performs the connection attempt in a background thread." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "8edb47106e1a46a883d545849b8ab81b", - "metadata": {}, - "outputs": [], - "source": [ - "class ConnectTimeoutError(TimeoutError):\n", - " pass\n", - "\n", - "\n", - "def connect_with_timeout(address, authkey: bytes, timeout_s: float = 10.0):\n", - " \"\"\"Connect to a multiprocessing.connection.Listener without freezing the notebook forever.\"\"\"\n", - " result_q: queue.Queue[tuple[str, Any]] = queue.Queue(maxsize=1)\n", - "\n", - " def worker():\n", - " try:\n", - " conn = Client(address, authkey=authkey)\n", - " result_q.put((\"ok\", conn))\n", - " except Exception as exc:\n", - " result_q.put((\"error\", exc))\n", - "\n", - " t = threading.Thread(target=worker, name=\"MockUnityConnect\", daemon=True)\n", - " t.start()\n", - " t.join(timeout_s)\n", - "\n", - " if t.is_alive():\n", - " raise ConnectTimeoutError(\n", - " f\"Timed out after {timeout_s:.1f}s while connecting to {address}. \"\n", - " \"For legacy MyProcessor_socket, this can happen if DLC process() has not called listener.accept() yet.\"\n", - " )\n", - "\n", - " status, payload = result_q.get_nowait()\n", - " if status == \"ok\":\n", - " return payload\n", - " raise payload\n", - "\n", - "\n", - "@dataclass\n", - "class LegacyPosePacket:\n", - " timestamp: float\n", - " x: float\n", - " y: float\n", - " heading: float\n", - " head_angle: float\n", - " signal: float\n", - " raw: Any\n", - "\n", - "\n", - "def decode_payload(payload: Any) -> dict[str, Any]:\n", - " \"\"\"Decode either legacy list payloads or newer dict/list mock payloads.\"\"\"\n", - " # Legacy MyProcessor_socket payload:\n", - " # [time.time(), x, y, heading, head_angle, signal]\n", - " if isinstance(payload, list) and len(payload) == 6:\n", - " pkt = LegacyPosePacket(\n", - " timestamp=float(payload[0]),\n", - " x=float(payload[1]),\n", - " y=float(payload[2]),\n", - " heading=float(payload[3]),\n", - " head_angle=float(payload[4]),\n", - " signal=float(payload[5]),\n", - " raw=payload,\n", - " )\n", - " return {\"kind\": \"legacy_pose\", **asdict(pkt)}\n", - "\n", - " # Newer/base mock payloads may be dictionaries.\n", - " if isinstance(payload, dict):\n", - " kind = payload.get(\"type\", \"dict\")\n", - " return {\"kind\": kind, \"raw\": payload}\n", - "\n", - " # Some processors broadcast [timestamp, pose].\n", - " if isinstance(payload, list) and len(payload) == 2:\n", - " return {\"kind\": \"timestamp_pose\", \"timestamp\": payload[0], \"pose\": payload[1], \"raw\": payload}\n", - "\n", - " return {\"kind\": \"unknown\", \"raw\": payload}" - ] - }, - { - "cell_type": "markdown", - "id": "10185d26023b46108eb7d9f57d49d2b3", - "metadata": {}, - "source": [ - "## Connect to the DLC processor socket\n", - "\n", - "Run this cell once the processor has been created and its listener should be available.\n", - "\n", - "If it times out, it likely means either:\n", - "\n", - "1. the processor has not been instantiated yet,\n", - "2. the address/authkey are wrong,\n", - "3. the legacy processor is waiting until `process()` runs before accepting the connection,\n", - "4. another process is using the port." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "8763a12b2bbd4a93a75aff182afb95dc", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connected to processor socket: ('127.0.0.1', 6000)\n" - ] - } - ], - "source": [ - "conn = connect_with_timeout(ADDRESS, AUTHKEY, timeout_s=CONNECT_TIMEOUT_S)\n", - "print(\"Connected to processor socket:\", ADDRESS)" - ] - }, - { - "cell_type": "markdown", - "id": "7623eae2785240b9bd12b16a66d81610", - "metadata": {}, - "source": [ - "## Optional: send a ping/status command\n", - "\n", - "Only use this for processors that implement command handling, such as the newer `BaseProcessorSocket` or the standalone mock processor.\n", - "\n", - "The legacy `MyProcessor_socket` does **not** read commands from the client, so skip this cell for that processor." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7cdc8c89c7104fffa095e18ddfef8986", - "metadata": {}, - "outputs": [], - "source": [ - "# Uncomment only for BaseProcessorSocket-style processors or the standalone mock.\n", - "# conn.send({\"cmd\": \"ping\"})\n", - "# if conn.poll(2.0):\n", - "# print(\"Response:\", conn.recv())\n", - "# else:\n", - "# print(\"No response. This is expected for legacy MyProcessor_socket.\")" - ] - }, - { - "cell_type": "markdown", - "id": "b118ea5561624da68c537baed56e602f", - "metadata": {}, - "source": [ - "## Receive pose packets\n", - "\n", - "This cell listens for up to `duration_s` seconds and prints decoded packets. For legacy `MyProcessor_socket`, you should see `legacy_pose` packets once `process()` is called by DLC inference." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "938c804e27f84196a10c8828c723f798", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.750286,\n", - " \"step\": 12,\n", - " \"pose\": [\n", - " [\n", - " 288.7191162109375,\n", - " 298.63250732421875,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.0001220703125,\n", - " 307.95672607421875,\n", - " 0.973543643951416\n", - " ],\n", - " [\n", - " 297.29254150390625,\n", - " 314.52410888671875,\n", - " 0.6293796896934509\n", - " ],\n", - " [\n", - " 290.9452819824219,\n", - " 310.3515319824219,\n", - " 0.640852153301239\n", - " ],\n", - " [\n", - " 304.86236572265625,\n", - " 313.2810974121094,\n", - " 0.6921122670173645\n", - " ],\n", - " [\n", - " 284.668212890625,\n", - " 266.52752685546875,\n", - " 0.9321146607398987\n", - " ],\n", - " [\n", - " 284.6539001464844,\n", - " 237.49722290039062,\n", - " 0.4642881155014038\n", - " ],\n", - " [\n", - " 298.547607421875,\n", - " 218.42587280273438,\n", - " 0.26315996050834656\n", - " ],\n", - " [\n", - " 182.9195098876953,\n", - " 312.5831604003906,\n", - " 0.4563772678375244\n", - " ],\n", - " [\n", - " 181.693359375,\n", - " 306.1708068847656,\n", - " 0.39262306690216064\n", - " ],\n", - " [\n", - " 309.70013427734375,\n", - " 273.22198486328125,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.6557922363281,\n", - " 278.49371337890625,\n", - " 0.5969972014427185\n", - " ],\n", - " [\n", - " 362.46343994140625,\n", - " 283.172119140625,\n", - " 0.4280658960342407\n", - " ],\n", - " [\n", - " 185.8195343017578,\n", - " 311.7103271484375,\n", - " 0.27244052290916443\n", - " ],\n", - " [\n", - " 192.61837768554688,\n", - " 305.4747009277344,\n", - " 0.39853179454803467\n", - " ],\n", - " [\n", - " 355.7668151855469,\n", - " 339.8248596191406,\n", - " 0.40097054839134216\n", - " ],\n", - " [\n", - " 364.0209655761719,\n", - " 341.5860595703125,\n", - " 0.5437613129615784\n", - " ],\n", - " [\n", - " 335.2928771972656,\n", - " 344.40032958984375,\n", - " 0.3498704731464386\n", - " ],\n", - " [\n", - " 331.4139404296875,\n", - " 356.1664123535156,\n", - " 0.4611521065235138\n", - " ],\n", - " [\n", - " 366.796142578125,\n", - " 344.254150390625,\n", - " 0.47546425461769104\n", - " ],\n", - " [\n", - " 270.3355712890625,\n", - " 358.1495666503906,\n", - " 0.4529607594013214\n", - " ],\n", - " [\n", - " 379.02679443359375,\n", - " 356.05645751953125,\n", - " 0.4878201186656952\n", - " ],\n", - " [\n", - " 268.0301818847656,\n", - " 359.48583984375,\n", - " 0.366856187582016\n", - " ],\n", - " [\n", - " 599.5546264648438,\n", - " 377.7467956542969,\n", - " 0.564258873462677\n", - " ],\n", - " [\n", - " 185.41236877441406,\n", - " 207.2476043701172,\n", - " 0.3415561616420746\n", - " ],\n", - " [\n", - " 518.7420043945312,\n", - " 391.21240234375,\n", - " 0.3360799551010132\n", - " ],\n", - " [\n", - " 516.0563354492188,\n", - " 429.2253723144531,\n", - " 0.7114025354385376\n", - " ],\n", - " [\n", - " 164.67864990234375,\n", - " 212.08599853515625,\n", - " 0.22173050045967102\n", - " ],\n", - " [\n", - " 508.5932312011719,\n", - " 391.6039123535156,\n", - " 0.30660656094551086\n", - " ],\n", - " [\n", - " 511.993896484375,\n", - " 447.9190368652344,\n", - " 0.5064514875411987\n", - " ],\n", - " [\n", - " 591.1927490234375,\n", - " 409.2403564453125,\n", - " 0.5136002898216248\n", - " ],\n", - " [\n", - " 106.3792495727539,\n", - " 271.0600891113281,\n", - " 0.14335250854492188\n", - " ],\n", - " [\n", - " 112.1520767211914,\n", - " 279.07525634765625,\n", - " 0.2755976915359497\n", - " ],\n", - " [\n", - " 572.9004516601562,\n", - " 393.2549743652344,\n", - " 0.39857158064842224\n", - " ],\n", - " [\n", - " 144.4106903076172,\n", - " 313.3831787109375,\n", - " 0.27947890758514404\n", - " ],\n", - " [\n", - " 550.626953125,\n", - " 466.50970458984375,\n", - " 0.5572487115859985\n", - " ],\n", - " [\n", - " 466.3507385253906,\n", - " 413.0749206542969,\n", - " 0.39403966069221497\n", - " ],\n", - " [\n", - " 461.47198486328125,\n", - " 400.29541015625,\n", - " 0.3778141140937805\n", - " ],\n", - " [\n", - " 460.13824462890625,\n", - " 403.57171630859375,\n", - " 0.5188782811164856\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.7246952,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.750286\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.78031,\n", - " \"step\": 13,\n", - " \"pose\": [\n", - " [\n", - " 287.8656921386719,\n", - " 298.9954833984375,\n", - " 1.0\n", - " ],\n", - " [\n", - " 292.98870849609375,\n", - " 308.14825439453125,\n", - " 0.9376083016395569\n", - " ],\n", - " [\n", - " 297.0699462890625,\n", - " 315.7899169921875,\n", - " 0.605503499507904\n", - " ],\n", - " [\n", - " 290.7883605957031,\n", - " 311.0892333984375,\n", - " 0.653724193572998\n", - " ],\n", - " [\n", - " 304.9418029785156,\n", - " 314.27423095703125,\n", - " 0.6581617593765259\n", - " ],\n", - " [\n", - " 284.5467224121094,\n", - " 266.99755859375,\n", - " 0.9386962056159973\n", - " ],\n", - " [\n", - " 285.4912109375,\n", - " 236.9186248779297,\n", - " 0.48566538095474243\n", - " ],\n", - " [\n", - " 282.66357421875,\n", - " 236.26953125,\n", - " 0.2793366611003876\n", - " ],\n", - " [\n", - " 182.8148193359375,\n", - " 314.7862548828125,\n", - " 0.4555658996105194\n", - " ],\n", - " [\n", - " 183.0238037109375,\n", - " 309.2024230957031,\n", - " 0.3395337760448456\n", - " ],\n", - " [\n", - " 310.2979431152344,\n", - " 272.9541015625,\n", - " 1.0\n", - " ],\n", - " [\n", - " 362.39019775390625,\n", - " 278.04864501953125,\n", - " 0.7049410343170166\n", - " ],\n", - " [\n", - " 362.625,\n", - " 281.328369140625,\n", - " 0.4549161195755005\n", - " ],\n", - " [\n", - " 357.58203125,\n", - " 282.10272216796875,\n", - " 0.26718243956565857\n", - " ],\n", - " [\n", - " 194.67514038085938,\n", - " 306.698974609375,\n", - " 0.45219412446022034\n", - " ],\n", - " [\n", - " 355.5292663574219,\n", - " 339.20318603515625,\n", - " 0.41419631242752075\n", - " ],\n", - " [\n", - " 367.3896179199219,\n", - " 340.3309326171875,\n", - " 0.5223292112350464\n", - " ],\n", - " [\n", - " 335.11529541015625,\n", - " 343.81201171875,\n", - " 0.347744345664978\n", - " ],\n", - " [\n", - " 332.03765869140625,\n", - " 356.7191162109375,\n", - " 0.4733559191226959\n", - " ],\n", - " [\n", - " 367.4532470703125,\n", - " 344.79302978515625,\n", - " 0.4827011823654175\n", - " ],\n", - " [\n", - " 270.3110046386719,\n", - " 357.83343505859375,\n", - " 0.4700448215007782\n", - " ],\n", - " [\n", - " 378.0578308105469,\n", - " 356.4914855957031,\n", - " 0.4333026707172394\n", - " ],\n", - " [\n", - " 267.2149963378906,\n", - " 359.4980773925781,\n", - " 0.39140474796295166\n", - " ],\n", - " [\n", - " 596.2949829101562,\n", - " 378.6457824707031,\n", - " 0.5882202386856079\n", - " ],\n", - " [\n", - " 185.2808837890625,\n", - " 208.80474853515625,\n", - " 0.4029167890548706\n", - " ],\n", - " [\n", - " 563.7208862304688,\n", - " 387.8863220214844,\n", - " 0.390456885099411\n", - " ],\n", - " [\n", - " 519.1982421875,\n", - " 433.89654541015625,\n", - " 0.5289698839187622\n", - " ],\n", - " [\n", - " 442.9023132324219,\n", - " 356.9894714355469,\n", - " 0.2825982868671417\n", - " ],\n", - " [\n", - " 568.7670288085938,\n", - " 195.95079040527344,\n", - " 0.43732380867004395\n", - " ],\n", - " [\n", - " 512.254638671875,\n", - " 449.7223815917969,\n", - " 0.6101775169372559\n", - " ],\n", - " [\n", - " 591.0804443359375,\n", - " 409.246337890625,\n", - " 0.5442432761192322\n", - " ],\n", - " [\n", - " 470.5218200683594,\n", - " 402.7597961425781,\n", - " 0.1880091279745102\n", - " ],\n", - " [\n", - " 107.73304748535156,\n", - " 270.0023193359375,\n", - " 0.14866414666175842\n", - " ],\n", - " [\n", - " 572.8255615234375,\n", - " 393.6127624511719,\n", - " 0.5601691603660583\n", - " ],\n", - " [\n", - " 481.23486328125,\n", - " 431.833984375,\n", - " 0.2556696832180023\n", - " ],\n", - " [\n", - " 550.4242553710938,\n", - " 467.0722961425781,\n", - " 0.4299638867378235\n", - " ],\n", - " [\n", - " 466.45404052734375,\n", - " 412.8644714355469,\n", - " 0.38715386390686035\n", - " ],\n", - " [\n", - " 462.144775390625,\n", - " 401.0147705078125,\n", - " 0.3404390513896942\n", - " ],\n", - " [\n", - " 459.4046325683594,\n", - " 403.1348571777344,\n", - " 0.4926401376724243\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.7554483,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.78031\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.821648,\n", - " \"step\": 14,\n", - " \"pose\": [\n", - " [\n", - " 288.8023681640625,\n", - " 298.86077880859375,\n", - " 1.0\n", - " ],\n", - " [\n", - " 292.8272399902344,\n", - " 308.04949951171875,\n", - " 0.9570764899253845\n", - " ],\n", - " [\n", - " 296.77056884765625,\n", - " 315.70367431640625,\n", - " 0.631300687789917\n", - " ],\n", - " [\n", - " 289.9299621582031,\n", - " 310.9530944824219,\n", - " 0.6520562171936035\n", - " ],\n", - " [\n", - " 305.0509338378906,\n", - " 313.7876892089844,\n", - " 0.7196981310844421\n", - " ],\n", - " [\n", - " 284.6484680175781,\n", - " 266.7134704589844,\n", - " 0.9353039860725403\n", - " ],\n", - " [\n", - " 285.0174560546875,\n", - " 238.79736328125,\n", - " 0.4836384952068329\n", - " ],\n", - " [\n", - " 154.6018829345703,\n", - " 315.4358215332031,\n", - " 0.2590964734554291\n", - " ],\n", - " [\n", - " 336.0355529785156,\n", - " 466.2019348144531,\n", - " 0.37860575318336487\n", - " ],\n", - " [\n", - " 181.9645233154297,\n", - " 308.6495361328125,\n", - " 0.3605335056781769\n", - " ],\n", - " [\n", - " 309.65972900390625,\n", - " 273.17425537109375,\n", - " 1.0\n", - " ],\n", - " [\n", - " 362.54388427734375,\n", - " 277.6341857910156,\n", - " 0.5562496781349182\n", - " ],\n", - " [\n", - " 361.87298583984375,\n", - " 282.2453308105469,\n", - " 0.477169394493103\n", - " ],\n", - " [\n", - " 358.46246337890625,\n", - " 282.86480712890625,\n", - " 0.2796511948108673\n", - " ],\n", - " [\n", - " 189.29440307617188,\n", - " 305.3114929199219,\n", - " 0.41088035702705383\n", - " ],\n", - " [\n", - " 355.5824890136719,\n", - " 341.4902648925781,\n", - " 0.3962235450744629\n", - " ],\n", - " [\n", - " 363.149658203125,\n", - " 340.7292785644531,\n", - " 0.48571205139160156\n", - " ],\n", - " [\n", - " 332.39642333984375,\n", - " 340.5425720214844,\n", - " 0.32239603996276855\n", - " ],\n", - " [\n", - " 331.2975769042969,\n", - " 355.4726257324219,\n", - " 0.4472481310367584\n", - " ],\n", - " [\n", - " 364.4246520996094,\n", - " 346.6827087402344,\n", - " 0.4476216733455658\n", - " ],\n", - " [\n", - " 269.8668212890625,\n", - " 359.0860900878906,\n", - " 0.43882080912590027\n", - " ],\n", - " [\n", - " 379.4068298339844,\n", - " 356.64239501953125,\n", - " 0.4417465329170227\n", - " ],\n", - " [\n", - " 266.974853515625,\n", - " 360.31402587890625,\n", - " 0.33691221475601196\n", - " ],\n", - " [\n", - " 596.21826171875,\n", - " 377.820068359375,\n", - " 0.5398204922676086\n", - " ],\n", - " [\n", - " 183.42034912109375,\n", - " 208.78756713867188,\n", - " 0.34466955065727234\n", - " ],\n", - " [\n", - " 564.060791015625,\n", - " 388.1324157714844,\n", - " 0.4286687970161438\n", - " ],\n", - " [\n", - " 517.4417724609375,\n", - " 430.2832336425781,\n", - " 0.5827439427375793\n", - " ],\n", - " [\n", - " 169.510009765625,\n", - " 212.77224731445312,\n", - " 0.23520678281784058\n", - " ],\n", - " [\n", - " 509.5054016113281,\n", - " 390.251708984375,\n", - " 0.2855446934700012\n", - " ],\n", - " [\n", - " 511.0235290527344,\n", - " 447.9842224121094,\n", - " 0.5595549941062927\n", - " ],\n", - " [\n", - " 591.0059204101562,\n", - " 408.37109375,\n", - " 0.5015893578529358\n", - " ],\n", - " [\n", - " 475.4132385253906,\n", - " 422.1249084472656,\n", - " 0.19282308220863342\n", - " ],\n", - " [\n", - " 110.6484146118164,\n", - " 276.17138671875,\n", - " 0.3112924098968506\n", - " ],\n", - " [\n", - " 572.998046875,\n", - " 393.3849182128906,\n", - " 0.4534406363964081\n", - " ],\n", - " [\n", - " 481.1611022949219,\n", - " 432.43438720703125,\n", - " 0.2341541200876236\n", - " ],\n", - " [\n", - " 551.3787231445312,\n", - " 466.6352844238281,\n", - " 0.5391212105751038\n", - " ],\n", - " [\n", - " 467.7240905761719,\n", - " 412.1317138671875,\n", - " 0.3793080151081085\n", - " ],\n", - " [\n", - " 465.2247619628906,\n", - " 409.2422180175781,\n", - " 0.33666175603866577\n", - " ],\n", - " [\n", - " 460.2418212890625,\n", - " 403.50830078125,\n", - " 0.42937344312667847\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.7889726,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.821648\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.851803,\n", - " \"step\": 15,\n", - " \"pose\": [\n", - " [\n", - " 288.8399963378906,\n", - " 298.389892578125,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.063232421875,\n", - " 307.6488952636719,\n", - " 0.9409026503562927\n", - " ],\n", - " [\n", - " 296.73828125,\n", - " 314.7473449707031,\n", - " 0.6393375992774963\n", - " ],\n", - " [\n", - " 290.7121887207031,\n", - " 310.599609375,\n", - " 0.6521811485290527\n", - " ],\n", - " [\n", - " 304.9431457519531,\n", - " 311.9217224121094,\n", - " 0.7004058957099915\n", - " ],\n", - " [\n", - " 284.689697265625,\n", - " 267.53924560546875,\n", - " 0.9271823167800903\n", - " ],\n", - " [\n", - " 182.9373779296875,\n", - " 313.93133544921875,\n", - " 0.5212528109550476\n", - " ],\n", - " [\n", - " 153.7313690185547,\n", - " 313.92779541015625,\n", - " 0.3010638356208801\n", - " ],\n", - " [\n", - " 181.32296752929688,\n", - " 312.8819580078125,\n", - " 0.5419734120368958\n", - " ],\n", - " [\n", - " 183.34437561035156,\n", - " 306.7300720214844,\n", - " 0.37734419107437134\n", - " ],\n", - " [\n", - " 309.9617004394531,\n", - " 273.3157653808594,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.39080810546875,\n", - " 277.68914794921875,\n", - " 0.7047985792160034\n", - " ],\n", - " [\n", - " 362.3508605957031,\n", - " 282.02313232421875,\n", - " 0.4799898564815521\n", - " ],\n", - " [\n", - " 179.46484375,\n", - " 315.53436279296875,\n", - " 0.3219224810600281\n", - " ],\n", - " [\n", - " 193.1734161376953,\n", - " 304.5758056640625,\n", - " 0.4651084244251251\n", - " ],\n", - " [\n", - " 355.8511047363281,\n", - " 340.7768859863281,\n", - " 0.42659905552864075\n", - " ],\n", - " [\n", - " 364.3086242675781,\n", - " 341.3064270019531,\n", - " 0.5809048414230347\n", - " ],\n", - " [\n", - " 310.90179443359375,\n", - " 347.79620361328125,\n", - " 0.37011390924453735\n", - " ],\n", - " [\n", - " 329.7757263183594,\n", - " 356.66290283203125,\n", - " 0.5357598066329956\n", - " ],\n", - " [\n", - " 369.3732604980469,\n", - " 347.80084228515625,\n", - " 0.5102704167366028\n", - " ],\n", - " [\n", - " 269.4798583984375,\n", - " 358.50128173828125,\n", - " 0.36626455187797546\n", - " ],\n", - " [\n", - " 379.74285888671875,\n", - " 356.38800048828125,\n", - " 0.4842686653137207\n", - " ],\n", - " [\n", - " 265.9881286621094,\n", - " 360.00592041015625,\n", - " 0.27803361415863037\n", - " ],\n", - " [\n", - " 596.4786376953125,\n", - " 379.2182922363281,\n", - " 0.5961218476295471\n", - " ],\n", - " [\n", - " 254.6011505126953,\n", - " 382.6790466308594,\n", - " 0.4003625512123108\n", - " ],\n", - " [\n", - " 563.052001953125,\n", - " 386.73590087890625,\n", - " 0.36300426721572876\n", - " ],\n", - " [\n", - " 517.0165405273438,\n", - " 430.59613037109375,\n", - " 0.5643782019615173\n", - " ],\n", - " [\n", - " 164.36508178710938,\n", - " 213.65438842773438,\n", - " 0.2149515450000763\n", - " ],\n", - " [\n", - " 509.01336669921875,\n", - " 392.5158996582031,\n", - " 0.23822596669197083\n", - " ],\n", - " [\n", - " 510.94085693359375,\n", - " 449.33477783203125,\n", - " 0.6286779642105103\n", - " ],\n", - " [\n", - " 590.7813110351562,\n", - " 409.1065368652344,\n", - " 0.4439380168914795\n", - " ],\n", - " [\n", - " 463.3310546875,\n", - " 401.2489318847656,\n", - " 0.1755678504705429\n", - " ],\n", - " [\n", - " 109.76718139648438,\n", - " 271.8696594238281,\n", - " 0.2973553240299225\n", - " ],\n", - " [\n", - " 572.5289306640625,\n", - " 393.1651916503906,\n", - " 0.5296833515167236\n", - " ],\n", - " [\n", - " 480.9592590332031,\n", - " 432.1273193359375,\n", - " 0.21754036843776703\n", - " ],\n", - " [\n", - " 550.6238403320312,\n", - " 466.08721923828125,\n", - " 0.5377715229988098\n", - " ],\n", - " [\n", - " 468.291015625,\n", - " 413.2549133300781,\n", - " 0.3708413243293762\n", - " ],\n", - " [\n", - " 136.44790649414062,\n", - " 171.77438354492188,\n", - " 0.3511451184749603\n", - " ],\n", - " [\n", - " 460.23358154296875,\n", - " 404.2395935058594,\n", - " 0.47236377000808716\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.8206406,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.8528142\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.8770585,\n", - " \"step\": 16,\n", - " \"pose\": [\n", - " [\n", - " 289.1402282714844,\n", - " 299.0330810546875,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.5198974609375,\n", - " 308.2564392089844,\n", - " 0.9859002232551575\n", - " ],\n", - " [\n", - " 297.6006164550781,\n", - " 314.6565856933594,\n", - " 0.6514089107513428\n", - " ],\n", - " [\n", - " 290.924072265625,\n", - " 311.3872985839844,\n", - " 0.6389972567558289\n", - " ],\n", - " [\n", - " 304.9306945800781,\n", - " 313.3708801269531,\n", - " 0.7217590808868408\n", - " ],\n", - " [\n", - " 284.5622863769531,\n", - " 267.4127502441406,\n", - " 0.9266435503959656\n", - " ],\n", - " [\n", - " 184.82669067382812,\n", - " 312.5155334472656,\n", - " 0.5616798400878906\n", - " ],\n", - " [\n", - " 154.43678283691406,\n", - " 314.49652099609375,\n", - " 0.24991558492183685\n", - " ],\n", - " [\n", - " 182.59487915039062,\n", - " 312.3272705078125,\n", - " 0.592433512210846\n", - " ],\n", - " [\n", - " 182.8374786376953,\n", - " 306.06494140625,\n", - " 0.419148325920105\n", - " ],\n", - " [\n", - " 310.11920166015625,\n", - " 273.36737060546875,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.7231140136719,\n", - " 277.9724426269531,\n", - " 0.5769408345222473\n", - " ],\n", - " [\n", - " 362.3317565917969,\n", - " 282.26922607421875,\n", - " 0.43514740467071533\n", - " ],\n", - " [\n", - " 184.4752655029297,\n", - " 311.1407470703125,\n", - " 0.38407573103904724\n", - " ],\n", - " [\n", - " 187.931640625,\n", - " 302.62933349609375,\n", - " 0.4633309543132782\n", - " ],\n", - " [\n", - " 356.0732421875,\n", - " 340.64599609375,\n", - " 0.4025139808654785\n", - " ],\n", - " [\n", - " 362.9293212890625,\n", - " 341.8232727050781,\n", - " 0.5162671208381653\n", - " ],\n", - " [\n", - " 309.4438781738281,\n", - " 345.9781799316406,\n", - " 0.33583828806877136\n", - " ],\n", - " [\n", - " 331.2337341308594,\n", - " 355.5598449707031,\n", - " 0.4219924509525299\n", - " ],\n", - " [\n", - " 366.71087646484375,\n", - " 345.36962890625,\n", - " 0.4366249144077301\n", - " ],\n", - " [\n", - " 269.210693359375,\n", - " 358.0929260253906,\n", - " 0.3690962493419647\n", - " ],\n", - " [\n", - " 424.5083923339844,\n", - " 376.5726013183594,\n", - " 0.42966294288635254\n", - " ],\n", - " [\n", - " 303.0454406738281,\n", - " 218.3123779296875,\n", - " 0.31208333373069763\n", - " ],\n", - " [\n", - " 598.5891723632812,\n", - " 378.4424133300781,\n", - " 0.5889440178871155\n", - " ],\n", - " [\n", - " 197.69949340820312,\n", - " 217.56845092773438,\n", - " 0.3414533734321594\n", - " ],\n", - " [\n", - " 563.177734375,\n", - " 386.8681945800781,\n", - " 0.39073020219802856\n", - " ],\n", - " [\n", - " 516.6555786132812,\n", - " 428.7958679199219,\n", - " 0.5345339179039001\n", - " ],\n", - " [\n", - " 442.6798095703125,\n", - " 357.461181640625,\n", - " 0.23529931902885437\n", - " ],\n", - " [\n", - " 507.02862548828125,\n", - " 392.94842529296875,\n", - " 0.26026907563209534\n", - " ],\n", - " [\n", - " 511.629150390625,\n", - " 447.88116455078125,\n", - " 0.5966296792030334\n", - " ],\n", - " [\n", - " 592.001708984375,\n", - " 408.8056945800781,\n", - " 0.5580258965492249\n", - " ],\n", - " [\n", - " 463.76220703125,\n", - " 402.1715087890625,\n", - " 0.16743294894695282\n", - " ],\n", - " [\n", - " 111.18097686767578,\n", - " 276.2333679199219,\n", - " 0.2950418293476105\n", - " ],\n", - " [\n", - " 573.3688354492188,\n", - " 393.168701171875,\n", - " 0.4543166756629944\n", - " ],\n", - " [\n", - " 481.0332946777344,\n", - " 433.19580078125,\n", - " 0.2525552809238434\n", - " ],\n", - " [\n", - " 551.67578125,\n", - " 466.51300048828125,\n", - " 0.48711535334587097\n", - " ],\n", - " [\n", - " 468.5693054199219,\n", - " 413.4539489746094,\n", - " 0.38309159874916077\n", - " ],\n", - " [\n", - " 135.75698852539062,\n", - " 170.189697265625,\n", - " 0.30588316917419434\n", - " ],\n", - " [\n", - " 459.2412109375,\n", - " 403.5185852050781,\n", - " 0.3879672586917877\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.8528142,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.8770585\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.9255464,\n", - " \"step\": 17,\n", - " \"pose\": [\n", - " [\n", - " 289.01116943359375,\n", - " 299.0413818359375,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.1086730957031,\n", - " 308.51885986328125,\n", - " 0.95180344581604\n", - " ],\n", - " [\n", - " 296.3152770996094,\n", - " 316.4967956542969,\n", - " 0.5928277373313904\n", - " ],\n", - " [\n", - " 290.5437927246094,\n", - " 311.6708679199219,\n", - " 0.6084922552108765\n", - " ],\n", - " [\n", - " 306.17803955078125,\n", - " 313.6896057128906,\n", - " 0.7187818288803101\n", - " ],\n", - " [\n", - " 284.83319091796875,\n", - " 267.0993957519531,\n", - " 0.9082852602005005\n", - " ],\n", - " [\n", - " 285.2369384765625,\n", - " 239.99192810058594,\n", - " 0.4807704985141754\n", - " ],\n", - " [\n", - " 281.5351257324219,\n", - " 244.12281799316406,\n", - " 0.25091421604156494\n", - " ],\n", - " [\n", - " 183.10739135742188,\n", - " 313.957275390625,\n", - " 0.47192785143852234\n", - " ],\n", - " [\n", - " 182.70115661621094,\n", - " 306.3948974609375,\n", - " 0.3859936594963074\n", - " ],\n", - " [\n", - " 310.44354248046875,\n", - " 273.25341796875,\n", - " 1.0\n", - " ],\n", - " [\n", - " 362.1278076171875,\n", - " 276.0966796875,\n", - " 0.66349196434021\n", - " ],\n", - " [\n", - " 363.0843200683594,\n", - " 282.9954528808594,\n", - " 0.44170665740966797\n", - " ],\n", - " [\n", - " 182.47335815429688,\n", - " 311.0877685546875,\n", - " 0.2967713475227356\n", - " ],\n", - " [\n", - " 188.00137329101562,\n", - " 304.48931884765625,\n", - " 0.45905861258506775\n", - " ],\n", - " [\n", - " 354.645263671875,\n", - " 339.31219482421875,\n", - " 0.3976733684539795\n", - " ],\n", - " [\n", - " 363.1065979003906,\n", - " 339.9534606933594,\n", - " 0.5860384702682495\n", - " ],\n", - " [\n", - " 316.1658935546875,\n", - " 342.28369140625,\n", - " 0.3572511076927185\n", - " ],\n", - " [\n", - " 331.590087890625,\n", - " 355.1735534667969,\n", - " 0.4502539336681366\n", - " ],\n", - " [\n", - " 363.5017395019531,\n", - " 344.60235595703125,\n", - " 0.5015437006950378\n", - " ],\n", - " [\n", - " 268.5982666015625,\n", - " 358.1197204589844,\n", - " 0.36430323123931885\n", - " ],\n", - " [\n", - " 378.5502014160156,\n", - " 355.8092041015625,\n", - " 0.42007115483283997\n", - " ],\n", - " [\n", - " 303.5655517578125,\n", - " 217.58612060546875,\n", - " 0.33906295895576477\n", - " ],\n", - " [\n", - " 596.765380859375,\n", - " 379.427001953125,\n", - " 0.5411680340766907\n", - " ],\n", - " [\n", - " 199.35903930664062,\n", - " 222.4739990234375,\n", - " 0.29007023572921753\n", - " ],\n", - " [\n", - " 562.4085083007812,\n", - " 386.65509033203125,\n", - " 0.3170825242996216\n", - " ],\n", - " [\n", - " 517.340087890625,\n", - " 430.5169677734375,\n", - " 0.6213215589523315\n", - " ],\n", - " [\n", - " 441.74444580078125,\n", - " 357.04644775390625,\n", - " 0.2545863389968872\n", - " ],\n", - " [\n", - " 503.5122375488281,\n", - " 393.9283447265625,\n", - " 0.30190107226371765\n", - " ],\n", - " [\n", - " 510.5965576171875,\n", - " 450.106201171875,\n", - " 0.5517356991767883\n", - " ],\n", - " [\n", - " 591.1541748046875,\n", - " 408.65826416015625,\n", - " 0.49750760197639465\n", - " ],\n", - " [\n", - " 475.8544921875,\n", - " 422.05078125,\n", - " 0.1733374446630478\n", - " ],\n", - " [\n", - " 109.9720687866211,\n", - " 271.4387512207031,\n", - " 0.2799350619316101\n", - " ],\n", - " [\n", - " 572.795166015625,\n", - " 393.3072204589844,\n", - " 0.3979363441467285\n", - " ],\n", - " [\n", - " 479.5787353515625,\n", - " 432.65380859375,\n", - " 0.22706195712089539\n", - " ],\n", - " [\n", - " 550.501708984375,\n", - " 467.0165100097656,\n", - " 0.4965691566467285\n", - " ],\n", - " [\n", - " 468.164306640625,\n", - " 412.325927734375,\n", - " 0.3681727349758148\n", - " ],\n", - " [\n", - " 461.05029296875,\n", - " 400.1574401855469,\n", - " 0.36365073919296265\n", - " ],\n", - " [\n", - " 460.8140563964844,\n", - " 402.9066467285156,\n", - " 0.4981914758682251\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.8993094,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.9255464\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.9609218,\n", - " \"step\": 18,\n", - " \"pose\": [\n", - " [\n", - " 288.9327392578125,\n", - " 299.13629150390625,\n", - " 1.0\n", - " ],\n", - " [\n", - " 292.8038330078125,\n", - " 308.6182861328125,\n", - " 0.965558648109436\n", - " ],\n", - " [\n", - " 297.1647644042969,\n", - " 315.5074157714844,\n", - " 0.6364358067512512\n", - " ],\n", - " [\n", - " 290.55609130859375,\n", - " 311.7894287109375,\n", - " 0.6080928444862366\n", - " ],\n", - " [\n", - " 305.4518127441406,\n", - " 313.89239501953125,\n", - " 0.7770905494689941\n", - " ],\n", - " [\n", - " 284.95733642578125,\n", - " 267.11175537109375,\n", - " 0.9243097305297852\n", - " ],\n", - " [\n", - " 285.8481750488281,\n", - " 238.2171630859375,\n", - " 0.49581968784332275\n", - " ],\n", - " [\n", - " 281.67034912109375,\n", - " 238.3629608154297,\n", - " 0.2495463341474533\n", - " ],\n", - " [\n", - " 181.5650177001953,\n", - " 314.2547912597656,\n", - " 0.5168197751045227\n", - " ],\n", - " [\n", - " 182.05751037597656,\n", - " 306.9068908691406,\n", - " 0.36109668016433716\n", - " ],\n", - " [\n", - " 310.2335205078125,\n", - " 273.6082763671875,\n", - " 1.0\n", - " ],\n", - " [\n", - " 362.219482421875,\n", - " 276.6759948730469,\n", - " 0.618270754814148\n", - " ],\n", - " [\n", - " 362.4667663574219,\n", - " 280.8567199707031,\n", - " 0.40474116802215576\n", - " ],\n", - " [\n", - " 184.80101013183594,\n", - " 316.924560546875,\n", - " 0.3415667712688446\n", - " ],\n", - " [\n", - " 189.2113800048828,\n", - " 305.0552062988281,\n", - " 0.44207993149757385\n", - " ],\n", - " [\n", - " 354.8471984863281,\n", - " 341.1282043457031,\n", - " 0.4782800078392029\n", - " ],\n", - " [\n", - " 363.4027404785156,\n", - " 341.2732238769531,\n", - " 0.518268346786499\n", - " ],\n", - " [\n", - " 316.40673828125,\n", - " 342.68695068359375,\n", - " 0.3713846504688263\n", - " ],\n", - " [\n", - " 330.7171630859375,\n", - " 355.67803955078125,\n", - " 0.46434029936790466\n", - " ],\n", - " [\n", - " 367.51568603515625,\n", - " 344.2020568847656,\n", - " 0.4221855401992798\n", - " ],\n", - " [\n", - " 269.15869140625,\n", - " 357.6059265136719,\n", - " 0.43143007159233093\n", - " ],\n", - " [\n", - " 377.92431640625,\n", - " 355.83026123046875,\n", - " 0.42217832803726196\n", - " ],\n", - " [\n", - " 266.35400390625,\n", - " 359.1109313964844,\n", - " 0.36265674233436584\n", - " ],\n", - " [\n", - " 598.98876953125,\n", - " 378.3539123535156,\n", - " 0.5779925584793091\n", - " ],\n", - " [\n", - " 521.8517456054688,\n", - " 317.60223388671875,\n", - " 0.3474227786064148\n", - " ],\n", - " [\n", - " 563.2191162109375,\n", - " 386.7420654296875,\n", - " 0.3701111972332001\n", - " ],\n", - " [\n", - " 516.5422973632812,\n", - " 430.6274108886719,\n", - " 0.5754683613777161\n", - " ],\n", - " [\n", - " 566.955078125,\n", - " 197.94085693359375,\n", - " 0.35363760590553284\n", - " ],\n", - " [\n", - " 573.0870971679688,\n", - " 198.2352294921875,\n", - " 0.5906892418861389\n", - " ],\n", - " [\n", - " 511.6949157714844,\n", - " 449.3252258300781,\n", - " 0.6369988322257996\n", - " ],\n", - " [\n", - " 591.0794067382812,\n", - " 408.18438720703125,\n", - " 0.5102192759513855\n", - " ],\n", - " [\n", - " 508.6004638671875,\n", - " 381.352783203125,\n", - " 0.2138703316450119\n", - " ],\n", - " [\n", - " 109.30960083007812,\n", - " 272.1946716308594,\n", - " 0.336846262216568\n", - " ],\n", - " [\n", - " 572.595703125,\n", - " 393.81610107421875,\n", - " 0.48220178484916687\n", - " ],\n", - " [\n", - " 482.5830383300781,\n", - " 431.64202880859375,\n", - " 0.18888558447360992\n", - " ],\n", - " [\n", - " 549.8436279296875,\n", - " 465.8060302734375,\n", - " 0.6060653924942017\n", - " ],\n", - " [\n", - " 470.1370544433594,\n", - " 413.1282653808594,\n", - " 0.4226745069026947\n", - " ],\n", - " [\n", - " 134.86871337890625,\n", - " 172.37551879882812,\n", - " 0.34861159324645996\n", - " ],\n", - " [\n", - " 460.8798522949219,\n", - " 403.1666564941406,\n", - " 0.4525850713253021\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.933552,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.9609218\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588105.996339,\n", - " \"step\": 19,\n", - " \"pose\": [\n", - " [\n", - " 289.3584289550781,\n", - " 299.6400451660156,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.6114807128906,\n", - " 308.7494201660156,\n", - " 0.9362009763717651\n", - " ],\n", - " [\n", - " 296.5362548828125,\n", - " 315.8563537597656,\n", - " 0.6216031908988953\n", - " ],\n", - " [\n", - " 290.3965759277344,\n", - " 311.8982849121094,\n", - " 0.5908151865005493\n", - " ],\n", - " [\n", - " 305.3473205566406,\n", - " 314.0240783691406,\n", - " 0.6830025911331177\n", - " ],\n", - " [\n", - " 284.38623046875,\n", - " 267.3375244140625,\n", - " 0.938642144203186\n", - " ],\n", - " [\n", - " 183.94895935058594,\n", - " 312.9803771972656,\n", - " 0.5159981846809387\n", - " ],\n", - " [\n", - " 153.8936767578125,\n", - " 314.8314514160156,\n", - " 0.26622244715690613\n", - " ],\n", - " [\n", - " 181.40602111816406,\n", - " 314.3370056152344,\n", - " 0.5327624082565308\n", - " ],\n", - " [\n", - " 182.94410705566406,\n", - " 307.2097473144531,\n", - " 0.4217340648174286\n", - " ],\n", - " [\n", - " 310.5429992675781,\n", - " 273.63134765625,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.03759765625,\n", - " 275.5606994628906,\n", - " 0.6987367868423462\n", - " ],\n", - " [\n", - " 363.2841491699219,\n", - " 282.46881103515625,\n", - " 0.45098239183425903\n", - " ],\n", - " [\n", - " 183.90602111816406,\n", - " 316.1698913574219,\n", - " 0.39084556698799133\n", - " ],\n", - " [\n", - " 193.3860321044922,\n", - " 305.0615234375,\n", - " 0.507167398929596\n", - " ],\n", - " [\n", - " 355.57421875,\n", - " 341.30316162109375,\n", - " 0.4452424645423889\n", - " ],\n", - " [\n", - " 363.03021240234375,\n", - " 340.71435546875,\n", - " 0.5201398730278015\n", - " ],\n", - " [\n", - " 317.2238464355469,\n", - " 345.26666259765625,\n", - " 0.34468573331832886\n", - " ],\n", - " [\n", - " 332.4403991699219,\n", - " 356.6281433105469,\n", - " 0.46254584193229675\n", - " ],\n", - " [\n", - " 363.12640380859375,\n", - " 345.547119140625,\n", - " 0.42061465978622437\n", - " ],\n", - " [\n", - " 269.6493225097656,\n", - " 357.64892578125,\n", - " 0.4019133448600769\n", - " ],\n", - " [\n", - " 378.178466796875,\n", - " 356.5284729003906,\n", - " 0.4181462824344635\n", - " ],\n", - " [\n", - " 266.38238525390625,\n", - " 358.6065368652344,\n", - " 0.33007708191871643\n", - " ],\n", - " [\n", - " 599.5531005859375,\n", - " 377.76666259765625,\n", - " 0.629446804523468\n", - " ],\n", - " [\n", - " 184.41952514648438,\n", - " 208.114013671875,\n", - " 0.4009157419204712\n", - " ],\n", - " [\n", - " 519.3410034179688,\n", - " 390.22406005859375,\n", - " 0.3750861883163452\n", - " ],\n", - " [\n", - " 516.8319702148438,\n", - " 428.9804382324219,\n", - " 0.657153844833374\n", - " ],\n", - " [\n", - " 567.7550659179688,\n", - " 193.29896545410156,\n", - " 0.34581509232521057\n", - " ],\n", - " [\n", - " 509.476806640625,\n", - " 386.754150390625,\n", - " 0.3405891954898834\n", - " ],\n", - " [\n", - " 516.4321899414062,\n", - " 443.82684326171875,\n", - " 0.5484656095504761\n", - " ],\n", - " [\n", - " 594.040283203125,\n", - " 409.7172546386719,\n", - " 0.5296698212623596\n", - " ],\n", - " [\n", - " 508.0539855957031,\n", - " 380.4469299316406,\n", - " 0.20867124199867249\n", - " ],\n", - " [\n", - " 115.30363464355469,\n", - " 228.87652587890625,\n", - " 0.15687808394432068\n", - " ],\n", - " [\n", - " 573.0440063476562,\n", - " 393.09100341796875,\n", - " 0.39379948377609253\n", - " ],\n", - " [\n", - " 480.1328125,\n", - " 431.7356262207031,\n", - " 0.2691611051559448\n", - " ],\n", - " [\n", - " 550.718505859375,\n", - " 466.6239318847656,\n", - " 0.5681474804878235\n", - " ],\n", - " [\n", - " 469.20660400390625,\n", - " 413.4725036621094,\n", - " 0.39115187525749207\n", - " ],\n", - " [\n", - " 133.34088134765625,\n", - " 172.20274353027344,\n", - " 0.3368547558784485\n", - " ],\n", - " [\n", - " 459.3551025390625,\n", - " 403.5044250488281,\n", - " 0.45792460441589355\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.9642372,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588105.996339\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.0221846,\n", - " \"step\": 20,\n", - " \"pose\": [\n", - " [\n", - " 288.93731689453125,\n", - " 298.9955749511719,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.2713928222656,\n", - " 308.37408447265625,\n", - " 0.943423330783844\n", - " ],\n", - " [\n", - " 296.5384826660156,\n", - " 315.9530944824219,\n", - " 0.603498101234436\n", - " ],\n", - " [\n", - " 290.7826843261719,\n", - " 311.4992980957031,\n", - " 0.6115655899047852\n", - " ],\n", - " [\n", - " 304.9278259277344,\n", - " 313.83294677734375,\n", - " 0.7098742127418518\n", - " ],\n", - " [\n", - " 284.4320373535156,\n", - " 267.431884765625,\n", - " 0.9166838526725769\n", - " ],\n", - " [\n", - " 287.8583984375,\n", - " 231.46864318847656,\n", - " 0.4841695725917816\n", - " ],\n", - " [\n", - " 294.63043212890625,\n", - " 218.80035400390625,\n", - " 0.29108670353889465\n", - " ],\n", - " [\n", - " 181.39418029785156,\n", - " 314.2521057128906,\n", - " 0.5189660787582397\n", - " ],\n", - " [\n", - " 304.42047119140625,\n", - " 209.36367797851562,\n", - " 0.40350794792175293\n", - " ],\n", - " [\n", - " 310.4044494628906,\n", - " 273.7499694824219,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.916259765625,\n", - " 275.2131042480469,\n", - " 0.6535776853561401\n", - " ],\n", - " [\n", - " 364.2809753417969,\n", - " 282.39776611328125,\n", - " 0.40148383378982544\n", - " ],\n", - " [\n", - " 180.97071838378906,\n", - " 317.89715576171875,\n", - " 0.3581520915031433\n", - " ],\n", - " [\n", - " 187.54930114746094,\n", - " 302.6390686035156,\n", - " 0.4738801419734955\n", - " ],\n", - " [\n", - " 354.7167663574219,\n", - " 341.0120849609375,\n", - " 0.38925886154174805\n", - " ],\n", - " [\n", - " 362.65179443359375,\n", - " 341.0782775878906,\n", - " 0.5246103405952454\n", - " ],\n", - " [\n", - " 333.5454406738281,\n", - " 342.33709716796875,\n", - " 0.3726454973220825\n", - " ],\n", - " [\n", - " 331.2152099609375,\n", - " 355.3799133300781,\n", - " 0.5273229479789734\n", - " ],\n", - " [\n", - " 292.8047790527344,\n", - " 357.6714172363281,\n", - " 0.4529617428779602\n", - " ],\n", - " [\n", - " 269.3791198730469,\n", - " 357.7329406738281,\n", - " 0.33708542585372925\n", - " ],\n", - " [\n", - " 413.47406005859375,\n", - " 371.240478515625,\n", - " 0.38202741742134094\n", - " ],\n", - " [\n", - " 266.7149658203125,\n", - " 358.42596435546875,\n", - " 0.255717396736145\n", - " ],\n", - " [\n", - " 599.5459594726562,\n", - " 377.59088134765625,\n", - " 0.6171793937683105\n", - " ],\n", - " [\n", - " 182.58990478515625,\n", - " 208.6776580810547,\n", - " 0.3807677626609802\n", - " ],\n", - " [\n", - " 563.1406860351562,\n", - " 387.25885009765625,\n", - " 0.4175715744495392\n", - " ],\n", - " [\n", - " 517.3438110351562,\n", - " 431.8337097167969,\n", - " 0.5834364295005798\n", - " ],\n", - " [\n", - " 571.3803100585938,\n", - " 186.68905639648438,\n", - " 0.3315950930118561\n", - " ],\n", - " [\n", - " 572.1192016601562,\n", - " 190.96864318847656,\n", - " 0.44181355834007263\n", - " ],\n", - " [\n", - " 513.9149169921875,\n", - " 444.82080078125,\n", - " 0.5983200073242188\n", - " ],\n", - " [\n", - " 590.0543212890625,\n", - " 409.4712219238281,\n", - " 0.48646411299705505\n", - " ],\n", - " [\n", - " 474.74908447265625,\n", - " 422.23394775390625,\n", - " 0.2016405165195465\n", - " ],\n", - " [\n", - " 108.44601440429688,\n", - " 270.20269775390625,\n", - " 0.18030454218387604\n", - " ],\n", - " [\n", - " 572.8609008789062,\n", - " 393.09124755859375,\n", - " 0.4335196018218994\n", - " ],\n", - " [\n", - " 479.59942626953125,\n", - " 431.8648376464844,\n", - " 0.26634445786476135\n", - " ],\n", - " [\n", - " 551.6661376953125,\n", - " 466.70135498046875,\n", - " 0.5170703530311584\n", - " ],\n", - " [\n", - " 468.4554443359375,\n", - " 413.3636779785156,\n", - " 0.391674280166626\n", - " ],\n", - " [\n", - " 135.02671813964844,\n", - " 171.04122924804688,\n", - " 0.3395681083202362\n", - " ],\n", - " [\n", - " 460.0732727050781,\n", - " 402.4387512207031,\n", - " 0.43441158533096313\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588105.9977791,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.0241945\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.0568671,\n", - " \"step\": 21,\n", - " \"pose\": [\n", - " [\n", - " 289.0395202636719,\n", - " 299.1026611328125,\n", - " 1.0\n", - " ],\n", - " [\n", - " 294.0700378417969,\n", - " 308.541015625,\n", - " 0.9578058123588562\n", - " ],\n", - " [\n", - " 296.70440673828125,\n", - " 315.8837890625,\n", - " 0.6122352480888367\n", - " ],\n", - " [\n", - " 291.8658752441406,\n", - " 311.9002685546875,\n", - " 0.5979498624801636\n", - " ],\n", - " [\n", - " 306.3223876953125,\n", - " 312.81304931640625,\n", - " 0.7840145826339722\n", - " ],\n", - " [\n", - " 284.8127136230469,\n", - " 267.5986328125,\n", - " 0.9234873652458191\n", - " ],\n", - " [\n", - " 295.66680908203125,\n", - " 222.97425842285156,\n", - " 0.5263190865516663\n", - " ],\n", - " [\n", - " 296.760009765625,\n", - " 216.06723022460938,\n", - " 0.33805859088897705\n", - " ],\n", - " [\n", - " 182.27871704101562,\n", - " 313.2104187011719,\n", - " 0.4452279508113861\n", - " ],\n", - " [\n", - " 302.8914794921875,\n", - " 207.4759521484375,\n", - " 0.4147292673587799\n", - " ],\n", - " [\n", - " 310.67901611328125,\n", - " 273.2028503417969,\n", - " 1.0\n", - " ],\n", - " [\n", - " 364.8524169921875,\n", - " 274.2853088378906,\n", - " 0.6022342443466187\n", - " ],\n", - " [\n", - " 363.9833679199219,\n", - " 282.55401611328125,\n", - " 0.4224339723587036\n", - " ],\n", - " [\n", - " 181.81272888183594,\n", - " 307.6803283691406,\n", - " 0.26984354853630066\n", - " ],\n", - " [\n", - " 194.29331970214844,\n", - " 306.2869873046875,\n", - " 0.40715906023979187\n", - " ],\n", - " [\n", - " 351.9360046386719,\n", - " 343.8916015625,\n", - " 0.3862766921520233\n", - " ],\n", - " [\n", - " 363.7510986328125,\n", - " 342.71826171875,\n", - " 0.5607332587242126\n", - " ],\n", - " [\n", - " 333.4976501464844,\n", - " 343.6668701171875,\n", - " 0.35671743750572205\n", - " ],\n", - " [\n", - " 332.6026916503906,\n", - " 355.7337341308594,\n", - " 0.49907931685447693\n", - " ],\n", - " [\n", - " 281.2688903808594,\n", - " 357.4167175292969,\n", - " 0.5524068474769592\n", - " ],\n", - " [\n", - " 270.1759338378906,\n", - " 357.4426574707031,\n", - " 0.47422295808792114\n", - " ],\n", - " [\n", - " 379.6234436035156,\n", - " 356.0726318359375,\n", - " 0.4222455620765686\n", - " ],\n", - " [\n", - " 266.31121826171875,\n", - " 358.93865966796875,\n", - " 0.4061802625656128\n", - " ],\n", - " [\n", - " 598.6159057617188,\n", - " 377.72808837890625,\n", - " 0.5579972863197327\n", - " ],\n", - " [\n", - " 522.2592163085938,\n", - " 318.6231994628906,\n", - " 0.3378920257091522\n", - " ],\n", - " [\n", - " 518.2576293945312,\n", - " 390.44976806640625,\n", - " 0.41228482127189636\n", - " ],\n", - " [\n", - " 515.7354736328125,\n", - " 429.0234069824219,\n", - " 0.5634108185768127\n", - " ],\n", - " [\n", - " 443.417724609375,\n", - " 356.7914733886719,\n", - " 0.26663434505462646\n", - " ],\n", - " [\n", - " 507.91925048828125,\n", - " 388.6474304199219,\n", - " 0.35248884558677673\n", - " ],\n", - " [\n", - " 512.753173828125,\n", - " 449.7189636230469,\n", - " 0.578186571598053\n", - " ],\n", - " [\n", - " 581.30078125,\n", - " 414.9436340332031,\n", - " 0.4276328682899475\n", - " ],\n", - " [\n", - " 463.5183410644531,\n", - " 402.17388916015625,\n", - " 0.13629432022571564\n", - " ],\n", - " [\n", - " 111.8991470336914,\n", - " 278.3125915527344,\n", - " 0.151170015335083\n", - " ],\n", - " [\n", - " 575.8933715820312,\n", - " 398.4580993652344,\n", - " 0.40502646565437317\n", - " ],\n", - " [\n", - " 143.69412231445312,\n", - " 345.78521728515625,\n", - " 0.24476872384548187\n", - " ],\n", - " [\n", - " 143.1280517578125,\n", - " 373.794189453125,\n", - " 0.4513229429721832\n", - " ],\n", - " [\n", - " 468.4044189453125,\n", - " 411.8726806640625,\n", - " 0.37500157952308655\n", - " ],\n", - " [\n", - " 462.43206787109375,\n", - " 399.8568115234375,\n", - " 0.3841189444065094\n", - " ],\n", - " [\n", - " 461.3127136230469,\n", - " 403.23052978515625,\n", - " 0.5030932426452637\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.0300262,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.0578823\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.0853903,\n", - " \"step\": 22,\n", - " \"pose\": [\n", - " [\n", - " 289.0032653808594,\n", - " 299.5898132324219,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.6610412597656,\n", - " 308.364501953125,\n", - " 0.9171743392944336\n", - " ],\n", - " [\n", - " 297.53411865234375,\n", - " 315.0213928222656,\n", - " 0.5967923998832703\n", - " ],\n", - " [\n", - " 291.45599365234375,\n", - " 310.9009094238281,\n", - " 0.6069502234458923\n", - " ],\n", - " [\n", - " 305.15667724609375,\n", - " 312.3479919433594,\n", - " 0.7363993525505066\n", - " ],\n", - " [\n", - " 284.5779724121094,\n", - " 267.80059814453125,\n", - " 0.8958398699760437\n", - " ],\n", - " [\n", - " 287.5237121582031,\n", - " 231.7471923828125,\n", - " 0.5256384611129761\n", - " ],\n", - " [\n", - " 297.3427734375,\n", - " 217.41281127929688,\n", - " 0.32152754068374634\n", - " ],\n", - " [\n", - " 182.88433837890625,\n", - " 314.53692626953125,\n", - " 0.489521861076355\n", - " ],\n", - " [\n", - " 303.3013916015625,\n", - " 209.451171875,\n", - " 0.42159581184387207\n", - " ],\n", - " [\n", - " 310.30352783203125,\n", - " 273.47393798828125,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.04669189453125,\n", - " 275.4956970214844,\n", - " 0.6113792061805725\n", - " ],\n", - " [\n", - " 364.1795959472656,\n", - " 282.3038024902344,\n", - " 0.44920432567596436\n", - " ],\n", - " [\n", - " 185.60179138183594,\n", - " 312.93963623046875,\n", - " 0.2924720048904419\n", - " ],\n", - " [\n", - " 188.79063415527344,\n", - " 306.9405822753906,\n", - " 0.4584471881389618\n", - " ],\n", - " [\n", - " 355.7679443359375,\n", - " 340.5166320800781,\n", - " 0.4532307982444763\n", - " ],\n", - " [\n", - " 362.6515197753906,\n", - " 342.4863586425781,\n", - " 0.5490198731422424\n", - " ],\n", - " [\n", - " 334.1615905761719,\n", - " 344.5025939941406,\n", - " 0.33372119069099426\n", - " ],\n", - " [\n", - " 331.80181884765625,\n", - " 355.52691650390625,\n", - " 0.4720294773578644\n", - " ],\n", - " [\n", - " 290.5385437011719,\n", - " 358.4304504394531,\n", - " 0.4409693479537964\n", - " ],\n", - " [\n", - " 270.8581237792969,\n", - " 356.5791931152344,\n", - " 0.4198722839355469\n", - " ],\n", - " [\n", - " 424.2137145996094,\n", - " 375.2749328613281,\n", - " 0.4370681345462799\n", - " ],\n", - " [\n", - " 268.27886962890625,\n", - " 357.68585205078125,\n", - " 0.36227190494537354\n", - " ],\n", - " [\n", - " 599.3430786132812,\n", - " 377.6954650878906,\n", - " 0.5684486031532288\n", - " ],\n", - " [\n", - " 520.1165771484375,\n", - " 318.81927490234375,\n", - " 0.3776821196079254\n", - " ],\n", - " [\n", - " 562.5358276367188,\n", - " 387.04693603515625,\n", - " 0.3308621942996979\n", - " ],\n", - " [\n", - " 516.3358154296875,\n", - " 429.0692443847656,\n", - " 0.5608440637588501\n", - " ],\n", - " [\n", - " 566.9133911132812,\n", - " 193.52452087402344,\n", - " 0.33105629682540894\n", - " ],\n", - " [\n", - " 570.7525634765625,\n", - " 196.58692932128906,\n", - " 0.4755719006061554\n", - " ],\n", - " [\n", - " 512.5366821289062,\n", - " 448.55328369140625,\n", - " 0.5968109369277954\n", - " ],\n", - " [\n", - " 590.8397216796875,\n", - " 408.89471435546875,\n", - " 0.4935222268104553\n", - " ],\n", - " [\n", - " 110.8057632446289,\n", - " 271.4251403808594,\n", - " 0.16251139342784882\n", - " ],\n", - " [\n", - " 113.5406494140625,\n", - " 279.87841796875,\n", - " 0.38004982471466064\n", - " ],\n", - " [\n", - " 576.237548828125,\n", - " 397.9443664550781,\n", - " 0.3451719284057617\n", - " ],\n", - " [\n", - " 144.29734802246094,\n", - " 315.7247009277344,\n", - " 0.25869646668434143\n", - " ],\n", - " [\n", - " 549.8232421875,\n", - " 465.3925476074219,\n", - " 0.483270525932312\n", - " ],\n", - " [\n", - " 141.22862243652344,\n", - " 190.88340759277344,\n", - " 0.3440597653388977\n", - " ],\n", - " [\n", - " 462.27349853515625,\n", - " 407.5166931152344,\n", - " 0.39141225814819336\n", - " ],\n", - " [\n", - " 459.8392333984375,\n", - " 402.88421630859375,\n", - " 0.48188316822052\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.0603576,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.0853903\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.113898,\n", - " \"step\": 23,\n", - " \"pose\": [\n", - " [\n", - " 289.3703918457031,\n", - " 299.01654052734375,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.63714599609375,\n", - " 308.040771484375,\n", - " 0.9116361141204834\n", - " ],\n", - " [\n", - " 297.54144287109375,\n", - " 314.6742248535156,\n", - " 0.5906339287757874\n", - " ],\n", - " [\n", - " 292.0235290527344,\n", - " 310.41552734375,\n", - " 0.6293807029724121\n", - " ],\n", - " [\n", - " 305.7689514160156,\n", - " 312.5812072753906,\n", - " 0.6732482314109802\n", - " ],\n", - " [\n", - " 284.58123779296875,\n", - " 267.2427673339844,\n", - " 0.9067515134811401\n", - " ],\n", - " [\n", - " 183.6278076171875,\n", - " 312.8146667480469,\n", - " 0.5189109444618225\n", - " ],\n", - " [\n", - " 373.2416076660156,\n", - " 300.8122863769531,\n", - " 0.3220520615577698\n", - " ],\n", - " [\n", - " 182.9404754638672,\n", - " 312.4514465332031,\n", - " 0.5909151434898376\n", - " ],\n", - " [\n", - " 303.0302429199219,\n", - " 210.0631561279297,\n", - " 0.4749321937561035\n", - " ],\n", - " [\n", - " 310.4212951660156,\n", - " 273.31060791015625,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.5233459472656,\n", - " 275.4824523925781,\n", - " 0.5813355445861816\n", - " ],\n", - " [\n", - " 363.8905029296875,\n", - " 281.9700622558594,\n", - " 0.42637211084365845\n", - " ],\n", - " [\n", - " 185.95431518554688,\n", - " 311.6904296875,\n", - " 0.34200912714004517\n", - " ],\n", - " [\n", - " 192.42385864257812,\n", - " 305.3067932128906,\n", - " 0.4337136447429657\n", - " ],\n", - " [\n", - " 355.9394836425781,\n", - " 340.25689697265625,\n", - " 0.43801149725914\n", - " ],\n", - " [\n", - " 363.40325927734375,\n", - " 341.9284362792969,\n", - " 0.5555760264396667\n", - " ],\n", - " [\n", - " 314.46746826171875,\n", - " 342.5721130371094,\n", - " 0.35660916566848755\n", - " ],\n", - " [\n", - " 339.87835693359375,\n", - " 359.97998046875,\n", - " 0.4949004054069519\n", - " ],\n", - " [\n", - " 290.6855773925781,\n", - " 358.2529602050781,\n", - " 0.4353252649307251\n", - " ],\n", - " [\n", - " 270.9769287109375,\n", - " 356.5190124511719,\n", - " 0.40807199478149414\n", - " ],\n", - " [\n", - " 424.1128845214844,\n", - " 374.64202880859375,\n", - " 0.42985209822654724\n", - " ],\n", - " [\n", - " 267.4349670410156,\n", - " 357.9966125488281,\n", - " 0.3516447842121124\n", - " ],\n", - " [\n", - " 597.1614990234375,\n", - " 379.19207763671875,\n", - " 0.5612181425094604\n", - " ],\n", - " [\n", - " 200.91233825683594,\n", - " 229.2719268798828,\n", - " 0.3809609115123749\n", - " ],\n", - " [\n", - " 563.5918579101562,\n", - " 387.2767028808594,\n", - " 0.3711811602115631\n", - " ],\n", - " [\n", - " 516.4181518554688,\n", - " 428.2336120605469,\n", - " 0.6263866424560547\n", - " ],\n", - " [\n", - " 570.1748046875,\n", - " 189.33961486816406,\n", - " 0.3523023724555969\n", - " ],\n", - " [\n", - " 569.3735961914062,\n", - " 194.79763793945312,\n", - " 0.36297234892845154\n", - " ],\n", - " [\n", - " 512.5416259765625,\n", - " 448.44085693359375,\n", - " 0.5241063833236694\n", - " ],\n", - " [\n", - " 155.5992889404297,\n", - " 363.5351867675781,\n", - " 0.46883660554885864\n", - " ],\n", - " [\n", - " 508.84210205078125,\n", - " 377.6853332519531,\n", - " 0.1948014795780182\n", - " ],\n", - " [\n", - " 112.65641021728516,\n", - " 278.4239501953125,\n", - " 0.18293216824531555\n", - " ],\n", - " [\n", - " 572.6771850585938,\n", - " 392.94891357421875,\n", - " 0.39113080501556396\n", - " ],\n", - " [\n", - " 481.0816650390625,\n", - " 432.840087890625,\n", - " 0.22011691331863403\n", - " ],\n", - " [\n", - " 145.10130310058594,\n", - " 373.3096618652344,\n", - " 0.4219147264957428\n", - " ],\n", - " [\n", - " 468.0207824707031,\n", - " 413.1219787597656,\n", - " 0.3619706630706787\n", - " ],\n", - " [\n", - " 136.87318420410156,\n", - " 172.54913330078125,\n", - " 0.3659115135669708\n", - " ],\n", - " [\n", - " 459.65655517578125,\n", - " 403.489013671875,\n", - " 0.46539226174354553\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.0933394,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.113898\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.1507206,\n", - " \"step\": 24,\n", - " \"pose\": [\n", - " [\n", - " 289.14605712890625,\n", - " 298.5685729980469,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.7758483886719,\n", - " 308.03326416015625,\n", - " 0.9337582588195801\n", - " ],\n", - " [\n", - " 297.73248291015625,\n", - " 315.2346496582031,\n", - " 0.6327982544898987\n", - " ],\n", - " [\n", - " 291.4134216308594,\n", - " 311.653564453125,\n", - " 0.609774649143219\n", - " ],\n", - " [\n", - " 305.1743469238281,\n", - " 313.22369384765625,\n", - " 0.7303615808486938\n", - " ],\n", - " [\n", - " 285.11077880859375,\n", - " 267.58795166015625,\n", - " 0.9044018387794495\n", - " ],\n", - " [\n", - " 287.801025390625,\n", - " 230.8278045654297,\n", - " 0.5132544636726379\n", - " ],\n", - " [\n", - " 294.9792785644531,\n", - " 217.2659912109375,\n", - " 0.3110557496547699\n", - " ],\n", - " [\n", - " 182.70785522460938,\n", - " 312.7431640625,\n", - " 0.4303653836250305\n", - " ],\n", - " [\n", - " 302.67926025390625,\n", - " 209.3670196533203,\n", - " 0.3863958716392517\n", - " ],\n", - " [\n", - " 310.23931884765625,\n", - " 273.41265869140625,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.62945556640625,\n", - " 276.4300842285156,\n", - " 0.6256653070449829\n", - " ],\n", - " [\n", - " 363.4427185058594,\n", - " 281.51226806640625,\n", - " 0.4842453896999359\n", - " ],\n", - " [\n", - " 357.33184814453125,\n", - " 280.7920227050781,\n", - " 0.27625176310539246\n", - " ],\n", - " [\n", - " 194.59628295898438,\n", - " 306.9119873046875,\n", - " 0.41847968101501465\n", - " ],\n", - " [\n", - " 355.71160888671875,\n", - " 338.88134765625,\n", - " 0.44307053089141846\n", - " ],\n", - " [\n", - " 362.9523010253906,\n", - " 339.22442626953125,\n", - " 0.6132069230079651\n", - " ],\n", - " [\n", - " 317.2615051269531,\n", - " 345.85675048828125,\n", - " 0.35067668557167053\n", - " ],\n", - " [\n", - " 331.7022705078125,\n", - " 355.4823303222656,\n", - " 0.45521220564842224\n", - " ],\n", - " [\n", - " 363.580810546875,\n", - " 345.0674743652344,\n", - " 0.4948749542236328\n", - " ],\n", - " [\n", - " 269.8246765136719,\n", - " 356.2699279785156,\n", - " 0.5052472949028015\n", - " ],\n", - " [\n", - " 423.73480224609375,\n", - " 374.68438720703125,\n", - " 0.4521806836128235\n", - " ],\n", - " [\n", - " 266.7372131347656,\n", - " 357.82562255859375,\n", - " 0.44915807247161865\n", - " ],\n", - " [\n", - " 598.9044189453125,\n", - " 377.9191589355469,\n", - " 0.5668219923973083\n", - " ],\n", - " [\n", - " 185.02847290039062,\n", - " 209.2593536376953,\n", - " 0.29630619287490845\n", - " ],\n", - " [\n", - " 232.27960205078125,\n", - " 424.1368408203125,\n", - " 0.35069289803504944\n", - " ],\n", - " [\n", - " 516.3990478515625,\n", - " 429.19805908203125,\n", - " 0.6708835959434509\n", - " ],\n", - " [\n", - " 443.72869873046875,\n", - " 355.66790771484375,\n", - " 0.18464210629463196\n", - " ],\n", - " [\n", - " 509.17938232421875,\n", - " 388.7170104980469,\n", - " 0.4232410788536072\n", - " ],\n", - " [\n", - " 513.5040893554688,\n", - " 449.72918701171875,\n", - " 0.5325906872749329\n", - " ],\n", - " [\n", - " 583.7595825195312,\n", - " 413.1138916015625,\n", - " 0.4442688822746277\n", - " ],\n", - " [\n", - " 115.84835052490234,\n", - " 221.41104125976562,\n", - " 0.1638604700565338\n", - " ],\n", - " [\n", - " 116.47493743896484,\n", - " 283.7770690917969,\n", - " 0.2362251728773117\n", - " ],\n", - " [\n", - " 576.6185913085938,\n", - " 399.1842041015625,\n", - " 0.4103768765926361\n", - " ],\n", - " [\n", - " 144.26768493652344,\n", - " 343.4598693847656,\n", - " 0.2693617641925812\n", - " ],\n", - " [\n", - " 550.6482543945312,\n", - " 465.9261474609375,\n", - " 0.52559894323349\n", - " ],\n", - " [\n", - " 467.72491455078125,\n", - " 412.5487365722656,\n", - " 0.3213626742362976\n", - " ],\n", - " [\n", - " 466.04193115234375,\n", - " 410.77960205078125,\n", - " 0.32539424300193787\n", - " ],\n", - " [\n", - " 459.31622314453125,\n", - " 402.8010559082031,\n", - " 0.4295216500759125\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.1260552,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.1507206\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.1780674,\n", - " \"step\": 25,\n", - " \"pose\": [\n", - " [\n", - " 289.6149597167969,\n", - " 299.89166259765625,\n", - " 1.0\n", - " ],\n", - " [\n", - " 294.52008056640625,\n", - " 308.8827209472656,\n", - " 0.9231247305870056\n", - " ],\n", - " [\n", - " 298.28369140625,\n", - " 316.6026916503906,\n", - " 0.6108699440956116\n", - " ],\n", - " [\n", - " 292.314697265625,\n", - " 312.0814514160156,\n", - " 0.5867233276367188\n", - " ],\n", - " [\n", - " 306.8653564453125,\n", - " 313.03192138671875,\n", - " 0.7602759599685669\n", - " ],\n", - " [\n", - " 284.97998046875,\n", - " 268.31109619140625,\n", - " 0.9025271534919739\n", - " ],\n", - " [\n", - " 285.36627197265625,\n", - " 239.4760284423828,\n", - " 0.45559558272361755\n", - " ],\n", - " [\n", - " 278.97967529296875,\n", - " 251.67454528808594,\n", - " 0.3040803074836731\n", - " ],\n", - " [\n", - " 182.5161590576172,\n", - " 314.0823974609375,\n", - " 0.487441748380661\n", - " ],\n", - " [\n", - " 303.5965881347656,\n", - " 209.8799591064453,\n", - " 0.39406585693359375\n", - " ],\n", - " [\n", - " 310.803466796875,\n", - " 273.83941650390625,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.4359130859375,\n", - " 276.5120849609375,\n", - " 0.5850675106048584\n", - " ],\n", - " [\n", - " 364.39306640625,\n", - " 281.8566589355469,\n", - " 0.43770086765289307\n", - " ],\n", - " [\n", - " 184.96409606933594,\n", - " 312.2420349121094,\n", - " 0.32801908254623413\n", - " ],\n", - " [\n", - " 188.65463256835938,\n", - " 306.26580810546875,\n", - " 0.44451019167900085\n", - " ],\n", - " [\n", - " 356.0030517578125,\n", - " 340.06390380859375,\n", - " 0.393046498298645\n", - " ],\n", - " [\n", - " 361.8909606933594,\n", - " 340.0927429199219,\n", - " 0.5161962509155273\n", - " ],\n", - " [\n", - " 315.6810607910156,\n", - " 343.1770935058594,\n", - " 0.37498360872268677\n", - " ],\n", - " [\n", - " 330.904052734375,\n", - " 355.0949401855469,\n", - " 0.5339792370796204\n", - " ],\n", - " [\n", - " 367.3807067871094,\n", - " 343.9208679199219,\n", - " 0.41494372487068176\n", - " ],\n", - " [\n", - " 270.63043212890625,\n", - " 356.5071716308594,\n", - " 0.39088016748428345\n", - " ],\n", - " [\n", - " 424.5980529785156,\n", - " 377.47625732421875,\n", - " 0.40465661883354187\n", - " ],\n", - " [\n", - " 267.8541259765625,\n", - " 358.0108642578125,\n", - " 0.33039915561676025\n", - " ],\n", - " [\n", - " 599.1412353515625,\n", - " 378.35345458984375,\n", - " 0.5644627213478088\n", - " ],\n", - " [\n", - " 184.03726196289062,\n", - " 209.42005920410156,\n", - " 0.459592342376709\n", - " ],\n", - " [\n", - " 511.7866516113281,\n", - " 408.0044860839844,\n", - " 0.3771219551563263\n", - " ],\n", - " [\n", - " 516.994873046875,\n", - " 430.9440612792969,\n", - " 0.5924205780029297\n", - " ],\n", - " [\n", - " 443.1100158691406,\n", - " 355.68621826171875,\n", - " 0.2457418441772461\n", - " ],\n", - " [\n", - " 499.76837158203125,\n", - " 422.5597839355469,\n", - " 0.24084235727787018\n", - " ],\n", - " [\n", - " 511.1026611328125,\n", - " 445.35107421875,\n", - " 0.5383387804031372\n", - " ],\n", - " [\n", - " 589.8642578125,\n", - " 408.1378173828125,\n", - " 0.456050306558609\n", - " ],\n", - " [\n", - " 507.7047119140625,\n", - " 379.2082214355469,\n", - " 0.16193071007728577\n", - " ],\n", - " [\n", - " 112.13590240478516,\n", - " 276.6860656738281,\n", - " 0.282148540019989\n", - " ],\n", - " [\n", - " 573.1347045898438,\n", - " 393.6588439941406,\n", - " 0.4297298192977905\n", - " ],\n", - " [\n", - " 482.9963073730469,\n", - " 432.75811767578125,\n", - " 0.2714385986328125\n", - " ],\n", - " [\n", - " 550.9261474609375,\n", - " 467.3409118652344,\n", - " 0.5166769623756409\n", - " ],\n", - " [\n", - " 468.4465026855469,\n", - " 413.2013244628906,\n", - " 0.42883676290512085\n", - " ],\n", - " [\n", - " 133.96446228027344,\n", - " 171.50291442871094,\n", - " 0.35597431659698486\n", - " ],\n", - " [\n", - " 465.5216369628906,\n", - " 407.3671875,\n", - " 0.4744454324245453\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.1553848,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.1780674\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.2132318,\n", - " \"step\": 26,\n", - " \"pose\": [\n", - " [\n", - " 289.6811218261719,\n", - " 299.3060607910156,\n", - " 1.0\n", - " ],\n", - " [\n", - " 294.1546630859375,\n", - " 308.6109924316406,\n", - " 0.9218483567237854\n", - " ],\n", - " [\n", - " 297.23516845703125,\n", - " 316.22552490234375,\n", - " 0.5849137902259827\n", - " ],\n", - " [\n", - " 292.8453369140625,\n", - " 312.3377990722656,\n", - " 0.5977075695991516\n", - " ],\n", - " [\n", - " 306.42193603515625,\n", - " 313.16546630859375,\n", - " 0.73295658826828\n", - " ],\n", - " [\n", - " 285.2742919921875,\n", - " 268.0978698730469,\n", - " 0.8919780254364014\n", - " ],\n", - " [\n", - " 285.22930908203125,\n", - " 240.1131134033203,\n", - " 0.48513516783714294\n", - " ],\n", - " [\n", - " 278.72900390625,\n", - " 252.45925903320312,\n", - " 0.30949145555496216\n", - " ],\n", - " [\n", - " 182.2201690673828,\n", - " 313.8691711425781,\n", - " 0.4198264181613922\n", - " ],\n", - " [\n", - " 302.0194091796875,\n", - " 209.4342803955078,\n", - " 0.3796064555644989\n", - " ],\n", - " [\n", - " 311.20037841796875,\n", - " 273.72052001953125,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.5647888183594,\n", - " 275.8450622558594,\n", - " 0.551364004611969\n", - " ],\n", - " [\n", - " 361.8849792480469,\n", - " 280.7410583496094,\n", - " 0.4334920346736908\n", - " ],\n", - " [\n", - " 185.7280731201172,\n", - " 312.75677490234375,\n", - " 0.29518699645996094\n", - " ],\n", - " [\n", - " 188.90249633789062,\n", - " 306.63177490234375,\n", - " 0.43662378191947937\n", - " ],\n", - " [\n", - " 355.60833740234375,\n", - " 340.0720520019531,\n", - " 0.4073914885520935\n", - " ],\n", - " [\n", - " 362.4900817871094,\n", - " 339.8585205078125,\n", - " 0.5256603956222534\n", - " ],\n", - " [\n", - " 310.6485900878906,\n", - " 346.7264404296875,\n", - " 0.3758726119995117\n", - " ],\n", - " [\n", - " 331.7028503417969,\n", - " 357.2618408203125,\n", - " 0.5182921886444092\n", - " ],\n", - " [\n", - " 282.8375244140625,\n", - " 356.63385009765625,\n", - " 0.47025105357170105\n", - " ],\n", - " [\n", - " 271.1107482910156,\n", - " 356.7911376953125,\n", - " 0.49355366826057434\n", - " ],\n", - " [\n", - " 424.5859680175781,\n", - " 375.9132080078125,\n", - " 0.3878205716609955\n", - " ],\n", - " [\n", - " 267.3672180175781,\n", - " 358.4584655761719,\n", - " 0.4485253393650055\n", - " ],\n", - " [\n", - " 598.8714599609375,\n", - " 377.88165283203125,\n", - " 0.5623369216918945\n", - " ],\n", - " [\n", - " 184.80422973632812,\n", - " 207.90528869628906,\n", - " 0.4335136115550995\n", - " ],\n", - " [\n", - " 513.0913696289062,\n", - " 407.55938720703125,\n", - " 0.34691154956817627\n", - " ],\n", - " [\n", - " 517.757080078125,\n", - " 432.6747131347656,\n", - " 0.5728744864463806\n", - " ],\n", - " [\n", - " 163.63232421875,\n", - " 212.3854217529297,\n", - " 0.32033267617225647\n", - " ],\n", - " [\n", - " 570.7784423828125,\n", - " 193.9116973876953,\n", - " 0.40288540720939636\n", - " ],\n", - " [\n", - " 513.4071655273438,\n", - " 445.5836486816406,\n", - " 0.6099948883056641\n", - " ],\n", - " [\n", - " 590.9609985351562,\n", - " 408.857421875,\n", - " 0.4939699172973633\n", - " ],\n", - " [\n", - " 476.909423828125,\n", - " 420.9211120605469,\n", - " 0.17517627775669098\n", - " ],\n", - " [\n", - " 111.49516296386719,\n", - " 278.0699462890625,\n", - " 0.20062971115112305\n", - " ],\n", - " [\n", - " 572.727294921875,\n", - " 393.2834167480469,\n", - " 0.39421916007995605\n", - " ],\n", - " [\n", - " 481.32647705078125,\n", - " 431.9410400390625,\n", - " 0.2601860463619232\n", - " ],\n", - " [\n", - " 511.23797607421875,\n", - " 458.18768310546875,\n", - " 0.5182275772094727\n", - " ],\n", - " [\n", - " 468.54132080078125,\n", - " 413.6605529785156,\n", - " 0.40847253799438477\n", - " ],\n", - " [\n", - " 464.9053649902344,\n", - " 410.059326171875,\n", - " 0.35141322016716003\n", - " ],\n", - " [\n", - " 459.9055480957031,\n", - " 403.2681579589844,\n", - " 0.4966220259666443\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.1875935,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.2132318\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.2483606,\n", - " \"step\": 27,\n", - " \"pose\": [\n", - " [\n", - " 289.65423583984375,\n", - " 298.561767578125,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.9566955566406,\n", - " 308.3065490722656,\n", - " 0.9470576047897339\n", - " ],\n", - " [\n", - " 297.505859375,\n", - " 314.407958984375,\n", - " 0.6088659763336182\n", - " ],\n", - " [\n", - " 291.20574951171875,\n", - " 310.8966064453125,\n", - " 0.607652485370636\n", - " ],\n", - " [\n", - " 306.73492431640625,\n", - " 312.7744445800781,\n", - " 0.7566508054733276\n", - " ],\n", - " [\n", - " 285.1037902832031,\n", - " 267.3839416503906,\n", - " 0.906174898147583\n", - " ],\n", - " [\n", - " 185.3070526123047,\n", - " 311.79327392578125,\n", - " 0.5088176727294922\n", - " ],\n", - " [\n", - " 154.76300048828125,\n", - " 314.7828674316406,\n", - " 0.29141828417778015\n", - " ],\n", - " [\n", - " 182.6195068359375,\n", - " 312.42791748046875,\n", - " 0.5560340285301208\n", - " ],\n", - " [\n", - " 304.5721435546875,\n", - " 209.04302978515625,\n", - " 0.4600820243358612\n", - " ],\n", - " [\n", - " 310.9137878417969,\n", - " 273.2908935546875,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.1856994628906,\n", - " 276.47381591796875,\n", - " 0.5621199011802673\n", - " ],\n", - " [\n", - " 363.20672607421875,\n", - " 281.5467224121094,\n", - " 0.427944153547287\n", - " ],\n", - " [\n", - " 181.0316619873047,\n", - " 311.6895446777344,\n", - " 0.3624846041202545\n", - " ],\n", - " [\n", - " 188.46315002441406,\n", - " 304.592529296875,\n", - " 0.5054963231086731\n", - " ],\n", - " [\n", - " 353.033447265625,\n", - " 342.5238037109375,\n", - " 0.40231525897979736\n", - " ],\n", - " [\n", - " 362.5194091796875,\n", - " 339.60711669921875,\n", - " 0.6008350849151611\n", - " ],\n", - " [\n", - " 317.26544189453125,\n", - " 342.3359069824219,\n", - " 0.3960641920566559\n", - " ],\n", - " [\n", - " 331.4060974121094,\n", - " 356.75445556640625,\n", - " 0.5937938690185547\n", - " ],\n", - " [\n", - " 293.347412109375,\n", - " 357.84515380859375,\n", - " 0.4450031816959381\n", - " ],\n", - " [\n", - " 269.11407470703125,\n", - " 357.3741455078125,\n", - " 0.4057061970233917\n", - " ],\n", - " [\n", - " 378.8157653808594,\n", - " 355.3031921386719,\n", - " 0.39472222328186035\n", - " ],\n", - " [\n", - " 265.99078369140625,\n", - " 358.6902160644531,\n", - " 0.3461160957813263\n", - " ],\n", - " [\n", - " 598.9271850585938,\n", - " 377.77618408203125,\n", - " 0.5730097889900208\n", - " ],\n", - " [\n", - " 184.35238647460938,\n", - " 209.6236572265625,\n", - " 0.3743739724159241\n", - " ],\n", - " [\n", - " 513.5267333984375,\n", - " 407.8452453613281,\n", - " 0.394991397857666\n", - " ],\n", - " [\n", - " 517.43359375,\n", - " 432.714111328125,\n", - " 0.6186484694480896\n", - " ],\n", - " [\n", - " 566.1676635742188,\n", - " 200.06082153320312,\n", - " 0.36133381724357605\n", - " ],\n", - " [\n", - " 570.642333984375,\n", - " 203.29977416992188,\n", - " 0.2848742604255676\n", - " ],\n", - " [\n", - " 513.6658935546875,\n", - " 447.7688903808594,\n", - " 0.566241979598999\n", - " ],\n", - " [\n", - " 590.916259765625,\n", - " 409.0061950683594,\n", - " 0.5227742791175842\n", - " ],\n", - " [\n", - " 476.4045104980469,\n", - " 421.7107849121094,\n", - " 0.23629191517829895\n", - " ],\n", - " [\n", - " 109.81532287597656,\n", - " 272.0874938964844,\n", - " 0.2973298132419586\n", - " ],\n", - " [\n", - " 572.2440185546875,\n", - " 393.4117431640625,\n", - " 0.4230390191078186\n", - " ],\n", - " [\n", - " 481.6334228515625,\n", - " 434.35992431640625,\n", - " 0.20489251613616943\n", - " ],\n", - " [\n", - " 511.52740478515625,\n", - " 458.0361633300781,\n", - " 0.47707173228263855\n", - " ],\n", - " [\n", - " 468.145751953125,\n", - " 413.9035339355469,\n", - " 0.4124965965747833\n", - " ],\n", - " [\n", - " 465.691650390625,\n", - " 411.6900329589844,\n", - " 0.3242393732070923\n", - " ],\n", - " [\n", - " 459.133544921875,\n", - " 403.29656982421875,\n", - " 0.4457142651081085\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.2229764,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.2503667\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.2815137,\n", - " \"step\": 28,\n", - " \"pose\": [\n", - " [\n", - " 289.7947998046875,\n", - " 299.4747314453125,\n", - " 1.0\n", - " ],\n", - " [\n", - " 294.1377868652344,\n", - " 308.9644470214844,\n", - " 0.9098001718521118\n", - " ],\n", - " [\n", - " 297.0553894042969,\n", - " 316.2694396972656,\n", - " 0.5839970111846924\n", - " ],\n", - " [\n", - " 292.7187194824219,\n", - " 312.2520446777344,\n", - " 0.5812930464744568\n", - " ],\n", - " [\n", - " 306.51007080078125,\n", - " 313.4143981933594,\n", - " 0.7014763355255127\n", - " ],\n", - " [\n", - " 285.4446105957031,\n", - " 267.66400146484375,\n", - " 0.885840654373169\n", - " ],\n", - " [\n", - " 287.2601623535156,\n", - " 232.3909912109375,\n", - " 0.47544583678245544\n", - " ],\n", - " [\n", - " 279.16656494140625,\n", - " 250.76365661621094,\n", - " 0.29200607538223267\n", - " ],\n", - " [\n", - " 182.51055908203125,\n", - " 314.830322265625,\n", - " 0.4986189305782318\n", - " ],\n", - " [\n", - " 304.1475524902344,\n", - " 207.91217041015625,\n", - " 0.5152255892753601\n", - " ],\n", - " [\n", - " 311.350830078125,\n", - " 273.5013122558594,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.06219482421875,\n", - " 276.67120361328125,\n", - " 0.5790920853614807\n", - " ],\n", - " [\n", - " 362.5161437988281,\n", - " 283.22723388671875,\n", - " 0.4104999601840973\n", - " ],\n", - " [\n", - " 181.13710021972656,\n", - " 312.584716796875,\n", - " 0.29986241459846497\n", - " ],\n", - " [\n", - " 188.63394165039062,\n", - " 303.74395751953125,\n", - " 0.45568570494651794\n", - " ],\n", - " [\n", - " 357.4764099121094,\n", - " 339.7001953125,\n", - " 0.4089646339416504\n", - " ],\n", - " [\n", - " 362.9626770019531,\n", - " 339.128662109375,\n", - " 0.4946100115776062\n", - " ],\n", - " [\n", - " 311.04779052734375,\n", - " 346.371826171875,\n", - " 0.39371228218078613\n", - " ],\n", - " [\n", - " 339.0541687011719,\n", - " 359.549560546875,\n", - " 0.5586241483688354\n", - " ],\n", - " [\n", - " 283.3900146484375,\n", - " 356.6086120605469,\n", - " 0.4299786686897278\n", - " ],\n", - " [\n", - " 270.4642333984375,\n", - " 357.4127502441406,\n", - " 0.40563082695007324\n", - " ],\n", - " [\n", - " 423.99005126953125,\n", - " 375.4238586425781,\n", - " 0.43916311860084534\n", - " ],\n", - " [\n", - " 268.08233642578125,\n", - " 358.42919921875,\n", - " 0.3316787779331207\n", - " ],\n", - " [\n", - " 598.7156982421875,\n", - " 377.3929138183594,\n", - " 0.5510212182998657\n", - " ],\n", - " [\n", - " 182.943115234375,\n", - " 208.30332946777344,\n", - " 0.44296297430992126\n", - " ],\n", - " [\n", - " 564.0025024414062,\n", - " 386.8399353027344,\n", - " 0.39091676473617554\n", - " ],\n", - " [\n", - " 517.2132568359375,\n", - " 432.1662292480469,\n", - " 0.6276928186416626\n", - " ],\n", - " [\n", - " 570.1396484375,\n", - " 190.4938507080078,\n", - " 0.2375359982252121\n", - " ],\n", - " [\n", - " 492.12896728515625,\n", - " 413.6061096191406,\n", - " 0.3405477702617645\n", - " ],\n", - " [\n", - " 512.7205200195312,\n", - " 450.4252624511719,\n", - " 0.6235648393630981\n", - " ],\n", - " [\n", - " 590.1025390625,\n", - " 409.0594787597656,\n", - " 0.5204903483390808\n", - " ],\n", - " [\n", - " 476.5130310058594,\n", - " 422.0225524902344,\n", - " 0.14199496805667877\n", - " ],\n", - " [\n", - " 110.05477142333984,\n", - " 271.470703125,\n", - " 0.22186370193958282\n", - " ],\n", - " [\n", - " 571.6903686523438,\n", - " 393.01318359375,\n", - " 0.4487222135066986\n", - " ],\n", - " [\n", - " 142.70831298828125,\n", - " 346.6425476074219,\n", - " 0.2570325434207916\n", - " ],\n", - " [\n", - " 511.7834167480469,\n", - " 456.3761901855469,\n", - " 0.5804262161254883\n", - " ],\n", - " [\n", - " 468.8331298828125,\n", - " 413.937744140625,\n", - " 0.36150258779525757\n", - " ],\n", - " [\n", - " 465.01104736328125,\n", - " 410.2015380859375,\n", - " 0.3218684196472168\n", - " ],\n", - " [\n", - " 460.2878723144531,\n", - " 402.61614990234375,\n", - " 0.44286683201789856\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.2539957,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.2815137\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.313648,\n", - " \"step\": 29,\n", - " \"pose\": [\n", - " [\n", - " 290.2019958496094,\n", - " 299.82733154296875,\n", - " 1.0\n", - " ],\n", - " [\n", - " 294.038818359375,\n", - " 309.61590576171875,\n", - " 0.8994060754776001\n", - " ],\n", - " [\n", - " 297.1882019042969,\n", - " 316.2828369140625,\n", - " 0.580872118473053\n", - " ],\n", - " [\n", - " 292.24969482421875,\n", - " 311.94366455078125,\n", - " 0.553147554397583\n", - " ],\n", - " [\n", - " 305.8408508300781,\n", - " 312.88555908203125,\n", - " 0.7357133626937866\n", - " ],\n", - " [\n", - " 285.2379455566406,\n", - " 267.34576416015625,\n", - " 0.8884855508804321\n", - " ],\n", - " [\n", - " 288.9740905761719,\n", - " 231.48561096191406,\n", - " 0.5197221636772156\n", - " ],\n", - " [\n", - " 299.1871032714844,\n", - " 218.01211547851562,\n", - " 0.3429694175720215\n", - " ],\n", - " [\n", - " 181.70339965820312,\n", - " 317.9532165527344,\n", - " 0.46619749069213867\n", - " ],\n", - " [\n", - " 304.53472900390625,\n", - " 208.62942504882812,\n", - " 0.45269933342933655\n", - " ],\n", - " [\n", - " 311.17626953125,\n", - " 274.0134582519531,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.29833984375,\n", - " 278.41729736328125,\n", - " 0.5592008233070374\n", - " ],\n", - " [\n", - " 363.13763427734375,\n", - " 281.8921813964844,\n", - " 0.45556432008743286\n", - " ],\n", - " [\n", - " 357.6162414550781,\n", - " 281.98394775390625,\n", - " 0.30312928557395935\n", - " ],\n", - " [\n", - " 187.75889587402344,\n", - " 305.17547607421875,\n", - " 0.44207459688186646\n", - " ],\n", - " [\n", - " 355.2580261230469,\n", - " 341.6760559082031,\n", - " 0.38897281885147095\n", - " ],\n", - " [\n", - " 363.04852294921875,\n", - " 339.8902893066406,\n", - " 0.5060890316963196\n", - " ],\n", - " [\n", - " 314.8399963378906,\n", - " 341.91680908203125,\n", - " 0.40578165650367737\n", - " ],\n", - " [\n", - " 338.7541198730469,\n", - " 359.4593200683594,\n", - " 0.548823893070221\n", - " ],\n", - " [\n", - " 366.559814453125,\n", - " 343.24029541015625,\n", - " 0.4393633306026459\n", - " ],\n", - " [\n", - " 270.7618103027344,\n", - " 357.1903991699219,\n", - " 0.4302641451358795\n", - " ],\n", - " [\n", - " 379.385498046875,\n", - " 355.3668212890625,\n", - " 0.45166778564453125\n", - " ],\n", - " [\n", - " 268.61431884765625,\n", - " 357.9519348144531,\n", - " 0.36507532000541687\n", - " ],\n", - " [\n", - " 597.203125,\n", - " 379.3829345703125,\n", - " 0.587417721748352\n", - " ],\n", - " [\n", - " 182.3574676513672,\n", - " 208.2609100341797,\n", - " 0.39093875885009766\n", - " ],\n", - " [\n", - " 512.343994140625,\n", - " 407.104736328125,\n", - " 0.3616585433483124\n", - " ],\n", - " [\n", - " 517.728759765625,\n", - " 431.97174072265625,\n", - " 0.5530462265014648\n", - " ],\n", - " [\n", - " 567.7471313476562,\n", - " 195.76194763183594,\n", - " 0.41424036026000977\n", - " ],\n", - " [\n", - " 568.5385131835938,\n", - " 196.24961853027344,\n", - " 0.3196858763694763\n", - " ],\n", - " [\n", - " 512.130615234375,\n", - " 450.2174072265625,\n", - " 0.6944491863250732\n", - " ],\n", - " [\n", - " 154.75747680664062,\n", - " 365.33026123046875,\n", - " 0.46970120072364807\n", - " ],\n", - " [\n", - " 476.37847900390625,\n", - " 421.13104248046875,\n", - " 0.2024911344051361\n", - " ],\n", - " [\n", - " 109.27305603027344,\n", - " 276.56787109375,\n", - " 0.22318890690803528\n", - " ],\n", - " [\n", - " 573.2276611328125,\n", - " 393.43096923828125,\n", - " 0.45069870352745056\n", - " ],\n", - " [\n", - " 144.93081665039062,\n", - " 343.8260803222656,\n", - " 0.26525822281837463\n", - " ],\n", - " [\n", - " 510.0262145996094,\n", - " 457.258544921875,\n", - " 0.6254387497901917\n", - " ],\n", - " [\n", - " 468.2686767578125,\n", - " 412.5702209472656,\n", - " 0.40957126021385193\n", - " ],\n", - " [\n", - " 464.2906188964844,\n", - " 410.6726989746094,\n", - " 0.32647469639778137\n", - " ],\n", - " [\n", - " 460.0201721191406,\n", - " 403.037841796875,\n", - " 0.41243088245391846\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.285935,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.313648\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.3619356,\n", - " \"step\": 30,\n", - " \"pose\": [\n", - " [\n", - " 290.1323547363281,\n", - " 299.7147521972656,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.99139404296875,\n", - " 309.4753112792969,\n", - " 0.8872848749160767\n", - " ],\n", - " [\n", - " 297.3484802246094,\n", - " 315.24835205078125,\n", - " 0.5460883975028992\n", - " ],\n", - " [\n", - " 291.71209716796875,\n", - " 310.8031311035156,\n", - " 0.5654347538948059\n", - " ],\n", - " [\n", - " 305.3967590332031,\n", - " 312.7275085449219,\n", - " 0.7120907306671143\n", - " ],\n", - " [\n", - " 285.19940185546875,\n", - " 267.3405456542969,\n", - " 0.8942697048187256\n", - " ],\n", - " [\n", - " 289.3546447753906,\n", - " 231.18653869628906,\n", - " 0.483479768037796\n", - " ],\n", - " [\n", - " 299.21112060546875,\n", - " 218.4276580810547,\n", - " 0.4001926779747009\n", - " ],\n", - " [\n", - " 181.76585388183594,\n", - " 313.5556945800781,\n", - " 0.46045035123825073\n", - " ],\n", - " [\n", - " 309.8026428222656,\n", - " 210.481689453125,\n", - " 0.5369957089424133\n", - " ],\n", - " [\n", - " 310.97064208984375,\n", - " 273.8341369628906,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.9340515136719,\n", - " 277.03912353515625,\n", - " 0.6095738410949707\n", - " ],\n", - " [\n", - " 362.3292541503906,\n", - " 282.6873474121094,\n", - " 0.5387850999832153\n", - " ],\n", - " [\n", - " 183.47048950195312,\n", - " 311.634521484375,\n", - " 0.30759158730506897\n", - " ],\n", - " [\n", - " 193.97259521484375,\n", - " 306.5625,\n", - " 0.4332640469074249\n", - " ],\n", - " [\n", - " 355.8443908691406,\n", - " 341.6749267578125,\n", - " 0.3820899724960327\n", - " ],\n", - " [\n", - " 362.45452880859375,\n", - " 340.09088134765625,\n", - " 0.493086040019989\n", - " ],\n", - " [\n", - " 315.4454650878906,\n", - " 343.0971984863281,\n", - " 0.3856670558452606\n", - " ],\n", - " [\n", - " 339.348388671875,\n", - " 359.60870361328125,\n", - " 0.55845707654953\n", - " ],\n", - " [\n", - " 284.3851623535156,\n", - " 356.5133972167969,\n", - " 0.4924909174442291\n", - " ],\n", - " [\n", - " 271.4208679199219,\n", - " 357.4637451171875,\n", - " 0.3702590763568878\n", - " ],\n", - " [\n", - " 423.9642028808594,\n", - " 375.77801513671875,\n", - " 0.42082488536834717\n", - " ],\n", - " [\n", - " 269.3265075683594,\n", - " 358.4918212890625,\n", - " 0.3108918368816376\n", - " ],\n", - " [\n", - " 595.7271728515625,\n", - " 378.74505615234375,\n", - " 0.6383695006370544\n", - " ],\n", - " [\n", - " 182.5079803466797,\n", - " 209.4701690673828,\n", - " 0.3769323527812958\n", - " ],\n", - " [\n", - " 563.5107421875,\n", - " 388.12860107421875,\n", - " 0.34879517555236816\n", - " ],\n", - " [\n", - " 516.7620239257812,\n", - " 429.2193908691406,\n", - " 0.6218969225883484\n", - " ],\n", - " [\n", - " 443.3829650878906,\n", - " 357.2695007324219,\n", - " 0.2294222116470337\n", - " ],\n", - " [\n", - " 508.9411926269531,\n", - " 390.8059387207031,\n", - " 0.27742016315460205\n", - " ],\n", - " [\n", - " 513.4525756835938,\n", - " 451.084228515625,\n", - " 0.588313102722168\n", - " ],\n", - " [\n", - " 590.62939453125,\n", - " 409.5919494628906,\n", - " 0.49015718698501587\n", - " ],\n", - " [\n", - " 505.6385498046875,\n", - " 379.8114929199219,\n", - " 0.16522188484668732\n", - " ],\n", - " [\n", - " 108.85206604003906,\n", - " 271.24273681640625,\n", - " 0.2543381452560425\n", - " ],\n", - " [\n", - " 573.4502563476562,\n", - " 393.5893249511719,\n", - " 0.49820849299430847\n", - " ],\n", - " [\n", - " 142.85484313964844,\n", - " 314.2268981933594,\n", - " 0.19713541865348816\n", - " ],\n", - " [\n", - " 549.9910278320312,\n", - " 465.56768798828125,\n", - " 0.5062677264213562\n", - " ],\n", - " [\n", - " 469.6101379394531,\n", - " 412.96905517578125,\n", - " 0.3788171112537384\n", - " ],\n", - " [\n", - " 465.2295837402344,\n", - " 411.72161865234375,\n", - " 0.343596875667572\n", - " ],\n", - " [\n", - " 460.37811279296875,\n", - " 402.3249206542969,\n", - " 0.46701255440711975\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.3327684,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.3619356\n", - "}\n", - "{\n", - " \"kind\": \"pose\",\n", - " \"raw\": {\n", - " \"type\": \"pose\",\n", - " \"timestamp\": 1783588106.3911848,\n", - " \"step\": 31,\n", - " \"pose\": [\n", - " [\n", - " 289.88616943359375,\n", - " 299.245849609375,\n", - " 1.0\n", - " ],\n", - " [\n", - " 293.7210693359375,\n", - " 308.9605407714844,\n", - " 0.8916722536087036\n", - " ],\n", - " [\n", - " 297.2877502441406,\n", - " 315.5369873046875,\n", - " 0.5746937394142151\n", - " ],\n", - " [\n", - " 292.2024230957031,\n", - " 311.0600891113281,\n", - " 0.5595524907112122\n", - " ],\n", - " [\n", - " 305.3230285644531,\n", - " 313.09588623046875,\n", - " 0.7339632511138916\n", - " ],\n", - " [\n", - " 285.2215576171875,\n", - " 266.96453857421875,\n", - " 0.894293487071991\n", - " ],\n", - " [\n", - " 297.3047180175781,\n", - " 221.42364501953125,\n", - " 0.5297285914421082\n", - " ],\n", - " [\n", - " 295.8182067871094,\n", - " 216.30775451660156,\n", - " 0.38408026099205017\n", - " ],\n", - " [\n", - " 183.77606201171875,\n", - " 316.7649841308594,\n", - " 0.4275979697704315\n", - " ],\n", - " [\n", - " 308.4361572265625,\n", - " 210.48973083496094,\n", - " 0.5560570359230042\n", - " ],\n", - " [\n", - " 311.0546875,\n", - " 273.9168395996094,\n", - " 1.0\n", - " ],\n", - " [\n", - " 363.8443298339844,\n", - " 277.56488037109375,\n", - " 0.632280170917511\n", - " ],\n", - " [\n", - " 363.19598388671875,\n", - " 283.2601013183594,\n", - " 0.5504708290100098\n", - " ],\n", - " [\n", - " 360.5338439941406,\n", - " 282.9017028808594,\n", - " 0.3014270067214966\n", - " ],\n", - " [\n", - " 187.5973663330078,\n", - " 304.839111328125,\n", - " 0.4563285708427429\n", - " ],\n", - " [\n", - " 355.62762451171875,\n", - " 341.2684020996094,\n", - " 0.4316878914833069\n", - " ],\n", - " [\n", - " 362.1797180175781,\n", - " 340.63848876953125,\n", - " 0.523749828338623\n", - " ],\n", - " [\n", - " 310.7569580078125,\n", - " 347.2984313964844,\n", - " 0.3826305568218231\n", - " ],\n", - " [\n", - " 338.3757629394531,\n", - " 360.44317626953125,\n", - " 0.5659022927284241\n", - " ],\n", - " [\n", - " 367.23004150390625,\n", - " 344.0614929199219,\n", - " 0.43424880504608154\n", - " ],\n", - " [\n", - " 272.1240234375,\n", - " 355.71612548828125,\n", - " 0.5028250217437744\n", - " ],\n", - " [\n", - " 379.81280517578125,\n", - " 355.9754943847656,\n", - " 0.38737767934799194\n", - " ],\n", - " [\n", - " 268.998046875,\n", - " 357.2021789550781,\n", - " 0.44784843921661377\n", - " ],\n", - " [\n", - " 596.336181640625,\n", - " 379.1740417480469,\n", - " 0.545729398727417\n", - " ],\n", - " [\n", - " 201.0037841796875,\n", - " 228.0155029296875,\n", - " 0.42291054129600525\n", - " ],\n", - " [\n", - " 562.3812866210938,\n", - " 386.2914123535156,\n", - " 0.36510559916496277\n", - " ],\n", - " [\n", - " 517.466796875,\n", - " 433.1764831542969,\n", - " 0.5716246962547302\n", - " ],\n", - " [\n", - " 570.5004272460938,\n", - " 194.27825927734375,\n", - " 0.31970250606536865\n", - " ],\n", - " [\n", - " 572.74462890625,\n", - " 201.8236541748047,\n", - " 0.39004698395729065\n", - " ],\n", - " [\n", - " 513.2064208984375,\n", - " 451.0941162109375,\n", - " 0.6408159732818604\n", - " ],\n", - " [\n", - " 590.6119995117188,\n", - " 408.5152282714844,\n", - " 0.5167672634124756\n", - " ],\n", - " [\n", - " 114.4150619506836,\n", - " 235.0469207763672,\n", - " 0.13695929944515228\n", - " ],\n", - " [\n", - " 112.1463623046875,\n", - " 279.28265380859375,\n", - " 0.23755685985088348\n", - " ],\n", - " [\n", - " 571.991943359375,\n", - " 393.113525390625,\n", - " 0.4135165214538574\n", - " ],\n", - " [\n", - " 144.2115478515625,\n", - " 313.7943115234375,\n", - " 0.23972396552562714\n", - " ],\n", - " [\n", - " 510.1828918457031,\n", - " 456.5469055175781,\n", - " 0.503180205821991\n", - " ],\n", - " [\n", - " 468.22821044921875,\n", - " 413.3734436035156,\n", - " 0.37297725677490234\n", - " ],\n", - " [\n", - " 465.0234069824219,\n", - " 410.9199523925781,\n", - " 0.36548110842704773\n", - " ],\n", - " [\n", - " 465.41192626953125,\n", - " 406.6351623535156,\n", - " 0.4397272765636444\n", - " ]\n", - " ],\n", - " \"frame_time\": 1783588106.3645778,\n", - " \"pose_time\": null,\n", - " \"recording\": false\n", - " },\n", - " \"received_at\": 1783588106.3911848\n", - "}\n", - "Received 20 packet(s).\n" - ] - } - ], - "source": [ - "def receive_packets(conn, duration_s: float = 30.0, max_packets: int | None = None):\n", - " start = time.time()\n", - " packets: list[dict[str, Any]] = []\n", - "\n", - " while time.time() - start < duration_s:\n", - " if conn.poll(POLL_INTERVAL_S):\n", - " payload = conn.recv()\n", - " decoded = decode_payload(payload)\n", - " decoded[\"received_at\"] = time.time()\n", - " packets.append(decoded)\n", - " print(json.dumps(decoded, default=str, indent=2))\n", - "\n", - " if max_packets is not None and len(packets) >= max_packets:\n", - " break\n", - "\n", - " print(f\"Received {len(packets)} packet(s).\")\n", - " return packets\n", - "\n", - "\n", - "packets = receive_packets(conn, duration_s=30.0, max_packets=20)" - ] - }, - { - "cell_type": "markdown", - "id": "504fb2a444614c0babb325280ed9130a", - "metadata": {}, - "source": [ - "## Save captured packets\n", - "\n", - "This is useful if you want to inspect the stream shape after a test run." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "59bbdb311c014d738909a11f9e486628", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Saved 20 packet(s) to C:\\Users\\Cyril A\\Desktop\\Code\\DeepLabCut-live-GUI\\dlclivegui\\processors\\custom\\mock_unity_captured_packets.json\n" - ] - } - ], - "source": [ - "out_path = Path(\"mock_unity_captured_packets.json\")\n", - "out_path.write_text(json.dumps(packets, default=str, indent=2), encoding=\"utf-8\")\n", - "print(\"Saved\", len(packets), \"packet(s) to\", out_path.resolve())" - ] - }, - { - "cell_type": "markdown", - "id": "b43b363d81ae4b689946ece5c682cd59", - "metadata": {}, - "source": [ - "## Close the client connection" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "8a65eabff63a45729fe45fb5ade58bdc", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connection closed\n" - ] - } - ], - "source": [ - "try:\n", - " conn.close()\n", - " print(\"Connection closed\")\n", - "except Exception as exc:\n", - " print(\"Close failed:\", exc)" - ] - }, - { - "cell_type": "markdown", - "id": "c3933fab20d04ec698c2621248eb3be0", - "metadata": {}, - "source": [ - "## Optional local smoke test with a standalone mock server\n", - "\n", - "Use this only if you want to validate the notebook client logic without running DLCLiveGUI.\n", - "\n", - "This requires `mock_socket_processor.py` to be importable, for example by placing it next to this notebook or adding its directory to `sys.path`." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "4dd4641cc4064e0191573fe9c69df29b", - "metadata": {}, - "outputs": [], - "source": [ - "# Optional smoke test. Leave commented unless you have mock_socket_processor.py available.\n", - "#\n", - "# import socket\n", - "# import sys\n", - "# from multiprocessing.connection import Client\n", - "#\n", - "# from mock_socket_processor import MockSocketProcessor\n", - "#\n", - "# def free_port():\n", - "# s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n", - "# s.bind((\"127.0.0.1\", 0))\n", - "# port = s.getsockname()[1]\n", - "# s.close()\n", - "# return port\n", - "#\n", - "# port = free_port()\n", - "# mock = MockSocketProcessor(bind=(\"127.0.0.1\", port), authkey=AUTHKEY)\n", - "# test_conn = Client(mock.address, authkey=AUTHKEY)\n", - "# test_conn.send({\"cmd\": \"ping\"})\n", - "# print(test_conn.recv())\n", - "# mock.process([[1, 2, 0.9]], frame_time=123.456)\n", - "# print(decode_payload(test_conn.recv()))\n", - "# test_conn.close()\n", - "# mock.stop()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "deeplabcut-live-gui (3.12.12)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.12" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From cf4e9fc951da6e36c2ed9a2be85e1f24f8310e92 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 11:21:29 +0200 Subject: [PATCH 08/17] pre-commit --- dlclivegui/gui/main_window.py | 32 +++++-------------- .../custom_processors/test_base_processor.py | 3 -- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index c0710273d..efe671993 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -2161,16 +2161,11 @@ def _configure_dlc(self) -> bool: RuntimeError, json.JSONDecodeError, ) as exc: - self._show_error( - f"Invalid DLCLive settings: {exc}" - ) + self._show_error(f"Invalid DLCLive settings: {exc}") return False if not settings.model_path: - self._show_error( - "Please select a DLCLive model before " - "starting inference." - ) + self._show_error("Please select a DLCLive model before starting inference.") return False processor = None @@ -2181,26 +2176,21 @@ def _configure_dlc(self) -> bool: if self._custom_processor_enabled(): try: - processor_info = self._scanned_processors[ - selected_key - ] + processor_info = self._scanned_processors[selected_key] processor_class = processor_info["class"] processor_name = processor_info.get( "name", processor_class.__name__, ) - if processor_builds_in_worker( - processor_class - ): + if processor_builds_in_worker(processor_class): processor_spec = create_spec_from_scan( self._scanned_processors, selected_key, ) log_processor_context( - "MainWindow._configure_dlc - " - f"SPEC: {processor_class.__name__}", + f"MainWindow._configure_dlc - SPEC: {processor_class.__name__}", logger, ) else: @@ -2210,8 +2200,7 @@ def _configure_dlc(self) -> bool: ) log_processor_context( - "MainWindow._configure_dlc - " - f"INSTANCE: {type(processor).__name__}", + f"MainWindow._configure_dlc - INSTANCE: {type(processor).__name__}", logger, ) @@ -2221,10 +2210,7 @@ def _configure_dlc(self) -> bool: ) except Exception as exc: - error_msg = ( - "Failed to configure processor: " - f"{exc}" - ) + error_msg = f"Failed to configure processor: {exc}" self._show_error(error_msg) logger.exception(error_msg) return False @@ -2240,9 +2226,7 @@ def _configure_dlc(self) -> bool: processor=processor, processor_spec=processor_spec, ) - self._model_path_store.save_if_valid( - settings.model_path - ) + self._model_path_store.save_if_valid(settings.model_path) return True def _update_inference_buttons(self) -> None: diff --git a/tests/custom_processors/test_base_processor.py b/tests/custom_processors/test_base_processor.py index cb8d2e7f8..db42d4952 100644 --- a/tests/custom_processors/test_base_processor.py +++ b/tests/custom_processors/test_base_processor.py @@ -3,9 +3,6 @@ import importlib import pickle -from pathlib import Path -import sys -import types import numpy as np import pandas as pd From a700f05f6cf5f2a0d855e0fc9ebd44c5d065bd6f Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 11:47:41 +0200 Subject: [PATCH 09/17] Use shared worker-build processor helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced the service’s direct dependency on `BaseProcessorSocket.do_build_in_worker` with `processor_builds_in_worker` from `processor_utils`, improving reuse and reducing coupling to socket internals. Also updated the recording context docstring to use `timestamp_json_files` for consistent key naming. --- dlclivegui/processors/dlc_processor_socket.py | 2 +- dlclivegui/services/dlc_processor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dlclivegui/processors/dlc_processor_socket.py b/dlclivegui/processors/dlc_processor_socket.py index 5c28bc887..c1f7aa5e8 100644 --- a/dlclivegui/processors/dlc_processor_socket.py +++ b/dlclivegui/processors/dlc_processor_socket.py @@ -421,7 +421,7 @@ def set_recording_context(self, context: dict | None) -> None: filename_stem processor_base_path video_files - timestamp_files + timestamp_json_files """ self.recording_context = dict(context or {}) diff --git a/dlclivegui/services/dlc_processor.py b/dlclivegui/services/dlc_processor.py index e51be7500..d141a8562 100644 --- a/dlclivegui/services/dlc_processor.py +++ b/dlclivegui/services/dlc_processor.py @@ -17,12 +17,12 @@ from PySide6.QtCore import QObject, Signal from dlclivegui.config import DLC_DO_LOG_TIMING, DLCProcessorSettings, ModelType -from dlclivegui.processors.dlc_processor_socket import BaseProcessorSocket from dlclivegui.processors.processor_utils import ( ProcessorSpec, create_spec_from_scan, instantiate_from_scan, log_processor_context, + processor_builds_in_worker, ) from dlclivegui.temp import Engine # type: ignore # TODO use main package enum when released from dlclivegui.utils.stats import WorkerTimingStats @@ -948,7 +948,7 @@ def configure(self, settings: DLCProcessorSettings, scanned_processors: dict, se processor_info = scanned_processors[selected_key] processor_class = processor_info["class"] - if BaseProcessorSocket.do_build_in_worker(processor_class): + if processor_builds_in_worker(processor_class): processor_spec = create_spec_from_scan(scanned_processors, selected_key) log_processor_context( From 1d7eaef2dab8295e3b30abc3fc1cc0c56f709e9a Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 11:52:25 +0200 Subject: [PATCH 10/17] Preserve processor recording context lifecycle Store the processor recording context when recording starts and reuse that same context when notifying `on_recording_stopped`, instead of rebuilding it from recorder state. This also centralizes stop-time processor finalization (stop hook + save) and invokes it both on normal async stop and during shutdown when a recording context exists. --- dlclivegui/gui/main_window.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index efe671993..98a7ac8a0 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -154,6 +154,7 @@ def __init__(self, config: ApplicationSettings | None = None): self._raw_frame: np.ndarray | None = None self._last_pose: PoseResult | None = None self._dlc_active: bool = False + self._processor_recording_context: dict | None = None self._pending_recording_after_preview = False self._active_camera_settings: CameraSettings | None = None self._last_drop_warning = 0.0 @@ -1863,7 +1864,9 @@ def _start_multi_camera_recording(self) -> None: if run_dir is None: self._show_error("Failed to start recording.") return - self._notify_processor_recording_started(run_dir) + + self._processor_recording_context = self._build_processor_recording_context(run_dir) + self._notify_processor_recording_started(self._processor_recording_context) self.multi_camera_controller.set_recording_sink(self._rec_manager.write_frame) self.multi_camera_controller.set_recording_frame_is_enabled(True) @@ -1957,7 +1960,7 @@ def _save_processor_data_if_available(self) -> None: except Exception: logger.exception("Processor save() failed.") - def _notify_processor_recording_started(self, run_dir) -> None: + def _notify_processor_recording_started(self, context: dict) -> None: processor = self._get_dlc_processor_instance() if processor is None: return @@ -1967,14 +1970,11 @@ def _notify_processor_recording_started(self, run_dir) -> None: return try: - context = self._build_processor_recording_context(run_dir) hook(context) logger.info("Notified processor recording started: %s", context) except Exception: logger.exception("Processor on_recording_started hook failed") - from pathlib import Path - def _build_processor_recording_context(self, run_dir) -> dict: run_dir = Path(run_dir) if run_dir is not None else None @@ -2013,26 +2013,30 @@ def _build_processor_recording_context(self, run_dir) -> dict: def _notify_processor_recording_stopped(self) -> None: processor = self._get_dlc_processor_instance() if processor is None: - return False + return hook = getattr(processor, "on_recording_stopped", None) if not callable(hook): - return False + return try: - run_dir = getattr(self._rec_manager, "run_dir", None) - context = self._build_processor_recording_context(run_dir) + context = self._processor_recording_context or self._build_processor_recording_context(None) hook(context) logger.info("Notified processor recording stopped") - return True except Exception: logger.exception("Processor on_recording_stopped hook failed") - return False - def _on_recording_stopped_async(self) -> None: - handled_by_stop_hook = self._notify_processor_recording_stopped() - if not handled_by_stop_hook: + def _finalize_processor_recording(self) -> None: + """Notify the processor and save its recording data.""" + try: + self._notify_processor_recording_stopped() self._save_processor_data_if_available() + finally: + self._processor_recording_finalized = None + + def _on_recording_stopped_async(self) -> None: + if self._processor_recording_context is not None: + self._finalize_processor_recording() self._recording_stopping = False self.start_record_button.setEnabled(True) @@ -2667,9 +2671,12 @@ def closeEvent(self, event: QCloseEvent) -> None: # pragma: no cover - GUI beha self.multi_camera_controller.set_recording_frame_is_enabled(False) except Exception: logger.exception("Failed to disable recording frame emission during shutdown") + while not self._rec_manager.stop_all(): logger.info("Retrying recorder stop during shutdown...") time.sleep(RECORD_STOP_RETRY_INTERVAL) + if self._processor_recording_context is not None: + self._finalize_processor_recording() # Close the camera dialog if open (ensures its worker thread is canceled) if getattr(self, "_cam_dialog", None) is not None and self._cam_dialog.isVisible(): From e1bb57590605089f480ba86c8dadccc1d7504b3c Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 11:53:22 +0200 Subject: [PATCH 11/17] Defer DLC processor cleanup until worker exits Adjust shutdown/reset flow to avoid cleaning up the processor while the worker thread is still alive. Cleanup now runs immediately only after a successful stop, and is deferred to the reaper path when a pending reset completes after the worker eventually joins. --- dlclivegui/services/dlc_processor.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/dlclivegui/services/dlc_processor.py b/dlclivegui/services/dlc_processor.py index d141a8562..4a51bbc55 100644 --- a/dlclivegui/services/dlc_processor.py +++ b/dlclivegui/services/dlc_processor.py @@ -276,13 +276,13 @@ def shutdown(self) -> None: stopped = self._stop_worker() if not stopped: with self._lifecycle_lock: - if self._processor is not None: - self._cleanup_processor() self._pending_reset = True - logger.warning( - "Shutdown requested but worker thread is still alive; DLCLive instance may not be fully released." - ) - return + logger.warning( + "Shutdown requested but worker thread is still alive; DLCLive instance may not be fully released." + ) + return + + self._cleanup_processor() self._dlc = None self._initialized = False @@ -546,6 +546,7 @@ def _schedule_reap(self, t: threading.Thread) -> None: # ensure only one reaper def reap(): + should_cleanup = False try: t.join() # wait without timeout in background with self._lifecycle_lock: @@ -557,12 +558,16 @@ def reap(): self._stop_event.clear() if self._pending_reset: + should_cleanup = True self._dlc = None self._initialized = False self._pending_reset = False logger.warning("DLC worker thread stopped after the timeout and was cleaned up late.") + if should_cleanup: # worker joined, cleanup is safe + self._cleanup_processor() + finally: with self._lifecycle_lock: self._reaping = False From c2a48b125f95c89b0f5ecd6abbd666c3adc76801 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 11:55:08 +0200 Subject: [PATCH 12/17] Fix processor recording context cleanup Corrects the recording-stop cleanup path to clear `_processor_recording_context` instead of an unrelated attribute. This ensures the async stop handler sees the finalized state consistently and avoids stale recording context after shutdown. --- dlclivegui/gui/main_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index 98a7ac8a0..e7647630a 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -2032,7 +2032,7 @@ def _finalize_processor_recording(self) -> None: self._notify_processor_recording_stopped() self._save_processor_data_if_available() finally: - self._processor_recording_finalized = None + self._processor_recording_context = None def _on_recording_stopped_async(self) -> None: if self._processor_recording_context is not None: From b06d9bf311ddf99c58ddfbb18a31f3e05f07da7b Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 14:28:01 +0200 Subject: [PATCH 13/17] Improve processor reset and recording hooks Refines recording lifecycle integration by capturing finalized recording file context when notifying `on_recording_stopped`, and introducing tracking for recording-start notifications during DLC initialization. It also tightens processor teardown behavior by distinguishing pending reset vs pending plugin cleanup, and ensures full processor plugin cleanup when stopping DLC from the UI/service. --- dlclivegui/gui/main_window.py | 30 +++++++++++++++++++++++----- dlclivegui/services/dlc_processor.py | 14 +++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index e7647630a..542fae1b3 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -155,6 +155,7 @@ def __init__(self, config: ApplicationSettings | None = None): self._last_pose: PoseResult | None = None self._dlc_active: bool = False self._processor_recording_context: dict | None = None + self._processor_recording_started_notified = False self._pending_recording_after_preview = False self._active_camera_settings: CameraSettings | None = None self._last_drop_warning = 0.0 @@ -1866,6 +1867,7 @@ def _start_multi_camera_recording(self) -> None: return self._processor_recording_context = self._build_processor_recording_context(run_dir) + self._processor_recording_started_notified = False self._notify_processor_recording_started(self._processor_recording_context) self.multi_camera_controller.set_recording_sink(self._rec_manager.write_frame) self.multi_camera_controller.set_recording_frame_is_enabled(True) @@ -1960,20 +1962,22 @@ def _save_processor_data_if_available(self) -> None: except Exception: logger.exception("Processor save() failed.") - def _notify_processor_recording_started(self, context: dict) -> None: + def _notify_processor_recording_started(self, context: dict) -> bool: processor = self._get_dlc_processor_instance() if processor is None: - return + return False hook = getattr(processor, "on_recording_started", None) if not callable(hook): - return + return False try: hook(context) logger.info("Notified processor recording started: %s", context) + return True except Exception: logger.exception("Processor on_recording_started hook failed") + return False def _build_processor_recording_context(self, run_dir) -> dict: run_dir = Path(run_dir) if run_dir is not None else None @@ -2010,6 +2014,18 @@ def _build_processor_recording_context(self, run_dir) -> dict: ctx.update(file_context) return ctx + def _final_processor_recording_context(self) -> dict: + context = dict(self._processor_recording_context or {}) + + try: + final_file_context = self._rec_manager.get_recording_file_context() + except Exception: + logger.exception("Failed to get finalized recording file context") + final_file_context = {} + + context.update(final_file_context) + return context + def _notify_processor_recording_stopped(self) -> None: processor = self._get_dlc_processor_instance() if processor is None: @@ -2020,7 +2036,7 @@ def _notify_processor_recording_stopped(self) -> None: return try: - context = self._processor_recording_context or self._build_processor_recording_context(None) + context = self._final_processor_recording_context() hook(context) logger.info("Notified processor recording stopped") except Exception: @@ -2033,6 +2049,7 @@ def _finalize_processor_recording(self) -> None: self._save_processor_data_if_available() finally: self._processor_recording_context = None + self._processor_recording_started_notified = False def _on_recording_stopped_async(self) -> None: if self._processor_recording_context is not None: @@ -2457,7 +2474,7 @@ def _stop_inference(self, show_message: bool = True) -> None: was_active = self._dlc_active self._dlc_active = False self._dlc_initialized = False - self._dlc.reset() + self._dlc.reset(reset_processor_plugin=True) self._last_pose = None self._last_processor_vid_recording = False self._auto_record_session_name = None @@ -2605,6 +2622,9 @@ def _on_bbox_changed(self, _value: int = 0) -> None: def _on_dlc_initialised(self, success: bool) -> None: if success: self._dlc_initialized = True + if self._processor_recording_context is not None and not self._processor_recording_started_notified: + self._notify_processor_recording_started(self._processor_recording_context) + # Update button to show running state self.start_inference_button.setText("DLCLive running!") self.start_inference_button.setStyleSheet("background-color: #4CAF50; color: white;") diff --git a/dlclivegui/services/dlc_processor.py b/dlclivegui/services/dlc_processor.py index 4a51bbc55..8bd6cc755 100644 --- a/dlclivegui/services/dlc_processor.py +++ b/dlclivegui/services/dlc_processor.py @@ -163,7 +163,7 @@ def __init__(self) -> None: self._dlc: Any | None = None self._processor: Any | None = None self._processor_spec: ProcessorSpec | None = None - self.processor_built_from_spec = False + self._processor_built_from_spec = False # Worker thread and queue self._queue: queue.Queue[Any] | None = None self._worker_thread: threading.Thread | None = None @@ -174,6 +174,7 @@ def __init__(self) -> None: ## Worker cleanup self._reaping = False self._pending_reset = False + self._pending_processor_cleanup = False # Statistics tracking self._frames_enqueued = 0 @@ -221,7 +222,7 @@ def configure( self._settings = settings self._processor = processor self._processor_spec = processor_spec - self.processor_built_from_spec = False + self._processor_built_from_spec = False def reset(self, reset_processor_plugin: bool = False) -> None: """Stop the worker thread and drop the current DLCLive instance.""" @@ -233,6 +234,9 @@ def reset(self, reset_processor_plugin: bool = False) -> None: if not stopped: with self._lifecycle_lock: self._pending_reset = True + self._pending_processor_cleanup = ( + self._pending_processor_cleanup or reset_processor_plugin or had_runtime + ) logger.warning( "Reset requested but worker thread is still alive; skipping DLCLive reset to avoid potential issues." ) @@ -277,6 +281,7 @@ def shutdown(self) -> None: if not stopped: with self._lifecycle_lock: self._pending_reset = True + self._pending_processor_cleanup = True logger.warning( "Shutdown requested but worker thread is still alive; DLCLive instance may not be fully released." ) @@ -558,10 +563,11 @@ def reap(): self._stop_event.clear() if self._pending_reset: - should_cleanup = True + should_cleanup = self._pending_processor_cleanup self._dlc = None self._initialized = False self._pending_reset = False + self._pending_processor_cleanup = False logger.warning("DLC worker thread stopped after the timeout and was cleaned up late.") @@ -981,7 +987,7 @@ def start(self): def stop(self): self.active = False - self._proc.reset() + self._proc.reset(reset_processor_plugin=True) self._last_pose = None def stats(self) -> ProcessorStats: From 1e2207292a4137c5b2378e12f39a6f61c6379161 Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 14:43:42 +0200 Subject: [PATCH 14/17] Disable debug log --- dlclivegui/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlclivegui/config.py b/dlclivegui/config.py index c9628ca97..11bcf8049 100644 --- a/dlclivegui/config.py +++ b/dlclivegui/config.py @@ -40,7 +40,7 @@ ### Trigger debug logging DEBUG_TRIGGER_LOGS = False ### Extra logs for DLC lifecycle (model loading, etc) -DLC_LIFECYCLE_EXTRA_LOGS: bool = True +DLC_LIFECYCLE_EXTRA_LOGS: bool = False # MAIN_WINDOW_DO_LOG_TIMING: bool = False #### Backends BASLER_DO_LOG_TIMING: bool = False From b1435ad23d63c1c032e772a6bcb3d3863ba270ff Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 14:44:12 +0200 Subject: [PATCH 15/17] Fix save path and recording context resolution Improve processor metadata finalization by rebuilding recording context from the current run directory and then overlaying cached values, so missing fields are backfilled before merging file context. Also update socket processor saving so explicitly provided relative filenames are saved under the `data/` directory, matching expected output behavior. --- dlclivegui/gui/main_window.py | 12 +++++++++++- dlclivegui/processors/dlc_processor_socket.py | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index 542fae1b3..0acb7de35 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -2015,7 +2015,17 @@ def _build_processor_recording_context(self, run_dir) -> dict: return ctx def _final_processor_recording_context(self) -> dict: - context = dict(self._processor_recording_context or {}) + cached = self._processor_recording_context + + run_dir = None + if cached is not None: + run_dir = cached.get("run_dir") + if run_dir is None: + run_dir = getattr(self._rec_manager, "run_dir", None) + + context = self._build_processor_recording_context(run_dir) + if cached is not None: + context.update(cached) try: final_file_context = self._rec_manager.get_recording_file_context() diff --git a/dlclivegui/processors/dlc_processor_socket.py b/dlclivegui/processors/dlc_processor_socket.py index c1f7aa5e8..58513f80c 100644 --- a/dlclivegui/processors/dlc_processor_socket.py +++ b/dlclivegui/processors/dlc_processor_socket.py @@ -376,6 +376,7 @@ def _clear_data_queues(self): def save(self, file=None): target = file + explicit_file = file is not None if target is None: target = getattr(self, "save_path", None) @@ -387,6 +388,9 @@ def save(self, file=None): try: save_dict = self.get_data() save_path = Path(target) + if explicit_file and not save_path.is_absolute(): + save_path = Path("data") / save_path + save_path.parent.mkdir(parents=True, exist_ok=True) if self.save_original: From 87ed4efdb36892165f318a68a4009dc212f2dddb Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 14:44:36 +0200 Subject: [PATCH 16/17] Expand recording context test coverage Adds focused tests around processor recording context handling in `DLCLiveMainWindow`, including file metadata propagation, stop-time context refresh, and optional processor hooks. It also introduces a regression test for `BaseProcessorSocket.save()` to ensure explicit relative filenames still write under the legacy `data/` directory. UI label testing for unknown camera IDs was relaxed to assert the neutral text is present rather than requiring an exact string. --- .../test_processor_rec_context.py | 118 +++++++++++++++++- tests/gui/main_window/test_ui.py | 2 +- 2 files changed, 115 insertions(+), 5 deletions(-) diff --git a/tests/custom_processors/test_processor_rec_context.py b/tests/custom_processors/test_processor_rec_context.py index 16a1461d9..85d840f39 100644 --- a/tests/custom_processors/test_processor_rec_context.py +++ b/tests/custom_processors/test_processor_rec_context.py @@ -212,13 +212,27 @@ def main_window_cls(): return mod.DLCLiveMainWindow -def make_window_shell(main_window_cls, processor=None, run_dir=None): +class DummyRecordingManager: + def __init__(self, run_dir=None, file_context=None): + self.run_dir = run_dir + self.file_context = dict(file_context or {}) + + def get_recording_file_context(self): + context = dict(self.file_context) + if self.run_dir is not None: + context.setdefault("run_dir", self.run_dir) + return context + + +def make_window_shell(main_window_cls, processor=None, run_dir=None, file_context=None): """Create a DLCLiveMainWindow shell without running QMainWindow.__init__.""" win = main_window_cls.__new__(main_window_cls) win._dlc = SimpleNamespace(_processor=processor, _dlc=None) - win._rec_manager = SimpleNamespace(run_dir=run_dir) + win._rec_manager = DummyRecordingManager(run_dir=run_dir, file_context=file_context) win.session_name_edit = DummyLineEdit("MouseA") win.filename_edit = DummyLineEdit("MouseA_2026-07-10_1.avi") + win._processor_recording_context = None + win._processor_recording_started_notified = False return win @@ -253,7 +267,8 @@ def test_main_window_notify_processor_recording_started_calls_hook(main_window_c processor = HookProcessor() win = make_window_shell(main_window_cls, processor=processor, run_dir=tmp_path) - win._notify_processor_recording_started(tmp_path) + context = win._build_processor_recording_context(tmp_path) + win._notify_processor_recording_started(context) assert len(processor.started_contexts) == 1 context = processor.started_contexts[0] @@ -287,6 +302,101 @@ class NoHooks: win = make_window_shell(main_window_cls, processor=NoHooks(), run_dir=tmp_path) # Optional hooks/save absence should not crash. - win._notify_processor_recording_started(tmp_path) + ctx = win._build_processor_recording_context(tmp_path) + win._notify_processor_recording_started(ctx) win._notify_processor_recording_stopped() win._save_processor_data_if_available() + + +def test_main_window_build_context_includes_recording_files( + main_window_cls, + tmp_path, +): + video_file = tmp_path / "camera_0.mp4" + timestamp_file = tmp_path / "camera_0.json" + + win = make_window_shell( + main_window_cls, + run_dir=tmp_path, + file_context={ + "video_files": { + "camera-0": video_file, + }, + "timestamp_json_files": { + "camera-0": timestamp_file, + }, + }, + ) + + context = win._build_processor_recording_context(tmp_path) + + assert context["video_files"] == { + "camera-0": video_file, + } + assert context["timestamp_json_files"] == { + "camera-0": timestamp_file, + } + + +def test_processor_stop_context_uses_finalized_file_context( + main_window_cls, + tmp_path, +): + processor = HookProcessor() + initial_video = tmp_path / "initial.mp4" + final_video = tmp_path / "final.mp4" + + win = make_window_shell( + main_window_cls, + processor=processor, + run_dir=tmp_path, + file_context={ + "video_files": { + "camera-0": initial_video, + }, + }, + ) + win._processor_recording_context = win._build_processor_recording_context(tmp_path) + + # Simulate RecordingManager updating its context during stop_all(). + win._rec_manager.file_context = { + "video_files": { + "camera-0": final_video, + }, + } + + win._notify_processor_recording_stopped() + + assert len(processor.stopped_contexts) == 1 + assert processor.stopped_contexts[0]["video_files"] == { + "camera-0": final_video, + } + + +def test_base_processor_explicit_relative_file_uses_legacy_data_dir( + socket_mod, + tmp_path, + monkeypatch, +): + BaseProcessorSocket = socket_mod.BaseProcessorSocket + proc = BaseProcessorSocket( + bind=("127.0.0.1", 0), + save_original=False, + ) + + monkeypatch.chdir(tmp_path) + + try: + ret = proc.save("session_dlc_processor_data.pkl") + + expected = tmp_path / "data" / "session_dlc_processor_data.pkl" + + assert ret == 1 + assert expected.exists() + + with expected.open("rb") as handle: + payload = pickle.load(handle) + + assert "start_time" in payload + finally: + proc.stop() diff --git a/tests/gui/main_window/test_ui.py b/tests/gui/main_window/test_ui.py index 17ba0513e..351539883 100644 --- a/tests/gui/main_window/test_ui.py +++ b/tests/gui/main_window/test_ui.py @@ -49,5 +49,5 @@ def test_label_for_cam_id_uses_runtime_display_id_fallback(self, window): assert w._label_for_cam_id("runtime:id") == "Runtime Camera" - def test_label_for_cam_id_unknown_is_neutral(self, window): + def test_label_for_cam_id_unknown(self, window): assert "Unknown camera" in window._label_for_cam_id("missing:id") From 64fe64cd2a38fd5a53594c9be1be8e9c034069bf Mon Sep 17 00:00:00 2001 From: C-Achard Date: Mon, 17 Aug 2026 15:25:40 +0200 Subject: [PATCH 17/17] Fix recording start flag and add worker test Set `_processor_recording_started_notified` immediately after a successful recording-start hook callback so the UI tracks notification state correctly and avoids repeated notifications. Also add a unit test that verifies `ProcessorSpec` processors are instantiated on the `DLCLiveWorker` thread, wired into `DLCLive`, and properly stopped/cleared when resetting the processor. --- dlclivegui/gui/main_window.py | 1 + tests/services/test_dlc_processor.py | 100 +++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/dlclivegui/gui/main_window.py b/dlclivegui/gui/main_window.py index 0acb7de35..7d3bc27a2 100644 --- a/dlclivegui/gui/main_window.py +++ b/dlclivegui/gui/main_window.py @@ -1973,6 +1973,7 @@ def _notify_processor_recording_started(self, context: dict) -> bool: try: hook(context) + self._processor_recording_started_notified = True logger.info("Notified processor recording started: %s", context) return True except Exception: diff --git a/tests/services/test_dlc_processor.py b/tests/services/test_dlc_processor.py index a58490e3e..42b72d718 100644 --- a/tests/services/test_dlc_processor.py +++ b/tests/services/test_dlc_processor.py @@ -1,6 +1,7 @@ from __future__ import annotations import queue +import threading import numpy as np import pytest @@ -8,6 +9,7 @@ from dlclivegui.config import DLCProcessorSettings # from dlclivegui.config import DLCProcessorSettings +from dlclivegui.processors.processor_utils import ProcessorSpec from dlclivegui.services.dlc_processor import ( DLCLiveProcessor, ProcessorStats, @@ -55,6 +57,104 @@ def test_worker_initializes_on_first_frame(qtbot, monkeypatch_dlclive, settings_ proc.reset() # Ensure thread cleanup +@pytest.mark.unit +def test_processor_spec_builds_in_worker_and_is_cleaned( + qtbot, + monkeypatch, + settings_model, +): + """Build a ProcessorSpec in DLCLiveWorker and clean it on reset.""" + from dlclivegui.services import dlc_processor as dlc_processor_module + + built_processors = [] + dlclive_instances = [] + + class WorkerBuiltProcessor: + PROCESSOR_NAME = "Worker-built test processor" + + def __init__(self): + self.build_thread_name = threading.current_thread().name + self.stop_calls = 0 + built_processors.append(self) + + def process(self, pose, **kwargs): + return pose + + def stop(self): + self.stop_calls += 1 + + class FakeRunner: + def get_pose(self, _processed_frame): + return np.array( + [[1.0, 2.0, 0.9]], + dtype=np.float32, + ) + + class FakeDLCLive: + def __init__(self, **options): + self.processor = options["processor"] + self.runner = FakeRunner() + self.pose = None + self.cfg = {} + self.init_called = False + dlclive_instances.append(self) + + def init_inference(self, _frame): + self.init_called = True + + def process_frame(self, frame): + return frame + + monkeypatch.setattr( + dlc_processor_module, + "DLCLive", + FakeDLCLive, + ) + + proc = DLCLiveProcessor() + proc.configure( + settings_model, + processor_spec=ProcessorSpec( + cls=WorkerBuiltProcessor, + ), + ) + + frame = np.zeros( + (32, 32, 3), + dtype=np.uint8, + ) + + try: + with qtbot.waitSignal( + proc.initialized, + timeout=1500, + ) as blocker: + proc.enqueue_frame(frame, timestamp=1.0) + + assert blocker.args == [True] + assert len(built_processors) == 1 + assert len(dlclive_instances) == 1 + + built_processor = built_processors[0] + dlclive_instance = dlclive_instances[0] + + assert built_processor.build_thread_name == "DLCLiveWorker" + assert proc._processor is built_processor + assert proc._processor_built_from_spec is True + assert dlclive_instance.processor is built_processor + assert dlclive_instance.init_called is True + + proc.reset(reset_processor_plugin=True) + + assert built_processor.stop_calls == 1 + assert proc._processor is None + assert proc._processor_built_from_spec is False + assert proc._state == WorkerState.STOPPED + + finally: + proc.shutdown() + + @pytest.mark.unit def test_worker_processes_frames(qtbot, monkeypatch_dlclive, settings_model): proc = DLCLiveProcessor()