Uh oh!
There was an error while loading. Please reload this page.
Processor-related fixes - #89
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses two processor-related UX/plug-in discovery issues in DeepLabCut-live-GUI: (1) ensuring DLC/processor controls correctly re-enable after preview stop, and (2) improving processor class discovery so indirect dlclive.Processor subclasses can be surfaced in the GUI.
Changes:
- Adds a shared
discover_processor_classes()helper to finddlclive.Processorsubclasses (including indirect subclasses) and reuses it for both package and file-based processor loading. - Hooks
allow_processor_ctrl_checkboxchanges into UI update routines, and refreshes DLC control enabled-state on multi-camera preview start/stop. - Simplifies DLC control enabling logic so processor-related widgets re-enable based on inference state.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| dlclivegui/processors/processor_utils.py | Refactors processor discovery into reusable helpers and uses them for package/file scanning. |
| dlclivegui/gui/main_window.py | Updates signal wiring and control enable/disable logic to restore processor/DLC UI states after preview transitions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
52f9b92 to
a800c08Compare| with self._timing.measure("Single.read"): | ||
| frame, timestamp = self._backend.read() | ||
| captured = self._backend.read() | ||
| frame = captured.frame | ||
| timestamp = captured.software_timestamp | ||
| timestamp_metadata = captured.timestamp_metadata |
| try: | ||
| from dlclive.processor import Processor # type: ignore | ||
| except ImportError: | ||
| Processor = object # Fallback for type checking if dlclive is not installed |
| def _processor_base_class(): | ||
| from dlclive.processor import Processor | ||
| return Processor |
a800c08 to
f4859fbComparef4859fb to
7ca8905Compare7ca8905 to
ee5b82cCompareee5b82c to
571175eCompare571175e to
59b2d26Compare59b2d26 to
9fadb36Compare| @@ -39,11 +98,9 @@ def scan_processor_folder(folder_path): | |||
| return all_processors | |||
| def scan_processor_package(package_name: str = "dlclivegui.processors") -> dict[str | dict]: | |||
| def scan_processor_package(package_name: str = "dlclivegui.processors") -> dict[str, dict]: | |||
| """ | |||
| Discover and load processor classes from a package namespace. | |||
| Returns a dict keyed as 'module.py::ClassName' with the same | |||
| structure you use today. | |||
| """ | |||
| all_processors: dict[str, dict] = {} | |||
There was a problem hiding this comment.
scan_processor_package now skips the base class successfullly, but for scan_processor_folder it seems that dlc_processor_socket.py still fits the detection criteria. Is that intended?
More general: do we really nead this way of detecting processors via two different mechanisms? It feels a bit overly complex, but maybe I am missing something.
| for widget in processor_widgets: | ||
| widget.setEnabled(processor_controls) | ||
| widget.setEnabled(allow_changes) | ||
There was a problem hiding this comment.
This seems a bit awkward: when this tickbox is not selected, _configure_dlc configures processor=None. That means you can pick a processor, but it will be dropped if the box is not ticked.
Also: unticking the box reads to me as "don't let it control the recording", not as "don't load a processor at all".
There was a problem hiding this comment.
This still states that a typical processor module defines a registry and a decorator, but that is now a shared import right?
9fadb36 to
fa2ef52Comparefa2ef52 to
f16e440Comparef16e440 to
f13a8f7CompareDisable all DLC and processor configuration widgets consistently while inference is active, including the processor-control checkbox. Refactor processor discovery into shared helpers that detect direct and indirect `dlclive.Processor` subclasses, standardize metadata extraction, and reuse the same fallback logic for package scans and file-based loading.
Expand processor class discovery to include re-exported classes by disabling module-only filtering in package/file scans. Also broaden subclass-check error handling to catch unexpected exceptions and log full context when discovery encounters problematic objects.
Create `dlclivegui/processors/__init__.py` to re-export `register_processor`, `BaseProcessorSocket`, and `PROCESSOR_REGISTRY` from `dlc_processor_socket`, making these APIs available via package-level imports.
Refactors `dlc_processor_socket.py` by removing the in-file example processors and `OneEuroFilter`, and adds them to a new `dlclivegui/processors/examples.py` module. This separates demonstration/experiment-specific logic from the core socket processor implementation, improving maintainability while preserving existing example processor behavior.
Refines `PLUGIN_SYSTEM.md` to reflect the current processor structure: it now points to `examples.py` for sample implementations and keeps `dlc_processor_socket.py` focused on the socket base class. The registration example was also updated to import `register_processor` and `PROCESSOR_REGISTRY` from `dlclivegui.processors` instead of redefining them inline.
Update processor package discovery to ignore `dlc_processor_socket` during namespace scanning, since it only provides the base class/registry and should not be listed as an available processor source. The package fallback scan now uses default class discovery behavior, and related outdated comments/docstring lines were cleaned up.
Change `register_processor` to log a warning instead of raising on duplicate `PROCESSOR_ID` keys, allowing later registrations to override earlier ones without import-time failures. Update subclass save tests to load processor classes from `dlclivegui.processors.examples` via a dedicated fixture, so the parametrized tests validate the concrete example processors against the correct module data path.
Updates `scan_processor_package` to use a more precise return type annotation (`dict[str, dict]`
Update processor imports to use `from dlclive.processor import Processor` in runtime code to avoid torch import side effects
Moves processor registration and discovery helpers out of `dlc_processor_socket.py` into a new `registry.py` module so registry access no longer depends on importing socket logic. `dlc_processor_socket.py` now imports the shared registry helpers and adds a safe fallback when `dlclive` is unavailable, reducing import-time failures in environments without that dependency. Package exports were updated to expose registry APIs from the new module.
Update the base processor test helper to better mirror the real dlclive package layout by mocking both `dlclive` and `dlclive.processor`, and add a no-op `process` method on the dummy `Processor`. This prevents import/behavior mismatches in tests that rely on the processor interface.
Update `Engine` to inherit from `str, Enum` so enum members behave like strings where needed. Also harden `from_model_type` by coercing non-string inputs (including enum-like values with `.value`) before lowercasing, and raise a clear `ValueError` when conversion is not possible.
Remember the processor folder across sessions and use it when initializing the main window. The folder is now saved when browsing, during refresh (after resolving a valid directory), and on close. Processor refresh messaging was updated to show whether processors came from the selected folder or the built-in package. Settings store gained processor-folder get/set helpers that validate and normalize paths, with safe fallback to defaults when paths are missing or invalid.
Enhance error reporting and handling for video recording. recording_manager now logs exception type, message, and frame shape/dtype when a write fails. VideoRecorder adds detailed messages for frame-size mismatches, queue retrieval errors, and encoding failures (including frame description, expected size, frames_written/frames_enqueued/dropped, and queue_size) and stops the recorder to avoid FFmpeg pipe errors. Introduced _describe_frame to summarize frames and _set_encode_error to centralize creation of a RuntimeError (preserving original exception as __cause__) and set _encode_error under the stats lock. Minor test file newline fix.
f13a8f7 to
0be4614Compare
Closes#87 and closes#88