batoolset 0.1.0__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.
- batoolset/GUI/__init__.py +0 -0
- batoolset/GUI/list_gui.py +513 -0
- batoolset/GUI/multi_image_display.py +989 -0
- batoolset/GUI/open.py +325 -0
- batoolset/GUI/overlay_hints.py +45 -0
- batoolset/GUI/paint2.py +1345 -0
- batoolset/GUI/scrollablepaint.py +1152 -0
- batoolset/IJ_ROIs/__init__.py +0 -0
- batoolset/IJ_ROIs/tools.py +40 -0
- batoolset/PATH/__init__.py +0 -0
- batoolset/PATH/tools.py +79 -0
- batoolset/SQLite_tools/__init__.py +0 -0
- batoolset/SQLite_tools/tools.py +840 -0
- batoolset/__init__.py +0 -0
- batoolset/binary/__init__.py +0 -0
- batoolset/binary/tools.py +123 -0
- batoolset/binarytools/__init__.py +0 -0
- batoolset/binarytools/cell_center_detector.py +290 -0
- batoolset/clipboard_tools/__init__.py +0 -0
- batoolset/clipboard_tools/tools.py +7 -0
- batoolset/colors/__init__.py +0 -0
- batoolset/colors/colorgen.py +193 -0
- batoolset/colors/tools.py +22 -0
- batoolset/command_line/__init__.py +0 -0
- batoolset/command_line/tools.py +18 -0
- batoolset/dialogs/__init__.py +0 -0
- batoolset/dialogs/opensave.py +228 -0
- batoolset/dicts/__init__.py +0 -0
- batoolset/dicts/tools.py +299 -0
- batoolset/dims/__init__.py +0 -0
- batoolset/dims/tools.py +83 -0
- batoolset/draw/__init__.py +0 -0
- batoolset/draw/shapes/Position.py +183 -0
- batoolset/draw/shapes/__init__.py +0 -0
- batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
- batoolset/draw/shapes/circle2d.py +99 -0
- batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
- batoolset/draw/shapes/ellipse2d.py +336 -0
- batoolset/draw/shapes/freehand2d.py +235 -0
- batoolset/draw/shapes/group.py +1830 -0
- batoolset/draw/shapes/image2d.py +2469 -0
- batoolset/draw/shapes/line2d.py +575 -0
- batoolset/draw/shapes/point2d.py +257 -0
- batoolset/draw/shapes/polygon2d.py +357 -0
- batoolset/draw/shapes/polyline2d.py +235 -0
- batoolset/draw/shapes/rect2d.py +388 -0
- batoolset/draw/shapes/rectangle2d.py +601 -0
- batoolset/draw/shapes/scalebar.py +304 -0
- batoolset/draw/shapes/serializablefont.py +126 -0
- batoolset/draw/shapes/serializableguiparams.py +10 -0
- batoolset/draw/shapes/square2d.py +103 -0
- batoolset/draw/shapes/textorientation.py +45 -0
- batoolset/draw/shapes/tools.py +467 -0
- batoolset/draw/shapes/transform2D.py +350 -0
- batoolset/draw/shapes/txt2d.py +550 -0
- batoolset/draw/widgets/__init__.py +0 -0
- batoolset/draw/widgets/col.py +343 -0
- batoolset/draw/widgets/image_group.py +248 -0
- batoolset/draw/widgets/image_organizer.py +96 -0
- batoolset/draw/widgets/paint.py +184 -0
- batoolset/draw/widgets/vectorial.py +257 -0
- batoolset/figure/__init__.py +0 -0
- batoolset/figure/alignment.py +814 -0
- batoolset/figure/column.py +1117 -0
- batoolset/figure/fig_tools.py +285 -0
- batoolset/figure/minimal_debugger_scale_etc.py +139 -0
- batoolset/figure/panel.py +697 -0
- batoolset/figure/row.py +852 -0
- batoolset/figure/user_selection.py +287 -0
- batoolset/files/__init__.py +0 -0
- batoolset/files/tools.py +1321 -0
- batoolset/images/__init__.py +0 -0
- batoolset/images/tools.py +81 -0
- batoolset/img.py +5216 -0
- batoolset/linux/__init__.py +0 -0
- batoolset/linux/tools.py +72 -0
- batoolset/lists/__init__.py +0 -0
- batoolset/lists/tools.py +436 -0
- batoolset/luts/__init__.py +0 -0
- batoolset/luts/lut_minimal_test.py +1207 -0
- batoolset/matplolib/__init__.py +0 -0
- batoolset/matplolib/tools.py +44 -0
- batoolset/np_tools/__init__.py +0 -0
- batoolset/np_tools/tools.py +252 -0
- batoolset/np_tools/trash.py +15 -0
- batoolset/pandas_tools/__init__.py +0 -0
- batoolset/pandas_tools/tools.py +880 -0
- batoolset/partials/__init__.py +0 -0
- batoolset/partials/tools.py +83 -0
- batoolset/plot_tools/__init__.py +0 -0
- batoolset/plot_tools/tools.py +70 -0
- batoolset/pyqt/__init__.py +0 -0
- batoolset/pyqt/tools.py +691 -0
- batoolset/re_tools/__init__.py +0 -0
- batoolset/re_tools/tools.py +35 -0
- batoolset/serialization/__init__.py +0 -0
- batoolset/serialization/tools.py +1192 -0
- batoolset/sets/__init__.py +0 -0
- batoolset/sets/tools.py +109 -0
- batoolset/settings/__init__.py +0 -0
- batoolset/settings/global_settings.py +134 -0
- batoolset/strings/__init__.py +0 -0
- batoolset/strings/tools.py +225 -0
- batoolset/ta/__init__.py +0 -0
- batoolset/ta/database/__init__.py +0 -0
- batoolset/ta/database/advanced_sql_plotter.py +1170 -0
- batoolset/ta/database/my_qt_table.py +79 -0
- batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
- batoolset/ta/database/sql.py +2141 -0
- batoolset/ta/database/sql_column_name_dialog.py +122 -0
- batoolset/ta/measurements/TAmeasures.py +2226 -0
- batoolset/ta/measurements/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
- batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
- batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
- batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
- batoolset/ta/measurements/nematic.py +562 -0
- batoolset/ta/measurements/nematic_measurements.py +107 -0
- batoolset/ta/segmentation/__init__.py +0 -0
- batoolset/ta/segmentation/neo_wshed.py +314 -0
- batoolset/ta/selections/__init__.py +0 -0
- batoolset/ta/selections/selection.py +329 -0
- batoolset/ta/tracking/__init__.py +0 -0
- batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
- batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
- batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
- batoolset/ta/tracking/registration.py +108 -0
- batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
- batoolset/ta/tracking/track_correction.py +238 -0
- batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
- batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
- batoolset/ta/utils/TA_tools.py +43 -0
- batoolset/ta/utils/__init__.py +0 -0
- batoolset/ta/utils/rps_tools.py +1783 -0
- batoolset/tests/__init__.py +0 -0
- batoolset/tests/rps_tools.py +51 -0
- batoolset/tests/tools.py +10 -0
- batoolset/tools/__init__.py +0 -0
- batoolset/tools/early_stopper_class.py +9 -0
- batoolset/tools/guided_data_recoverer.py +110 -0
- batoolset/tools/logger.py +100 -0
- batoolset/tools/mytimer.py +31 -0
- batoolset/tools/prompt_color.py +160 -0
- batoolset/tools/qthandler.py +118 -0
- batoolset/uitools/__init__.py +0 -0
- batoolset/uitools/blinker.py +56 -0
- batoolset/unsafe/__init__.py +0 -0
- batoolset/unsafe/tools.py +115 -0
- batoolset/utils/__init__.py +0 -0
- batoolset/utils/commontools.py +158 -0
- batoolset/utils/loadlist.py +728 -0
- batoolset/version.py +10 -0
- batoolset/xml/__init__.py +0 -0
- batoolset/xml/tools.py +65 -0
- batoolset/zip_tools/__init__.py +0 -0
- batoolset/zip_tools/tools.py +76 -0
- batoolset-0.1.0.dist-info/LICENSE +24 -0
- batoolset-0.1.0.dist-info/METADATA +45 -0
- batoolset-0.1.0.dist-info/RECORD +162 -0
- batoolset-0.1.0.dist-info/WHEEL +5 -0
- batoolset-0.1.0.dist-info/top_level.txt +1 -0
batoolset/pyqt/tools.py
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
from batoolset.settings.global_settings import set_UI # set the UI to qtpy
|
|
2
|
+
set_UI()
|
|
3
|
+
from qtpy.QtGui import QPainter,QTransform,QImage,QPolygonF,QFont, QTextDocument,QColor,QPolygon
|
|
4
|
+
from qtpy.QtCore import QRectF, QPointF, QSize, QRect, QPoint
|
|
5
|
+
from qtpy.QtCore import Qt, QObject, QEvent
|
|
6
|
+
from qtpy.QtWidgets import QHBoxLayout, QSlider,QLabel,QComboBox,QSpinBox,QWidget,QScrollArea,QDoubleSpinBox
|
|
7
|
+
import math
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# class WheelFilter(QObject):
|
|
12
|
+
# def eventFilter(self, obj, event):
|
|
13
|
+
# if event.type() == QEvent.Wheel:
|
|
14
|
+
# if isinstance(obj, (QComboBox, QSpinBox)):
|
|
15
|
+
# return True
|
|
16
|
+
# return super().eventFilter(obj, event)
|
|
17
|
+
#
|
|
18
|
+
#
|
|
19
|
+
# def disable_wheel_for_widget_and_children(widget):
|
|
20
|
+
# wheel_filter = WheelFilter(widget)
|
|
21
|
+
# widget.installEventFilter(wheel_filter)
|
|
22
|
+
#
|
|
23
|
+
# for child in widget.findChildren(QWidget):
|
|
24
|
+
# if isinstance(child, (QComboBox, QSpinBox)):
|
|
25
|
+
# child.installEventFilter(wheel_filter)
|
|
26
|
+
# elif not isinstance(child, QScrollArea):
|
|
27
|
+
# child.installEventFilter(wheel_filter)
|
|
28
|
+
# disable_wheel_for_widget_and_children(child)
|
|
29
|
+
|
|
30
|
+
# def disable_wheel_for_combo_and_spin(app):
|
|
31
|
+
# app.setStyleSheet("""
|
|
32
|
+
# QComboBox, QSpinBox, QDoubleSpinBox {
|
|
33
|
+
# qproperty-wheelEnabled: false;
|
|
34
|
+
# }
|
|
35
|
+
# """)
|
|
36
|
+
|
|
37
|
+
# class WheelBlocker(QObject):
|
|
38
|
+
# def eventFilter(self, obj, event):
|
|
39
|
+
# if event.type() == QEvent.Wheel:
|
|
40
|
+
# return isinstance(obj, (QComboBox, QSpinBox, QDoubleSpinBox))
|
|
41
|
+
# return False
|
|
42
|
+
#
|
|
43
|
+
# def disable_wheel_for_combo_and_spin(app):
|
|
44
|
+
# blocker = WheelBlocker(app)
|
|
45
|
+
# app.installEventFilter(blocker)
|
|
46
|
+
|
|
47
|
+
def getCtrlModifierAsString():
|
|
48
|
+
if sys.platform == "darwin":
|
|
49
|
+
return "Meta" # Command key on macOS
|
|
50
|
+
# else:
|
|
51
|
+
return "Ctrl" # Control key on other platforms
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def getCtrlModifier():
|
|
55
|
+
if sys.platform == "darwin":
|
|
56
|
+
return Qt.MetaModifier
|
|
57
|
+
else:
|
|
58
|
+
return Qt.ControlModifier
|
|
59
|
+
|
|
60
|
+
class WheelBlocker(QObject):
|
|
61
|
+
def eventFilter(self, obj, event):
|
|
62
|
+
if event.type() == QEvent.Wheel:
|
|
63
|
+
return isinstance(obj, (QComboBox, QSpinBox, QDoubleSpinBox))
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def disable_wheel_for_combo_and_spin(widget):
|
|
68
|
+
blocker = WheelBlocker(widget)
|
|
69
|
+
widget.installEventFilter(blocker)
|
|
70
|
+
|
|
71
|
+
for child in widget.findChildren(QWidget):
|
|
72
|
+
child.installEventFilter(blocker)
|
|
73
|
+
|
|
74
|
+
# return true if antialiasing is on
|
|
75
|
+
def check_antialiasing(painter):
|
|
76
|
+
# print('testing antialias', painter.testRenderHint(QPainter.Antialiasing))
|
|
77
|
+
return painter.testRenderHint(QPainter.Antialiasing)
|
|
78
|
+
|
|
79
|
+
def get_items_of_combo(combo_box):
|
|
80
|
+
items = [combo_box.itemText(i) for i in range(combo_box.count())]
|
|
81
|
+
return items
|
|
82
|
+
|
|
83
|
+
def get_painter_transform_and_rotation(painter):
|
|
84
|
+
# Get the current transformation matrix
|
|
85
|
+
transform = painter.transform()
|
|
86
|
+
|
|
87
|
+
# Get the rotation angle from the transformation matrix
|
|
88
|
+
rotation_angle = math.atan2(transform.m12(), transform.m11()) * 180 / math.pi
|
|
89
|
+
|
|
90
|
+
return transform, rotation_angle
|
|
91
|
+
|
|
92
|
+
def copy_transform_without_rotation(current_transform):
|
|
93
|
+
# Extract the components from the current transform
|
|
94
|
+
m11 = current_transform.m11() # Horizontal scaling
|
|
95
|
+
m12 = current_transform.m12() # Vertical shearing
|
|
96
|
+
m13 = current_transform.m13() # Horizontal projection (should be 0 for affine transforms)
|
|
97
|
+
m21 = current_transform.m21() # Horizontal shearing
|
|
98
|
+
m22 = current_transform.m22() # Vertical scaling
|
|
99
|
+
m23 = current_transform.m23() # Vertical projection (should be 0 for affine transforms)
|
|
100
|
+
m31 = current_transform.m31() # Horizontal translation
|
|
101
|
+
m32 = current_transform.m32() # Vertical translation
|
|
102
|
+
m33 = current_transform.m33() # Additional scaling factor (usually 1 for affine transforms)
|
|
103
|
+
|
|
104
|
+
# Create a new transform without the rotation
|
|
105
|
+
new_transform = QTransform(m11, 0, m13,
|
|
106
|
+
0, m22, m23,
|
|
107
|
+
m31, m32, m33)
|
|
108
|
+
|
|
109
|
+
return new_transform
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def copy_scale_only(current_transform):
|
|
113
|
+
# Extract the scale components from the current transform
|
|
114
|
+
m11 = current_transform.m11() # Horizontal scaling
|
|
115
|
+
m22 = current_transform.m22() # Vertical scaling
|
|
116
|
+
|
|
117
|
+
# Create a new transform with only the scaling components
|
|
118
|
+
new_transform = QTransform(m11, 0, 0,
|
|
119
|
+
0, m22, 0,
|
|
120
|
+
0, 0, 1)
|
|
121
|
+
|
|
122
|
+
return new_transform
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def copy_scale_and_translation_only(current_transform):
|
|
126
|
+
# Extract the scale components from the current transform
|
|
127
|
+
m11 = current_transform.m11() # Horizontal scaling
|
|
128
|
+
m22 = current_transform.m22() # Vertical scaling
|
|
129
|
+
|
|
130
|
+
# Extract the translation components from the current transform
|
|
131
|
+
m31 = current_transform.m31() # Horizontal translation
|
|
132
|
+
m32 = current_transform.m32() # Vertical translation
|
|
133
|
+
|
|
134
|
+
# Create a new transform with only the scaling and translation components
|
|
135
|
+
new_transform = QTransform(m11, 0, 0,
|
|
136
|
+
0, m22, 0,
|
|
137
|
+
m31, m32, 1)
|
|
138
|
+
|
|
139
|
+
return new_transform
|
|
140
|
+
|
|
141
|
+
def rotate_image(image, angle, parent_painter=None, annotations=None,__DEBUG__=False):
|
|
142
|
+
# Get the dimensions of the original image
|
|
143
|
+
width, height = image.width(), image.height()
|
|
144
|
+
# print('inside', width, height, angle)
|
|
145
|
+
|
|
146
|
+
# Define the initial rectangle
|
|
147
|
+
init_rect = QRectF(0, 0, width, height)
|
|
148
|
+
center = init_rect.center()
|
|
149
|
+
|
|
150
|
+
# Create the transformation for rotating the image
|
|
151
|
+
transformation_for_image = QTransform()
|
|
152
|
+
transformation_for_image.translate(center.x(), center.y())
|
|
153
|
+
transformation_for_image.rotate(angle)
|
|
154
|
+
transformation_for_image.translate(-center.x(), -center.y())
|
|
155
|
+
|
|
156
|
+
# Map the initial rectangle to the rotated polygon
|
|
157
|
+
rotated_polygon = transformation_for_image.mapToPolygon(init_rect.toRect())
|
|
158
|
+
rotated_rect = QRectF(rotated_polygon.boundingRect())
|
|
159
|
+
|
|
160
|
+
# try:
|
|
161
|
+
# print('rotated_rect', rotated_rect.size(), rotated_rect, 'angle', angle, 'init', init_rect)
|
|
162
|
+
# except:
|
|
163
|
+
# print('rotated_rect', rotated_rect.size(), rotated_rect, 'angle', angle)
|
|
164
|
+
|
|
165
|
+
# Create a new QImage with the dimensions of the rotated rectangle
|
|
166
|
+
rotated_image = QImage(int(rotated_rect.width()), int(rotated_rect.height()), QImage.Format_ARGB32_Premultiplied)
|
|
167
|
+
rotated_image.fill(Qt.transparent)
|
|
168
|
+
|
|
169
|
+
# Create a QPainter to draw the rotated image
|
|
170
|
+
painter2 = QPainter(rotated_image)
|
|
171
|
+
if parent_painter:
|
|
172
|
+
painter2.setRenderHint(parent_painter.renderHints())
|
|
173
|
+
else:
|
|
174
|
+
# print('removing antialisaing')
|
|
175
|
+
# painter2.setRenderHint(QPainter.Antialiasing, False)
|
|
176
|
+
painter2.setRenderHint(QPainter.Antialiasing, True) # just for now
|
|
177
|
+
|
|
178
|
+
if __DEBUG__:
|
|
179
|
+
pass # TODO -−> set a pen and plot the rects
|
|
180
|
+
|
|
181
|
+
# Calculate the offset to center the rotated image
|
|
182
|
+
offset = QPointF((rotated_rect.width() - width) / 2, (rotated_rect.height() - height) / 2)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# print('offset inside dabula', offset)
|
|
186
|
+
|
|
187
|
+
# Translate the painter to the calculated offset
|
|
188
|
+
painter2.translate(offset)
|
|
189
|
+
|
|
190
|
+
# Apply the rotation transformation
|
|
191
|
+
painter2.translate(center.x(), center.y())
|
|
192
|
+
painter2.rotate(angle)
|
|
193
|
+
painter2.translate(-center.x(), -center.y())
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# Draw the original image onto the rotated image
|
|
198
|
+
painter2.drawImage(init_rect, image, init_rect)
|
|
199
|
+
|
|
200
|
+
if annotations:
|
|
201
|
+
for elm in annotations:
|
|
202
|
+
if hasattr(elm, 'placement'):
|
|
203
|
+
if elm.placement is not None:
|
|
204
|
+
if not elm.placement.position_to_string():
|
|
205
|
+
# continue
|
|
206
|
+
# else:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
painter2.save()
|
|
212
|
+
elm.draw(painter2)
|
|
213
|
+
painter2.restore()
|
|
214
|
+
except:
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
# then draw all annotations
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
painter2.end()
|
|
222
|
+
|
|
223
|
+
# Save the rotated image for verification
|
|
224
|
+
# rotated_image.save('/home/aigouy/Bureau/test.png')
|
|
225
|
+
|
|
226
|
+
return rotated_image
|
|
227
|
+
|
|
228
|
+
def crop_image(image: QImage, crop_left: int, crop_right: int, crop_top: int, crop_bottom: int) -> QImage:
|
|
229
|
+
"""
|
|
230
|
+
Crops the given QImage object by the specified amount of pixels on each side.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
image: The QImage object to crop.
|
|
234
|
+
crop_left: The number of pixels to crop from the left side.
|
|
235
|
+
crop_top: The number of pixels to crop from the top side.
|
|
236
|
+
crop_right: The number of pixels to crop from the right side.
|
|
237
|
+
crop_bottom: The number of pixels to crop from the bottom side.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
A new QImage object with the cropped image.
|
|
241
|
+
"""
|
|
242
|
+
if not crop_left:
|
|
243
|
+
crop_left=0
|
|
244
|
+
if not crop_right:
|
|
245
|
+
crop_right=0
|
|
246
|
+
if not crop_top:
|
|
247
|
+
crop_top=0
|
|
248
|
+
if not crop_bottom:
|
|
249
|
+
crop_bottom=0
|
|
250
|
+
|
|
251
|
+
# Calculate the position and dimensions of the cropped image
|
|
252
|
+
x = int(crop_left)
|
|
253
|
+
y = int(crop_top)
|
|
254
|
+
width = int(image.width() - crop_left - crop_right)
|
|
255
|
+
height = int(image.height() - crop_top - crop_bottom)
|
|
256
|
+
|
|
257
|
+
# Create a QRect object with the position and dimensions of the cropped image
|
|
258
|
+
rect = QRect(x, y, width, height)
|
|
259
|
+
|
|
260
|
+
# Create a new QImage object with the cropped image
|
|
261
|
+
cropped_image = image.copy(rect)
|
|
262
|
+
|
|
263
|
+
return cropped_image
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def tst_translation(rect: QRectF, angle: float, crop_left: float, crop_right: float, crop_top: float, crop_bottom: float) -> QPolygonF:
|
|
267
|
+
rotated_shape = get_shape_after_rotation_and_crop(rect, angle, crop_left, crop_right, crop_top, crop_bottom)
|
|
268
|
+
return rect.center()-get_centroid(rotated_shape)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_shape_after_rotation_and_crop(rect, angle, crop_left, crop_right, crop_top, crop_bottom, return_AR = False):
|
|
272
|
+
# this retruns the qrectf corresponding to the initial image after rotation and crop --> what will be displayed --> that seems to work fine --> TODO
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
if not angle and not crop_left and not crop_right and not crop_top and not crop_bottom:
|
|
277
|
+
# print('NADA FOUND --> cancelling')
|
|
278
|
+
if return_AR:
|
|
279
|
+
return rect.width()/rect.height(), None
|
|
280
|
+
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
if True:
|
|
284
|
+
# maybe this should be an option
|
|
285
|
+
# rect.setX(0)
|
|
286
|
+
# rect.setY(0)
|
|
287
|
+
rect = QRectF(0,0,rect.width(), rect.height())
|
|
288
|
+
|
|
289
|
+
# assume all is at 0 -->
|
|
290
|
+
|
|
291
|
+
# Calculate the center of the rectangle
|
|
292
|
+
center = rect.center()
|
|
293
|
+
|
|
294
|
+
# Create a transformation matrix to rotate the rectangle around its center
|
|
295
|
+
transform = QTransform()
|
|
296
|
+
if angle:
|
|
297
|
+
transform.translate(center.x(), center.y())
|
|
298
|
+
transform.rotate(angle)
|
|
299
|
+
transform.translate(-center.x(), -center.y())
|
|
300
|
+
|
|
301
|
+
# Apply the transformation to the rectangle
|
|
302
|
+
# bounding_rect = transform.mapRect(rect)
|
|
303
|
+
rotated_rect_as_polygon = transform.mapToPolygon(rect.toRect())
|
|
304
|
+
|
|
305
|
+
# Calculate the bounding rectangle of the rotated rectangle
|
|
306
|
+
|
|
307
|
+
bounds_rotated = rotated_rect_as_polygon.boundingRect()
|
|
308
|
+
|
|
309
|
+
# print(bounds_rotated)
|
|
310
|
+
|
|
311
|
+
bounds_rotated.setLeft(int(bounds_rotated.left()+crop_left))
|
|
312
|
+
bounds_rotated.setRight(int(bounds_rotated.right()-crop_right))
|
|
313
|
+
bounds_rotated.setTop(int(bounds_rotated.top()+crop_top))
|
|
314
|
+
bounds_rotated.setBottom(int(bounds_rotated.bottom()-crop_bottom))
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# this is what should be used to get AR !!! ---> TODO
|
|
318
|
+
# print('AR in the bidule', bounds_rotated.width()/bounds_rotated.height())
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# this gives me a rect that I can plot back on the orig that will show what is displayed -->
|
|
325
|
+
|
|
326
|
+
# no in fact I need to get the union of the yellow rotated poly/rect and the blue --> TODO
|
|
327
|
+
|
|
328
|
+
# Get the overlapping area between the polygon and the rectangle
|
|
329
|
+
transform = QTransform()
|
|
330
|
+
crop_rect_as_polygon = transform.mapToPolygon(bounds_rotated)
|
|
331
|
+
|
|
332
|
+
# print(crop_rect_as_polygon.boundingRect()) # c'est le bounding rect difference --> -23 --> see why that is ???
|
|
333
|
+
|
|
334
|
+
overlapping_polygon = rotated_rect_as_polygon.intersected(crop_rect_as_polygon)
|
|
335
|
+
# print(overlapping_polygon.boundingRect())
|
|
336
|
+
|
|
337
|
+
# overlapping_polygon_f = QPolygonF(overlapping_polygon)
|
|
338
|
+
transform = QTransform()
|
|
339
|
+
# --> all good --> I have it now see how I can plot it back onto the original --> see the translation and rotation I need to apply
|
|
340
|
+
# transform.translate(bounds_rotated.center().x(),bounds_rotated.center().y())
|
|
341
|
+
# transform.rotate(-angle)
|
|
342
|
+
# transform.translate(-bounds_rotated.center().x(), -bounds_rotated.center().y())
|
|
343
|
+
|
|
344
|
+
# we revert to initial rotation !!!
|
|
345
|
+
if angle:
|
|
346
|
+
transform.translate(center.x(),center.y()) #
|
|
347
|
+
transform.rotate(-angle)
|
|
348
|
+
transform.translate(-center.x(), -center.y())
|
|
349
|
+
# overlapping_polygon_rotated = overlapping_polygon_f.transformed(transform)
|
|
350
|
+
overlapping_polygon_rotated = transform.map(overlapping_polygon)
|
|
351
|
+
|
|
352
|
+
# print(overlapping_polygon_rotated.boundingRect())
|
|
353
|
+
# print()
|
|
354
|
+
if not return_AR:
|
|
355
|
+
return overlapping_polygon_rotated
|
|
356
|
+
else:
|
|
357
|
+
return bounds_rotated.width()/bounds_rotated.height(), overlapping_polygon_rotated
|
|
358
|
+
|
|
359
|
+
def get_original_shape_from_rect_and_angle(parent_rect, drawn_rect, angle):
|
|
360
|
+
# that works perfect for shapes below 90 degrees --> see how to fix that
|
|
361
|
+
# Calculate the bounding rectangle of the rotated rectangle
|
|
362
|
+
drawn_rect = drawn_rect.normalized()
|
|
363
|
+
|
|
364
|
+
center = parent_rect.center()
|
|
365
|
+
# the mapped rect will have the same centroid position as the polygon and as the rect by the way or as the
|
|
366
|
+
transform = QTransform()
|
|
367
|
+
transform.translate(center.x(), center.y())
|
|
368
|
+
transform.rotate(-angle)
|
|
369
|
+
transform.translate(-center.x(), -center.y())
|
|
370
|
+
|
|
371
|
+
# get the transformed shape
|
|
372
|
+
rotated_back_drawn = transform.mapToPolygon(drawn_rect.toRect())
|
|
373
|
+
rotated_back_parent = transform.mapToPolygon(parent_rect.toRect())
|
|
374
|
+
|
|
375
|
+
# if I have the position of the centroid I can place any rect on top
|
|
376
|
+
drawn_rect.translate(QPointF(rotated_back_drawn.boundingRect().center())-drawn_rect.center())
|
|
377
|
+
|
|
378
|
+
crop_left = -(rotated_back_parent.boundingRect().left()-drawn_rect.left())
|
|
379
|
+
crop_right = rotated_back_parent.boundingRect().right()-drawn_rect.right()
|
|
380
|
+
crop_top = -(rotated_back_parent.boundingRect().top() - drawn_rect.top())
|
|
381
|
+
crop_bottom = rotated_back_parent.boundingRect().bottom()-drawn_rect.bottom()
|
|
382
|
+
|
|
383
|
+
if True: # maybe put this as a parameter
|
|
384
|
+
crop_left = int(crop_left)
|
|
385
|
+
crop_right = int(crop_right)
|
|
386
|
+
crop_top = int(crop_top)
|
|
387
|
+
crop_bottom = int(crop_bottom)
|
|
388
|
+
|
|
389
|
+
return -angle, crop_left, crop_right, crop_top, crop_bottom
|
|
390
|
+
|
|
391
|
+
def get_html_text_with_font(text, font):
|
|
392
|
+
# Create a font
|
|
393
|
+
# font = QFont()
|
|
394
|
+
# font.setFamily(font_family)
|
|
395
|
+
# font.setPointSize(font_size)
|
|
396
|
+
# font.setWeight(QFont.Normal if font_style == 'Normal' else QFont.Bold)
|
|
397
|
+
|
|
398
|
+
# Create a QTextDocument object
|
|
399
|
+
text_document = QTextDocument()
|
|
400
|
+
|
|
401
|
+
# Apply the font to the text document
|
|
402
|
+
text_document.setDefaultFont(font)
|
|
403
|
+
|
|
404
|
+
# Set the text
|
|
405
|
+
text_document.setHtml('<p>' + text + '</p>')
|
|
406
|
+
|
|
407
|
+
# Get the HTML equivalent with the applied font
|
|
408
|
+
html_text = text_document.toHtml()
|
|
409
|
+
|
|
410
|
+
return html_text
|
|
411
|
+
|
|
412
|
+
def get_centroid(polygon: QPolygonF) -> QPointF:
|
|
413
|
+
# Ensure the polygon is closed
|
|
414
|
+
if polygon.isEmpty():
|
|
415
|
+
return QPointF()
|
|
416
|
+
|
|
417
|
+
if polygon.first() != polygon.last():
|
|
418
|
+
polygon.append(polygon.first())
|
|
419
|
+
|
|
420
|
+
n = len(polygon)
|
|
421
|
+
A = 0 # Partial area
|
|
422
|
+
Cx = 0 # x coordinate of centroid
|
|
423
|
+
Cy = 0 # y coordinate of centroid
|
|
424
|
+
|
|
425
|
+
for i in range(n - 1):
|
|
426
|
+
x0, y0 = polygon[i].x(), polygon[i].y()
|
|
427
|
+
x1, y1 = polygon[i + 1].x(), polygon[i + 1].y()
|
|
428
|
+
cross_product = x0 * y1 - x1 * y0
|
|
429
|
+
A += cross_product
|
|
430
|
+
Cx += (x0 + x1) * cross_product
|
|
431
|
+
Cy += (y0 + y1) * cross_product
|
|
432
|
+
|
|
433
|
+
A /= 2
|
|
434
|
+
Cx /= (6 * A)
|
|
435
|
+
Cy /= (6 * A)
|
|
436
|
+
|
|
437
|
+
return QPointF(Cx, Cy)
|
|
438
|
+
# def get_centroid2(polygon):
|
|
439
|
+
# """
|
|
440
|
+
# Returns the centroid (center of mass) of a QPolygonF object.
|
|
441
|
+
# """
|
|
442
|
+
# area = 0.0
|
|
443
|
+
# centroid = QPointF(0.0, 0.0)
|
|
444
|
+
#
|
|
445
|
+
# # Calculate the signed area and centroid using the Shoelace formula
|
|
446
|
+
# for i in range(len(polygon)):
|
|
447
|
+
# j = (i + 1) % len(polygon)
|
|
448
|
+
# a = polygon[i].x() * polygon[j].y() - polygon[j].x() * polygon[i].y()
|
|
449
|
+
# area += a
|
|
450
|
+
# centroid += QPointF((polygon[i].x() + polygon[j].x()) * a, (polygon[i].y() + polygon[j].y()) * a)
|
|
451
|
+
#
|
|
452
|
+
# area *= 0.5
|
|
453
|
+
# if area != 0.0:
|
|
454
|
+
# centroid /= (3.0 * area)
|
|
455
|
+
#
|
|
456
|
+
# return centroid
|
|
457
|
+
|
|
458
|
+
# def get_centroid(qpolygonf):
|
|
459
|
+
# x_sum = 0
|
|
460
|
+
# y_sum = 0
|
|
461
|
+
# num_points = qpolygonf.count()-1 # assume the shape is closed so the last point is the first and we should take it only once
|
|
462
|
+
#
|
|
463
|
+
# for i in range(num_points):
|
|
464
|
+
# point = qpolygonf.at(i)
|
|
465
|
+
# x_sum += point.x()
|
|
466
|
+
# y_sum += point.y()
|
|
467
|
+
#
|
|
468
|
+
# centroid_x = x_sum / num_points
|
|
469
|
+
# centroid_y = y_sum / num_points
|
|
470
|
+
#
|
|
471
|
+
# return QPointF(centroid_x, centroid_y)
|
|
472
|
+
def grab_n_find_color(qwidget, color=QColor(0, 0, 0)):
|
|
473
|
+
# Capture the contents of the widget as a QPixmap object
|
|
474
|
+
first_black_pixel = None
|
|
475
|
+
pixmap = qwidget.grab()
|
|
476
|
+
# Find the coordinates of the first black pixel
|
|
477
|
+
width = pixmap.width()
|
|
478
|
+
height = pixmap.height()
|
|
479
|
+
image = pixmap.toImage()
|
|
480
|
+
for x in range(width):
|
|
481
|
+
for y in range(height):
|
|
482
|
+
cur_col = image.pixelColor(x, y)
|
|
483
|
+
if cur_col == color:
|
|
484
|
+
first_black_pixel = (x, y)
|
|
485
|
+
print(first_black_pixel) # --> 15,89 --> ok -−> do a fix then but ok for now
|
|
486
|
+
break
|
|
487
|
+
else:
|
|
488
|
+
continue
|
|
489
|
+
break
|
|
490
|
+
del pixmap
|
|
491
|
+
del image
|
|
492
|
+
return first_black_pixel
|
|
493
|
+
|
|
494
|
+
def average_polygons(*polygons):
|
|
495
|
+
"""
|
|
496
|
+
Calculate the average of the QPolygonF objects.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
*polygons: A variable number of QPolygonF objects.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
QPolygonF: The average of the QPolygonF objects.
|
|
503
|
+
"""
|
|
504
|
+
if not polygons:
|
|
505
|
+
return QPolygon()
|
|
506
|
+
|
|
507
|
+
# Check that all polygons have the same number of points
|
|
508
|
+
num_points = len(polygons[0])
|
|
509
|
+
for polygon in polygons[1:]:
|
|
510
|
+
if len(polygon) != num_points:
|
|
511
|
+
raise ValueError("All polygons must have the same number of points.")
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
out = QPolygon()
|
|
515
|
+
# Calculate the average of the x and y coordinates of each point
|
|
516
|
+
# average_points = []
|
|
517
|
+
for i in range(num_points):
|
|
518
|
+
x_sum = 0
|
|
519
|
+
y_sum = 0
|
|
520
|
+
for polygon in polygons:
|
|
521
|
+
x_sum += polygon[i].x()
|
|
522
|
+
y_sum += polygon[i].y()
|
|
523
|
+
x_avg = x_sum / len(polygons)
|
|
524
|
+
y_avg = y_sum / len(polygons)
|
|
525
|
+
# average_points.append(QPointF(x_avg, y_avg))
|
|
526
|
+
out.append(QPoint(int(x_avg), int(y_avg)))
|
|
527
|
+
return out
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def create_dim_slider(dimension=None, max_dim=1, return_slider=True):
|
|
531
|
+
dim_slider_with_label1 = QHBoxLayout()
|
|
532
|
+
label_slider1 = QLabel()
|
|
533
|
+
if dimension is not None:
|
|
534
|
+
label_slider1.setText(dimension)
|
|
535
|
+
dim_slider = QSlider(Qt.Horizontal)
|
|
536
|
+
dim_slider.setMinimum(0)
|
|
537
|
+
dim_slider.setMaximum(max_dim-1)
|
|
538
|
+
dim_slider_with_label1.addWidget(label_slider1)
|
|
539
|
+
dim_slider_with_label1.addWidget(dim_slider)
|
|
540
|
+
dim_slider.setObjectName(dimension)
|
|
541
|
+
if not return_slider:
|
|
542
|
+
return dim_slider_with_label1
|
|
543
|
+
else:
|
|
544
|
+
return dim_slider_with_label1, dim_slider
|
|
545
|
+
|
|
546
|
+
# def empty_widget_layout(widget):
|
|
547
|
+
# if widget is None:
|
|
548
|
+
# return
|
|
549
|
+
# layout = widget.layout()
|
|
550
|
+
# if not layout:
|
|
551
|
+
# return
|
|
552
|
+
# while layout.count():
|
|
553
|
+
# item = layout.takeAt(0)
|
|
554
|
+
# widget = item.widget()
|
|
555
|
+
# # Delete the widget associated with the item
|
|
556
|
+
# if widget is not None:
|
|
557
|
+
# widget.setParent(None)
|
|
558
|
+
# widget.deleteLater()
|
|
559
|
+
# # else:
|
|
560
|
+
# # print('weirdo stuff',item)
|
|
561
|
+
# # layout = item.layout()
|
|
562
|
+
# # Delete the item
|
|
563
|
+
# # del item
|
|
564
|
+
# # return layout
|
|
565
|
+
|
|
566
|
+
def clear_layout(layout):
|
|
567
|
+
while layout.count():
|
|
568
|
+
item = layout.takeAt(0)
|
|
569
|
+
if item.widget() is not None:
|
|
570
|
+
item.widget().deleteLater()
|
|
571
|
+
else:
|
|
572
|
+
clear_layout(item.layout())
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def distance(point1, point2):
|
|
576
|
+
"""
|
|
577
|
+
Compute the Euclidean distance between two points.
|
|
578
|
+
|
|
579
|
+
Parameters:
|
|
580
|
+
point1 (QPointF): First point
|
|
581
|
+
point2 (QPointF): Second point
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
float: Euclidean distance between the two points
|
|
585
|
+
"""
|
|
586
|
+
dx = point1.x() - point2.x()
|
|
587
|
+
dy = point1.y() - point2.y()
|
|
588
|
+
return math.sqrt(dx * dx + dy * dy)
|
|
589
|
+
|
|
590
|
+
def select_in_combobox(combobox, text=None,value=None, default_index_on_not_found=None):
|
|
591
|
+
if text is None and value is None:
|
|
592
|
+
print('Error text or value must be specified!!!')
|
|
593
|
+
|
|
594
|
+
if value is not None:
|
|
595
|
+
index = combobox.findVale(value)
|
|
596
|
+
elif text is not None:
|
|
597
|
+
index = combobox.findText(text)
|
|
598
|
+
if index >= 0:
|
|
599
|
+
combobox.setCurrentIndex(index)
|
|
600
|
+
else:
|
|
601
|
+
if default_index_on_not_found is not None:
|
|
602
|
+
combobox.setCurrentIndex(default_index_on_not_found)
|
|
603
|
+
|
|
604
|
+
def find_action_by_text(menu, text='ignore'):
|
|
605
|
+
for action in menu.actions():
|
|
606
|
+
if action.text() == text:
|
|
607
|
+
return action
|
|
608
|
+
return None
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
if __name__ == '__main__':
|
|
612
|
+
import sys
|
|
613
|
+
|
|
614
|
+
if False:
|
|
615
|
+
from PyQt6.QtCore import QPointF, QRectF
|
|
616
|
+
|
|
617
|
+
# Define the two sets of rectangles
|
|
618
|
+
rects1 = [
|
|
619
|
+
QRectF(0.0, 0.0, 145.0, 303.0),
|
|
620
|
+
QRectF(0.0, 0.0, 205.20137371117733, 428.80011196197745),
|
|
621
|
+
QRectF(0.0, 0.0, 205.20137371117733, 428.80011196197745)
|
|
622
|
+
]
|
|
623
|
+
rects2 = [
|
|
624
|
+
QRectF(0.0, 0.0, 145.0, 303.0),
|
|
625
|
+
QRectF(0.0, 0.0, 106.1386415747469, 230.67464768911657),
|
|
626
|
+
QRectF(0.0, 0.0, 342.47401681451663, 260.3934673300457)
|
|
627
|
+
]
|
|
628
|
+
|
|
629
|
+
point = rects1[-1].center()-rects2[-1].center()
|
|
630
|
+
print(point)
|
|
631
|
+
print(rects2[-1].translated(point.x(), point.y()))
|
|
632
|
+
|
|
633
|
+
# Define the desired overall translation
|
|
634
|
+
desired_translation = QPointF(50, -50)
|
|
635
|
+
|
|
636
|
+
# Calculate the center of each rectangle
|
|
637
|
+
centers1 = [rect.center() for rect in rects1]
|
|
638
|
+
centers2 = [rect.center() for rect in rects2]
|
|
639
|
+
|
|
640
|
+
print('centers1',centers1)
|
|
641
|
+
print('centers2',centers2)
|
|
642
|
+
|
|
643
|
+
# Calculate the translations required to move the centers of the rectangles in the first set to the centers of the rectangles in the second set
|
|
644
|
+
translations = [centers2[i] - centers1[i] for i in range(len(rects1))]
|
|
645
|
+
|
|
646
|
+
# Calculate the average of the translations
|
|
647
|
+
average_translation = QPointF(sum(t.x() for t in translations) / len(translations),
|
|
648
|
+
sum(t.y() for t in translations) / len(translations))
|
|
649
|
+
|
|
650
|
+
# If the average translation is not equal to the desired translation, adjust the individual translations
|
|
651
|
+
if average_translation != desired_translation:
|
|
652
|
+
# Calculate the difference between the average translation and the desired translation
|
|
653
|
+
diff = desired_translation - average_translation
|
|
654
|
+
|
|
655
|
+
# Adjust the individual translations
|
|
656
|
+
adjusted_translations = [t + diff for t in translations]
|
|
657
|
+
|
|
658
|
+
# Use the adjusted translations to move the rectangles in the first set to the second set
|
|
659
|
+
moved_rects = [rect.translated(t) for rect, t in zip(rects1, adjusted_translations)]
|
|
660
|
+
else:
|
|
661
|
+
# Use the original translations to move the rectangles in the first set to the second set
|
|
662
|
+
moved_rects = [rect.translated(t) for rect, t in zip(rects1, translations)]
|
|
663
|
+
|
|
664
|
+
print(translations)
|
|
665
|
+
print(average_translation)
|
|
666
|
+
|
|
667
|
+
sys.exit(0)
|
|
668
|
+
|
|
669
|
+
if True:
|
|
670
|
+
# get_shape_after_rotation_and_crop(QRectF(0,0,145,303), 42, 30,40,60,80)
|
|
671
|
+
print(get_shape_after_rotation_and_crop(QRectF(0,0,145,303), 42, 30,40,60,80))
|
|
672
|
+
print(tst_translation(QRectF(0,0,145,303), 42, 30,40,60,80))
|
|
673
|
+
# print(get_shape_after_rotation_and_crop(QRectF(0, 0, 145, 303), 42, 60, 30, 0, 0))
|
|
674
|
+
# print(tst_translation(QRectF(0,0,145,303), 42, 60,30,0,0))
|
|
675
|
+
|
|
676
|
+
print(get_centroid(get_shape_after_rotation_and_crop(QRectF(0,0,145,303), 42, 30,40,60,80)))
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
sys.exit(0)
|
|
681
|
+
|
|
682
|
+
if True:
|
|
683
|
+
# rotation of images 1 and 2 are correct so the error is clearly elsewhere if not centered -−> see why that is
|
|
684
|
+
image = QImage('/E/Sample_images/counter/01.png')
|
|
685
|
+
# image = QImage('/E/Sample_images/counter/02.png')
|
|
686
|
+
# image = QImage('/E/Sample_images/sample_images_PA/test.png')
|
|
687
|
+
rotated = rotate_image(image, 42)
|
|
688
|
+
rotated.save('/home/aigouy/Bureau/test.png')
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
sys.exit(0)
|
|
File without changes
|