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,989 @@
|
|
|
1
|
+
# TODO need handle tracking and need break buttons to stop the loop
|
|
2
|
+
from batoolset.settings.global_settings import set_UI # set the UI to qtpy
|
|
3
|
+
set_UI()# none of the label drawings is great --> ignore t for now or maybe add it to the vectorial draw pane but do not allow edit and save of this and move it with the image!!!
|
|
4
|
+
import traceback
|
|
5
|
+
from functools import partial
|
|
6
|
+
import numpy as np
|
|
7
|
+
from qtpy import QtWidgets, QtGui, QtCore
|
|
8
|
+
from qtpy.QtCore import QRect, Qt, QSize
|
|
9
|
+
from qtpy.QtGui import QPalette
|
|
10
|
+
from qtpy.QtWidgets import QApplication, QGridLayout, QScrollArea, QLabel, QVBoxLayout, QDialog, QStyle, \
|
|
11
|
+
QStyleOptionTitleBar, QMessageBox, QDialogButtonBox
|
|
12
|
+
# from deprecated_demos.ta.wshed import Wshed
|
|
13
|
+
from batoolset.ta.segmentation.neo_wshed import wshed
|
|
14
|
+
# from batoolset.draw.widgets.paint import Createpaintwidget
|
|
15
|
+
from batoolset.img import Img, RGB_to_int24
|
|
16
|
+
from batoolset.draw.shapes.rect2d import Rect2D
|
|
17
|
+
|
|
18
|
+
# TODO add shortcuts to zoom and so on so that I can handle it better
|
|
19
|
+
# TODO maybe store the raw image in it
|
|
20
|
+
|
|
21
|
+
# in fact that is maybe already what I want!!!
|
|
22
|
+
# but I may also want to draw on it with a pen --> should have everything
|
|
23
|
+
|
|
24
|
+
# draw_mode= None #'pen' #'rect'
|
|
25
|
+
|
|
26
|
+
# fit to window or not
|
|
27
|
+
# maybe whatever happens the scroll should fit to window size
|
|
28
|
+
|
|
29
|
+
# NB in TA that works exactly as I want it !!!
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ok I finally have it
|
|
33
|
+
# implement all the zooms, crops, etc...
|
|
34
|
+
# --> TODO
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# TODO --> maybe add a title
|
|
38
|
+
# TODO maybe offer to set a title in setimage --> can be a good idea
|
|
39
|
+
# faire un get image also
|
|
40
|
+
# faire un get mask
|
|
41
|
+
# ...
|
|
42
|
+
# faire un get shapes --> pas mal
|
|
43
|
+
# peut etre aussi permettre le right click pr supprimer des images
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# TODO allow scroll all or zoom all at the same time or not!!!
|
|
47
|
+
# TODO allow support for shortcuts
|
|
48
|
+
# if sync shortcuts are for all, if not they are for the in focus stuff --> TODO
|
|
49
|
+
|
|
50
|
+
# vraiment tres bon mais à finaliser!!!
|
|
51
|
+
|
|
52
|
+
# TODO add labels --> would be really cool in a way
|
|
53
|
+
# maybe also add labels on the image ???
|
|
54
|
+
|
|
55
|
+
# TODO allow add labels on the sides --> like a table --> can be really useful!!!
|
|
56
|
+
from batoolset.GUI.paint2 import Createpaintwidget
|
|
57
|
+
from batoolset.ta.tracking.local_to_track_correspondance import add_localID_to_trackID_correspondance_in_DB
|
|
58
|
+
from batoolset.files.tools import smart_name_parser
|
|
59
|
+
from batoolset.ta.tracking.track_correction import swap_tracks, connect_tracks
|
|
60
|
+
from batoolset.tools.logger import TA_logger # logging
|
|
61
|
+
|
|
62
|
+
logger = TA_logger()
|
|
63
|
+
|
|
64
|
+
class ImgDisplayWindow(QDialog):
|
|
65
|
+
|
|
66
|
+
# replace preview only by a drawing mode --> TODO
|
|
67
|
+
# TODO try several displays at once then sync all
|
|
68
|
+
def __init__(self, parent_window=None, draw_mode=None, default_width=512, default_height=512, nb_rows=1, nb_cols=1,
|
|
69
|
+
synced=True , lst=None, cur_frame_idx=None):
|
|
70
|
+
super().__init__(parent=parent_window)
|
|
71
|
+
self.default_width = default_width
|
|
72
|
+
self.default_height = default_height
|
|
73
|
+
self.nb_rows = nb_rows
|
|
74
|
+
self.nb_cols = nb_cols
|
|
75
|
+
self.draw_mode = draw_mode
|
|
76
|
+
self.synced = synced
|
|
77
|
+
self.initUI()
|
|
78
|
+
self.lst = lst
|
|
79
|
+
self.cur_frame_idx = cur_frame_idx
|
|
80
|
+
|
|
81
|
+
# self.last_propagated_mouse_event = None
|
|
82
|
+
|
|
83
|
+
# override the QDialoog keyPressEvent to prevent it from stealing the return key from the drawing panels
|
|
84
|
+
def keyPressEvent(self, event):
|
|
85
|
+
|
|
86
|
+
# print('key intercepted ') # can I pass it to the paint in focus ??? next ????
|
|
87
|
+
|
|
88
|
+
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
|
|
89
|
+
self.apply()
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
# else:
|
|
95
|
+
#
|
|
96
|
+
# print('in there')
|
|
97
|
+
# idx = self.get_idx_of_widget_under_mouse()
|
|
98
|
+
# # dirty hack to transfer shortcut to child
|
|
99
|
+
# if idx != -1:
|
|
100
|
+
# print('passing')
|
|
101
|
+
# # print('applying in', idx)
|
|
102
|
+
# if idx in [0,1,2]:
|
|
103
|
+
# print('forwarding')
|
|
104
|
+
# self.paints[idx].keyPressEvent(event)
|
|
105
|
+
super().keyPressEvent(event)
|
|
106
|
+
|
|
107
|
+
def initUI(self):
|
|
108
|
+
self.is_width_for_alternating = True
|
|
109
|
+
self.scale = 1.0
|
|
110
|
+
self.scroll_areas = []
|
|
111
|
+
self.paints = []
|
|
112
|
+
self.labels = []
|
|
113
|
+
# TODO should I also store the labels ???
|
|
114
|
+
|
|
115
|
+
layout = QGridLayout()
|
|
116
|
+
layout.setSpacing(0)
|
|
117
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
118
|
+
|
|
119
|
+
# maybe set one master that can propagate the event to all others
|
|
120
|
+
|
|
121
|
+
# TODO draw a label directly on the scrollpane --> need override its paint method
|
|
122
|
+
# if self.label is not None:
|
|
123
|
+
# # formato
|
|
124
|
+
# canvasPainter.setPen(QColor("Green"))
|
|
125
|
+
# canvasPainter.setFont(QFont('Helvetica', 48))
|
|
126
|
+
# print('drawing text')
|
|
127
|
+
# canvasPainter.drawText(50, 50, self.label)
|
|
128
|
+
|
|
129
|
+
for jjj in range(self.nb_rows):
|
|
130
|
+
for iii in range(self.nb_cols):
|
|
131
|
+
|
|
132
|
+
# ça marche pas mal est ce plus simple outside ????
|
|
133
|
+
# or add a label directly on the image at various position just by drawing it in the paint window --> can also be very interesting!!!
|
|
134
|
+
main_container = QVBoxLayout()
|
|
135
|
+
# need store this in arrays
|
|
136
|
+
# scrollArea = QScrollArea()
|
|
137
|
+
scrollArea = QScrollArea()
|
|
138
|
+
|
|
139
|
+
# maybe add a vertical stuff above
|
|
140
|
+
|
|
141
|
+
# try add a Qlabel on top of it to label things up
|
|
142
|
+
|
|
143
|
+
label = QLabel('')
|
|
144
|
+
self.labels.append(label)
|
|
145
|
+
|
|
146
|
+
scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
147
|
+
scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
148
|
+
|
|
149
|
+
# ui->scrollArea->verticalScrollBar()->isVisible();
|
|
150
|
+
# self.setMouseTracking(not self.preview_only) # I was doing crazy stuff here --> rather keep the draing mode in the inner object --> much simpler to handle
|
|
151
|
+
|
|
152
|
+
paint = Createpaintwidget()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
paint.vdp.active = False
|
|
156
|
+
paint.vdp.drawing_mode = True
|
|
157
|
+
|
|
158
|
+
paint.force_cursor_visible = True # required to force activate cursor to be visible
|
|
159
|
+
if self.synced:
|
|
160
|
+
paint.propagate_mouse_move = True
|
|
161
|
+
paint.propagate_mouse_event = self.mouse_move_multihandler
|
|
162
|
+
|
|
163
|
+
# code to override apply
|
|
164
|
+
if True:
|
|
165
|
+
if jjj == 0:
|
|
166
|
+
# save a function with its partial
|
|
167
|
+
paint.apply = partial(self.custom_apply, paint)
|
|
168
|
+
elif jjj == 1:
|
|
169
|
+
paint.apply = partial(self.get_colors_drawn_over, paint)
|
|
170
|
+
else:
|
|
171
|
+
paint.vdp.drawing_mode = False
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if self.draw_mode == 'rect':
|
|
175
|
+
paint.vdp.active = True
|
|
176
|
+
paint.vdp.shape_to_draw = Rect2D
|
|
177
|
+
# removing this makes it paint drawable --> really easy in fact
|
|
178
|
+
# print(self.draw_mode is not None)
|
|
179
|
+
paint.setMouseTracking(self.draw_mode is not None) # KEEP IMPORTANT
|
|
180
|
+
paint.drawing = False # is that useful, I don't like it --> make it better
|
|
181
|
+
|
|
182
|
+
# print(self.paint.hasMouseTracking())
|
|
183
|
+
# self.paint.drawing = False # why always true
|
|
184
|
+
# self.paint.mouseMoveEvent = self.mouseMoveEvent
|
|
185
|
+
# self.paint.mousePressEvent = self.mousePressEvent
|
|
186
|
+
# self.paint.mouseReleaseEvent = self.mouseReleaseEvent
|
|
187
|
+
|
|
188
|
+
# self.prev_width = 192
|
|
189
|
+
# self.prev_height = 192
|
|
190
|
+
|
|
191
|
+
scrollArea.setGeometry(QRect(0, 0, self.default_width, self.default_height))
|
|
192
|
+
scrollArea.setBackgroundRole(QPalette.Dark)
|
|
193
|
+
scrollArea.setWidgetResizable(
|
|
194
|
+
False) # allows it to resize when the parent window is resized --> force a new repaint
|
|
195
|
+
|
|
196
|
+
if self.synced:
|
|
197
|
+
scrollArea.verticalScrollBar().valueChanged.connect(self.scroll_vertically_together)
|
|
198
|
+
scrollArea.horizontalScrollBar().valueChanged.connect(self.scroll_horizontally_together)
|
|
199
|
+
|
|
200
|
+
scrollArea.setWidget(paint)
|
|
201
|
+
paint.scrollArea = scrollArea
|
|
202
|
+
# self.paint.setBaseSize(512,512)
|
|
203
|
+
|
|
204
|
+
self.scroll_areas.append(scrollArea)
|
|
205
|
+
self.paints.append(paint)
|
|
206
|
+
|
|
207
|
+
self.setGeometry(QRect(0, 0, self.default_width, self.default_height))
|
|
208
|
+
|
|
209
|
+
main_container.addWidget(label)
|
|
210
|
+
main_container.addWidget(scrollArea)
|
|
211
|
+
|
|
212
|
+
# layout.addWidget(scrollArea,jjj,iii)
|
|
213
|
+
layout.addLayout(main_container, jjj, iii)
|
|
214
|
+
# self.setFixedSize(self.size()) # I actually want it resizable and upon resize change image size accordingly
|
|
215
|
+
|
|
216
|
+
# see how to define scrollarea size in the window --> that is really key though --> I think I have a bug maybe check in top TA
|
|
217
|
+
# add shortcuts --> needs love
|
|
218
|
+
# if self.synced:
|
|
219
|
+
self.add_shortcuts() # ça marche --> mais à améliorer if in sync remove self stuff
|
|
220
|
+
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
221
|
+
|
|
222
|
+
# dirty way of doing this but ok!!!
|
|
223
|
+
self.buttonBox.button(QDialogButtonBox.Ok).setText("Next")
|
|
224
|
+
self.buttonBox.button(QDialogButtonBox.Cancel).setText("Stop")
|
|
225
|
+
|
|
226
|
+
self.buttonBox.accepted.connect(self.accept_if_clicked)
|
|
227
|
+
self.buttonBox.rejected.connect(self.reject)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# self.buttonBox.button(QDialogButtonBox.Ok).setAutoDefault(False)
|
|
232
|
+
# self.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False)
|
|
233
|
+
# self.buttonBox.button(QDialogButtonBox.Ok).setDefault(False)
|
|
234
|
+
# self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(False)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
layout.addWidget(self.buttonBox)
|
|
239
|
+
|
|
240
|
+
self.setLayout(layout)
|
|
241
|
+
|
|
242
|
+
# dirty hack to ignore enter pressed
|
|
243
|
+
def accept_if_clicked(self):
|
|
244
|
+
if not self.buttonBox.button(QDialogButtonBox.Ok).hasFocus():
|
|
245
|
+
return
|
|
246
|
+
return self.accept()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def get_colors_drawn_over(self, paint):
|
|
250
|
+
mask = paint.get_user_drawing()
|
|
251
|
+
# print('I was called')
|
|
252
|
+
if mask is None:
|
|
253
|
+
return
|
|
254
|
+
# print(mask)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# need get all the colors of the raw image that correspond to user selection
|
|
258
|
+
selected_colors =paint.get_raw_image()[mask!=0]
|
|
259
|
+
selected_colors = RGB_to_int24(selected_colors)
|
|
260
|
+
|
|
261
|
+
selected_colors =set(selected_colors.ravel().tolist())
|
|
262
|
+
if 0xFFFFFF in selected_colors:
|
|
263
|
+
selected_colors.remove(0xFFFFFF)
|
|
264
|
+
selected_colors = list(selected_colors)
|
|
265
|
+
|
|
266
|
+
# print('colors', selected_colors) #.ravel()
|
|
267
|
+
#neee=d get the colors as RGB single values --> convert the output to RGB
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ned convert to
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# plt.imshow(mask)
|
|
275
|
+
# plt.show()
|
|
276
|
+
|
|
277
|
+
# paint.set_mask(None)# reset the mask
|
|
278
|
+
# paint.set_mask(None)
|
|
279
|
+
# paint.reset_user_drawing()
|
|
280
|
+
# paint.raw_user_drawing.fill(QtCore.Qt.transparent)
|
|
281
|
+
|
|
282
|
+
# this is the only way I found to reset the user drawing but this is dirty and I must find a better way
|
|
283
|
+
paint.set_mask(np.zeros_like(mask))
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# marche pas...Why ?
|
|
287
|
+
# paint.raw_user_drawing = QtGui.QImage(paint.imageDraw.size(), QtGui.QImage.Format_ARGB32)
|
|
288
|
+
# paint.raw_user_drawing.fill(QtCore.Qt.transparent) # somehow this is really required to have an empty image otherwise it is not --> ??? why
|
|
289
|
+
|
|
290
|
+
# test = paint.get_user_drawing()
|
|
291
|
+
# plt.imshow(test)
|
|
292
|
+
# plt.show()
|
|
293
|
+
# update drawing after user input has been reset
|
|
294
|
+
|
|
295
|
+
paint.update()
|
|
296
|
+
return selected_colors
|
|
297
|
+
|
|
298
|
+
def custom_apply(self, paint):
|
|
299
|
+
# print('you called me', paint, filename)
|
|
300
|
+
# print(QtGui.QImageWriter.supportedImageFormats())
|
|
301
|
+
# save as tiff ???
|
|
302
|
+
|
|
303
|
+
# drawn_img = paint.imageDraw
|
|
304
|
+
|
|
305
|
+
# ça marche mais c'est vraiment juste le dessin --> faudrait donc que je passe le masque entier en mask et pas seulement ça --> voir ds TA comment faire en fait et aussi faire que le mask et le wshed tourne là dedans en fait car smarter
|
|
306
|
+
|
|
307
|
+
# drawn_img.save("/E/Sample_images/sample_images_PA/trash/image.png", "PNG", -1)
|
|
308
|
+
# drawn_img.save("/E/Sample_images/sample_images_PA/trash/image.tif", "TIF", -1)
|
|
309
|
+
# print('saving',"/E/Sample_images/sample_images_PA/trash/image.tif")
|
|
310
|
+
# print(drawn_img)
|
|
311
|
+
|
|
312
|
+
# nb will only work for red pen !!!! --> do not allow to change pen color
|
|
313
|
+
mask = paint.get_mask()
|
|
314
|
+
if mask is None:
|
|
315
|
+
return
|
|
316
|
+
# TODO would need to run the wshed on it --> finally fix or recode my wshed can be done with numba
|
|
317
|
+
|
|
318
|
+
# ça marche en fait maintenant --> utiliser ça du coup!!!
|
|
319
|
+
# d'ailleurs verif ce code et le nettoyer une bonne fois pr toute et le mettre dans paint et dans mask d'ailleurs...
|
|
320
|
+
# if nothing is saved image become compeletely full of white
|
|
321
|
+
mask = wshed(mask, seeds='mask')
|
|
322
|
+
|
|
323
|
+
# print('saving filename',filename)
|
|
324
|
+
# plt.imshow(mask[...,2])
|
|
325
|
+
# plt.show()
|
|
326
|
+
# Img(mask, dimensions='hw').save(filename)
|
|
327
|
+
|
|
328
|
+
# update the mask
|
|
329
|
+
# nb maybe only save on demand each ??? --> think what is the best way to do that
|
|
330
|
+
paint.set_mask(mask)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# Img(img, dimensions='hwc').save(filename, mode='raw')
|
|
334
|
+
|
|
335
|
+
def mouse_move_multihandler(self, event):
|
|
336
|
+
# pb is I need get the sender not to send it it all
|
|
337
|
+
# very dirty and infinite loop --> see how to do that for all
|
|
338
|
+
# print('toto', self, event)
|
|
339
|
+
# just get it to draw the cursor --> no infinite propagation then
|
|
340
|
+
# if event == self.last_propagated_mouse_event:
|
|
341
|
+
# return
|
|
342
|
+
# idx = self.get_idx_of_widget_under_mouse()
|
|
343
|
+
# print(idx)
|
|
344
|
+
# copied_event = event
|
|
345
|
+
# if event.type() == QEvent.MouseMove:
|
|
346
|
+
# copied_event = event.pos()
|
|
347
|
+
# copied_event = event.copy()
|
|
348
|
+
|
|
349
|
+
# copied_event = QMouseEvent()
|
|
350
|
+
# self.receiver.move(event.pos())
|
|
351
|
+
for iii, paint in enumerate(self.paints):
|
|
352
|
+
try:
|
|
353
|
+
# if iii != idx:
|
|
354
|
+
# MEGA TODO: SUPER ULTIMATE DIRTY HACK FOR MOUSE EVENT PROPAGATION THROUGH PAINT WIDGETS THINK ABOUT A SMARTER WAY TO DO THAT IN THE FUTURE yet ok for now...
|
|
355
|
+
paint.propagate_mouse_move = False
|
|
356
|
+
# paint.mouseMove(copied_event) #Event( event)
|
|
357
|
+
# paint.mouseMoveEvent(copied_event) #Event( event)
|
|
358
|
+
paint.mouseMoveEvent(event) # Event( event)
|
|
359
|
+
paint.propagate_mouse_move = True
|
|
360
|
+
# self.last_propagated_mouse_event = event
|
|
361
|
+
except:
|
|
362
|
+
traceback.print_exc()
|
|
363
|
+
pass
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# could add the same shortcuts within individual panels
|
|
367
|
+
def add_shortcuts(self):
|
|
368
|
+
# if True:
|
|
369
|
+
# return
|
|
370
|
+
zoomPlus = QtWidgets.QShortcut("Ctrl+Shift+=", self)
|
|
371
|
+
zoomPlus.activated.connect(self.zoomIn)
|
|
372
|
+
zoomPlus.setContext(QtCore.Qt.ApplicationShortcut)
|
|
373
|
+
|
|
374
|
+
zoomPlus2 = QtWidgets.QShortcut("Ctrl++", self)
|
|
375
|
+
zoomPlus2.activated.connect(self.zoomIn)
|
|
376
|
+
zoomPlus2.setContext(QtCore.Qt.ApplicationShortcut)
|
|
377
|
+
|
|
378
|
+
zoomMinus = QtWidgets.QShortcut("Ctrl+Shift+-", self)
|
|
379
|
+
zoomMinus.activated.connect(self.zoomOut)
|
|
380
|
+
zoomMinus.setContext(QtCore.Qt.ApplicationShortcut)
|
|
381
|
+
|
|
382
|
+
zoomMinus2 = QtWidgets.QShortcut("Ctrl+-", self)
|
|
383
|
+
zoomMinus2.activated.connect(self.zoomOut)
|
|
384
|
+
zoomMinus2.setContext(QtCore.Qt.ApplicationShortcut)
|
|
385
|
+
|
|
386
|
+
# dirty reimplementation of the shortcuts --> TODO recode that better some day --> but ok for now
|
|
387
|
+
enterShortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Return), self)
|
|
388
|
+
enterShortcut.activated.connect(self.apply)
|
|
389
|
+
enterShortcut.setContext(QtCore.Qt.ApplicationShortcut)
|
|
390
|
+
|
|
391
|
+
enterShortcut2 = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Enter), self)
|
|
392
|
+
enterShortcut2.activated.connect(self.apply)
|
|
393
|
+
enterShortcut2.setContext(QtCore.Qt.ApplicationShortcut)
|
|
394
|
+
|
|
395
|
+
shrtM = QtWidgets.QShortcut("M", self)
|
|
396
|
+
shrtM.activated.connect(self.m_apply)
|
|
397
|
+
shrtM.setContext(QtCore.Qt.ApplicationShortcut)
|
|
398
|
+
|
|
399
|
+
ctrlM = QtWidgets.QShortcut("Ctrl+M", self)
|
|
400
|
+
ctrlM.activated.connect(self.ctrl_m_apply)
|
|
401
|
+
ctrlM.setContext(QtCore.Qt.ApplicationShortcut)
|
|
402
|
+
|
|
403
|
+
# pkoi ça ne marche pas !!!
|
|
404
|
+
shiftEnterShortcut = QtWidgets.QShortcut("Shift+Enter", self)
|
|
405
|
+
shiftEnterShortcut.activated.connect(self.shift_apply)
|
|
406
|
+
shiftEnterShortcut.setContext(QtCore.Qt.ApplicationShortcut)
|
|
407
|
+
#
|
|
408
|
+
shiftEnterShortcut2 = QtWidgets.QShortcut("Shift+Return", self)
|
|
409
|
+
shiftEnterShortcut2.activated.connect(self.shift_apply)
|
|
410
|
+
shiftEnterShortcut2.setContext(QtCore.Qt.ApplicationShortcut)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def take_intelligent_decision_for_track_editing_based_on_user_selection(self, selected_cells_prev, selected_cells_cur, selected_cells_next):
|
|
415
|
+
# if selected_cells_prev is not None and selected_cells_prev:
|
|
416
|
+
# # check how many cells have been selected and take action depending in the stuff
|
|
417
|
+
# pass
|
|
418
|
+
# if selected_cells_cur is not None and selected_cells_cur:
|
|
419
|
+
# pass
|
|
420
|
+
# if selected_cells_next is not None and selected_cells_next:
|
|
421
|
+
# pass
|
|
422
|
+
if selected_cells_next is None:
|
|
423
|
+
selected_cells_next = []
|
|
424
|
+
if selected_cells_prev is None:
|
|
425
|
+
selected_cells_prev = []
|
|
426
|
+
if selected_cells_cur is None:
|
|
427
|
+
selected_cells_cur = []
|
|
428
|
+
|
|
429
|
+
if len(selected_cells_prev) + len(selected_cells_cur) + len(selected_cells_next) != 2:
|
|
430
|
+
# print('Error: ')
|
|
431
|
+
if len(selected_cells_prev) + len(selected_cells_cur) + len(selected_cells_next) < 2:
|
|
432
|
+
# not enough cells --> need add more
|
|
433
|
+
self._show_warning_msage(text='Not enough cells drawn over, please click on exactly two cells')
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
# print(len(selected_cells_prev) + len(selected_cells_cur) + len(selected_cells_next))
|
|
437
|
+
# print(selected_cells_next, selected_cells_prev, selected_cells_cur)
|
|
438
|
+
self._show_warning_msage()
|
|
439
|
+
return
|
|
440
|
+
|
|
441
|
+
# the user has drawn properly over the image --> some decision can be taken then
|
|
442
|
+
# --> do the stuff
|
|
443
|
+
|
|
444
|
+
# sqsqdsqsqdqsdqsdqsd
|
|
445
|
+
|
|
446
|
+
# if both are in the same then do swap otherwise try connect cells
|
|
447
|
+
# I also need to know the current frame --> TODO
|
|
448
|
+
|
|
449
|
+
# I need have the list and the current frame
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
path_to_cur_track = smart_name_parser(self.lst[self.cur_frame_idx], 'tracked_cells_resized.tif')
|
|
453
|
+
except:
|
|
454
|
+
path_to_cur_track = None
|
|
455
|
+
try:
|
|
456
|
+
path_to_next_track = smart_name_parser(self.lst[self.cur_frame_idx+1], 'tracked_cells_resized.tif')
|
|
457
|
+
except:
|
|
458
|
+
path_to_next_track = None
|
|
459
|
+
try:
|
|
460
|
+
path_to_prev_track = smart_name_parser(self.lst[self.cur_frame_idx-1], 'tracked_cells_resized.tif')
|
|
461
|
+
except:
|
|
462
|
+
path_to_prev_track = None
|
|
463
|
+
|
|
464
|
+
something_changed = False
|
|
465
|
+
if len(selected_cells_cur) == 2:
|
|
466
|
+
# launch a swap starting from the current image onwards
|
|
467
|
+
# start swapping fropm current idx onwards
|
|
468
|
+
something_changed = True
|
|
469
|
+
swap_tracks(self.lst, self.cur_frame_idx, selected_cells_cur[0],selected_cells_cur[1])
|
|
470
|
+
# check the images that need be reloaded and update them
|
|
471
|
+
# I need update cur and next image
|
|
472
|
+
if path_to_cur_track is not None:
|
|
473
|
+
img = Img(path_to_cur_track)
|
|
474
|
+
self.paints[4].set_image(img)
|
|
475
|
+
if path_to_next_track is not None:
|
|
476
|
+
img = Img(path_to_next_track)
|
|
477
|
+
self.paints[5].set_image(img)
|
|
478
|
+
|
|
479
|
+
# need reload two images
|
|
480
|
+
if len(selected_cells_next) == 2:
|
|
481
|
+
something_changed = True
|
|
482
|
+
swap_tracks(self.lst, self.cur_frame_idx+1, selected_cells_next[0], selected_cells_next[1])
|
|
483
|
+
|
|
484
|
+
if path_to_next_track is not None:
|
|
485
|
+
img = Img(path_to_next_track)
|
|
486
|
+
self.paints[5].set_image(img)
|
|
487
|
+
|
|
488
|
+
if len(selected_cells_prev) == 2:
|
|
489
|
+
something_changed = True
|
|
490
|
+
swap_tracks(self.lst, self.cur_frame_idx-1, selected_cells_prev[0], selected_cells_prev[1])
|
|
491
|
+
|
|
492
|
+
if path_to_prev_track is not None:
|
|
493
|
+
img = Img(path_to_prev_track)
|
|
494
|
+
self.paints[3].set_image(img)
|
|
495
|
+
if path_to_cur_track is not None:
|
|
496
|
+
img = Img(path_to_cur_track)
|
|
497
|
+
self.paints[4].set_image(img)
|
|
498
|
+
if path_to_next_track is not None:
|
|
499
|
+
img = Img(path_to_next_track)
|
|
500
|
+
self.paints[5].set_image(img)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
if len(selected_cells_cur)==1:
|
|
504
|
+
if len(selected_cells_prev)==1:
|
|
505
|
+
something_changed = True
|
|
506
|
+
connect_tracks(self.lst, self.cur_frame_idx, selected_cells_cur[0],selected_cells_prev[0])
|
|
507
|
+
if path_to_cur_track is not None:
|
|
508
|
+
img = Img(path_to_cur_track)
|
|
509
|
+
self.paints[4].set_image(img)
|
|
510
|
+
if path_to_next_track is not None:
|
|
511
|
+
img = Img(path_to_next_track)
|
|
512
|
+
self.paints[5].set_image(img)
|
|
513
|
+
|
|
514
|
+
if len(selected_cells_next)==1:
|
|
515
|
+
something_changed = True
|
|
516
|
+
connect_tracks(self.lst, self.cur_frame_idx+1, selected_cells_cur[0],selected_cells_next[0])
|
|
517
|
+
|
|
518
|
+
if path_to_next_track is not None:
|
|
519
|
+
img = Img(path_to_next_track)
|
|
520
|
+
self.paints[5].set_image(img)
|
|
521
|
+
else:
|
|
522
|
+
logger.error('Please try to connect tracks across two consecutive frames, not between images distant from one another')
|
|
523
|
+
|
|
524
|
+
# TODO better check the something changed to avoid unnecessary db updates that may be lengthy
|
|
525
|
+
if something_changed:
|
|
526
|
+
# need update the track to local ID correpsondace
|
|
527
|
+
add_localID_to_trackID_correspondance_in_DB(self.lst)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def shift_apply(self):
|
|
532
|
+
idx = self.get_idx_of_widget_under_mouse()
|
|
533
|
+
# dirty hack to transfer shortcut to child
|
|
534
|
+
if idx != -1:
|
|
535
|
+
# print('applying in', idx)
|
|
536
|
+
if idx in [0,1,2]:
|
|
537
|
+
self.paints[idx].apply_drawing(minimal_cell_size=10)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# full of bugs --> deactivate it here ????
|
|
541
|
+
def ctrl_m_apply(self):
|
|
542
|
+
idx = self.get_idx_of_widget_under_mouse()
|
|
543
|
+
# dirty hack to transfer shortcut to child
|
|
544
|
+
if idx != -1:
|
|
545
|
+
# print('applying in', idx)
|
|
546
|
+
if idx in [0, 1, 2]:
|
|
547
|
+
try:
|
|
548
|
+
self.paints[idx].manually_reseeded_wshed()
|
|
549
|
+
except:
|
|
550
|
+
traceback.print_exc()
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def m_apply(self):
|
|
554
|
+
idx = self.get_idx_of_widget_under_mouse()
|
|
555
|
+
# dirty hack to transfer shortcut to child
|
|
556
|
+
if idx != -1:
|
|
557
|
+
# print('applying in', idx)
|
|
558
|
+
if idx in [0,1,2]:
|
|
559
|
+
self.paints[idx].m_apply()
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
# ça marche mais c'est super dirty je pense ???? en plus pr je ne sais quelle raison marche pas avec enter
|
|
565
|
+
# ça fera
|
|
566
|
+
def apply(self):
|
|
567
|
+
# print('apply method to override in main')
|
|
568
|
+
idx = self.get_idx_of_widget_under_mouse()
|
|
569
|
+
# dirty hack to transfer shortcut to child
|
|
570
|
+
if idx != -1:
|
|
571
|
+
# print('applying in', idx)
|
|
572
|
+
if idx not in [3,4,5]:
|
|
573
|
+
self.paints[idx].apply()
|
|
574
|
+
else:
|
|
575
|
+
# get swaps from all the images and take action if makes sense
|
|
576
|
+
# or warn for errors
|
|
577
|
+
selected_cells_prev = []
|
|
578
|
+
selected_cells_cur = []
|
|
579
|
+
selected_cells_next = []
|
|
580
|
+
if len(self.paints) >= 4:
|
|
581
|
+
selected_cells_prev = self.paints[3].apply()
|
|
582
|
+
if len(self.paints) >= 5:
|
|
583
|
+
selected_cells_cur = self.paints[4].apply()
|
|
584
|
+
if len(self.paints) >= 6:
|
|
585
|
+
selected_cells_next = self.paints[5].apply()
|
|
586
|
+
|
|
587
|
+
self.take_intelligent_decision_for_track_editing_based_on_user_selection(selected_cells_prev, selected_cells_cur, selected_cells_next)
|
|
588
|
+
|
|
589
|
+
def _show_warning_msage(self, text='Too many cells for track correction please just draw over two cells, no more...'):
|
|
590
|
+
msg = QMessageBox()
|
|
591
|
+
msg.setIcon(QMessageBox.Critical)
|
|
592
|
+
msg.setText("Error")
|
|
593
|
+
msg.setInformativeText(text)
|
|
594
|
+
msg.setWindowTitle("Error")
|
|
595
|
+
# msg.setDetailedText("Detailed explanation of the error: .......") # TODO maybe add more some day
|
|
596
|
+
retval = msg.exec_()
|
|
597
|
+
# in case I would need to do something with the retval but I gss I do
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
# print()
|
|
601
|
+
# allows the bars to scroll together --> quite good in fact
|
|
602
|
+
# do it also for horiz
|
|
603
|
+
def scroll_vertically_together(self, event):
|
|
604
|
+
# print(event)
|
|
605
|
+
for scrollbar in self.scroll_areas:
|
|
606
|
+
scrollbar.verticalScrollBar().setValue(event)
|
|
607
|
+
|
|
608
|
+
def scroll_horizontally_together(self, event):
|
|
609
|
+
# print(event)
|
|
610
|
+
for scrollbar in self.scroll_areas:
|
|
611
|
+
scrollbar.horizontalScrollBar().setValue(event)
|
|
612
|
+
|
|
613
|
+
def set_labels(self, *args):
|
|
614
|
+
for iii, label in enumerate(args):
|
|
615
|
+
self.set_label(label, nb=iii)
|
|
616
|
+
|
|
617
|
+
def set_images(self, *args):
|
|
618
|
+
for iii, img in enumerate(args):
|
|
619
|
+
self.set_image(img, nb=iii)
|
|
620
|
+
|
|
621
|
+
def set_label(self, label, nb=0):
|
|
622
|
+
if label is not None:
|
|
623
|
+
self.labels[nb].setText(label)
|
|
624
|
+
else:
|
|
625
|
+
self.labels[nb].setText('')
|
|
626
|
+
# paint = self.paints[nb]
|
|
627
|
+
# paint.vdp.shapes.clear()
|
|
628
|
+
# paint.set_image(img) # bug is here
|
|
629
|
+
# if img is None:
|
|
630
|
+
# paint.scale = self.scale = paint.vdp.scale = 1. # will that work ??? remove global scale ???
|
|
631
|
+
# else:
|
|
632
|
+
# max_size = min(self.prev_width / img.get_width(), self.prev_height / img.get_height())
|
|
633
|
+
# self.paint.scale = self.scale = self.paint.vdp.scale = max_size
|
|
634
|
+
|
|
635
|
+
# TODO handle scale nicely here
|
|
636
|
+
# also allow drawing over the image
|
|
637
|
+
|
|
638
|
+
# if paint.image is not None:
|
|
639
|
+
# paint.resize(self.scale * paint.image.size())
|
|
640
|
+
# self.scrollArea.resize(self.scale * self.paint.image.size())
|
|
641
|
+
# self.scrollArea.resize(self.size())
|
|
642
|
+
|
|
643
|
+
# print('self.paint.hasMouseTracking()', self.paint.hasMouseTracking())# how come it can track if no mouse tracking ???
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
# should I offer a crop of the image --> maybe also make it to store the relevant raw data --> can be very useful
|
|
648
|
+
# do a clear also for the image
|
|
649
|
+
def set_image(self, img, nb=0):
|
|
650
|
+
|
|
651
|
+
paint = self.paints[nb]
|
|
652
|
+
paint.vdp.shapes.clear()
|
|
653
|
+
paint.set_image(img) # bug is here
|
|
654
|
+
if img is None:
|
|
655
|
+
paint.scale = self.scale = paint.vdp.scale = 1. # will that work ??? remove global scale ???
|
|
656
|
+
# else:
|
|
657
|
+
# max_size = min(self.prev_width / img.get_width(), self.prev_height / img.get_height())
|
|
658
|
+
# self.paint.scale = self.scale = self.paint.vdp.scale = max_size
|
|
659
|
+
|
|
660
|
+
# TODO handle scale nicely here
|
|
661
|
+
# also allow drawing over the image
|
|
662
|
+
|
|
663
|
+
if paint.image is not None:
|
|
664
|
+
paint.resize(self.scale * paint.image.size())
|
|
665
|
+
# self.scrollArea.resize(self.scale * self.paint.image.size())
|
|
666
|
+
# self.scrollArea.resize(self.size())
|
|
667
|
+
|
|
668
|
+
# print('self.paint.hasMouseTracking()', self.paint.hasMouseTracking())# how come it can track if no mouse tracking ???
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
# clear image
|
|
673
|
+
def clear(self, nb=0):
|
|
674
|
+
self.set_image(None, nb=nb)
|
|
675
|
+
# print(self.size())
|
|
676
|
+
# self.scrollArea.resize(self.size())
|
|
677
|
+
size = self.size()
|
|
678
|
+
scrollArea = self.scroll_areas[nb]
|
|
679
|
+
scrollArea.setGeometry(QRect(0, 0, size.width(), size.height()))
|
|
680
|
+
paint = self.paints[nb]
|
|
681
|
+
paint.setGeometry(
|
|
682
|
+
QRect(0, 0, self.default_width - 2, self.default_height - 2)) # force all black and no scrollbars ---> ok
|
|
683
|
+
# self.scale = 1.
|
|
684
|
+
|
|
685
|
+
# ça marche --> ajouter fit to width ou fit to height
|
|
686
|
+
def fit_to_width_or_height(self):
|
|
687
|
+
if self.is_width_for_alternating:
|
|
688
|
+
self.fit_to_width()
|
|
689
|
+
else:
|
|
690
|
+
self.fit_to_height()
|
|
691
|
+
self.is_width_for_alternating = not self.is_width_for_alternating
|
|
692
|
+
|
|
693
|
+
# almost there but small bugs
|
|
694
|
+
def fit_to_width(self, nb=0):
|
|
695
|
+
paint = self.paints[nb]
|
|
696
|
+
if paint.image is None:
|
|
697
|
+
return
|
|
698
|
+
scrollArea = self.scroll_areas[nb]
|
|
699
|
+
width = scrollArea.width() - 2
|
|
700
|
+
width -= scrollArea.verticalScrollBar().sizeHint().width()
|
|
701
|
+
# height-=self.scrollArea.horizontalScrollBar().sizeHint().height()
|
|
702
|
+
width_im = paint.image.width()
|
|
703
|
+
scale = width / width_im
|
|
704
|
+
self.scaleImage(scale, nb=nb)
|
|
705
|
+
|
|
706
|
+
def fit_to_height(self, nb=0):
|
|
707
|
+
paint = self.paints[nb]
|
|
708
|
+
if paint.image is None:
|
|
709
|
+
return
|
|
710
|
+
scrollArea = self.scroll_areas[nb]
|
|
711
|
+
height = scrollArea.height() - 2
|
|
712
|
+
height -= scrollArea.horizontalScrollBar().sizeHint().height()
|
|
713
|
+
height_im = paint.image.height()
|
|
714
|
+
scale = height / height_im
|
|
715
|
+
self.scaleImage(scale, nb=nb)
|
|
716
|
+
|
|
717
|
+
def fit_to_window(self, nb=0):
|
|
718
|
+
# compute best fit
|
|
719
|
+
# just get the size of the stuff and best fit it
|
|
720
|
+
paint = self.paints[nb]
|
|
721
|
+
if paint.image is None:
|
|
722
|
+
return
|
|
723
|
+
|
|
724
|
+
# QScrollArea.ensureWidgetVisible(
|
|
725
|
+
# QScrollArea.ensureVisible
|
|
726
|
+
# QScrollArea.setWidgetResizable
|
|
727
|
+
scrollArea = self.scroll_areas[nb]
|
|
728
|
+
width = scrollArea.width() - 2 # required to make sure bars not visible
|
|
729
|
+
height = scrollArea.height() - 2
|
|
730
|
+
|
|
731
|
+
# scale image that it fits in --> change scale
|
|
732
|
+
|
|
733
|
+
# width-=self.scrollArea.verticalScrollBar().sizeHint().width()
|
|
734
|
+
# height-=self.scrollArea.horizontalScrollBar().sizeHint().height()
|
|
735
|
+
|
|
736
|
+
height_im = paint.image.height()
|
|
737
|
+
width_im = paint.image.width()
|
|
738
|
+
scale = height / height_im
|
|
739
|
+
if width / width_im < scale:
|
|
740
|
+
scale = width / width_im
|
|
741
|
+
self.scaleImage(scale, nb=nb)
|
|
742
|
+
|
|
743
|
+
# don't want a factor
|
|
744
|
+
def scaleImage(self, scale, nb=0):
|
|
745
|
+
paint = self.paints[nb]
|
|
746
|
+
self.scale = scale
|
|
747
|
+
if paint.image is not None:
|
|
748
|
+
paint.resize(self.scale * paint.image.size())
|
|
749
|
+
else:
|
|
750
|
+
# no image set size to 0, 0 --> scroll pane will auto adjust
|
|
751
|
+
paint.resize(QSize(0, 0))
|
|
752
|
+
# self.scale -= factor # reset zoom
|
|
753
|
+
|
|
754
|
+
paint.scale = self.scale
|
|
755
|
+
paint.vdp.scale = self.scale
|
|
756
|
+
paint.update()
|
|
757
|
+
|
|
758
|
+
# self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor)
|
|
759
|
+
# self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor)
|
|
760
|
+
|
|
761
|
+
# self.zoomInAct.setEnabled(self.scale < self.max_scaling_factor)
|
|
762
|
+
# self.zoomOutAct.setEnabled(self.scale > self.min_scaling_factor)
|
|
763
|
+
|
|
764
|
+
# synced version of scale
|
|
765
|
+
def scale_image_all(self, scale):
|
|
766
|
+
for img_nb in range(len(self.paints)):
|
|
767
|
+
self.scaleImage(scale, nb=img_nb)
|
|
768
|
+
|
|
769
|
+
#
|
|
770
|
+
# def fitToWindow(self):
|
|
771
|
+
# # TODO most likely a bug there because calls self.defaultSize that resets the scale --> MAKE NO SENSE
|
|
772
|
+
# fitToWindow = self.fitToWindowAct.isChecked()
|
|
773
|
+
# self.scrollArea.setWidgetResizable(fitToWindow)
|
|
774
|
+
# if not fitToWindow:
|
|
775
|
+
# self.defaultSize()
|
|
776
|
+
|
|
777
|
+
def zoomIn(self):
|
|
778
|
+
if self.synced:
|
|
779
|
+
self.scale_image_all(scale=self.scale + 0.1)
|
|
780
|
+
else:
|
|
781
|
+
idx = self.get_idx_of_widget_under_mouse()
|
|
782
|
+
if idx != -1:
|
|
783
|
+
self.scaleImage(self.scale + 0.1, nb=idx)
|
|
784
|
+
# get index
|
|
785
|
+
|
|
786
|
+
def zoomOut(self):
|
|
787
|
+
if self.synced:
|
|
788
|
+
self.scale_image_all(scale=self.scale - 0.1)
|
|
789
|
+
else:
|
|
790
|
+
idx = self.get_idx_of_widget_under_mouse()
|
|
791
|
+
if idx != -1:
|
|
792
|
+
self.scaleImage(self.scale - 0.1, nb=idx)
|
|
793
|
+
|
|
794
|
+
# get the index of the image under the mouse!!
|
|
795
|
+
def get_idx_of_widget_under_mouse(self):
|
|
796
|
+
pos = QtGui.QCursor.pos()
|
|
797
|
+
widget = QApplication.widgetAt(pos)
|
|
798
|
+
for iii, paint in enumerate(self.paints):
|
|
799
|
+
if widget == paint:
|
|
800
|
+
return iii
|
|
801
|
+
for iii, scrollarea in enumerate(self.scroll_areas):
|
|
802
|
+
if widget == scrollarea:
|
|
803
|
+
return iii
|
|
804
|
+
return -1
|
|
805
|
+
|
|
806
|
+
def get_paints(self):
|
|
807
|
+
return self.paints
|
|
808
|
+
|
|
809
|
+
@staticmethod
|
|
810
|
+
def display(parent=None, draw_mode=None, default_width=512, default_height=512, nb_rows=1, nb_cols=1, synced=True,
|
|
811
|
+
images=None, labels=None, lst=None, cur_frame_idx=None):
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
dialog = ImgDisplayWindow(parent, draw_mode=draw_mode, default_width=default_width,
|
|
815
|
+
default_height=default_height, nb_cols=nb_cols, nb_rows=nb_rows, synced=synced, lst=lst, cur_frame_idx=cur_frame_idx)
|
|
816
|
+
# dialog.showFullScreen() # pas mal mais vraiùment full screen --> can cause trouble --> maybe change this some day
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
# show dialog full screen without maximizing, see # https://stackoverflow.com/questions/15702470/how-to-make-a-window-that-occupies-the-full-screen-without-maximising
|
|
820
|
+
titleBarHeight = dialog.style().pixelMetric(
|
|
821
|
+
QStyle.PM_TitleBarHeight,
|
|
822
|
+
QStyleOptionTitleBar(),
|
|
823
|
+
dialog
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
# geometry = app.desktop().availableGeometry()
|
|
827
|
+
# geometry = QtWidgets.QDesktopWidget().availableGeometry()
|
|
828
|
+
# geometry.setHeight(geometry.height() - (titleBarHeight*2))
|
|
829
|
+
try:
|
|
830
|
+
geometry = QtWidgets.QDesktopWidget().availableGeometry()
|
|
831
|
+
geometry.setHeight(geometry.height() - (titleBarHeight * 2))
|
|
832
|
+
except:
|
|
833
|
+
from qtpy.QtGui import QGuiApplication
|
|
834
|
+
geometry = QGuiApplication.primaryScreen().geometry()
|
|
835
|
+
|
|
836
|
+
dialog.setGeometry(geometry)
|
|
837
|
+
# end show full screen without maximizing
|
|
838
|
+
|
|
839
|
+
if images is not None:
|
|
840
|
+
dialog.set_images(*images)
|
|
841
|
+
|
|
842
|
+
if labels is not None:
|
|
843
|
+
dialog.set_labels(*labels)
|
|
844
|
+
|
|
845
|
+
result = dialog.exec_()
|
|
846
|
+
augment = dialog.get_paints()
|
|
847
|
+
return (augment, result == QDialog.Accepted)
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
# does not work
|
|
851
|
+
# class labelled_scrollarea(QScrollArea):
|
|
852
|
+
#
|
|
853
|
+
# def __init__(self):
|
|
854
|
+
# super().__init__()
|
|
855
|
+
#
|
|
856
|
+
# # def paintEvent(self, event):
|
|
857
|
+
# # # ne marche pas car l'autre truc ecrit par dessus
|
|
858
|
+
# # # super().paintEvent(event)
|
|
859
|
+
# # super(labelled_scrollarea, self).paintEvent(event)
|
|
860
|
+
# #
|
|
861
|
+
# # # self.viewport().paintEvent(event)
|
|
862
|
+
# # # pb the widget overrides that --> need first paint it then call that
|
|
863
|
+
# # # print('inside')
|
|
864
|
+
# # painter = QtGui.QPainter(self.viewport())
|
|
865
|
+
# # # this will never work here --> really need be done in the other painter
|
|
866
|
+
# #
|
|
867
|
+
# # # painter = QPainter()
|
|
868
|
+
# # # painter.begin(self.viewport())
|
|
869
|
+
# # # self.drawRectangles(qp)
|
|
870
|
+
# # # painter.end()
|
|
871
|
+
# #
|
|
872
|
+
# # # painter = QPainter(self.viewport()) # If a widget has a viewport, you have to pass that to the QPainter constructor: https://stackoverflow.com/questions/12226930/overriding-qpaintevents-in-pyqt
|
|
873
|
+
# # # painter = QPainter(self) # If a widget has a viewport, you have to pass that to the QPainter constructor: https://stackoverflow.com/questions/12226930/overriding-qpaintevents-in-pyqt
|
|
874
|
+
# # # painter.setRenderHint(QPainter.Antialiasing)
|
|
875
|
+
# # painter.setPen(Qt.darkRed)
|
|
876
|
+
# # rectangle = QRectF(10.0, 20.0, 80.0, 60.0)
|
|
877
|
+
# # painter.drawRect(rectangle)
|
|
878
|
+
# #
|
|
879
|
+
# # painter.setPen(QColor("Green"))
|
|
880
|
+
# # painter.setFont(QFont('Helvetica', 48))
|
|
881
|
+
# # # print('drawing text')
|
|
882
|
+
# # painter.drawText(50, 50, 'this is a test of your system')
|
|
883
|
+
# #
|
|
884
|
+
# # painter.end()
|
|
885
|
+
# #
|
|
886
|
+
# # # scrollArea.paintEvent = paintEvent
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
if __name__ == '__main__':
|
|
890
|
+
# TODO --> last thing todo is to apply several 'apply' methods for each of the paint methods --> maybe simply override them ???
|
|
891
|
+
|
|
892
|
+
# TODO allow unknown number of files, sort of on demand creation --> clear and restore all paints and scrolls
|
|
893
|
+
# move most of the commands to the image panel itself
|
|
894
|
+
# allow crops
|
|
895
|
+
# add shortcuts
|
|
896
|
+
# offer synchronous pointer maybe ???
|
|
897
|
+
# offer drift maybe ???
|
|
898
|
+
# store the image directly in the object --> much smarter and easier to retrieve (maybe handle Z and or t stacks also from within)
|
|
899
|
+
# would be perfect to see corrections
|
|
900
|
+
# for corrections maybe first do the human one fully then try to automate things with automated solutions with accept of reject à la tinder --> TODO
|
|
901
|
+
# already showing the three original images with masks, the three tracks and the scores with prev and next for the current as well as new and lost cells should do most of the job
|
|
902
|
+
# later I can try automated corrections but quite ok already
|
|
903
|
+
# NB IDEALLY THE SCALE SHOULD ALSO BE SAVED DIRECTLY IN THE THING THAT DISPLAYS IMAGES
|
|
904
|
+
|
|
905
|
+
if True:
|
|
906
|
+
# try open this as a dialog
|
|
907
|
+
import sys
|
|
908
|
+
|
|
909
|
+
app = QApplication(sys.argv)
|
|
910
|
+
# TODO find a way to synchronize the mouse for all
|
|
911
|
+
|
|
912
|
+
# find a way to propagate mouse events from one to all
|
|
913
|
+
# ok = ImgDisplayWindow.display(draw_mode='pen', nb_cols=5, images=[
|
|
914
|
+
# Img('/E/Sample_images/sample_images_PA/egg_chambers/120404_division_EcadKI.lif - Series006_RGB_manual_proj.png'),
|
|
915
|
+
# Img('/E/Sample_images/sample_images_PA/mini10_fake_swaps/focused_Series012.png'),
|
|
916
|
+
# Img('/E/Sample_images/sample_images_PA/egg_chambers/120404_division_EcadKI.lif - Series006_RGB_manual_proj.png'),
|
|
917
|
+
# Img('/E/Sample_images/sample_images_PA/mini10_fake_swaps/focused_Series012.png'), None])
|
|
918
|
+
|
|
919
|
+
lst = ['/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012.png', '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series014.png', '/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series016.png']
|
|
920
|
+
|
|
921
|
+
augment, ok = ImgDisplayWindow.display(draw_mode='pen', nb_rows=3, nb_cols=3, images=[
|
|
922
|
+
[Img('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012.png'),Img('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/handCorrection.tif')],
|
|
923
|
+
Img('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series014.png'),
|
|
924
|
+
Img('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series016.png'),
|
|
925
|
+
Img('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series012/tracked_cells_resized.tif'),
|
|
926
|
+
Img('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series014/tracked_cells_resized.tif'),
|
|
927
|
+
Img('/E/Sample_images/sample_images_PA/mini_different_nb_of_channels/focused_Series016/tracked_cells_resized.tif'), None],
|
|
928
|
+
labels=['test1', 'test2', 'test3', None], lst=lst, cur_frame_idx=1)
|
|
929
|
+
print(augment, ok)
|
|
930
|
+
sys.exit(0)
|
|
931
|
+
|
|
932
|
+
if False:
|
|
933
|
+
# pas
|
|
934
|
+
|
|
935
|
+
# TODO essayer le multidisplay
|
|
936
|
+
# avec un set pr bcp de choses
|
|
937
|
+
|
|
938
|
+
# ok in fact that is already a great popup window --> can I further improve it ???
|
|
939
|
+
|
|
940
|
+
# just for a test
|
|
941
|
+
app = QApplication(sys.argv)
|
|
942
|
+
ex = ImgDisplayWindow(nb_rows=2, nb_cols=2)
|
|
943
|
+
# ex = ImgDisplayWindow(preview_only=True)
|
|
944
|
+
# img = Img('/home/aigouy/mon_prog/Python/Deep_learning/unet/data/membrane/test/11.png')
|
|
945
|
+
# img = Img('/home/aigouy/mon_prog/Python/Deep_learning/unet/data/membrane/test/122.png')
|
|
946
|
+
# img = Img('/home/aigouy/mon_prog/Python/data/3D_bicolor_ovipo.tif')
|
|
947
|
+
# img = Img('/home/aigouy/mon_prog/Python/data/Image11.lsm')
|
|
948
|
+
# img = Img('/home/aigouy/mon_prog/Python/data/lion.jpeg')
|
|
949
|
+
# img = Img('/home/aigouy/mon_prog/Python/data/epi_test.png')
|
|
950
|
+
|
|
951
|
+
img = Img(
|
|
952
|
+
'/E/Sample_images/sample_images_PA/egg_chambers/120404_division_EcadKI.lif - Series006_RGB_manual_proj.png')
|
|
953
|
+
ex.set_image(img)
|
|
954
|
+
|
|
955
|
+
# test = QRectF(None, None, 128, 128)
|
|
956
|
+
ex.scaleImage(
|
|
957
|
+
22) # ça marche faudra finalizer des trucs mais pas mal déjà --> tt mettre en interne dans ma classe d'intéret --> I'm almost done
|
|
958
|
+
# store image inside and also give it more options
|
|
959
|
+
ex.fit_to_window() # ça marche mais pb avec la taille des barres --> voir comment faire et tricher
|
|
960
|
+
ex.fit_to_height()
|
|
961
|
+
ex.fit_to_width()
|
|
962
|
+
|
|
963
|
+
# ça marche pas mal et en plus c'est simple!!!
|
|
964
|
+
ex.fit_to_width_or_height()
|
|
965
|
+
ex.fit_to_width_or_height()
|
|
966
|
+
ex.fit_to_width_or_height()
|
|
967
|
+
ex.fit_to_width_or_height()
|
|
968
|
+
|
|
969
|
+
# img = Img('/E/Sample_images/sample_images_PA/mini10_fake_swaps/focused_Series012.png')
|
|
970
|
+
ex.set_image(img)
|
|
971
|
+
ex.clear()
|
|
972
|
+
|
|
973
|
+
ex.set_image(img, nb=0)
|
|
974
|
+
img = Img('/E/Sample_images/sample_images_PA/mini10_fake_swaps/focused_Series012.png')
|
|
975
|
+
ex.set_image(img, nb=3)
|
|
976
|
+
|
|
977
|
+
ex.scale_image_all(0.3)
|
|
978
|
+
ex.scaleImage(1, nb=3) # tt marche et en plus vraiment pas mal --> je vais adorer ce truc à la fin!!!
|
|
979
|
+
|
|
980
|
+
# ça a l'air de marcher --> just allow or disallow sync
|
|
981
|
+
# ex.set_image(None)
|
|
982
|
+
ex.show()
|
|
983
|
+
app.exec_()
|
|
984
|
+
|
|
985
|
+
# all of the things seem ok !!!
|
|
986
|
+
|
|
987
|
+
# all is ok for now
|
|
988
|
+
# maybe add ROIs to zoom on a region of the image
|
|
989
|
+
# maybe stor all in the image because it is easier to handle !!!
|