imagebaker 0.0.50__py3-none-any.whl → 0.0.51__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.
@@ -74,6 +74,22 @@ class BaseConfig(BaseModel):
74
74
  logger.info(f"Created assets folder at {asset_dir}")
75
75
  return asset_dir
76
76
 
77
+ @property
78
+ def cache_dir(self):
79
+ cache_dir = self.project_dir / ".imagebaker" / "cache"
80
+ if not cache_dir.exists():
81
+ cache_dir.mkdir(parents=True)
82
+ logger.info(f"Created cache folder at {cache_dir}")
83
+ return cache_dir
84
+
85
+ @property
86
+ def bake_dir(self):
87
+ bake_dir = self.project_dir / ".imagebaker" / "bake"
88
+ if not bake_dir.exists():
89
+ bake_dir.mkdir(parents=True)
90
+ logger.info(f"Created bake folder at {bake_dir}")
91
+ return bake_dir
92
+
77
93
  class Config:
78
94
  arbitrary_types_allowed = True
79
95
 
@@ -96,6 +112,7 @@ class LayerConfig(BaseConfig):
96
112
  )
97
113
  # whether to search image in subfolders as well
98
114
  full_search: bool = False
115
+ cleanup_on_exit: bool = False
99
116
 
100
117
  def get_label_color(self, label):
101
118
  for lbl in self.predefined_labels:
@@ -210,6 +210,10 @@ class Annotation:
210
210
  def load_from_json(path: str):
211
211
  import json
212
212
 
213
+ # if path does not exist, return empty list
214
+ if not Path(path).exists():
215
+ return []
216
+
213
217
  with open(path, "r") as f:
214
218
  data = json.load(f)
215
219
 
@@ -42,6 +42,7 @@ class AnnotableLayer(BaseLayer):
42
42
  annotationCleared = Signal()
43
43
  annotationMoved = Signal()
44
44
  layersChanged = Signal()
45
+ labelUpdated = Signal(tuple)
45
46
 
46
47
  def __init__(self, parent, config: LayerConfig, canvas_config: CanvasConfig):
47
48
  super().__init__(parent, config)
@@ -534,13 +535,11 @@ class AnnotableLayer(BaseLayer):
534
535
  self.current_annotation.polygon = QPolygonF(
535
536
  [p for p in self.current_annotation.polygon][:-1]
536
537
  )
537
- self.update()
538
538
 
539
539
  # If the polygon is now empty, reset to idle mode
540
540
  if len(self.current_annotation.polygon) == 0:
541
541
  self.current_annotation = None
542
542
  self.mouse_mode = MouseMode.IDLE
543
- self.update()
544
543
 
545
544
  # If not drawing a polygon, go to idle mode
546
545
  if not self.current_annotation:
@@ -548,7 +547,7 @@ class AnnotableLayer(BaseLayer):
548
547
  for ann in self.annotations:
549
548
  ann.selected = False
550
549
  self.annotationUpdated.emit(ann)
551
- self.update()
550
+ self.update()
552
551
 
553
552
  # If left-clicked
554
553
  if event.button() == Qt.LeftButton:
@@ -751,8 +750,8 @@ class AnnotableLayer(BaseLayer):
751
750
  and len(self.current_annotation.polygon) >= 3
752
751
  ):
753
752
  self.current_annotation.is_complete = True
753
+
754
754
  self.finalize_annotation()
755
- self.annotationAdded.emit(self.current_annotation)
756
755
  self.current_annotation = None
757
756
 
758
757
  return
@@ -794,6 +793,7 @@ class AnnotableLayer(BaseLayer):
794
793
  self, "Edit Label", "Enter new label:", text=annotation.label
795
794
  )
796
795
  if ok and new_label:
796
+ self.labelUpdated.emit((annotation.label, new_label))
797
797
  annotation.label = new_label
798
798
  self.annotationUpdated.emit(annotation) # Emit signal
799
799
  self.update()
@@ -1030,7 +1030,7 @@ class CanvasLayer(BaseLayer):
1030
1030
  logger.info(f"Saved baked image to {filename}")
1031
1031
  if self.config.write_annotations:
1032
1032
  image = qpixmap_to_numpy(image.copy())
1033
- image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGR)
1033
+ image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA)
1034
1034
  drawn = draw_annotations(image, annotations)
1035
1035
  write_to = filename.parent / f"annotated_{filename.name}"
1036
1036
 
@@ -52,6 +52,7 @@ class AnnotationList(QDockWidget):
52
52
  if self.layer is None:
53
53
  return
54
54
  for idx, ann in enumerate(self.layer.annotations):
55
+
55
56
  item = QListWidgetItem(self.list_widget)
56
57
 
57
58
  # Create container widget
@@ -19,6 +19,7 @@ from pathlib import Path
19
19
 
20
20
  class ImageListPanel(QDockWidget):
21
21
  imageSelected = Signal(Path)
22
+ activeImageEntries = Signal(list)
22
23
 
23
24
  def __init__(
24
25
  self,
@@ -26,6 +27,7 @@ class ImageListPanel(QDockWidget):
26
27
  processed_images: set[Path],
27
28
  parent=None,
28
29
  max_name_length=15,
30
+ images_per_page=10,
29
31
  ):
30
32
  """
31
33
  :param image_entries: List of image paths to display.
@@ -35,7 +37,7 @@ class ImageListPanel(QDockWidget):
35
37
  self.image_entries: list["ImageEntry"] = image_entries
36
38
  self.processed_images = processed_images
37
39
  self.current_page = 0
38
- self.images_per_page = 10
40
+ self.images_per_page = images_per_page
39
41
  self.max_name_length = max_name_length
40
42
  self.init_ui()
41
43
 
@@ -90,7 +92,20 @@ class ImageListPanel(QDockWidget):
90
92
  """Update the image list with image paths and baked results."""
91
93
  self.list_widget.clear()
92
94
 
93
- for idx, image_entry in enumerate(image_entries):
95
+ # Calculate the range of images to display for the current page
96
+ start_index = self.current_page * self.images_per_page
97
+ end_index = min(start_index + self.images_per_page, len(image_entries))
98
+
99
+ # Update the pagination label
100
+ self.pagination_label.setText(
101
+ f"Showing {start_index + 1} to {end_index} of {len(image_entries)}"
102
+ )
103
+
104
+ # Display only the images for the current page
105
+ active_image_entries = []
106
+ for idx, image_entry in enumerate(
107
+ image_entries[start_index:end_index], start=start_index + 1
108
+ ):
94
109
  item_widget = QWidget()
95
110
  item_layout = QHBoxLayout(item_widget)
96
111
  item_layout.setContentsMargins(5, 5, 5, 5)
@@ -101,7 +116,7 @@ class ImageListPanel(QDockWidget):
101
116
  thumbnail_pixmap = (
102
117
  image_entry.data.get_thumbnail()
103
118
  ) # Baked result thumbnail
104
- name_label_text = f"Baked Result {idx + 1}"
119
+ name_label_text = f"Baked Result {idx}"
105
120
  else:
106
121
  thumbnail_pixmap = QPixmap(str(image_entry.data)).scaled(
107
122
  50, 50, Qt.KeepAspectRatio, Qt.SmoothTransformation
@@ -126,11 +141,9 @@ class ImageListPanel(QDockWidget):
126
141
 
127
142
  # Store metadata for the image
128
143
  list_item.setData(Qt.UserRole, image_entry)
129
- self.pagination_label.setText(
130
- f"Showing {self.current_page * self.images_per_page + 1} to "
131
- f"{min((self.current_page + 1) * self.images_per_page, len(image_entries))} "
132
- f"of {len(image_entries)}"
133
- )
144
+ active_image_entries.append(image_entry)
145
+
146
+ self.activeImageEntries.emit(active_image_entries)
134
147
  self.update()
135
148
 
136
149
  def handle_item_clicked(self, item: QListWidgetItem):
@@ -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)
@@ -166,6 +173,8 @@ class LayerifyTab(QWidget):
166
173
  if not image_entry.is_baked_result: # Regular image
167
174
  image_path = image_entry.data
168
175
  self.curr_image_idx = self.image_entries.index(image_entry)
176
+ # convert curr_image_idx to correct index
177
+ self.curr_image_idx = self.curr_image_idx % len(self.annotable_layers)
169
178
 
170
179
  # Make the corresponding layer visible and set the image
171
180
  selected_layer = self.annotable_layers[self.curr_image_idx]
@@ -206,6 +215,8 @@ class LayerifyTab(QWidget):
206
215
  for i, layer in enumerate(self.annotable_layers):
207
216
  if i < len(self.image_entries):
208
217
  layer.set_image(self.image_entries[i].data)
218
+ self.load_layer_annotations(layer)
219
+ layer.layer_name = f"Layer_{i + 1}"
209
220
  layer.setVisible(
210
221
  i == 0
211
222
  ) # Only the first layer is visible by default
@@ -224,12 +235,68 @@ class LayerifyTab(QWidget):
224
235
  self.image_list_panel.update_image_list(self.image_entries)
225
236
  self.update()
226
237
 
238
+ def save_layer_annotations(self, layer: AnnotableLayer):
239
+ """Save annotations for a specific layer"""
240
+ if len(layer.annotations) > 0:
241
+ file_path = layer.file_path
242
+ file_name = file_path.name
243
+ save_dir = self.config.cache_dir / f"{file_name}.json"
244
+ Annotation.save_as_json(layer.annotations, save_dir)
245
+ logger.info(f"Saved annotations for {layer.layer_name} to {save_dir}")
246
+
247
+ def load_layer_annotations(self, layer: AnnotableLayer):
248
+ """Load annotations for a specific layer"""
249
+ if layer.file_path:
250
+ file_path = layer.file_path
251
+ file_name = file_path.name
252
+ load_dir = self.config.cache_dir / f"{file_name}.json"
253
+ if load_dir.exists():
254
+ layer.annotations = Annotation.load_from_json(load_dir)
255
+ logger.info(
256
+ f"Loaded annotations for {layer.layer_name} from {load_dir}"
257
+ )
258
+ else:
259
+ logger.warning(f"No annotations found for {layer.layer_name}")
260
+
261
+ def update_active_entries(self, image_entries: list[ImageEntry]):
262
+ """Update the active entries in the image list panel."""
263
+ self.curr_image_idx = 0
264
+ for i, layer in enumerate(self.annotable_layers):
265
+ self.save_layer_annotations(layer)
266
+ layer.annotations = []
267
+
268
+ if i < len(image_entries):
269
+ # get index on the self.image_entries
270
+ idx = self.image_entries.index(image_entries[i])
271
+ if self.image_entries[idx].is_baked_result:
272
+ # if the image is a baked result, set the layer to the baked result
273
+ layer = self.image_entries[idx].data
274
+ layer.file_path = layer.file_path
275
+ else:
276
+ layer.set_image(self.image_entries[idx].data)
277
+ self.load_layer_annotations(layer)
278
+
279
+ layer.layer_name = f"Layer_{idx + 1}"
280
+ layer.setVisible(i == 0)
281
+ if i == 0:
282
+ self.layer = layer
283
+ else:
284
+ layer.setVisible(False)
285
+ logger.info("Updated active entries in image list panel.")
286
+
227
287
  def clear_annotations(self):
228
288
  """Safely clear all annotations"""
229
289
  try:
230
290
  # Clear layer annotations
231
291
  self.clearAnnotations.emit()
232
292
  self.messageSignal.emit("Annotations cleared")
293
+ # clear cache annotation of layer
294
+ annotation_path = (
295
+ self.config.cache_dir / f"{self.layer.file_path.name}.json"
296
+ )
297
+ if annotation_path.exists():
298
+ os.remove(annotation_path)
299
+ logger.info(f"Cleared annotations from {annotation_path}")
233
300
 
234
301
  except Exception as e:
235
302
  logger.error(f"Clear error: {str(e)}")
@@ -241,13 +308,17 @@ class LayerifyTab(QWidget):
241
308
  Args:
242
309
  annotation (Annotation): The annotation that was added.
243
310
  """
244
- if annotation.label not in self.config.predefined_labels:
311
+
312
+ # if annotation.label is not in the predefined labels, add it
313
+ if annotation.label not in [lbl.name for lbl in self.config.predefined_labels]:
314
+ logger.info(f"Label {annotation.label} created.")
245
315
  self.config.predefined_labels.append(
246
316
  Label(annotation.label, annotation.color)
247
317
  )
248
318
  self.update_label_combo()
249
319
  logger.info(f"Added annotation: {annotation.label}")
250
320
  self.messageSignal.emit(f"Added annotation: {annotation.label}")
321
+ self.save_layer_annotations(self.layer)
251
322
 
252
323
  # Refresh the annotation list
253
324
  self.annotation_list.update_list()
@@ -259,11 +330,12 @@ class LayerifyTab(QWidget):
259
330
  Args:
260
331
  annotation (Annotation): The updated annotation.
261
332
  """
262
- logger.info(f"Updated annotation: {annotation.label}")
333
+ # logger.info(f"Updated annotation: {annotation}")
263
334
  self.messageSignal.emit(f"Updated annotation: {annotation.label}")
264
335
 
265
336
  # Refresh the annotation list
266
337
  self.annotation_list.update_list()
338
+ self.save_layer_annotations(self.layer)
267
339
 
268
340
  def update_label_combo(self):
269
341
  """
@@ -276,6 +348,27 @@ class LayerifyTab(QWidget):
276
348
  pixmap = QPixmap(16, 16)
277
349
  pixmap.fill(label.color)
278
350
  self.label_combo.addItem(QIcon(pixmap), label.name)
351
+ logger.info("Updated label combo box with predefined labels.")
352
+ self.label_combo.setCurrentText(self.current_label)
353
+
354
+ def on_label_update(self, old_new_label: tuple[str, str]):
355
+ new_labels = []
356
+ index = 0
357
+ for i, label in enumerate(self.config.predefined_labels):
358
+ if label.name == old_new_label[0]:
359
+ label.name = old_new_label[1]
360
+ index = i
361
+ new_labels.append(label)
362
+
363
+ self.config.predefined_labels = new_labels
364
+ logger.info(f"Updated label from {old_new_label[0]} to {old_new_label[1]}")
365
+ self.messageSignal.emit(
366
+ f"Updated label from {old_new_label[0]} to {old_new_label[1]}."
367
+ )
368
+
369
+ self.update_label_combo()
370
+ self.handle_label_change(index=index)
371
+ self.label_combo.update()
279
372
 
280
373
  def load_default_image(self):
281
374
  """
@@ -589,6 +682,24 @@ class LayerifyTab(QWidget):
589
682
  )
590
683
  msg = f"Label changed to {self.current_label}"
591
684
  self.messageSignal.emit(msg)
685
+ self.layer.selected_annotation = self.layer._get_selected_annotation()
686
+ if self.layer.selected_annotation:
687
+ annotations = []
688
+ for ann in self.layer.annotations:
689
+ if ann == self.layer.selected_annotation:
690
+ ann.label = label_info.name
691
+ ann.color = label_info.color
692
+ annotations.append(ann)
693
+
694
+ self.layer.annotations = annotations
695
+ self.on_annotation_updated(self.layer.selected_annotation)
696
+ # disable label change callback
697
+ self.label_combo.currentIndexChanged.disconnect()
698
+ self.label_combo.currentIndexChanged.connect(lambda: None)
699
+ self.update_label_combo()
700
+ # set it back
701
+ self.label_combo.currentIndexChanged.connect(self.handle_label_change)
702
+
592
703
  self.layer.update()
593
704
  self.update()
594
705
 
@@ -628,7 +739,7 @@ class LayerifyTab(QWidget):
628
739
  ("🎨", "Color", self.choose_color),
629
740
  ("🧅", "Layerify All", self.layerify_all),
630
741
  ("🏷️", "Add Label", self.add_new_label),
631
- ("🗑️", "Clear", lambda x: self.clearAnnotations.emit()),
742
+ ("🗑️", "Clear", self.clear_annotations),
632
743
  ]
633
744
 
634
745
  # Folder navigation buttons
@@ -636,18 +747,6 @@ class LayerifyTab(QWidget):
636
747
  self.select_folder_btn.clicked.connect(self.select_folder)
637
748
  toolbar_layout.addWidget(self.select_folder_btn)
638
749
 
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
750
  # Add mode buttons
652
751
  for icon, text, mode in modes:
653
752
  btn_txt = icon + text
@@ -703,11 +802,25 @@ class LayerifyTab(QWidget):
703
802
  ImageEntry(is_baked_result=False, data=img_path)
704
803
  )
705
804
 
805
+ # load from bake folder if it exists
806
+ bake_folder = self.config.bake_dir
807
+ if bake_folder.exists() and bake_folder.is_dir():
808
+ for img_path in bake_folder.glob("*.*"):
809
+ if img_path.suffix.lower() in [
810
+ ".jpg",
811
+ ".jpeg",
812
+ ".png",
813
+ ".bmp",
814
+ ".tiff",
815
+ ]:
816
+ self.image_entries.append(
817
+ ImageEntry(is_baked_result=False, data=img_path)
818
+ )
819
+
706
820
  def select_folder(self):
707
821
  """Allow the user to select a folder and load images from it."""
708
822
  folder_path = QFileDialog.getExistingDirectory(self, "Select Folder")
709
823
  if folder_path:
710
- self.image_entries = [] # Clear the existing image paths
711
824
  folder_path = Path(folder_path)
712
825
 
713
826
  self._load_images_from_folder(folder_path)
@@ -726,9 +839,6 @@ class LayerifyTab(QWidget):
726
839
  # Load the first set of images into the layers
727
840
  self.load_default_images()
728
841
 
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
842
  else:
733
843
  QMessageBox.warning(
734
844
  self,
@@ -736,28 +846,6 @@ class LayerifyTab(QWidget):
736
846
  "No valid image files found in the selected folder.",
737
847
  )
738
848
 
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
849
  def __del__(self):
762
850
  logger.warning(f"Tab {id(self)} deleted")
763
851
 
@@ -770,10 +858,20 @@ class LayerifyTab(QWidget):
770
858
  config=self.config,
771
859
  canvas_config=self.canvas_config,
772
860
  )
773
- layer.annotations = baking_result.annotations
861
+ # save it in cache
862
+ filename = baking_result.filename
863
+ filepath = self.config.bake_dir / filename.name
864
+ baking_result.image.save(str(filepath))
865
+ #
866
+ Annotation.save_as_json(
867
+ baking_result.annotations, self.config.cache_dir / f"{filename.name}.json"
868
+ )
774
869
 
775
- layer.annotationAdded.connect(self.annotation_list.update_list)
776
- layer.annotationUpdated.connect(self.annotation_list.update_list)
870
+ layer.set_image(filepath)
871
+
872
+ layer.annotationAdded.connect(self.on_annotation_added)
873
+ layer.annotationUpdated.connect(self.on_annotation_updated)
874
+ layer.labelUpdated.connect(self.on_label_update)
777
875
  layer.messageSignal.connect(self.messageSignal)
778
876
  layer.layerSignal.connect(self.add_layer)
779
877
 
@@ -785,7 +883,7 @@ class LayerifyTab(QWidget):
785
883
  self.annotable_layers.append(layer)
786
884
 
787
885
  # Add baked result to image_entries
788
- baked_result_entry = ImageEntry(is_baked_result=True, data=layer)
886
+ baked_result_entry = ImageEntry(is_baked_result=False, data=filepath)
789
887
  self.image_entries.append(baked_result_entry)
790
888
  # baking_result.image.save(str(baking_result.filename))
791
889
  layer.update()
@@ -793,6 +891,12 @@ class LayerifyTab(QWidget):
793
891
  logger.info("A baked result has arrived, adding it to the image list.")
794
892
 
795
893
  # Update the image list panel
894
+ # find the page index where this layer is
895
+ page_index = (
896
+ self.image_entries.index(baked_result_entry) // self.config.deque_maxlen
897
+ )
898
+ # set the current page to the page index
899
+ self.image_list_panel.current_page = page_index
796
900
  self.image_list_panel.update_image_list(self.image_entries)
797
901
  self.image_list_panel.imageSelected.emit(baked_result_entry)
798
902
 
@@ -831,6 +935,19 @@ class LayerifyTab(QWidget):
831
935
  self.annotation_list.update_list()
832
936
  logger.info("Selected annotation deleted.")
833
937
 
938
+ # if clicked q, set the mode to point
939
+ elif key == Qt.Key_Q:
940
+ self.layer.set_mode(MouseMode.POINT)
941
+ logger.info("Mouse mode set to POINT.")
942
+ # if clicked w, set the mode to polygon
943
+ elif key == Qt.Key_W:
944
+ self.layer.set_mode(MouseMode.POLYGON)
945
+ logger.info("Mouse mode set to POLYGON.")
946
+ # if clicked e, set the mode to rectangle
947
+ elif key == Qt.Key_E:
948
+ self.layer.set_mode(MouseMode.RECTANGLE)
949
+ logger.info("Mouse mode set to RECTANGLE.")
950
+
834
951
  # Pass the event to the annotation list if it needs to handle it
835
952
  if self.annotation_list.hasFocus():
836
953
  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
@@ -175,9 +175,15 @@ 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
+ )
@@ -224,6 +224,8 @@ class BakerWorker(QObject):
224
224
  self.finished.emit(results)
225
225
 
226
226
  except Exception as e:
227
+ import traceback
228
+
227
229
  logger.error(f"Error in BakerWorker: {e}")
228
230
  self.error.emit(str(e))
229
231
  traceback.print_exc()
@@ -248,9 +250,14 @@ class BakerWorker(QObject):
248
250
  new_annotation.points = ann.points
249
251
  elif ann.rectangle:
250
252
  xywhs = mask_to_rectangles(alpha_channel, merge_rectangles=True)
251
- new_annotation.rectangle = QRectF(
252
- xywhs[0][0], xywhs[0][1], xywhs[0][2], xywhs[0][3]
253
- )
253
+ if len(xywhs) == 0:
254
+ logger.info("No rectangles found")
255
+ # return None
256
+ else:
257
+ logger.info(f"Found {len(xywhs)} rectangles")
258
+ new_annotation.rectangle = QRectF(
259
+ xywhs[0][0], xywhs[0][1], xywhs[0][2], xywhs[0][3]
260
+ )
254
261
  elif ann.polygon:
255
262
  polygon = mask_to_polygons(alpha_channel, merge_polygons=True)
256
263
  poly = QPolygonF([QPointF(p[0], p[1]) for p in polygon[0]])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.50
3
+ Version: 0.0.51
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -112,6 +112,9 @@ After cloning and going to the project directory, the following code should work
112
112
  * **Ctrl + D**: Draw Mode on Baker Tab. Drawing can happen on a selected or main layer.
113
113
  * **Ctrl + E**: Erase Mode on Baker Tab.
114
114
  * **Wheel**: Change the size of the drawing pointer.
115
+ * **Q**: Point mode on annotation.
116
+ * **W**: Polygon mode on annotation.
117
+ * **E**: Rectangle mode on annotation.
115
118
 
116
119
  ## Demo
117
120
  ### Annotation Page
@@ -1,43 +1,43 @@
1
1
  imagebaker/__init__.py,sha256=zrrxwyzuqVNeIu3rVPrOGYf6SCd5kWsoGcdqqUfsYX4,258
2
2
  imagebaker/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  imagebaker/core/configs/__init__.py,sha256=iyR_GOVMFw3XJSm7293YfyTnaLZa7pLQMfn5tGxVofI,31
4
- imagebaker/core/configs/configs.py,sha256=-qy7vmYaaUk3bh49pwsBig8de_3Y2JWjTDeyWcGQods,5130
4
+ imagebaker/core/configs/configs.py,sha256=hm0CHfMVTD9353wB61Df9bVUb898zQVIFjdoXELdJcc,5694
5
5
  imagebaker/core/defs/__init__.py,sha256=NqV7gYIlRkaS7nx_UTNPSNZbdPrx4w-VurKOKyRLbKY,28
6
- imagebaker/core/defs/defs.py,sha256=-ZItfJdWaK9yFSuFn2LmQ4ncqukAZ_hvYFgE44HUdIo,8394
6
+ imagebaker/core/defs/defs.py,sha256=oZCkgqHgvE_yupUzD5IB_ZMoRhyTh0EdCR8Gmh6TMsI,8505
7
7
  imagebaker/core/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  imagebaker/core/plugins/base_plugin.py,sha256=ROa1HTwV5LgGL-40CHKk_5MZYI5QAT1MzpYO7Fx-9P0,1084
9
9
  imagebaker/core/plugins/cosine_plugin.py,sha256=IXBfvaoxrvf-hytg_dT1zFmOfDbcWXBZ7NvIFPJ2tWQ,1251
10
10
  imagebaker/layers/__init__.py,sha256=q1kUDHhUXEGBOdu6CHDfqCnE2mraLHRqh0DFHYTbnRY,158
11
- imagebaker/layers/annotable_layer.py,sha256=ejBp6nooLtHs8c_G6lOsTJmSwBT404F_dmPsSkjvdEQ,37167
11
+ imagebaker/layers/annotable_layer.py,sha256=xkV6KTXN8-N6Wu3QAj5nQDMPZ4bRr673_fu5QuRKBj8,37128
12
12
  imagebaker/layers/base_layer.py,sha256=1K7Nt6OPITrILj-p4I6Jf0eaesCpdeecOXGj_8oAQb8,30650
13
- imagebaker/layers/canvas_layer.py,sha256=7eDo0UHXRRZ5BX3BciPX88JsumzmwVemtS6cBjU9qwM,42115
13
+ imagebaker/layers/canvas_layer.py,sha256=6quVe_Ieyz14zPL92xTfrinAT7X4yb9iyEl-f9SnQZU,42116
14
14
  imagebaker/list_views/__init__.py,sha256=Aa9slE6do8eYgZp77wrofpd_mlBDwxgF3adMyHYFanE,144
15
- imagebaker/list_views/annotation_list.py,sha256=HGV6lGlkFjvJvvGnCcLuX1kkfXA0GL8wKo8jOXSBXec,7527
15
+ imagebaker/list_views/annotation_list.py,sha256=w6jNMyo3nxhQKZx8MyusNvxs_R6Pj-b-YBI5e0APZVM,7528
16
16
  imagebaker/list_views/canvas_list.py,sha256=JYSYR0peGyJFJ6amL1894KsUHETPUkR3qAWdGL50Lbc,6717
17
- imagebaker/list_views/image_list.py,sha256=NInkc893FGU7L6oSxy8KrWql-i6RB0BqvkroPAASjVw,4912
17
+ imagebaker/list_views/image_list.py,sha256=EX2YmoTRffBXTbbVnuflbdsxZQD8fgM2StSxg1a9ANA,5389
18
18
  imagebaker/list_views/layer_list.py,sha256=fLx3Ry72fas1W5y_V84hSp41ARneogQN3qjfYTOcpxY,14476
19
19
  imagebaker/list_views/layer_settings.py,sha256=0WVSCm_RSBKo4pCkYU5c2OYjb_sW8x0UUfFC4So26jQ,9752
20
20
  imagebaker/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  imagebaker/models/base_model.py,sha256=4RyS4vShqWFHhdQDwYluwTPnRPEXjpZl9UjYY_w8NL0,4203
22
22
  imagebaker/tabs/__init__.py,sha256=ijg7MA17RvcHA2AuZE4OgRJXWxjecaUAlfASKAoCQ6Q,86
23
23
  imagebaker/tabs/baker_tab.py,sha256=yFQRiNmLIua5BvgW7Ysj5FrNbTLhGxqGRTRCABxeTtw,20493
24
- imagebaker/tabs/layerify_tab.py,sha256=Qsf9w81-43sB0LZy_vIB4wCzUhHHhoIiarZJvKRxeZ0,32340
24
+ imagebaker/tabs/layerify_tab.py,sha256=F3BK5XsVCteB-2zyFBKqeWyHgIif5cU3TBEMbuDH3QM,37415
25
25
  imagebaker/utils/__init__.py,sha256=I1z5VVEf6QPOMvVkgVHDauQ9ew7tcTVguV4Kdi3Lk4Y,130
26
- imagebaker/utils/image.py,sha256=fq7g3DqSdjF9okxZ3fe5kF4Hxn32rqhvVqxy8yI5bnI,3067
26
+ imagebaker/utils/image.py,sha256=2-wbwD3PMwefgswge0drFM1XfXE7yQ64lqZZ5PwyCWs,3165
27
27
  imagebaker/utils/state_utils.py,sha256=Y2JVRGVfsoffwfA2lsCcqHwIxH_jOrEJAdH-oWfe2XE,3841
28
28
  imagebaker/utils/transform_mask.py,sha256=k8MfTgM5-_U2TvDHQHRelz-leGFX6OcsllV6-J4BKfw,3651
29
29
  imagebaker/utils/utils.py,sha256=MnJ4flxxwZbjROWJ5iKHnJxPSSMbfWRbF9GKfVcKutA,840
30
30
  imagebaker/utils/vis.py,sha256=f7c44gm6g9ja5hgVeXKfOhHzxHdzXcIUwKiA1RZU_F8,4736
31
31
  imagebaker/window/__init__.py,sha256=FIxtUR1qnbQMYzppQv7tEfv1-ueHhpu0Z7xuWZR794w,44
32
32
  imagebaker/window/app.py,sha256=e6FGO_BnvkiQC9JN3AmqkgbF72zzZS0hc7PFc43QiVc,4725
33
- imagebaker/window/main_window.py,sha256=gpJ7DDuPmxhHh_6Rv3YH2J_1AqG7-NM8R3tKNYhFT3E,7030
33
+ imagebaker/window/main_window.py,sha256=-CP7q6xzkhv9Cl5RwUft1Rv8nIAuj_SLOiZDS2x6QJ4,7374
34
34
  imagebaker/workers/__init__.py,sha256=XfXENwAYyNg9q_zR-gOsYJGjzwg_iIb_gING8ydnp9c,154
35
- imagebaker/workers/baker_worker.py,sha256=EJTL4ln09NuntFpu0o-Hfk0vCtDxpKqJxJcmtgTnMwo,11297
35
+ imagebaker/workers/baker_worker.py,sha256=_jWeyYAGoO2mfxXDn7fBm9tIA69OITewDVN0hSAt3Jc,11532
36
36
  imagebaker/workers/layerify_worker.py,sha256=EOqKvhdACtf3y5Ljy6M7MvddAjlZW5DNfBFMtNPD-us,3223
37
37
  imagebaker/workers/model_worker.py,sha256=Tlg6_D977iK-kuGCNdQY4OnGiP8QqWY7adpRNXZw4rA,1636
38
- imagebaker-0.0.50.dist-info/LICENSE,sha256=1vkysFPOnT7y4LsoFTv9YsopIrQvBc2l6vUOfv4KKLc,1082
39
- imagebaker-0.0.50.dist-info/METADATA,sha256=Zsl37tIGiV9wrQ2zfiaubhmPbhOMucos2V_fF5mtbXU,6829
40
- imagebaker-0.0.50.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
41
- imagebaker-0.0.50.dist-info/entry_points.txt,sha256=IDjZHJCiiHpH5IUTByT2en0nMbnnnlrJZ5FPFehUvQM,61
42
- imagebaker-0.0.50.dist-info/top_level.txt,sha256=Gg-eILTlqJXwVQr0saSwsx3-H4SPdZ2agBZaufe194s,11
43
- imagebaker-0.0.50.dist-info/RECORD,,
38
+ imagebaker-0.0.51.dist-info/LICENSE,sha256=1vkysFPOnT7y4LsoFTv9YsopIrQvBc2l6vUOfv4KKLc,1082
39
+ imagebaker-0.0.51.dist-info/METADATA,sha256=1DumEPL4LyKVeuV_tDwyepZ9PsreraeKSTEJBernVmw,6940
40
+ imagebaker-0.0.51.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
41
+ imagebaker-0.0.51.dist-info/entry_points.txt,sha256=IDjZHJCiiHpH5IUTByT2en0nMbnnnlrJZ5FPFehUvQM,61
42
+ imagebaker-0.0.51.dist-info/top_level.txt,sha256=Gg-eILTlqJXwVQr0saSwsx3-H4SPdZ2agBZaufe194s,11
43
+ imagebaker-0.0.51.dist-info/RECORD,,