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