Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
e437f51
Add debug recorder and environment utilities
C-Achard Apr 14, 2026
81ff6f5
Add build_debug_report helper
C-Achard Apr 14, 2026
493a480
Add DebugTextWindow UI for diagnostics
C-Achard Apr 14, 2026
77db302
Add debug window and move properties to top
C-Achard Apr 14, 2026
2f34a10
Specify napari-dlc in menus; fix debug window
C-Achard Apr 14, 2026
841852b
Add debug logging to KeypointControls
C-Achard Apr 14, 2026
537faec
Allow smaller log buffer and return POSIX paths
C-Achard Apr 14, 2026
8e50bfa
Add tests for debug window and debug utils
C-Achard Apr 14, 2026
6a9128f
Refactor UI actions and form helper methods
C-Achard Apr 14, 2026
0cb40c2
WIP Add LayerLifecycleManager and registry
C-Achard Apr 14, 2026
3cc3df7
Migrate KeypointControls to LayerLifecycleManager
C-Achard Apr 22, 2026
a972898
Switch tests to layer manager API
C-Achard Apr 22, 2026
3c50826
Add tests for layer lifecycle manager and registry
C-Achard Apr 22, 2026
48d7232
Refactor runtime registry and lifecycle manager
C-Achard Apr 22, 2026
90a58c7
Update and expand layer manager/registry tests
C-Achard Apr 22, 2026
c566a96
Deprecate legacy layer lifecycle methods
C-Achard Apr 22, 2026
fd2b0ce
Mark layer setup methods deprecated
C-Achard Apr 22, 2026
1d371b1
Fix deprecated() args calls
C-Achard Apr 22, 2026
c0db41c
Move layer lifecycle wiring into manager
C-Achard Apr 22, 2026
0cec61d
Use public keypoints API; add layer resolver tests
C-Achard Apr 22, 2026
bcf33c0
Update lifecycle manager API and tests
C-Achard Apr 22, 2026
6109409
Move image/project context to lifecycle manager
C-Achard Apr 22, 2026
7944c9f
Propagate DLC session metadata to layers
C-Achard Apr 22, 2026
bc07126
Merge config placeholder points; robust image lookup
C-Achard Apr 22, 2026
a294164
Reject conflicting DLC sessions and notify UI
C-Achard Apr 22, 2026
5855377
Avoid redundant layer refreshes
C-Achard Apr 22, 2026
dc70770
Add scoped timing logs and improve debug output
C-Achard Apr 22, 2026
a67fb0e
Merge branch 'cy/refactor-fix-traj-plot-y-axis-rebased' into cy/layer…
C-Achard Apr 22, 2026
41372e7
Toggle log timing display in in-memory recorder
C-Achard Apr 22, 2026
a9c8138
Fix test timing and clean up debug log
C-Achard Apr 22, 2026
719ab62
Use manager flush instead of qtbot.wait in test
C-Achard Apr 22, 2026
e243200
Fix typos
C-Achard Apr 22, 2026
7f313dc
Add spawn utilities for layer lifecycle manager
C-Achard Apr 23, 2026
84a29c7
Add PointsLayerSetupRequest and re-export helpers
C-Achard Apr 23, 2026
4805a0c
Add ViewerSingletonWidget and widget factory
C-Achard Apr 23, 2026
ff99e9c
Refactor lifecycle: delegate points/image logic to manager
C-Achard Apr 23, 2026
f165ba3
Centralize header/multianimal logic
C-Achard Apr 23, 2026
a2c6682
Refactor lifecycle manager tests to use signals
C-Achard Apr 23, 2026
0ce1418
Remove deprecated code and simplify validation checks
C-Achard Apr 23, 2026
1be08a6
Add points save workflow and UI dialogs
C-Achard Apr 23, 2026
5bd2aff
Delegate points saving to save workflow
C-Achard Apr 23, 2026
f10d687
Patch overwrite confirmation import, clean tests
C-Achard Apr 23, 2026
d576e56
Show project path tooltip in layer status
C-Achard Apr 23, 2026
14ef24f
Clarify tooltip
C-Achard Apr 23, 2026
e7f5566
Block direct-video label saves; add fallbacks
C-Achard Apr 24, 2026
ab3f0fb
Update save.py
C-Achard Apr 24, 2026
1cd5658
Add qtbot fixture and clarify save log
C-Achard Apr 24, 2026
a156acd
Use dummy viewer in store fixture
C-Achard Apr 24, 2026
a6e79a0
Add tests for save workflow and singleton
C-Achard Apr 24, 2026
6e4a070
Don't skip header refresh for same layer
C-Achard Apr 27, 2026
a686875
Switch HDF5 dataset key to df_with_missing
C-Achard Apr 27, 2026
3eddde5
Use DLC_CANONICAL_H5_KEY for HDF reads
C-Achard Apr 27, 2026
d254849
Use 'df_with_missing' HDF key in tests
C-Achard Apr 27, 2026
a0006e0
Restore DataFrame to authoritative DLC header
C-Achard Apr 27, 2026
d53b0ea
Add e2e tests for SA save/merge behavior
C-Achard Apr 27, 2026
77499b6
Use DLC_CANONICAL_H5_KEY for HDF5 reads
C-Achard Apr 27, 2026
034a3cb
Respect multianimal config when writing DLC
C-Achard Apr 27, 2026
edb9621
Preserve column names when replacing scorer
C-Achard Apr 27, 2026
29c9973
Clear stale selection; only advance on change
C-Achard Apr 27, 2026
7b4d113
Sync dropdown visibility to active layer
C-Achard Apr 27, 2026
7858b74
Change tox ini to dev extra reqs
C-Achard Apr 27, 2026
104e4ee
Handle empty DataFrame in guarantee_multiindex_rows
C-Achard Apr 27, 2026
ad46db0
Manage widget timers and replace singleShot
C-Achard Apr 27, 2026
19e55d4
Update _widgets.py
C-Achard Apr 27, 2026
058c73c
Add experimental tracking feature (UI, worker, core)
C-Achard Apr 20, 2026
6d24aa2
Restore plugin tracking manifest
C-Achard Apr 20, 2026
86e60b4
Make tracking shortcuts configurable & conditional
C-Achard Apr 20, 2026
ccd8052
Fix shortcut tests
C-Achard Apr 20, 2026
073408b
Improve tracking feature pipeline and result layers
C-Achard Apr 20, 2026
67e14b1
Fix tracking tests
C-Achard Apr 20, 2026
1f4fb44
Fix incorrect dataclass use
C-Achard Apr 20, 2026
6245b27
Improve tracking tests: seed frames and assertions
C-Achard Apr 20, 2026
ed84020
Remove duplicate worker
C-Achard Apr 20, 2026
62f6c83
Use queued signals and refine TrackingWorker signals
C-Achard Apr 20, 2026
bb63cbf
Refactor TrackingWorker stop flow and logging
C-Achard Apr 20, 2026
ae1ec34
Use CoTracker offline model and fix visibility
C-Achard Apr 20, 2026
26c8ee5
Adjust tracking UI visuals and threading bugfix
C-Achard Apr 20, 2026
d5f1e45
Update src/napari_deeplabcut/tracking/ui/worker.py
C-Achard Apr 22, 2026
0dfa5e8
Update src/napari_deeplabcut/tracking/core/models.py
C-Achard Apr 22, 2026
fd204ff
Update src/napari_deeplabcut/_tests/config/test_keybinds.py
C-Achard Apr 22, 2026
d55efba
Improve tracking widgets and lazy torch import
C-Achard Apr 23, 2026
57653c0
Run pre-commit
C-Achard Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ dev = [
"pytest-qt",
"tox",
]
tracking = [
"torch",
]
[project.urls]
"Bug Tracker" = "https://github.com/DeepLabCut/napari-deeplabcut/issues"
Documentation = "https://github.com/DeepLabCut/napari-deeplabcut#README.md"
Expand Down
71 changes: 66 additions & 5 deletions src/napari_deeplabcut/_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from __future__ import annotations

import logging
from functools import partial
from pathlib import Path

from napari_deeplabcut.config._autostart import maybe_install_keypoint_controls_autostart
from napari_deeplabcut.config.models import DLCProjectContext
from napari_deeplabcut.core.discovery import discover_annotations
from napari_deeplabcut.core.io import (
SUPPORTED_IMAGES,
Expand All @@ -17,11 +19,41 @@
read_images,
read_video,
)
from napari_deeplabcut.core.project_paths import looks_like_dlc_labeled_folder
from napari_deeplabcut.core.project_paths import (
infer_dlc_project_from_labeled_folder,
infer_dlc_project_from_video_path,
looks_like_dlc_labeled_folder,
session_key_from_project_context,
)

logger = logging.getLogger(__name__)


def _build_dlc_layer_meta(
*,
session_role: str | None,
project_context: DLCProjectContext | None,
) -> dict:
"""
Build explicit DLC lifecycle metadata for image/video layers.

If session_role is None or project_context is None, the layer should be
treated as a non-session image/video by lifecycle code.
"""
if session_role is None or project_context is None:
return {
"session_role": None,
"project_context": None,
"session_key": None,
}

return {
"session_role": session_role,
"project_context": project_context.model_dump(mode="python", exclude_none=True),
"session_key": session_key_from_project_context(project_context),
}


def get_hdf_reader(path):
if isinstance(path, list):
path = path[0]
Expand All @@ -40,9 +72,28 @@ def get_image_reader(path):


def get_video_reader(path):
if isinstance(path, str) and any(path.lower().endswith(ext) for ext in SUPPORTED_VIDEOS):
return read_video
return None
if not isinstance(path, str) or not any(path.lower().endswith(ext) for ext in SUPPORTED_VIDEOS):
return None

ctx = infer_dlc_project_from_video_path(path)
if ctx is None:
# Generic non-DLC video layer: allowed, but ignored by lifecycle session context.
return partial(
read_video,
dlc_meta=_build_dlc_layer_meta(
session_role=None,
project_context=None,
),
)

maybe_install_keypoint_controls_autostart()
return partial(
read_video,
dlc_meta=_build_dlc_layer_meta(
session_role="video",
project_context=ctx,
),
)


def get_config_reader(path):
Expand Down Expand Up @@ -87,7 +138,17 @@ def get_folder_parser(path):
)
return None

layers.extend(read_images(images))
ctx = infer_dlc_project_from_labeled_folder(path)

layers.extend(
read_images(
images,
dlc_meta=_build_dlc_layer_meta(
session_role="image",
project_context=ctx,
),
)
)

# Deterministic discovery: load ALL H5 artifacts
artifacts = discover_annotations(path)
Expand Down
8 changes: 6 additions & 2 deletions src/napari_deeplabcut/_tests/config/test_keybinds.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ def bind_key(self, key, callback, overwrite=False):

def test_iter_shortcuts_returns_registry():
shortcuts = tuple(keybinds.iter_shortcuts())
assert shortcuts == keybinds.SHORTCUTS
assert shortcuts, "SHORTCUTS should not be empty"
expected = keybinds.SHORTCUTS
if keybinds.TRACKING_SHORTCUTS_ENABLED:
expected = expected + keybinds.TRACKING_SHORTCUTS

assert shortcuts == expected
assert keybinds.SHORTCUTS, "SHORTCUTS should not be empty"


def test_shortcuts_registry_points_layer_entries_have_callbacks():
Expand Down
39 changes: 37 additions & 2 deletions src/napari_deeplabcut/_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import numpy as np
import pandas as pd
import pytest
from napari.utils.events import Event
from qtpy.QtWidgets import QApplication, QDockWidget
from skimage.io import imsave

Expand Down Expand Up @@ -199,9 +200,43 @@ def images(tmp_path_factory, viewer, fake_image):
return viewer.open(output_path, plugin="napari-deeplabcut")[0]


class DummyDimsForStore:
def __init__(self, nsteps=5, current_step=0):
self.nsteps = (nsteps,)
self.current_step = (current_step,)
self.set_calls = []

def set_current_step(self, axis, value):
self.set_calls.append((axis, value))
steps = list(self.current_step)
while len(steps) <= axis:
steps.append(0)
steps[axis] = value
self.current_step = tuple(steps)


class DummyViewerForStore:
def __init__(self, nsteps=5, current_step=0):
self.dims = DummyDimsForStore(nsteps=nsteps, current_step=current_step)


@pytest.fixture
def store(viewer, points):
return keypoints.KeypointStore(viewer, points)
def store(points):
try:
data = np.asarray(points.data)
nsteps = int(np.nanmax(data[:, 0])) + 1 if data.size else 1
except Exception:
nsteps = 1

viewer = DummyViewerForStore(nsteps=nsteps)
store = keypoints.KeypointStore(viewer, points)

# Mimic the minimal runtime wiring used by LOOP mode
if not hasattr(points.events, "query_next_frame"):
points.events.add(query_next_frame=Event)
points.events.query_next_frame.connect(store._advance_step)

return store


@pytest.fixture
Expand Down
2 changes: 1 addition & 1 deletion src/napari_deeplabcut/_tests/core/io/test_hdf_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _write_h5_single_animal(
)
df = pd.DataFrame([values], index=list(index), columns=cols)
path.parent.mkdir(parents=True, exist_ok=True)
df.to_hdf(path, key="keypoints", mode="w")
df.to_hdf(path, key="df_with_missing", mode="w")
return df


Expand Down
6 changes: 3 additions & 3 deletions src/napari_deeplabcut/_tests/core/io/test_write_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def test_resolve_output_path_returns_none_for_machine_without_save_target():
"project_root": str(Path.cwd()),
"source_relpath_posix": "machinelabels-iter0.h5",
"kind": AnnotationKind.MACHINE,
"dataset_key": "keypoints",
"dataset_key": "df_with_missing",
}
}
}
Expand All @@ -39,7 +39,7 @@ def test_write_hdf_refuses_machine_without_promotion(tmp_path: Path):
"project_root": str(tmp_path),
"source_relpath_posix": "machinelabels-iter0.h5",
"kind": AnnotationKind.MACHINE,
"dataset_key": "keypoints",
"dataset_key": "df_with_missing",
},
# header is required by writer
"header": {
Expand Down Expand Up @@ -84,7 +84,7 @@ def test_write_hdf_aborts_machine_without_promotion_target(tmp_path: Path):
"project_root": str(tmp_path),
"source_relpath_posix": "machinelabels-iter0.h5",
"kind": AnnotationKind.MACHINE,
"dataset_key": "keypoints",
"dataset_key": "df_with_missing",
},
"header": {"columns": [("S", "", "bp1", "x"), ("S", "", "bp1", "y")]},
},
Expand Down
Empty file.
Loading
Loading