imagebaker 0.0.51__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.
@@ -78,6 +78,7 @@ class LayerState:
78
78
  drawing_states: list[DrawingState] = field(default_factory=list)
79
79
  edge_opacity: int = 100
80
80
  edge_width: int = 10
81
+ caption: str = ""
81
82
 
82
83
  def copy(self):
83
84
  return LayerState(
@@ -109,6 +110,7 @@ class LayerState:
109
110
  ],
110
111
  edge_opacity=self.edge_opacity,
111
112
  edge_width=self.edge_width,
113
+ caption=self.caption,
112
114
  )
113
115
 
114
116
 
@@ -138,6 +140,7 @@ class Annotation:
138
140
  file_path: Path = field(default_factory=lambda: Path("Runtime"))
139
141
  is_model_generated: bool = False
140
142
  model_name: str = None
143
+ caption: str = ""
141
144
 
142
145
  def copy(self):
143
146
  ann = Annotation(
@@ -156,6 +159,7 @@ class Annotation:
156
159
  file_path=self.file_path,
157
160
  is_model_generated=self.is_model_generated,
158
161
  model_name=self.model_name,
162
+ caption=self.caption,
159
163
  )
160
164
  ann.is_selected = False
161
165
  return ann
@@ -200,6 +204,7 @@ class Annotation:
200
204
  "file_path": str(annotation.file_path),
201
205
  "is_model_generated": annotation.is_model_generated,
202
206
  "model_name": annotation.model_name,
207
+ "caption": annotation.caption,
203
208
  }
204
209
  annotations_dict.append(data)
205
210
 
@@ -234,6 +239,7 @@ class Annotation:
234
239
  file_path=Path(d.get("file_path", "Runtime")),
235
240
  is_model_generated=d.get("is_model_generated", False),
236
241
  model_name=d.get("model_name", None),
242
+ caption=d.get("caption", ""),
237
243
  )
238
244
 
239
245
  # Handle points safely
@@ -24,6 +24,7 @@ from PySide6.QtGui import (
24
24
  QWheelEvent,
25
25
  QMouseEvent,
26
26
  QKeyEvent,
27
+ QFont,
27
28
  )
28
29
  from PySide6.QtWidgets import (
29
30
  QApplication,
@@ -68,14 +69,23 @@ class AnnotableLayer(BaseLayer):
68
69
  self.annotationCleared.emit()
69
70
  self.update()
70
71
 
72
+ def toggle_annotation_visibility(self):
73
+ """Toggle visibility of all annotations."""
74
+ selected_annotation = self._get_selected_annotation()
75
+ if selected_annotation is not None:
76
+ selected_annotation.visible = not selected_annotation.visible
77
+ self.annotationUpdated.emit(selected_annotation)
78
+ self.update()
79
+
71
80
  def handle_key_press(self, event: QKeyEvent):
72
81
  # Handle Ctrl key for panning
73
82
  if event.key() == Qt.Key_Control:
74
- if (
75
- self.mouse_mode != MouseMode.POLYGON
76
- ): # Only activate pan mode when not drawing polygons
83
+ # disable this for now to allow pannings
84
+ # if (
85
+ # self.mouse_mode != MouseMode.POLYGON
86
+ # ): # Only activate pan mode when not drawing polygons
77
87
 
78
- self.mouse_mode = MouseMode.PAN
88
+ self.mouse_mode = MouseMode.PAN
79
89
 
80
90
  # Handle Ctrl+C for copy
81
91
  if event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_C:
@@ -258,7 +268,7 @@ class AnnotableLayer(BaseLayer):
258
268
 
259
269
  # Draw labels
260
270
  if annotation.is_complete and annotation.label:
261
- painter.save()
271
+ # painter.save()
262
272
  label_pos = self.get_label_position(annotation)
263
273
  text = annotation.label
264
274
 
@@ -298,8 +308,58 @@ class AnnotableLayer(BaseLayer):
298
308
  # Draw text
299
309
  painter.setPen(Qt.white)
300
310
  painter.drawText(bg_rect, Qt.AlignCenter, text)
301
- painter.restore()
302
- self.label_rects.append((bg_rect, annotation))
311
+
312
+ # now if the annotations has caption,
313
+ # draw the caption below the label in itallic
314
+ # just below the label, in a font lighter than a label
315
+
316
+ if annotation.caption:
317
+ caption_font = painter.font()
318
+ # Draw a background rectangle for the caption to ensure visibility
319
+ caption_text = annotation.caption
320
+ caption_font.setItalic(True)
321
+ caption_font.setPixelSize(
322
+ self.config.selected_draw_config.label_font_size * self.scale + 2
323
+ )
324
+ caption_font.setWeight(QFont.Light)
325
+ painter.setFont(caption_font)
326
+ metrics = painter.fontMetrics()
327
+ text_width = metrics.horizontalAdvance(caption_text)
328
+ text_height = metrics.height()
329
+ # Draw background rectangle for caption
330
+ caption_rect = QRectF(
331
+ widget_pos.x() - text_width / 2 - 2,
332
+ widget_pos.y() + text_height / 2 - 2,
333
+ text_width + 4,
334
+ text_height + 4,
335
+ )
336
+ painter.setPen(Qt.NoPen)
337
+ painter.setBrush(
338
+ self.config.normal_draw_config.label_font_background_color
339
+ )
340
+ painter.drawRect(caption_rect)
341
+ # Draw caption text
342
+ painter.setPen(Qt.white)
343
+ painter.drawText(caption_rect, Qt.AlignCenter, caption_text)
344
+ caption_font.setItalic(True)
345
+ caption_font.setPixelSize(
346
+ self.config.selected_draw_config.label_font_size * self.scale + 2
347
+ )
348
+ caption_font.setWeight(QFont.Light)
349
+ painter.setFont(caption_font)
350
+ metrics = painter.fontMetrics()
351
+ text_width = metrics.horizontalAdvance(annotation.caption)
352
+ text_height = metrics.height()
353
+ painter.setPen(Qt.white)
354
+ painter.setBrush(Qt.gray)
355
+
356
+ caption_rect = QRectF(
357
+ widget_pos.x() - text_width / 2 - 2,
358
+ widget_pos.y() + text_height / 2 - 2,
359
+ text_width + 4,
360
+ text_height + 4,
361
+ )
362
+ painter.drawText(caption_rect, Qt.AlignCenter, annotation.caption)
303
363
 
304
364
  painter.restore()
305
365
 
@@ -398,7 +458,14 @@ class AnnotableLayer(BaseLayer):
398
458
  MouseMode.ZOOM_IN,
399
459
  MouseMode.ZOOM_OUT,
400
460
  ]:
401
- self.mouse_mode = MouseMode.IDLE
461
+ if self.current_annotation:
462
+ if (
463
+ self.current_annotation.polygon
464
+ and not self.current_annotation.is_complete
465
+ ):
466
+ self.mouse_mode = MouseMode.POLYGON
467
+ else:
468
+ self.mouse_mode = MouseMode.IDLE
402
469
 
403
470
  # Clean up transformation state
404
471
  if hasattr(self, "selected_annotation"):
@@ -602,6 +669,17 @@ class AnnotableLayer(BaseLayer):
602
669
  elif self.mouse_mode == MouseMode.POLYGON:
603
670
  # If not double-click
604
671
  if not self.current_annotation:
672
+ # if this point is equal to the last point of the previous polygon, then ignore it
673
+ if len(self.annotations) > 0:
674
+ last_polygon = self.annotations[-1].polygon
675
+ if last_polygon:
676
+ last_point = last_polygon[-1]
677
+ if last_point == clamped_pos:
678
+ logger.info(
679
+ "Ignoring point, same as last polygon point"
680
+ )
681
+ return
682
+
605
683
  self.current_annotation = Annotation(
606
684
  file_path=self.file_path,
607
685
  annotation_id=len(self.annotations),
@@ -811,8 +889,6 @@ class AnnotableLayer(BaseLayer):
811
889
  self.current_annotation
812
890
  )
813
891
  self.annotationAdded.emit(self.current_annotation)
814
- self.current_annotation = None
815
- self.update()
816
892
  else:
817
893
  # Show custom label dialog
818
894
  label, ok = QInputDialog.getText(self, "Label", "Enter label name:")
@@ -827,8 +903,8 @@ class AnnotableLayer(BaseLayer):
827
903
  )
828
904
  self.annotationAdded.emit(self.current_annotation)
829
905
  self.current_annotation.annotation_id = len(self.annotations)
830
- self.current_annotation = None
831
- self.update()
906
+ self.current_annotation = None
907
+ self.update()
832
908
 
833
909
  # in update, update cursor
834
910
 
@@ -915,6 +991,7 @@ class AnnotableLayer(BaseLayer):
915
991
  new_layer.layer_name = (
916
992
  f"{annotation.label} {annotation.annotation_id} {annotation.annotator}"
917
993
  )
994
+ new_layer.caption = annotation.caption
918
995
 
919
996
  new_layer._apply_edge_opacity()
920
997
  new_layer.update()
@@ -240,7 +240,7 @@ class BaseLayer(QWidget):
240
240
  for step, state in enumerate(intermediate_states):
241
241
  step += self.current_step
242
242
 
243
- logger.info(f"Saving state {step} for layer {layer.layer_id}")
243
+ logger.info(f"Saving state {step} for layer {layer.layer_id}: {state}")
244
244
  state.selected = False
245
245
  if step not in curr_states:
246
246
  curr_states[step] = []
@@ -853,3 +853,11 @@ class BaseLayer(QWidget):
853
853
  @edge_width.setter
854
854
  def edge_width(self, value: int):
855
855
  self.layer_state.edge_width = value
856
+
857
+ @property
858
+ def caption(self) -> str:
859
+ return self.layer_state.caption
860
+
861
+ @caption.setter
862
+ def caption(self, value: str):
863
+ self.layer_state.caption = value
@@ -27,7 +27,6 @@ from PySide6.QtGui import (
27
27
  QMouseEvent,
28
28
  QKeyEvent,
29
29
  QTransform,
30
- QImage,
31
30
  )
32
31
  from PySide6.QtWidgets import (
33
32
  QApplication,
@@ -127,7 +126,6 @@ class CanvasLayer(BaseLayer):
127
126
 
128
127
  ## Helper functions ##
129
128
  def handle_key_press(self, event: QKeyEvent):
130
-
131
129
  # Handle Delete key
132
130
  if event.key() == Qt.Key_Delete:
133
131
  self._delete_layer()
@@ -149,6 +147,15 @@ class CanvasLayer(BaseLayer):
149
147
  if event.modifiers() & Qt.ControlModifier and event.key() == Qt.Key_V:
150
148
  self._paste_layer()
151
149
  return # Important: return after handling
150
+ # if event.key() == Qt.Key_H:
151
+ # if self.selected_layer:
152
+ # # Toggle visibility of the selected layer
153
+ # logger.info(
154
+ # f"Toggling visibility of layer: {self.selected_layer.layer_name}"
155
+ # )
156
+ # self.selected_layer.visible = not self.selected_layer.visible
157
+ # self.selected_layer.update()
158
+ # return # Important: return after handling
152
159
 
153
160
  def paint_layer(self, painter: QPainter):
154
161
  """
@@ -362,7 +369,16 @@ class CanvasLayer(BaseLayer):
362
369
  """
363
370
  """Add a new drawing state."""
364
371
  self.selected_layer = self._get_selected_layer()
365
- layer = self.selected_layer if self.selected_layer else self
372
+ layer = self.selected_layer
373
+ if not layer:
374
+ logger.debug("No layer selected for drawing.")
375
+ # show popup message window that closes in 2 seconds
376
+ QMessageBox.information(
377
+ self.parent(),
378
+ "No Layer Selected",
379
+ "Please select a layer to draw on.",
380
+ )
381
+ return
366
382
 
367
383
  # Convert the position to be relative to the layer
368
384
  relative_pos = pos - layer.position
@@ -384,8 +400,14 @@ class CanvasLayer(BaseLayer):
384
400
  )
385
401
  layer.layer_state.drawing_states.append(drawing_state)
386
402
  self._last_draw_point = relative_pos # Update the last draw point
387
- # logger.debug(f"Added drawing state at position: {relative_pos}")
388
-
403
+ logger.debug(
404
+ f"Added drawing state at position: {relative_pos} to layer {layer.layer_name}"
405
+ )
406
+ else:
407
+ logger.debug(
408
+ f"Mouse mode {self.mouse_mode} does not support drawing states."
409
+ )
410
+ return
389
411
  self.update() # Refresh the canvas to show the new drawing
390
412
 
391
413
  def handle_wheel(self, event: QWheelEvent):
@@ -822,6 +844,7 @@ class CanvasLayer(BaseLayer):
822
844
  filename = self.config.export_folder / f"{filename}.png"
823
845
  logger.info(f"Exporting baked image to {filename}")
824
846
  self.states = {0: [layer.layer_state for layer in self.layers]}
847
+ logger.debug(f"Exporting states: {self.states}")
825
848
 
826
849
  self.loading_dialog = QProgressDialog(
827
850
  "Baking Please wait...", "Cancel", 0, 0, self.parentWidget()
@@ -201,5 +201,6 @@ class AnnotationList(QDockWidget):
201
201
  self.layer.paste_annotation()
202
202
  elif event.key() == Qt.Key_Delete:
203
203
  self.delete_annotation(self.layer.selected_annotation_index)
204
- else:
205
- self.parentWidget().keyPressEvent(event)
204
+ elif event.key() == Qt.Key_H:
205
+ self.layer.toggle_annotation_visibility()
206
+ # self.parentWidget().keyPressEvent(event)
@@ -144,6 +144,11 @@ class ImageListPanel(QDockWidget):
144
144
  active_image_entries.append(image_entry)
145
145
 
146
146
  self.activeImageEntries.emit(active_image_entries)
147
+ # also set first item as selected if there are any items
148
+ if self.list_widget.count() > 0:
149
+ self.list_widget.setCurrentRow(0)
150
+ self.list_widget.setFocus()
151
+
147
152
  self.update()
148
153
 
149
154
  def handle_item_clicked(self, item: QListWidgetItem):
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import (
8
8
  QSizePolicy,
9
9
  QDockWidget,
10
10
  QSlider,
11
+ QLineEdit,
11
12
  )
12
13
 
13
14
  from imagebaker.core.defs import LayerState
@@ -82,6 +83,32 @@ class LayerSettings(QDockWidget):
82
83
  )
83
84
  self.main_layout.addWidget(self.edge_width_slider["widget"])
84
85
 
86
+ # Caption input
87
+ caption_layout = QHBoxLayout()
88
+ caption_label = QLabel("Caption:")
89
+ caption_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
90
+ self.caption_input = QLineEdit()
91
+ self.caption_input.setPlaceholderText("Enter caption...")
92
+ self.caption_input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
93
+ caption_layout.addWidget(caption_label)
94
+ caption_layout.addWidget(self.caption_input)
95
+ self.main_layout.addLayout(caption_layout)
96
+
97
+ # Set initial value if layer is selected
98
+ if self.selected_layer:
99
+ self.caption_input.setText(self.selected_layer.caption)
100
+
101
+ # Update layer caption on editing finished
102
+ def update_caption():
103
+ if self.selected_layer:
104
+ logger.info(
105
+ f"Updating caption for layer {self.selected_layer.layer_name} to {self.caption_input.text()}"
106
+ )
107
+ self.selected_layer.caption = self.caption_input.text()
108
+ self.selected_layer.update()
109
+
110
+ self.caption_input.editingFinished.connect(update_caption)
111
+
85
112
  # Add stretch to push content to the top
86
113
  self.main_layout.addStretch()
87
114
 
@@ -177,6 +204,7 @@ class LayerSettings(QDockWidget):
177
204
  edge_opacity=self.selected_layer.edge_opacity,
178
205
  edge_width=self.selected_layer.edge_width,
179
206
  visible=self.selected_layer.visible,
207
+ caption=self.selected_layer.caption,
180
208
  )
181
209
  logger.info(f"Storing state {bake_settings}")
182
210
  self.messageSignal.emit(f"Stored state for {bake_settings.layer_name}")
@@ -240,6 +268,7 @@ class LayerSettings(QDockWidget):
240
268
  self.edge_width_slider["slider"].setValue(
241
269
  int(self.selected_layer.edge_width)
242
270
  )
271
+ self.caption_input.setText(self.selected_layer.caption)
243
272
  else:
244
273
  self.widget.setEnabled(False)
245
274
  self.layer_name_label.setText("No BaseLayer")
@@ -18,7 +18,6 @@ from PySide6.QtWidgets import (
18
18
  QSlider,
19
19
  QLabel,
20
20
  QSpinBox,
21
- QComboBox,
22
21
  )
23
22
  from collections import deque
24
23
 
@@ -485,5 +484,54 @@ class BakerTab(QWidget):
485
484
  self.layer_list.update_list()
486
485
  self.messageSignal.emit(f"Added new layer: {new_layer.layer_name}")
487
486
 
487
+ if event.key() == Qt.Key_H:
488
+ # if pressed h key, then toggle visibility of layer
489
+ selected_layer = self.current_canvas._get_selected_layer()
490
+ if selected_layer:
491
+ selected_layer.visible = not selected_layer.visible
492
+ selected_layer.update()
493
+ self.current_canvas.update()
494
+ self.layer_settings.update_sliders()
495
+ self.messageSignal.emit(
496
+ f"Toggled visibility of annotations in layer: {selected_layer.layer_name}"
497
+ )
498
+ # if pressed W key, move the order of the selected layer up
499
+ # make it circular, if the first layer is selected, then move it to the last
500
+ if event.key() == Qt.Key_W:
501
+ selected_layer = self.current_canvas._get_selected_layer()
502
+ if selected_layer:
503
+ index = self.current_canvas.layers.index(selected_layer)
504
+ if index > 0:
505
+ # Move the layer up
506
+ self.current_canvas.layers.insert(index - 1, selected_layer)
507
+ del self.current_canvas.layers[index + 1]
508
+ else:
509
+ # Move the first layer to the end
510
+ self.current_canvas.layers.append(selected_layer)
511
+ del self.current_canvas.layers[0]
512
+ self.current_canvas.update()
513
+ self.layer_list.update_list()
514
+ self.messageSignal.emit(
515
+ f"Moved layer {selected_layer.layer_name} up in order."
516
+ )
517
+ # if pressed S key, move the order of the selected layer down
518
+ # make it circular, if the last layer is selected, then move it to the first
519
+ if event.key() == Qt.Key_S:
520
+ selected_layer = self.current_canvas._get_selected_layer()
521
+ if selected_layer:
522
+ index = self.current_canvas.layers.index(selected_layer)
523
+ if index < len(self.current_canvas.layers) - 1:
524
+ # Move the layer down
525
+ self.current_canvas.layers.insert(index + 2, selected_layer)
526
+ del self.current_canvas.layers[index]
527
+ else:
528
+ # Move the last layer to the beginning
529
+ self.current_canvas.layers.insert(0, selected_layer)
530
+ del self.current_canvas.layers[-1]
531
+ self.current_canvas.update()
532
+ self.layer_list.update_list()
533
+ self.messageSignal.emit(
534
+ f"Moved layer {selected_layer.layer_name} down in order."
535
+ )
488
536
  self.update()
489
537
  return super().keyPressEvent(event)
@@ -169,6 +169,8 @@ class LayerifyTab(QWidget):
169
169
  for idx, layer in enumerate(self.annotable_layers):
170
170
  layer.setVisible(False)
171
171
  # logger.info(f"Layer {idx} hidden.")
172
+ current_label = self.layer.current_label
173
+ current_color = self.layer.current_color
172
174
 
173
175
  if not image_entry.is_baked_result: # Regular image
174
176
  image_path = image_entry.data
@@ -194,6 +196,9 @@ class LayerifyTab(QWidget):
194
196
  # logger.info(f"Layer {self.curr_image_idx} made visible for baked result.")
195
197
  self.layer = baked_result_layer # Set the baked result as the current layer
196
198
 
199
+ # Set the current label and color
200
+ self.layer.current_label = current_label
201
+ self.layer.current_color = current_color
197
202
  self.annotation_list.layer = self.layer
198
203
  self.annotation_list.update_list()
199
204
 
@@ -222,6 +227,10 @@ class LayerifyTab(QWidget):
222
227
  ) # Only the first layer is visible by default
223
228
  if i == 0:
224
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
+
225
234
  else:
226
235
  layer.setVisible(False)
227
236
 
@@ -235,21 +244,30 @@ class LayerifyTab(QWidget):
235
244
  self.image_list_panel.update_image_list(self.image_entries)
236
245
  self.update()
237
246
 
238
- def save_layer_annotations(self, layer: AnnotableLayer):
247
+ def save_layer_annotations(
248
+ self, layer: AnnotableLayer, save_dir: Path | None = None
249
+ ):
239
250
  """Save annotations for a specific layer"""
240
251
  if len(layer.annotations) > 0:
241
252
  file_path = layer.file_path
242
253
  file_name = file_path.name
243
- save_dir = self.config.cache_dir / f"{file_name}.json"
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"
244
258
  Annotation.save_as_json(layer.annotations, save_dir)
245
259
  logger.info(f"Saved annotations for {layer.layer_name} to {save_dir}")
246
260
 
247
- def load_layer_annotations(self, layer: AnnotableLayer):
261
+ def load_layer_annotations(
262
+ self, layer: AnnotableLayer, load_dir: Path | None = None
263
+ ):
248
264
  """Load annotations for a specific layer"""
249
265
  if layer.file_path:
250
266
  file_path = layer.file_path
251
267
  file_name = file_path.name
252
- load_dir = self.config.cache_dir / f"{file_name}.json"
268
+ if load_dir is None:
269
+ load_dir = self.config.cache_dir
270
+ load_dir = load_dir / f"{file_name}.json"
253
271
  if load_dir.exists():
254
272
  layer.annotations = Annotation.load_from_json(load_dir)
255
273
  logger.info(
@@ -280,6 +298,9 @@ class LayerifyTab(QWidget):
280
298
  layer.setVisible(i == 0)
281
299
  if i == 0:
282
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()
283
304
  else:
284
305
  layer.setVisible(False)
285
306
  logger.info("Updated active entries in image list panel.")
@@ -319,7 +340,6 @@ class LayerifyTab(QWidget):
319
340
  logger.info(f"Added annotation: {annotation.label}")
320
341
  self.messageSignal.emit(f"Added annotation: {annotation.label}")
321
342
  self.save_layer_annotations(self.layer)
322
-
323
343
  # Refresh the annotation list
324
344
  self.annotation_list.update_list()
325
345
 
@@ -564,6 +584,34 @@ class LayerifyTab(QWidget):
564
584
  QMessageBox.warning(self, "Warning", "No annotations to save!")
565
585
  return
566
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
+
567
615
  options = QFileDialog.Options()
568
616
  file_name, _ = QFileDialog.getSaveFileName(
569
617
  self, "Save Annotations", "", "JSON Files (*.json)", options=options
@@ -586,6 +634,34 @@ class LayerifyTab(QWidget):
586
634
  """
587
635
  Load annotations from a JSON file.
588
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
589
665
  options = QFileDialog.Options()
590
666
  file_name, _ = QFileDialog.getOpenFileName(
591
667
  self, "Load Annotations", "", "JSON Files (*.json)", options=options
@@ -947,6 +1023,39 @@ class LayerifyTab(QWidget):
947
1023
  elif key == Qt.Key_E:
948
1024
  self.layer.set_mode(MouseMode.RECTANGLE)
949
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()
950
1059
 
951
1060
  # Pass the event to the annotation list if it needs to handle it
952
1061
  if self.annotation_list.hasFocus():
@@ -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
@@ -187,3 +187,68 @@ class MainWindow(QMainWindow):
187
187
  logger.info(
188
188
  f"Deleted cache directory: {self.layerify_config.cache_dir}"
189
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)
@@ -39,6 +39,8 @@ class BakerWorker(QObject):
39
39
  self.layers = layers
40
40
  self.filename = filename
41
41
 
42
+ # logger.info(f"Received States: {self.states}")
43
+
42
44
  def process(self):
43
45
  results = []
44
46
  try:
@@ -128,6 +130,9 @@ class BakerWorker(QObject):
128
130
  painter.restore()
129
131
 
130
132
  # Draw the drawing states
133
+ logger.debug(
134
+ f"Drawing states for layer {layer.layer_name}: {state.drawing_states}"
135
+ )
131
136
  if state.drawing_states:
132
137
  painter.save()
133
138
  try:
@@ -147,6 +152,10 @@ class BakerWorker(QObject):
147
152
  painter.drawPoint(
148
153
  drawing_state.position - top_left
149
154
  )
155
+ except Exception as e:
156
+ logger.error(
157
+ f"Error drawing state for layer {layer.layer_name}: {e}"
158
+ )
150
159
  finally:
151
160
  painter.restore()
152
161
 
@@ -201,6 +210,7 @@ class BakerWorker(QObject):
201
210
  new_annotation = self._generate_annotation(
202
211
  ann, alpha_channel
203
212
  )
213
+ new_annotation.caption = layer.caption
204
214
  new_annotations.append(new_annotation)
205
215
  finally:
206
216
  painter.end()
@@ -244,6 +254,8 @@ class BakerWorker(QObject):
244
254
  annotation_id=ann.annotation_id,
245
255
  is_complete=True,
246
256
  visible=True,
257
+ caption=ann.caption,
258
+ is_model_generated=ann.is_model_generated,
247
259
  )
248
260
 
249
261
  if ann.points:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.51
3
+ Version: 0.0.53
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -38,7 +38,7 @@ Requires-Dist: mkdocs-awesome-pages-plugin; extra == "docs"
38
38
  [![PyPI version](https://img.shields.io/pypi/v/imagebaker.svg)](https://pypi.org/imagebaker/)
39
39
 
40
40
  <p align="center">
41
- <img src="assets/demo.gif" alt="Centered Demo" />
41
+ <img src="https://github.com/q-viper/image-baker/blob/main/assets/demo.gif?raw=true" alt="Centered Demo" />
42
42
  </p>
43
43
 
44
44
 
@@ -76,27 +76,28 @@ Run the following command to launch the GUI:
76
76
 
77
77
  `imagebaker`
78
78
 
79
- By default, the above command will not run any models on the backend. So please take a look into the example of model definition at [examples/loaded_models.py](examples/loaded_models.py). Then we need to pass it as:
79
+ By default, the above command will not run any models on the backend. So please take a look into the example of model definition at [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py). Then we need to pass it as:
80
80
 
81
81
  `imagebaker --models-file examples/loaded_models.py`
82
82
 
83
83
  For more options, please do: `imagebaker --help` It should give the following options.
84
84
 
85
- ![](assets/demo/options.png)
85
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/options.png?raw=true)
86
86
 
87
87
 
88
- * **`--configs-file`** allows us to define custom configs. The custom configs have to inherit LayerConfig and CanvasConfig defined at [imagebaker/core/configs/configs.py](imagebaker/core/configs/configs.py). An example is available at [examples](examples/).
88
+ * **`--configs-file`** allows us to define custom configs. The custom configs have to inherit LayerConfig and CanvasConfig defined at [imagebaker/core/configs/configs.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/core/configs/configs.py). An example is available at [examples](https://github.com/q-viper/image-baker/blob/main/examples/).
89
89
 
90
90
  After cloning and going to the project directory, the following code should work.
91
91
  `imagebaker --models-file examples/loaded_models.py --configs-file examples/example_config.py`
92
92
 
93
93
  ## Features
94
94
  - **Annotating Images**: Load a folder of images and annotate them using bounding boxes or polygons.
95
- - **Model Testing**: Define models for detection, segmentation, and prompts (e.g., points or rectangles) by following the base model structure in [imagebaker/models/base_model.py](imagebaker/models/base_model.py). See [examples/loaded_models.py](examples/loaded_models.py) for a working example.
95
+ - **Model Testing**: Define models for detection, segmentation, and prompts (e.g., points or rectangles) by following the base model structure in [imagebaker/models/base_model.py](https://github.com/q-viper/image-baker/blob/main/imagebaker/models/base_model.py). See [examples/loaded_models.py](https://github.com/q-viper/image-baker/blob/main/examples/loaded_models.py) for a working example.
96
96
  - **Layerifying**: Crop images based on annotations to create reusable layers. Each cropped image represents a single layer.
97
97
  - **Baking States**: Arrange layers to create image variations by dragging, rotating, adjusting opacity, and more. Save the state using the Save State button or Ctrl + S.
98
98
  - **Playing States**: Replay saved states, export them locally, or use them for further predictions.
99
99
  - **Exporting States**: Export the final annotated JSON and the baked multilayer image.
100
+ - **Drawing On Layers**: First select a layer then draw upon it. Only selected layer will be drawn. And if no layers are selected, then the drawing will not be exported.
100
101
 
101
102
  ### Shortcuts
102
103
  * **Ctrl + C**: Copy selected annotation/layer.
@@ -105,37 +106,44 @@ After cloning and going to the project directory, the following code should work
105
106
  * **Left Click**: Select an annotation/layer on mouse position.
106
107
  * **Left Click + Drag**: Drag a selected annotation/layer.
107
108
  * **Double Left Click**: When using polygon annotation, completes the polygon.
108
- * **Right Click**: Deselect an annotation/layer. While annotating the polygon, undo the last point.
109
+ * **Right Click**: Unselect an annotation/layer. While annotating the polygon, undo the last point.
109
110
  * **Ctrl + Mouse Wheel**: Zoom In/Out on the mouse position, i.e., resize the viewport.
110
111
  * **Ctrl + Drag**: If done on the background, the viewport is panned.
111
112
  * **Ctrl + S**: Save State on Baker Tab.
112
113
  * **Ctrl + D**: Draw Mode on Baker Tab. Drawing can happen on a selected or main layer.
113
114
  * **Ctrl + E**: Erase Mode on Baker Tab.
115
+ * **Ctrl + H**: Opens a help window.
114
116
  * **Wheel**: Change the size of the drawing pointer.
115
117
  * **Q**: Point mode on annotation.
116
- * **W**: Polygon mode on annotation.
118
+ * **W**: Polygon mode on annotation. Moves selected layer one step up in layer lists in baker.
119
+ * **S**: Moves selected layer one step down in layer list in baker.
117
120
  * **E**: Rectangle mode on annotation.
121
+ * **H**: Hides/un-hides selected annotation/layer.
122
+ * **L**: Creates layer from an annotation. If any annotation selected, creates only its, else creates layers from all visible annotations.
123
+ * **C**: If any annotation is selected, a input box for Caption is created. It can be edited on baker tab as well and is state aware.
124
+ * **Numerics**: Selecting number 1, 2, till 9 selects label. If not available, asks for a new label.
125
+ * **Escape**: Closes the application.
118
126
 
119
127
  ## Demo
120
128
  ### Annotation Page
121
129
  This is where the loading of the image folder and annotation, connection with the model running in the backend, and layerifying happen.
122
130
 
123
- ![](assets/demo/annotation_page.png)
131
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotation_page.png?raw=True)
124
132
 
125
133
  ### Baker Page
126
134
  This is where the layer baking happens. And the extraction of the layers as well.
127
135
 
128
- ![](assets/demo/baker_page.png)
136
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/baker_page.png?raw=True)
129
137
 
130
138
  An example of drawing:
131
139
 
132
- ![](assets/demo/drawing.png)
140
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/drawing.png?raw=True)
133
141
 
134
142
  ### Annotated
135
143
 
136
144
  The JSON and the baked image will be exported to the local folder, and in debug mode, the annotations and the mask for each layer will be exported too.
137
145
 
138
- ![](assets/demo/annotated_veg_smiley.png)
146
+ ![](https://github.com/q-viper/image-baker/blob/main/assets/demo/annotated_veg_smiley.png?raw=True)
139
147
 
140
148
  ### Demo Video
141
149
 
@@ -154,3 +162,5 @@ Click on the image above to play the video on YouTube.
154
162
  Contributions are welcome!
155
163
 
156
164
  Do you find this project to be useful and are you looking for some features that are not implemented yet? Feel free to open issues or submit pull requests to improve the project.
165
+
166
+ For more please visit [CONTRIBUTING](CONTRIBUTING).
@@ -3,41 +3,41 @@ imagebaker/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  imagebaker/core/configs/__init__.py,sha256=iyR_GOVMFw3XJSm7293YfyTnaLZa7pLQMfn5tGxVofI,31
4
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=oZCkgqHgvE_yupUzD5IB_ZMoRhyTh0EdCR8Gmh6TMsI,8505
6
+ imagebaker/core/defs/defs.py,sha256=67L65v8zTKwcrgEgJUVXn3n4xhVuylO23GLvJ-qD1ZU,8710
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=xkV6KTXN8-N6Wu3QAj5nQDMPZ4bRr673_fu5QuRKBj8,37128
12
- imagebaker/layers/base_layer.py,sha256=1K7Nt6OPITrILj-p4I6Jf0eaesCpdeecOXGj_8oAQb8,30650
13
- imagebaker/layers/canvas_layer.py,sha256=6quVe_Ieyz14zPL92xTfrinAT7X4yb9iyEl-f9SnQZU,42116
11
+ imagebaker/layers/annotable_layer.py,sha256=v8sE73ewESh6sMzFZGrcYTWI-FY-J5-noiIr4YN_rXc,40696
12
+ imagebaker/layers/base_layer.py,sha256=Tbu1D6M2jzK_pIns3ghbHa3NK-htV-klxGo13lR5hNk,30841
13
+ imagebaker/layers/canvas_layer.py,sha256=9nwRzEo1Bp5KrtVYPFKXUVMCqkSW2qflgKBpI0V9FzI,43132
14
14
  imagebaker/list_views/__init__.py,sha256=Aa9slE6do8eYgZp77wrofpd_mlBDwxgF3adMyHYFanE,144
15
- imagebaker/list_views/annotation_list.py,sha256=w6jNMyo3nxhQKZx8MyusNvxs_R6Pj-b-YBI5e0APZVM,7528
15
+ imagebaker/list_views/annotation_list.py,sha256=3AN9uv-RcKFrJidfdC8R2m_tIbvf4IPRYcea4fCB5j4,7604
16
16
  imagebaker/list_views/canvas_list.py,sha256=JYSYR0peGyJFJ6amL1894KsUHETPUkR3qAWdGL50Lbc,6717
17
- imagebaker/list_views/image_list.py,sha256=EX2YmoTRffBXTbbVnuflbdsxZQD8fgM2StSxg1a9ANA,5389
17
+ imagebaker/list_views/image_list.py,sha256=kS6IMPhsyYXHZvx9lrUgPLmbZgNoiLlbFJW6wS6icjU,5582
18
18
  imagebaker/list_views/layer_list.py,sha256=fLx3Ry72fas1W5y_V84hSp41ARneogQN3qjfYTOcpxY,14476
19
- imagebaker/list_views/layer_settings.py,sha256=0WVSCm_RSBKo4pCkYU5c2OYjb_sW8x0UUfFC4So26jQ,9752
19
+ imagebaker/list_views/layer_settings.py,sha256=KT0B4yIjR9DQmekIDh_TS0gBB6OkjdNBIZ1HnCFdUto,11025
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
- imagebaker/tabs/baker_tab.py,sha256=yFQRiNmLIua5BvgW7Ysj5FrNbTLhGxqGRTRCABxeTtw,20493
24
- imagebaker/tabs/layerify_tab.py,sha256=F3BK5XsVCteB-2zyFBKqeWyHgIif5cU3TBEMbuDH3QM,37415
23
+ imagebaker/tabs/baker_tab.py,sha256=8V1t-cDCSotz6ZDv6o-6HA8H9eli5_0vVBPkkUP9_x4,23091
24
+ imagebaker/tabs/layerify_tab.py,sha256=4kbWOgDszuYEFI6VOrzqFpwvBAxe_ihFayubul30lz8,42703
25
25
  imagebaker/utils/__init__.py,sha256=I1z5VVEf6QPOMvVkgVHDauQ9ew7tcTVguV4Kdi3Lk4Y,130
26
26
  imagebaker/utils/image.py,sha256=2-wbwD3PMwefgswge0drFM1XfXE7yQ64lqZZ5PwyCWs,3165
27
- imagebaker/utils/state_utils.py,sha256=Y2JVRGVfsoffwfA2lsCcqHwIxH_jOrEJAdH-oWfe2XE,3841
27
+ imagebaker/utils/state_utils.py,sha256=6ivXVvamuYHQ6LjfoRIAuiTiZ4-LxUad22fvCH0CBcE,3932
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=-CP7q6xzkhv9Cl5RwUft1Rv8nIAuj_SLOiZDS2x6QJ4,7374
33
+ imagebaker/window/main_window.py,sha256=lpfTffUOKZx6rN_9uP1OQ3A2GRdRKzQYcDn-GZxAuLg,11342
34
34
  imagebaker/workers/__init__.py,sha256=XfXENwAYyNg9q_zR-gOsYJGjzwg_iIb_gING8ydnp9c,154
35
- imagebaker/workers/baker_worker.py,sha256=_jWeyYAGoO2mfxXDn7fBm9tIA69OITewDVN0hSAt3Jc,11532
35
+ imagebaker/workers/baker_worker.py,sha256=Bg5gcO91Ip1Egvs_hy9reQrsGNssbS7_nn_oYsmX5mE,12164
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.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,,
38
+ imagebaker-0.0.53.dist-info/LICENSE,sha256=1vkysFPOnT7y4LsoFTv9YsopIrQvBc2l6vUOfv4KKLc,1082
39
+ imagebaker-0.0.53.dist-info/METADATA,sha256=_PgfaiQ55tSgQ-kZyPA-qc35RlpfFFf_n1aB3WXLLgw,8382
40
+ imagebaker-0.0.53.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
41
+ imagebaker-0.0.53.dist-info/entry_points.txt,sha256=IDjZHJCiiHpH5IUTByT2en0nMbnnnlrJZ5FPFehUvQM,61
42
+ imagebaker-0.0.53.dist-info/top_level.txt,sha256=Gg-eILTlqJXwVQr0saSwsx3-H4SPdZ2agBZaufe194s,11
43
+ imagebaker-0.0.53.dist-info/RECORD,,