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.
- imagebaker/core/defs/defs.py +6 -0
- imagebaker/layers/annotable_layer.py +89 -12
- imagebaker/layers/base_layer.py +9 -1
- imagebaker/layers/canvas_layer.py +28 -5
- imagebaker/list_views/annotation_list.py +3 -2
- imagebaker/list_views/image_list.py +5 -0
- imagebaker/list_views/layer_settings.py +29 -0
- imagebaker/tabs/baker_tab.py +49 -1
- imagebaker/tabs/layerify_tab.py +114 -5
- imagebaker/utils/state_utils.py +1 -0
- imagebaker/window/main_window.py +65 -0
- imagebaker/workers/baker_worker.py +12 -0
- {imagebaker-0.0.51.dist-info → imagebaker-0.0.53.dist-info}/METADATA +22 -12
- {imagebaker-0.0.51.dist-info → imagebaker-0.0.53.dist-info}/RECORD +18 -18
- {imagebaker-0.0.51.dist-info → imagebaker-0.0.53.dist-info}/LICENSE +0 -0
- {imagebaker-0.0.51.dist-info → imagebaker-0.0.53.dist-info}/WHEEL +0 -0
- {imagebaker-0.0.51.dist-info → imagebaker-0.0.53.dist-info}/entry_points.txt +0 -0
- {imagebaker-0.0.51.dist-info → imagebaker-0.0.53.dist-info}/top_level.txt +0 -0
imagebaker/core/defs/defs.py
CHANGED
@@ -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
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
302
|
-
|
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.
|
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
|
-
|
831
|
-
|
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()
|
imagebaker/layers/base_layer.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
205
|
-
self.
|
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")
|
imagebaker/tabs/baker_tab.py
CHANGED
@@ -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)
|
imagebaker/tabs/layerify_tab.py
CHANGED
@@ -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(
|
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
|
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(
|
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
|
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():
|
imagebaker/utils/state_utils.py
CHANGED
@@ -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
|
imagebaker/window/main_window.py
CHANGED
@@ -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.
|
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
|
[](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
|
-

|
85
|
+

|
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**:
|
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
|
-

|
131
|
+

|
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
|
-

|
136
|
+

|
129
137
|
|
130
138
|
An example of drawing:
|
131
139
|
|
132
|
-

|
140
|
+

|
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
|
-

|
146
|
+

|
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=
|
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=
|
12
|
-
imagebaker/layers/base_layer.py,sha256=
|
13
|
-
imagebaker/layers/canvas_layer.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
24
|
-
imagebaker/tabs/layerify_tab.py,sha256=
|
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=
|
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
|
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=
|
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.
|
39
|
-
imagebaker-0.0.
|
40
|
-
imagebaker-0.0.
|
41
|
-
imagebaker-0.0.
|
42
|
-
imagebaker-0.0.
|
43
|
-
imagebaker-0.0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|