Skip to content

Commit dd10a63

Browse files
committed
⚡ perf(label_widget): batch shape visibility updates and disable toggle under active filters
1 parent a936a47 commit dd10a63

1 file changed

Lines changed: 121 additions & 77 deletions

File tree

anylabeling/views/labeling/label_widget.py

Lines changed: 121 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2742,6 +2742,9 @@ def reset_state(self):
27422742
def toggle_select_all(self):
27432743
if self.select_toggle_action is None:
27442744
return
2745+
if self._has_active_shape_filter():
2746+
self._update_select_toggle_button_tooltip()
2747+
return
27452748
if not self.canvas.shapes:
27462749
self._update_select_toggle_button_tooltip()
27472750
return
@@ -2750,9 +2753,23 @@ def toggle_select_all(self):
27502753
self._set_all_objects_visibility(not all_visible)
27512754
self._update_select_toggle_button_tooltip()
27522755

2756+
def _has_active_shape_filter(self):
2757+
current_label = self.label_filter_combobox.text_box.currentText()
2758+
current_gid = self.gid_filter_combobox.gid_box.currentText()
2759+
return bool(current_label) or current_gid not in ["", "-1"]
2760+
27532761
def _update_select_toggle_button_tooltip(self):
27542762
if self.select_toggle_action is None:
27552763
return
2764+
if self._has_active_shape_filter():
2765+
tooltip = self.tr(
2766+
"Toggle shapes visibility is unavailable while a label or group filter is active"
2767+
)
2768+
self.select_toggle_action.setEnabled(False)
2769+
self.select_toggle_action.setToolTip(tooltip)
2770+
self.select_toggle_action.setStatusTip(tooltip)
2771+
return
2772+
self.select_toggle_action.setEnabled(bool(self.canvas.shapes))
27562773
if self._are_all_shapes_visible():
27572774
tooltip = self.tr("Hide all shapes")
27582775
icon = utils.new_icon("eye")
@@ -2776,27 +2793,11 @@ def _are_all_shapes_visible(self):
27762793

27772794
def _set_all_objects_visibility(self, visible):
27782795
"""Set all Objects panel checkboxes and shape visibility to visible (True/False)."""
2779-
model = self.label_list.model()
2780-
model.blockSignals(True)
2781-
try:
2782-
check_state = (
2783-
Qt.CheckState.Checked if visible else Qt.CheckState.Unchecked
2784-
)
2785-
for item in self.label_list:
2786-
item.setCheckState(check_state)
2787-
shape = item.shape()
2788-
shape.visible = visible
2789-
self.canvas.set_shape_visible(shape, visible)
2790-
label = shape.label
2791-
if label in self.label_info:
2792-
self.label_info[label]["visible"] = visible
2793-
finally:
2794-
model.blockSignals(False)
2795-
if (
2796-
hasattr(self, "navigator_dialog")
2797-
and self.navigator_dialog.isVisible()
2798-
):
2799-
self.update_navigator_shapes()
2796+
for item in self.label_list:
2797+
label = item.shape().label
2798+
if label in self.label_info:
2799+
self.label_info[label]["visible"] = visible
2800+
self._sync_label_list_visibility(lambda _item: visible)
28002801

28012802
def reset_attribute(self, text, shape):
28022803
# Skip validation for auto-labeling special constants
@@ -3492,8 +3493,7 @@ def batch_edit_labels(self, shapes):
34923493
)
34933494

34943495
self.set_dirty()
3495-
self.update_combo_box()
3496-
self.update_gid_box()
3496+
self._refresh_shape_filters()
34973497

34983498
def edit_label(self, item=None):
34993499
if item and not isinstance(item, LabelListWidgetItem):
@@ -3577,8 +3577,7 @@ def edit_label(self, item=None):
35773577
else:
35783578
item.setText(f"{shape.label} ({shape.group_id})")
35793579
self.set_dirty()
3580-
self.update_combo_box()
3581-
self.update_gid_box()
3580+
self._refresh_shape_filters()
35823581

35833582
# update top-right attributes panel
35843583
selected_idx = self.canvas.shapes.index(selected_shapes[0])
@@ -4113,7 +4112,7 @@ def shape_selection_changed(self, selected_shapes):
41134112
else:
41144113
self.hide_attributes_panel()
41154114

4116-
def add_label(self, shape, update_last_label=True):
4115+
def add_label(self, shape, update_last_label=True, refresh_filters=True):
41174116
if shape.group_id is None:
41184117
text = shape.label
41194118
else:
@@ -4157,8 +4156,8 @@ def add_label(self, shape, update_last_label=True):
41574156
color = shape.fill_color.getRgb()[:3]
41584157
label_list_item.setText("{}".format(html.escape(text)))
41594158
label_list_item.setBackground(QtGui.QColor(*color, LABEL_OPACITY))
4160-
self.update_combo_box()
4161-
self.update_gid_box()
4159+
if refresh_filters:
4160+
self._refresh_shape_filters()
41624161

41634162
def load_labels(self, labels, clear_existing=True):
41644163
"""
@@ -4222,17 +4221,24 @@ def remove_labels(self, shapes):
42224221
for shape in shapes:
42234222
item = self.label_list.find_item_by_shape(shape)
42244223
self.label_list.remove_item(item)
4225-
self.update_combo_box()
4226-
self.update_gid_box()
4224+
self._refresh_shape_filters()
42274225

42284226
def load_shapes(self, shapes, replace=True, update_last_label=True):
42294227
self._no_selection_slot = True
4230-
for shape in shapes:
4231-
self.add_label(shape, update_last_label=update_last_label)
4232-
self.label_list.clearSelection()
4233-
self._no_selection_slot = False
4228+
self.label_list.setUpdatesEnabled(False)
4229+
try:
4230+
for shape in shapes:
4231+
self.add_label(
4232+
shape,
4233+
update_last_label=update_last_label,
4234+
refresh_filters=False,
4235+
)
4236+
self.label_list.clearSelection()
4237+
finally:
4238+
self.label_list.setUpdatesEnabled(True)
4239+
self._no_selection_slot = False
42344240
self.canvas.load_shapes(shapes, replace=replace)
4235-
self.apply_label_visibility()
4241+
self._refresh_shape_filters()
42364242

42374243
def load_flags(self, flags):
42384244
self.flag_widget.clear()
@@ -4244,18 +4250,57 @@ def load_flags(self, flags):
42444250
)
42454251
self.flag_widget.addItem(item)
42464252

4247-
def apply_label_visibility(self):
4248-
for item in self.label_list:
4249-
label = item.shape().label
4250-
if label in self.label_info:
4251-
is_visible = self.label_info[label].get("visible", True)
4252-
else:
4253-
is_visible = True
4254-
if is_visible:
4255-
item.setCheckState(Qt.CheckState.Checked)
4256-
else:
4257-
item.setCheckState(Qt.CheckState.Unchecked)
4253+
def _sync_label_list_visibility(self, get_visible):
4254+
model = self.label_list.model()
4255+
blocker = QtCore.QSignalBlocker(model)
4256+
self.label_list.setUpdatesEnabled(False)
4257+
try:
4258+
for item in self.label_list:
4259+
is_visible = bool(get_visible(item))
4260+
check_state = (
4261+
Qt.CheckState.Checked
4262+
if is_visible
4263+
else Qt.CheckState.Unchecked
4264+
)
4265+
if item.checkState() != check_state:
4266+
item.setCheckState(check_state)
4267+
shape = item.shape()
4268+
shape.visible = is_visible
4269+
self.canvas.visible[shape] = is_visible
4270+
finally:
4271+
self.label_list.setUpdatesEnabled(True)
4272+
del blocker
4273+
self.canvas.update()
42584274
self._update_select_toggle_button_tooltip()
4275+
if (
4276+
hasattr(self, "navigator_dialog")
4277+
and self.navigator_dialog.isVisible()
4278+
):
4279+
self.update_navigator_shapes()
4280+
4281+
def _refresh_shape_filters(self):
4282+
self.update_combo_box(block_signal=True)
4283+
self.update_gid_box(block_signal=True)
4284+
current_gid = self.gid_filter_combobox.gid_box.currentText()
4285+
if current_gid and current_gid != "-1":
4286+
self.gid_selection_changed(
4287+
self.gid_filter_combobox.gid_box.currentIndex()
4288+
)
4289+
return
4290+
current_label = self.label_filter_combobox.text_box.currentText()
4291+
if current_label:
4292+
self.text_selection_changed(
4293+
self.label_filter_combobox.text_box.currentIndex()
4294+
)
4295+
return
4296+
self.apply_label_visibility()
4297+
4298+
def apply_label_visibility(self):
4299+
self._sync_label_list_visibility(
4300+
lambda item: self.label_info.get(item.shape().label, {}).get(
4301+
"visible", True
4302+
)
4303+
)
42594304

42604305
def update_combo_box(self, block_signal=False):
42614306
current_label = self.label_filter_combobox.text_box.currentText()
@@ -4366,8 +4411,14 @@ def save_labels(self, filename):
43664411
def duplicate_selected_shape(self):
43674412
added_shapes = self.canvas.duplicate_selected_shapes()
43684413
self.label_list.clearSelection()
4369-
for shape in added_shapes:
4370-
self.add_label(shape)
4414+
self.label_list.setUpdatesEnabled(False)
4415+
try:
4416+
for shape in added_shapes:
4417+
self.add_label(shape, refresh_filters=False)
4418+
finally:
4419+
self.label_list.setUpdatesEnabled(True)
4420+
if added_shapes:
4421+
self._refresh_shape_filters()
43714422
self.set_dirty()
43724423

43734424
def paste_selected_shape(self):
@@ -4410,34 +4461,23 @@ def copy_selected_shape(self):
44104461

44114462
def text_selection_changed(self, index):
44124463
label = self.label_filter_combobox.text_box.itemText(index)
4413-
for item in self.label_list:
4414-
item_label = item.shape().label
4415-
if label in ["", item_label]:
4416-
if item_label in self.label_info:
4417-
is_visible = self.label_info[item_label].get(
4418-
"visible", True
4419-
)
4420-
item.setCheckState(
4421-
Qt.CheckState.Checked
4422-
if is_visible
4423-
else Qt.CheckState.Unchecked
4424-
)
4425-
else:
4426-
item.setCheckState(Qt.CheckState.Checked)
4427-
else:
4428-
item.setCheckState(Qt.CheckState.Unchecked)
4464+
self._sync_label_list_visibility(
4465+
lambda item: label in ["", item.shape().label]
4466+
and self.label_info.get(item.shape().label, {}).get(
4467+
"visible", True
4468+
)
4469+
)
44294470

44304471
def gid_selection_changed(self, index):
44314472
gid = self.gid_filter_combobox.gid_box.itemText(index)
4432-
for item in self.label_list:
4433-
if item.shape().group_id is not None:
4434-
checked_gid = ["-1", str(item.shape().group_id)]
4435-
else:
4436-
checked_gid = ["-1"]
4437-
if str(gid) in checked_gid:
4438-
item.setCheckState(Qt.CheckState.Checked)
4439-
else:
4440-
item.setCheckState(Qt.CheckState.Unchecked)
4473+
self._sync_label_list_visibility(
4474+
lambda item: str(gid)
4475+
in (
4476+
["-1", str(item.shape().group_id)]
4477+
if item.shape().group_id is not None
4478+
else ["-1"]
4479+
)
4480+
)
44414481

44424482
def label_selection_changed(self):
44434483
if self._no_selection_slot:
@@ -5183,8 +5223,6 @@ def load_file(self, filename=None): # noqa: C901
51835223
**default_flags,
51845224
**shape.flags,
51855225
}
5186-
self.update_combo_box()
5187-
self.update_gid_box()
51885226
self.load_shapes(self.label_file.shapes, update_last_label=False)
51895227
if self.label_file.flags is not None:
51905228
flags.update(self.label_file.flags)
@@ -5842,8 +5880,14 @@ def delete_selected_shape(self):
58425880

58435881
def copy_shape(self):
58445882
self.canvas.end_move(copy=True)
5845-
for shape in self.canvas.selected_shapes:
5846-
self.add_label(shape)
5883+
self.label_list.setUpdatesEnabled(False)
5884+
try:
5885+
for shape in self.canvas.selected_shapes:
5886+
self.add_label(shape, refresh_filters=False)
5887+
finally:
5888+
self.label_list.setUpdatesEnabled(True)
5889+
if self.canvas.selected_shapes:
5890+
self._refresh_shape_filters()
58475891
self.label_list.clearSelection()
58485892
self.set_dirty()
58495893

0 commit comments

Comments
 (0)