Skip to content

Commit 4e915f2

Browse files
authored
Merge branch 'main' into keypoint_diff
2 parents 043e89d + eabe0a5 commit 4e915f2

File tree

8 files changed

+58
-26
lines changed

8 files changed

+58
-26
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
include LICENSE
22
include README.md
3+
include src/napari_deeplabcut/assets/*.svg
34

45
recursive-exclude * __pycache__
56
recursive-exclude * *.py[co]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ The easiest way to get started is to drop a folder (typically a folder from with
4545

4646
- `2` and `3`, to easily switch between labeling and selection mode
4747
- `4`, to enable pan & zoom (which is achieved using the mouse wheel or finger scrolling on the Trackpad)
48-
- `M`, to cycle through regular (sequential), quick, and cycle annotation mode (see the description [here](https://github.com/DeepLabCut/DeepLabCut-label/blob/ee71b0e15018228c98db3b88769e8a8f4e2c0454/dlclabel/layers.py#L9-L19))
48+
- `M`, to cycle through regular (sequential), quick, and cycle annotation mode (see the description [here](https://github.com/DeepLabCut/napari-deeplabcut/blob/5a5709dd38868341568d66eab548ae8abf37cd63/src/napari_deeplabcut/keypoints.py#L25-L34))
4949
- `E`, to enable edge coloring (by default, if using this in refinement GUI mode, points with a confidence lower than 0.6 are marked
5050
in red)
5151
- `F`, to toggle between animal and body part color scheme.

src/napari_deeplabcut/_tests/test_keypoints.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,6 @@ def test_store(store, fake_keypoints):
2626
assert store.current_keypoint == kpt
2727
store.next_keypoint()
2828

29-
store.smart_reset(event=None)
30-
assert store.current_keypoint == kpt
31-
assert store.current_label == "kpt_0"
32-
assert store.current_id == "animal_0"
33-
store.current_id = "animal_1"
34-
assert store.current_id == "animal_1"
35-
3629
store._find_first_unlabeled_frame(event=None)
3730
assert store.current_step == store.n_steps - 1
3831
# Remove a frame to test whether it is correctly found

src/napari_deeplabcut/_tests/test_widgets.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ def test_keypoints_dropdown_menu(store):
9090
assert label_menu.count() == 0
9191

9292

93+
def test_keypoints_dropdown_menu_smart_reset(store):
94+
widget = _widgets.KeypointsDropdownMenu(store)
95+
label_menu = widget.menus['label']
96+
label_menu.update_to("kpt_2")
97+
widget._locked = True
98+
widget.smart_reset(event=None)
99+
assert label_menu.currentText() == "kpt_2"
100+
widget._locked = False
101+
widget.smart_reset(event=None)
102+
assert label_menu.currentText() == "kpt_0"
103+
104+
93105
def test_color_pair():
94106
pair = _widgets.LabelPair(color="pink", name="kpt", parent=None)
95107
assert pair.part_name == "kpt"

src/napari_deeplabcut/_widgets.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from napari.layers.utils import color_manager
1616
from napari.utils.events import Event
1717
from napari.utils.history import get_save_history, update_save_history
18-
from qtpy.QtCore import Qt, QTimer, Signal
19-
from qtpy.QtGui import QPainter
18+
from qtpy.QtCore import Qt, QTimer, Signal, QSize
19+
from qtpy.QtGui import QPainter, QIcon
2020
from qtpy.QtWidgets import (
2121
QButtonGroup,
2222
QCheckBox,
@@ -324,6 +324,11 @@ def _store_crop_coordinates(self, *args):
324324

325325
def _form_dropdown_menus(self, store):
326326
menu = KeypointsDropdownMenu(store)
327+
self.viewer.dims.events.current_step.connect(
328+
menu.smart_reset,
329+
position="last",
330+
)
331+
menu.smart_reset(event=None)
327332
self._menus.append(menu)
328333
layout = QVBoxLayout()
329334
layout.addWidget(menu)
@@ -473,11 +478,7 @@ def on_insert(self, event):
473478
layer.events.query_next_frame.connect(store._advance_step)
474479
layer.bind_key("Shift-Right", store._find_first_unlabeled_frame)
475480
layer.bind_key("Shift-Left", store._find_first_unlabeled_frame)
476-
self.viewer.dims.events.current_step.connect(
477-
store.smart_reset,
478-
position="last",
479-
)
480-
store.smart_reset(event=None)
481+
481482
layer.bind_key("Down", store.next_keypoint, overwrite=True)
482483
layer.bind_key("Up", store.prev_keypoint, overwrite=True)
483484
layer.face_color_mode = "cycle"
@@ -579,6 +580,7 @@ def __init__(
579580
super().__init__(parent)
580581
self.store = store
581582
self.store.layer.events.current_properties.connect(self.update_menus)
583+
self._locked = False
582584

583585
self.id2label = defaultdict(list)
584586
self.menus = dict()
@@ -591,6 +593,11 @@ def __init__(
591593
layout2 = QVBoxLayout()
592594
for menu in self.menus.values():
593595
layout2.addWidget(menu)
596+
self.lock_button = QPushButton("Lock selection")
597+
self.lock_button.setIcon(QIcon('src/napari_deeplabcut/assets/unlock.svg'))
598+
self.lock_button.setIconSize(QSize(24, 24))
599+
self.lock_button.clicked.connect(self._lock_current_keypoint)
600+
layout2.addWidget(self.lock_button)
594601
group_box.setLayout(layout2)
595602
layout1.addWidget(group_box)
596603
self.setLayout(layout1)
@@ -618,6 +625,15 @@ def _update_items(self):
618625
self.menus["id"].update_items(list(self.id2label))
619626
self.menus["label"].update_items(self.id2label[id_])
620627

628+
def _lock_current_keypoint(self):
629+
self._locked = not self._locked
630+
if self._locked:
631+
self.lock_button.setText("Unlock selection")
632+
self.lock_button.setIcon(QIcon('src/napari_deeplabcut/assets/lock.svg'))
633+
else:
634+
self.lock_button.setText("Lock selection")
635+
self.lock_button.setIcon(QIcon('src/napari_deeplabcut/assets/unlock.svg'))
636+
621637
def update_menus(self, event):
622638
keypoint = self.store.current_keypoint
623639
for attr, menu in self.menus.items():
@@ -632,6 +648,18 @@ def refresh_label_menu(self, text: str):
632648
menu.blockSignals(False)
633649
menu.addItems(self.id2label[text])
634650

651+
def smart_reset(self, event):
652+
"""Set current keypoint to the first unlabeled one."""
653+
if self._locked:
654+
return
655+
unannotated = ""
656+
already_annotated = self.store.annotated_keypoints
657+
for keypoint in self.store._keypoints:
658+
if keypoint not in already_annotated:
659+
unannotated = keypoint
660+
break
661+
self.store.current_keypoint = unannotated if unannotated else self.store._keypoints[0]
662+
635663

636664
def create_dropdown_menu(store, items, attr):
637665
menu = DropdownMenu(items)
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

src/napari_deeplabcut/keypoints.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class LabelMode(CycleEnum):
3131
annotated point actually moves it to the cursor location.
3232
LOOP: the first point is placed frame by frame, then it wraps
3333
to the next label at the end and restart from frame 1, etc.
34+
Unless the keypoint selection is locked, the dropdown menu is
35+
automatically set to the first unlabeled keypoint of
36+
the current frame.
3437
"""
3538

3639
SEQUENTIAL = auto()
@@ -49,7 +52,10 @@ def default(cls):
4952
"QUICK": "Similar to SEQUENTIAL, but trying to add an already\n"
5053
"annotated point actually moves it to the cursor location.",
5154
"LOOP": "The first point is placed frame by frame, then it wraps\n"
52-
"to the next label at the end and restart from frame 1, etc.",
55+
"to the next label at the end and restart from frame 1, etc.\n"
56+
"Unless the keypoint selection is locked, the dropdown menu is\n"
57+
"automatically set to the first unlabeled keypoint of\n"
58+
"the current frame.",
5359
}
5460

5561

@@ -108,16 +114,6 @@ def current_keypoint(self, keypoint: Keypoint):
108114
current_properties["id"] = np.asarray([keypoint.id])
109115
self.layer.current_properties = current_properties
110116

111-
def smart_reset(self, event):
112-
"""Set current keypoint to the first unlabeled one."""
113-
unannotated = ""
114-
already_annotated = self.annotated_keypoints
115-
for keypoint in self._keypoints:
116-
if keypoint not in already_annotated:
117-
unannotated = keypoint
118-
break
119-
self.current_keypoint = unannotated if unannotated else self._keypoints[0]
120-
121117
def next_keypoint(self, *args):
122118
ind = self._keypoints.index(self.current_keypoint) + 1
123119
if ind <= len(self._keypoints) - 1:

0 commit comments

Comments
 (0)