| Age | Commit message (Collapse) | Author |
|
Arduino analog Signal ids used to be the pin name itself, so fixing a
wrong pin silently changed the id and orphaned any plot trace, control,
or filter pipeline pointing at it. Analog Signal ids are now positional
(ai0, ai1, ...) and decoupled from the underlying pin, matching how
digital I/O already worked.
More broadly, formalizes a Device -> Signal -> Channel -> Plot naming
model (SignalConfig for physical device channels, Channel for derived/
computed ones) and adds core/reference_registry.py to propagate any
Signal or Channel id rename through plot traces, controls, and filter
pipelines instead of leaving them dangling. Signal ids are now editable
in the Signals tab with the same propagation.
.labui profile schema bumped to v2.0 with a transparent migration for
old files, including remapping old pin-name Signal ids to the new
positional ones. Removed six confirmed-dead files (two legacy device
classes, four superseded top-level ui/ duplicates).
|
|
Several compounding freeze sources in plugins/motion_capture/:
- tracker.py: _run_real had no frame-rate cap independent of the camera's
configured fps (up to 120fps/2560x1440 was possible), and no bound on
consecutive failed reads — a camera that "opens" (cv2 isOpened() can lie,
seen with Windows MSMF/obsensor) but never actually delivers frames would
get hammered forever. Added a 20Hz processing cap, a real test-read before
committing to a camera, and a give-up-after-30-failures fallback to
simulation.
- tracker.py/device.py: cv2.VideoCapture() opening a real camera is a slow,
blocking call — CameraTracker.start() used to do this synchronously on
the caller's thread, which froze the whole app when ProfileManager.apply()
called it during profile load. Now runs on the background thread; status
reporting (CameraDevice) moved to an on_result callback since the caller
can no longer learn the outcome synchronously.
- window.py: the Camera View preview ran a 30Hz GUI-thread timer doing a
full-frame copy + QImage/QPixmap rebuild every tick unconditionally (no
visibility check) — dropped to 10Hz and skips entirely when hidden.
Camera enumeration (list_cameras(), up to 8 sequential cv2.VideoCapture
probes) also ran inline in the window constructor, blocking it open the
same way camera_panel.py's CameraScanThread already avoided for the Add
Device dialog — moved to the same async pattern here.
config_window.py: separately, ConfigWindow._dev_poll (device status label
refresh) was stopped in closeEvent and never restarted, since MainWindow
reuses the same ConfigWindow instance across opens — invisible before
because connect() was synchronous, but with the above change a device can
sit in CONNECTING for a while, and a stale poll made that look stuck
forever after reopening the window. Restart it in showEvent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
- Removed log file and plugin enabled states.
- Updated esp32_stringPot.ino: added time delay for config screen
and group declaration together.
|
|
Branding:
- Rename app, window title, file extension (.labdaq → .labui), user data
dirs (~/.labui/), spec file (labdaq.spec → labui.spec), and APP_NAME
throughout all source, docs, and config files
Plugin system:
- Plugins no longer bundled in the PyInstaller build — installed at
runtime by users via Settings → Plugins → Install Plugin (zip)
- PluginManager now takes user_dir + extra_scan_dirs; user plugins live
in ~/.labui/plugins/, dev scan additionally covers project plugins/
- install_from_zip / uninstall / is_user_installed added to PluginManager
- vendor/ dir inside plugin zips: prepended to sys.path at load time so
plugins can ship their own deps without requiring pip on end-user machine
- source_url field in manifest.json: shown as Download button in the
missing-plugins dialog when a profile requires an absent plugin
- Frozen-app pip install now targets ~/.labui/plugin_packages/ using a
real system Python (sys.executable is the exe in frozen builds)
Profile loading:
- Profile now stores plugins_manifest snapshot (id, name, version,
source_url) alongside plugins_enabled
- On load, missing or dep-broken plugins trigger MissingPluginsDialog
before the rest of the profile is applied; user can install from zip
or download via source_url in-dialog, or cancel the load
- Plugin reconciliation only enables installed plugins — missing ones
are not written to enabled.json
UI:
- Version label added to status bar (bottom-right, muted colour)
- Settings → Plugins tab: Install Plugin… button, per-plugin Remove
button for user-installed plugins, live list refresh after install/remove
Docs:
- New docs/building.md covers the full release pipeline
- docs/plugin-development.md updated for new install flow, vendoring,
source_url, profile behaviour, and distribution instructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
manifest.json already had an unused "requires" field (motion_capture's
manifest lists opencv-python>=4.8.0) — PluginManifest never parsed it,
so a missing dependency just crashed the import inside _load_plugin(),
caught by the broad except and only ever printed to the console.
Adds PluginManifest.requires, PluginManager.missing_requirements()/
get_missing_dependencies(), and an early check in _load_plugin() that
skips the risky import entirely when a requirement is missing.
main_window.plugin_enable() now checks this before calling
PluginManager.enable() and shows a QMessageBox with the missing
packages and a pip install command instead of failing silently.
Checks by distribution name via importlib.metadata (what pip installed
it as), not import name — those differ for packages like opencv-python
(imports as cv2) or pyserial (imports as serial), so importlib.util.
find_spec() would give false negatives.
Also fixes a button-state bug this surfaces: SettingsWindow's plugin
toggle optimistically flipped to "Disable" the instant Enable was
clicked, before knowing whether enabling actually succeeds — pre-existing,
but now trivially reproducible (any plugin missing a dependency). Enable
no longer flips the button immediately; main_window calls the new
sync_plugin_button() once the real outcome is known.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
DeviceInfo.name existed but was hardcoded per device type and never
operator-editable. Adds a Display Name field to Add Device and makes
the Info-tab Name field in the device config dialog editable, and
threads the name through get_save_config()/ProfileManager.apply() so
a custom name survives a .labdaq save/reload instead of reverting to
the type default. Name is applied post-construction rather than as a
constructor kwarg, since no device factory declares a "name" param.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
motion_capture setting to false
|
|
Profile integration:
- CameraDevice.get_save_config() serialises camera_index, simulate,
resolution, fps — cameras now persist in .labdaq profiles
- ProfileManager.register/unregister_device_factory() lets plugins
register their device classes for profile restoration without
core → plugin dependency
- MotionCapturePlugin registers CameraDevice factory on load/unload
Camera configuration:
- Add Frame Rate combo to CameraPanel (Default/15/24/30/60/120 fps)
defaults to 30 fps; flows through to CAP_PROP_FPS on connect
- tracker.start() accepts fps param alongside existing resolution
Tracking persistence:
- CameraTracker.is_running() — checks thread alive state
- MotionCaptureWindow initialises _connected from tracker.is_running()
so reopening the window resumes the live feed without restarting
the camera or losing the active tracking state
Window state:
- Plugin saves px_per_mm + camera_idx when window closes
- Reopened window restores saved values and syncs mode/shape UI
from tracker state via restore_ui_state() (signals blocked to
avoid resetting tracker)
- Window type changed to Qt.WindowType.Window for independent
close button on all Linux WMs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Tracking:
- Add "template" mode (default) using snapshot + shape-masked matchTemplate
- Add "csrt" mode as alternative for texture-rich targets
- set_roi_rect(x, y, w, h, frame_rgb) initialises from exact selection rect
- set_roi() delegates to set_roi_rect (centred square, backwards-compat)
- start() accepts resolution=(w, h) to set camera capture resolution
Selection UX:
- Replace QLabel feed with FrameSelector widget (custom QWidget)
- "Click to Track" freezes frame; user scrolls to zoom (anchored to cursor),
right-drags to pan, left-drags to draw rubber-band selection
- Shape preview: rectangle or inscribed ellipse depending on mode
- "Confirm Selection" extracts template from frozen frame and starts tracking
- "Clear" cancels selection or clears active tracking
Configuration:
- CameraPanel adds Resolution combo (Default / 640×480 / 720p / 1080p / 1440p)
- CameraDevice stores and forwards resolution to tracker.start()
- Template Match listed first in Mode combo; shape combo hidden for CSRT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
get_toolbar_actions() returns a checkable 🎥 button. On click:
- 0 camera devices → info message, button unchecks
- 1 camera device → opens MotionCaptureWindow directly
- N camera devices → QInputDialog picker, opens selected device's window
Windows tracked per device_id; closing a window removes it from the
map and unchecks the button when none remain. on_unload closes all
open windows before deregistering the Camera panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Plugin now registers a "Camera" option in the Add Device dialog on load
(removes it on unload) instead of auto-creating a single device.
- camera_panel.py (new): CameraPanel with CameraScanThread that probes
OpenCV indices 0–5, lists found cameras with resolution, shows
"No cameras detected" when none available, simulation checkbox.
build_device() returns CameraDevice for the selected index.
- device.py: rewrite MotionCaptureDevice → CameraDevice. Each instance
owns its own CameraTracker created in connect(). Status is CONNECTED
or SIMULATED based on tracker.simulated. get_config_widget() shows
source/status and an "Open Camera View" button that launches the
existing MotionCaptureWindow for that device's tracker. Backward-compat
alias MotionCaptureDevice = CameraDevice kept for saved profiles.
- plugin.py: simplified to on_load/_PANELS registration + pixel_to_mm
filter. No auto-devices, no toolbar button, no settings widget.
Camera data flows through the standard Device → Signal → Channel → Plot
pipeline like any other device.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
|
|
- Add missing QGroupBox import to settings_window (caused silent crash on open)
- Remove Controls tab and _controls_tab() method from settings
- Clamp child window positions to screen bounds so they don't go off-screen on Windows when main window is maximized
- Apply same screen-clamp fix to Motion Capture plugin window
- Delete example plugin; disable it in enabled.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Replace camera index spinbox with a combo listing detected cameras by
name. On Linux enumerates /dev/video* and reads device names from sysfs
(fast, no probing). Fallback probes cv2.VideoCapture indices 0-7 on
other platforms. Simulation entry always appended as last option.
Also adds explicit simulation mode (index -1) to tracker.start() and a
rescan button (⟳) in both the main window and the Settings > Plugins
widget.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Tracks a user-selected point via webcam (OpenCV CSRT tracker) and
streams x_pos / y_pos pixel coordinates as live channels into the
acquisition pipeline. Falls back to a Lissajous figure simulation
when opencv-python is not installed, so the plugin works immediately
without hardware.
- tracker.py: background thread, real camera + sim modes, thread-safe API
- device.py: BaseDevice reading x/y from tracker, wires into engine
- filter.py: custom pixel_to_mm filter (registered in FILTER_CLASSES)
- window.py: floating tool window, live feed, click-to-track ROI selection
- plugin.py: LabPlugin wiring all pieces together, toolbar button,
settings widget, profile save/restore
Also: PluginAction.button_ref_callback lets plugins store a ref to their
toolbar button so they can uncheck it when their window closes.
opencv-python install note added to requirements.txt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Plugins live in plugins/<name>/ with a manifest.json and a LabPlugin
subclass. Enabled/disabled in Settings > Plugins tab; state persists in
plugins/enabled.json and in .labdaq profiles (loading a profile
enables/disables plugins to match). Plugins can contribute toolbar
buttons, BaseDevice instances (auto-wired into acquisition pipeline),
custom signal filter classes, a settings widget, and profile state via
get_save_state/apply_save_state. Load errors now report the exact
failing stage (file not found, class missing, on_load crash).
Includes example plugin skeleton and docs/plugin-development.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|