batoolset 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batoolset/GUI/__init__.py +0 -0
- batoolset/GUI/list_gui.py +513 -0
- batoolset/GUI/multi_image_display.py +989 -0
- batoolset/GUI/open.py +325 -0
- batoolset/GUI/overlay_hints.py +45 -0
- batoolset/GUI/paint2.py +1345 -0
- batoolset/GUI/scrollablepaint.py +1152 -0
- batoolset/IJ_ROIs/__init__.py +0 -0
- batoolset/IJ_ROIs/tools.py +40 -0
- batoolset/PATH/__init__.py +0 -0
- batoolset/PATH/tools.py +79 -0
- batoolset/SQLite_tools/__init__.py +0 -0
- batoolset/SQLite_tools/tools.py +840 -0
- batoolset/__init__.py +0 -0
- batoolset/binary/__init__.py +0 -0
- batoolset/binary/tools.py +123 -0
- batoolset/binarytools/__init__.py +0 -0
- batoolset/binarytools/cell_center_detector.py +290 -0
- batoolset/clipboard_tools/__init__.py +0 -0
- batoolset/clipboard_tools/tools.py +7 -0
- batoolset/colors/__init__.py +0 -0
- batoolset/colors/colorgen.py +193 -0
- batoolset/colors/tools.py +22 -0
- batoolset/command_line/__init__.py +0 -0
- batoolset/command_line/tools.py +18 -0
- batoolset/dialogs/__init__.py +0 -0
- batoolset/dialogs/opensave.py +228 -0
- batoolset/dicts/__init__.py +0 -0
- batoolset/dicts/tools.py +299 -0
- batoolset/dims/__init__.py +0 -0
- batoolset/dims/tools.py +83 -0
- batoolset/draw/__init__.py +0 -0
- batoolset/draw/shapes/Position.py +183 -0
- batoolset/draw/shapes/__init__.py +0 -0
- batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
- batoolset/draw/shapes/circle2d.py +99 -0
- batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
- batoolset/draw/shapes/ellipse2d.py +336 -0
- batoolset/draw/shapes/freehand2d.py +235 -0
- batoolset/draw/shapes/group.py +1830 -0
- batoolset/draw/shapes/image2d.py +2469 -0
- batoolset/draw/shapes/line2d.py +575 -0
- batoolset/draw/shapes/point2d.py +257 -0
- batoolset/draw/shapes/polygon2d.py +357 -0
- batoolset/draw/shapes/polyline2d.py +235 -0
- batoolset/draw/shapes/rect2d.py +388 -0
- batoolset/draw/shapes/rectangle2d.py +601 -0
- batoolset/draw/shapes/scalebar.py +304 -0
- batoolset/draw/shapes/serializablefont.py +126 -0
- batoolset/draw/shapes/serializableguiparams.py +10 -0
- batoolset/draw/shapes/square2d.py +103 -0
- batoolset/draw/shapes/textorientation.py +45 -0
- batoolset/draw/shapes/tools.py +467 -0
- batoolset/draw/shapes/transform2D.py +350 -0
- batoolset/draw/shapes/txt2d.py +550 -0
- batoolset/draw/widgets/__init__.py +0 -0
- batoolset/draw/widgets/col.py +343 -0
- batoolset/draw/widgets/image_group.py +248 -0
- batoolset/draw/widgets/image_organizer.py +96 -0
- batoolset/draw/widgets/paint.py +184 -0
- batoolset/draw/widgets/vectorial.py +257 -0
- batoolset/figure/__init__.py +0 -0
- batoolset/figure/alignment.py +814 -0
- batoolset/figure/column.py +1117 -0
- batoolset/figure/fig_tools.py +285 -0
- batoolset/figure/minimal_debugger_scale_etc.py +139 -0
- batoolset/figure/panel.py +697 -0
- batoolset/figure/row.py +852 -0
- batoolset/figure/user_selection.py +287 -0
- batoolset/files/__init__.py +0 -0
- batoolset/files/tools.py +1321 -0
- batoolset/images/__init__.py +0 -0
- batoolset/images/tools.py +81 -0
- batoolset/img.py +5216 -0
- batoolset/linux/__init__.py +0 -0
- batoolset/linux/tools.py +72 -0
- batoolset/lists/__init__.py +0 -0
- batoolset/lists/tools.py +436 -0
- batoolset/luts/__init__.py +0 -0
- batoolset/luts/lut_minimal_test.py +1207 -0
- batoolset/matplolib/__init__.py +0 -0
- batoolset/matplolib/tools.py +44 -0
- batoolset/np_tools/__init__.py +0 -0
- batoolset/np_tools/tools.py +252 -0
- batoolset/np_tools/trash.py +15 -0
- batoolset/pandas_tools/__init__.py +0 -0
- batoolset/pandas_tools/tools.py +880 -0
- batoolset/partials/__init__.py +0 -0
- batoolset/partials/tools.py +83 -0
- batoolset/plot_tools/__init__.py +0 -0
- batoolset/plot_tools/tools.py +70 -0
- batoolset/pyqt/__init__.py +0 -0
- batoolset/pyqt/tools.py +691 -0
- batoolset/re_tools/__init__.py +0 -0
- batoolset/re_tools/tools.py +35 -0
- batoolset/serialization/__init__.py +0 -0
- batoolset/serialization/tools.py +1192 -0
- batoolset/sets/__init__.py +0 -0
- batoolset/sets/tools.py +109 -0
- batoolset/settings/__init__.py +0 -0
- batoolset/settings/global_settings.py +134 -0
- batoolset/strings/__init__.py +0 -0
- batoolset/strings/tools.py +225 -0
- batoolset/ta/__init__.py +0 -0
- batoolset/ta/database/__init__.py +0 -0
- batoolset/ta/database/advanced_sql_plotter.py +1170 -0
- batoolset/ta/database/my_qt_table.py +79 -0
- batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
- batoolset/ta/database/sql.py +2141 -0
- batoolset/ta/database/sql_column_name_dialog.py +122 -0
- batoolset/ta/measurements/TAmeasures.py +2226 -0
- batoolset/ta/measurements/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
- batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
- batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
- batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
- batoolset/ta/measurements/nematic.py +562 -0
- batoolset/ta/measurements/nematic_measurements.py +107 -0
- batoolset/ta/segmentation/__init__.py +0 -0
- batoolset/ta/segmentation/neo_wshed.py +314 -0
- batoolset/ta/selections/__init__.py +0 -0
- batoolset/ta/selections/selection.py +329 -0
- batoolset/ta/tracking/__init__.py +0 -0
- batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
- batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
- batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
- batoolset/ta/tracking/registration.py +108 -0
- batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
- batoolset/ta/tracking/track_correction.py +238 -0
- batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
- batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
- batoolset/ta/utils/TA_tools.py +43 -0
- batoolset/ta/utils/__init__.py +0 -0
- batoolset/ta/utils/rps_tools.py +1783 -0
- batoolset/tests/__init__.py +0 -0
- batoolset/tests/rps_tools.py +51 -0
- batoolset/tests/tools.py +10 -0
- batoolset/tools/__init__.py +0 -0
- batoolset/tools/early_stopper_class.py +9 -0
- batoolset/tools/guided_data_recoverer.py +110 -0
- batoolset/tools/logger.py +100 -0
- batoolset/tools/mytimer.py +31 -0
- batoolset/tools/prompt_color.py +160 -0
- batoolset/tools/qthandler.py +118 -0
- batoolset/uitools/__init__.py +0 -0
- batoolset/uitools/blinker.py +56 -0
- batoolset/unsafe/__init__.py +0 -0
- batoolset/unsafe/tools.py +115 -0
- batoolset/utils/__init__.py +0 -0
- batoolset/utils/commontools.py +158 -0
- batoolset/utils/loadlist.py +728 -0
- batoolset/version.py +10 -0
- batoolset/xml/__init__.py +0 -0
- batoolset/xml/tools.py +65 -0
- batoolset/zip_tools/__init__.py +0 -0
- batoolset/zip_tools/tools.py +76 -0
- batoolset-0.1.0.dist-info/LICENSE +24 -0
- batoolset-0.1.0.dist-info/METADATA +45 -0
- batoolset-0.1.0.dist-info/RECORD +162 -0
- batoolset-0.1.0.dist-info/WHEEL +5 -0
- batoolset-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
|