imagebaker 0.0.4__py3-none-any.whl → 0.0.46__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.
Files changed (41) hide show
  1. imagebaker/core/__init__.py +0 -0
  2. imagebaker/core/configs/__init__.py +1 -0
  3. imagebaker/core/configs/configs.py +156 -0
  4. imagebaker/core/defs/__init__.py +1 -0
  5. imagebaker/core/defs/defs.py +258 -0
  6. imagebaker/core/plugins/__init__.py +0 -0
  7. imagebaker/core/plugins/base_plugin.py +39 -0
  8. imagebaker/core/plugins/cosine_plugin.py +39 -0
  9. imagebaker/layers/__init__.py +3 -0
  10. imagebaker/layers/annotable_layer.py +847 -0
  11. imagebaker/layers/base_layer.py +724 -0
  12. imagebaker/layers/canvas_layer.py +1007 -0
  13. imagebaker/list_views/__init__.py +3 -0
  14. imagebaker/list_views/annotation_list.py +203 -0
  15. imagebaker/list_views/canvas_list.py +185 -0
  16. imagebaker/list_views/image_list.py +138 -0
  17. imagebaker/list_views/layer_list.py +390 -0
  18. imagebaker/list_views/layer_settings.py +219 -0
  19. imagebaker/models/__init__.py +0 -0
  20. imagebaker/models/base_model.py +150 -0
  21. imagebaker/tabs/__init__.py +2 -0
  22. imagebaker/tabs/baker_tab.py +496 -0
  23. imagebaker/tabs/layerify_tab.py +837 -0
  24. imagebaker/utils/__init__.py +0 -0
  25. imagebaker/utils/image.py +105 -0
  26. imagebaker/utils/state_utils.py +92 -0
  27. imagebaker/utils/transform_mask.py +107 -0
  28. imagebaker/window/__init__.py +1 -0
  29. imagebaker/window/app.py +136 -0
  30. imagebaker/window/main_window.py +181 -0
  31. imagebaker/workers/__init__.py +3 -0
  32. imagebaker/workers/baker_worker.py +247 -0
  33. imagebaker/workers/layerify_worker.py +91 -0
  34. imagebaker/workers/model_worker.py +54 -0
  35. {imagebaker-0.0.4.dist-info → imagebaker-0.0.46.dist-info}/METADATA +63 -27
  36. imagebaker-0.0.46.dist-info/RECORD +41 -0
  37. {imagebaker-0.0.4.dist-info → imagebaker-0.0.46.dist-info}/WHEEL +1 -1
  38. imagebaker-0.0.4.dist-info/RECORD +0 -7
  39. {imagebaker-0.0.4.dist-info/licenses → imagebaker-0.0.46.dist-info}/LICENSE +0 -0
  40. {imagebaker-0.0.4.dist-info → imagebaker-0.0.46.dist-info}/entry_points.txt +0 -0
  41. {imagebaker-0.0.4.dist-info → imagebaker-0.0.46.dist-info}/top_level.txt +0 -0
File without changes
@@ -0,0 +1 @@
1
+ from .configs import * # noqa
@@ -0,0 +1,156 @@
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
+ from imagebaker import logger
10
+
11
+
12
+ class DrawConfig(BaseModel):
13
+ color: QColor = Field(default_factory=lambda: QColor(255, 255, 255))
14
+ point_size: int = 5
15
+ line_width: int = 5
16
+ control_point_size: int = 1.5
17
+ ellipse_size: int = 8
18
+ pen_alpha: int = 150
19
+ brush_alpha: int = 50
20
+ brush_fill_pattern: Qt.BrushStyle = Qt.BrushStyle.DiagCrossPattern
21
+ thumbnail_size: Tuple[int, int] = Field(default_factory=lambda: (50, 50))
22
+ background_color: QColor = Field(default_factory=lambda: QColor(0, 0, 0, 255))
23
+ label_font_size: int = 12
24
+ label_font_background_color: QColor = Field(
25
+ default_factory=lambda: QColor(0, 0, 0, 150)
26
+ )
27
+ handle_color: QColor = Field(default_factory=lambda: QColor(0, 255, 255, 150))
28
+ handle_width: int = 5
29
+ handle_point_size: int = 8
30
+ handle_edge_size: int = 5
31
+
32
+ button_width: int = 30
33
+
34
+ class Config:
35
+ arbitrary_types_allowed = True
36
+
37
+
38
+ class BaseConfig(BaseModel):
39
+ project_name: str = "ImageBaker"
40
+ version: str = "0.1.0"
41
+ project_dir: Path = Path(".")
42
+
43
+ is_debug: bool = True
44
+ deque_maxlen: int = 10
45
+
46
+ # drawing configs #
47
+ # ON SELECTION
48
+ selected_draw_config: DrawConfig = DrawConfig(
49
+ color=QColor(255, 0, 0),
50
+ point_size=5,
51
+ line_width=5,
52
+ ellipse_size=8,
53
+ pen_alpha=150,
54
+ brush_alpha=50,
55
+ thumbnail_size=(50, 50),
56
+ brush_fill_pattern=Qt.BrushStyle.CrossPattern,
57
+ )
58
+ normal_draw_config: DrawConfig = DrawConfig()
59
+ zoom_in_factor: float = 1.1
60
+ zoom_out_factor: float = 0.9
61
+
62
+ @property
63
+ def assets_folder(self):
64
+ asset_dir = self.project_dir / "assets"
65
+ if not asset_dir.exists():
66
+ asset_dir.mkdir(parents=True)
67
+ logger.info(f"Created assets folder at {asset_dir}")
68
+ return asset_dir
69
+
70
+ class Config:
71
+ arbitrary_types_allowed = True
72
+
73
+
74
+ class LayerConfig(BaseConfig):
75
+ show_labels: bool = True
76
+ show_annotations: bool = True
77
+
78
+ default_label: Label = Field(
79
+ default_factory=lambda: Label("Unlabeled", QColor(255, 255, 255))
80
+ )
81
+ predefined_labels: List[Label] = Field(
82
+ default_factory=lambda: [
83
+ Label("Unlabeled", QColor(255, 255, 255)),
84
+ Label("Label 1", QColor(255, 0, 0)),
85
+ Label("Label 2", QColor(0, 255, 0)),
86
+ Label("Label 3", QColor(0, 0, 255)),
87
+ Label("Custom", QColor(128, 128, 128)),
88
+ ]
89
+ )
90
+
91
+ def get_label_color(self, label):
92
+ for lbl in self.predefined_labels:
93
+ if lbl.name == label:
94
+ return lbl.color
95
+ return self.default_label.color
96
+
97
+
98
+ class CanvasConfig(BaseConfig):
99
+ save_on_bake: bool = True
100
+ bake_timeout: float = -1.0
101
+ filename_format: str = "{project_name}_{timestamp}"
102
+ export_format: str = "png"
103
+ max_xpos: int = 1000
104
+ max_ypos: int = 1000
105
+ max_scale: int = 1000
106
+ # whether to allow the use of sliders to change layer properties
107
+ allow_slider_usage: bool = True
108
+
109
+ write_annotations: bool = True
110
+ write_labels: bool = True
111
+ write_masks: bool = True
112
+ fps: int = 5
113
+
114
+ @property
115
+ def export_folder(self):
116
+ folder = self.project_dir / "assets" / "exports"
117
+ folder.mkdir(parents=True, exist_ok=True)
118
+ return folder
119
+
120
+
121
+ class CursorDef:
122
+ POINT_CURSOR: Qt.CursorShape = Qt.CrossCursor
123
+ POLYGON_CURSOR: Qt.CursorShape = Qt.CrossCursor
124
+ RECTANGLE_CURSOR: Qt.CursorShape = Qt.CrossCursor
125
+ IDLE_CURSOR: Qt.CursorShape = Qt.ArrowCursor
126
+ PAN_CURSOR: Qt.CursorShape = Qt.OpenHandCursor
127
+ ZOOM_IN_CURSOR: Qt.CursorShape = Qt.SizeFDiagCursor
128
+ ZOOM_OUT_CURSOR: Qt.CursorShape = Qt.SizeBDiagCursor
129
+ TRANSFORM_UPDOWN: Qt.CursorShape = Qt.SizeVerCursor
130
+ TRANSFORM_LEFTRIGHT: Qt.CursorShape = Qt.SizeHorCursor
131
+ TRANSFORM_ALL: Qt.CursorShape = Qt.SizeAllCursor
132
+ GRAB_CURSOR: Qt.CursorShape = Qt.OpenHandCursor
133
+ GRABBING_CURSOR: Qt.CursorShape = Qt.ClosedHandCursor
134
+ DRAW_CURSOR: Qt.CursorShape = Qt.CrossCursor
135
+ ERASE_CURSOR: Qt.CursorShape = Qt.CrossCursor
136
+
137
+
138
+ class DefaultModelConfig(BaseModel):
139
+ model_type: ModelType = ModelType.DETECTION
140
+ model_name: str = "Dummy Model"
141
+ model_description: str = "This is a dummy model"
142
+ model_version: str = "1.0"
143
+ model_author: str = "Anonymous"
144
+ model_license: str = "MIT"
145
+ input_size: Tuple[int, int] = (224, 224)
146
+ input_channels: int = 3
147
+ class_names: List[str] = ["class1", "class2", "class3"]
148
+ device: str = "cpu"
149
+ return_annotated_image: bool = False
150
+
151
+ @property
152
+ def num_classes(self):
153
+ return len(self.class_names)
154
+
155
+ class Config:
156
+ arbitrary_types_allowed = True
@@ -0,0 +1 @@
1
+ from .defs import * # noqa
@@ -0,0 +1,258 @@
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
+ DRAW = 12
26
+ ERASE = 13
27
+
28
+
29
+ class ModelType(str, Enum):
30
+ DETECTION = "detection"
31
+ SEGMENTATION = "segmentation"
32
+ CLASSIFICATION = "classification"
33
+ PROMPT = "prompt"
34
+
35
+
36
+ @dataclass
37
+ class DrawingState:
38
+ position: QPointF = field(default_factory=lambda: QPointF(0, 0))
39
+ color: QColor = field(default_factory=lambda: QColor(255, 255, 255))
40
+ size: int = 5
41
+
42
+
43
+ class PredictionResult(BaseModel):
44
+ class_name: str = None
45
+ class_id: int = None
46
+ score: float = None
47
+ rectangle: list[int] | None = None
48
+ mask: np.ndarray | None = None
49
+ keypoints: list[list[int, int]] | None = None
50
+ polygon: np.ndarray | None = None
51
+ prompt: str | None = None
52
+ annotated_image: np.ndarray | None = None
53
+ annotation_time: str | None = None
54
+
55
+ class Config:
56
+ arbitrary_types_allowed = True
57
+
58
+
59
+ @dataclass
60
+ class LayerState:
61
+ layer_id: str = ""
62
+ state_step: int = 0
63
+ layer_name: str = "Layer"
64
+ opacity: float = 255.00
65
+ position: QPointF = field(default_factory=lambda: QPointF(0, 0))
66
+ rotation: float = 0.00
67
+ scale: float = 1.00
68
+ scale_x: float = 1.00
69
+ scale_y: float = 1.00
70
+ transform_origin: QPointF = field(default_factory=lambda: QPointF(0.0, 0.0))
71
+ order: int = 0
72
+ visible: bool = True
73
+ allow_annotation_export: bool = True
74
+ playing: bool = False
75
+ selected: bool = False
76
+ is_annotable: bool = True
77
+ status: str = "Ready"
78
+ drawing_states: list[DrawingState] = field(default_factory=list)
79
+
80
+ def copy(self):
81
+ return LayerState(
82
+ layer_id=self.layer_id,
83
+ layer_name=self.layer_name,
84
+ opacity=self.opacity,
85
+ position=QPointF(self.position.x(), self.position.y()),
86
+ rotation=self.rotation,
87
+ scale=self.scale,
88
+ scale_x=self.scale_x,
89
+ scale_y=self.scale_y,
90
+ transform_origin=QPointF(
91
+ self.transform_origin.x(), self.transform_origin.y()
92
+ ),
93
+ order=self.order,
94
+ visible=self.visible,
95
+ allow_annotation_export=self.allow_annotation_export,
96
+ playing=self.playing,
97
+ selected=self.selected,
98
+ is_annotable=self.is_annotable,
99
+ status=self.status,
100
+ drawing_states=[
101
+ DrawingState(
102
+ QPointF(d.position.x(), d.position.y()),
103
+ QColor(d.color.red(), d.color.green(), d.color.blue()),
104
+ d.size,
105
+ )
106
+ for d in self.drawing_states
107
+ ],
108
+ )
109
+
110
+
111
+ @dataclass
112
+ class Label:
113
+ name: str = "Unlabeled"
114
+ color: QColor = field(default_factory=lambda: QColor(255, 255, 255))
115
+
116
+
117
+ @dataclass
118
+ class Annotation:
119
+ annotation_id: int
120
+ label: str
121
+ color: QColor = field(default_factory=lambda: QColor(255, 255, 255))
122
+ points: list[QPointF] = field(default_factory=list)
123
+ # [x, y, width, height]
124
+ rectangle: QRectF = None
125
+ polygon: QPolygonF = None
126
+ is_complete: bool = False
127
+ selected: bool = False
128
+ score: float = None
129
+ annotator: str = "User"
130
+ annotation_time: str = field(
131
+ default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S")
132
+ )
133
+ visible: bool = True
134
+ file_path: Path = field(default_factory=lambda: Path("Runtime"))
135
+ is_model_generated: bool = False
136
+ model_name: str = None
137
+
138
+ def copy(self):
139
+ ann = Annotation(
140
+ annotation_id=self.annotation_id,
141
+ label=self.label,
142
+ color=QColor(self.color.red(), self.color.green(), self.color.blue()),
143
+ points=[QPointF(p) for p in self.points],
144
+ rectangle=QRectF(self.rectangle) if self.rectangle else None,
145
+ polygon=QPolygonF(self.polygon) if self.polygon else None,
146
+ is_complete=self.is_complete,
147
+ selected=self.selected,
148
+ score=self.score,
149
+ annotator=self.annotator,
150
+ annotation_time=self.annotation_time,
151
+ visible=self.visible,
152
+ file_path=self.file_path,
153
+ is_model_generated=self.is_model_generated,
154
+ model_name=self.model_name,
155
+ )
156
+ ann.is_selected = False
157
+ return ann
158
+
159
+ @property
160
+ def name(self):
161
+ return f"{self.annotation_id} {self.label}"
162
+
163
+ @staticmethod
164
+ def save_as_json(annotations: list["Annotation"], path: str):
165
+ import json
166
+
167
+ annotations_dict = []
168
+ for annotation in annotations:
169
+ rectangle = None
170
+ if annotation.rectangle:
171
+ rectangle = [
172
+ annotation.rectangle.x(),
173
+ annotation.rectangle.y(),
174
+ annotation.rectangle.width(),
175
+ annotation.rectangle.height(),
176
+ ]
177
+ polygon = None
178
+ if annotation.polygon:
179
+ polygon = [[p.x(), p.y()] for p in annotation.polygon]
180
+ points = None
181
+ if annotation.points:
182
+ points = [[p.x(), p.y()] for p in annotation.points]
183
+ data = {
184
+ "annotation_id": annotation.annotation_id,
185
+ "label": annotation.label,
186
+ "color": annotation.color.getRgb(),
187
+ "points": points,
188
+ "rectangle": rectangle,
189
+ "polygon": polygon,
190
+ "is_complete": annotation.is_complete,
191
+ "selected": annotation.selected,
192
+ "score": annotation.score,
193
+ "annotator": annotation.annotator,
194
+ "annotation_time": annotation.annotation_time,
195
+ "visible": annotation.visible,
196
+ "file_path": str(annotation.file_path),
197
+ "is_model_generated": annotation.is_model_generated,
198
+ "model_name": annotation.model_name,
199
+ }
200
+ annotations_dict.append(data)
201
+
202
+ with open(path, "w") as f:
203
+ json.dump(annotations_dict, f, indent=4)
204
+
205
+ @staticmethod
206
+ def load_from_json(path: str):
207
+ import json
208
+
209
+ with open(path, "r") as f:
210
+ data = json.load(f)
211
+
212
+ annotations = []
213
+ for d in data:
214
+ annotation = Annotation(
215
+ annotation_id=d["annotation_id"],
216
+ label=d["label"],
217
+ color=QColor(*d["color"]),
218
+ is_complete=d.get("is_complete", False),
219
+ selected=d.get("selected", False),
220
+ score=d.get("score", None),
221
+ annotator=d.get("annotator", None),
222
+ annotation_time=d.get(
223
+ "annotation_time", datetime.now().strftime("%Y-%m-%d %H:%M:%S")
224
+ ),
225
+ visible=d.get("visible", True),
226
+ file_path=Path(d.get("file_path", "Runtime")),
227
+ is_model_generated=d.get("is_model_generated", False),
228
+ model_name=d.get("model_name", None),
229
+ )
230
+
231
+ # Handle points safely
232
+ points_data = d.get("points")
233
+ if points_data:
234
+ annotation.points = [QPointF(*p) for p in points_data]
235
+
236
+ # Handle rectangle safely
237
+ rect_data = d.get("rectangle")
238
+ if rect_data:
239
+ annotation.rectangle = QRectF(*rect_data)
240
+
241
+ # Handle polygon safely
242
+ polygon_data = d.get("polygon")
243
+ if polygon_data:
244
+ annotation.polygon = QPolygonF([QPointF(*p) for p in polygon_data])
245
+
246
+ annotations.append(annotation)
247
+
248
+ return annotations
249
+
250
+
251
+ @dataclass
252
+ class BakingResult:
253
+ filename: Path
254
+ step: int = 0
255
+ image: QImage = field(default=None)
256
+ masks: list[np.ndarray] = field(default_factory=list)
257
+ mask_names: list[str] = field(default_factory=list)
258
+ 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
@@ -0,0 +1,3 @@
1
+ from .base_layer import BaseLayer # noqa: F401
2
+ from .annotable_layer import AnnotableLayer # noqa: F401
3
+ from .canvas_layer import CanvasLayer # noqa: F401