imagebaker 0.0.46__py3-none-any.whl → 0.0.49__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
@@ -2,4 +2,4 @@ from loguru import logger # noqa
2
2
 
3
3
  logger.info("imagebaker package loaded with loguru logger.")
4
4
 
5
- __version__ = "0.0.4" # noqa
5
+ # __version__ = "0.0.49" # noqa
@@ -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:
@@ -472,10 +472,13 @@ class AnnotableLayer(BaseLayer):
472
472
  self.selected_annotation.polygon[self.active_point_index] = (
473
473
  clamped_pos
474
474
  )
475
+ elif self.selected_annotation.points:
476
+ self.selected_annotation.points[0] = clamped_pos
475
477
  self.annotationMoved.emit()
476
478
  self.annotationUpdated.emit(self.selected_annotation)
477
479
  self.update()
478
480
  return
481
+
479
482
  if self.mouse_mode == MouseMode.PAN and event.buttons() & Qt.LeftButton:
480
483
  if self.pan_start:
481
484
  delta = event.position() - self.pan_start
@@ -534,6 +537,7 @@ class AnnotableLayer(BaseLayer):
534
537
  self.mouse_mode = MouseMode.IDLE
535
538
  for ann in self.annotations:
536
539
  ann.selected = False
540
+ self.annotationUpdated.emit(ann)
537
541
  self.update()
538
542
 
539
543
  # If left-clicked
@@ -559,7 +563,9 @@ class AnnotableLayer(BaseLayer):
559
563
  elif self.selected_annotation.polygon:
560
564
  self.initial_polygon = QPolygonF(self.selected_annotation.polygon)
561
565
  if "point_" in self.active_handle:
562
- self.active_point = int(self.active_handle.split("_")[1])
566
+ self.active_point_index = int(self.active_handle.split("_")[1])
567
+ elif self.selected_annotation.points:
568
+ self.active_point_index = 0
563
569
 
564
570
  # If pan mode
565
571
  if self.mouse_mode == MouseMode.PAN:
@@ -646,6 +652,11 @@ class AnnotableLayer(BaseLayer):
646
652
  if annotation.polygon.containsPoint(pos, Qt.OddEvenFill):
647
653
  return annotation, "move"
648
654
 
655
+ # Check points
656
+ elif annotation.points:
657
+ if (annotation.points[0] - pos).manhattanLength() < margin:
658
+ return annotation, "point_0"
659
+
649
660
  return None, None
650
661
 
651
662
  def handle_mouse_double_click(self, event: QMouseEvent, pos: QPoint):
@@ -656,6 +667,73 @@ class AnnotableLayer(BaseLayer):
656
667
  break
657
668
  # if left double click
658
669
  if event.button() == Qt.LeftButton:
670
+ img_pos = self.widget_to_image_pos(event.position())
671
+
672
+ self.selected_annotation, self.active_handle = (
673
+ self.find_annotation_and_handle_at(img_pos)
674
+ )
675
+
676
+ if self.selected_annotation:
677
+ if self.selected_annotation.polygon and self.active_handle:
678
+ if "point_" in self.active_handle:
679
+ index = int(self.active_handle.split("_")[1])
680
+ # Remove the point at the clicked index
681
+ polygon = self.selected_annotation.polygon
682
+ polygon = QPolygonF(
683
+ [p for i, p in enumerate(polygon) if i != index]
684
+ )
685
+
686
+ self.selected_annotation.polygon = polygon
687
+ self.annotationUpdated.emit(self.selected_annotation)
688
+ self.update()
689
+ logger.info(f"Removed point at index {index}")
690
+ return
691
+
692
+ # Check if an edge was double-clicked
693
+ polygon = self.selected_annotation.polygon
694
+ if polygon:
695
+ for i in range(len(polygon)):
696
+ start_point = polygon[i]
697
+ end_point = polygon[
698
+ (i + 1) % len(polygon)
699
+ ] # Wrap around to the first point
700
+
701
+ # Calculate the vector along the edge and the vector from the start point to the clicked position
702
+ line_vector = end_point - start_point
703
+ point_vector = img_pos - start_point
704
+
705
+ # Calculate the length of the edge
706
+ line_length_squared = (
707
+ line_vector.x() ** 2 + line_vector.y() ** 2
708
+ )
709
+ if line_length_squared == 0:
710
+ continue # Avoid division by zero for degenerate edges
711
+
712
+ # Project the point onto the line (normalized)
713
+ projection = (
714
+ point_vector.x() * line_vector.x()
715
+ + point_vector.y() * line_vector.y()
716
+ ) / line_length_squared
717
+
718
+ # Clamp the projection to the range [0, 1] to ensure it lies on the segment
719
+ projection = max(0, min(1, projection))
720
+
721
+ # Calculate the projection point on the edge
722
+ projection_point = start_point + projection * line_vector
723
+
724
+ # Calculate the perpendicular distance from the clicked position to the edge
725
+ perpendicular_distance = (
726
+ img_pos - projection_point
727
+ ).manhattanLength()
728
+
729
+ # Check if the perpendicular distance is within the margin
730
+ if perpendicular_distance < 10: # Margin of 10
731
+ # Insert a new point at the projection point
732
+ polygon.insert(i + 1, projection_point)
733
+ self.annotationUpdated.emit(self.selected_annotation)
734
+ self.update()
735
+ return
736
+
659
737
  # if drawing a polygon, close the polygon
660
738
  if (
661
739
  self.current_annotation
@@ -670,20 +748,20 @@ class AnnotableLayer(BaseLayer):
670
748
  return
671
749
 
672
750
  # 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
751
+ # annotation = self.find_annotation_at(self.widget_to_image_pos(pos))
752
+ # if annotation:
753
+ # # toggle selection
754
+ # annotation.selected = not annotation.selected
755
+
756
+ # # make all other annotations unselected
757
+ # for ann in self.annotations:
758
+ # if ann != annotation:
759
+ # ann.selected = False
760
+ # else:
761
+ # # we clicked on the background
762
+ # # make all annotations unselected
763
+ # for ann in self.annotations:
764
+ # ann.selected = False
687
765
  # update the view
688
766
  for ann in self.annotations:
689
767
  self.annotationUpdated.emit(ann)
@@ -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)
@@ -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:
@@ -690,6 +681,27 @@ class LayerifyTab(QWidget):
690
681
  self.model_combo.currentIndexChanged.connect(self.handle_model_change)
691
682
  toolbar_layout.addWidget(self.model_combo)
692
683
 
684
+ def _load_images_from_folder(self, folder_path: Path):
685
+ """Load images from a folder and update the image list."""
686
+ self.image_entries = [] # Clear the existing image paths
687
+
688
+ if self.config.full_search:
689
+ image_paths = list(folder_path.rglob("*.*"))
690
+ else:
691
+ image_paths = list(folder_path.glob("*.*"))
692
+
693
+ for img_path in image_paths:
694
+ if img_path.suffix.lower() in [
695
+ ".jpg",
696
+ ".jpeg",
697
+ ".png",
698
+ ".bmp",
699
+ ".tiff",
700
+ ]:
701
+ self.image_entries.append(
702
+ ImageEntry(is_baked_result=False, data=img_path)
703
+ )
704
+
693
705
  def select_folder(self):
694
706
  """Allow the user to select a folder and load images from it."""
695
707
  folder_path = QFileDialog.getExistingDirectory(self, "Select Folder")
@@ -697,18 +709,7 @@ class LayerifyTab(QWidget):
697
709
  self.image_entries = [] # Clear the existing image paths
698
710
  folder_path = Path(folder_path)
699
711
 
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
- )
712
+ self._load_images_from_folder(folder_path)
712
713
 
713
714
  self.curr_image_idx = 0 # Reset the current image index
714
715
 
@@ -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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.46
3
+ Version: 0.0.49
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -16,7 +16,7 @@ Requires-Dist: numpy>=1.21
16
16
  Requires-Dist: matplotlib>=3.4
17
17
  Requires-Dist: opencv-python>=4.5
18
18
  Requires-Dist: black>=23.1
19
- Requires-Dist: pydantic>=2.0
19
+ Requires-Dist: pydantic>=2.11.1
20
20
  Requires-Dist: flake8>=6.0
21
21
  Requires-Dist: typer>=0.9
22
22
  Requires-Dist: PySide6==6.8.3
@@ -1,41 +1,41 @@
1
- imagebaker/__init__.py,sha256=UeXmuH87AJb0MVzWgT4HXNtl99XxS7HGenLyI9K93go,127
1
+ imagebaker/__init__.py,sha256=ep22Ki-KljDvYzGvAfrDe9UU77GWgfoAsLd7t0S273c,130
2
2
  imagebaker/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  imagebaker/core/configs/__init__.py,sha256=iyR_GOVMFw3XJSm7293YfyTnaLZa7pLQMfn5tGxVofI,31
4
- imagebaker/core/configs/configs.py,sha256=5KRZfLShu4JqV459n5dX4AlbIlkySyAmJr8OzYG4X7Q,4850
4
+ imagebaker/core/configs/configs.py,sha256=uyUhoHA9-Ult-vVtnLX-JNmeUght4mUUV80yKWzUO_o,5100
5
5
  imagebaker/core/defs/__init__.py,sha256=NqV7gYIlRkaS7nx_UTNPSNZbdPrx4w-VurKOKyRLbKY,28
6
6
  imagebaker/core/defs/defs.py,sha256=nIg2ZQADbpcyC0ZOl54L14yLZ38-SUnfMkklCDfhN3E,8257
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=8Y35JvgSQMBTkvjG8VlD1xQgTsskyMwjU6ETU_xalAw,32819
11
+ imagebaker/layers/annotable_layer.py,sha256=ZjDqNMEqB78iEAThotfksv8-xIK6NCr52fI26Rv6CXA,36627
12
12
  imagebaker/layers/base_layer.py,sha256=uncseSxTbLKnV84hAPbJLDbBMV0nu1gQ4N-SLHoGB6s,25486
13
13
  imagebaker/layers/canvas_layer.py,sha256=47R-g3NAzAQ720tWrIZUYIAupxImdORVYK5BFIRopqo,40414
14
14
  imagebaker/list_views/__init__.py,sha256=Aa9slE6do8eYgZp77wrofpd_mlBDwxgF3adMyHYFanE,144
15
- imagebaker/list_views/annotation_list.py,sha256=Wx2MbDGxcGeqss9TccFWVVYvlDo9hsefBMQBi4s72is,7436
15
+ imagebaker/list_views/annotation_list.py,sha256=HGV6lGlkFjvJvvGnCcLuX1kkfXA0GL8wKo8jOXSBXec,7527
16
16
  imagebaker/list_views/canvas_list.py,sha256=JYSYR0peGyJFJ6amL1894KsUHETPUkR3qAWdGL50Lbc,6717
17
- imagebaker/list_views/image_list.py,sha256=o4yGNXRffPJOZxd_c9JIK9MVzJkeWtXbZUF7pAVmfzw,4813
17
+ imagebaker/list_views/image_list.py,sha256=NInkc893FGU7L6oSxy8KrWql-i6RB0BqvkroPAASjVw,4912
18
18
  imagebaker/list_views/layer_list.py,sha256=fLx3Ry72fas1W5y_V84hSp41ARneogQN3qjfYTOcpxY,14476
19
19
  imagebaker/list_views/layer_settings.py,sha256=38E39z-rEdl0YSe2C_k4wd5CgHPETQeJE5VvJLfFQ-k,8454
20
20
  imagebaker/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  imagebaker/models/base_model.py,sha256=ZfZ_VgP-jzfmEAdOycVtOYkr03m8EyXWdmK0-50MxIk,4197
22
22
  imagebaker/tabs/__init__.py,sha256=ijg7MA17RvcHA2AuZE4OgRJXWxjecaUAlfASKAoCQ6Q,86
23
23
  imagebaker/tabs/baker_tab.py,sha256=kIVoF4w_Ch1YNdHtHsNZKfr46tZWBDSj5xrrEkF1piM,20752
24
- imagebaker/tabs/layerify_tab.py,sha256=L8FqZf5JHHYc8BCRKwLWIRdgUSq2YvS7XiJ5Zd5kkTo,32427
24
+ imagebaker/tabs/layerify_tab.py,sha256=2IEdRyYHhNdY2696YiLcnWWKYN6gYwcKH6jsffYiL5M,32304
25
25
  imagebaker/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  imagebaker/utils/image.py,sha256=fq7g3DqSdjF9okxZ3fe5kF4Hxn32rqhvVqxy8yI5bnI,3067
27
27
  imagebaker/utils/state_utils.py,sha256=sI0R3ht4W_kQGwOpzUdBjy6M4PaKaAQlj-_MM6pidfM,3584
28
28
  imagebaker/utils/transform_mask.py,sha256=k8MfTgM5-_U2TvDHQHRelz-leGFX6OcsllV6-J4BKfw,3651
29
29
  imagebaker/window/__init__.py,sha256=FIxtUR1qnbQMYzppQv7tEfv1-ueHhpu0Z7xuWZR794w,44
30
30
  imagebaker/window/app.py,sha256=e6FGO_BnvkiQC9JN3AmqkgbF72zzZS0hc7PFc43QiVc,4725
31
- imagebaker/window/main_window.py,sha256=GM5Pf7wpR8u99FarL7eqyc09Khwi6TWEgka6MvrjP6Y,6978
31
+ imagebaker/window/main_window.py,sha256=gpJ7DDuPmxhHh_6Rv3YH2J_1AqG7-NM8R3tKNYhFT3E,7030
32
32
  imagebaker/workers/__init__.py,sha256=XfXENwAYyNg9q_zR-gOsYJGjzwg_iIb_gING8ydnp9c,154
33
33
  imagebaker/workers/baker_worker.py,sha256=JyV1Hu4mzbYhmogc7K3U24adklmT3x3V0ZNMxe7iT-w,10697
34
34
  imagebaker/workers/layerify_worker.py,sha256=EOqKvhdACtf3y5Ljy6M7MvddAjlZW5DNfBFMtNPD-us,3223
35
35
  imagebaker/workers/model_worker.py,sha256=Tlg6_D977iK-kuGCNdQY4OnGiP8QqWY7adpRNXZw4rA,1636
36
- imagebaker-0.0.46.dist-info/LICENSE,sha256=1vkysFPOnT7y4LsoFTv9YsopIrQvBc2l6vUOfv4KKLc,1082
37
- imagebaker-0.0.46.dist-info/METADATA,sha256=eQ78w8cbBe1zk2PCQlStYW5BzoMj1hO3yxlHnm7xVfA,6733
38
- imagebaker-0.0.46.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
39
- imagebaker-0.0.46.dist-info/entry_points.txt,sha256=IDjZHJCiiHpH5IUTByT2en0nMbnnnlrJZ5FPFehUvQM,61
40
- imagebaker-0.0.46.dist-info/top_level.txt,sha256=Gg-eILTlqJXwVQr0saSwsx3-H4SPdZ2agBZaufe194s,11
41
- imagebaker-0.0.46.dist-info/RECORD,,
36
+ imagebaker-0.0.49.dist-info/LICENSE,sha256=1vkysFPOnT7y4LsoFTv9YsopIrQvBc2l6vUOfv4KKLc,1082
37
+ imagebaker-0.0.49.dist-info/METADATA,sha256=BldoEWej9PUXnJVw_rpJEYFnjXosJIXWKJB2oY9ZLyI,6736
38
+ imagebaker-0.0.49.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
39
+ imagebaker-0.0.49.dist-info/entry_points.txt,sha256=IDjZHJCiiHpH5IUTByT2en0nMbnnnlrJZ5FPFehUvQM,61
40
+ imagebaker-0.0.49.dist-info/top_level.txt,sha256=Gg-eILTlqJXwVQr0saSwsx3-H4SPdZ2agBZaufe194s,11
41
+ imagebaker-0.0.49.dist-info/RECORD,,