imagebaker 0.0.50__py3-none-any.whl → 0.0.53__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,6 +40,7 @@ from imagebaker.core.defs import (
40
40
  from collections import deque
41
41
  from typing import Deque
42
42
  from dataclasses import dataclass
43
+ import os
43
44
 
44
45
 
45
46
  @dataclass
@@ -103,13 +104,17 @@ class LayerifyTab(QWidget):
103
104
  """Connect all necessary signals"""
104
105
  # Connect all layers in the deque to annotation list
105
106
  for layer in self.annotable_layers:
106
- layer.annotationAdded.connect(self.annotation_list.update_list)
107
- layer.annotationUpdated.connect(self.annotation_list.update_list)
107
+ # layer.annotationAdded.connect(self.annotation_list.update_list)
108
+ layer.annotationAdded.connect(self.on_annotation_added)
109
+ # layer.annotationUpdated.connect(self.annotation_list.update_list)
110
+ layer.annotationUpdated.connect(self.on_annotation_updated)
108
111
  layer.messageSignal.connect(self.messageSignal)
109
112
  layer.layerSignal.connect(self.add_layer)
113
+ layer.labelUpdated.connect(self.on_label_update)
110
114
 
111
115
  # Connect image list panel signals
112
116
  self.image_list_panel.imageSelected.connect(self.on_image_selected)
117
+ self.image_list_panel.activeImageEntries.connect(self.update_active_entries)
113
118
 
114
119
  def init_ui(self):
115
120
  """Initialize the UI components"""
@@ -118,7 +123,9 @@ class LayerifyTab(QWidget):
118
123
  None, parent=self.main_window, max_name_length=self.config.max_name_length
119
124
  )
120
125
  self.image_list_panel = ImageListPanel(
121
- self.image_entries, self.processed_images
126
+ self.image_entries,
127
+ self.processed_images,
128
+ images_per_page=self.config.deque_maxlen,
122
129
  )
123
130
 
124
131
  self.main_window.addDockWidget(Qt.LeftDockWidgetArea, self.image_list_panel)
@@ -162,10 +169,14 @@ class LayerifyTab(QWidget):
162
169
  for idx, layer in enumerate(self.annotable_layers):
163
170
  layer.setVisible(False)
164
171
  # logger.info(f"Layer {idx} hidden.")
172
+ current_label = self.layer.current_label
173
+ current_color = self.layer.current_color
165
174
 
166
175
  if not image_entry.is_baked_result: # Regular image
167
176
  image_path = image_entry.data
168
177
  self.curr_image_idx = self.image_entries.index(image_entry)
178
+ # convert curr_image_idx to correct index
179
+ self.curr_image_idx = self.curr_image_idx % len(self.annotable_layers)
169
180
 
170
181
  # Make the corresponding layer visible and set the image
171
182
  selected_layer = self.annotable_layers[self.curr_image_idx]
@@ -185,6 +196,9 @@ class LayerifyTab(QWidget):
185
196
  # logger.info(f"Layer {self.curr_image_idx} made visible for baked result.")
186
197
  self.layer = baked_result_layer # Set the baked result as the current layer
187
198
 
199
+ # Set the current label and color
200
+ self.layer.current_label = current_label
201
+ self.layer.current_color = current_color
188
202
  self.annotation_list.layer = self.layer
189
203
  self.annotation_list.update_list()
190
204
 
@@ -206,11 +220,17 @@ class LayerifyTab(QWidget):
206
220
  for i, layer in enumerate(self.annotable_layers):
207
221
  if i < len(self.image_entries):
208
222
  layer.set_image(self.image_entries[i].data)
223
+ self.load_layer_annotations(layer)
224
+ layer.layer_name = f"Layer_{i + 1}"
209
225
  layer.setVisible(
210
226
  i == 0
211
227
  ) # Only the first layer is visible by default
212
228
  if i == 0:
213
229
  self.layer = layer # Set the first layer as the current layer
230
+ # Select the first item in the image list panel's list widget
231
+ if self.image_list_panel.list_widget.count() > 0:
232
+ self.image_list_panel.list_widget.setCurrentRow(0)
233
+
214
234
  else:
215
235
  layer.setVisible(False)
216
236
 
@@ -224,12 +244,80 @@ class LayerifyTab(QWidget):
224
244
  self.image_list_panel.update_image_list(self.image_entries)
225
245
  self.update()
226
246
 
247
+ def save_layer_annotations(
248
+ self, layer: AnnotableLayer, save_dir: Path | None = None
249
+ ):
250
+ """Save annotations for a specific layer"""
251
+ if len(layer.annotations) > 0:
252
+ file_path = layer.file_path
253
+ file_name = file_path.name
254
+ if save_dir is None:
255
+ # Save to the cache directory
256
+ save_dir = self.config.cache_dir
257
+ save_dir = save_dir / f"{file_name}.json"
258
+ Annotation.save_as_json(layer.annotations, save_dir)
259
+ logger.info(f"Saved annotations for {layer.layer_name} to {save_dir}")
260
+
261
+ def load_layer_annotations(
262
+ self, layer: AnnotableLayer, load_dir: Path | None = None
263
+ ):
264
+ """Load annotations for a specific layer"""
265
+ if layer.file_path:
266
+ file_path = layer.file_path
267
+ file_name = file_path.name
268
+ if load_dir is None:
269
+ load_dir = self.config.cache_dir
270
+ load_dir = load_dir / f"{file_name}.json"
271
+ if load_dir.exists():
272
+ layer.annotations = Annotation.load_from_json(load_dir)
273
+ logger.info(
274
+ f"Loaded annotations for {layer.layer_name} from {load_dir}"
275
+ )
276
+ else:
277
+ logger.warning(f"No annotations found for {layer.layer_name}")
278
+
279
+ def update_active_entries(self, image_entries: list[ImageEntry]):
280
+ """Update the active entries in the image list panel."""
281
+ self.curr_image_idx = 0
282
+ for i, layer in enumerate(self.annotable_layers):
283
+ self.save_layer_annotations(layer)
284
+ layer.annotations = []
285
+
286
+ if i < len(image_entries):
287
+ # get index on the self.image_entries
288
+ idx = self.image_entries.index(image_entries[i])
289
+ if self.image_entries[idx].is_baked_result:
290
+ # if the image is a baked result, set the layer to the baked result
291
+ layer = self.image_entries[idx].data
292
+ layer.file_path = layer.file_path
293
+ else:
294
+ layer.set_image(self.image_entries[idx].data)
295
+ self.load_layer_annotations(layer)
296
+
297
+ layer.layer_name = f"Layer_{idx + 1}"
298
+ layer.setVisible(i == 0)
299
+ if i == 0:
300
+ self.layer = layer
301
+ # update annotation list to the first layer
302
+ self.annotation_list.layer = self.layer
303
+ self.annotation_list.update_list()
304
+ else:
305
+ layer.setVisible(False)
306
+ logger.info("Updated active entries in image list panel.")
307
+
227
308
  def clear_annotations(self):
228
309
  """Safely clear all annotations"""
229
310
  try:
230
311
  # Clear layer annotations
231
312
  self.clearAnnotations.emit()
232
313
  self.messageSignal.emit("Annotations cleared")
314
+ # clear cache annotation of layer
315
+ annotation_path = (
316
+ self.config.cache_dir / f"{self.layer.file_path.name}.json"
317
+ )
318
+ if annotation_path.exists():
319
+ os.remove(annotation_path)
320
+ logger.info(f"Cleared annotations from {annotation_path}")
233
321
 
234
322
  except Exception as e:
235
323
  logger.error(f"Clear error: {str(e)}")
@@ -241,14 +329,17 @@ class LayerifyTab(QWidget):
241
329
  Args:
242
330
  annotation (Annotation): The annotation that was added.
243
331
  """
244
- if annotation.label not in self.config.predefined_labels:
332
+
333
+ # if annotation.label is not in the predefined labels, add it
334
+ if annotation.label not in [lbl.name for lbl in self.config.predefined_labels]:
335
+ logger.info(f"Label {annotation.label} created.")
245
336
  self.config.predefined_labels.append(
246
337
  Label(annotation.label, annotation.color)
247
338
  )
248
339
  self.update_label_combo()
249
340
  logger.info(f"Added annotation: {annotation.label}")
250
341
  self.messageSignal.emit(f"Added annotation: {annotation.label}")
251
-
342
+ self.save_layer_annotations(self.layer)
252
343
  # Refresh the annotation list
253
344
  self.annotation_list.update_list()
254
345
 
@@ -259,11 +350,12 @@ class LayerifyTab(QWidget):
259
350
  Args:
260
351
  annotation (Annotation): The updated annotation.
261
352
  """
262
- logger.info(f"Updated annotation: {annotation.label}")
353
+ # logger.info(f"Updated annotation: {annotation}")
263
354
  self.messageSignal.emit(f"Updated annotation: {annotation.label}")
264
355
 
265
356
  # Refresh the annotation list
266
357
  self.annotation_list.update_list()
358
+ self.save_layer_annotations(self.layer)
267
359
 
268
360
  def update_label_combo(self):
269
361
  """
@@ -276,6 +368,27 @@ class LayerifyTab(QWidget):
276
368
  pixmap = QPixmap(16, 16)
277
369
  pixmap.fill(label.color)
278
370
  self.label_combo.addItem(QIcon(pixmap), label.name)
371
+ logger.info("Updated label combo box with predefined labels.")
372
+ self.label_combo.setCurrentText(self.current_label)
373
+
374
+ def on_label_update(self, old_new_label: tuple[str, str]):
375
+ new_labels = []
376
+ index = 0
377
+ for i, label in enumerate(self.config.predefined_labels):
378
+ if label.name == old_new_label[0]:
379
+ label.name = old_new_label[1]
380
+ index = i
381
+ new_labels.append(label)
382
+
383
+ self.config.predefined_labels = new_labels
384
+ logger.info(f"Updated label from {old_new_label[0]} to {old_new_label[1]}")
385
+ self.messageSignal.emit(
386
+ f"Updated label from {old_new_label[0]} to {old_new_label[1]}."
387
+ )
388
+
389
+ self.update_label_combo()
390
+ self.handle_label_change(index=index)
391
+ self.label_combo.update()
279
392
 
280
393
  def load_default_image(self):
281
394
  """
@@ -471,6 +584,34 @@ class LayerifyTab(QWidget):
471
584
  QMessageBox.warning(self, "Warning", "No annotations to save!")
472
585
  return
473
586
 
587
+ # an option to save all annotations (i.e. from all layers) or just the current layer
588
+ options = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
589
+ # Change the "No" button text to "Just This Layer"
590
+ msg_box = QMessageBox(self)
591
+ msg_box.setWindowTitle("Save Annotations")
592
+ msg_box.setText("Do you want to save annotations from all layers?")
593
+ yes_button = msg_box.addButton("All Layers", QMessageBox.YesRole)
594
+ just_this_button = msg_box.addButton("Just This Layer", QMessageBox.NoRole)
595
+ msg_box.setDefaultButton(yes_button)
596
+ msg_box.exec()
597
+ save_all = msg_box.clickedButton() == yes_button
598
+
599
+ if save_all:
600
+ # folder select dialog
601
+ save_dir = QFileDialog.getExistingDirectory(
602
+ self, "Select Save Directory", str(self.config.cache_dir)
603
+ )
604
+ if not save_dir:
605
+ QMessageBox.warning(self, "Warning", "No directory selected!")
606
+ return
607
+ for layer in self.annotable_layers:
608
+ if layer.annotations:
609
+ self.save_layer_annotations(layer, Path(save_dir))
610
+ QMessageBox.information(
611
+ self, "Success", "All annotations saved successfully!"
612
+ )
613
+ return
614
+
474
615
  options = QFileDialog.Options()
475
616
  file_name, _ = QFileDialog.getSaveFileName(
476
617
  self, "Save Annotations", "", "JSON Files (*.json)", options=options
@@ -493,6 +634,34 @@ class LayerifyTab(QWidget):
493
634
  """
494
635
  Load annotations from a JSON file.
495
636
  """
637
+ # dialog box to load all annotations or just the current layer
638
+ options = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
639
+ # Change the "No" button text to "Just This Layer"
640
+ msg_box = QMessageBox(self)
641
+ msg_box.setWindowTitle("Load Annotations")
642
+ msg_box.setText("Do you want to load annotations from all layers?")
643
+ yes_button = msg_box.addButton("All Layers", QMessageBox.YesRole)
644
+ just_this_button = msg_box.addButton("Just This Layer", QMessageBox.NoRole)
645
+ msg_box.setDefaultButton(yes_button)
646
+ msg_box.exec()
647
+ load_all = msg_box.clickedButton() == yes_button
648
+ if load_all:
649
+ # folder select dialog
650
+ load_dir = QFileDialog.getExistingDirectory(
651
+ self, "Select Load Directory", str(self.config.cache_dir)
652
+ )
653
+ if not load_dir:
654
+ QMessageBox.warning(self, "Warning", "No directory selected!")
655
+ return
656
+ for layer in self.annotable_layers:
657
+ layer.annotations = []
658
+ layer.update()
659
+ self.load_layer_annotations(layer, Path(load_dir))
660
+ self.update_annotation_list()
661
+ QMessageBox.information(
662
+ self, "Success", "All annotations loaded successfully!"
663
+ )
664
+ return
496
665
  options = QFileDialog.Options()
497
666
  file_name, _ = QFileDialog.getOpenFileName(
498
667
  self, "Load Annotations", "", "JSON Files (*.json)", options=options
@@ -589,6 +758,24 @@ class LayerifyTab(QWidget):
589
758
  )
590
759
  msg = f"Label changed to {self.current_label}"
591
760
  self.messageSignal.emit(msg)
761
+ self.layer.selected_annotation = self.layer._get_selected_annotation()
762
+ if self.layer.selected_annotation:
763
+ annotations = []
764
+ for ann in self.layer.annotations:
765
+ if ann == self.layer.selected_annotation:
766
+ ann.label = label_info.name
767
+ ann.color = label_info.color
768
+ annotations.append(ann)
769
+
770
+ self.layer.annotations = annotations
771
+ self.on_annotation_updated(self.layer.selected_annotation)
772
+ # disable label change callback
773
+ self.label_combo.currentIndexChanged.disconnect()
774
+ self.label_combo.currentIndexChanged.connect(lambda: None)
775
+ self.update_label_combo()
776
+ # set it back
777
+ self.label_combo.currentIndexChanged.connect(self.handle_label_change)
778
+
592
779
  self.layer.update()
593
780
  self.update()
594
781
 
@@ -628,7 +815,7 @@ class LayerifyTab(QWidget):
628
815
  ("🎨", "Color", self.choose_color),
629
816
  ("🧅", "Layerify All", self.layerify_all),
630
817
  ("🏷️", "Add Label", self.add_new_label),
631
- ("🗑️", "Clear", lambda x: self.clearAnnotations.emit()),
818
+ ("🗑️", "Clear", self.clear_annotations),
632
819
  ]
633
820
 
634
821
  # Folder navigation buttons
@@ -636,18 +823,6 @@ class LayerifyTab(QWidget):
636
823
  self.select_folder_btn.clicked.connect(self.select_folder)
637
824
  toolbar_layout.addWidget(self.select_folder_btn)
638
825
 
639
- self.next_image_btn = QPushButton("Next")
640
- self.next_image_btn.clicked.connect(self.show_next_image)
641
- toolbar_layout.addWidget(self.next_image_btn)
642
-
643
- self.prev_image_btn = QPushButton("Prev")
644
- self.prev_image_btn.clicked.connect(self.show_prev_image)
645
- toolbar_layout.addWidget(self.prev_image_btn)
646
-
647
- # Initially hide next/prev buttons
648
- self.next_image_btn.setVisible(False)
649
- self.prev_image_btn.setVisible(False)
650
-
651
826
  # Add mode buttons
652
827
  for icon, text, mode in modes:
653
828
  btn_txt = icon + text
@@ -703,11 +878,25 @@ class LayerifyTab(QWidget):
703
878
  ImageEntry(is_baked_result=False, data=img_path)
704
879
  )
705
880
 
881
+ # load from bake folder if it exists
882
+ bake_folder = self.config.bake_dir
883
+ if bake_folder.exists() and bake_folder.is_dir():
884
+ for img_path in bake_folder.glob("*.*"):
885
+ if img_path.suffix.lower() in [
886
+ ".jpg",
887
+ ".jpeg",
888
+ ".png",
889
+ ".bmp",
890
+ ".tiff",
891
+ ]:
892
+ self.image_entries.append(
893
+ ImageEntry(is_baked_result=False, data=img_path)
894
+ )
895
+
706
896
  def select_folder(self):
707
897
  """Allow the user to select a folder and load images from it."""
708
898
  folder_path = QFileDialog.getExistingDirectory(self, "Select Folder")
709
899
  if folder_path:
710
- self.image_entries = [] # Clear the existing image paths
711
900
  folder_path = Path(folder_path)
712
901
 
713
902
  self._load_images_from_folder(folder_path)
@@ -726,9 +915,6 @@ class LayerifyTab(QWidget):
726
915
  # Load the first set of images into the layers
727
916
  self.load_default_images()
728
917
 
729
- # Unhide the next/prev buttons if there are multiple images
730
- self.next_image_btn.setVisible(len(self.image_entries) > 1)
731
- self.prev_image_btn.setVisible(len(self.image_entries) > 1)
732
918
  else:
733
919
  QMessageBox.warning(
734
920
  self,
@@ -736,28 +922,6 @@ class LayerifyTab(QWidget):
736
922
  "No valid image files found in the selected folder.",
737
923
  )
738
924
 
739
- def show_next_image(self):
740
- """Show next image in the list. If at the end, show first image."""
741
- if self.curr_image_idx < len(self.image_entries) - 1:
742
- self.curr_image_idx += 1
743
- else:
744
- self.curr_image_idx = 0
745
- self.layer.set_image(self.image_entries[self.curr_image_idx]["data"])
746
- self.messageSignal.emit(
747
- f"Showing image {self.curr_image_idx + 1}/{len(self.image_entries)}"
748
- )
749
-
750
- def show_prev_image(self):
751
- """Show previous image in the list. If at the start, show last image."""
752
- if self.curr_image_idx > 0:
753
- self.curr_image_idx -= 1
754
- else:
755
- self.curr_image_idx = len(self.image_entries) - 1
756
- self.layer.set_image(self.image_entries[self.curr_image_idx]["data"])
757
- self.messageSignal.emit(
758
- f"Showing image {self.curr_image_idx + 1}/{len(self.image_entries)}"
759
- )
760
-
761
925
  def __del__(self):
762
926
  logger.warning(f"Tab {id(self)} deleted")
763
927
 
@@ -770,10 +934,20 @@ class LayerifyTab(QWidget):
770
934
  config=self.config,
771
935
  canvas_config=self.canvas_config,
772
936
  )
773
- layer.annotations = baking_result.annotations
937
+ # save it in cache
938
+ filename = baking_result.filename
939
+ filepath = self.config.bake_dir / filename.name
940
+ baking_result.image.save(str(filepath))
941
+ #
942
+ Annotation.save_as_json(
943
+ baking_result.annotations, self.config.cache_dir / f"{filename.name}.json"
944
+ )
774
945
 
775
- layer.annotationAdded.connect(self.annotation_list.update_list)
776
- layer.annotationUpdated.connect(self.annotation_list.update_list)
946
+ layer.set_image(filepath)
947
+
948
+ layer.annotationAdded.connect(self.on_annotation_added)
949
+ layer.annotationUpdated.connect(self.on_annotation_updated)
950
+ layer.labelUpdated.connect(self.on_label_update)
777
951
  layer.messageSignal.connect(self.messageSignal)
778
952
  layer.layerSignal.connect(self.add_layer)
779
953
 
@@ -785,7 +959,7 @@ class LayerifyTab(QWidget):
785
959
  self.annotable_layers.append(layer)
786
960
 
787
961
  # Add baked result to image_entries
788
- baked_result_entry = ImageEntry(is_baked_result=True, data=layer)
962
+ baked_result_entry = ImageEntry(is_baked_result=False, data=filepath)
789
963
  self.image_entries.append(baked_result_entry)
790
964
  # baking_result.image.save(str(baking_result.filename))
791
965
  layer.update()
@@ -793,6 +967,12 @@ class LayerifyTab(QWidget):
793
967
  logger.info("A baked result has arrived, adding it to the image list.")
794
968
 
795
969
  # Update the image list panel
970
+ # find the page index where this layer is
971
+ page_index = (
972
+ self.image_entries.index(baked_result_entry) // self.config.deque_maxlen
973
+ )
974
+ # set the current page to the page index
975
+ self.image_list_panel.current_page = page_index
796
976
  self.image_list_panel.update_image_list(self.image_entries)
797
977
  self.image_list_panel.imageSelected.emit(baked_result_entry)
798
978
 
@@ -831,6 +1011,52 @@ class LayerifyTab(QWidget):
831
1011
  self.annotation_list.update_list()
832
1012
  logger.info("Selected annotation deleted.")
833
1013
 
1014
+ # if clicked q, set the mode to point
1015
+ elif key == Qt.Key_Q:
1016
+ self.layer.set_mode(MouseMode.POINT)
1017
+ logger.info("Mouse mode set to POINT.")
1018
+ # if clicked w, set the mode to polygon
1019
+ elif key == Qt.Key_W:
1020
+ self.layer.set_mode(MouseMode.POLYGON)
1021
+ logger.info("Mouse mode set to POLYGON.")
1022
+ # if clicked e, set the mode to rectangle
1023
+ elif key == Qt.Key_E:
1024
+ self.layer.set_mode(MouseMode.RECTANGLE)
1025
+ logger.info("Mouse mode set to RECTANGLE.")
1026
+ # if pressed h key, then toggle visibility of annotations
1027
+ elif event.key() == Qt.Key_H:
1028
+ self.layer.toggle_annotation_visibility()
1029
+
1030
+ # if selected l, then run layerify the selected annotations
1031
+ elif event.key() == Qt.Key_L:
1032
+ if self.layer and self.layer.annotations:
1033
+ selected_annotations = [
1034
+ ann for ann in self.layer.annotations if ann.selected
1035
+ ]
1036
+ if selected_annotations:
1037
+ logger.info(
1038
+ f"Layerifying {len(selected_annotations)} selected annotations."
1039
+ )
1040
+ self.layer.layerify_annotation(selected_annotations)
1041
+ else:
1042
+ # layerify all annotations in the current layer
1043
+ self.layer.layerify_annotation(self.layer.annotations)
1044
+ logger.info("Layerified all annotations in the current layer.")
1045
+ else:
1046
+ logger.warning("No annotations to layerify.")
1047
+ elif event.key() == Qt.Key_C:
1048
+ # if an annotation is selected, open a dialog box
1049
+ # in that dialog box, ask for a caption
1050
+ self.layer.selected_annotation = self.layer._get_selected_annotation()
1051
+ if self.layer.selected_annotation:
1052
+ current_caption = getattr(self.layer.selected_annotation, "caption", "")
1053
+ text, ok = QInputDialog.getMultiLineText(
1054
+ self, "Edit Caption", "Enter caption:", current_caption
1055
+ )
1056
+ if ok:
1057
+ self.layer.selected_annotation.caption = text
1058
+ self.layer.update()
1059
+
834
1060
  # Pass the event to the annotation list if it needs to handle it
835
1061
  if self.annotation_list.hasFocus():
836
1062
  self.annotation_list.keyPressEvent(event)
imagebaker/utils/image.py CHANGED
@@ -49,8 +49,11 @@ def draw_annotations(image: np.ndarray, annotations: list[Annotation]) -> np.nda
49
49
  Returns:
50
50
  np.ndarray: Image with annotations drawn.
51
51
  """
52
+ color = (0, 255, 0, 255) if image.shape[2] == 4 else (0, 255, 0)
53
+
52
54
  for i, ann in enumerate(annotations):
53
55
  if ann.rectangle:
56
+ # if image has alpha channel, make color full alpha
54
57
  cv2.rectangle(
55
58
  image,
56
59
  (int(ann.rectangle.x()), int(ann.rectangle.y())),
@@ -58,7 +61,7 @@ def draw_annotations(image: np.ndarray, annotations: list[Annotation]) -> np.nda
58
61
  int(ann.rectangle.x() + ann.rectangle.width()),
59
62
  int(ann.rectangle.y() + ann.rectangle.height()),
60
63
  ),
61
- (0, 255, 0),
64
+ color,
62
65
  2,
63
66
  )
64
67
  rect_center = ann.rectangle.center()
@@ -69,7 +72,7 @@ def draw_annotations(image: np.ndarray, annotations: list[Annotation]) -> np.nda
69
72
  (int(rect_center.x()), int(rect_center.y())),
70
73
  cv2.FONT_HERSHEY_SIMPLEX,
71
74
  1,
72
- (0, 255, 0),
75
+ color,
73
76
  2,
74
77
  )
75
78
  elif ann.polygon:
@@ -77,7 +80,7 @@ def draw_annotations(image: np.ndarray, annotations: list[Annotation]) -> np.nda
77
80
  image,
78
81
  [np.array([[int(p.x()), int(p.y())] for p in ann.polygon])],
79
82
  True,
80
- (0, 255, 0),
83
+ color,
81
84
  2,
82
85
  )
83
86
  polygon_center = ann.polygon.boundingRect().center()
@@ -87,19 +90,19 @@ def draw_annotations(image: np.ndarray, annotations: list[Annotation]) -> np.nda
87
90
  (int(polygon_center.x()), int(polygon_center.y())),
88
91
  cv2.FONT_HERSHEY_SIMPLEX,
89
92
  1,
90
- (0, 255, 0),
93
+ color,
91
94
  2,
92
95
  )
93
96
  elif ann.points:
94
97
  for p in ann.points:
95
- cv2.circle(image, (int(p.x()), int(p.y())), 5, (0, 255, 0), -1)
98
+ cv2.circle(image, (int(p.x()), int(p.y())), 5, color, -1)
96
99
  cv2.putText(
97
100
  image,
98
101
  ann.label,
99
102
  (int(ann.points[0].x()), int(ann.points[0].y())),
100
103
  cv2.FONT_HERSHEY_SIMPLEX,
101
104
  1,
102
- (0, 255, 0),
105
+ color,
103
106
  2,
104
107
  )
105
108
  return image
@@ -66,6 +66,7 @@ def calculate_intermediate_states(
66
66
  + (current_state.edge_opacity - previous_state.edge_opacity) * (i / steps),
67
67
  edge_width=previous_state.edge_width
68
68
  + (current_state.edge_width - previous_state.edge_width) * (i / steps),
69
+ caption=previous_state.caption, # Assuming caption is the same in both states
69
70
  )
70
71
 
71
72
  # Deep copy the drawing_states from the previous_state
@@ -175,9 +175,80 @@ class MainWindow(QMainWindow):
175
175
  self.status_bar.showMessage(status_text)
176
176
 
177
177
  def closeEvent(self, event):
178
- # Clean up tabs first
179
- if hasattr(self, "layerify_tab"):
180
- self.layerify_tab.deleteLater()
181
- if hasattr(self, "baker_tab"):
182
- self.baker_tab.deleteLater()
183
- super().closeEvent(event)
178
+ logger.info("Closing the application.")
179
+ if self.layerify_config.cleanup_on_exit:
180
+ import shutil
181
+
182
+ if self.layerify_config.bake_dir.exists():
183
+ shutil.rmtree(self.layerify_config.bake_dir)
184
+ logger.info(f"Deleted bake directory: {self.layerify_config.bake_dir}")
185
+ if self.layerify_config.cache_dir.exists():
186
+ shutil.rmtree(self.layerify_config.cache_dir)
187
+ logger.info(
188
+ f"Deleted cache directory: {self.layerify_config.cache_dir}"
189
+ )
190
+
191
+ def keyPressEvent(self, event):
192
+ # if pressed escape key, close the application
193
+ if event.key() == Qt.Key_Escape:
194
+ logger.info("Escape key pressed, closing the application.")
195
+ self.close()
196
+
197
+ # if ctrl+h, show help dialog
198
+ elif event.key() == Qt.Key_H and event.modifiers() == Qt.ControlModifier:
199
+ logger.info("Ctrl+H pressed, showing help dialog.")
200
+ current_tab = self.tab_widget.currentIndex()
201
+ if current_tab == 0:
202
+ info = (
203
+ "<b>Layerify Tab Shortcuts:</b><br>"
204
+ "<ul>"
205
+ "<li><b>Ctrl + C</b>: Copy selected annotation.</li>"
206
+ "<li><b>Ctrl + V</b>: Paste copied annotation in its parent image if it is currently open.</li>"
207
+ "<li><b>Delete</b>: Delete selected annotation.</li>"
208
+ "<li><b>Left Click</b>: Select an annotation on mouse position.</li>"
209
+ "<li><b>Left Click + Drag</b>: Drag a selected annotation.</li>"
210
+ "<li><b>Double Left Click</b>: When using polygon annotation, completes the polygon.</li>"
211
+ "<li><b>Right Click</b>: Unselect an annotation. While annotating the polygon, undo the last point.</li>"
212
+ "<li><b>Ctrl + Mouse Wheel</b>: Zoom In/Out on the mouse position, i.e., resize the viewport.</li>"
213
+ "<li><b>Ctrl + Drag</b>: If done on the background, the viewport is panned.</li>"
214
+ "<li><b>Q</b>: Point mode on annotation.</li>"
215
+ "<li><b>W</b>: Polygon mode on annotation.</li>"
216
+ "<li><b>E</b>: Rectangle mode on annotation.</li>"
217
+ "<li><b>H</b>: Hides/un-hides selected annotation.</li>"
218
+ "<li><b>L</b>: Creates layer from an annotation. If any annotation selected, creates only its, else creates layers from all visible annotations.</li>"
219
+ "<li><b>C</b>: If any annotation is selected, a input box for Caption is created.</li>"
220
+ "<li><b>Numerics</b>: Selecting number 1, 2, till 9 selects label. If not available, asks for a new label.</li>"
221
+ "<li><b>Escape</b>: Closes the application.</li>"
222
+ "</ul>"
223
+ )
224
+ elif current_tab == 1:
225
+ info = (
226
+ "<b>Baker Tab Shortcuts:</b><br>"
227
+ "<ul>"
228
+ "<li><b>Ctrl + C</b>: Copy selected layer.</li>"
229
+ "<li><b>Ctrl + V</b>: Paste copied layer in its parent if it is currently open.</li>"
230
+ "<li><b>Delete</b>: Delete selected layer.</li>"
231
+ "<li><b>Left Click</b>: Select a layer on mouse position.</li>"
232
+ "<li><b>Left Click + Drag</b>: Drag a selected layer.</li>"
233
+ "<li><b>Ctrl + S</b>: Save State on Baker Tab.</li>"
234
+ "<li><b>Ctrl + D</b>: Draw Mode on Baker Tab. Drawing can happen on a selected or main layer.</li>"
235
+ "<li><b>Ctrl + E</b>: Erase Mode on Baker Tab.</li>"
236
+ "<li><b>Wheel</b>: Change the size of the drawing pointer.</li>"
237
+ "<li><b>W</b>: Moves selected layer one step up in layer list in baker.</li>"
238
+ "<li><b>S</b>: Moves selected layer one step down in layer list in baker.</li>"
239
+ "<li><b>H</b>: Hides/un-hides selected layer.</li>"
240
+ "<li><b>C</b>: Edit Caption for selected layer.</li>"
241
+ "<li><b>Escape</b>: Closes the application.</li>"
242
+ "</ul>"
243
+ )
244
+ else:
245
+ info = "No shortcuts available for this tab."
246
+
247
+ QMessageBox.information(
248
+ self,
249
+ "Help",
250
+ info,
251
+ )
252
+
253
+ # pass event to other widgets
254
+ return super().keyPressEvent(event)