imagebaker 0.0.2__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.
- {imagebaker-0.0.2 → imagebaker-0.0.3}/PKG-INFO +1 -1
- imagebaker-0.0.3/imagebaker/utils/__init__.py +0 -0
- imagebaker-0.0.3/imagebaker/utils/image.py +95 -0
- imagebaker-0.0.3/imagebaker/utils/state_utils.py +64 -0
- imagebaker-0.0.3/imagebaker/utils/transform_mask.py +112 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker.egg-info/PKG-INFO +1 -1
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker.egg-info/SOURCES.txt +4 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/setup.py +1 -1
- {imagebaker-0.0.2 → imagebaker-0.0.3}/LICENSE +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/README.md +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/configs/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/configs/configs.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/defs/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/defs/defs.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/plugins/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/plugins/base_plugin.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/core/plugins/cosine_plugin.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/layers/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/layers/annotable_layer.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/layers/base_layer.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/layers/canvas_layer.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/list_views/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/list_views/annotation_list.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/list_views/canvas_list.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/list_views/image_list.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/list_views/layer_list.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/list_views/layer_settings.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/models/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/models/base_model.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/models/rtdetr_v2.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/models/sam_model.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/models/segmentation.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/tabs/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/tabs/baker_tab.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/tabs/layerify_tab.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/window/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/window/app.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/window/main_window.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/workers/__init__.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/workers/baker_worker.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/workers/layerfy_worker.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker/workers/model_worker.py +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker.egg-info/dependency_links.txt +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker.egg-info/entry_points.txt +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker.egg-info/requires.txt +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/imagebaker.egg-info/top_level.txt +0 -0
- {imagebaker-0.0.2 → imagebaker-0.0.3}/setup.cfg +0 -0
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
|
@@ -34,6 +34,10 @@ imagebaker/models/segmentation.py
|
|
34
34
|
imagebaker/tabs/__init__.py
|
35
35
|
imagebaker/tabs/baker_tab.py
|
36
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
|
37
41
|
imagebaker/window/__init__.py
|
38
42
|
imagebaker/window/app.py
|
39
43
|
imagebaker/window/main_window.py
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|