imagebaker 0.0.48__py3-none-any.whl → 0.0.50__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/__init__.py CHANGED
@@ -1,5 +1,9 @@
1
1
  from loguru import logger # noqa
2
+ from importlib.metadata import version, PackageNotFoundError
2
3
 
3
4
  logger.info("imagebaker package loaded with loguru logger.")
4
5
 
5
- __version__ = "0.0.48" # noqa
6
+ try:
7
+ __version__ = version("imagebaker")
8
+ except PackageNotFoundError:
9
+ __version__ = "0.0.0"
@@ -8,6 +8,11 @@ from pydantic import BaseModel, Field
8
8
  from imagebaker.core.defs import Label, ModelType
9
9
  from imagebaker import logger
10
10
 
11
+ try:
12
+ from imagebaker import __version__
13
+ except ImportError:
14
+ __version__ = "unknown"
15
+
11
16
 
12
17
  class DrawConfig(BaseModel):
13
18
  color: QColor = Field(default_factory=lambda: QColor(255, 255, 255))
@@ -37,11 +42,13 @@ class DrawConfig(BaseModel):
37
42
 
38
43
  class BaseConfig(BaseModel):
39
44
  project_name: str = "ImageBaker"
40
- version: str = "0.1.0"
45
+ version: str = __version__
41
46
  project_dir: Path = Path(".")
42
47
 
43
48
  is_debug: bool = True
44
49
  deque_maxlen: int = 10
50
+ # max num_characters to show in name
51
+ max_name_length: int = 15
45
52
 
46
53
  # drawing configs #
47
54
  # ON SELECTION
@@ -87,6 +94,8 @@ class LayerConfig(BaseConfig):
87
94
  Label("Custom", QColor(128, 128, 128)),
88
95
  ]
89
96
  )
97
+ # whether to search image in subfolders as well
98
+ full_search: bool = False
90
99
 
91
100
  def get_label_color(self, label):
92
101
  for lbl in self.predefined_labels:
@@ -110,6 +119,7 @@ class CanvasConfig(BaseConfig):
110
119
  write_labels: bool = True
111
120
  write_masks: bool = True
112
121
  fps: int = 5
122
+ max_edge_width: int = 100
113
123
 
114
124
  @property
115
125
  def export_folder(self):
@@ -76,6 +76,8 @@ class LayerState:
76
76
  is_annotable: bool = True
77
77
  status: str = "Ready"
78
78
  drawing_states: list[DrawingState] = field(default_factory=list)
79
+ edge_opacity: int = 100
80
+ edge_width: int = 10
79
81
 
80
82
  def copy(self):
81
83
  return LayerState(
@@ -105,6 +107,8 @@ class LayerState:
105
107
  )
106
108
  for d in self.drawing_states
107
109
  ],
110
+ edge_opacity=self.edge_opacity,
111
+ edge_width=self.edge_width,
108
112
  )
109
113
 
110
114
 
@@ -54,6 +54,7 @@ class AnnotableLayer(BaseLayer):
54
54
  self.file_path: Path = Path("Runtime")
55
55
  self.layers: list[BaseLayer] = []
56
56
  self.is_annotable = True
57
+ self.handle_zoom: float = 1
57
58
 
58
59
  def init_ui(self):
59
60
  logger.info(f"Initializing Layer UI of {self.layer_name}")
@@ -157,14 +158,14 @@ class AnnotableLayer(BaseLayer):
157
158
  self.config.normal_draw_config.brush_alpha,
158
159
  )
159
160
 
160
- pen = QPen(pen_color, self.config.normal_draw_config.line_width)
161
+ pen = QPen(pen_color, self.config.normal_draw_config.line_width / self.scale)
161
162
  brush = QBrush(brush_color, Qt.DiagCrossPattern)
162
163
 
163
164
  if annotation.selected:
164
165
  painter.setPen(
165
166
  QPen(
166
167
  self.config.selected_draw_config.color,
167
- self.config.selected_draw_config.line_width,
168
+ self.config.selected_draw_config.line_width / self.scale,
168
169
  )
169
170
  )
170
171
  painter.setBrush(
@@ -184,8 +185,8 @@ class AnnotableLayer(BaseLayer):
184
185
  elif annotation.points:
185
186
  painter.drawEllipse(
186
187
  annotation.points[0],
187
- self.config.selected_draw_config.ellipse_size,
188
- self.config.selected_draw_config.ellipse_size,
188
+ self.config.selected_draw_config.ellipse_size / self.scale,
189
+ self.config.selected_draw_config.ellipse_size / self.scale,
189
190
  )
190
191
 
191
192
  if is_temp:
@@ -200,8 +201,8 @@ class AnnotableLayer(BaseLayer):
200
201
  for point in annotation.points:
201
202
  painter.drawEllipse(
202
203
  point,
203
- self.config.normal_draw_config.point_size,
204
- self.config.normal_draw_config.point_size,
204
+ self.config.normal_draw_config.point_size / self.scale,
205
+ self.config.normal_draw_config.point_size / self.scale,
205
206
  )
206
207
  elif annotation.rectangle:
207
208
  painter.drawRect(annotation.rectangle)
@@ -223,28 +224,34 @@ class AnnotableLayer(BaseLayer):
223
224
  ]
224
225
  painter.save()
225
226
  painter.setPen(
226
- QPen(Qt.black, self.config.normal_draw_config.control_point_size)
227
+ QPen(
228
+ Qt.black,
229
+ self.config.normal_draw_config.control_point_size / self.scale,
230
+ )
227
231
  )
228
232
  painter.setBrush(QBrush(Qt.white))
229
233
  for corner in corners:
230
234
  painter.drawEllipse(
231
235
  corner,
232
- self.config.normal_draw_config.point_size,
233
- self.config.normal_draw_config.point_size,
236
+ self.config.normal_draw_config.point_size / self.scale,
237
+ self.config.normal_draw_config.point_size / self.scale,
234
238
  )
235
239
  painter.restore()
236
240
 
237
241
  if annotation.polygon and len(annotation.polygon) > 0:
238
242
  painter.save()
239
243
  painter.setPen(
240
- QPen(Qt.white, self.config.normal_draw_config.control_point_size)
244
+ QPen(
245
+ Qt.white,
246
+ self.config.normal_draw_config.control_point_size / self.scale,
247
+ )
241
248
  )
242
249
  painter.setBrush(QBrush(Qt.darkGray))
243
250
  for point in annotation.polygon:
244
251
  painter.drawEllipse(
245
252
  point,
246
- self.config.normal_draw_config.point_size,
247
- self.config.normal_draw_config.point_size,
253
+ self.config.normal_draw_config.point_size / self.scale,
254
+ self.config.normal_draw_config.point_size / self.scale,
248
255
  )
249
256
  painter.restore()
250
257
 
@@ -266,7 +273,7 @@ class AnnotableLayer(BaseLayer):
266
273
  # Set up font
267
274
  font = painter.font()
268
275
  font.setPixelSize(
269
- self.config.normal_draw_config.label_font_size
276
+ self.config.normal_draw_config.label_font_size * self.scale
270
277
  ) # Fixed screen size
271
278
  painter.setFont(font)
272
279
 
@@ -300,7 +307,10 @@ class AnnotableLayer(BaseLayer):
300
307
  painter.save()
301
308
  handle_color = self.config.selected_draw_config.handle_color
302
309
  painter.setPen(
303
- QPen(handle_color, self.config.selected_draw_config.handle_width)
310
+ QPen(
311
+ handle_color,
312
+ self.config.selected_draw_config.handle_width / self.scale,
313
+ )
304
314
  )
305
315
  painter.setBrush(QBrush(handle_color))
306
316
 
@@ -315,8 +325,8 @@ class AnnotableLayer(BaseLayer):
315
325
  ]:
316
326
  painter.drawEllipse(
317
327
  corner,
318
- self.config.selected_draw_config.handle_point_size,
319
- self.config.selected_draw_config.handle_point_size,
328
+ self.config.selected_draw_config.handle_point_size / self.scale,
329
+ self.config.selected_draw_config.handle_point_size / self.scale,
320
330
  )
321
331
  # Draw edge handles
322
332
  for edge in [
@@ -327,8 +337,8 @@ class AnnotableLayer(BaseLayer):
327
337
  ]:
328
338
  painter.drawEllipse(
329
339
  edge,
330
- self.config.selected_draw_config.handle_edge_size,
331
- self.config.selected_draw_config.handle_edge_size,
340
+ self.config.selected_draw_config.handle_edge_size / self.scale,
341
+ self.config.selected_draw_config.handle_edge_size / self.scale,
332
342
  )
333
343
 
334
344
  elif annotation.polygon:
@@ -336,8 +346,8 @@ class AnnotableLayer(BaseLayer):
336
346
  for point in annotation.polygon:
337
347
  painter.drawEllipse(
338
348
  point,
339
- self.config.selected_draw_config.handle_point_size,
340
- self.config.selected_draw_config.handle_point_size,
349
+ self.config.selected_draw_config.handle_point_size / self.scale,
350
+ self.config.selected_draw_config.handle_point_size / self.scale,
341
351
  )
342
352
 
343
353
  painter.restore()
@@ -472,10 +482,13 @@ class AnnotableLayer(BaseLayer):
472
482
  self.selected_annotation.polygon[self.active_point_index] = (
473
483
  clamped_pos
474
484
  )
485
+ elif self.selected_annotation.points:
486
+ self.selected_annotation.points[0] = clamped_pos
475
487
  self.annotationMoved.emit()
476
488
  self.annotationUpdated.emit(self.selected_annotation)
477
489
  self.update()
478
490
  return
491
+
479
492
  if self.mouse_mode == MouseMode.PAN and event.buttons() & Qt.LeftButton:
480
493
  if self.pan_start:
481
494
  delta = event.position() - self.pan_start
@@ -534,6 +547,7 @@ class AnnotableLayer(BaseLayer):
534
547
  self.mouse_mode = MouseMode.IDLE
535
548
  for ann in self.annotations:
536
549
  ann.selected = False
550
+ self.annotationUpdated.emit(ann)
537
551
  self.update()
538
552
 
539
553
  # If left-clicked
@@ -559,7 +573,9 @@ class AnnotableLayer(BaseLayer):
559
573
  elif self.selected_annotation.polygon:
560
574
  self.initial_polygon = QPolygonF(self.selected_annotation.polygon)
561
575
  if "point_" in self.active_handle:
562
- self.active_point = int(self.active_handle.split("_")[1])
576
+ self.active_point_index = int(self.active_handle.split("_")[1])
577
+ elif self.selected_annotation.points:
578
+ self.active_point_index = 0
563
579
 
564
580
  # If pan mode
565
581
  if self.mouse_mode == MouseMode.PAN:
@@ -646,6 +662,11 @@ class AnnotableLayer(BaseLayer):
646
662
  if annotation.polygon.containsPoint(pos, Qt.OddEvenFill):
647
663
  return annotation, "move"
648
664
 
665
+ # Check points
666
+ elif annotation.points:
667
+ if (annotation.points[0] - pos).manhattanLength() < margin:
668
+ return annotation, "point_0"
669
+
649
670
  return None, None
650
671
 
651
672
  def handle_mouse_double_click(self, event: QMouseEvent, pos: QPoint):
@@ -656,6 +677,73 @@ class AnnotableLayer(BaseLayer):
656
677
  break
657
678
  # if left double click
658
679
  if event.button() == Qt.LeftButton:
680
+ img_pos = self.widget_to_image_pos(event.position())
681
+
682
+ self.selected_annotation, self.active_handle = (
683
+ self.find_annotation_and_handle_at(img_pos)
684
+ )
685
+
686
+ if self.selected_annotation:
687
+ if self.selected_annotation.polygon and self.active_handle:
688
+ if "point_" in self.active_handle:
689
+ index = int(self.active_handle.split("_")[1])
690
+ # Remove the point at the clicked index
691
+ polygon = self.selected_annotation.polygon
692
+ polygon = QPolygonF(
693
+ [p for i, p in enumerate(polygon) if i != index]
694
+ )
695
+
696
+ self.selected_annotation.polygon = polygon
697
+ self.annotationUpdated.emit(self.selected_annotation)
698
+ self.update()
699
+ logger.info(f"Removed point at index {index}")
700
+ return
701
+
702
+ # Check if an edge was double-clicked
703
+ polygon = self.selected_annotation.polygon
704
+ if polygon:
705
+ for i in range(len(polygon)):
706
+ start_point = polygon[i]
707
+ end_point = polygon[
708
+ (i + 1) % len(polygon)
709
+ ] # Wrap around to the first point
710
+
711
+ # Calculate the vector along the edge and the vector from the start point to the clicked position
712
+ line_vector = end_point - start_point
713
+ point_vector = img_pos - start_point
714
+
715
+ # Calculate the length of the edge
716
+ line_length_squared = (
717
+ line_vector.x() ** 2 + line_vector.y() ** 2
718
+ )
719
+ if line_length_squared == 0:
720
+ continue # Avoid division by zero for degenerate edges
721
+
722
+ # Project the point onto the line (normalized)
723
+ projection = (
724
+ point_vector.x() * line_vector.x()
725
+ + point_vector.y() * line_vector.y()
726
+ ) / line_length_squared
727
+
728
+ # Clamp the projection to the range [0, 1] to ensure it lies on the segment
729
+ projection = max(0, min(1, projection))
730
+
731
+ # Calculate the projection point on the edge
732
+ projection_point = start_point + projection * line_vector
733
+
734
+ # Calculate the perpendicular distance from the clicked position to the edge
735
+ perpendicular_distance = (
736
+ img_pos - projection_point
737
+ ).manhattanLength()
738
+
739
+ # Check if the perpendicular distance is within the margin
740
+ if perpendicular_distance < 10: # Margin of 10
741
+ # Insert a new point at the projection point
742
+ polygon.insert(i + 1, projection_point)
743
+ self.annotationUpdated.emit(self.selected_annotation)
744
+ self.update()
745
+ return
746
+
659
747
  # if drawing a polygon, close the polygon
660
748
  if (
661
749
  self.current_annotation
@@ -670,20 +758,20 @@ class AnnotableLayer(BaseLayer):
670
758
  return
671
759
 
672
760
  # did we click on an annotation?
673
- annotation = self.find_annotation_at(self.widget_to_image_pos(pos))
674
- if annotation:
675
- # toggle selection
676
- annotation.selected = not annotation.selected
677
-
678
- # make all other annotations unselected
679
- for ann in self.annotations:
680
- if ann != annotation:
681
- ann.selected = False
682
- else:
683
- # we clicked on the background
684
- # make all annotations unselected
685
- for ann in self.annotations:
686
- ann.selected = False
761
+ # annotation = self.find_annotation_at(self.widget_to_image_pos(pos))
762
+ # if annotation:
763
+ # # toggle selection
764
+ # annotation.selected = not annotation.selected
765
+
766
+ # # make all other annotations unselected
767
+ # for ann in self.annotations:
768
+ # if ann != annotation:
769
+ # ann.selected = False
770
+ # else:
771
+ # # we clicked on the background
772
+ # # make all annotations unselected
773
+ # for ann in self.annotations:
774
+ # ann.selected = False
687
775
  # update the view
688
776
  for ann in self.annotations:
689
777
  self.annotationUpdated.emit(ann)
@@ -828,6 +916,8 @@ class AnnotableLayer(BaseLayer):
828
916
  f"{annotation.label} {annotation.annotation_id} {annotation.annotator}"
829
917
  )
830
918
 
919
+ new_layer._apply_edge_opacity()
920
+ new_layer.update()
831
921
  self.messageSignal.emit(f"Layerified: {new_layer.layer_name}")
832
922
  logger.info(f"Num annotations: {len(self.annotations)}")
833
923
 
@@ -18,6 +18,8 @@ from PySide6.QtWidgets import QWidget
18
18
 
19
19
  from typing import Optional
20
20
  from pathlib import Path
21
+ import cv2
22
+ import numpy as np
21
23
 
22
24
 
23
25
  class BaseLayer(QWidget):
@@ -157,7 +159,8 @@ class BaseLayer(QWidget):
157
159
  self.offset: QPointF = QPointF(0, 0)
158
160
  self.pan_start: QPointF = None
159
161
  self.pan_offset: QPointF = None
160
- self.image = QPixmap()
162
+ self._image = QPixmap()
163
+ self._original_image = QPixmap()
161
164
  self.annotations: list[Annotation] = []
162
165
  self.current_annotation: Optional[Annotation] = None
163
166
  self.copied_annotation: Optional[Annotation] = None
@@ -365,8 +368,120 @@ class BaseLayer(QWidget):
365
368
  self.reset_view()
366
369
  self.update()
367
370
 
371
+ self._original_image = self.image.copy() # Store a copy of the original image
368
372
  self.original_size = QSizeF(self.image.size()) # Store original size
369
373
 
374
+ @property
375
+ def image(self):
376
+ """
377
+ Get the current image of the canvas layer.
378
+
379
+ Returns:
380
+ QPixmap: The current image of the canvas layer.
381
+ """
382
+ return self._image
383
+
384
+ @image.setter
385
+ def image(self, value: QPixmap):
386
+ """
387
+ Set the image of the canvas layer.
388
+
389
+ Args:
390
+ value (QPixmap): The new image for the canvas layer.
391
+ """
392
+ self._image = value
393
+
394
+ def _apply_edge_opacity(self):
395
+ """
396
+ Apply edge opacity to the image. This function modifies the edges of the image
397
+ to have reduced opacity based on the configuration.
398
+ """
399
+ logger.debug("Applying edge opacity to the image.")
400
+ edge_width = self.edge_width
401
+ edge_opacity = self.edge_opacity
402
+
403
+ # Convert QPixmap to QImage for pixel manipulation
404
+ image = self._original_image.toImage()
405
+ image = image.convertToFormat(
406
+ QImage.Format_ARGB32
407
+ ) # Ensure format supports alpha
408
+
409
+ width = image.width()
410
+ height = image.height()
411
+ annotation = self.annotations[0] if self.annotations else None
412
+ if annotation is None:
413
+ return
414
+
415
+ if annotation.rectangle:
416
+ for x in range(width):
417
+ for y in range(height):
418
+ color = image.pixelColor(x, y)
419
+ if color.alpha() != 0: # If the pixel is not fully transparent
420
+ # Calculate horizontal and vertical distances to the edges
421
+ horizontal_distance = min(x, width - x - 1)
422
+ vertical_distance = min(y, height - y - 1)
423
+
424
+ # If the pixel is within the edge region
425
+ if (
426
+ horizontal_distance < edge_width
427
+ or vertical_distance < edge_width
428
+ ):
429
+ distance_to_edge = min(
430
+ horizontal_distance, vertical_distance
431
+ )
432
+ # Calculate the new alpha based on the distance to the edge
433
+ factor = (edge_width - distance_to_edge) / edge_width
434
+ new_alpha = int(
435
+ color.alpha()
436
+ * ((1 - factor) + (factor * (edge_opacity / 255.0)))
437
+ )
438
+ color.setAlpha(new_alpha)
439
+ image.setPixelColor(x, y, color)
440
+
441
+ elif annotation.polygon:
442
+ # Extract alpha channel and find contours
443
+ alpha_image = image.convertToFormat(QImage.Format_Alpha8)
444
+ bytes_per_line = (
445
+ alpha_image.bytesPerLine()
446
+ ) # Get the stride (bytes per line)
447
+ alpha_data = alpha_image.bits().tobytes()
448
+
449
+ # Extract only the valid data (remove padding)
450
+ alpha_array = np.frombuffer(alpha_data, dtype=np.uint8).reshape(
451
+ (alpha_image.height(), bytes_per_line)
452
+ )[
453
+ :, : alpha_image.width()
454
+ ] # Remove padding to match the actual width
455
+
456
+ # Use OpenCV to find contours
457
+ contours, _ = cv2.findContours(
458
+ alpha_array, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
459
+ )
460
+
461
+ # Iterate over each pixel and apply edge opacity
462
+ for x in range(width):
463
+ for y in range(height):
464
+ color = image.pixelColor(x, y)
465
+ if color.alpha() != 0: # If the pixel is not fully transparent
466
+ # Calculate distance to the nearest contour
467
+ distance_to_edge = cv2.pointPolygonTest(
468
+ contours[0], (x, y), True
469
+ ) # True for distance calculation
470
+
471
+ # If the pixel is within the edge region
472
+ if 0 <= distance_to_edge < edge_width:
473
+ # Calculate the new alpha based on the distance to the edge
474
+ factor = (edge_width - distance_to_edge) / edge_width
475
+ new_alpha = int(
476
+ color.alpha()
477
+ * ((1 - factor) + (factor * (edge_opacity / 255.0)))
478
+ )
479
+ color.setAlpha(new_alpha)
480
+ image.setPixelColor(x, y, color)
481
+
482
+ # Convert the modified QImage back to QPixmap
483
+ self.image = QPixmap.fromImage(image)
484
+
370
485
  def get_thumbnail(self, annotation: Annotation = None):
371
486
  """
372
487
  Generate a thumbnail for the layer or a specific annotation.
@@ -722,3 +837,19 @@ class BaseLayer(QWidget):
722
837
  @drawing_states.setter
723
838
  def drawing_states(self, value: list[DrawingState]):
724
839
  self.layer_state.drawing_states = value
840
+
841
+ @property
842
+ def edge_opacity(self) -> int:
843
+ return self.layer_state.edge_opacity
844
+
845
+ @edge_opacity.setter
846
+ def edge_opacity(self, value: int):
847
+ self.layer_state.edge_opacity = value
848
+
849
+ @property
850
+ def edge_width(self) -> int:
851
+ return self.layer_state.edge_width
852
+
853
+ @edge_width.setter
854
+ def edge_width(self, value: int):
855
+ self.layer_state.edge_width = value
@@ -27,6 +27,7 @@ from PySide6.QtGui import (
27
27
  QMouseEvent,
28
28
  QKeyEvent,
29
29
  QTransform,
30
+ QImage,
30
31
  )
31
32
  from PySide6.QtWidgets import (
32
33
  QApplication,
@@ -57,7 +58,6 @@ class CanvasLayer(BaseLayer):
57
58
  config (CanvasConfig): Configuration settings for the canvas layer.
58
59
  """
59
60
  super().__init__(parent, config)
60
- self.image = QPixmap()
61
61
  self.is_annotable = False
62
62
  self.last_pan_point = None
63
63
  self.state_thumbnail = dict()
@@ -178,6 +178,7 @@ class CanvasLayer(BaseLayer):
178
178
  opacity = layer.opacity / 255.0
179
179
  temp_painter.setOpacity(opacity) # Scale opacity to 0.0-1.0
180
180
  temp_painter.drawPixmap(0, 0, layer.image)
181
+
181
182
  temp_painter.end()
182
183
 
183
184
  # Draw the modified pixmap
@@ -287,20 +288,20 @@ class CanvasLayer(BaseLayer):
287
288
  painter.setPen(
288
289
  QPen(
289
290
  self.config.selected_draw_config.handle_color,
290
- self.config.selected_draw_config.handle_width,
291
+ self.config.selected_draw_config.handle_width / self.scale,
291
292
  )
292
293
  )
293
294
  painter.setBrush(self.config.selected_draw_config.handle_color)
294
295
  painter.drawEllipse(
295
296
  rotation_pos,
296
- self.config.selected_draw_config.handle_point_size * 2,
297
- self.config.selected_draw_config.handle_point_size * 2,
297
+ self.config.selected_draw_config.handle_point_size * 1.1 / self.scale,
298
+ self.config.selected_draw_config.handle_point_size * 1.1 / self.scale,
298
299
  )
299
300
  # now draw rotation symbol
300
301
  painter.setPen(
301
302
  QPen(
302
303
  self.config.selected_draw_config.handle_color,
303
- self.config.selected_draw_config.handle_width,
304
+ self.config.selected_draw_config.handle_width / self.scale,
304
305
  )
305
306
  )
306
307
  painter.drawLine(
@@ -323,21 +324,23 @@ class CanvasLayer(BaseLayer):
323
324
  # Draw scale handles
324
325
  handle_color = self.config.selected_draw_config.handle_color
325
326
  painter.setPen(
326
- QPen(handle_color, self.config.selected_draw_config.handle_width)
327
+ QPen(
328
+ handle_color, self.config.selected_draw_config.handle_width / self.scale
329
+ )
327
330
  )
328
331
  painter.setBrush(self.config.selected_draw_config.handle_color)
329
332
  for corner in corners:
330
333
  painter.drawEllipse(
331
334
  corner,
332
- self.config.selected_draw_config.handle_point_size,
333
- self.config.selected_draw_config.handle_point_size,
335
+ self.config.selected_draw_config.handle_point_size / self.scale,
336
+ self.config.selected_draw_config.handle_point_size / self.scale,
334
337
  )
335
338
  for edge in edges:
336
339
  # draw small circles on the edges
337
340
  painter.drawEllipse(
338
341
  edge,
339
- self.config.selected_draw_config.handle_edge_size,
340
- self.config.selected_draw_config.handle_edge_size,
342
+ self.config.selected_draw_config.handle_edge_size / self.scale,
343
+ self.config.selected_draw_config.handle_edge_size / self.scale,
341
344
  )
342
345
  # draw sides
343
346
  painter.drawLine(
@@ -874,6 +877,33 @@ class CanvasLayer(BaseLayer):
874
877
  """
875
878
  self.export_current_state(export_to_annotation_tab=True)
876
879
 
880
+ def seek_state(self, step):
881
+ """Seek to a specific state using the timeline slider."""
882
+ self.messageSignal.emit(f"Seeking to step {step}")
883
+ logger.info(f"Seeking to step {step}")
884
+
885
+ # Get the states for the selected step
886
+ if step in self.states:
887
+ states = self.states[step]
888
+ for state in states:
889
+ layer = self.get_layer(state.layer_id)
890
+ if layer:
891
+ # Update the layer's state
892
+ update_opacities = False
893
+ logger.debug(
894
+ f"Updating layer {layer.layer_name} with state: {state}"
895
+ )
896
+
897
+ if (
898
+ layer.edge_width != state.edge_width
899
+ or layer.edge_opacity != state.edge_opacity
900
+ ):
901
+ update_opacities = True
902
+ layer.layer_state = state
903
+ if update_opacities:
904
+ layer._apply_edge_opacity()
905
+ layer.update()
906
+
877
907
  def play_states(self):
878
908
  """Play all the states stored in self.states."""
879
909
  if len(self.states) == 0:
@@ -896,7 +926,19 @@ class CanvasLayer(BaseLayer):
896
926
  layer = self.get_layer(state.layer_id)
897
927
  if layer:
898
928
  # Update the layer's state
929
+ update_opacities = False
930
+ logger.debug(
931
+ f"Updating layer {layer.layer_name} with state: {state}"
932
+ )
933
+
934
+ if (
935
+ layer.edge_width != state.edge_width
936
+ or layer.edge_opacity != state.edge_opacity
937
+ ):
938
+ update_opacities = True
899
939
  layer.layer_state = state
940
+ if update_opacities:
941
+ layer._apply_edge_opacity()
900
942
  layer.update()
901
943
 
902
944
  # Update the UI to reflect the changes
@@ -23,9 +23,10 @@ from imagebaker import logger
23
23
  class AnnotationList(QDockWidget):
24
24
  messageSignal = Signal(str)
25
25
 
26
- def __init__(self, layer: AnnotableLayer, parent=None):
26
+ def __init__(self, layer: AnnotableLayer, parent=None, max_name_length=15):
27
27
  super().__init__("Annotations", parent)
28
28
  self.layer = layer
29
+ self.max_name_length = max_name_length
29
30
  self.init_ui()
30
31
 
31
32
  def init_ui(self):
@@ -100,7 +101,7 @@ class AnnotationList(QDockWidget):
100
101
  else ann.annotator
101
102
  )
102
103
  secondary_text.append(score_text)
103
- short_path = ann.file_path.stem
104
+ short_path = ann.file_path.stem[: self.max_name_length]
104
105
  secondary_text.append(f"<span style='color:#666;'>{short_path}</span>")
105
106
 
106
107
  if secondary_text:
@@ -25,6 +25,7 @@ class ImageListPanel(QDockWidget):
25
25
  image_entries: list["ImageEntry"],
26
26
  processed_images: set[Path],
27
27
  parent=None,
28
+ max_name_length=15,
28
29
  ):
29
30
  """
30
31
  :param image_entries: List of image paths to display.
@@ -35,6 +36,7 @@ class ImageListPanel(QDockWidget):
35
36
  self.processed_images = processed_images
36
37
  self.current_page = 0
37
38
  self.images_per_page = 10
39
+ self.max_name_length = max_name_length
38
40
  self.init_ui()
39
41
 
40
42
  def init_ui(self):
@@ -104,7 +106,7 @@ class ImageListPanel(QDockWidget):
104
106
  thumbnail_pixmap = QPixmap(str(image_entry.data)).scaled(
105
107
  50, 50, Qt.KeepAspectRatio, Qt.SmoothTransformation
106
108
  )
107
- name_label_text = Path(image_entry.data).name
109
+ name_label_text = Path(image_entry.data).name[: self.max_name_length]
108
110
 
109
111
  thumbnail_label.setPixmap(thumbnail_pixmap)
110
112
  item_layout.addWidget(thumbnail_label)
@@ -20,7 +20,14 @@ class LayerSettings(QDockWidget):
20
20
  layerState = Signal(LayerState)
21
21
  messageSignal = Signal(str)
22
22
 
23
- def __init__(self, parent=None, max_xpos=1000, max_ypos=1000, max_scale=100):
23
+ def __init__(
24
+ self,
25
+ parent=None,
26
+ max_xpos=1000,
27
+ max_ypos=1000,
28
+ max_scale=100,
29
+ max_edge_width=10,
30
+ ):
24
31
  super().__init__("BaseLayer Settings", parent)
25
32
  self.selected_layer: BaseLayer = None
26
33
 
@@ -29,6 +36,7 @@ class LayerSettings(QDockWidget):
29
36
  self.max_xpos = max_xpos
30
37
  self.max_ypos = max_ypos
31
38
  self.max_scale = max_scale
39
+ self.max_edge_width = max_edge_width
32
40
  self.init_ui()
33
41
  self.setFeatures(
34
42
  QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable
@@ -67,6 +75,12 @@ class LayerSettings(QDockWidget):
67
75
  self.main_layout.addWidget(self.scale_y_slider["widget"])
68
76
  self.rotation_slider = self.create_slider("Rotation:", 0, 360, 0, 1)
69
77
  self.main_layout.addWidget(self.rotation_slider["widget"])
78
+ self.edge_opacity_slider = self.create_slider("Edge Opacity:", 0, 255, 255, 1)
79
+ self.main_layout.addWidget(self.edge_opacity_slider["widget"])
80
+ self.edge_width_slider = self.create_slider(
81
+ "Edge Width:", 0, self.max_edge_width, 5, 1
82
+ )
83
+ self.main_layout.addWidget(self.edge_width_slider["widget"])
70
84
 
71
85
  # Add stretch to push content to the top
72
86
  self.main_layout.addStretch()
@@ -125,7 +139,6 @@ class LayerSettings(QDockWidget):
125
139
 
126
140
  try:
127
141
  self._disable_updates = True
128
-
129
142
  if sender == self.opacity_slider["slider"]:
130
143
  self.selected_layer.opacity = value
131
144
  elif sender == self.x_slider["slider"]:
@@ -138,6 +151,12 @@ class LayerSettings(QDockWidget):
138
151
  self.selected_layer.scale_y = value / 100.0
139
152
  elif sender == self.rotation_slider["slider"]:
140
153
  self.selected_layer.rotation = value
154
+ elif sender == self.edge_opacity_slider["slider"]:
155
+ self.selected_layer.edge_opacity = value
156
+ self.selected_layer._apply_edge_opacity()
157
+ elif sender == self.edge_width_slider["slider"]:
158
+ self.selected_layer.edge_width = value
159
+ self.selected_layer._apply_edge_opacity()
141
160
 
142
161
  self.selected_layer.update() # Trigger a repaint
143
162
 
@@ -154,6 +173,10 @@ class LayerSettings(QDockWidget):
154
173
  rotation=self.selected_layer.rotation,
155
174
  scale_x=self.selected_layer.scale_x,
156
175
  scale_y=self.selected_layer.scale_y,
176
+ opacity=self.selected_layer.opacity,
177
+ edge_opacity=self.selected_layer.edge_opacity,
178
+ edge_width=self.selected_layer.edge_width,
179
+ visible=self.selected_layer.visible,
157
180
  )
158
181
  logger.info(f"Storing state {bake_settings}")
159
182
  self.messageSignal.emit(f"Stored state for {bake_settings.layer_name}")
@@ -211,6 +234,12 @@ class LayerSettings(QDockWidget):
211
234
  self.rotation_slider["slider"].setValue(
212
235
  int(self.selected_layer.rotation)
213
236
  )
237
+ self.edge_opacity_slider["slider"].setValue(
238
+ int(self.selected_layer.edge_opacity)
239
+ )
240
+ self.edge_width_slider["slider"].setValue(
241
+ int(self.selected_layer.edge_width)
242
+ )
214
243
  else:
215
244
  self.widget.setEnabled(False)
216
245
  self.layer_name_label.setText("No BaseLayer")
@@ -39,7 +39,7 @@ class BaseModel(ABC):
39
39
  return image
40
40
 
41
41
  # @abstractmethod
42
- def postprocess(self, output) -> PredictionResult:
42
+ def postprocess(self, output) -> list[PredictionResult]:
43
43
  return output
44
44
 
45
45
  def predict(
@@ -66,6 +66,7 @@ class BakerTab(QWidget):
66
66
  max_xpos=self.config.max_xpos,
67
67
  max_ypos=self.config.max_ypos,
68
68
  max_scale=self.config.max_scale,
69
+ max_edge_width=self.config.max_edge_width,
69
70
  )
70
71
  self.layer_list = LayerList(
71
72
  canvas=self.current_canvas,
@@ -415,15 +416,7 @@ class BakerTab(QWidget):
415
416
  """Seek to a specific state using the timeline slider."""
416
417
  self.messageSignal.emit(f"Seeking to step {step}")
417
418
  logger.info(f"Seeking to step {step}")
418
-
419
- # Get the states for the selected step
420
- if step in self.current_canvas.states:
421
- states = self.current_canvas.states[step]
422
- for state in states:
423
- layer = self.current_canvas.get_layer(state.layer_id)
424
- if layer:
425
- layer.layer_state = state
426
- layer.update()
419
+ self.current_canvas.seek_state(step)
427
420
 
428
421
  # Update the canvas
429
422
  self.current_canvas.update()
@@ -114,7 +114,9 @@ class LayerifyTab(QWidget):
114
114
  def init_ui(self):
115
115
  """Initialize the UI components"""
116
116
  # Create annotation list and image list panel
117
- self.annotation_list = AnnotationList(None, parent=self.main_window)
117
+ self.annotation_list = AnnotationList(
118
+ None, parent=self.main_window, max_name_length=self.config.max_name_length
119
+ )
118
120
  self.image_list_panel = ImageListPanel(
119
121
  self.image_entries, self.processed_images
120
122
  )
@@ -197,18 +199,7 @@ class LayerifyTab(QWidget):
197
199
  if not self.image_entries:
198
200
  assets_folder = self.config.assets_folder
199
201
  if assets_folder.exists() and assets_folder.is_dir():
200
- for img_path in assets_folder.rglob("*.*"):
201
- if img_path.suffix.lower() in [
202
- ".jpg",
203
- ".jpeg",
204
- ".png",
205
- ".bmp",
206
- ".tiff",
207
- ]:
208
- # Add regular images as dictionaries with type and data
209
- self.image_entries.append(
210
- ImageEntry(is_baked_result=False, data=img_path)
211
- )
202
+ self._load_images_from_folder(assets_folder)
212
203
 
213
204
  # Load images into layers if any are found
214
205
  if self.image_entries:
@@ -472,6 +463,7 @@ class LayerifyTab(QWidget):
472
463
  def handle_model_error(self, error):
473
464
  logger.error(f"Model error: {error}")
474
465
  QMessageBox.critical(self, "Error", f"Model error: {error}")
466
+ self.loading_dialog.close()
475
467
 
476
468
  def save_annotations(self):
477
469
  """Save annotations to a JSON file."""
@@ -690,6 +682,27 @@ class LayerifyTab(QWidget):
690
682
  self.model_combo.currentIndexChanged.connect(self.handle_model_change)
691
683
  toolbar_layout.addWidget(self.model_combo)
692
684
 
685
+ def _load_images_from_folder(self, folder_path: Path):
686
+ """Load images from a folder and update the image list."""
687
+ self.image_entries = [] # Clear the existing image paths
688
+
689
+ if self.config.full_search:
690
+ image_paths = list(folder_path.rglob("*.*"))
691
+ else:
692
+ image_paths = list(folder_path.glob("*.*"))
693
+
694
+ for img_path in image_paths:
695
+ if img_path.suffix.lower() in [
696
+ ".jpg",
697
+ ".jpeg",
698
+ ".png",
699
+ ".bmp",
700
+ ".tiff",
701
+ ]:
702
+ self.image_entries.append(
703
+ ImageEntry(is_baked_result=False, data=img_path)
704
+ )
705
+
693
706
  def select_folder(self):
694
707
  """Allow the user to select a folder and load images from it."""
695
708
  folder_path = QFileDialog.getExistingDirectory(self, "Select Folder")
@@ -697,18 +710,7 @@ class LayerifyTab(QWidget):
697
710
  self.image_entries = [] # Clear the existing image paths
698
711
  folder_path = Path(folder_path)
699
712
 
700
- # Use rglob to get all image files in the folder and subfolders
701
- for img_path in folder_path.rglob("*.*"):
702
- if img_path.suffix.lower() in [
703
- ".jpg",
704
- ".jpeg",
705
- ".png",
706
- ".bmp",
707
- ".tiff",
708
- ]:
709
- self.image_entries.append(
710
- ImageEntry(is_baked_result=False, data=img_path)
711
- )
713
+ self._load_images_from_folder(folder_path)
712
714
 
713
715
  self.curr_image_idx = 0 # Reset the current image index
714
716
 
@@ -0,0 +1,3 @@
1
+ from .utils import * # noqa: F401, F403
2
+ from .transform_mask import * # noqa: F401, F403
3
+ from .vis import * # noqa: F401, F403
@@ -59,9 +59,13 @@ def calculate_intermediate_states(
59
59
  visible=current_state.visible,
60
60
  allow_annotation_export=current_state.allow_annotation_export,
61
61
  playing=current_state.playing,
62
- selected=current_state.selected,
62
+ selected=False,
63
63
  is_annotable=current_state.is_annotable,
64
64
  status=current_state.status,
65
+ edge_opacity=previous_state.edge_opacity
66
+ + (current_state.edge_opacity - previous_state.edge_opacity) * (i / steps),
67
+ edge_width=previous_state.edge_width
68
+ + (current_state.edge_width - previous_state.edge_width) * (i / steps),
65
69
  )
66
70
 
67
71
  # Deep copy the drawing_states from the previous_state
@@ -0,0 +1,26 @@
1
+ import numpy as np
2
+ import cv2
3
+
4
+
5
+ def generate_color_map(num_colors: int = 20):
6
+ """Generate a color map for the segmentation masks"""
7
+ np.random.seed(42) # For reproducible colors
8
+
9
+ colors = {}
10
+ for i in range(num_colors):
11
+ # Generate distinct colors with good visibility
12
+ # Using HSV color space for better distribution
13
+ hue = i / num_colors
14
+ saturation = 0.8 + np.random.random() * 0.2
15
+ value = 0.8 + np.random.random() * 0.2
16
+
17
+ # Convert HSV to BGR (OpenCV uses BGR)
18
+ hsv_color = np.array(
19
+ [[[hue * 180, saturation * 255, value * 255]]], dtype=np.uint8
20
+ )
21
+ bgr_color = cv2.cvtColor(hsv_color, cv2.COLOR_HSV2BGR)[0][0]
22
+
23
+ # Store as (B, G, R) tuple
24
+ colors[i] = (int(bgr_color[0]), int(bgr_color[1]), int(bgr_color[2]))
25
+
26
+ return colors
@@ -0,0 +1,174 @@
1
+ from imagebaker.core.defs import PredictionResult
2
+
3
+ import cv2
4
+ import numpy as np
5
+ from typing import List
6
+
7
+
8
+ def annotate_detection(
9
+ image: np.ndarray,
10
+ results: List[PredictionResult],
11
+ color_map: dict[str, tuple[int, int, int]],
12
+ box_thickness: int = 2,
13
+ font_face: int = cv2.FONT_HERSHEY_SIMPLEX,
14
+ text_scale: float = 0.5,
15
+ text_thickness: int = 1,
16
+ ) -> np.ndarray:
17
+ """
18
+ Draw bounding boxes and labels on the image
19
+
20
+ Args:
21
+ image: The original image as a numpy array
22
+ results: List of PredictionResult objects
23
+
24
+ Returns:
25
+ Annotated image as a numpy array
26
+ """
27
+ annotated_image = image.copy()
28
+
29
+ for result in results:
30
+ # Extract data from result
31
+ box = result.rectangle # [x1, y1, x2, y2]
32
+ score = result.score
33
+ class_name = result.class_name
34
+
35
+ if not box:
36
+ continue
37
+
38
+ # Get color for this class
39
+ color = color_map.get(
40
+ result.class_name, (0, 255, 0)
41
+ ) # Default to green if not found
42
+
43
+ # Draw bounding box
44
+ cv2.rectangle(
45
+ annotated_image,
46
+ (box[0], box[1]),
47
+ (box[2], box[3]),
48
+ color,
49
+ box_thickness,
50
+ )
51
+
52
+ # Prepare label text with class name and score
53
+ label_text = f"{class_name}: {score:.2f}"
54
+
55
+ # Calculate text size to create background rectangle
56
+ (text_width, text_height), baseline = cv2.getTextSize(
57
+ label_text,
58
+ font_face,
59
+ text_scale,
60
+ text_thickness,
61
+ )
62
+
63
+ # Draw text background
64
+ cv2.rectangle(
65
+ annotated_image,
66
+ (box[0], box[1] - text_height - 5),
67
+ (box[0] + text_width, box[1]),
68
+ color,
69
+ -1, # Fill the rectangle
70
+ )
71
+
72
+ # Draw text
73
+ cv2.putText(
74
+ annotated_image,
75
+ label_text,
76
+ (box[0], box[1] - 5),
77
+ font_face,
78
+ text_scale,
79
+ (255, 255, 255), # White text
80
+ text_thickness,
81
+ )
82
+
83
+ return annotated_image
84
+
85
+
86
+ def annotate_segmentation(
87
+ image: np.ndarray,
88
+ results: List[PredictionResult],
89
+ color_map: dict[int, tuple[int, int, int]],
90
+ contour_thickness: int = 2,
91
+ mask_opacity: float = 0.5,
92
+ font_face: int = cv2.FONT_HERSHEY_SIMPLEX,
93
+ text_scale: float = 0.5,
94
+ text_thickness: int = 1,
95
+ ) -> np.ndarray:
96
+ """
97
+ Draw segmentation masks and contours on the image
98
+ """
99
+ annotated_image = image.copy()
100
+ mask_overlay = np.zeros_like(image)
101
+
102
+ for i, result in enumerate(results):
103
+ if (result.polygon is not None) or not result.mask:
104
+ continue
105
+
106
+ # Get color for this mask
107
+ color_idx = i % len(color_map)
108
+ color = color_map[color_idx]
109
+
110
+ # Create mask from polygons
111
+ mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8)
112
+ for poly in result.polygon:
113
+ poly_np = np.array(poly, dtype=np.int32).reshape((-1, 1, 2))
114
+ cv2.fillPoly(mask, [poly_np], 1)
115
+
116
+ # Apply color to mask overlay
117
+ color_mask = np.zeros_like(image)
118
+ color_mask[mask == 1] = color
119
+ mask_overlay = cv2.addWeighted(mask_overlay, 1.0, color_mask, 1.0, 0)
120
+
121
+ # Draw contours
122
+ for poly in result.polygon:
123
+ poly_np = np.array(poly, dtype=np.int32).reshape((-1, 1, 2))
124
+ cv2.polylines(
125
+ annotated_image,
126
+ [poly_np],
127
+ True,
128
+ color,
129
+ contour_thickness,
130
+ )
131
+
132
+ # Add label text
133
+ label_position = (
134
+ result.polygon[0][0] if result.polygon and result.polygon[0] else [10, 10]
135
+ )
136
+ label_text = f"{result.class_id}: {result.score:.2f}"
137
+
138
+ # Draw text background
139
+ (text_width, text_height), baseline = cv2.getTextSize(
140
+ label_text,
141
+ font_face,
142
+ text_scale,
143
+ text_thickness,
144
+ )
145
+
146
+ cv2.rectangle(
147
+ annotated_image,
148
+ (label_position[0], label_position[1] - text_height - 5),
149
+ (label_position[0] + text_width, label_position[1]),
150
+ color,
151
+ -1, # Fill the rectangle
152
+ )
153
+
154
+ # Draw text
155
+ cv2.putText(
156
+ annotated_image,
157
+ label_text,
158
+ (label_position[0], label_position[1] - 5),
159
+ font_face,
160
+ text_scale,
161
+ (255, 255, 255), # White text
162
+ text_thickness,
163
+ )
164
+
165
+ # Blend mask overlay with original image
166
+ annotated_image = cv2.addWeighted(
167
+ annotated_image,
168
+ 1.0,
169
+ mask_overlay,
170
+ mask_opacity,
171
+ 0,
172
+ )
173
+
174
+ return annotated_image
@@ -1,6 +1,8 @@
1
1
  from imagebaker.core.configs import LayerConfig, CanvasConfig
2
2
  from imagebaker import logger
3
3
  from imagebaker.tabs import LayerifyTab, BakerTab
4
+ from imagebaker import __version__
5
+
4
6
 
5
7
  from PySide6.QtCore import Qt, QTimer
6
8
  from PySide6.QtWidgets import (
@@ -37,7 +39,7 @@ class MainWindow(QMainWindow):
37
39
  def init_ui(self):
38
40
  """Initialize the main window and set up tabs."""
39
41
  try:
40
- self.setWindowTitle("Image Baker")
42
+ self.setWindowTitle(f"Image Baker v{__version__}")
41
43
  self.setGeometry(100, 100, 1200, 800)
42
44
 
43
45
  self.status_bar = self.statusBar()
@@ -49,10 +49,23 @@ class BakerWorker(QObject):
49
49
  top_left = QPointF(sys.maxsize, sys.maxsize)
50
50
  bottom_right = QPointF(-sys.maxsize, -sys.maxsize)
51
51
 
52
+ # contains all states in currenct step
52
53
  for state in states:
53
54
  layer = self._get_layer(state.layer_id)
54
55
  if layer and layer.visible and not layer.image.isNull():
56
+ update_opacities = False
57
+ logger.debug(
58
+ f"Updating layer {layer.layer_name} with state: {state}"
59
+ )
60
+
61
+ if (
62
+ layer.edge_width != state.edge_width
63
+ or layer.edge_opacity != state.edge_opacity
64
+ ):
65
+ update_opacities = True
55
66
  layer.layer_state = state
67
+ if update_opacities:
68
+ layer._apply_edge_opacity()
56
69
  layer.update()
57
70
 
58
71
  transform = QTransform()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.48
3
+ Version: 0.0.50
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -35,7 +35,7 @@ Requires-Dist: mkdocs-awesome-pages-plugin; extra == "docs"
35
35
  ![code size in bytes](https://img.shields.io/github/languages/code-size/q-viper/image-baker)
36
36
  <!-- ![Tests](https://github.com/q-viper/SmokeSim/actions/workflows/test-on-push.yml/badge.svg) -->
37
37
  ![Code Formatting](https://github.com/q-viper/image-baker/actions/workflows/black-formatter.yml/badge.svg)
38
-
38
+ [![PyPI version](https://img.shields.io/pypi/v/imagebaker.svg)](https://pypi.org/imagebaker/)
39
39
 
40
40
  <p align="center">
41
41
  <img src="assets/demo.gif" alt="Centered Demo" />
@@ -0,0 +1,43 @@
1
+ imagebaker/__init__.py,sha256=zrrxwyzuqVNeIu3rVPrOGYf6SCd5kWsoGcdqqUfsYX4,258
2
+ imagebaker/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ imagebaker/core/configs/__init__.py,sha256=iyR_GOVMFw3XJSm7293YfyTnaLZa7pLQMfn5tGxVofI,31
4
+ imagebaker/core/configs/configs.py,sha256=-qy7vmYaaUk3bh49pwsBig8de_3Y2JWjTDeyWcGQods,5130
5
+ imagebaker/core/defs/__init__.py,sha256=NqV7gYIlRkaS7nx_UTNPSNZbdPrx4w-VurKOKyRLbKY,28
6
+ imagebaker/core/defs/defs.py,sha256=-ZItfJdWaK9yFSuFn2LmQ4ncqukAZ_hvYFgE44HUdIo,8394
7
+ imagebaker/core/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ imagebaker/core/plugins/base_plugin.py,sha256=ROa1HTwV5LgGL-40CHKk_5MZYI5QAT1MzpYO7Fx-9P0,1084
9
+ imagebaker/core/plugins/cosine_plugin.py,sha256=IXBfvaoxrvf-hytg_dT1zFmOfDbcWXBZ7NvIFPJ2tWQ,1251
10
+ imagebaker/layers/__init__.py,sha256=q1kUDHhUXEGBOdu6CHDfqCnE2mraLHRqh0DFHYTbnRY,158
11
+ imagebaker/layers/annotable_layer.py,sha256=ejBp6nooLtHs8c_G6lOsTJmSwBT404F_dmPsSkjvdEQ,37167
12
+ imagebaker/layers/base_layer.py,sha256=1K7Nt6OPITrILj-p4I6Jf0eaesCpdeecOXGj_8oAQb8,30650
13
+ imagebaker/layers/canvas_layer.py,sha256=7eDo0UHXRRZ5BX3BciPX88JsumzmwVemtS6cBjU9qwM,42115
14
+ imagebaker/list_views/__init__.py,sha256=Aa9slE6do8eYgZp77wrofpd_mlBDwxgF3adMyHYFanE,144
15
+ imagebaker/list_views/annotation_list.py,sha256=HGV6lGlkFjvJvvGnCcLuX1kkfXA0GL8wKo8jOXSBXec,7527
16
+ imagebaker/list_views/canvas_list.py,sha256=JYSYR0peGyJFJ6amL1894KsUHETPUkR3qAWdGL50Lbc,6717
17
+ imagebaker/list_views/image_list.py,sha256=NInkc893FGU7L6oSxy8KrWql-i6RB0BqvkroPAASjVw,4912
18
+ imagebaker/list_views/layer_list.py,sha256=fLx3Ry72fas1W5y_V84hSp41ARneogQN3qjfYTOcpxY,14476
19
+ imagebaker/list_views/layer_settings.py,sha256=0WVSCm_RSBKo4pCkYU5c2OYjb_sW8x0UUfFC4So26jQ,9752
20
+ imagebaker/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ imagebaker/models/base_model.py,sha256=4RyS4vShqWFHhdQDwYluwTPnRPEXjpZl9UjYY_w8NL0,4203
22
+ imagebaker/tabs/__init__.py,sha256=ijg7MA17RvcHA2AuZE4OgRJXWxjecaUAlfASKAoCQ6Q,86
23
+ imagebaker/tabs/baker_tab.py,sha256=yFQRiNmLIua5BvgW7Ysj5FrNbTLhGxqGRTRCABxeTtw,20493
24
+ imagebaker/tabs/layerify_tab.py,sha256=Qsf9w81-43sB0LZy_vIB4wCzUhHHhoIiarZJvKRxeZ0,32340
25
+ imagebaker/utils/__init__.py,sha256=I1z5VVEf6QPOMvVkgVHDauQ9ew7tcTVguV4Kdi3Lk4Y,130
26
+ imagebaker/utils/image.py,sha256=fq7g3DqSdjF9okxZ3fe5kF4Hxn32rqhvVqxy8yI5bnI,3067
27
+ imagebaker/utils/state_utils.py,sha256=Y2JVRGVfsoffwfA2lsCcqHwIxH_jOrEJAdH-oWfe2XE,3841
28
+ imagebaker/utils/transform_mask.py,sha256=k8MfTgM5-_U2TvDHQHRelz-leGFX6OcsllV6-J4BKfw,3651
29
+ imagebaker/utils/utils.py,sha256=MnJ4flxxwZbjROWJ5iKHnJxPSSMbfWRbF9GKfVcKutA,840
30
+ imagebaker/utils/vis.py,sha256=f7c44gm6g9ja5hgVeXKfOhHzxHdzXcIUwKiA1RZU_F8,4736
31
+ imagebaker/window/__init__.py,sha256=FIxtUR1qnbQMYzppQv7tEfv1-ueHhpu0Z7xuWZR794w,44
32
+ imagebaker/window/app.py,sha256=e6FGO_BnvkiQC9JN3AmqkgbF72zzZS0hc7PFc43QiVc,4725
33
+ imagebaker/window/main_window.py,sha256=gpJ7DDuPmxhHh_6Rv3YH2J_1AqG7-NM8R3tKNYhFT3E,7030
34
+ imagebaker/workers/__init__.py,sha256=XfXENwAYyNg9q_zR-gOsYJGjzwg_iIb_gING8ydnp9c,154
35
+ imagebaker/workers/baker_worker.py,sha256=EJTL4ln09NuntFpu0o-Hfk0vCtDxpKqJxJcmtgTnMwo,11297
36
+ imagebaker/workers/layerify_worker.py,sha256=EOqKvhdACtf3y5Ljy6M7MvddAjlZW5DNfBFMtNPD-us,3223
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,,
@@ -1,41 +0,0 @@
1
- imagebaker/__init__.py,sha256=6so9hrBqCwTtCvUvaNB-hBAtmag8RbMJz5irhPANmD4,128
2
- imagebaker/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- imagebaker/core/configs/__init__.py,sha256=iyR_GOVMFw3XJSm7293YfyTnaLZa7pLQMfn5tGxVofI,31
4
- imagebaker/core/configs/configs.py,sha256=5KRZfLShu4JqV459n5dX4AlbIlkySyAmJr8OzYG4X7Q,4850
5
- imagebaker/core/defs/__init__.py,sha256=NqV7gYIlRkaS7nx_UTNPSNZbdPrx4w-VurKOKyRLbKY,28
6
- imagebaker/core/defs/defs.py,sha256=nIg2ZQADbpcyC0ZOl54L14yLZ38-SUnfMkklCDfhN3E,8257
7
- imagebaker/core/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- imagebaker/core/plugins/base_plugin.py,sha256=ROa1HTwV5LgGL-40CHKk_5MZYI5QAT1MzpYO7Fx-9P0,1084
9
- imagebaker/core/plugins/cosine_plugin.py,sha256=IXBfvaoxrvf-hytg_dT1zFmOfDbcWXBZ7NvIFPJ2tWQ,1251
10
- imagebaker/layers/__init__.py,sha256=q1kUDHhUXEGBOdu6CHDfqCnE2mraLHRqh0DFHYTbnRY,158
11
- imagebaker/layers/annotable_layer.py,sha256=8Y35JvgSQMBTkvjG8VlD1xQgTsskyMwjU6ETU_xalAw,32819
12
- imagebaker/layers/base_layer.py,sha256=uncseSxTbLKnV84hAPbJLDbBMV0nu1gQ4N-SLHoGB6s,25486
13
- imagebaker/layers/canvas_layer.py,sha256=47R-g3NAzAQ720tWrIZUYIAupxImdORVYK5BFIRopqo,40414
14
- imagebaker/list_views/__init__.py,sha256=Aa9slE6do8eYgZp77wrofpd_mlBDwxgF3adMyHYFanE,144
15
- imagebaker/list_views/annotation_list.py,sha256=Wx2MbDGxcGeqss9TccFWVVYvlDo9hsefBMQBi4s72is,7436
16
- imagebaker/list_views/canvas_list.py,sha256=JYSYR0peGyJFJ6amL1894KsUHETPUkR3qAWdGL50Lbc,6717
17
- imagebaker/list_views/image_list.py,sha256=o4yGNXRffPJOZxd_c9JIK9MVzJkeWtXbZUF7pAVmfzw,4813
18
- imagebaker/list_views/layer_list.py,sha256=fLx3Ry72fas1W5y_V84hSp41ARneogQN3qjfYTOcpxY,14476
19
- imagebaker/list_views/layer_settings.py,sha256=38E39z-rEdl0YSe2C_k4wd5CgHPETQeJE5VvJLfFQ-k,8454
20
- imagebaker/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- imagebaker/models/base_model.py,sha256=ZfZ_VgP-jzfmEAdOycVtOYkr03m8EyXWdmK0-50MxIk,4197
22
- imagebaker/tabs/__init__.py,sha256=ijg7MA17RvcHA2AuZE4OgRJXWxjecaUAlfASKAoCQ6Q,86
23
- imagebaker/tabs/baker_tab.py,sha256=kIVoF4w_Ch1YNdHtHsNZKfr46tZWBDSj5xrrEkF1piM,20752
24
- imagebaker/tabs/layerify_tab.py,sha256=L8FqZf5JHHYc8BCRKwLWIRdgUSq2YvS7XiJ5Zd5kkTo,32427
25
- imagebaker/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- imagebaker/utils/image.py,sha256=fq7g3DqSdjF9okxZ3fe5kF4Hxn32rqhvVqxy8yI5bnI,3067
27
- imagebaker/utils/state_utils.py,sha256=sI0R3ht4W_kQGwOpzUdBjy6M4PaKaAQlj-_MM6pidfM,3584
28
- imagebaker/utils/transform_mask.py,sha256=k8MfTgM5-_U2TvDHQHRelz-leGFX6OcsllV6-J4BKfw,3651
29
- imagebaker/window/__init__.py,sha256=FIxtUR1qnbQMYzppQv7tEfv1-ueHhpu0Z7xuWZR794w,44
30
- imagebaker/window/app.py,sha256=e6FGO_BnvkiQC9JN3AmqkgbF72zzZS0hc7PFc43QiVc,4725
31
- imagebaker/window/main_window.py,sha256=GM5Pf7wpR8u99FarL7eqyc09Khwi6TWEgka6MvrjP6Y,6978
32
- imagebaker/workers/__init__.py,sha256=XfXENwAYyNg9q_zR-gOsYJGjzwg_iIb_gING8ydnp9c,154
33
- imagebaker/workers/baker_worker.py,sha256=JyV1Hu4mzbYhmogc7K3U24adklmT3x3V0ZNMxe7iT-w,10697
34
- imagebaker/workers/layerify_worker.py,sha256=EOqKvhdACtf3y5Ljy6M7MvddAjlZW5DNfBFMtNPD-us,3223
35
- imagebaker/workers/model_worker.py,sha256=Tlg6_D977iK-kuGCNdQY4OnGiP8QqWY7adpRNXZw4rA,1636
36
- imagebaker-0.0.48.dist-info/LICENSE,sha256=1vkysFPOnT7y4LsoFTv9YsopIrQvBc2l6vUOfv4KKLc,1082
37
- imagebaker-0.0.48.dist-info/METADATA,sha256=RpP88IOsQrk0ju2vBDfwcUpC9D6swAk-FDCC4srIg1U,6736
38
- imagebaker-0.0.48.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
39
- imagebaker-0.0.48.dist-info/entry_points.txt,sha256=IDjZHJCiiHpH5IUTByT2en0nMbnnnlrJZ5FPFehUvQM,61
40
- imagebaker-0.0.48.dist-info/top_level.txt,sha256=Gg-eILTlqJXwVQr0saSwsx3-H4SPdZ2agBZaufe194s,11
41
- imagebaker-0.0.48.dist-info/RECORD,,