imagebaker 0.0.41__py3-none-any.whl → 0.0.48__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 +1 -1
- imagebaker/core/__init__.py +0 -0
- imagebaker/core/configs/__init__.py +1 -0
- imagebaker/core/configs/configs.py +156 -0
- imagebaker/core/defs/__init__.py +1 -0
- imagebaker/core/defs/defs.py +258 -0
- imagebaker/core/plugins/__init__.py +0 -0
- imagebaker/core/plugins/base_plugin.py +39 -0
- imagebaker/core/plugins/cosine_plugin.py +39 -0
- imagebaker/layers/__init__.py +3 -0
- imagebaker/layers/annotable_layer.py +847 -0
- imagebaker/layers/base_layer.py +724 -0
- imagebaker/layers/canvas_layer.py +1007 -0
- imagebaker/list_views/__init__.py +3 -0
- imagebaker/list_views/annotation_list.py +203 -0
- imagebaker/list_views/canvas_list.py +185 -0
- imagebaker/list_views/image_list.py +138 -0
- imagebaker/list_views/layer_list.py +390 -0
- imagebaker/list_views/layer_settings.py +219 -0
- imagebaker/models/__init__.py +0 -0
- imagebaker/models/base_model.py +150 -0
- imagebaker/tabs/__init__.py +2 -0
- imagebaker/tabs/baker_tab.py +496 -0
- imagebaker/tabs/layerify_tab.py +837 -0
- imagebaker/utils/__init__.py +0 -0
- imagebaker/utils/image.py +105 -0
- imagebaker/utils/state_utils.py +92 -0
- imagebaker/utils/transform_mask.py +107 -0
- imagebaker/window/__init__.py +1 -0
- imagebaker/window/app.py +136 -0
- imagebaker/window/main_window.py +181 -0
- imagebaker/workers/__init__.py +3 -0
- imagebaker/workers/baker_worker.py +247 -0
- imagebaker/workers/layerify_worker.py +91 -0
- imagebaker/workers/model_worker.py +54 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/METADATA +6 -6
- imagebaker-0.0.48.dist-info/RECORD +41 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/WHEEL +1 -1
- imagebaker-0.0.41.dist-info/RECORD +0 -7
- {imagebaker-0.0.41.dist-info/licenses → imagebaker-0.0.48.dist-info}/LICENSE +0 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/entry_points.txt +0 -0
- {imagebaker-0.0.41.dist-info → imagebaker-0.0.48.dist-info}/top_level.txt +0 -0
imagebaker/__init__.py
CHANGED
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
|