imagebaker 0.0.1__tar.gz → 0.0.3__tar.gz

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.
Files changed (49) hide show
  1. {imagebaker-0.0.1 → imagebaker-0.0.3}/PKG-INFO +1 -1
  2. imagebaker-0.0.3/imagebaker/core/configs/__init__.py +1 -0
  3. imagebaker-0.0.3/imagebaker/core/configs/configs.py +149 -0
  4. imagebaker-0.0.3/imagebaker/core/defs/__init__.py +1 -0
  5. imagebaker-0.0.3/imagebaker/core/defs/defs.py +239 -0
  6. imagebaker-0.0.3/imagebaker/core/plugins/__init__.py +0 -0
  7. imagebaker-0.0.3/imagebaker/core/plugins/base_plugin.py +39 -0
  8. imagebaker-0.0.3/imagebaker/core/plugins/cosine_plugin.py +39 -0
  9. imagebaker-0.0.3/imagebaker/models/__init__.py +0 -0
  10. imagebaker-0.0.3/imagebaker/utils/__init__.py +0 -0
  11. imagebaker-0.0.3/imagebaker/utils/image.py +95 -0
  12. imagebaker-0.0.3/imagebaker/utils/state_utils.py +64 -0
  13. imagebaker-0.0.3/imagebaker/utils/transform_mask.py +112 -0
  14. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker.egg-info/PKG-INFO +1 -1
  15. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker.egg-info/SOURCES.txt +12 -0
  16. {imagebaker-0.0.1 → imagebaker-0.0.3}/setup.py +1 -1
  17. {imagebaker-0.0.1 → imagebaker-0.0.3}/LICENSE +0 -0
  18. {imagebaker-0.0.1 → imagebaker-0.0.3}/README.md +0 -0
  19. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/__init__.py +0 -0
  20. {imagebaker-0.0.1/imagebaker/models → imagebaker-0.0.3/imagebaker/core}/__init__.py +0 -0
  21. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/layers/__init__.py +0 -0
  22. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/layers/annotable_layer.py +0 -0
  23. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/layers/base_layer.py +0 -0
  24. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/layers/canvas_layer.py +0 -0
  25. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/list_views/__init__.py +0 -0
  26. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/list_views/annotation_list.py +0 -0
  27. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/list_views/canvas_list.py +0 -0
  28. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/list_views/image_list.py +0 -0
  29. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/list_views/layer_list.py +0 -0
  30. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/list_views/layer_settings.py +0 -0
  31. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/models/base_model.py +0 -0
  32. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/models/rtdetr_v2.py +0 -0
  33. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/models/sam_model.py +0 -0
  34. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/models/segmentation.py +0 -0
  35. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/tabs/__init__.py +0 -0
  36. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/tabs/baker_tab.py +0 -0
  37. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/tabs/layerify_tab.py +0 -0
  38. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/window/__init__.py +0 -0
  39. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/window/app.py +0 -0
  40. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/window/main_window.py +0 -0
  41. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/workers/__init__.py +0 -0
  42. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/workers/baker_worker.py +0 -0
  43. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/workers/layerfy_worker.py +0 -0
  44. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker/workers/model_worker.py +0 -0
  45. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker.egg-info/dependency_links.txt +0 -0
  46. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker.egg-info/entry_points.txt +0 -0
  47. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker.egg-info/requires.txt +0 -0
  48. {imagebaker-0.0.1 → imagebaker-0.0.3}/imagebaker.egg-info/top_level.txt +0 -0
  49. {imagebaker-0.0.1 → imagebaker-0.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -0,0 +1 @@
1
+ from .configs import * # noqa
@@ -0,0 +1,149 @@
1
+ from pathlib import Path
2
+ from typing import List, Tuple
3
+
4
+ from PySide6.QtCore import Qt
5
+ from PySide6.QtGui import QColor
6
+ from pydantic import BaseModel, Field
7
+
8
+ from imagebaker.core.defs import Label, ModelType
9
+
10
+
11
+ class DrawConfig(BaseModel):
12
+ color: QColor = Field(default_factory=lambda: QColor(255, 255, 255))
13
+ point_size: int = 5
14
+ line_width: int = 5
15
+ control_point_size: int = 1.5
16
+ ellipse_size: int = 8
17
+ pen_alpha: int = 150
18
+ brush_alpha: int = 50
19
+ brush_fill_pattern: Qt.BrushStyle = Qt.BrushStyle.DiagCrossPattern
20
+ thumbnail_size: Tuple[int, int] = Field(default_factory=lambda: (50, 50))
21
+ background_color: QColor = Field(default_factory=lambda: QColor(0, 0, 0, 255))
22
+ label_font_size: int = 12
23
+ label_font_background_color: QColor = Field(
24
+ default_factory=lambda: QColor(0, 0, 0, 150)
25
+ )
26
+ handle_color: QColor = Field(default_factory=lambda: QColor(0, 255, 255, 150))
27
+ handle_width: int = 2
28
+ handle_point_size: int = 6
29
+ handle_edge_size: int = 4
30
+
31
+ button_width: int = 30
32
+
33
+ class Config:
34
+ arbitrary_types_allowed = True
35
+
36
+
37
+ class BaseConfig(BaseModel):
38
+ project_name: str = "ImageBaker"
39
+ version: str = "0.1.0"
40
+ project_dir: Path = Path(".")
41
+
42
+ is_debug: bool = True
43
+ deque_maxlen: int = 10
44
+
45
+ # drawing configs #
46
+ # ON SELECTION
47
+ selected_draw_config: DrawConfig = DrawConfig(
48
+ color=QColor(255, 0, 0),
49
+ point_size=5,
50
+ line_width=5,
51
+ ellipse_size=8,
52
+ pen_alpha=150,
53
+ brush_alpha=50,
54
+ thumbnail_size=(50, 50),
55
+ brush_fill_pattern=Qt.BrushStyle.CrossPattern,
56
+ )
57
+ normal_draw_config: DrawConfig = DrawConfig()
58
+ zoom_in_factor: float = 1.1
59
+ zoom_out_factor: float = 0.9
60
+
61
+ @property
62
+ def assets_folder(self):
63
+ return self.project_dir / "assets"
64
+
65
+ class Config:
66
+ arbitrary_types_allowed = True
67
+
68
+
69
+ class LayerConfig(BaseConfig):
70
+ show_labels: bool = True
71
+ show_annotations: bool = True
72
+
73
+ default_label: Label = Field(
74
+ default_factory=lambda: Label("Unlabeled", QColor(255, 255, 255))
75
+ )
76
+ predefined_labels: List[Label] = Field(
77
+ default_factory=lambda: [
78
+ Label("Unlabeled", QColor(255, 255, 255)),
79
+ Label("Label 1", QColor(255, 0, 0)),
80
+ Label("Label 2", QColor(0, 255, 0)),
81
+ Label("Label 3", QColor(0, 0, 255)),
82
+ Label("Custom", QColor(128, 128, 128)),
83
+ ]
84
+ )
85
+
86
+ def get_label_color(self, label):
87
+ for lbl in self.predefined_labels:
88
+ if lbl.name == label:
89
+ return lbl.color
90
+ return self.default_label.color
91
+
92
+
93
+ class CanvasConfig(BaseConfig):
94
+ save_on_bake: bool = True
95
+ bake_timeout: float = -1.0
96
+ filename_format: str = "{project_name}_{timestamp}"
97
+ export_format: str = "png"
98
+ max_xpos: int = 1000
99
+ max_ypos: int = 1000
100
+ max_scale: int = 10
101
+ # whether to allow the use of sliders to change layer properties
102
+ allow_slider_usage: bool = True
103
+
104
+ write_annotations: bool = True
105
+ write_labels: bool = True
106
+ write_masks: bool = True
107
+ fps: int = 5
108
+
109
+ @property
110
+ def export_folder(self):
111
+ folder = self.project_dir / "assets" / "exports"
112
+ folder.mkdir(parents=True, exist_ok=True)
113
+ return folder
114
+
115
+
116
+ class CursorDef:
117
+ POINT_CURSOR: Qt.CursorShape = Qt.CrossCursor
118
+ POLYGON_CURSOR: Qt.CursorShape = Qt.CrossCursor
119
+ RECTANGLE_CURSOR: Qt.CursorShape = Qt.CrossCursor
120
+ IDLE_CURSOR: Qt.CursorShape = Qt.ArrowCursor
121
+ PAN_CURSOR: Qt.CursorShape = Qt.OpenHandCursor
122
+ ZOOM_IN_CURSOR: Qt.CursorShape = Qt.SizeFDiagCursor
123
+ ZOOM_OUT_CURSOR: Qt.CursorShape = Qt.SizeBDiagCursor
124
+ TRANSFORM_UPDOWN: Qt.CursorShape = Qt.SizeVerCursor
125
+ TRANSFORM_LEFTRIGHT: Qt.CursorShape = Qt.SizeHorCursor
126
+ TRANSFORM_ALL: Qt.CursorShape = Qt.SizeAllCursor
127
+ GRAB_CURSOR: Qt.CursorShape = Qt.OpenHandCursor
128
+ GRABBING_CURSOR: Qt.CursorShape = Qt.ClosedHandCursor
129
+
130
+
131
+ class DefaultModelConfig(BaseModel):
132
+ model_type: ModelType = ModelType.DETECTION
133
+ model_name: str = "Dummy Model"
134
+ model_description: str = "This is a dummy model"
135
+ model_version: str = "1.0"
136
+ model_author: str = "Anonymous"
137
+ model_license: str = "MIT"
138
+ input_size: Tuple[int, int] = (224, 224)
139
+ input_channels: int = 3
140
+ class_names: List[str] = ["class1", "class2", "class3"]
141
+ device: str = "cpu"
142
+ return_annotated_image: bool = False
143
+
144
+ @property
145
+ def num_classes(self):
146
+ return len(self.class_names)
147
+
148
+ class Config:
149
+ arbitrary_types_allowed = True
@@ -0,0 +1 @@
1
+ from .defs import * # noqa
@@ -0,0 +1,239 @@
1
+ from PySide6.QtCore import QPointF, QRectF
2
+ from PySide6.QtGui import QColor, QPolygonF
3
+ from PySide6.QtGui import QImage
4
+
5
+ from enum import Enum
6
+ from dataclasses import dataclass, field
7
+ import numpy as np
8
+ from datetime import datetime
9
+ from pydantic import BaseModel
10
+ from pathlib import Path
11
+
12
+
13
+ class MouseMode(Enum):
14
+ IDLE = 0
15
+ POINT = 1
16
+ POLYGON = 2
17
+ RECTANGLE = 3
18
+ PAN = 4
19
+ ZOOM_IN = 5
20
+ ZOOM_OUT = 6
21
+ RESIZE = 7
22
+ RESIZE_HEIGHT = 8
23
+ RESIZE_WIDTH = 9
24
+ GRAB = 11
25
+
26
+
27
+ class ModelType(str, Enum):
28
+ DETECTION = "detection"
29
+ SEGMENTATION = "segmentation"
30
+ CLASSIFICATION = "classification"
31
+ PROMPT = "prompt"
32
+
33
+
34
+ class PredictionResult(BaseModel):
35
+ class_name: str = None
36
+ class_id: int = None
37
+ score: float = None
38
+ rectangle: list[int] | None = None
39
+ mask: np.ndarray | None = None
40
+ keypoints: list[list[int, int]] | None = None
41
+ polygon: np.ndarray | None = None
42
+ prompt: str | None = None
43
+ annotated_image: np.ndarray | None = None
44
+ annotation_time: str | None = None
45
+
46
+ class Config:
47
+ arbitrary_types_allowed = True
48
+
49
+
50
+ @dataclass
51
+ class LayerState:
52
+ layer_id: str = ""
53
+ state_step: int = 0
54
+ layer_name: str = "Layer"
55
+ opacity: float = 255.00
56
+ position: QPointF = field(default_factory=lambda: QPointF(0, 0))
57
+ rotation: float = 0.00
58
+ scale: float = 1.00
59
+ scale_x: float = 1.00
60
+ scale_y: float = 1.00
61
+ transform_origin: QPointF = field(default_factory=lambda: QPointF(0.0, 0.0))
62
+ order: int = 0
63
+ visible: bool = True
64
+ allow_annotation_export: bool = True
65
+ playing: bool = False
66
+ selected: bool = False
67
+ is_annotable: bool = True
68
+ status: str = "Ready"
69
+
70
+ def copy(self):
71
+ return LayerState(
72
+ layer_id=self.layer_id,
73
+ layer_name=self.layer_name,
74
+ opacity=self.opacity,
75
+ position=QPointF(self.position.x(), self.position.y()),
76
+ rotation=self.rotation,
77
+ scale=self.scale,
78
+ scale_x=self.scale_x,
79
+ scale_y=self.scale_y,
80
+ transform_origin=QPointF(
81
+ self.transform_origin.x(), self.transform_origin.y()
82
+ ),
83
+ order=self.order,
84
+ visible=self.visible,
85
+ allow_annotation_export=self.allow_annotation_export,
86
+ playing=self.playing,
87
+ selected=self.selected,
88
+ is_annotable=self.is_annotable,
89
+ status=self.status,
90
+ )
91
+
92
+
93
+ @dataclass
94
+ class Label:
95
+ name: str = "Unlabeled"
96
+ color: QColor = field(default_factory=lambda: QColor(255, 255, 255))
97
+
98
+
99
+ @dataclass
100
+ class Annotation:
101
+ annotation_id: int
102
+ label: str
103
+ color: QColor = field(default_factory=lambda: QColor(255, 255, 255))
104
+ points: list[QPointF] = field(default_factory=list)
105
+ # [x, y, width, height]
106
+ rectangle: QRectF = None
107
+ polygon: QPolygonF = None
108
+ is_complete: bool = False
109
+ selected: bool = False
110
+ score: float = None
111
+ annotator: str = "User"
112
+ annotation_time: str = field(
113
+ default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S")
114
+ )
115
+ visible: bool = True
116
+ file_path: Path = field(default_factory=lambda: Path("Runtime"))
117
+ is_model_generated: bool = False
118
+ model_name: str = None
119
+
120
+ def copy(self):
121
+ ann = Annotation(
122
+ annotation_id=self.annotation_id,
123
+ label=self.label,
124
+ color=QColor(self.color.red(), self.color.green(), self.color.blue()),
125
+ points=[QPointF(p) for p in self.points],
126
+ rectangle=QRectF(self.rectangle) if self.rectangle else None,
127
+ polygon=QPolygonF(self.polygon) if self.polygon else None,
128
+ is_complete=self.is_complete,
129
+ selected=self.selected,
130
+ score=self.score,
131
+ annotator=self.annotator,
132
+ annotation_time=self.annotation_time,
133
+ visible=self.visible,
134
+ file_path=self.file_path,
135
+ is_model_generated=self.is_model_generated,
136
+ model_name=self.model_name,
137
+ )
138
+ ann.is_selected = False
139
+ return ann
140
+
141
+ @property
142
+ def name(self):
143
+ return f"{self.annotation_id} {self.label}"
144
+
145
+ @staticmethod
146
+ def save_as_json(annotations: list["Annotation"], path: str):
147
+ import json
148
+
149
+ annotations_dict = []
150
+ for annotation in annotations:
151
+ rectangle = None
152
+ if annotation.rectangle:
153
+ rectangle = [
154
+ annotation.rectangle.x(),
155
+ annotation.rectangle.y(),
156
+ annotation.rectangle.width(),
157
+ annotation.rectangle.height(),
158
+ ]
159
+ polygon = None
160
+ if annotation.polygon:
161
+ polygon = [[p.x(), p.y()] for p in annotation.polygon]
162
+ points = None
163
+ if annotation.points:
164
+ points = [[p.x(), p.y()] for p in annotation.points]
165
+ data = {
166
+ "annotation_id": annotation.annotation_id,
167
+ "label": annotation.label,
168
+ "color": annotation.color.getRgb(),
169
+ "points": points,
170
+ "rectangle": rectangle,
171
+ "polygon": polygon,
172
+ "is_complete": annotation.is_complete,
173
+ "selected": annotation.selected,
174
+ "score": annotation.score,
175
+ "annotator": annotation.annotator,
176
+ "annotation_time": annotation.annotation_time,
177
+ "visible": annotation.visible,
178
+ "file_path": str(annotation.file_path),
179
+ "is_model_generated": annotation.is_model_generated,
180
+ "model_name": annotation.model_name,
181
+ }
182
+ annotations_dict.append(data)
183
+
184
+ with open(path, "w") as f:
185
+ json.dump(annotations_dict, f, indent=4)
186
+
187
+ @staticmethod
188
+ def load_from_json(path: str):
189
+ import json
190
+
191
+ with open(path, "r") as f:
192
+ data = json.load(f)
193
+
194
+ annotations = []
195
+ for d in data:
196
+ annotation = Annotation(
197
+ annotation_id=d["annotation_id"],
198
+ label=d["label"],
199
+ color=QColor(*d["color"]),
200
+ is_complete=d.get("is_complete", False),
201
+ selected=d.get("selected", False),
202
+ score=d.get("score", None),
203
+ annotator=d.get("annotator", None),
204
+ annotation_time=d.get(
205
+ "annotation_time", datetime.now().strftime("%Y-%m-%d %H:%M:%S")
206
+ ),
207
+ visible=d.get("visible", True),
208
+ file_path=Path(d.get("file_path", "Runtime")),
209
+ is_model_generated=d.get("is_model_generated", False),
210
+ model_name=d.get("model_name", None),
211
+ )
212
+
213
+ # Handle points safely
214
+ points_data = d.get("points")
215
+ if points_data:
216
+ annotation.points = [QPointF(*p) for p in points_data]
217
+
218
+ # Handle rectangle safely
219
+ rect_data = d.get("rectangle")
220
+ if rect_data:
221
+ annotation.rectangle = QRectF(*rect_data)
222
+
223
+ # Handle polygon safely
224
+ polygon_data = d.get("polygon")
225
+ if polygon_data:
226
+ annotation.polygon = QPolygonF([QPointF(*p) for p in polygon_data])
227
+
228
+ annotations.append(annotation)
229
+
230
+ return annotations
231
+
232
+
233
+ @dataclass
234
+ class BakingResult:
235
+ filename: Path
236
+ image: QImage = field(default=None)
237
+ masks: list[np.ndarray] = field(default_factory=list)
238
+ mask_names: list[str] = field(default_factory=list)
239
+ annotations: list[Annotation] = field(default_factory=list)
File without changes
@@ -0,0 +1,39 @@
1
+ from abc import ABC, abstractmethod
2
+ from imagebaker.core.defs import LayerState
3
+ from imagebaker import logger
4
+
5
+
6
+ class BasePlugin(ABC):
7
+
8
+ def __init__(self, layer_state: LayerState, final_step: int = -1):
9
+ self.initial_layer_state = layer_state
10
+ self.final_step = final_step
11
+ self.current_step = 0
12
+
13
+ def __str__(self):
14
+ return self.__class__.__name
15
+
16
+ @abstractmethod
17
+ def compute_step(self, step: int):
18
+ """
19
+ Compute the step based on the given step.
20
+
21
+ :param step: The step to compute the step by.
22
+ """
23
+ pass
24
+
25
+ def update(self, step: int = 1):
26
+ """
27
+ Returns the updated state after passed step.
28
+
29
+ :param step: The step to update the state by.
30
+ """
31
+ if (
32
+ self.final_step != -1
33
+ and step > self.final_step
34
+ or self.current_step >= self.final_step
35
+ ):
36
+ logger.info(f"Final step reached for {self}. Returning last step.")
37
+ step = self.final_step
38
+ self.compute_step(step)
39
+ self.current_step += step
@@ -0,0 +1,39 @@
1
+ from imagebaker.core.plugins.base_plugin import BasePlugin
2
+ from imagebaker.core.defs import LayerState
3
+
4
+ import numpy as np
5
+ from PySide6.QtCore import QPointF
6
+
7
+
8
+ class CosinePlugin(BasePlugin):
9
+ """
10
+ Cosine Plugin implementation.
11
+ """
12
+
13
+ def __init__(self, layer_state: LayerState, amplitude=50, frequency=0.1):
14
+ """
15
+ Initialize the CosinePlugin.
16
+
17
+ :param layer_state: The LayerState to modify.
18
+ :param amplitude: The amplitude of the cosine wave (how far it moves).
19
+ :param frequency: The frequency of the cosine wave (how fast it oscillates).
20
+ """
21
+ super().__init__(layer_state)
22
+ self.amplitude = amplitude
23
+ self.frequency = frequency
24
+
25
+ def compute_step(self, step):
26
+ """
27
+ Update the x and y positions of the LayerState based on a cosine curve.
28
+
29
+ :param step: The step to compute the cosine value for.
30
+ """
31
+ # Compute the new x position based on the cosine curve
32
+ layer_state = LayerState()
33
+ layer_state.position = QPointF(
34
+ self.initial_layer_state.position.x()
35
+ + self.amplitude * np.cos(self.frequency * step),
36
+ self.initial_layer_state.position.y(),
37
+ )
38
+
39
+ return layer_state
File without changes
File without changes
@@ -0,0 +1,95 @@
1
+ import numpy as np
2
+ from PySide6.QtGui import QPixmap, QImage
3
+ import cv2
4
+
5
+ from imagebaker.core.defs.defs import Annotation
6
+
7
+
8
+ def qpixmap_to_numpy(pixmap: QPixmap | QImage) -> np.ndarray:
9
+ """
10
+ Convert QPixmap to RGBA numpy array.
11
+
12
+ Args:
13
+ pixmap: The QPixmap to convert
14
+
15
+ Returns:
16
+ numpy.ndarray: Array with shape (height, width, 4) containing RGBA values
17
+ """
18
+
19
+ if isinstance(pixmap, QPixmap):
20
+ # Convert QPixmap to QImage first
21
+ image = pixmap.toImage()
22
+ else:
23
+ image = pixmap
24
+ # Convert to Format_RGBA8888 for consistent channel ordering
25
+ if image.format() != QImage.Format_RGBA8888:
26
+ image = image.convertToFormat(QImage.Format_RGBA8888)
27
+
28
+ width = image.width()
29
+ height = image.height()
30
+
31
+ # Get the bytes directly from the QImage
32
+ ptr = image.constBits()
33
+
34
+ # Convert memoryview to bytes and then to numpy array
35
+ bytes_data = bytes(ptr)
36
+ arr = np.frombuffer(bytes_data, dtype=np.uint8).reshape((height, width, 4))
37
+
38
+ return arr
39
+
40
+
41
+ def draw_annotations(image: np.ndarray, annotations: list[Annotation]):
42
+ for i, ann in enumerate(annotations):
43
+ if ann.rectangle:
44
+ cv2.rectangle(
45
+ image,
46
+ (int(ann.rectangle.x()), int(ann.rectangle.y())),
47
+ (
48
+ int(ann.rectangle.x() + ann.rectangle.width()),
49
+ int(ann.rectangle.y() + ann.rectangle.height()),
50
+ ),
51
+ (0, 255, 0),
52
+ 2,
53
+ )
54
+ rect_center = ann.rectangle.center()
55
+
56
+ cv2.putText(
57
+ image,
58
+ ann.label,
59
+ (int(rect_center.x()), int(rect_center.y())),
60
+ cv2.FONT_HERSHEY_SIMPLEX,
61
+ 1,
62
+ (0, 255, 0),
63
+ 2,
64
+ )
65
+ elif ann.polygon:
66
+ cv2.polylines(
67
+ image,
68
+ [np.array([[int(p.x()), int(p.y())] for p in ann.polygon])],
69
+ True,
70
+ (0, 255, 0),
71
+ 2,
72
+ )
73
+ polygon_center = ann.polygon.boundingRect().center()
74
+ cv2.putText(
75
+ image,
76
+ ann.label,
77
+ (int(polygon_center.x()), int(polygon_center.y())),
78
+ cv2.FONT_HERSHEY_SIMPLEX,
79
+ 1,
80
+ (0, 255, 0),
81
+ 2,
82
+ )
83
+ elif ann.points:
84
+ for p in ann.points:
85
+ cv2.circle(image, (int(p.x()), int(p.y())), 5, (0, 255, 0), -1)
86
+ cv2.putText(
87
+ image,
88
+ ann.label,
89
+ (int(ann.points[0].x()), int(ann.points[0].y())),
90
+ cv2.FONT_HERSHEY_SIMPLEX,
91
+ 1,
92
+ (0, 255, 0),
93
+ 2,
94
+ )
95
+ return image
@@ -0,0 +1,64 @@
1
+ from imagebaker.core.defs import LayerState
2
+ from PySide6.QtCore import QPointF
3
+
4
+
5
+ def calculate_intermediate_states(previous_state, current_state, steps: int):
6
+ """
7
+ Calculate intermediate states between previous_state and current_state for a layer.
8
+ Append the current_state to the list of states after calculating intermediates.
9
+ """
10
+ if not previous_state or not current_state:
11
+ return [current_state] # If no previous state, return only the current state
12
+
13
+ intermediate_states = []
14
+ for i in range(steps):
15
+ # Interpolate attributes between previous_state and current_state
16
+ interpolated_state = LayerState(
17
+ layer_id=current_state.layer_id,
18
+ layer_name=current_state.layer_name,
19
+ opacity=previous_state.opacity
20
+ + (current_state.opacity - previous_state.opacity) * (i / steps),
21
+ position=QPointF(
22
+ previous_state.position.x()
23
+ + (current_state.position.x() - previous_state.position.x())
24
+ * (i / steps),
25
+ previous_state.position.y()
26
+ + (current_state.position.y() - previous_state.position.y())
27
+ * (i / steps),
28
+ ),
29
+ rotation=previous_state.rotation
30
+ + (current_state.rotation - previous_state.rotation) * (i / steps),
31
+ scale=previous_state.scale
32
+ + (current_state.scale - previous_state.scale) * (i / steps),
33
+ scale_x=previous_state.scale_x
34
+ + (current_state.scale_x - previous_state.scale_x) * (i / steps),
35
+ scale_y=previous_state.scale_y
36
+ + (current_state.scale_y - previous_state.scale_y) * (i / steps),
37
+ transform_origin=QPointF(
38
+ previous_state.transform_origin.x()
39
+ + (
40
+ current_state.transform_origin.x()
41
+ - previous_state.transform_origin.x()
42
+ )
43
+ * (i / steps),
44
+ previous_state.transform_origin.y()
45
+ + (
46
+ current_state.transform_origin.y()
47
+ - previous_state.transform_origin.y()
48
+ )
49
+ * (i / steps),
50
+ ),
51
+ order=current_state.order,
52
+ visible=current_state.visible,
53
+ allow_annotation_export=current_state.allow_annotation_export,
54
+ playing=current_state.playing,
55
+ selected=current_state.selected,
56
+ is_annotable=current_state.is_annotable,
57
+ status=current_state.status,
58
+ )
59
+ intermediate_states.append(interpolated_state)
60
+
61
+ # Append the current state as the final state
62
+ intermediate_states.append(current_state)
63
+
64
+ return intermediate_states
@@ -0,0 +1,112 @@
1
+ import cv2
2
+ import numpy as np
3
+ from typing import List, Tuple
4
+
5
+
6
+ def mask_to_polygons(
7
+ mask: np.ndarray,
8
+ min_polygon_area: float = 10,
9
+ merge_polygons: bool = False,
10
+ merge_distance: int = 5, # Max distance between polygons to merge
11
+ ) -> List[List[Tuple[int, int]]]:
12
+ """
13
+ Convert a binary mask to a list of polygons.
14
+ Each polygon is a list of (x, y) coordinates.
15
+
16
+ Args:
17
+ mask (np.ndarray): Binary mask (0 or 255).
18
+ min_polygon_area (float): Minimum area for a polygon to be included.
19
+ merge_polygons (bool): If True, merges nearby/overlapping polygons.
20
+ merge_distance (int): Max distance between polygons to merge (if merge_polygons=True).
21
+
22
+ Returns:
23
+ List[List[Tuple[int, int]]]: List of polygons, each represented as a list of (x, y) points.
24
+ """
25
+ contours, _ = cv2.findContours(
26
+ mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
27
+ )
28
+
29
+ polygons = []
30
+ for contour in contours:
31
+ area = cv2.contourArea(contour)
32
+ if area >= min_polygon_area:
33
+ polygons.append(contour)
34
+
35
+ # Sort polygons by area (descending)
36
+ polygons = sorted(
37
+ polygons, key=lambda p: cv2.contourArea(np.array(p)), reverse=True
38
+ )
39
+
40
+ # Merge polygons if requested
41
+ if merge_polygons and len(polygons) > 1:
42
+ # Use morphological dilation to merge nearby regions
43
+ kernel = np.ones((merge_distance, merge_distance), np.uint8)
44
+ merged_mask = cv2.dilate(mask, kernel, iterations=1)
45
+
46
+ # Re-extract contours after merging
47
+ merged_contours, _ = cv2.findContours(
48
+ merged_mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
49
+ )
50
+
51
+ # Filter again by area
52
+ merged_polygons = []
53
+ for contour in merged_contours:
54
+ area = cv2.contourArea(contour)
55
+ if area >= min_polygon_area:
56
+ merged_polygons.append(contour)
57
+
58
+ polygons = merged_polygons
59
+
60
+ # Convert contours to list of points
61
+ result = []
62
+ for poly in polygons:
63
+ points = poly.squeeze().tolist() # Remove extra dimensions
64
+ if len(points) >= 3: # Ensure it's a valid polygon
65
+ result.append([(int(x), int(y)) for x, y in points])
66
+
67
+ return result
68
+
69
+
70
+ import cv2
71
+ import numpy as np
72
+ from typing import List, Tuple
73
+
74
+
75
+ def mask_to_rectangles(
76
+ mask: np.ndarray,
77
+ merge_rectangles: bool = False,
78
+ merge_threshold: int = 1,
79
+ merge_epsilon: float = 0.5,
80
+ ) -> List[Tuple[int, int, int, int]]:
81
+ """
82
+ Convert a binary mask to a list of rectangles.
83
+ Each rectangle is a tuple of (x, y, w, h).
84
+
85
+ Args:
86
+ mask (np.ndarray): Binary mask (0 or 255).
87
+ merge_rectangles (bool): If True, merges overlapping or nearby rectangles.
88
+ merge_threshold (int): Min number of rectangles to merge into one.
89
+ merge_epsilon (float): Controls how close rectangles must be to merge (0.0 to 1.0).
90
+
91
+ Returns:
92
+ List[Tuple[int, int, int, int]]: List of rectangles, each as (x, y, w, h).
93
+ """
94
+ contours, _ = cv2.findContours(
95
+ mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
96
+ )
97
+
98
+ rectangles = []
99
+ for contour in contours:
100
+ x, y, w, h = cv2.boundingRect(contour)
101
+ rectangles.append((x, y, w, h))
102
+
103
+ if merge_rectangles and len(rectangles) > 1:
104
+ # Convert rectangles to the format expected by groupRectangles
105
+ rects = np.array(rectangles)
106
+ # groupRectangles requires [x, y, w, h] format
107
+ grouped_rects, _ = cv2.groupRectangles(
108
+ rects.tolist(), merge_threshold, merge_epsilon
109
+ )
110
+ rectangles = [tuple(map(int, rect)) for rect in grouped_rects]
111
+
112
+ return rectangles
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: imagebaker
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: A package for baking images.
5
5
  Home-page: https://github.com/q-viper/Image-Baker
6
6
  Author: Ramkrishna Acharya
@@ -8,6 +8,14 @@ imagebaker.egg-info/dependency_links.txt
8
8
  imagebaker.egg-info/entry_points.txt
9
9
  imagebaker.egg-info/requires.txt
10
10
  imagebaker.egg-info/top_level.txt
11
+ imagebaker/core/__init__.py
12
+ imagebaker/core/configs/__init__.py
13
+ imagebaker/core/configs/configs.py
14
+ imagebaker/core/defs/__init__.py
15
+ imagebaker/core/defs/defs.py
16
+ imagebaker/core/plugins/__init__.py
17
+ imagebaker/core/plugins/base_plugin.py
18
+ imagebaker/core/plugins/cosine_plugin.py
11
19
  imagebaker/layers/__init__.py
12
20
  imagebaker/layers/annotable_layer.py
13
21
  imagebaker/layers/base_layer.py
@@ -26,6 +34,10 @@ imagebaker/models/segmentation.py
26
34
  imagebaker/tabs/__init__.py
27
35
  imagebaker/tabs/baker_tab.py
28
36
  imagebaker/tabs/layerify_tab.py
37
+ imagebaker/utils/__init__.py
38
+ imagebaker/utils/image.py
39
+ imagebaker/utils/state_utils.py
40
+ imagebaker/utils/transform_mask.py
29
41
  imagebaker/window/__init__.py
30
42
  imagebaker/window/app.py
31
43
  imagebaker/window/main_window.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="imagebaker",
5
- version="0.0.1",
5
+ version="0.0.3",
6
6
  packages=find_packages(), # Automatically finds all packages
7
7
  install_requires=[
8
8
  "numpy",
File without changes
File without changes
File without changes