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.
Files changed (162) hide show
  1. batoolset/GUI/__init__.py +0 -0
  2. batoolset/GUI/list_gui.py +513 -0
  3. batoolset/GUI/multi_image_display.py +989 -0
  4. batoolset/GUI/open.py +325 -0
  5. batoolset/GUI/overlay_hints.py +45 -0
  6. batoolset/GUI/paint2.py +1345 -0
  7. batoolset/GUI/scrollablepaint.py +1152 -0
  8. batoolset/IJ_ROIs/__init__.py +0 -0
  9. batoolset/IJ_ROIs/tools.py +40 -0
  10. batoolset/PATH/__init__.py +0 -0
  11. batoolset/PATH/tools.py +79 -0
  12. batoolset/SQLite_tools/__init__.py +0 -0
  13. batoolset/SQLite_tools/tools.py +840 -0
  14. batoolset/__init__.py +0 -0
  15. batoolset/binary/__init__.py +0 -0
  16. batoolset/binary/tools.py +123 -0
  17. batoolset/binarytools/__init__.py +0 -0
  18. batoolset/binarytools/cell_center_detector.py +290 -0
  19. batoolset/clipboard_tools/__init__.py +0 -0
  20. batoolset/clipboard_tools/tools.py +7 -0
  21. batoolset/colors/__init__.py +0 -0
  22. batoolset/colors/colorgen.py +193 -0
  23. batoolset/colors/tools.py +22 -0
  24. batoolset/command_line/__init__.py +0 -0
  25. batoolset/command_line/tools.py +18 -0
  26. batoolset/dialogs/__init__.py +0 -0
  27. batoolset/dialogs/opensave.py +228 -0
  28. batoolset/dicts/__init__.py +0 -0
  29. batoolset/dicts/tools.py +299 -0
  30. batoolset/dims/__init__.py +0 -0
  31. batoolset/dims/tools.py +83 -0
  32. batoolset/draw/__init__.py +0 -0
  33. batoolset/draw/shapes/Position.py +183 -0
  34. batoolset/draw/shapes/__init__.py +0 -0
  35. batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
  36. batoolset/draw/shapes/circle2d.py +99 -0
  37. batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
  38. batoolset/draw/shapes/ellipse2d.py +336 -0
  39. batoolset/draw/shapes/freehand2d.py +235 -0
  40. batoolset/draw/shapes/group.py +1830 -0
  41. batoolset/draw/shapes/image2d.py +2469 -0
  42. batoolset/draw/shapes/line2d.py +575 -0
  43. batoolset/draw/shapes/point2d.py +257 -0
  44. batoolset/draw/shapes/polygon2d.py +357 -0
  45. batoolset/draw/shapes/polyline2d.py +235 -0
  46. batoolset/draw/shapes/rect2d.py +388 -0
  47. batoolset/draw/shapes/rectangle2d.py +601 -0
  48. batoolset/draw/shapes/scalebar.py +304 -0
  49. batoolset/draw/shapes/serializablefont.py +126 -0
  50. batoolset/draw/shapes/serializableguiparams.py +10 -0
  51. batoolset/draw/shapes/square2d.py +103 -0
  52. batoolset/draw/shapes/textorientation.py +45 -0
  53. batoolset/draw/shapes/tools.py +467 -0
  54. batoolset/draw/shapes/transform2D.py +350 -0
  55. batoolset/draw/shapes/txt2d.py +550 -0
  56. batoolset/draw/widgets/__init__.py +0 -0
  57. batoolset/draw/widgets/col.py +343 -0
  58. batoolset/draw/widgets/image_group.py +248 -0
  59. batoolset/draw/widgets/image_organizer.py +96 -0
  60. batoolset/draw/widgets/paint.py +184 -0
  61. batoolset/draw/widgets/vectorial.py +257 -0
  62. batoolset/figure/__init__.py +0 -0
  63. batoolset/figure/alignment.py +814 -0
  64. batoolset/figure/column.py +1117 -0
  65. batoolset/figure/fig_tools.py +285 -0
  66. batoolset/figure/minimal_debugger_scale_etc.py +139 -0
  67. batoolset/figure/panel.py +697 -0
  68. batoolset/figure/row.py +852 -0
  69. batoolset/figure/user_selection.py +287 -0
  70. batoolset/files/__init__.py +0 -0
  71. batoolset/files/tools.py +1321 -0
  72. batoolset/images/__init__.py +0 -0
  73. batoolset/images/tools.py +81 -0
  74. batoolset/img.py +5216 -0
  75. batoolset/linux/__init__.py +0 -0
  76. batoolset/linux/tools.py +72 -0
  77. batoolset/lists/__init__.py +0 -0
  78. batoolset/lists/tools.py +436 -0
  79. batoolset/luts/__init__.py +0 -0
  80. batoolset/luts/lut_minimal_test.py +1207 -0
  81. batoolset/matplolib/__init__.py +0 -0
  82. batoolset/matplolib/tools.py +44 -0
  83. batoolset/np_tools/__init__.py +0 -0
  84. batoolset/np_tools/tools.py +252 -0
  85. batoolset/np_tools/trash.py +15 -0
  86. batoolset/pandas_tools/__init__.py +0 -0
  87. batoolset/pandas_tools/tools.py +880 -0
  88. batoolset/partials/__init__.py +0 -0
  89. batoolset/partials/tools.py +83 -0
  90. batoolset/plot_tools/__init__.py +0 -0
  91. batoolset/plot_tools/tools.py +70 -0
  92. batoolset/pyqt/__init__.py +0 -0
  93. batoolset/pyqt/tools.py +691 -0
  94. batoolset/re_tools/__init__.py +0 -0
  95. batoolset/re_tools/tools.py +35 -0
  96. batoolset/serialization/__init__.py +0 -0
  97. batoolset/serialization/tools.py +1192 -0
  98. batoolset/sets/__init__.py +0 -0
  99. batoolset/sets/tools.py +109 -0
  100. batoolset/settings/__init__.py +0 -0
  101. batoolset/settings/global_settings.py +134 -0
  102. batoolset/strings/__init__.py +0 -0
  103. batoolset/strings/tools.py +225 -0
  104. batoolset/ta/__init__.py +0 -0
  105. batoolset/ta/database/__init__.py +0 -0
  106. batoolset/ta/database/advanced_sql_plotter.py +1170 -0
  107. batoolset/ta/database/my_qt_table.py +79 -0
  108. batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
  109. batoolset/ta/database/sql.py +2141 -0
  110. batoolset/ta/database/sql_column_name_dialog.py +122 -0
  111. batoolset/ta/measurements/TAmeasures.py +2226 -0
  112. batoolset/ta/measurements/__init__.py +0 -0
  113. batoolset/ta/measurements/measurements3D/__init__.py +0 -0
  114. batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
  115. batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
  116. batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
  117. batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
  118. batoolset/ta/measurements/nematic.py +562 -0
  119. batoolset/ta/measurements/nematic_measurements.py +107 -0
  120. batoolset/ta/segmentation/__init__.py +0 -0
  121. batoolset/ta/segmentation/neo_wshed.py +314 -0
  122. batoolset/ta/selections/__init__.py +0 -0
  123. batoolset/ta/selections/selection.py +329 -0
  124. batoolset/ta/tracking/__init__.py +0 -0
  125. batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
  126. batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
  127. batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
  128. batoolset/ta/tracking/registration.py +108 -0
  129. batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
  130. batoolset/ta/tracking/track_correction.py +238 -0
  131. batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
  132. batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
  133. batoolset/ta/utils/TA_tools.py +43 -0
  134. batoolset/ta/utils/__init__.py +0 -0
  135. batoolset/ta/utils/rps_tools.py +1783 -0
  136. batoolset/tests/__init__.py +0 -0
  137. batoolset/tests/rps_tools.py +51 -0
  138. batoolset/tests/tools.py +10 -0
  139. batoolset/tools/__init__.py +0 -0
  140. batoolset/tools/early_stopper_class.py +9 -0
  141. batoolset/tools/guided_data_recoverer.py +110 -0
  142. batoolset/tools/logger.py +100 -0
  143. batoolset/tools/mytimer.py +31 -0
  144. batoolset/tools/prompt_color.py +160 -0
  145. batoolset/tools/qthandler.py +118 -0
  146. batoolset/uitools/__init__.py +0 -0
  147. batoolset/uitools/blinker.py +56 -0
  148. batoolset/unsafe/__init__.py +0 -0
  149. batoolset/unsafe/tools.py +115 -0
  150. batoolset/utils/__init__.py +0 -0
  151. batoolset/utils/commontools.py +158 -0
  152. batoolset/utils/loadlist.py +728 -0
  153. batoolset/version.py +10 -0
  154. batoolset/xml/__init__.py +0 -0
  155. batoolset/xml/tools.py +65 -0
  156. batoolset/zip_tools/__init__.py +0 -0
  157. batoolset/zip_tools/tools.py +76 -0
  158. batoolset-0.1.0.dist-info/LICENSE +24 -0
  159. batoolset-0.1.0.dist-info/METADATA +45 -0
  160. batoolset-0.1.0.dist-info/RECORD +162 -0
  161. batoolset-0.1.0.dist-info/WHEEL +5 -0
  162. batoolset-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2469 @@
1
+ from batoolset.settings.global_settings import set_UI # set the UI to qtpy
2
+ set_UI()
3
+ from batoolset.lists.tools import bool_list_to_index_list
4
+ from batoolset.luts.lut_minimal_test import PaletteCreator, list_available_luts, apply_lut, lsm_LUT_to_numpy
5
+ from batoolset.files.tools import smart_name_parser
6
+ from batoolset.files.tools import get_consolidated_filename_from_parent
7
+ from batoolset.strings.tools import reload_string_list, is_string_a_list_string, find_letter_before_h
8
+ from batoolset.draw.shapes.automated_alignment_drawing_refactored import compute_translation_corrections
9
+ from batoolset.pyqt.tools import check_antialiasing, copy_transform_without_rotation, rotate_image, copy_scale_only, \
10
+ copy_scale_and_translation_only, crop_image, get_shape_after_rotation_and_crop, tst_translation
11
+ from batoolset.serialization.tools import create_objects_from_dict, object_to_xml
12
+ from batoolset.matplolib.tools import get_fig_rect
13
+ import os
14
+ import traceback
15
+ from batoolset.draw.shapes.Position import groupby_position, Position
16
+ from batoolset.draw.shapes.rectangle2d import Rectangle2D
17
+ import numpy as np
18
+ import matplotlib.pyplot as plt
19
+ from qtpy import QtGui
20
+ from qtpy.QtGui import QPainter, QColor, QBrush, QPen, QTransform, QFontMetrics,QImage
21
+ from qtpy.QtCore import Qt
22
+ from qtpy.QtSvg import QSvgGenerator
23
+ from qtpy.QtSvg import QSvgRenderer
24
+ from batoolset.draw.shapes.line2d import Line2D
25
+ from batoolset.draw.shapes.point2d import Point2D
26
+ from batoolset.figure.alignment import alignRight, alignLeft, alignTop, alignBottom, alignCenterH, alignCenterV, packY, \
27
+ packX, packYreverse, pack2, packing_modes
28
+ from batoolset.draw.shapes.scalebar import ScaleBar
29
+ from batoolset.draw.shapes.txt2d import TAText2D
30
+
31
+ from batoolset.img import Img, toQimage, RGB_to_BGR, guess_dimensions, max_proj, min_proj, avg_proj
32
+ from qtpy.QtCore import QRectF, QPointF, QSize, QRect
33
+ from qtpy.QtWidgets import QApplication
34
+ import sys
35
+ import io
36
+ from batoolset.tools.logger import TA_logger
37
+ import ast
38
+ import re
39
+
40
+ logger = TA_logger()
41
+
42
+
43
+ __DEBUG_NORMALIZATION__ = False
44
+
45
+ class Image2D(Rectangle2D):
46
+ # min = None, max = None,
47
+ def __init__(self, *args, x=None, y=None, width=None, height=None, data=None, dimensions=None, channels=None, LUTs=None, projection=None, normalization=None, filename=None, isText=False, fraction_of_parent_image_width_if_image_is_inset=0.25, opacity=1., theta=None, crop_left=0, crop_right=0, crop_top=0, crop_bottom=0,fill_color=None, border_color=0xFFFFFF,border_size=None, placement=Position('Top-Right'),annotations=None, custom_loading_script=None, allow_unknown_files=True, scale=1, user_comments='',__version__=1.0, px_to_unit_conversion_factor=0, extra_user_translation=(0,0),script_log=None, template_rect=None,**kwargs):
48
+
49
+ # print('args',args)
50
+ # if kwargs:
51
+ # print('ignored parameters', kwargs)
52
+
53
+ if template_rect:
54
+ try:
55
+ # print('template_rect',template_rect)
56
+ pattern = r'\((.*?)\)'
57
+ match = re.search(pattern, template_rect)
58
+
59
+ if match:
60
+ values_str = match.group(1) # Get the matched substring
61
+ values = [float(x) for x in values_str.split(',')] # Convert to floats
62
+
63
+ # Create a QRectF object
64
+ template_rect = QRectF(*values)
65
+ # print(template_rect)
66
+ except:
67
+ pass
68
+ else:
69
+ # print('no template rect')
70
+ pass
71
+
72
+ self.lower_indices = None
73
+ self.upper_indices = None
74
+ self.lower_values = None
75
+ self.upper_values = None
76
+ # maybe allow fix hot pixels error -−> TODO
77
+ # self.min=min # will store the normalization min
78
+ # self.max=max # will store the normalization max # nB normalization is mandatory for 16bits images and probably recommended for LUTs -> see how I should handle that
79
+ self.projection = projection # to store if a max proj or alike should be made (I could in the future store first and last z for the projection)
80
+ self.normalization = normalization # this will contain all the normalization infos as a dict
81
+ self.isText = isText
82
+ if isinstance(self.normalization, str):
83
+ try:
84
+ self.normalization = ast.literal_eval(self.normalization)
85
+ except:
86
+ pass
87
+ self.channels = channels # will store the channel state (active or not)
88
+ if isinstance(self.channels, str):
89
+ try:
90
+ self.channels = ast.literal_eval(self.channels)
91
+ except:
92
+ pass
93
+
94
+ # print('inputed svchannels', self.channels)
95
+ # print('inputed svchannelsb', channels)
96
+
97
+ self.LUTs = LUTs # will store the user defined LUTs
98
+ if isinstance(self.LUTs, str):
99
+ try:
100
+ self.LUTs = ast.literal_eval(self.LUTs)
101
+ except:
102
+ pass
103
+ self.dimensions = dimensions
104
+ if isinstance(self.dimensions, str):
105
+ try:
106
+ self.dimensions = ast.literal_eval(self.dimensions)
107
+ except:
108
+ pass
109
+
110
+
111
+
112
+
113
+ self.script_log = script_log # a log of the screen so that the user can see and read the errors of their script -−> can be very useful for debug
114
+ self.extra_user_translation = extra_user_translation
115
+ self.px_to_unit_conversion_factor=px_to_unit_conversion_factor
116
+ self.user_comments = user_comments # contains some useful hints for the person that made the figure
117
+ # if this stuff is set then allow to load the image using that -−> TODO
118
+ self.qimage = None
119
+ self.custom_loading_script = custom_loading_script # TODO implement that --> should assign to self.img smthg that I can handle -−> either a raster or a svg or whatever # par exemple pr loader les certains frames d'une image -−> qq chose de facile à faire
120
+
121
+ # crops
122
+ self.crop_left = crop_left
123
+ self.crop_right = crop_right
124
+ self.crop_top = crop_top
125
+ self.crop_bottom = crop_bottom
126
+ self.img = None
127
+ self.line_style = None # useless yet just for compat
128
+ if not annotations: # DEV NOTE KEEP WEIRD SERIALIZATION CODE IF I DONT DO THAT --> SO KEEP --> IF I INITITATE annotations = [] in the init then nested images in annotations cause an infinite loop in serialization, no clue why though
129
+ annotations = []
130
+ if isinstance(annotations, dict): # deprecated --> maybe remove that because it was successfully changed
131
+ annotations=create_objects_from_dict(annotations)
132
+ elif isinstance(annotations, list) and len(annotations)>0:
133
+ if isinstance(annotations[0], dict):
134
+ annotations = create_objects_from_dict(annotations)
135
+
136
+ self.annotations=annotations
137
+ if isinstance(placement, str):
138
+ placement = Position(placement)
139
+ self.placement=placement
140
+ self.renderer = None # replaced all of these by self.img
141
+
142
+ # if the image is inserted as an inset then draw it as a fraction of parent width
143
+ # inset parameters
144
+ self.fraction_of_parent_image_width_if_image_is_inset = fraction_of_parent_image_width_if_image_is_inset
145
+ self.border_size = border_size # no border by default
146
+ self.border_color = border_color # white border by default
147
+ self.filename = filename
148
+
149
+ if not args and filename:
150
+ args = [filename]
151
+
152
+ if args:
153
+ if isinstance(args[0], str):
154
+ self.filename = args[0]
155
+ elif isinstance(args[0], Img):
156
+ self.filename = None
157
+ self.img = args[0]
158
+ # self.qimage = toQimage(self.img)
159
+ self.update_qimage()
160
+ if x is None:
161
+ x = 0
162
+ if y is None:
163
+ y = 0
164
+
165
+ w,h = self.get_raw_size()
166
+ # try:
167
+ super(Image2D, self).__init__(x, y, w, h)
168
+ # except:
169
+ # super(Image2D, self).__init__(x, y, self.img.shape[1], self.img.shape[0])
170
+ elif isinstance(args[0], plt.Figure):
171
+ # the passed image is in fact a matplotlib plot --> init super with the corresponding QrectF
172
+ # super(Image2D, self).__init__(get_fig_rect(args[0]))
173
+ self.img = args[0]
174
+ w,h = self.get_raw_size()
175
+ super(Image2D, self).__init__(0,0,w,h)
176
+ self.setFigure(self.img)
177
+ elif isinstance(args[0], np.ndarray):
178
+ # the only pb of that is that it cannot be reloaded -−> rather recommend the user to use scripts
179
+ self.img=args[0]
180
+ # self.qimage = toQimage(self.img)
181
+ self.update_qimage()
182
+
183
+ w,h = self.get_raw_size()
184
+ # try:
185
+ # super(Image2D, self).__init__(0,0, self.img.get_width(), self.img.get_height())
186
+ super(Image2D, self).__init__(0,0, w, h)
187
+ # except:
188
+ # super(Image2D, self).__init__(0,0, self.img.shape[1], self.img.shape[0])
189
+
190
+ if x is None and y is None and width is not None and height is not None and self.filename is None:
191
+ super(Image2D, self).__init__(0, 0, width, height)
192
+ # self.img = QRectF(0, 0, width, height) # hack to fake an image... to allow crop and alike to always work
193
+ # if not template_rect:
194
+ # self.img = QRectF(0, 0, width, height) # hack to fake an image... to allow crop and alike to always work
195
+ # else:
196
+ # self.img = template_rect
197
+ elif self.filename is not None: # and not isinstance(self.filename, TAText2D):
198
+ # handle svg files first
199
+ if self.filename.lower().endswith('.svg'):
200
+ super(Image2D, self).__init__(0, 0, 512, 512) # fake definition
201
+ self.setFigure(self.filename) #real loading of the parameters
202
+ else:
203
+ try:
204
+ self.img = Img(self.filename)
205
+ # self.qimage = toQimage(self.img)
206
+ self.update_qimage()
207
+ # width = self.qimage.width()
208
+ # height = self.qimage.height()
209
+
210
+ # print('width height defined here127', width, height, self.filename)
211
+ w,h = self.get_raw_size()
212
+ super(Image2D, self).__init__(0, 0, w, h)
213
+ except:
214
+ if not self.custom_loading_script:
215
+ # if the file canot be opened and there is no script --> then there is chance that it works
216
+ if not self.filename == '@template@':
217
+ logger.error('could not load image '+str(self.filename))
218
+ if not allow_unknown_files:
219
+ return
220
+ else:
221
+ # define a default image with 512x512 and display error on top of it
222
+ # in fact this can be an empty image
223
+ self.img = None # this is the indication that an error occurred and that I cannot do much
224
+ super(Image2D, self).__init__(0, 0, 512, 512)
225
+ if x is not None:
226
+ self.setX(x)
227
+ if y is not None:
228
+ self.setY(y)
229
+ if width is not None:
230
+ self.setWidth(width)
231
+ if height is not None:
232
+ self.setHeight(height)
233
+ elif x is not None and y is not None and width is not None and height is not None and self.img is None:
234
+ self.img = None
235
+ super(Image2D, self).__init__(x, y, width, height)
236
+ elif self.filename is not None: # and not isinstance(self.filename, TAText2D):
237
+ self.img = Img(self.filename)
238
+ # self.qimage = toQimage(self.img)
239
+ self.update_qimage()
240
+ if x is None:
241
+ x = 0
242
+ if y is None:
243
+ y = 0
244
+ w,h = self.get_raw_size()
245
+ # super(Image2D, self).__init__(x, y, self.img.get_width(), self.img.get_height())
246
+ super(Image2D, self).__init__(x, y, w,h)
247
+
248
+ # try a ultimate rescue if there was a script
249
+ if custom_loading_script:
250
+
251
+ # why is image None there
252
+ self.execute_code_unsafe(custom_loading_script) # MEGA TODO --> maybe I need to log the script error and put it somewhere in a widget -−>
253
+ # print('entering custom loading script', self.img, self.script_log)
254
+ if self.script_log:
255
+ print('Script output:\n',self.script_log)
256
+ if custom_loading_script and self.img is not None:
257
+ # TODO --> avoid code duplication!!! --> TODO -> split my code into plenty of stuff
258
+ if isinstance(self.img, plt.Figure):
259
+ super(Image2D, self).__init__(get_fig_rect(self.img))
260
+ self.setFigure(self.img)
261
+ if isinstance(self.img, np.ndarray):
262
+ # print('setting new stuff here')
263
+ # self.qimage = toQimage(self.img)
264
+ self.update_qimage()
265
+ w, h = self.get_raw_size()
266
+ # self.setWidth(self.qimage.width())
267
+ # self.setHeight(self.qimage.height())
268
+ # self.setWidth(w)
269
+ # self.setHeight(h)
270
+ super(Image2D, self).__init__(0,0, w, h)
271
+ try:
272
+ self.__init_called__
273
+ except:
274
+ if not allow_unknown_files:
275
+ raise Exception('Image not initialized properly!, super-class __init__() was never called')
276
+ else:
277
+ super(Image2D, self).__init__(0, 0, 512, 512) # create an error image maybe the custom code can be edited later?
278
+ # self.img=QRectF(0, 0, 512, 512)
279
+ if not template_rect:
280
+ self.img = QRectF(0, 0, 512,
281
+ 512) # hack to fake an image... to allow crop and alike to always work
282
+ else:
283
+ self.img = template_rect
284
+
285
+ # THIS SHOULD ALWAYS BE AT THE VERY END OTHERWISE SUPER OVERRIDES THE VALUES --> PROBABLY NEED TO DO IT IN A SMARTER WAY BUT OK FOR NOW !!!!
286
+ self.opacity = 1
287
+ self.scale = scale
288
+ self.translation = QPointF()
289
+ self.fill_color = fill_color # can be used to define a bg color for rotated images (and or transparent images...)
290
+ self.theta = theta
291
+
292
+
293
+ if self.is_template(): # if image is converted to template then we must remove the conversion factor (maybe make this an option some day but I think it makes sense)
294
+ self.px_to_unit_conversion_factor=0 # be sure to reset it if in a template
295
+
296
+ if not self.px_to_unit_conversion_factor:
297
+ # try get it from the image
298
+ self.get_px_to_unit_conversion_factor_from_metadata()
299
+
300
+
301
+
302
+ if template_rect:
303
+ if self.img is None or isinstance(self.img, QRectF):
304
+ self.img = template_rect
305
+ self.__version__ = __version__
306
+
307
+ def get_px_to_unit_conversion_factor_from_metadata(self):
308
+ try:
309
+ self.px_to_unit_conversion_factor = self.img.metadata['vx']
310
+ except:
311
+ self.px_to_unit_conversion_factor = 0
312
+ # print()
313
+
314
+ def clip_by_frequency(self, lower_cutoff=None, upper_cutoff=0.05, channel_mode=True):
315
+ '''
316
+ Clips the values of an image based on frequency cutoffs.
317
+
318
+ Args:
319
+ img (ndarray): Input image.
320
+ lower_cutoff (float, optional): Lower frequency cutoff.
321
+ upper_cutoff (float, optional): Upper frequency cutoff.
322
+ channel_mode (bool, optional): Specifies whether to perform clipping on individual channels.
323
+
324
+ Returns:
325
+ ndarray: Clipped image.
326
+ '''
327
+
328
+ logger.debug(' inside clip ' + str(lower_cutoff) + str(upper_cutoff) + str(channel_mode))
329
+
330
+ # Check if all cutoff values are 0 or None, in which case the image remains unchanged.
331
+ if lower_cutoff == upper_cutoff == 0 or lower_cutoff == upper_cutoff == None:
332
+ logger.debug('clip: keep image unchanged')
333
+ return img
334
+
335
+ # Check if either lower_cutoff or upper_cutoff is None while the other is 0, in which case the image remains unchanged.
336
+ if (lower_cutoff is None and upper_cutoff == 0) or (upper_cutoff is None and lower_cutoff == 0):
337
+ logger.debug('clip: keep image unchanged')
338
+ return img
339
+
340
+ logger.debug('chan mode ' + str(channel_mode))
341
+
342
+ # If the maximum and minimum values of the image are the same, return the image as is.
343
+ if img.max() == img.min():
344
+ return img
345
+
346
+ # If channel_mode is True, perform clipping on individual channels.
347
+ if channel_mode:
348
+ for ch in range(img.shape[-1]):
349
+ img[..., ch] = self.clip_by_frequency(img[..., ch], lower_cutoff=lower_cutoff, upper_cutoff=upper_cutoff,
350
+ channel_mode=False)
351
+ return img
352
+
353
+ logger.debug('Removing image outliers/hot pixels')
354
+
355
+ # Compute the maximum value for clipping based on the upper_cutoff.
356
+ if upper_cutoff is not None:
357
+ max = np.percentile(img, 100. * (1. - upper_cutoff))
358
+ img[img > max] = max
359
+
360
+ # Compute the minimum value for clipping based on the lower_cutoff.
361
+ if lower_cutoff is not None:
362
+ min = np.percentile(img, 100. * lower_cutoff)
363
+ img[img < min] = min
364
+
365
+ return img
366
+
367
+ def update_qimage(self, reset_outlier_pos=False):
368
+ # MEGA TODO NB SOME OF THE PARAMTERS PROBABLY NEED NOT BE CHANGED UNTIL IMAGE IS CHANGED --> SPLIT THIS IN INIT_IMAGE AND UPDATE_IMAGE AND CALL THE APPROPRIATE for example the bins/histo/min/max and so on need not be redone
369
+
370
+ # TODO --> merge all so that I do not have to create yet another layer ??? -−> TODO
371
+ # shall I use views to avoid memory overload !!!
372
+ # TODO --> try to finalize all!!!
373
+
374
+ # print('%%%'*32)
375
+ # print('neo parameters', self.normalization, self.projection)
376
+ # self.channels = 1
377
+
378
+ # if reset_outlier_pos:
379
+ # self.remove_outliers()
380
+ # self.reset_outliers()
381
+
382
+ # print('MEGA DEBUGGING',self.img.max())
383
+ if __DEBUG_NORMALIZATION__:
384
+ print('#'*20+'ENTERING UPDATE QIMAGE'+'#'*20)
385
+
386
+ if not isinstance(self.img, np.ndarray):
387
+ self.qimage = None
388
+ # now I need to get the max and min per channel maybe if per channel is on
389
+ return
390
+
391
+ if True:
392
+ # print('I am inside')
393
+ per_channel = False
394
+ if self.normalization is not None:
395
+ if __DEBUG_NORMALIZATION__:
396
+ print('called')
397
+ if self.normalization is not None:
398
+ if __DEBUG_NORMALIZATION__:
399
+ print('called 2')
400
+ if 'per_channel' in self.normalization:
401
+ per_channel = self.normalization['per_channel']
402
+
403
+ if __DEBUG_NORMALIZATION__:
404
+ print('called 3')
405
+ if 'ignore_hot_spots' in self.normalization and self.normalization['ignore_hot_spots'] is not None and self.normalization['ignore_hot_spots'] is not False:
406
+ # print('TODO -−> NOW I NEED TO CHANGE THE NORMALIZATION -−> ACTION REQUIRED')
407
+ # # pass
408
+ if __DEBUG_NORMALIZATION__:
409
+ print('called 4')
410
+ # # self.reset_outliers()
411
+ # print('called 5')
412
+ #
413
+ # print('removing outliers')
414
+
415
+ self.remove_outliers(per_channel=per_channel)
416
+ else:
417
+ if __DEBUG_NORMALIZATION__:
418
+ print('called 6')
419
+ pass
420
+ # self.restore_outliers()
421
+ # self.reset_outliers()
422
+ else:
423
+ if __DEBUG_NORMALIZATION__:
424
+ print('called 7')
425
+ pass
426
+ # self.restore_outliers()
427
+ # self.reset_outliers()
428
+
429
+ # print('MEGA DEBUGGING3', self.img.max())
430
+
431
+
432
+ # maybe I could integrate in there the rotation (maybe also crop) and all alike stuff and max proj and also the
433
+ image_to_display = self.img
434
+
435
+
436
+
437
+
438
+ target_dims = guess_dimensions(self.img)
439
+ # original_dims=target_dims
440
+
441
+ # print('super init target_dims',target_dims)
442
+ proj_letter = find_letter_before_h(target_dims)
443
+
444
+
445
+
446
+ # parse the image if needed
447
+ if isinstance(self.dimensions, dict):
448
+ # print('checking order',self.dimensions['order'])
449
+ # print('checking values',self.dimensions['values'])
450
+ if len(self.dimensions['order'])!=len(self.img.shape) or target_dims!=self.dimensions['order']:
451
+ print('dimension mismatch between dimensions and current image -−> ignoring')
452
+ else:
453
+ for ddd,dim_to_chose in enumerate(self.dimensions['values']):
454
+ if target_dims[ddd] == proj_letter and self.projection: # we keep the letter for projection if projection is selected
455
+ # if there is a dim dimension and the user wants a projection --> do not allow dimension reduction!!!
456
+ continue
457
+ if dim_to_chose>=0 and dim_to_chose<self.img.shape[ddd]:
458
+ image_to_display = image_to_display[dim_to_chose]
459
+ else:
460
+ # print('invalid dimension specified assuming image has changed and resetting the value')
461
+ image_to_display = image_to_display[0]
462
+
463
+ if len(target_dims) > len(image_to_display.shape):
464
+ target_dims = target_dims[len(target_dims)-len(image_to_display.shape):]
465
+
466
+ # print('final redndering',image_to_display.shape)
467
+ # print('checking img', image_to_display.shape, 'vs', self.img.shape)
468
+ # if self.channels is not None and not self.channels == 'merge' and 'c' in target_dims:
469
+ # if self.channels>=0 and self.channels < self.img.shape[-1]:
470
+ # image_to_display=image_to_display[...,self.channels]
471
+ # # image_to_display=image_to_display[...,np.newaxis] # do this to avoid losing dimension
472
+ # target_dims = target_dims.replace('c','')
473
+
474
+ # print('user defined Luts here', self.LUTs)
475
+
476
+ # print('checking img', image_to_display.shape)
477
+ # shall I allow LUTS for non channels images −−> if so add them a channel before that -−> but then need add a dimension
478
+
479
+ if False:
480
+ # try add a channel but somehow that creates a bug --> probably do not use that!!!
481
+ if not 'c' in target_dims:
482
+ image_to_display = image_to_display[..., np.newaxis]
483
+ target_dims+='c'
484
+
485
+
486
+ if self.channels and 'c' in target_dims:
487
+ # print('inside channels sel self.channels', self.channels)
488
+ list_of_true_indices = bool_list_to_index_list(self.channels)
489
+ # print('list_of_true_indices',list_of_true_indices)
490
+ if list_of_true_indices:
491
+ image_to_display=image_to_display[...,list_of_true_indices] # that is funny the behaviour is different as a list keeps the last channel even if unique whererad the int removes it!!!!
492
+ else:
493
+ list_of_true_indices = [i for i,_ in enumerate(self.channels)] # if no index --> force select all for the LUTs --> make sens
494
+ # print('list_of_true_indices fixed', list_of_true_indices)
495
+ if image_to_display.shape[-1]==1:
496
+ target_dims = target_dims.replace('c', '') # force it to be gray # so why is it colored this time ???
497
+ image_to_display=image_to_display[...,0] # needed to remove the channel and the color # see how I can handle the LUts naow and do a serializable LUT object
498
+ # print('removing c')
499
+
500
+
501
+ # print('image_to_display after channel selection',image_to_display.shape)
502
+
503
+ luts = None
504
+ # if 'c'in original_dims:
505
+ if True:
506
+ if __DEBUG_NORMALIZATION__:
507
+ print('entering Lut1')
508
+ # keep existing LUTs if they exist !!!
509
+ try:
510
+ if hasattr(self.img,'metadata') and 'LUTs' in self.img.metadata:
511
+ if self.img.metadata['LUTs']:
512
+ # print('checking luts',self.img.metadata['LUTs'])
513
+ # print('checking luts',len(self.img.metadata['LUTs']))
514
+ luts=self.img.metadata['LUTs']
515
+ # print(type(luts[0]))
516
+ # print('shape lut', luts[0].shape) # (3, 256)
517
+ # Use a list comprehension to select only the specified elements
518
+ if self.channels:
519
+ # luts = [luts[i] if channel_state else for i, channel_state in enumerate(self.channels)] # if there are LUTs they are applied --> I could also swap these LUts for the user chosen ones if any
520
+ # tmp = []
521
+ # for sss, state in enumerate(self.channels):
522
+ # if state:
523
+ # tmp.append(luts[sss])
524
+ # luts=tmp
525
+ tmp = [lut for lut, state in zip(luts, self.channels) if state] # we only get the luts for active states
526
+ if not tmp:
527
+ pass
528
+ else:
529
+ luts=tmp
530
+ else:
531
+ luts = luts
532
+ # I would need a LUT button and also to serialize the LUts --> maybe make an object
533
+
534
+ # palette = None
535
+ # if True:
536
+ # # lut = self.lut_combo.currentText()
537
+ # lutcreator = PaletteCreator()
538
+ # luts_lst = lutcreator.list
539
+ # # lut = lutcreator.create3(luts[lut])
540
+ # try:
541
+ # # palette = lutcreator.create3(luts[0])
542
+ # palette = lutcreator.create3(luts_lst["RAINBOW_1"])# if LUT is from a string --> try save that, otherwise serialize the whole array in the sortest possible way
543
+ # # palette = lutcreator.create3("GRAY") #
544
+ # except:
545
+ #
546
+ # # palette = None
547
+ # # default to grey palette
548
+ # palette = lutcreator.create3(luts_lst['GRAY'])
549
+ #
550
+ # print('shape lut', luts[0].shape)
551
+ # print('palette',palette)
552
+ # if palette is not None:
553
+ # # ça marche je peux facilement remplacer une lut par ça en fait --> à faire -->
554
+ # palette = np.asarray(palette)
555
+ # print('shape pal', palette.shape)
556
+ # luts[0]=palette
557
+ except:
558
+ traceback.print_exc()
559
+ # I need to update the dims ???
560
+
561
+ # print('bobobob')
562
+ # print('self.LUTs',self.LUTs, 'fixed LUTs', len(luts) if luts else luts) #--> ok
563
+ # print('self.img.shape[-1]',self.img.shape[-1]) #--> ok
564
+
565
+ final_luts_to_apply = []
566
+ if luts:
567
+ final_luts_to_apply.extend(luts)
568
+ luts2 = []
569
+ if self.LUTs and self.img.shape[-1]==len(self.LUTs): # TODO --> check that
570
+ if __DEBUG_NORMALIZATION__:
571
+ print('entering Lut2')
572
+
573
+ # keep only the LUTs of the specified user channels
574
+ if self.channels:
575
+ # luts2 = [self.LUTs[i] for i, channel_state in enumerate(self.channels)] # if there are LUTs they are applied --> I could also swap these LUts for the user chosen ones if any
576
+ # luts2 = [self.LUTs[i] for lut, state in zip(luts, self.channels) if state] # we only get the luts for active states
577
+ luts2 = []
578
+ for i,state in enumerate(self.channels):
579
+ if state:
580
+ luts2.append(self.LUTs[i])
581
+ if not luts2: # nothing selected is the same as all is selected -−> fix all
582
+ luts2=self.LUTs
583
+ else:
584
+ luts2 = self.LUTs
585
+
586
+
587
+ if __DEBUG_NORMALIZATION__:
588
+ print('checking luts2', luts2)
589
+ # print('checking luts2', luts2) # vide
590
+ # print('checking final_luts_to_apply', len(final_luts_to_apply))
591
+ if luts2:
592
+ if not final_luts_to_apply:
593
+ final_luts_to_apply.extend(luts2)
594
+ else:
595
+ for l in range(len(final_luts_to_apply)):
596
+ final_luts_to_apply[l]=final_luts_to_apply[l] if luts2[l]==None else luts2[l]
597
+
598
+
599
+
600
+ luts=final_luts_to_apply
601
+ # print('###########')
602
+
603
+ # no in fact max and min should be obtained there !!!!
604
+
605
+ # print('checking img after', image_to_display.shape)
606
+ #
607
+ # print('target_dims',target_dims)
608
+ #
609
+ # print(len(target_dims), len(image_to_display.shape))
610
+
611
+ # TODO --> hack qimage so that if more than 3 channels and blend I blend each channel and then compute the sum or avg or max image of all the colors see how imageJ is doing and do the same!!!
612
+ # check my LUTs and finalize that!!!
613
+ self.qimage = None
614
+
615
+ # print('dabouyo')
616
+
617
+ # the real global max is there in fact -−> still I do not get it ???
618
+
619
+ # print('megrabouya', self.channels)
620
+ if __DEBUG_NORMALIZATION__:
621
+ print('megrabouya', self.channels)
622
+
623
+ # print('calling the prepare_image_for_qimage')
624
+ try:
625
+ self.prepare_image_for_qimage(image_to_display, target_dims, luts)
626
+ except:
627
+ traceback.print_exc()
628
+
629
+ if self.qimage is None:
630
+ # if __DEBUG_NORMALIZATION__:
631
+ print('failed --> ROLLING BACK')
632
+ # print('fixed target_dims', target_dims, image_to_display.shape)
633
+
634
+ self.qimage = toQimage(image_to_display, normalize=False, dimensions=target_dims, luts=luts)
635
+ # self.qimage = toQimage(image_to_display, dimensions=target_dims, luts=None)
636
+
637
+ if __DEBUG_NORMALIZATION__:
638
+ print('~'*30)
639
+
640
+ # print('restoring and resetting outliers',self.img.max())
641
+ self.restore_outliers()
642
+ self.reset_outliers()
643
+ # print(self.img.max())
644
+ # restore is ok but display is not
645
+
646
+ # print('~' * 30)
647
+
648
+ del image_to_display
649
+
650
+ # print('----------'*32)
651
+ # return qimage
652
+
653
+ def prepare_image_for_qimage(self, image_to_display, target_dims, luts, min=None, max=None, per_channel_normalization=False):
654
+ # here I will prepare the image to be dispalyed as a qimage --> it can handle normalization, etc...
655
+ # only keep from target_dims the hwc
656
+ # -> flatten before by taking 0
657
+
658
+ # we reduce the image until we have hwc left --> we stop at h unless we wanna do a max proj
659
+
660
+ if __DEBUG_NORMALIZATION__:
661
+ print('entering prepare_image_for_qimage')
662
+
663
+ # for normalization -−> global (whole stack), selected or projected, or custom defined by the user to some min and some max values
664
+ # --> TODO
665
+
666
+ # if image_to_display is None:
667
+ # return
668
+
669
+ list_of_true_indices = []
670
+ try:
671
+ list_of_true_indices = bool_list_to_index_list(self.channels)
672
+
673
+ # print('guess_dimensions(self.img)',self.channels, guess_dimensions(self.img),list_of_true_indices)
674
+
675
+ if not list_of_true_indices and 'c' in guess_dimensions(self.img):
676
+ list_of_true_indices = [i for i, _ in enumerate(self.channels)]
677
+
678
+ # print('list_of_true_indices', list_of_true_indices)
679
+ except:
680
+ pass
681
+
682
+
683
+ # print('inside prepare_image_for_qimage')
684
+
685
+ per_channel = False
686
+ if self.normalization is not None:
687
+ if 'per_channel' in self.normalization:
688
+ per_channel = self.normalization['per_channel']
689
+
690
+ global_max = self.img.max()
691
+ global_min = self.img.min()
692
+
693
+
694
+ # else:
695
+
696
+ # default normalization -−> my favourite one definitely
697
+ min = global_min
698
+ max = global_max
699
+ # I can also do it locally if there are several channels but then I need to keep only the channels ones!!!
700
+
701
+ # print('shape of img for local', image_to_display.shape)
702
+
703
+
704
+
705
+ # if self.projection:
706
+ # print('projection in prepare_image_for_qimage', self.projection)
707
+
708
+
709
+ extra_dims =target_dims.replace('h','').replace('w','').replace('c','')
710
+
711
+ # print('extra_dims',extra_dims)
712
+
713
+ # the d version for normalization should be here
714
+ # I haven't done the per channel but this could be made later too
715
+
716
+ final_target_dims = target_dims
717
+ extra_dim_length = len(extra_dims)
718
+
719
+ if extra_dims:
720
+ if __DEBUG_NORMALIZATION__:
721
+ print('extra_dims inside',extra_dims, image_to_display.shape)
722
+ for zzz,extra in enumerate(extra_dims):
723
+ if __DEBUG_NORMALIZATION__:
724
+ print('extra',extra)
725
+
726
+ # print('check',zzz, extra, extra_dim_length, zzz==extra_dim_length-1)
727
+
728
+ if self.projection and zzz==extra_dim_length-1:
729
+ if self.projection == 'max':
730
+ # print('running max proj on ', image_to_display.shape)
731
+ image_to_display = max_proj(image_to_display)
732
+ # print('running max proj on after ', image_to_display.shape)
733
+
734
+ # ça marche mais du coup pkoi pas de changement ????
735
+ elif self.projection == 'min':
736
+ image_to_display = min_proj(image_to_display)
737
+ else:
738
+ image_to_display = avg_proj(image_to_display)
739
+ else:
740
+ image_to_display=image_to_display[0]
741
+ final_target_dims=final_target_dims.replace(extra,'')
742
+
743
+ local_max = image_to_display.max()
744
+ local_min = image_to_display.min()
745
+
746
+ if self.normalization is not None:
747
+ if __DEBUG_NORMALIZATION__:
748
+ if self.normalization:
749
+ print(self.normalization, self.normalization['range'], self.normalization['min_value'], self.normalization['max_value'])
750
+ if 'range' in self.normalization and self.normalization['range'] == 'display':
751
+ min = local_min
752
+ max = local_max
753
+
754
+ if 'range' in self.normalization and self.normalization['range'] == 'custom':
755
+ min = self.normalization['min_value']
756
+ max = self.normalization['max_value']
757
+
758
+
759
+
760
+ if True:
761
+ # we perform the normalization here!!!
762
+
763
+ # print('image_to_display.max() before', image_to_display.max())
764
+ # print('image_to_display.min() before', image_to_display.min())
765
+
766
+ if __DEBUG_NORMALIZATION__:
767
+ print('used_min 1', min)
768
+ # print(image_to_display.shape)
769
+ print('used_max 1', max)
770
+
771
+ try:
772
+
773
+ if __DEBUG_NORMALIZATION__:
774
+ print('main entrance', min, max)
775
+ image_to_display = image_to_display.astype(float)
776
+
777
+ # print('image_to_display.max() after', image_to_display.max())
778
+ # print('image_to_display.min() after', image_to_display.min())
779
+
780
+
781
+
782
+ if not per_channel and not 'c' in target_dims: # global
783
+ if __DEBUG_NORMALIZATION__:
784
+ print('NEXT STEP', min, max, self.normalization) # was ok and then I fuck it
785
+ # try:
786
+ # print('NEXT STEP2a', self.normalization['range'])
787
+ # except:
788
+ # pass
789
+ if 'c' in guess_dimensions(self.img) and list_of_true_indices:
790
+ # case of a single channel yet global max and min required but for a single channel
791
+ # case there is a single channel selected --> yet using global max
792
+ # for cur_ch in list_of_true_indices:
793
+
794
+ if False:
795
+ max = self.img[..., list_of_true_indices[0]].max()
796
+ min = self.img[..., list_of_true_indices[0]].min()
797
+ else:
798
+ # dirty fix for display overwriting error
799
+ if not self.normalization or self.normalization['range']=='all':
800
+ max = self.img.max()
801
+ min = self.img.min()
802
+ else:
803
+ max = self.img.max()
804
+ min = self.img.min()
805
+
806
+ if __DEBUG_NORMALIZATION__:
807
+ print('NEXT STEP2', min, max)
808
+
809
+ if self.normalization is not None:
810
+ # print('entering')
811
+ if 'range' in self.normalization and self.normalization['range'] == 'custom':
812
+ min = self.normalization['min_value']
813
+ max = self.normalization['max_value']
814
+ if 'range' in self.normalization and self.normalization['range'] == 'all':
815
+ min = self.img.min()
816
+ max = self.img.max()
817
+ else:
818
+ min = self.img.min()
819
+ max = self.img.max()
820
+
821
+ if __DEBUG_NORMALIZATION__:
822
+ print('NEXT STEP3', min, max)
823
+ #
824
+ # print('selected max min non ch mode', self.img.shape, min, max, self, list_of_true_indices, target_dims, per_channel, not per_channel or not 'c' in target_dims)
825
+
826
+ if max == min:
827
+ # image_to_display = ((image_to_display - min) / (max - min + 1e-9)) * 255. # we just avoid div by 0
828
+ # if max !=0:
829
+ image_to_display.fill(0)
830
+ # else:
831
+ # image_to_display = image_to_display.fill(max)
832
+ else:
833
+ image_to_display = ((image_to_display - min) / (max - min)) * 255. # we just avoid div by 0
834
+ # to speed up the process use the other code
835
+ else:
836
+ if __DEBUG_NORMALIZATION__:
837
+ print('per_channel', min, max, list_of_true_indices, target_dims) # too many dims --> error
838
+ # print('list_of_true_indices',list_of_true_indices, self.channels, self.img.shape[-1],image_to_display.shape[-1], target_dims)
839
+
840
+ if not list_of_true_indices and 'c' in target_dims:
841
+ list_of_true_indices = [i for i in range(self.img.shape[-1])]
842
+
843
+ if __DEBUG_NORMALIZATION__:
844
+ print('vale before -1', min, max, list_of_true_indices)
845
+ if list_of_true_indices:
846
+ for ccc,ch in enumerate(list_of_true_indices):
847
+ if per_channel and self.normalization and self.normalization['range'] == 'display' and 'c' in target_dims:
848
+ max = image_to_display[...,ccc].max()
849
+ min = image_to_display[...,ccc].min()
850
+ if __DEBUG_NORMALIZATION__:
851
+ print('vale before 0', min, max)
852
+ if per_channel and ((self.normalization and self.normalization['range'] == 'all') or not self.normalization):
853
+ max = self.img[...,ch].max()
854
+ min = self.img[...,ch].min()
855
+
856
+ # bug is here
857
+ if __DEBUG_NORMALIZATION__:
858
+ print('vale before 1',min, max )
859
+
860
+ if self.normalization is not None:
861
+ if 'range' in self.normalization and self.normalization['range'] == 'custom':
862
+ min = self.normalization['min_value']
863
+ max = self.normalization['max_value']
864
+
865
+ if __DEBUG_NORMALIZATION__:
866
+ print('selected max min ch', ccc, 'vs',ch, self.img.shape,image_to_display.shape, min, max, self,list_of_true_indices, target_dims)
867
+ if max == min:
868
+ # image_to_display = ((image_to_display - min) / (max - min + 1e-9)) * 255. # we just avoid div by 0
869
+ # if max !=0:
870
+ if 'c' in target_dims:
871
+ image_to_display[...,ccc].fill(max)
872
+ else:
873
+ image_to_display.fill(0)
874
+ # else:
875
+ # image_to_display = image_to_display.fill(max)
876
+ else:
877
+ if 'c' in target_dims:
878
+ image_to_display[...,ccc] = ((image_to_display[...,ccc] - min) / (max - min)) * 255. # we just avoid div by 0
879
+ else:
880
+ image_to_display = ((image_to_display - min) / (max - min)) * 255. # we just avoid div by 0
881
+ # to speed up the process use the other code
882
+ else:
883
+ if __DEBUG_NORMALIZATION__:
884
+ print('no channel found --> normalization done independently')
885
+ image_to_display = ((image_to_display - min) / (max - min)) * 255.
886
+
887
+ if __DEBUG_NORMALIZATION__:
888
+ print('image_to_display.max() after2', image_to_display.max())
889
+ print('image_to_display.min() after2', image_to_display.min())
890
+
891
+ # print('image_to_display min max', image_to_display.min(), image_to_display.max())
892
+
893
+ np.clip(image_to_display, 0, 255, out=image_to_display) # do I need that probably only if the value iiiis less than global max
894
+ image_to_display = image_to_display.astype(np.uint8) # do I need that ???
895
+
896
+ if __DEBUG_NORMALIZATION__:
897
+ print('image_to_display.max() after3', image_to_display.max())
898
+ print('image_to_display.min() after3', image_to_display.min())
899
+ except:
900
+ traceback.print_exc()
901
+
902
+ if __DEBUG_NORMALIZATION__:
903
+ print('max-min exit', max - min)
904
+
905
+ # can do the same with the per_channel version −−> for which I should loop
906
+
907
+
908
+ # print('final_target_dims', final_target_dims, 'target_dims',target_dims)
909
+
910
+ # in fact if any projection needs be done it should be done here
911
+ # maybe I could also exclude some hot pixels in there --> see how I can do that
912
+
913
+ # target dim should be hw or hwc!!!
914
+ # try to apply LUT maybe here
915
+
916
+ # maybe here --> if no L
917
+
918
+ if luts:
919
+ if __DEBUG_NORMALIZATION__:
920
+ print('ENTERING LUTS', min, max)
921
+ # print('oubsi')
922
+ available_luts = list_available_luts()
923
+ for iii, lut in enumerate(luts):
924
+ if not isinstance(lut, np.ndarray):
925
+ # print('palette')
926
+
927
+ # else:
928
+ # print(lut)
929
+ lutcreator = PaletteCreator()
930
+ lut_list = lutcreator.list
931
+ # lut = lutcreator.create3(luts[lut])
932
+
933
+ # if isinstance(lut, list):
934
+ # print('to be fixed',lut) # [255, 0, 0, 0] --> convert this to a lut --> easy
935
+
936
+ if isinstance(lut, str):
937
+ if lut in available_luts:
938
+ try:
939
+ luts[iii] = np.asarray(lutcreator.create3(lut_list[lut]))
940
+ except:
941
+ traceback.print_exc()
942
+ luts[iii] = None
943
+ elif isinstance(lut, list) and len(lut)==4:
944
+ luts[iii]=lsm_LUT_to_numpy(lut)
945
+ else:
946
+ if __DEBUG_NORMALIZATION__:
947
+ print('FAILEUR NO LUT CASE', min, max)
948
+ pass
949
+
950
+
951
+ merge = None
952
+ if 'c' in target_dims:
953
+ if __DEBUG_NORMALIZATION__:
954
+ print('ENTERING LAST C LOOP', min, max)
955
+ # print('oubsi2')
956
+
957
+ list_of_true_indices = []
958
+ try:
959
+ list_of_true_indices = bool_list_to_index_list(self.channels)
960
+ # print('list_of_true_indices', list_of_true_indices)
961
+ if not list_of_true_indices:
962
+ list_of_true_indices = [i for i, _ in enumerate(self.channels)]
963
+ except:
964
+ pass
965
+ # apply LUT if there is a LUT
966
+
967
+ # why do I loop this 4 times whereas I just want one ???
968
+ for ch in range(image_to_display.shape[-1]):
969
+ if __DEBUG_NORMALIZATION__:
970
+ print('channel', ch, image_to_display.shape)
971
+
972
+ img = image_to_display[...,ch]
973
+
974
+ # print('img slice shape', img.shape)
975
+
976
+
977
+
978
+
979
+
980
+ # local_min = img.min()
981
+ # local_max = img.max()
982
+
983
+
984
+
985
+
986
+ # we normalize the images always in the same way and as defined by the user
987
+ # if max!=min:
988
+ # img = ((img-min)/(max-min+1e-9))*255. # we just avoid div by 0
989
+ # img = np.clip(img, 0, 255).astype(np.uint8)
990
+ # I can also use the global if wanted
991
+
992
+ # I need to get the mode for the normalization
993
+
994
+
995
+ # print('checks',luts, image_to_display.shape[-1]==len(luts), image_to_display.shape[-1],len(luts))
996
+
997
+
998
+
999
+ if luts is not None and image_to_display.shape[-1]==len(luts) and luts[ch] is not None:
1000
+
1001
+ if __DEBUG_NORMALIZATION__:
1002
+ print('entering LUT finalization')
1003
+ # print('oubsi3')
1004
+ # img = ((img-min)/(max-min))*255.
1005
+ # img = np.clip(img, 0,255) # force all stuff that need a lut clip to something like that
1006
+ # img = img.astypr(np.uint8)
1007
+ #
1008
+ # now I apply the LUT
1009
+
1010
+ # print('ch inside', ch, len(luts), luts[ch].shape)
1011
+
1012
+ # img = apply_lut(img, luts[ch], convert_to_RGB=True, min=min, max=max)
1013
+ img = apply_lut(img, luts[ch], convert_to_RGB=True, do_not_interpolate=True) # is it renormalizing here --> maybe I should prevent it
1014
+
1015
+ # img=luts[ch][img]
1016
+
1017
+
1018
+ # print('oubsi3-2', img.shape)
1019
+
1020
+ # plt.close('all')
1021
+ # plt.imshow(img)
1022
+ # plt.show()
1023
+
1024
+
1025
+
1026
+ else:
1027
+
1028
+ if __DEBUG_NORMALIZATION__:
1029
+ print('SKIPPING LUT')
1030
+ # print('dabouyo')
1031
+ # print('img.shape', img.shape)
1032
+ # print('*img.shape',*img.shape)
1033
+ # print('(*img.shape, 3)',(*img.shape, 3))
1034
+
1035
+ # I still need to normalize the image in there !!!
1036
+
1037
+ try:
1038
+ real_ch = list_of_true_indices[ch]
1039
+ except:
1040
+ real_ch=ch
1041
+ tmp = np.zeros(shape=(*img.shape, 3), dtype=np.uint8)
1042
+
1043
+ if __DEBUG_NORMALIZATION__:
1044
+ print('dabouyo2')
1045
+ if real_ch < tmp.shape[-1]:
1046
+ tmp[..., real_ch] = img
1047
+ else:
1048
+ # print('too many channels to make any sense anyway --> setting to white')
1049
+ tmp[..., 0] = img
1050
+ tmp[..., 1] = img
1051
+ tmp[..., 2] = img
1052
+ # otherwise I would need to apply LUTs in a specified order -−> and maybe loop
1053
+ # or worst case scenario --> do it white
1054
+
1055
+ # print('dabouyo3')
1056
+ img = tmp.astype(np.uint8)
1057
+ # print('dabouyo4')
1058
+
1059
+ # print('dabouyo end of shape', img.shape)
1060
+
1061
+
1062
+ # I also need to normalize the image like the others --> see how to do that
1063
+
1064
+
1065
+
1066
+
1067
+ # print('snip')
1068
+ if merge is None:
1069
+ # print('in there')
1070
+ merge = img
1071
+ # print('merge after init', merge.shape)
1072
+ else:
1073
+ # print('in there2')
1074
+
1075
+
1076
+ # if not merge.dtype == i
1077
+
1078
+ merge = np.maximum(merge, img)
1079
+ # print('snap')
1080
+ # print('merge end of loop', merge.shape)
1081
+
1082
+ # print(merge)
1083
+
1084
+
1085
+ # WHY THE FUCK IS THIS NEVER REACHED !!!!
1086
+ # print('snip2')
1087
+ # if merge is not None:
1088
+ # print('inside da stuff', merge.shape)
1089
+ # else:
1090
+ # print('not merged')
1091
+ # print('snap2')
1092
+
1093
+
1094
+
1095
+ self.qimage = toQimage(merge, normalize=False,dimensions=final_target_dims, luts=None)
1096
+ # self.qimage = toQimage(merge, dimensions=None, luts=None)
1097
+
1098
+ if __DEBUG_NORMALIZATION__:
1099
+ print('SUCCESS!!!')
1100
+ # else:
1101
+ #
1102
+ # self.qimage = toQimage(image_to_display, normalize=False, dimensions=final_target_dims, luts=None)
1103
+
1104
+ # if lut needs be applied and image is not normalized then need normalize it
1105
+ else:
1106
+ if __DEBUG_NORMALIZATION__:
1107
+ print('ENTERING LAST C LOOP ALTERNATIVE', min, max)
1108
+ # I still need to apply the lut if it exists
1109
+ # no channels -−> we can still apply the LUT
1110
+ if self.LUTs:
1111
+ if __DEBUG_NORMALIZATION__:
1112
+ print('ENTERING LAST C LOOP ALTERNATIVE part1', min, max, len(self.LUTs))
1113
+ # code duplication --> shall I really do that ??? --> maybe have a force gray that makes it gray instead -− >better
1114
+ luts=[]
1115
+ available_luts = list_available_luts()
1116
+ for iii, lut in enumerate(self.LUTs):
1117
+ if not isinstance(lut, np.ndarray):
1118
+ # print('palette')
1119
+ # else:
1120
+ # print(lut)
1121
+ lutcreator = PaletteCreator()
1122
+ lut_list = lutcreator.list
1123
+ # lut = lutcreator.create3(luts[lut])
1124
+ if lut in available_luts:
1125
+ try:
1126
+ luts.append(np.asarray(lutcreator.create3(lut_list[lut])))
1127
+ except:
1128
+ traceback.print_exc()
1129
+ # luts[iii] = None
1130
+ luts.append(None)
1131
+ else:
1132
+ luts.append(None)
1133
+ else:
1134
+ luts.append(lut)
1135
+ if luts:
1136
+
1137
+ if __DEBUG_NORMALIZATION__:
1138
+ print('THERE IS A LUT',len(luts)) # there is a bug in the the right lut is not selected
1139
+ list_of_true_indices = []
1140
+ try:
1141
+ list_of_true_indices = bool_list_to_index_list(self.channels)
1142
+ # print('list_of_true_indices', list_of_true_indices)
1143
+ if not list_of_true_indices:
1144
+ list_of_true_indices = [i for i, _ in enumerate(self.channels)]
1145
+ except:
1146
+ pass
1147
+
1148
+
1149
+ # image_to_display=luts[0][image_to_display]
1150
+ # just need the mapped lut else lut[0]
1151
+ # try:
1152
+ # image_to_display = apply_lut(image_to_display,luts[list_of_true_indices[0]], convert_to_RGB=True)
1153
+ # except:
1154
+ # I need really get the right index
1155
+
1156
+ # print('I am called with index 0', self.channels, list_of_true_indices)
1157
+
1158
+ # there is a bug there if the image has multichannels I need get the real index of the selected channel --> this will not be 0
1159
+ if list_of_true_indices: # seems to be required for proper mapping of LUTs for single channel images but maybe check
1160
+
1161
+
1162
+ # print('mode1')
1163
+ try:
1164
+ if __DEBUG_NORMALIZATION__:
1165
+ print('mode2', list_of_true_indices, len(luts)) # there are two luts --> see
1166
+ image_to_display = apply_lut(image_to_display, luts[list_of_true_indices[0]],
1167
+ convert_to_RGB=True, do_not_interpolate=True)
1168
+ except:
1169
+ if __DEBUG_NORMALIZATION__:
1170
+ print('mode3')
1171
+ # try:
1172
+ # image_to_display = apply_lut(image_to_display, luts[0],
1173
+ # convert_to_RGB=True, do_not_interpolate=True)
1174
+ # except:
1175
+ # print('mode3b')
1176
+ # pass
1177
+ pass
1178
+ else:
1179
+ if __DEBUG_NORMALIZATION__:
1180
+ print('mode4')
1181
+ image_to_display = apply_lut(image_to_display, luts[0],
1182
+ convert_to_RGB=True, do_not_interpolate=True)
1183
+
1184
+ # img = lut[ch][img]
1185
+ # print('inside da stuff2', image_to_display.shape)
1186
+ # self.qimage = toQimage(image_to_display, dimensions=final_target_dims,luts=luts)
1187
+
1188
+ # print('passing there', image_to_display.shape,self.qimage , min, max)
1189
+ # so this means
1190
+
1191
+ self.qimage = toQimage(image_to_display, normalize=False,dimensions=final_target_dims,luts=None)
1192
+
1193
+ if __DEBUG_NORMALIZATION__:
1194
+ print('out final', image_to_display.shape, image_to_display.max(),image_to_display.min(), min, max, self.qimage) # min and max are incorrect too
1195
+ # else just return stuff !!!!
1196
+ del image_to_display
1197
+
1198
+ def remove_outliers(self, lower_cutoff=None, upper_cutoff=0.001, per_channel=False):
1199
+
1200
+ try:
1201
+ # print('#-'*32)
1202
+ self.restore_outliers()
1203
+
1204
+ if not 'c' in guess_dimensions(self.img):
1205
+ per_channel=False
1206
+
1207
+ if per_channel and isinstance(self.img, np.ndarray) and 'c' in guess_dimensions(self.img):
1208
+ self.lower_indices=[]
1209
+ self.upper_indices=[]
1210
+ self.lower_values=[]
1211
+ self.upper_values=[]
1212
+
1213
+ if not lower_cutoff and not upper_cutoff:
1214
+ return
1215
+
1216
+ if self.img.max() == self.img.min():
1217
+ return
1218
+
1219
+ for ch in range(self.img.shape[-1]):
1220
+ one_channel_img = self.img[...,ch]
1221
+
1222
+ if one_channel_img.max()==one_channel_img.min():
1223
+ self.lower_indices.append(None)
1224
+ self.upper_indices.append(None)
1225
+ self.lower_values.append(None)
1226
+ self.upper_values.append(None)
1227
+ continue
1228
+
1229
+ if upper_cutoff:
1230
+ # Find indices of values above upper_threshold
1231
+ max = np.percentile(one_channel_img, 100. * (1. - upper_cutoff))
1232
+ self.upper_indices.append(np.where(one_channel_img > max))
1233
+ # Store original values
1234
+ self.upper_values.append(one_channel_img[self.upper_indices[-1]])
1235
+ # Replace outliers with placeholders
1236
+ one_channel_img[self.upper_indices[-1]] = max
1237
+
1238
+ if lower_cutoff:
1239
+ # Find indices of values below lower_threshold
1240
+ min = np.percentile(img, 100. * lower_cutoff)
1241
+ self.lower_indices.append(np.where(one_channel_img < min))
1242
+ # Store original values
1243
+ self.lower_values.append(one_channel_img[self.lower_indices[-1]])
1244
+ # Replace outliers with placeholders
1245
+ one_channel_img[self.lower_indices[-1]] = min
1246
+
1247
+ self.img[..., ch]=one_channel_img
1248
+ else:
1249
+ self.lower_indices = None
1250
+ self.upper_indices = None
1251
+ self.lower_values = None
1252
+ self.upper_values = None
1253
+
1254
+ if not lower_cutoff and not upper_cutoff:
1255
+ return
1256
+
1257
+ if self.img.max() == self.img.min():
1258
+ return
1259
+
1260
+ if upper_cutoff:
1261
+ # Find indices of values above upper_threshold
1262
+ max = np.percentile(self.img, 100. * (1. - upper_cutoff))
1263
+ self.upper_indices = np.where(self.img > max)
1264
+ # Store original values
1265
+ self.upper_values = self.img[self.upper_indices]
1266
+ # Replace outliers with placeholders
1267
+ self.img[self.upper_indices] = max
1268
+
1269
+ # print('gripsous', max)
1270
+
1271
+
1272
+ if lower_cutoff:
1273
+ # Find indices of values below lower_threshold
1274
+ min = np.percentile(img, 100. * lower_cutoff)
1275
+ self.lower_indices = np.where(self.img < min)
1276
+ # Store original values
1277
+ self.lower_values = self.img[self.lower_indices]
1278
+ # Replace outliers with placeholders
1279
+ self.img[self.lower_indices] = min
1280
+
1281
+ # print('-+' * 32)
1282
+ except:
1283
+ pass
1284
+
1285
+
1286
+ # return self.lower_indices, self.lower_values, self.upper_indices, self.upper_values
1287
+
1288
+ def restore_outliers(self):
1289
+ try:
1290
+ # print('in restore_outliers ', self.img.max())
1291
+ # Restore original values
1292
+ if self.lower_indices is not None:
1293
+ if isinstance(self.lower_indices, list):
1294
+ for iii, indices in enumerate(self.lower_indices):
1295
+ if indices is not None:
1296
+ # self.img[indices,...,iii] = self.lower_values[iii]
1297
+ tmp = self.img[..., iii]
1298
+ tmp[indices] = self.lower_values[iii]
1299
+ self.img[..., iii] = tmp
1300
+ else:
1301
+ self.img[self.lower_indices] = self.lower_values
1302
+ if self.upper_indices is not None:
1303
+ # print('in restore_outliers 2')
1304
+
1305
+
1306
+ if isinstance(self.upper_indices, list):
1307
+ # print('in restore_outliers 3')
1308
+ for iii,indices in enumerate(self.upper_indices):
1309
+ if indices is not None:
1310
+ tmp = self.img[...,iii]
1311
+ tmp[indices] = self.upper_values[iii]
1312
+ self.img[..., iii]=tmp
1313
+ else:
1314
+ # print('in restore_outliers 4')
1315
+ # print('beforere',self.upper_values.max(), self.img.max())
1316
+
1317
+ self.img[self.upper_indices] = self.upper_values
1318
+ # print('afeter', self.upper_values.max(), self.img.max())
1319
+ else:
1320
+ # print('in restore_outliers NONE FOUND')
1321
+ pass
1322
+ # print('outside restore_outlier', self.img.max())
1323
+ except:
1324
+ pass
1325
+
1326
+ def reset_outliers(self):
1327
+ try:
1328
+ # print('reset_outliers called')
1329
+ try:
1330
+ del self.lower_indices
1331
+ del self.lower_values
1332
+ except:
1333
+ pass
1334
+
1335
+ self.lower_indices = None
1336
+ self.lower_values = None
1337
+
1338
+ try:
1339
+ del self.upper_indices
1340
+ del self.upper_values
1341
+ except:
1342
+ pass
1343
+
1344
+ self.upper_indices = None
1345
+ self.upper_values = None
1346
+ except:
1347
+ pass
1348
+
1349
+ # centralized code for
1350
+ def get_raw_size(self):
1351
+ if self.img is not None and isinstance(self.img, np.ndarray):
1352
+ try:
1353
+ w = self.img.get_width()
1354
+ h = self.img.get_height()
1355
+ # print('hw is defined here 1', h,w, self.filename)
1356
+ return w,h
1357
+ except:
1358
+ pass
1359
+
1360
+ if len(self.img.shape) <= 3:
1361
+ w = self.img.shape[1]
1362
+ h = self.img.shape[0]
1363
+ # elif len(self.img.shape) == 4:
1364
+ # w = self.img.shape[2]
1365
+ # h = self.img.shape[1]
1366
+ else: # assume hwc or more
1367
+ w=self.img.shape[-2]
1368
+ h=self.img.shape[-3]
1369
+
1370
+ # print('hw is defined here 2', h, w, self.filename)
1371
+ return w,h
1372
+ elif isinstance(self.img, plt.Figure):
1373
+ rect = get_fig_rect(self.img)
1374
+ w = rect.width()
1375
+ h = rect.height()
1376
+ # print('hw is defined here 3', h, w, self.filename)
1377
+ return w,h
1378
+ elif isinstance(self.renderer, QSvgRenderer):
1379
+ # if figure is a svg file then get it from here
1380
+ w,h = self.renderer.defaultSize().width(),self.renderer.defaultSize().height()
1381
+ # print('hw is defined here 4', h, w, self.filename)
1382
+ return w,h
1383
+
1384
+ if isinstance(self.img, QRectF):
1385
+ return int(self.img.width()), int(self.img.height())
1386
+ # this is problematic and should never be reached!!!
1387
+
1388
+ # print('data to check',self.img, type(self.img), isinstance(self.img, QRectF), isinstance(self.img, str))
1389
+
1390
+ # raise Exception('do nbot reach that to avoid issues')
1391
+
1392
+ # if all failed just take the rect default size
1393
+ w = super().width()
1394
+ h = super().height()
1395
+ # print('hw is defined here 5', h, w, self.filename)
1396
+ return w,h
1397
+
1398
+
1399
+ def get_consolidated_filename(self, parent_file):
1400
+ self.filename = get_consolidated_filename_from_parent(self.filename, parent_file)
1401
+
1402
+
1403
+ def consolidate_names(self, files_to_consolidate, consolidated_files):
1404
+ if not self.filename:
1405
+ return
1406
+ if not is_string_a_list_string(self.filename):
1407
+ idx = files_to_consolidate.index(self.filename)
1408
+ if idx!=-1:
1409
+ # if self.filename in files_to_consolidate:
1410
+ self.filename = consolidated_files[idx]
1411
+ else:
1412
+ files = reload_string_list(self.filename)
1413
+ consolidated_file_names = []
1414
+ for file in files:
1415
+ idx = files_to_consolidate.index(file)
1416
+ if idx != -1:
1417
+ # if self.filename in files_to_consolidate:
1418
+ consolidated_file_names.append(consolidated_files[idx])
1419
+ self.filename = str(consolidated_file_names)
1420
+
1421
+
1422
+
1423
+ def _toBuffer(self, bufferType='raster'):
1424
+ if self.img is not None:
1425
+ buf = io.BytesIO()
1426
+ if bufferType == 'raster':
1427
+ self.img.savefig(buf, format='png', bbox_inches='tight')
1428
+ else:
1429
+ self.img.savefig(buf, format='svg', bbox_inches='tight')
1430
+ buf.seek(0)
1431
+ return buf
1432
+ return None
1433
+
1434
+ def get_extra_border_size(self):
1435
+ if self.border_size is not None and self.border_color is not None:
1436
+ return self.border_size
1437
+ return 0
1438
+
1439
+ def is_template(self):
1440
+ return self.filename == '@template@'
1441
+
1442
+ def set_rotation(self, theta): # by default set it to black or to None --> white could also be useful
1443
+ super().set_rotation(theta)
1444
+ self.theta = theta # CHECK WHY AND IF I NEED THE LINE ABOVE!!!
1445
+
1446
+ def draw_bg(self, painter, draw=True, parent=None, restore_painter_in_the_end=True):
1447
+
1448
+ # drawing was getting too big so I split the draw bg and draw annotations into two separate things
1449
+ if draw:
1450
+ painter.save()
1451
+ painter.setPen(Qt.NoPen)
1452
+ painter.setBrush(Qt.NoBrush)
1453
+
1454
+ if parent is None:
1455
+ painter.setClipRect(self.getRect(all=True)) # required first to fraw the stuff at the very beginning of it !!! --> it must be done before save
1456
+
1457
+ if self.fill_color is not None:
1458
+ painter.setBrush(QBrush(QColor(self.fill_color)))
1459
+ painter.drawRect(self.getRect(all=True))
1460
+
1461
+ try: # trick to force equal save and restore even upon error !!!
1462
+ painter.setOpacity(self.opacity)
1463
+
1464
+ if parent is not None:
1465
+ rect_to_plot = QRectF(0, 0, parent.width(all=True) * self.fraction_of_parent_image_width_if_image_is_inset, 0)
1466
+ rect_to_plot.setHeight(rect_to_plot.width() / (self.getRect().width() / self.getRect().height()))
1467
+
1468
+ self.set_to_scale(self.width() / rect_to_plot.width()) # TODO --> CHECK THAT --> is that ok or should I do the opposite ??? --> this is the only element that needs be rescaled because all the other shold not be changed
1469
+ rect_to_plot = rect_to_plot.translated(self.topLeft()) # very hacky way to get it to work --> see how I can do that # DEV NOT THIS IS MANDATORY TO GET THE INSETS TO DISPLAY --> NEVER REMOVE THIS
1470
+ else:
1471
+ rect_to_plot = self.getRect(all=True) # in that case I can add it a position
1472
+
1473
+ if self.img is not None and not isinstance(self.img, QRectF) and not isinstance(self.img, plt.Figure) and not isinstance(self.img, QSvgRenderer):
1474
+ x = 0
1475
+ y = 0
1476
+ # try:
1477
+ # w = self.img.get_width()
1478
+ # h = self.img.get_height()
1479
+ # except:
1480
+ # # see how to handle images that are not in the stuff
1481
+ # # TODO maybe fix that too
1482
+ # w = self.img.shape[1]
1483
+ # h = self.img.shape[0]
1484
+ #
1485
+ # if w is None or h is None:
1486
+ # w = self.img.shape[1]
1487
+ # h = self.img.shape[0]
1488
+ w,h = self.get_raw_size()
1489
+
1490
+ if not self.theta:
1491
+ if self.crop_top is not None:
1492
+ y = self.crop_top
1493
+ h -= self.crop_top
1494
+ if self.crop_left is not None:
1495
+ x = self.crop_left
1496
+ w -= self.crop_left
1497
+ if self.crop_right is not None:
1498
+ w -= self.crop_right
1499
+ if self.crop_bottom is not None:
1500
+ h -= self.crop_bottom
1501
+
1502
+ # pb here --> see how to really crop
1503
+ qsource = QRectF(x, y, w, h)
1504
+
1505
+ # TODO --> maybe have a look here some day https://stackoverflow.com/questions/15166754/stroking-a-path-only-inside-outside
1506
+
1507
+ if parent is not None and self.border_color is not None and self.border_size is not None and self.border_size >= 1: # we draw the square first then the image so that the image is not truncated by the bounding rect
1508
+ pen = QPen(QColor(self.border_color),
1509
+ self.border_size * 2.) # Adjust the color and line style as needed
1510
+ painter.setPen(pen)
1511
+ painter.drawRect(rect_to_plot)
1512
+
1513
+ if not self.theta:
1514
+ # original code -−> was not working if image was rotated --> needed a fix
1515
+ painter.drawImage(rect_to_plot, self.qimage, qsource) # , flags=QtCore.Qt.AutoColor
1516
+ else:
1517
+ # VERY DIRTY HACK --> DRAW ALL THE NON PLACED SHAPES ON THE ORIGINAL IMAGE THEN ROTATE IT -−> MEGA DIRTY HACK BUT WILL WORK !!!
1518
+ x = 0
1519
+ y = 0
1520
+ # try:
1521
+ # w = self.img.get_width()
1522
+ # h = self.img.get_height()
1523
+ # except:
1524
+ # # see how to handle images that are not in the stuff
1525
+ # w = self.img.shape[1]
1526
+ # h = self.img.shape[0]
1527
+ #
1528
+ # if w is None or h is None:
1529
+ # w = self.img.shape[1]
1530
+ # h = self.img.shape[0]
1531
+
1532
+ w,h=self.get_raw_size()
1533
+
1534
+ image_rect = QRectF(0, 0, w, h) # maybe w+1 and h+1 is more correct --> need think and check
1535
+ center_img = image_rect.center()
1536
+
1537
+ init_rect = QRectF(image_rect.x(), image_rect.y(), image_rect.width(), image_rect.height())
1538
+ transformation_for_image=QTransform()
1539
+ transformation_for_image.translate(center_img.x(), center_img.y())
1540
+ transformation_for_image.rotate(self.theta)
1541
+ transformation_for_image.translate(-center_img.x(), -center_img.y())
1542
+
1543
+ # we center the rotated rect to that
1544
+ rotated_image_rect = transformation_for_image.mapRect(init_rect)
1545
+
1546
+ rotated_image_rect.translate(center_img.x()-rotated_image_rect.center().x(), center_img.y()-rotated_image_rect.center().y())
1547
+
1548
+ if self.crop_top is not None:
1549
+ rotated_image_rect.setY(rotated_image_rect.y() + self.crop_top) # but the crop should be much less of the real image if
1550
+ if self.crop_left is not None:
1551
+ rotated_image_rect.setX(rotated_image_rect.x() + self.crop_left)
1552
+ if self.crop_right is not None:
1553
+ rotated_image_rect.setWidth(rotated_image_rect.width() - self.crop_right)
1554
+ if self.crop_bottom is not None:
1555
+ rotated_image_rect.setHeight(rotated_image_rect.height() - self.crop_bottom)
1556
+
1557
+ rotated_image = rotate_image(self.qimage, self.theta, annotations=None, parent_painter=painter) # DEV NOT KEEP there is a bug BE CAREFUL CALLING A QRECTF(ANOTHER) WITH ANOTHER that has negative x sets it to 0 and reduces its width and height --> NO CLUE WHY !!!!
1558
+ cropped_rotated_image = crop_image(rotated_image, self.crop_left, self.crop_right, self.crop_top, self.crop_bottom)
1559
+
1560
+ rotated_image_rect = QRectF(0, 0, cropped_rotated_image.width(), cropped_rotated_image.height())
1561
+ rotated_image_target = QRectF(rotated_image_rect.x(), rotated_image_rect.y(), rotated_image_rect.width(), rotated_image_rect.height())
1562
+ rotated_image_target.setWidth(rotated_image_target.width()/self.scale)
1563
+ rotated_image_target.setHeight(rotated_image_target.height()/self.scale)
1564
+
1565
+ rotated_image_target.translate(rect_to_plot.center()-rotated_image_target.center())
1566
+ painter.drawImage(rotated_image_target, cropped_rotated_image, rotated_image_rect)
1567
+
1568
+ elif self.renderer is not None or isinstance(self.img, plt.Figure):
1569
+ if self.crop_left or self.crop_right or self.crop_top or self.crop_bottom or self.theta:
1570
+ # if True:
1571
+ # almost all ok but somehow the new rendered does introduce a small size difference that I don't get and that was not in previous if no rotation -−> see
1572
+
1573
+ # Get the viewBox of the SVG file
1574
+ # view_box = self.renderer.viewBoxF()
1575
+ # Print the viewBox information
1576
+ # print(f"ViewBox: {view_box}", self.renderer.defaultSize(), self.getRect(all=True), rect_to_plot)
1577
+ # the pb is size is already int rounded --> may cause errors --> see if I can do something better
1578
+
1579
+
1580
+ # New code with crop and rotations for svg in the same way as for figs
1581
+ painter.save()
1582
+ rect_to_plot = self.getRect(all=True) # yes indeed it should be cropped -−> ok
1583
+ w,h = self.get_raw_size()
1584
+ svg_rect = QRectF(0, 0, w,h)
1585
+
1586
+ center = svg_rect.center()
1587
+
1588
+ # if self.theta:
1589
+ rotation_transform = QTransform().translate(center.x(), center.y()).rotate(self.theta if self.theta else 0).translate(-center.x(), -center.y()) # MEGA TODO --> replace by Transform2D, just make a version that supports maprect and maptopoly to make it easy to use
1590
+
1591
+ # Calculate the bounding rectangle of the rotated SVG
1592
+ bounding_rect = rotation_transform.mapRect(svg_rect)
1593
+ # else:
1594
+ # bounding_rect = QRectF(svg_rect)
1595
+
1596
+ # Crop the bounding rectangle
1597
+ cropped_bounding_rect = bounding_rect.adjusted(self.crop_left, self.crop_top, -self.crop_right, -self.crop_bottom)
1598
+ # cropped_bounding_rect = QRectF(bounding_rect.x()-self.crop_left, bounding_rect.y()-self.crop_top, bounding_rect.width()-self.crop_left-self.crop_right, bounding_rect.height()-self.crop_top-self.crop_bottom)
1599
+
1600
+ self.clip_rect = rect_to_plot
1601
+
1602
+ # Calculate the scale factor to fit cropped_bounding_rect into self.clip_rect
1603
+ scale_factor = min(self.clip_rect.width() / cropped_bounding_rect.width(), # this one generates some small borders --> None of these is perfect (where is the AR error comming from, rounding errors ???? ???
1604
+ self.clip_rect.height() / cropped_bounding_rect.height())
1605
+ if False:
1606
+ scale_factor = max(self.clip_rect.width() / cropped_bounding_rect.width(), # this one is a bit out
1607
+ self.clip_rect.height() / cropped_bounding_rect.height())
1608
+ scale_factor = (self.clip_rect.width() / cropped_bounding_rect.width()+ #
1609
+ self.clip_rect.height() / cropped_bounding_rect.height())/2
1610
+ #
1611
+ scale_factors = self.clip_rect.width() / cropped_bounding_rect.width(), self.clip_rect.height() / cropped_bounding_rect.height()
1612
+
1613
+ # is there an error or scale both ???
1614
+
1615
+
1616
+ # Scale and translate the painter to align cropped_bounding_rect with self.clip_rect
1617
+ scaled_cropped_bounding_rect = QRectF(cropped_bounding_rect.x()* scale_factor, cropped_bounding_rect.y()* scale_factor, cropped_bounding_rect.width()* scale_factor, cropped_bounding_rect.height() * scale_factor)
1618
+ painter.translate(self.clip_rect.center() - scaled_cropped_bounding_rect.center())
1619
+
1620
+ # WHY ARE ARs not the same ????
1621
+ # print('comparing ARs', self.clip_rect.width() / cropped_bounding_rect.width(), self.clip_rect.height() / cropped_bounding_rect.height(), self.clip_rect.height() / cropped_bounding_rect.height()==self.clip_rect.width() / cropped_bounding_rect.width())
1622
+
1623
+ # Scale the painter
1624
+ if True:
1625
+ painter.scale(scale_factor, scale_factor)
1626
+ else:
1627
+ painter.scale(scale_factors[0], scale_factors[1])
1628
+ # painter.scale(scale_factors[1], scale_factors[0])
1629
+
1630
+ if self.theta:
1631
+ painter.translate(svg_rect.center())
1632
+ painter.rotate(self.theta if self.theta else 0)
1633
+ painter.translate(-svg_rect.center())
1634
+
1635
+ # is this code changing AR somehow ???
1636
+
1637
+ # this does not work but that should --> so why is that
1638
+ # svg_rect2 = QRectF(0,0, svg_rect.width()*scale_factors[0], svg_rect.height()*scale_factors[1])
1639
+ # svg_rect2.translate(svg_rect.center()-svg_rect2.center())
1640
+
1641
+ self.renderer.render(painter, svg_rect)
1642
+ # self.renderer.render(painter, svg_rect2)
1643
+
1644
+ painter.restore()
1645
+ else:
1646
+ painter.save()
1647
+ if self.theta: # restored rotation of svgs
1648
+ # # I have added support for rotation to that maybe I will need something smarter if rotation 90 degrees or flips and images no same width and height
1649
+ painter.translate(rect_to_plot.center())
1650
+ painter.rotate(self.theta)
1651
+ painter.translate(-rect_to_plot.center())
1652
+ self.renderer.render(painter, rect_to_plot)
1653
+
1654
+ painter.restore()
1655
+
1656
+ else:
1657
+ if not check_antialiasing(painter): # only draw this in figure building mode, not in production
1658
+ if self.filename is None: # if the file has a name and no image was produced then there is an error somewhere and the user needs to enter custom code to handle things
1659
+ if self.isText:
1660
+ text = 'Label'
1661
+ pen = QPen(QColor(200, 255, 64)) # yellow
1662
+ else:
1663
+ if ((self.img is None or isinstance(self.img, QRectF)) and self.custom_loading_script):
1664
+ text = "Error"
1665
+ pen = QPen(QColor(255, 0, 0)) # red
1666
+ else:
1667
+ text = "Empty"
1668
+ pen = QPen(QColor(255, 0, 255)) # pu
1669
+ else:
1670
+ if self.filename == '@template@':
1671
+ text = "Template"
1672
+ pen = QPen(QColor(255, 165, 0)) # orange
1673
+ else:
1674
+ text = "Error"
1675
+ pen = QPen(QColor(255, 0, 0)) # red
1676
+ # TODO deactivate this for production but keep it for image editing!
1677
+
1678
+ pen.setWidthF(6)
1679
+ painter.setPen(pen)
1680
+ painter.drawRect(rect_to_plot) # this fills the rect I guess I rather wanna have a pen rather than a brush and a --> probably not what I want !!!
1681
+
1682
+ font = painter.font() # Use the current font or set a custom font if desired
1683
+
1684
+ # Calculate the text's center position within the rectangle
1685
+ font_metrics = QFontMetrics(font)
1686
+ text_width = font_metrics.width(text)
1687
+ text_height = font_metrics.height()
1688
+ text_x = rect_to_plot.x()+ rect_to_plot.width() / 2. - text_width / 2.
1689
+ text_y = rect_to_plot.y()+text_height# + rect_to_plot.height() / 2. + text_height / 2.+text_height # to make it more visible
1690
+
1691
+ # Draw the text in the middle of the rectangle
1692
+ painter.drawText(int(text_x), int(text_y), text)
1693
+ except:
1694
+ traceback.print_exc()
1695
+
1696
+ if restore_painter_in_the_end:
1697
+ painter.restore()
1698
+
1699
+ def setFigure(self, figure):
1700
+ self.must_update_figure_on_first_paint = False
1701
+
1702
+ if figure is not None and isinstance(figure, plt.Figure):
1703
+ self.img = figure
1704
+ buffer = self._toBuffer(bufferType='svg')
1705
+ self.renderer = QSvgRenderer(buffer.read())
1706
+ buffer.close()
1707
+ if self.width() == 0:
1708
+ size = self.renderer.defaultSize()
1709
+ self.setWidth(size.width())
1710
+ self.setHeight(size.height())
1711
+ elif figure is not None and isinstance(figure, str): # path to an svg file
1712
+ self.renderer = QSvgRenderer(figure) # data --> need convert data to be able to read it
1713
+ self.img= self.renderer
1714
+ self.filename = figure
1715
+ if self.width() == 0:
1716
+ size = self.renderer.defaultSize()
1717
+ self.setWidth(size.width())
1718
+ self.setHeight(size.height())
1719
+ if size.width() <= 0:
1720
+ logger.error('image "' + str(self.filename) + '" could not be loaded')
1721
+ self.isSet = False
1722
+ return
1723
+ else:
1724
+ logger.error(
1725
+ 'The provided figure is not a valid matplotlib figure nor a valid svg file! Nothing can be done with it... Sorry...')
1726
+
1727
+ def draw_annotations(self,painter, draw=True, parent=None,restore_painter_in_the_end=True):
1728
+ try:
1729
+ extra_space = 1
1730
+ # we get the current transform and it should be applied to all objects but the placed objects to which no rotation should be applied
1731
+ current_transform = painter.worldTransform()
1732
+ empty_transform = copy_transform_without_rotation(current_transform) # QTransform() #
1733
+
1734
+ if self.annotations is not None and self.annotations:
1735
+ objects_grouped_by_position = groupby_position( self.annotations)
1736
+ for extra in self.annotations:
1737
+ if isinstance(extra, ScaleBar):
1738
+ extra.unit_to_pixel_conversion_factor = self.px_to_unit_conversion_factor
1739
+ extra.update_bar_at_scale(self.scale)
1740
+ try:
1741
+ try:
1742
+ pos = extra.placement.get_position()
1743
+ except:
1744
+ pos = None
1745
+ if pos:
1746
+ if extra.placement.check_position('top'):
1747
+ extra.setTopLeft(extra.x(), self.topLeft().y())
1748
+ if extra.placement.check_position('left'):
1749
+ extra.setTopLeft(self.topLeft().x(), extra.y())
1750
+ if extra.placement.check_position('right'):
1751
+ extra.setTopLeft(self.x() + self.width(all=True) - extra.width(all=True), extra.y())
1752
+ if extra.placement.check_position('bottom'):
1753
+ extra.setTopLeft(extra.x(), self.topLeft().y() + self.height(all=True) - extra.height(
1754
+ all=True))
1755
+ if extra.placement.check_position('center_h'):
1756
+ extra.setTopLeft(
1757
+ self.x() + self.width(all=True) / 2. - extra.width(all=True) / 2., extra.y())
1758
+ if extra.placement.check_position('center_v'):
1759
+ extra.setTopLeft(extra.x(), self.y() + self.height(all=True) / 2. - extra.height(
1760
+ all=True) / 2.)
1761
+ except:
1762
+ traceback.print_exc()
1763
+
1764
+ # now for each group I need stack them --> give it a try
1765
+ for group, objects in objects_grouped_by_position.items():
1766
+ if group == ('free', 'relative'):
1767
+ continue
1768
+ else:
1769
+ pack2(extra_space, objects[0].placement, True,
1770
+ *objects) # TODO --> find packing mode from position --> should be doable --> and can ignore
1771
+
1772
+ for extra in self.annotations:
1773
+ rect_to_plot = self.getRect(all=True)
1774
+ center = rect_to_plot.center()
1775
+
1776
+ if ('free', 'relative') in objects_grouped_by_position and extra in objects_grouped_by_position[('free', 'relative')]:
1777
+ # THESE OBJECTS ARE NOT PLACED SO THEY SHOULD BE ROTATED
1778
+ painter.setWorldTransform(current_transform)
1779
+
1780
+ if self.theta and not (self.crop_left or self.crop_right or self.crop_top or self.crop_bottom):
1781
+ painter.translate(center.x(), center.y())
1782
+ painter.rotate(self.theta)
1783
+ painter.translate(-center.x(), -center.y())
1784
+ painter.translate(self.getRect(all=True).center()-self.getRect(scale=True).center()) # this is required for rotated shapes
1785
+
1786
+ if not self.theta and (self.crop_left or self.crop_right or self.crop_top or self.crop_bottom):
1787
+ # this fixes the stuff for rotation only but not for translation --> I'm almost there but not fully yet
1788
+ tmp = get_shape_after_rotation_and_crop(self.getRect(raw=True), self.theta, self.crop_left,
1789
+ self.crop_right, self.crop_top, self.crop_bottom)
1790
+
1791
+ parent = self.getRect(raw=True)
1792
+ parent = QRectF(0, 0, parent.width(), parent.height())
1793
+
1794
+ if tmp is not None:
1795
+ tmp2 = parent.topLeft() - QRectF(tmp.boundingRect()).topLeft()
1796
+ painter.translate(tmp2.x() / self.scale, tmp2.y() / self.scale)
1797
+
1798
+ if self.theta and (self.crop_left or self.crop_right or self.crop_top or self.crop_bottom):
1799
+ # painter.setClipping(False)
1800
+
1801
+ # TODO --> maybe code this below in getRect(raw_no_crop) for example
1802
+ tmp_crop_left = self.crop_left
1803
+ tmp_crop_right = self.crop_right
1804
+ tmp_crop_top = self.crop_top
1805
+ tmp_crop_bottom = self.crop_bottom
1806
+
1807
+ self.crop_left =0
1808
+ self.crop_right=0
1809
+ self.crop_top=0
1810
+ self.crop_bottom=0
1811
+
1812
+ rect_uncropped = self.getRect(all=True)
1813
+ rect_uncropped.translate(rect_to_plot.center()-rect_uncropped.center())
1814
+ # rect_uncropped.translate(rect_to_plot.topLeft()-rect_uncropped.topLeft())
1815
+ center = rect_uncropped.center()
1816
+
1817
+ self.crop_left = tmp_crop_left
1818
+ self.crop_right=tmp_crop_right
1819
+ self.crop_top=tmp_crop_top
1820
+ self.crop_bottom=tmp_crop_bottom
1821
+
1822
+ extra_user_translation, extra_translation_at_painter = compute_translation_corrections(self, minimal_return=True)
1823
+ painter.translate(extra_translation_at_painter/self.scale)
1824
+
1825
+ painter.translate(center.x(), center.y())
1826
+ painter.rotate(self.theta)
1827
+ painter.translate(-center.x(), -center.y())
1828
+
1829
+ if extra_user_translation:
1830
+ try:
1831
+ painter.translate(extra_user_translation[0]/self.scale, extra_user_translation[1]/self.scale)
1832
+ except:
1833
+ traceback.print_exc()
1834
+ else:
1835
+ if extra.placement is None or not extra.placement.position_to_string():
1836
+ # THIS OBJECT IS NOT PLACED SO IT SHOULD BE ROTATED
1837
+ painter.setWorldTransform(current_transform)
1838
+ if self.theta:
1839
+ painter.translate(center.x(), center.y())
1840
+ painter.rotate(self.theta)
1841
+ painter.translate(-center.x(), -center.y())
1842
+ painter.translate(self.getRect(all=True).center() - self.getRect(scale=True).center()) # this is required for rotated shapes
1843
+ else:
1844
+ # THIS OBJECT IS PLACED AND THEREFORE SHOULD NOT BE ROTATED
1845
+ painter.setWorldTransform(empty_transform)
1846
+
1847
+ extra.draw(painter=painter, parent=self) # added parent for relative plotting
1848
+ except:
1849
+ traceback.print_exc()
1850
+
1851
+ if restore_painter_in_the_end:
1852
+ painter.restore()
1853
+ def draw(self, painter, draw=True, parent=None):
1854
+ if draw:
1855
+ # TODO --> shall I recover and reuse the rect ???
1856
+
1857
+ self.draw_bg(painter, draw=draw, parent=parent, restore_painter_in_the_end=False)
1858
+
1859
+ # letter is good
1860
+ # then position all of them as a chain --> TODO
1861
+ self.draw_annotations(painter, draw=draw, parent=parent, restore_painter_in_the_end=False)
1862
+
1863
+ painter.restore()
1864
+
1865
+ def setToWidth(self, width_in_px):
1866
+ pure_image_width = self.width(scaled=False)# need original height and with in fact
1867
+ scale = width_in_px / pure_image_width
1868
+ self.scale = scale
1869
+
1870
+ def setToHeight(self, height_in_px):
1871
+ pure_image_height = self.height(scaled=False)
1872
+ scale = height_in_px/pure_image_height
1873
+ self.scale = scale
1874
+
1875
+ def crop(self, left=None, right=None, top=None, bottom=None, all=None):
1876
+ if left is not None:
1877
+ self.crop_left = left
1878
+ if right is not None:
1879
+ self.crop_right = right
1880
+ if top is not None:
1881
+ self.crop_top = top
1882
+ if bottom is not None:
1883
+ self.crop_bottom = bottom
1884
+ if all is not None:
1885
+ self.crop_left = all
1886
+ self.crop_right = all
1887
+ self.crop_top = all
1888
+ self.crop_bottom = all
1889
+ def set_to_translation(self, translation):
1890
+ self.translation = translation
1891
+
1892
+ def boundingRect(self, scaled=True):
1893
+ # en fait pas good besoin de prendre les crops et le scale en compte
1894
+ # is
1895
+ # rect_to_plot = self.adjusted(self.crop_left, self.crop_top, -self.crop_right, -self.crop_bottom)
1896
+ rect_to_plot = self.adjusted(0, 0, -self.crop_right-self.crop_left, -self.crop_bottom-self.crop_top)
1897
+ # rect_to_plot = self.adjusted(-self.crop_left, -self.crop_top, -self.crop_right, -self.crop_bottom)
1898
+ # rect_to_plot = self.adjusted(0,0,0,0)
1899
+ # print('begin rect_to_plot', rect_to_plot, self.scale)
1900
+ # if kwargs['draw']==True or kwargs['fill']==True:
1901
+ # if self.scale is None or self.scale==1:
1902
+ # painter.drawRect(self)
1903
+ # else:
1904
+ # on clone le rect
1905
+ if self.scale is not None and self.scale != 1 and scaled:
1906
+ # TODO KEEP THE ORDER THIS MUST BE DONE THIS WAY OR IT WILL GENERATE PLENTY OF BUGS...
1907
+ new_width = rect_to_plot.width() * self.scale
1908
+ new_height = rect_to_plot.height() * self.scale
1909
+ # print(rect_to_plot.width(), rect_to_plot.height()) # here ok
1910
+ # setX changes width --> why is that
1911
+
1912
+ # TODO BE EXTREMELY CAREFUL AS SETX AND SETY CAN CHANGE WIDTH AND HEIGHT --> ALWAYS TAKE SIZE BEFORE OTHERWISE THERE WILL BE A PB AND ALWAYS RESET THE SIZE WHEN SETX IS CALLED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1913
+ # Sets the left edge of the rectangle to the given x coordinate. May change the width, but will never change the right edge of the rectangle. --> NO CLUE WHY SHOULD CHANGE WIDTH THOUGH BUT BE CAREFUL!!!
1914
+ # rect_to_plot.setX(rect_to_plot.x() * self.scale)
1915
+ # rect_to_plot.setY(rect_to_plot.y() * self.scale)
1916
+ # maybe to avoid bugs I should use translate instead rather that set x but ok anyways
1917
+ # print(rect_to_plot.width(), rect_to_plot.height())# bug here --> too big
1918
+
1919
+ # print(new_height, new_height, self.width(), self.scale, self.scale* self.width())
1920
+ rect_to_plot.setWidth(new_width)
1921
+ rect_to_plot.setHeight(new_height)
1922
+ return rect_to_plot
1923
+
1924
+ SVG_INKSCAPE = 96
1925
+ SVG_ILLUSTRATOR = 72
1926
+
1927
+ qualities = [QPainter.SmoothPixmapTransform, QPainter.TextAntialiasing, None, QPainter.Antialiasing] # None sets antialiasing to False -−> see code after # painter.setRenderHint QPainter.HighQualityAntialiasing,
1928
+
1929
+ '''
1930
+ Here are some common interpolation modes:
1931
+
1932
+ Qt.SmoothPixmapTransform: This mode enables smooth transformation of pixmaps when they are scaled or rotated. It uses bilinear or bicubic interpolation to provide smoother results, which can be useful when resizing images or drawing rotated content. However, this mode may have a performance impact, especially when dealing with large images or complex transformations.
1933
+ Qt.HighQualityAntialiasing: This mode enables high-quality antialiasing for painting operations. Antialiasing helps to reduce the jagged appearance of diagonal lines and curves by smoothing their edges. This mode can be useful when drawing shapes, text, or other graphical elements that require smooth edges. However, it may also have a performance impact, especially when dealing with complex scenes or large-scale drawings.
1934
+ Qt.TextAntialiasing: This mode enables antialiasing specifically for text rendering. It can help to improve the readability and appearance of text in your application, especially at small font sizes or when using custom fonts. However, it may also have a performance impact, especially when dealing with large amounts of text or complex text layouts.
1935
+
1936
+ painter = QPainter(some_widget)
1937
+ painter.setRenderHint(Qt.SmoothPixmapTransform)
1938
+ # or
1939
+ painter.setRenderHint(Qt.HighQualityAntialiasing)
1940
+ # or
1941
+ painter.setRenderHint(Qt.TextAntialiasing)
1942
+
1943
+ painter.setRenderHints(Qt.SmoothPixmapTransform | Qt.HighQualityAntialiasing)
1944
+ '''
1945
+ def save(self, path, filetype=None, title=None, description=None, svg_dpi=SVG_INKSCAPE, quality=qualities[-1]):
1946
+ # if path is None or not isinstance(path, str):
1947
+ # logger.error('please provide a valide path to save the image "' + str(path) + '"')
1948
+ # return
1949
+ if path is None:
1950
+ filetype = '.tif'
1951
+
1952
+ if filetype is None:
1953
+ if path.lower().endswith('.svg'):
1954
+ filetype = 'svg'
1955
+ else:
1956
+ filetype = os.path.splitext(path)[1]
1957
+ dpi = 72 # 300 # inkscape 96 ? check for illustrator --> check
1958
+
1959
+ if filetype == 'svg':
1960
+ generator = QSvgGenerator()
1961
+ generator.setFileName(path)
1962
+ if svg_dpi == self.SVG_ILLUSTRATOR:
1963
+ generator.setSize(QSize(595, 842))
1964
+ generator.setViewBox(QRect(0, 0, 595, 842))
1965
+ else:
1966
+ generator.setSize(QSize(794, 1123))
1967
+ generator.setViewBox(QRect(0, 0, 794, 1123))
1968
+
1969
+ if title is not None and isinstance(title, str):
1970
+ generator.setTitle(title)
1971
+ if description is not None and isinstance(description, str):
1972
+ generator.setDescription(description)
1973
+ generator.setResolution(
1974
+ svg_dpi) # fixes issues in inkscape of pt size --> 72 pr illustrator and 96 pr inkscape but need change size
1975
+
1976
+ painter = QPainter(generator)
1977
+
1978
+ # print(generator.title(), generator.heightMM(), generator.height(), generator.widthMM(),
1979
+ # generator.resolution(), generator.description(), generator.logicalDpiX())
1980
+ else:
1981
+ scaling_factor_dpi = 1
1982
+ # scaling_factor_dpi = self.scaling_factor_to_achieve_DPI(300)
1983
+
1984
+ # in fact take actual page size ??? multiplied by factor
1985
+ # just take real image size instead
1986
+
1987
+
1988
+ # image = QtGui.QImage(QSize(self.cm_to_inch(21) * dpi * scaling_factor_dpi, self.cm_to_inch(29.7) * dpi * scaling_factor_dpi), QtGui.QImage.Format_RGBA8888) # minor change to support alpha # QtGui.QImage.Format_RGB32)
1989
+
1990
+ # NB THE FOLLOWING LINES CREATE A WEIRD ERROR WITH WEIRD PIXELS DRAWN some sort of lines NO CLUE WHY
1991
+
1992
+ img_bounds = self.boundingRect()
1993
+ # image = QtGui.QImage(QSize(img_bounds.width() * scaling_factor_dpi, img_bounds.height()* scaling_factor_dpi), QtGui.QImage.Format_RGBA8888) # minor change to support alpha # QtGui.QImage.Format_RGB32)
1994
+ image = QtGui.QImage(QSize(int(img_bounds.width() * scaling_factor_dpi), int(img_bounds.height()* scaling_factor_dpi)), QtGui.QImage.Format_RGBA8888) # minor change to support alpha # QtGui.QImage.Format_RGB32)
1995
+ # image = QtGui.QImage(QSize(int(img_bounds.scale(scaling_factor_dpi))), QtGui.QImage.Format_RGBA8888) # minor change to support alpha # QtGui.QImage.Format_RGB32)
1996
+ # print('size at dpi',QSize(img_bounds.width() * scaling_factor_dpi, img_bounds.height()* scaling_factor_dpi))
1997
+ # QSize(self.cm_to_inch(0.02646 * img_bounds.width())
1998
+ # self.cm_to_inch(0.02646 * img_bounds.height())
1999
+ # need convert pixels to inches
2000
+ # is there a rounding error
2001
+
2002
+ # force white bg for non jpg
2003
+ try:
2004
+ # print(filetype.lower())
2005
+ # the tif and png file formats support alpha
2006
+ if not filetype.lower() == '.png' and not filetype.lower() == '.tif' and not filetype.lower() == '.tiff':
2007
+ image.fill(QColor.fromRgbF(1,1,1))
2008
+ else:
2009
+ # image.fill(QColor.fromRgbF(1, 1, 1, alpha=1))
2010
+ # image.fill(QColor.fromRgbF(1, 1, 1, alpha=1))
2011
+ # TODO KEEP in fact image need BE FILLED WITH TRANSPARENT OTHERWISE GETS WEIRD DRAWING ERRORS
2012
+ # TODO KEEP SEE https://stackoverflow.com/questions/13464627/qt-empty-transparent-qimage-has-noise
2013
+ # image.fill(qRgba(0, 0, 0, 0))
2014
+ image.fill(QColor.fromRgbF(0,0,0,0))
2015
+ except:
2016
+ pass
2017
+ painter = QPainter(image) # see what happens in case of rounding of pixels
2018
+ # painter.begin()
2019
+ painter.scale(scaling_factor_dpi, scaling_factor_dpi)
2020
+ if quality is not None:
2021
+ painter.setRenderHint(quality) # to improve rendering quality
2022
+ else:
2023
+ painter.setRenderHint(QPainter.Antialiasing, False) # to improve rendering quality
2024
+ self.draw(painter)
2025
+ painter.end()
2026
+ if path is None:
2027
+ return image
2028
+ if filetype != 'svg':
2029
+ image.save(path)
2030
+ return image
2031
+
2032
+ #based on https://stackoverflow.com/questions/19902183/qimage-to-numpy-array-using-pyside
2033
+ def convert_qimage_to_numpy(self, qimage):
2034
+ qimage = qimage.convertToFormat(QtGui.QImage.Format.Format_RGB32)
2035
+ width = qimage.width()
2036
+ height = qimage.height()
2037
+ image_pointer = qimage.bits() # creates a deep copy --> this is what I want
2038
+ # image_pointer.setsize(qimage.byteCount())
2039
+ try:
2040
+ image_pointer.setsize(qimage.sizeInBytes()) # qt6 version of the stuff
2041
+ except:
2042
+ image_pointer.setsize(qimage.byteCount())
2043
+ # arr = np.array(image_pointer,copy=True).reshape(height, width, 4)
2044
+ arr = np.array(image_pointer).reshape(height, width, 4)
2045
+ arr = arr[..., 0:3]
2046
+ arr = RGB_to_BGR(arr) # that seems to do the job
2047
+ return arr
2048
+
2049
+ def draw_inner_layout_selection(self, painter):
2050
+ painter.save()
2051
+ try:
2052
+ rect = self.getRect(all=True) # there is a big bug with that
2053
+ painter.setPen(QPen(QColor(0, 255, 255)))
2054
+ painter.drawRect(rect)
2055
+ except:
2056
+ print('inner error')
2057
+ traceback.print_exc()
2058
+ painter.restore()
2059
+
2060
+ def width(self, *args, **kwargs):
2061
+ # this should be uncropped and unrotated in fact or rotated in fact and uncropped ??? -−> think about it
2062
+ if args or kwargs:
2063
+ return self.getRect(all=True).width() # this is probably incorrect but ok for now
2064
+ else:
2065
+ return self.getRect().width() #* (self.scale if self.scale else 1)
2066
+
2067
+ def height(self, *args, **kwargs):
2068
+ if args or kwargs:
2069
+ return self.getRect(all=True).height() # this is probably incorrect but ok for now
2070
+ else:
2071
+ return self.getRect().height()# * (self.scale if self.scale else 1)
2072
+
2073
+ def getRect(self, *args, **kwargs):
2074
+ # w = super().width()
2075
+ # h = super().height()
2076
+ x = self.x()
2077
+ y = self.y()
2078
+ # if isinstance(self.img, np.ndarray):
2079
+ # w = self.img.shape[1]
2080
+ # h = self.img.shape[0]
2081
+ # elif isinstance(self.img, plt.Figure):
2082
+ # rect = get_fig_rect(self.img)
2083
+ # w = rect.width()
2084
+ # h = rect.height()
2085
+ # elif isinstance(self.renderer, QSvgRenderer):
2086
+ # size = self.renderer.defaultSize()
2087
+ # w = size.width()
2088
+ # h = size.height()
2089
+ w, h = self.get_raw_size()
2090
+
2091
+ # if self.is_template():
2092
+ # return QRectF(x,y,w,h)
2093
+
2094
+ if args or kwargs:
2095
+
2096
+ if 'raw' in kwargs:
2097
+ return QRectF(x,y,w,h)
2098
+ if 'all' in kwargs:
2099
+ rect_to_plot = QRectF(x, y, w, h)
2100
+
2101
+
2102
+ center = rect_to_plot.center()
2103
+
2104
+ if True: # try to correct the stuff in fact not really working --> see how I can fix that
2105
+ tmp = QRectF(rect_to_plot)
2106
+ if self.crop_left:
2107
+ tmp.setWidth(tmp.width() - self.crop_left)
2108
+ if self.crop_right:
2109
+ tmp.setWidth(tmp.width() - self.crop_right)
2110
+ if self.crop_top:
2111
+ tmp.setHeight(tmp.height() - self.crop_top)
2112
+ if self.crop_bottom:
2113
+ tmp.setHeight(tmp.height() - self.crop_bottom)
2114
+ tmp.setWidth(tmp.width() / self.scale)
2115
+ tmp.setHeight(tmp.height() / self.scale)
2116
+
2117
+ if self.theta and not self.theta == 0: # with that the rotation is ok but the position of the rect is not --> but with angle 0 it is --> can I correct for the translation
2118
+ t = QTransform().translate(center.x(), center.y()).rotate(self.theta).translate(-center.x(),
2119
+ -center.y()) # .translate(center.x(), center.y())
2120
+ rotatedPolygon = t.mapToPolygon(rect_to_plot.toRect()) # Map the rectangle to a rotated polygon
2121
+ # Create a QRectF from the bounding rectangle of the rotated polygon
2122
+ rotatedRect = QRectF(rotatedPolygon.boundingRect())
2123
+ else:
2124
+ if self.crop_left:
2125
+ rect_to_plot.setWidth(rect_to_plot.width() - self.crop_left)
2126
+ if self.crop_right:
2127
+ rect_to_plot.setWidth(rect_to_plot.width() - self.crop_right)
2128
+ if self.crop_top:
2129
+ rect_to_plot.setHeight(rect_to_plot.height() - self.crop_top)
2130
+ if self.crop_bottom:
2131
+ rect_to_plot.setHeight(rect_to_plot.height() - self.crop_bottom)
2132
+ rect_to_plot.setWidth(rect_to_plot.width() / self.scale)
2133
+ rect_to_plot.setHeight(rect_to_plot.height() / self.scale)
2134
+
2135
+ return rect_to_plot
2136
+ try:
2137
+ rotatedRect = QRectF(rotatedRect.boundingRect())
2138
+
2139
+ except:
2140
+ pass
2141
+
2142
+ if self.crop_left:
2143
+ rotatedRect.setWidth(rotatedRect.width() - self.crop_left)
2144
+ if self.crop_right:
2145
+ rotatedRect.setWidth(rotatedRect.width() - self.crop_right)
2146
+ if self.crop_top:
2147
+ rotatedRect.setHeight(rotatedRect.height() - self.crop_top)
2148
+ if self.crop_bottom:
2149
+ rotatedRect.setHeight(rotatedRect.height() - self.crop_bottom)
2150
+
2151
+ rotatedRect.setWidth(rotatedRect.width() / self.scale)
2152
+ rotatedRect.setHeight(rotatedRect.height() / self.scale)
2153
+ rotatedRect.translate(tmp.topLeft() - rotatedRect.topLeft())
2154
+
2155
+ return rotatedRect
2156
+ else:
2157
+ rect_to_plot = QRectF(self.x(), self.y(), w, h)
2158
+ if self.crop_left:
2159
+ rect_to_plot.setWidth(rect_to_plot.width() - self.crop_left)
2160
+ if self.crop_right:
2161
+ rect_to_plot.setWidth(rect_to_plot.width() - self.crop_right)
2162
+ if self.crop_top:
2163
+ rect_to_plot.setHeight(rect_to_plot.height() - self.crop_top)
2164
+ if self.crop_bottom:
2165
+ rect_to_plot.setHeight(rect_to_plot.height() - self.crop_bottom)
2166
+ rect_to_plot.setWidth(rect_to_plot.width() / self.scale)
2167
+ rect_to_plot.setHeight(rect_to_plot.height() / self.scale)
2168
+ return rect_to_plot
2169
+ else:
2170
+ rect_to_plot = QRectF(self.x(), self.y(), w, h)
2171
+ center = rect_to_plot.center()
2172
+ rotatedRect = rect_to_plot
2173
+ if self.theta:
2174
+ t = QTransform().translate(center.x(), center.y()).rotate(self.theta).translate(-center.x(),
2175
+ -center.y())
2176
+ rotatedPolygon = t.mapToPolygon(rect_to_plot.toRect()) # Map the rectangle to a rotated polygon
2177
+ # Create a QRectF from the bounding rectangle of the rotated polygon
2178
+ rotatedRect = QRectF(rotatedPolygon.boundingRect())
2179
+ if self.crop_left:
2180
+ rotatedRect.setWidth(rotatedRect.width() - self.crop_left)
2181
+ if self.crop_right:
2182
+ rotatedRect.setWidth(rotatedRect.width() - self.crop_right)
2183
+ if self.crop_top:
2184
+ rotatedRect.setHeight(rotatedRect.height() - self.crop_top)
2185
+ if self.crop_bottom:
2186
+ rotatedRect.setHeight(rotatedRect.height() - self.crop_bottom)
2187
+ try:
2188
+ return QRectF(rotatedRect.boundingRect())
2189
+ except:
2190
+ return rotatedRect
2191
+
2192
+ # def contains(self, *__args):
2193
+ # try:
2194
+ # # if self.__sel:
2195
+ # # return self.__sel.contains(*__args)
2196
+ # if __args is not None and __args:
2197
+ # return self.getRect(all=True).contains(*__args)
2198
+ # else:
2199
+ # return False
2200
+ # # return self.getRect(scale=True).contains(*__args)
2201
+ # except:
2202
+ # # traceback.print_exc() # somehow this generates an error but does it make sense
2203
+ # # I have too many handling of rotations --> that may cause trouble too
2204
+ # # simplest is to really make the rect as it should be and as selection should be !!!
2205
+ # pass
2206
+ # return False
2207
+
2208
+
2209
+ def __contains__(self, item):
2210
+ return False
2211
+
2212
+ def __copy__(self):
2213
+ return self # TODO --> rather do a clone of self
2214
+
2215
+ def __deepcopy__(self, memo):
2216
+ return self # TODO --> rather do a clone of self
2217
+
2218
+ # DEV NOTE KEEP be extremely careful with that because this is what is compared when using == !!! so if poorly implemented this can have dramatic effects
2219
+ def __str__(self):
2220
+ return self.__repr__()
2221
+
2222
+ def __repr__(self):
2223
+ class_name = type(self).__name__
2224
+ memory_address = hex(id(self))
2225
+
2226
+ # print('debugggging', self.img, self.custom_loading_script, ((self.img is None or isinstance(self.img, QRectF)) and self.filename is not None), ((self.img is None or isinstance(self.img, QRectF)) and self.custom_loading_script))
2227
+
2228
+ if self.isText:
2229
+ class_name = 'Label_' + class_name
2230
+ elif self.is_template():
2231
+ class_name = 'Template_' + class_name
2232
+ # elif (self.img is None and self.filename is not None) or (self.img is None and self.custom_loading_script):
2233
+ elif ((self.img is None or isinstance(self.img, QRectF)) and self.filename is not None) or ((self.img is None or isinstance(self.img, QRectF)) and self.custom_loading_script):
2234
+ class_name = 'Error_' + class_name
2235
+ elif isinstance(self.img, plt.Figure):
2236
+ class_name = 'Fig_' + class_name
2237
+ elif self.renderer is not None:
2238
+ class_name = 'SVG_'+class_name
2239
+ elif self.img is None:
2240
+ class_name = 'Empty_'+class_name
2241
+
2242
+ if self.filename is not None:
2243
+ base_name = os.path.basename(self.filename)
2244
+ return f"{class_name}-{memory_address}-{base_name}"
2245
+ return f"{class_name}-{memory_address}"
2246
+
2247
+ def to_dict(self):
2248
+ x = self.x()
2249
+ y = self.y()
2250
+ width = self.width()
2251
+ height = self.height()
2252
+
2253
+ # Create a dictionary representation of the values of the super object
2254
+ output_dict = {
2255
+ 'x': x,
2256
+ 'y': y,
2257
+ 'width': width,
2258
+ 'height': height,
2259
+ # 'args':self.filename,
2260
+ 'filename':self.filename,
2261
+ }
2262
+ # Update the dictionary with the __dict__ of Rectangle2D
2263
+ output_dict.update(self.__dict__)
2264
+ try:
2265
+ output_dict['extra_user_translation']=list(self.extra_user_translation) #
2266
+ except:
2267
+ pass
2268
+ try:
2269
+ del output_dict['script_log']
2270
+ except:
2271
+ pass
2272
+ if isinstance(self.img, QRectF):
2273
+ output_dict['template_rect']=self.img
2274
+ output_dict['LUTs']=str(self.LUTs)
2275
+ output_dict['channels']=str(self.channels)
2276
+ output_dict['lower_indices']=None
2277
+ output_dict['upper_indices']=None
2278
+ output_dict['lower_values']=None
2279
+ output_dict['upper_values']=None
2280
+ # output_dict['theta']=self.theta
2281
+ return output_dict
2282
+
2283
+ def execute_code_unsafe(self, code, parent=None): # TODO --> see how to handle the security!!!
2284
+ # Save the original streams
2285
+ orig_stdout = sys.stdout
2286
+ orig_stderr = sys.stderr
2287
+
2288
+ # Create temporary streams
2289
+ temp_stdout = io.StringIO()
2290
+ temp_stderr = io.StringIO()
2291
+
2292
+ # Redirect the streams to the temporary buffers
2293
+ sys.stdout = temp_stdout
2294
+ sys.stderr = temp_stderr
2295
+
2296
+ # If an exception occurs, capture it and include it in the output
2297
+ local_namespace = {'plt': plt, 'Img': Img, 'np':np} # all imports should be made there (otherwise the user needs to do the imports himslef --> painful though
2298
+ try:
2299
+ if self.filename and (self.filename.strip().startswith("['") and self.filename.strip().endswith("']")):
2300
+ self.filename = reload_string_list(self.filename)
2301
+ exec(code, locals(), local_namespace)
2302
+ if 'self.img' in local_namespace:
2303
+ self.img = local_namespace['self.img'] # shall I transfer more things ? -−> maybe !!!
2304
+ except Exception as e:
2305
+ print("Error:", e)
2306
+ temp_stderr.write(f"An error occurred: {str(e)}\n")
2307
+ temp_stderr.write(f"{traceback.format_exc()}\n")
2308
+
2309
+ # Restore the original streams
2310
+ sys.stdout = orig_stdout
2311
+ sys.stderr = orig_stderr
2312
+
2313
+ # Return the captured output
2314
+ # return temp_stdout.getvalue(), temp_stderr.getvalue()
2315
+ # we redirect the log and maybe the output to a file
2316
+ self.script_log = temp_stderr.getvalue() if temp_stderr.getvalue() else temp_stdout.getvalue()
2317
+ # print('#'*20)
2318
+ # print('self.script_log', self.script_log)
2319
+ # print('-' * 20)
2320
+ # print('dabu', temp_stdout.getvalue())
2321
+ # print('%' * 20)
2322
+ # print('dabi',temp_stderr.getvalue())
2323
+ # print('#' * 20)
2324
+
2325
+ # Close the temporary streams
2326
+ temp_stdout.close()
2327
+ temp_stderr.close()
2328
+
2329
+ if self.filename:
2330
+ if not isinstance(self.filename, str) and isinstance(self.filename, list):
2331
+ self.filename= str(self.filename)
2332
+
2333
+
2334
+ if __name__ == '__main__':
2335
+ app = QApplication(sys.argv)# IMPORTANT KEEP !!!!!!!!!!!
2336
+ import sys
2337
+
2338
+ if True:
2339
+ img1 = Image2D('/E/Sample_images/counter/01.png', user_comments='comments added by the user test')
2340
+ print('before rotation',img1.getRect(), img1.getRect(scale=True), img1.getRect(all=True))
2341
+
2342
+ img1.crop(10,15,20,30)
2343
+ print('after crop', img1.getRect(), img1.getRect(scale=True), img1.getRect(all=True)) # -> all is ok --> so maybe just the drawing is fucked in fact now
2344
+
2345
+ img1.crop(0, 0, 0, 0)
2346
+ img1.set_rotation(45)
2347
+ print('after rotation',img1.getRect(), img1.getRect(scale=True), img1.getRect(all=True)) # there is a bug in scale because it does not apply rotation --> probably not correct
2348
+
2349
+ img1.crop(10,15,20,30)
2350
+ print('after rotation and crop', img1.getRect(), img1.getRect(scale=True), img1.getRect(all=True)) # there is a bug i
2351
+ sys.exit(0)
2352
+
2353
+ if True:
2354
+ # Data for plotting
2355
+ t = np.arange(0.0, 2.0, 0.01)
2356
+ s = 1 + np.sin(2 * np.pi * t)
2357
+
2358
+ fig, ax = plt.subplots()
2359
+ print(type(fig))
2360
+ ax.plot(t, s)
2361
+
2362
+ ax.set(xlabel='time (s)', ylabel='voltage (mV)', title=None)
2363
+ ax.grid()
2364
+
2365
+ figure = Image2D(fig)
2366
+
2367
+
2368
+
2369
+ sys.exit(0)
2370
+
2371
+ if True:
2372
+ # todo test the use of an empty image with given dimensions then once this is done try to do stuff
2373
+ # in fact in all cases the only thing that matters is the initial rect --> I could put a rect instead of an image and that would give me all the things I need
2374
+ # TODO
2375
+
2376
+ # try to do that
2377
+ try:
2378
+ empty_img = Image2D()
2379
+ print(empty_img.width())
2380
+ except:
2381
+ traceback.print_exc()
2382
+ print('test that empty images are not allowed')
2383
+
2384
+ # do I already have a construction that would do the job
2385
+ empty_img = Image2D(width=120, height=240)# that seems to work --> but super is never called --> see
2386
+ # maybe allow an image to be initialized by a rect (to act as an empty image and if that is the case the image could be replaced) by what I need !!!
2387
+
2388
+ print(empty_img)
2389
+ print(empty_img.width())
2390
+
2391
+ sys.exit(0)
2392
+
2393
+ # is it possible that I never implemented the rotation ???
2394
+ # can I do it???
2395
+
2396
+ # VERY GOOD --> I HAVE FINALLY FIXED IT
2397
+
2398
+ # ça marche --> voici deux examples de shapes
2399
+ test = Image2D(x=12, y=0, width=100, height=100) # could also be used to create empty image with
2400
+
2401
+ print(test.img)
2402
+ print(test.boundingRect())
2403
+ # print(test.get_P1().x())
2404
+
2405
+ # bug qd on definit une image comme param
2406
+ # test = Image2D('/E/Sample_images/counter/06.png')
2407
+ test = Image2D('/E/Sample_images/counter/01.png')
2408
+ print(test.boundingRect()) # --> it is ok there so why not below # not callable --> why -->
2409
+ # print(test.get_P1()) # ça marche donc où est le bug
2410
+ # print(test.get_P1().y()) # ça marche donc où est le bug
2411
+ # print(test.getP1().width())
2412
+ # ça marche
2413
+
2414
+ # try draw on the image the quivers
2415
+ # img0.setLettering('<font color="red">A</font>')
2416
+ # # letter
2417
+ # img0.annotation.append(Rect2D(88, 88, 200, 200, stroke=3, color=0xFF00FF))
2418
+ # img0.annotation.append(Ellipse2D(88, 88, 200, 200, stroke=3, color=0x00FF00))
2419
+ # img0.annotation.append(Circle2D(33, 33, 200, stroke=3, color=0x0000FF))
2420
+
2421
+ test.annotations.append(TAText2D('<font color="red">A</font>', placement='top-left'))
2422
+
2423
+ test.annotations.append(Line2D(33, 33, 88, 88, stroke=3, color=0x0000FF))
2424
+
2425
+ test.annotations.append(Line2D(128, 33, 88, 88, stroke=0.65, color=0xFFFF00))
2426
+ # img0.annotation.append(Freehand2D(10, 10, 20, 10, 20, 30, 288, 30, color=0xFFFF00, stroke=3))
2427
+ # # img0.annotation.append(PolyLine2D(10, 10, 20, 10, 20, 30, 288, 30, color=0xFFFF00, stroke=3))
2428
+ # img0.annotation.append(Point2D(128, 128, color=0xFFFF00, stroke=6))
2429
+
2430
+ # add a save to the image --> so that it exports as a raster --> TODO
2431
+
2432
+
2433
+ # painter =
2434
+ # img = test.draw() # if no painter --> create one as not to lose any data and or allow to save as vectorial
2435
+
2436
+
2437
+ img = test.save('/E/trash/test_line2D.tif')
2438
+
2439
+ # test.save('/E/Sample_images/sample_images_PA/mini_vide/analyzed/trash/test_line2D.svg')
2440
+
2441
+ #trop facile --> just hack it so that it can return a single qimage # or return a numpy image that is then plotted -> should not be too hard !!! I think --> TODO
2442
+ img = test.convert_qimage_to_numpy(img) # --> ok but I just need to swap the channels then I'll be done --> try that maybe with plenty of input images just to see
2443
+
2444
+ # empty image work --> now try to see if I can create an image from a Figure and or a vector graphic ??? -−> TODO
2445
+
2446
+ # shall I save
2447
+ # try with non RGB images just to see
2448
+
2449
+ # img = RGB_to_BGR(img)
2450
+
2451
+ # almost there --> just need to check that the size of the image is ok and that everything is fine
2452
+ plt.imshow(img)
2453
+ plt.show()
2454
+
2455
+
2456
+ print('here')
2457
+ img2 = Image2D('/E/Sample_images/counter/02.png')
2458
+ print('here3')
2459
+ preview(img2)
2460
+
2461
+ # test replace the plot of pyTA of the polarity by this one --> should be quite easy TODO I think --> TODO
2462
+
2463
+ # ok --> it all seems to work --> see how I can handle that
2464
+
2465
+
2466
+
2467
+ # --> all seems ok now
2468
+ # --> put this in the advanced sql plotter
2469
+