batoolset 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batoolset/GUI/__init__.py +0 -0
- batoolset/GUI/list_gui.py +513 -0
- batoolset/GUI/multi_image_display.py +989 -0
- batoolset/GUI/open.py +325 -0
- batoolset/GUI/overlay_hints.py +45 -0
- batoolset/GUI/paint2.py +1345 -0
- batoolset/GUI/scrollablepaint.py +1152 -0
- batoolset/IJ_ROIs/__init__.py +0 -0
- batoolset/IJ_ROIs/tools.py +40 -0
- batoolset/PATH/__init__.py +0 -0
- batoolset/PATH/tools.py +79 -0
- batoolset/SQLite_tools/__init__.py +0 -0
- batoolset/SQLite_tools/tools.py +840 -0
- batoolset/__init__.py +0 -0
- batoolset/binary/__init__.py +0 -0
- batoolset/binary/tools.py +123 -0
- batoolset/binarytools/__init__.py +0 -0
- batoolset/binarytools/cell_center_detector.py +290 -0
- batoolset/clipboard_tools/__init__.py +0 -0
- batoolset/clipboard_tools/tools.py +7 -0
- batoolset/colors/__init__.py +0 -0
- batoolset/colors/colorgen.py +193 -0
- batoolset/colors/tools.py +22 -0
- batoolset/command_line/__init__.py +0 -0
- batoolset/command_line/tools.py +18 -0
- batoolset/dialogs/__init__.py +0 -0
- batoolset/dialogs/opensave.py +228 -0
- batoolset/dicts/__init__.py +0 -0
- batoolset/dicts/tools.py +299 -0
- batoolset/dims/__init__.py +0 -0
- batoolset/dims/tools.py +83 -0
- batoolset/draw/__init__.py +0 -0
- batoolset/draw/shapes/Position.py +183 -0
- batoolset/draw/shapes/__init__.py +0 -0
- batoolset/draw/shapes/automated_alignment_drawing_refactored.py +346 -0
- batoolset/draw/shapes/circle2d.py +99 -0
- batoolset/draw/shapes/compute_optimal_size_all_merged.py +317 -0
- batoolset/draw/shapes/ellipse2d.py +336 -0
- batoolset/draw/shapes/freehand2d.py +235 -0
- batoolset/draw/shapes/group.py +1830 -0
- batoolset/draw/shapes/image2d.py +2469 -0
- batoolset/draw/shapes/line2d.py +575 -0
- batoolset/draw/shapes/point2d.py +257 -0
- batoolset/draw/shapes/polygon2d.py +357 -0
- batoolset/draw/shapes/polyline2d.py +235 -0
- batoolset/draw/shapes/rect2d.py +388 -0
- batoolset/draw/shapes/rectangle2d.py +601 -0
- batoolset/draw/shapes/scalebar.py +304 -0
- batoolset/draw/shapes/serializablefont.py +126 -0
- batoolset/draw/shapes/serializableguiparams.py +10 -0
- batoolset/draw/shapes/square2d.py +103 -0
- batoolset/draw/shapes/textorientation.py +45 -0
- batoolset/draw/shapes/tools.py +467 -0
- batoolset/draw/shapes/transform2D.py +350 -0
- batoolset/draw/shapes/txt2d.py +550 -0
- batoolset/draw/widgets/__init__.py +0 -0
- batoolset/draw/widgets/col.py +343 -0
- batoolset/draw/widgets/image_group.py +248 -0
- batoolset/draw/widgets/image_organizer.py +96 -0
- batoolset/draw/widgets/paint.py +184 -0
- batoolset/draw/widgets/vectorial.py +257 -0
- batoolset/figure/__init__.py +0 -0
- batoolset/figure/alignment.py +814 -0
- batoolset/figure/column.py +1117 -0
- batoolset/figure/fig_tools.py +285 -0
- batoolset/figure/minimal_debugger_scale_etc.py +139 -0
- batoolset/figure/panel.py +697 -0
- batoolset/figure/row.py +852 -0
- batoolset/figure/user_selection.py +287 -0
- batoolset/files/__init__.py +0 -0
- batoolset/files/tools.py +1321 -0
- batoolset/images/__init__.py +0 -0
- batoolset/images/tools.py +81 -0
- batoolset/img.py +5216 -0
- batoolset/linux/__init__.py +0 -0
- batoolset/linux/tools.py +72 -0
- batoolset/lists/__init__.py +0 -0
- batoolset/lists/tools.py +436 -0
- batoolset/luts/__init__.py +0 -0
- batoolset/luts/lut_minimal_test.py +1207 -0
- batoolset/matplolib/__init__.py +0 -0
- batoolset/matplolib/tools.py +44 -0
- batoolset/np_tools/__init__.py +0 -0
- batoolset/np_tools/tools.py +252 -0
- batoolset/np_tools/trash.py +15 -0
- batoolset/pandas_tools/__init__.py +0 -0
- batoolset/pandas_tools/tools.py +880 -0
- batoolset/partials/__init__.py +0 -0
- batoolset/partials/tools.py +83 -0
- batoolset/plot_tools/__init__.py +0 -0
- batoolset/plot_tools/tools.py +70 -0
- batoolset/pyqt/__init__.py +0 -0
- batoolset/pyqt/tools.py +691 -0
- batoolset/re_tools/__init__.py +0 -0
- batoolset/re_tools/tools.py +35 -0
- batoolset/serialization/__init__.py +0 -0
- batoolset/serialization/tools.py +1192 -0
- batoolset/sets/__init__.py +0 -0
- batoolset/sets/tools.py +109 -0
- batoolset/settings/__init__.py +0 -0
- batoolset/settings/global_settings.py +134 -0
- batoolset/strings/__init__.py +0 -0
- batoolset/strings/tools.py +225 -0
- batoolset/ta/__init__.py +0 -0
- batoolset/ta/database/__init__.py +0 -0
- batoolset/ta/database/advanced_sql_plotter.py +1170 -0
- batoolset/ta/database/my_qt_table.py +79 -0
- batoolset/ta/database/preview_and_edit_sqlite_db.py +405 -0
- batoolset/ta/database/sql.py +2141 -0
- batoolset/ta/database/sql_column_name_dialog.py +122 -0
- batoolset/ta/measurements/TAmeasures.py +2226 -0
- batoolset/ta/measurements/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/__init__.py +0 -0
- batoolset/ta/measurements/measurements3D/get_point_on_surface_if_centroid_is_bad.py +175 -0
- batoolset/ta/measurements/measurements3D/height_map_to_projection.py +82 -0
- batoolset/ta/measurements/measurements3D/measures3D.py +208 -0
- batoolset/ta/measurements/measurements3D/triangulate.py +331 -0
- batoolset/ta/measurements/nematic.py +562 -0
- batoolset/ta/measurements/nematic_measurements.py +107 -0
- batoolset/ta/segmentation/__init__.py +0 -0
- batoolset/ta/segmentation/neo_wshed.py +314 -0
- batoolset/ta/selections/__init__.py +0 -0
- batoolset/ta/selections/selection.py +329 -0
- batoolset/ta/tracking/__init__.py +0 -0
- batoolset/ta/tracking/last_tracking_based_on_matching_or_on_translation_from_mermaid_warp.py +653 -0
- batoolset/ta/tracking/local_to_track_correspondance.py +138 -0
- batoolset/ta/tracking/rapid_detection_of_vertices.py +517 -0
- batoolset/ta/tracking/registration.py +108 -0
- batoolset/ta/tracking/swap_detector_and_fixer.py +296 -0
- batoolset/ta/tracking/track_correction.py +238 -0
- batoolset/ta/tracking/tracking_error_detector_and_fixer.py +3099 -0
- batoolset/ta/tracking/tracking_yet_another_approach_pyramidal_registration_n_neo_swapping_correction.py +725 -0
- batoolset/ta/utils/TA_tools.py +43 -0
- batoolset/ta/utils/__init__.py +0 -0
- batoolset/ta/utils/rps_tools.py +1783 -0
- batoolset/tests/__init__.py +0 -0
- batoolset/tests/rps_tools.py +51 -0
- batoolset/tests/tools.py +10 -0
- batoolset/tools/__init__.py +0 -0
- batoolset/tools/early_stopper_class.py +9 -0
- batoolset/tools/guided_data_recoverer.py +110 -0
- batoolset/tools/logger.py +100 -0
- batoolset/tools/mytimer.py +31 -0
- batoolset/tools/prompt_color.py +160 -0
- batoolset/tools/qthandler.py +118 -0
- batoolset/uitools/__init__.py +0 -0
- batoolset/uitools/blinker.py +56 -0
- batoolset/unsafe/__init__.py +0 -0
- batoolset/unsafe/tools.py +115 -0
- batoolset/utils/__init__.py +0 -0
- batoolset/utils/commontools.py +158 -0
- batoolset/utils/loadlist.py +728 -0
- batoolset/version.py +10 -0
- batoolset/xml/__init__.py +0 -0
- batoolset/xml/tools.py +65 -0
- batoolset/zip_tools/__init__.py +0 -0
- batoolset/zip_tools/tools.py +76 -0
- batoolset-0.1.0.dist-info/LICENSE +24 -0
- batoolset-0.1.0.dist-info/METADATA +45 -0
- batoolset-0.1.0.dist-info/RECORD +162 -0
- batoolset-0.1.0.dist-info/WHEEL +5 -0
- batoolset-0.1.0.dist-info/top_level.txt +1 -0
batoolset/GUI/paint2.py
ADDED
|
@@ -0,0 +1,1345 @@
|
|
|
1
|
+
# TODO maybe hide green pointer if mouse is out of the image --> dangerous
|
|
2
|
+
# NB this will ultimately become the one and only paint widget in the app --> all others or most of them should disappear and be replaced by that
|
|
3
|
+
# TODO need add the save but only for the mask edit item!!!
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from batoolset.pyqt.tools import getCtrlModifier
|
|
7
|
+
from batoolset.settings.global_settings import set_UI # set the UI to qtpy
|
|
8
|
+
set_UI()
|
|
9
|
+
import traceback
|
|
10
|
+
from qtpy.QtGui import QKeySequence
|
|
11
|
+
from skimage.measure import label, regionprops
|
|
12
|
+
from qtpy.QtCore import QRect, QTimer, Qt
|
|
13
|
+
from qtpy.QtWidgets import QWidget, QApplication, QMessageBox
|
|
14
|
+
from batoolset.dialogs.opensave import saveFileDialog
|
|
15
|
+
from batoolset.draw.widgets.vectorial import VectorialDrawPane
|
|
16
|
+
from qtpy.QtWidgets import QMenu
|
|
17
|
+
from qtpy import QtCore, QtGui, QtWidgets
|
|
18
|
+
from batoolset.img import toQimage, Img, get_white_bounds, RGB_to_int24, is_binary, auto_scale, save_as_tiff
|
|
19
|
+
from batoolset.ta.measurements.measurements3D.get_point_on_surface_if_centroid_is_bad import point_on_surface
|
|
20
|
+
from batoolset.ta.segmentation.neo_wshed import wshed
|
|
21
|
+
from batoolset.ta.selections.selection import get_colors_drawn_over, convert_selection_color_to_coords
|
|
22
|
+
from batoolset.files.tools import smart_name_parser
|
|
23
|
+
import numpy as np
|
|
24
|
+
from batoolset.tools.logger import TA_logger # logging
|
|
25
|
+
|
|
26
|
+
logger = TA_logger()
|
|
27
|
+
|
|
28
|
+
class Createpaintwidget(QWidget):
|
|
29
|
+
# auto_convert_float_to_binary_threshold = 0.4
|
|
30
|
+
|
|
31
|
+
default_mask_name = 'handCorrection.tif'
|
|
32
|
+
|
|
33
|
+
# maybe store
|
|
34
|
+
def __init__(self, enable_shortcuts=False): #, methods_overriding=None
|
|
35
|
+
|
|
36
|
+
super().__init__()
|
|
37
|
+
#
|
|
38
|
+
# if methods_overriding is not None:
|
|
39
|
+
# for k,v in methods_overriding.items():
|
|
40
|
+
# # replace internal method by external one
|
|
41
|
+
# k = v
|
|
42
|
+
# self.image_path = None
|
|
43
|
+
self.save_path = None
|
|
44
|
+
self.vdp = VectorialDrawPane(active=False) # the vectorial drawing panel
|
|
45
|
+
self.raw_image = None # the original unprocessed image
|
|
46
|
+
self.image = None # the displayed image (a qimage representation of raw_image can be a subset of raw_image_also)
|
|
47
|
+
self.raw_mask = None # the original unprocessed mask
|
|
48
|
+
self.imageDraw = None # the mask image drawn over the image
|
|
49
|
+
self.raw_user_drawing = None # contains just the user drawing without image mask
|
|
50
|
+
self.cursor = None
|
|
51
|
+
self.maskVisible = True
|
|
52
|
+
self.scale = 1.0
|
|
53
|
+
self.drawing = False
|
|
54
|
+
self.brushSize = 3
|
|
55
|
+
self.minimal_cell_size = 10
|
|
56
|
+
self._clear_size = 30
|
|
57
|
+
self.drawColor = QtGui.QColor(QtCore.Qt.red) # blue green cyan
|
|
58
|
+
self.eraseColor = QtGui.QColor(QtCore.Qt.black)
|
|
59
|
+
self.eraseColor_visualizer = QtGui.QColor(QtCore.Qt.green)
|
|
60
|
+
self.cursorColor = QtGui.QColor(QtCore.Qt.green)
|
|
61
|
+
self.lastPoint = QtCore.QPointF()
|
|
62
|
+
self.change = False
|
|
63
|
+
self.propagate_mouse_move = False
|
|
64
|
+
# KEEP IMPORTANT required to track mouse even when not clicked
|
|
65
|
+
self.setMouseTracking(True) # KEEP IMPORTANT
|
|
66
|
+
self.scrollArea = None
|
|
67
|
+
self.statusBar = None
|
|
68
|
+
self.drawing_enabled = True
|
|
69
|
+
self.channel = None
|
|
70
|
+
self.force_cursor_visible = False # required to force activate cursor to be always visible
|
|
71
|
+
self.save_file_name=None
|
|
72
|
+
self.multichannel_mode = False
|
|
73
|
+
|
|
74
|
+
self.auto_convert_float_to_binary_threshold = 0.4
|
|
75
|
+
|
|
76
|
+
# self.multi_channel_editor_n_save = False
|
|
77
|
+
# TODO add sortcuts
|
|
78
|
+
|
|
79
|
+
if enable_shortcuts:
|
|
80
|
+
self.add_shortcuts() # TODO make this optional as it can cause trouble
|
|
81
|
+
|
|
82
|
+
def force_cursor_to_be_visible(self, boolean):
|
|
83
|
+
self.force_cursor_visible = boolean
|
|
84
|
+
|
|
85
|
+
# can be used for saving
|
|
86
|
+
def set_save_path(self, path):
|
|
87
|
+
self.save_path = path
|
|
88
|
+
|
|
89
|
+
def get_save_path(self):
|
|
90
|
+
return self.save_path
|
|
91
|
+
|
|
92
|
+
def set_scale(self, scale):
|
|
93
|
+
# print(self.scale) # 0.10000000000000014 --> this is still ok --> this value crashes 1.3877787807814457e-16
|
|
94
|
+
|
|
95
|
+
# make sure scale is bounded to avoid issues and non sense scalings
|
|
96
|
+
if scale<0.01:
|
|
97
|
+
scale = 0.01
|
|
98
|
+
if scale>50:
|
|
99
|
+
scale=50
|
|
100
|
+
|
|
101
|
+
self.scale = scale
|
|
102
|
+
self.vdp.scale =scale
|
|
103
|
+
# DO I NEED ADJUST MORE SCALES ???
|
|
104
|
+
# self.update()
|
|
105
|
+
|
|
106
|
+
def get_scale(self):
|
|
107
|
+
return self.scale
|
|
108
|
+
|
|
109
|
+
# comprend pas ça marche pas si parent
|
|
110
|
+
def add_shortcuts(self):
|
|
111
|
+
# zoomPlus = QtWidgets.QShortcut("Ctrl+Shift+=", self)
|
|
112
|
+
# zoomPlus.activated.connect(self.zoomIn)
|
|
113
|
+
# zoomPlus.setContext(QtCore.Qt.ApplicationShortcut) # make sure the shorctut always remain active
|
|
114
|
+
#
|
|
115
|
+
# zoomPlus2 = QtWidgets.QShortcut("Ctrl++", self)
|
|
116
|
+
# zoomPlus2.activated.connect(self.zoomIn)
|
|
117
|
+
# zoomPlus2.setContext(QtCore.Qt.ApplicationShortcut) # make sure the shorctut always remain active
|
|
118
|
+
#
|
|
119
|
+
# zoomMinus = QtWidgets.QShortcut("Ctrl+Shift+-", self)
|
|
120
|
+
# zoomMinus.activated.connect(self.zoomOut)
|
|
121
|
+
# zoomMinus.setContext(QtCore.Qt.ApplicationShortcut) # make sure the shorctut always remain active
|
|
122
|
+
#
|
|
123
|
+
# zoomMinus2 = QtWidgets.QShortcut("Ctrl+-", self)
|
|
124
|
+
# zoomMinus2.activated.connect(self.zoomOut)
|
|
125
|
+
# zoomMinus2.setContext(QtCore.Qt.ApplicationShortcut)
|
|
126
|
+
|
|
127
|
+
# enter_pressed = QtWidgets.QShortcut("Ctrl+Shift+=", self)
|
|
128
|
+
# zoomPlus.activated.connect(self.zoomIn)
|
|
129
|
+
# zoomPlus.setContext(QtCore.Qt.ApplicationShortcut) # make sure the shorctut always remain active
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
padEnterShortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Enter), self) # voila
|
|
133
|
+
padEnterShortcut.activated.connect(self.apply)
|
|
134
|
+
padEnterShortcut.setContext(QtCore.Qt.ApplicationShortcut) # make s
|
|
135
|
+
|
|
136
|
+
enterShortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Return), self) # voila
|
|
137
|
+
enterShortcut.activated.connect(self.apply)
|
|
138
|
+
enterShortcut.setContext(QtCore.Qt.ApplicationShortcut) # make s
|
|
139
|
+
|
|
140
|
+
enterShortcut2 = QtWidgets.QShortcut('Shift+Return', self) # voila
|
|
141
|
+
enterShortcut2.activated.connect(self.apply)
|
|
142
|
+
enterShortcut2.setContext(QtCore.Qt.ApplicationShortcut) # make s
|
|
143
|
+
|
|
144
|
+
# pb if parent has same shortcut then ignored
|
|
145
|
+
# the pb is really with enter --> maybe stolen focus by dialog ????
|
|
146
|
+
enterShortcut3 = QtWidgets.QShortcut('Shift+Enter', self)
|
|
147
|
+
enterShortcut3.activated.connect(self.apply)
|
|
148
|
+
enterShortcut3.setContext(QtCore.Qt.ApplicationShortcut) # make sure the shorctut always remain active
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
self.shrtM = QtWidgets.QShortcut("M", self)
|
|
152
|
+
self.shrtM.activated.connect(self.m_apply)
|
|
153
|
+
self.shrtM.setContext(QtCore.Qt.ApplicationShortcut)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
self.ctrl_shift_S_grab_screen_shot = QtWidgets.QShortcut('Ctrl+Shift+S', self)
|
|
157
|
+
self.ctrl_shift_S_grab_screen_shot.activated.connect(self.grab_screen_shot)
|
|
158
|
+
self.ctrl_shift_S_grab_screen_shot.setContext(QtCore.Qt.ApplicationShortcut)
|
|
159
|
+
|
|
160
|
+
self.increase_contrastC = QtWidgets.QShortcut('C', self)
|
|
161
|
+
self.increase_contrastC.activated.connect(self.increase_contrast)
|
|
162
|
+
self.increase_contrastC.setContext(QtCore.Qt.ApplicationShortcut)
|
|
163
|
+
|
|
164
|
+
# QtGui.QShortcut(
|
|
165
|
+
# self.supr = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Delete), self)
|
|
166
|
+
|
|
167
|
+
# QKeySequence(tr("Ctrl+X, Ctrl+C"))
|
|
168
|
+
# QKeySequence(Qt.CTRL + Qt.Key_X, Qt.CTRL + Qt.Key_C)
|
|
169
|
+
|
|
170
|
+
# self.supr = QtWidgets.QShortcut(QKeySequence(Qt.Key_Delete), self)
|
|
171
|
+
# self.supr.activated.connect(self.suppr_pressed)
|
|
172
|
+
# self.supr.setContext(QtCore.Qt.ApplicationShortcut)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# connect the m shortcut to show and hide mask
|
|
176
|
+
|
|
177
|
+
# this returns the coords of the cells drawn over --> very useful for clones, adding/removing cells such as dividing cells and dying cells !!!
|
|
178
|
+
# that seems to work very well --> TODO finalize that now or soon
|
|
179
|
+
def get_selection_coords_based_on_current_mask(self, bg_color=None):
|
|
180
|
+
coords_of_selection = []
|
|
181
|
+
|
|
182
|
+
# pb --> here I need do it on the labeled image -->
|
|
183
|
+
|
|
184
|
+
img_to_analyze = self.get_raw_image()
|
|
185
|
+
# si l'image est juste un mask binaire alors --> faut la convertir en label --> TODO
|
|
186
|
+
if is_binary(img_to_analyze):
|
|
187
|
+
if len(img_to_analyze.shape)==3:
|
|
188
|
+
# assume it's a handCorrection mask and the mask is 3channel
|
|
189
|
+
img_to_analyze = img_to_analyze[...,0]
|
|
190
|
+
img_to_analyze = label(img_to_analyze, connectivity=1, background=255) # will that always work
|
|
191
|
+
bg_color = 0
|
|
192
|
+
|
|
193
|
+
# why is this shit empty
|
|
194
|
+
mask=self.get_user_drawing()
|
|
195
|
+
|
|
196
|
+
# --> seems to be ok --> then why 0
|
|
197
|
+
# import matplotlib.pyplot as plt
|
|
198
|
+
# plt.imshow(img_to_analyze)
|
|
199
|
+
# plt.show()
|
|
200
|
+
|
|
201
|
+
selected_cells = get_colors_drawn_over(mask, img_to_analyze)
|
|
202
|
+
if not selected_cells:
|
|
203
|
+
logger.warning('No selection found --> nothing to do')
|
|
204
|
+
return coords_of_selection
|
|
205
|
+
|
|
206
|
+
self.set_mask(np.zeros_like(mask))
|
|
207
|
+
self.update()
|
|
208
|
+
|
|
209
|
+
tmp = img_to_analyze
|
|
210
|
+
if len(tmp.shape) == 3:
|
|
211
|
+
tmp = RGB_to_int24(tmp)
|
|
212
|
+
#
|
|
213
|
+
if bg_color is None:
|
|
214
|
+
if 0xFFFFFF in tmp:
|
|
215
|
+
bg_color = 0xFFFFFF
|
|
216
|
+
else:
|
|
217
|
+
bg_color = 0
|
|
218
|
+
#
|
|
219
|
+
# cell_label = label(tmp, connectivity=1, background=bg_color)
|
|
220
|
+
#
|
|
221
|
+
# # for all the selected cells --> return the coords --> probably a quite good idea...
|
|
222
|
+
# for region in regionprops(cell_label):
|
|
223
|
+
# color = tmp[region.coords[0][0], region.coords[0][1]]
|
|
224
|
+
# if color in selected_cells:
|
|
225
|
+
# point_awlays_inside_the_cell = point_on_surface(region, cell_label)
|
|
226
|
+
# coords_of_selection.append(point_awlays_inside_the_cell)
|
|
227
|
+
#
|
|
228
|
+
# return coords_of_selection
|
|
229
|
+
|
|
230
|
+
return convert_selection_color_to_coords(tmp, selected_cells=selected_cells, bg_color=bg_color)
|
|
231
|
+
|
|
232
|
+
# added this so that I can easily get user selection using a cell ID
|
|
233
|
+
def get_colors_drawn_over(self, forbidden_colors=[0,0xFFFFFF]):
|
|
234
|
+
mask = self.get_user_drawing()
|
|
235
|
+
# print('I was called')
|
|
236
|
+
if mask is None:
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
selected_colors = get_colors_drawn_over(mask, self.get_raw_image(),forbidden_colors=forbidden_colors)
|
|
240
|
+
# # print(mask)
|
|
241
|
+
# # need get all the colors of the raw image that correspond to user selection
|
|
242
|
+
# selected_colors = self.get_raw_image()[mask != 0]
|
|
243
|
+
# selected_colors = RGB_to_int24(selected_colors)
|
|
244
|
+
# selected_colors = set(selected_colors.ravel().tolist())
|
|
245
|
+
# # if 0xFFFFFF in selected_colors:
|
|
246
|
+
# # selected_colors.remove(0xFFFFFF)
|
|
247
|
+
# if forbidden_colors is not None:
|
|
248
|
+
# if not isinstance(forbidden_colors, list):
|
|
249
|
+
# forbidden_colors = [forbidden_colors]
|
|
250
|
+
# for color in forbidden_colors:
|
|
251
|
+
# if color in selected_colors:
|
|
252
|
+
# selected_colors.remove(color)
|
|
253
|
+
# selected_colors = list(selected_colors)
|
|
254
|
+
self.set_mask(np.zeros_like(mask))
|
|
255
|
+
self.update()
|
|
256
|
+
return selected_colors
|
|
257
|
+
|
|
258
|
+
# def suppr_pressed(self):
|
|
259
|
+
|
|
260
|
+
def increase_contrast(self):
|
|
261
|
+
# print('increasing contrast')
|
|
262
|
+
try:
|
|
263
|
+
# metadata = self.raw_image.metadata
|
|
264
|
+
# sqqsdq
|
|
265
|
+
# self.raw_image = Img(auto_threshold(self.raw_image),metadata=metadata)
|
|
266
|
+
# self.set_image(self.raw_image)
|
|
267
|
+
# shall I do it for the displayed image only ???? --> probably smarter porbably shoudl do a get display image that is independent of all other functions and handles all dims
|
|
268
|
+
# very dumb --> needs be done just for the dipslayed image and nothing else and --> maybe get the image back from the screen directly --> would be smarted and better
|
|
269
|
+
|
|
270
|
+
# maybe do this only on the displayed image --> using convert_qimage_to_numpy or do something that gets the image to be displayed including the channels
|
|
271
|
+
# or do a tool that will recover the image of interest with channels and also the rest of the dimensions --> MEGA TODO !!!
|
|
272
|
+
print('auto-increase contrast')
|
|
273
|
+
# hack to preserve luts --> TODO
|
|
274
|
+
copy = auto_scale(np.copy(self.raw_image))
|
|
275
|
+
meta = None
|
|
276
|
+
try:
|
|
277
|
+
meta=self.raw_image.metadata
|
|
278
|
+
except:
|
|
279
|
+
pass
|
|
280
|
+
# print(self.raw_image.metadata)
|
|
281
|
+
self.set_display(copy, metadata=meta)
|
|
282
|
+
except:
|
|
283
|
+
traceback.print_exc()
|
|
284
|
+
|
|
285
|
+
def grab_screen_shot(self):
|
|
286
|
+
# screenshot = QPixmap.grabWindow(self.winId())
|
|
287
|
+
output_file = saveFileDialog(parent_window=self, extensions="Supported Files (*.png);;All Files (*)",
|
|
288
|
+
default_ext='.png')
|
|
289
|
+
if output_file is not None:
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
# we hide the cursor
|
|
293
|
+
self.cursor = QtGui.QImage(self.image.size(), QtGui.QImage.Format_ARGB32)
|
|
294
|
+
self.cursor.fill(QtCore.Qt.transparent)
|
|
295
|
+
self.update()
|
|
296
|
+
screenshot=self.grab()
|
|
297
|
+
screenshot.save(output_file, 'png')
|
|
298
|
+
# restore cursor
|
|
299
|
+
# or paint without cursor --> TODO do that better some day
|
|
300
|
+
except:
|
|
301
|
+
logger.error('Could not grab a screenshot...')
|
|
302
|
+
|
|
303
|
+
def save(self):
|
|
304
|
+
# save_img
|
|
305
|
+
print('ctrl S save method to override')
|
|
306
|
+
|
|
307
|
+
def apply(self):
|
|
308
|
+
print('apply method to override')
|
|
309
|
+
|
|
310
|
+
def shift_apply(self):
|
|
311
|
+
print('shift apply method to override')
|
|
312
|
+
|
|
313
|
+
def ctrl_m_apply(self):
|
|
314
|
+
print('ctrl m apply method to override')
|
|
315
|
+
|
|
316
|
+
def suppr_pressed(self):
|
|
317
|
+
# print('Suppr method to override')
|
|
318
|
+
if self.vdp.active:
|
|
319
|
+
self.vdp.removeCurShape()
|
|
320
|
+
self.update()
|
|
321
|
+
else:
|
|
322
|
+
print('Suppr method to override')
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# def m_apply(self):
|
|
326
|
+
# print('m apply method to override')
|
|
327
|
+
# qsdsqqsdsq
|
|
328
|
+
def m_apply(self):
|
|
329
|
+
# print('m apply called')
|
|
330
|
+
self.maskVisible = not self.maskVisible
|
|
331
|
+
self.update()
|
|
332
|
+
|
|
333
|
+
def set_display(self, display, metadata=None):
|
|
334
|
+
# this actually sets the displayed image --> different from set imlage, can be used to show channels instead or stuff like that instead of the actula imge
|
|
335
|
+
if isinstance(display, np.ndarray):
|
|
336
|
+
display = toQimage(display, metadata=metadata)
|
|
337
|
+
self.image = display
|
|
338
|
+
self.update()
|
|
339
|
+
|
|
340
|
+
def set_image(self, img):
|
|
341
|
+
if img is None:
|
|
342
|
+
self.save_file_name = None
|
|
343
|
+
self.raw_image = None
|
|
344
|
+
self.image = None
|
|
345
|
+
self.imageDraw = None
|
|
346
|
+
self.raw_mask = None
|
|
347
|
+
self.raw_user_drawing = None
|
|
348
|
+
self.unsetCursor() # restore cursor
|
|
349
|
+
self.update()
|
|
350
|
+
return
|
|
351
|
+
else:
|
|
352
|
+
# remove cursor only if image is shown and not vectorial drawing mode!
|
|
353
|
+
if self.vdp.active:
|
|
354
|
+
self.unsetCursor()
|
|
355
|
+
else:
|
|
356
|
+
self.setCursor(Qt.BlankCursor)
|
|
357
|
+
# try:
|
|
358
|
+
# if isinstance(img, np.ndarray):
|
|
359
|
+
# self.image = img.getQimage() # bug is here
|
|
360
|
+
# # set an empty mask
|
|
361
|
+
# self.imageDraw = QtGui.QImage(self.image.size(), QtGui.QImage.Format_ARGB32)
|
|
362
|
+
# self.imageDraw.fill(QtCore.Qt.transparent)
|
|
363
|
+
# else:
|
|
364
|
+
# self.image = img[0].getQimage()
|
|
365
|
+
# # set the mask
|
|
366
|
+
# self.imageDraw = Img(self.createRGBA(img[1].getQimage()), dimensions='hwc').getQimage()
|
|
367
|
+
# except:
|
|
368
|
+
# NB THIS IS VERY REDUNDNAT ---> STICK TO ONE METHOD
|
|
369
|
+
if isinstance(img, str):
|
|
370
|
+
# self.image_path = img# maybe this image path can be used for saving, could also be provided serparately
|
|
371
|
+
self.save_file_name = smart_name_parser(img, ordered_output=self.default_mask_name)
|
|
372
|
+
img = Img(img)
|
|
373
|
+
|
|
374
|
+
# self.image_path=None # maybe this image path can be used for saving
|
|
375
|
+
if isinstance(img, np.ndarray):
|
|
376
|
+
|
|
377
|
+
# bug is exactly here --> why and where
|
|
378
|
+
|
|
379
|
+
# dimensions = None
|
|
380
|
+
# if isinstance(img, Img):
|
|
381
|
+
# dimensions = img.get_dimensions_as_string()
|
|
382
|
+
|
|
383
|
+
# self.image_path = None
|
|
384
|
+
self.raw_image = img
|
|
385
|
+
|
|
386
|
+
# print('tests',img.shape, isinstance(img,Img))
|
|
387
|
+
|
|
388
|
+
self.image = toQimage(img)# maybe this image path can be used for saving
|
|
389
|
+
if self.image is None:
|
|
390
|
+
logger.error('Image could not be displayed...')
|
|
391
|
+
self.unsetCursor()
|
|
392
|
+
self.update()
|
|
393
|
+
return
|
|
394
|
+
self.imageDraw = QtGui.QImage(self.image.size(), QtGui.QImage.Format_ARGB32)
|
|
395
|
+
self.imageDraw.fill(QtCore.Qt.transparent)
|
|
396
|
+
self.raw_user_drawing =QtGui.QImage(self.image.size(), QtGui.QImage.Format_ARGB32)
|
|
397
|
+
self.raw_user_drawing.fill(QtCore.Qt.transparent)
|
|
398
|
+
else:
|
|
399
|
+
# self.image_path = None
|
|
400
|
+
self.image = toQimage(img[0])
|
|
401
|
+
self.raw_image = img[0] #
|
|
402
|
+
# same as set mask --> how to do that
|
|
403
|
+
self.imageDraw = toQimage(Img(self.createRGBA(img[1]), dimensions='hwc'),preserve_alpha=True)#.getQimage()
|
|
404
|
+
self.raw_user_drawing = QtGui.QImage(self.imageDraw.size(), QtGui.QImage.Format_ARGB32)
|
|
405
|
+
self.raw_user_drawing.fill(QtCore.Qt.transparent)
|
|
406
|
+
|
|
407
|
+
if self.force_cursor_visible:
|
|
408
|
+
self.unsetCursor()
|
|
409
|
+
|
|
410
|
+
# self.image = QPixmap(100,200).toImage()
|
|
411
|
+
width = self.image.size().width()
|
|
412
|
+
height = self.image.size().height()
|
|
413
|
+
top = self.geometry().x()
|
|
414
|
+
left = self.geometry().y()
|
|
415
|
+
self.setGeometry(top, left, int(width*self.scale), int(height*self.scale))
|
|
416
|
+
|
|
417
|
+
self.cursor = QtGui.QImage(self.image.size(), QtGui.QImage.Format_ARGB32)
|
|
418
|
+
self.cursor.fill(QtCore.Qt.transparent)
|
|
419
|
+
self.update()
|
|
420
|
+
|
|
421
|
+
def get_raw_image(self):
|
|
422
|
+
return self.raw_image
|
|
423
|
+
|
|
424
|
+
def binarize(self, mask, auto_convert_float_to_binary=None, force=False):
|
|
425
|
+
if auto_convert_float_to_binary is None:
|
|
426
|
+
auto_convert_float_to_binary = self.auto_convert_float_to_binary_threshold
|
|
427
|
+
if auto_convert_float_to_binary and (mask.max() <= 1 or force):
|
|
428
|
+
# print('autoconvert')
|
|
429
|
+
mask = mask > auto_convert_float_to_binary
|
|
430
|
+
return mask
|
|
431
|
+
|
|
432
|
+
# do I need that ??? maybe yes if I just wanna change the mask
|
|
433
|
+
def set_mask(self, mask, auto_convert_float_to_binary=None):
|
|
434
|
+
if auto_convert_float_to_binary is None:
|
|
435
|
+
auto_convert_float_to_binary = self.auto_convert_float_to_binary_threshold
|
|
436
|
+
if isinstance(mask, str):
|
|
437
|
+
self.save_file_name = mask
|
|
438
|
+
# self.image_path = img# maybe this image path can be used for saving, could also be provided serparately
|
|
439
|
+
mask = Img(mask)
|
|
440
|
+
self.raw_mask = mask
|
|
441
|
+
# for TA compat
|
|
442
|
+
if mask.has_c():
|
|
443
|
+
# but should also save the raw mask as for the raw_image --> TODO --> maybe
|
|
444
|
+
if self.channel is not None:
|
|
445
|
+
mask=mask[...,self.channel]
|
|
446
|
+
else:
|
|
447
|
+
mask = mask[..., 0]
|
|
448
|
+
|
|
449
|
+
# convert mask to image draw
|
|
450
|
+
if mask is None:
|
|
451
|
+
self.imageDraw = None
|
|
452
|
+
self.raw_user_drawing = None
|
|
453
|
+
self.raw_mask = None
|
|
454
|
+
else:
|
|
455
|
+
# auto convert float deep learning masks to binary --> maybe this should be a parameter ...
|
|
456
|
+
# print('tada', auto_convert_float_to_binary, mask.max())
|
|
457
|
+
|
|
458
|
+
mask = self.binarize(mask, auto_convert_float_to_binary=auto_convert_float_to_binary)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# print('mask.shape',mask.shape)
|
|
462
|
+
# convert mask to a mask
|
|
463
|
+
# self.imageDraw = QtGui.QImage(self.image.size(), QtGui.QImage.Format_ARGB32)
|
|
464
|
+
# self.imageDraw.fill(QtCore.Qt.transparent)
|
|
465
|
+
|
|
466
|
+
# print('in here tmp ', mask.shape)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
# if image has z channels and is more than just 2D / is 3D I could make this smarter by loading just the right image --> TODO --> sync the channel here with the channel of the currently dispalyer image
|
|
470
|
+
# SHALL I MAKE A FULL GUI FOR THAT ON RELY ON WHAT I HAVE ALREADY !!!!
|
|
471
|
+
self.imageDraw = toQimage(Img(self.createRGBA(mask), dimensions='hwc'),preserve_alpha=True) #.getQimage() # marche pas car besoin d'une ARGB
|
|
472
|
+
self.raw_user_drawing = QtGui.QImage(self.imageDraw.size(), QtGui.QImage.Format_ARGB32)
|
|
473
|
+
self.raw_user_drawing.fill(QtCore.Qt.transparent) # somehow this is really required to have an empty image otherwise it is not --> ??? why
|
|
474
|
+
self.update()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# do I have a way to just get what was drawn by the user and the bounds of it --> maybe by substratction --> or can I get the difference
|
|
478
|
+
# I need bounds of drawn stuff --> think about how to do that !!!
|
|
479
|
+
# we have used the user drawing for something --> let's reset it
|
|
480
|
+
# shall I also save the raw mask unperturbed by user drawing --> can be useful too in fact to do things with it
|
|
481
|
+
def reset_user_drawing(self):
|
|
482
|
+
if self.imageDraw is None:
|
|
483
|
+
self.raw_user_drawing = None
|
|
484
|
+
else:
|
|
485
|
+
self.raw_user_drawing = QtGui.QImage(self.imageDraw.size(), QtGui.QImage.Format_ARGB32)
|
|
486
|
+
self.raw_user_drawing.fill(QtCore.Qt.transparent)
|
|
487
|
+
|
|
488
|
+
# shall I draw any user interaction also in a separate file so that I can get just what was drawn without having the original mask ??? --> maybe a good idea --> call it get user drawing ???
|
|
489
|
+
def get_mask(self):
|
|
490
|
+
# nb will only work for red pen !!!! --> do not allow to change pen color
|
|
491
|
+
# pas mal mais image est RGBA
|
|
492
|
+
if self.imageDraw is not None:
|
|
493
|
+
# return Img.qimageToNumpy(self.imageDraw, mode='bnw8')
|
|
494
|
+
mask = self.convert_qimage_to_numpy(self.imageDraw)[..., 2]
|
|
495
|
+
# for some reason there are some 1 in the image --> needs a fix
|
|
496
|
+
mask[mask!=255]=0
|
|
497
|
+
return mask
|
|
498
|
+
else:
|
|
499
|
+
return None
|
|
500
|
+
|
|
501
|
+
def get_user_drawing(self, show_erased=False):
|
|
502
|
+
if self.raw_user_drawing is not None:
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# bug here the user draing contains the original mask --> bug
|
|
507
|
+
|
|
508
|
+
all = self.convert_qimage_to_numpy(self.raw_user_drawing)
|
|
509
|
+
|
|
510
|
+
# plt.imshow(all)
|
|
511
|
+
# plt.show()
|
|
512
|
+
|
|
513
|
+
# no clue why this stuff is empty
|
|
514
|
+
# import matplotlib.pyplot as plt
|
|
515
|
+
# plt.imshow(all)
|
|
516
|
+
# plt.show()
|
|
517
|
+
|
|
518
|
+
drawn = all[..., 2]
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
# plt.imshow(erased) #--> ok
|
|
522
|
+
# plt.show()
|
|
523
|
+
|
|
524
|
+
# plt.imshow(drawn)
|
|
525
|
+
# plt.show()
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
drawn[drawn != 255] = 0
|
|
529
|
+
|
|
530
|
+
if show_erased:
|
|
531
|
+
erased = all[..., 1]
|
|
532
|
+
erased[erased != 255] = 0
|
|
533
|
+
return [erased, drawn]
|
|
534
|
+
|
|
535
|
+
return drawn
|
|
536
|
+
else:
|
|
537
|
+
return None
|
|
538
|
+
|
|
539
|
+
# from https://stackoverflow.com/questions/19902183/qimage-to-numpy-array-using-pyside
|
|
540
|
+
# nb there seems to be a bug in the order of the channels
|
|
541
|
+
def convert_qimage_to_numpy(self, qimage):
|
|
542
|
+
qimage = qimage.convertToFormat(QtGui.QImage.Format.Format_RGB32)
|
|
543
|
+
width = qimage.width()
|
|
544
|
+
height = qimage.height()
|
|
545
|
+
image_pointer = qimage.bits() # creates a deep copy --> this is what I want
|
|
546
|
+
try:
|
|
547
|
+
image_pointer.setsize(qimage.sizeInBytes()) # qt6 version of the stuff
|
|
548
|
+
except:
|
|
549
|
+
image_pointer.setsize(qimage.byteCount())
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
# arr = np.array(image_pointer,copy=True).reshape(height, width, 4)
|
|
553
|
+
arr = np.array(image_pointer).reshape(height, width, 4)
|
|
554
|
+
return arr
|
|
555
|
+
|
|
556
|
+
def createRGBA(self, handCorrection):
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
# print('Im called')
|
|
563
|
+
# use pen color to display the mask
|
|
564
|
+
# in fact I need to put the real color
|
|
565
|
+
# nb this will definitely not work if the user wants to have full 3D stuff
|
|
566
|
+
|
|
567
|
+
# shall I also offer the zooming on the objects --> ????
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
RGBA = np.zeros((handCorrection.shape[0], handCorrection.shape[1], 4), dtype=np.uint8)
|
|
573
|
+
|
|
574
|
+
red = self.drawColor.red()
|
|
575
|
+
green = self.drawColor.green()
|
|
576
|
+
blue = self.drawColor.blue()
|
|
577
|
+
|
|
578
|
+
#(handCorrection.shape[0], handCorrection.shape[1], 4))
|
|
579
|
+
# mask = handCorrection[handCorrection==255]
|
|
580
|
+
|
|
581
|
+
# ce truc est bleu --> pkoi
|
|
582
|
+
# RGBA[..., 0] = 255#handCorrection --> totalement transp --> transparence color --> GREEN --> BLUE
|
|
583
|
+
# RGBA[..., 1] = 0 # this is the red channel --> RED --> GREEN
|
|
584
|
+
# RGBA[..., 2] = 0 # rien ici --> tt transparent --> alpha ??? --> ALPHA -->RED
|
|
585
|
+
# RGBA[..., 3] = 255 # --> BLUE --> ALPHA --> ok
|
|
586
|
+
|
|
587
|
+
# pkoi je swappe les channels avant
|
|
588
|
+
|
|
589
|
+
# 255 partout --> blanc
|
|
590
|
+
# 0 255 255 0 --> rouge --> 1 = red channel and 2 = alpha --> weird
|
|
591
|
+
|
|
592
|
+
# RGBA[RGBA[..., 0] > 0, 0] = blue
|
|
593
|
+
# RGBA[RGBA[..., 1] > 0, 1] = green
|
|
594
|
+
# RGBA[RGBA[..., 2] > 0, 2] = red
|
|
595
|
+
# RGBA[..., 3] = 255 # ne s'affiche pas et je comprend pas pkoi
|
|
596
|
+
|
|
597
|
+
#BGR in fact --> need fix here or the other --> the one in
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
# how can I do that in a simpler way ???
|
|
601
|
+
# bug somewhere in qimage --> fix it some day --> due to bgra instead of RGBA
|
|
602
|
+
# assumes image has just the first
|
|
603
|
+
RGBA[handCorrection != 0, 0] = blue # b
|
|
604
|
+
# RGBA[..., 1] = handCorrection
|
|
605
|
+
RGBA[handCorrection != 0, 1] = green # g
|
|
606
|
+
RGBA[handCorrection != 0, 2] = red # r
|
|
607
|
+
RGBA[..., 3] = 255 # alpha --> indeed alpha
|
|
608
|
+
RGBA[handCorrection == 0, 3] = 0 # ça marche maintenant --> super complexe qd meme je trouve
|
|
609
|
+
|
|
610
|
+
# can I do it in one go --> I guess yes!!!
|
|
611
|
+
# RGBA[..., 3] = 255
|
|
612
|
+
# RGBA[handCorrection != 0] = blue,green,red, 0
|
|
613
|
+
# RGBA[handCorrection==0, 3] = 255
|
|
614
|
+
|
|
615
|
+
# ok mais plus transparent
|
|
616
|
+
|
|
617
|
+
#
|
|
618
|
+
# print(red, green, blue)
|
|
619
|
+
|
|
620
|
+
# RGBA[RGBA[..., 0] == 255] = red
|
|
621
|
+
# RGBA[RGBA[..., 1] == 255] = green
|
|
622
|
+
# RGBA[RGBA[..., 2] == 255] = blue
|
|
623
|
+
|
|
624
|
+
return RGBA
|
|
625
|
+
|
|
626
|
+
# TODO also implement channel change directly within the display tool
|
|
627
|
+
# if merge is applied --> apply on average of all channels --> maybe not so smart an idea but ok to start with and better than what I do in TA
|
|
628
|
+
|
|
629
|
+
# dunno if I put it here or not ???
|
|
630
|
+
# def get_nb_channels(self):
|
|
631
|
+
# if self.
|
|
632
|
+
|
|
633
|
+
# depreacted --> ideally should not be used
|
|
634
|
+
# TODO--> maybe implement a multi channel save -> TODO
|
|
635
|
+
# skip
|
|
636
|
+
def channelChange(self, i, skip_update_display=False):
|
|
637
|
+
# update displayed image depending on channel
|
|
638
|
+
# dqqsdqsdqsd
|
|
639
|
+
# pass
|
|
640
|
+
# try change channel if
|
|
641
|
+
|
|
642
|
+
# print('in channel change !!!')
|
|
643
|
+
# if image_to_display is None:
|
|
644
|
+
# image_to_display = self.raw_image
|
|
645
|
+
if self.raw_image is not None:
|
|
646
|
+
# print('in', self.img.metadata)
|
|
647
|
+
# if self.Stack.currentIndex() == 0:
|
|
648
|
+
# need copy the image --> implement that
|
|
649
|
+
# print(self.img[..., i].copy())
|
|
650
|
+
# print(self.img[..., i])
|
|
651
|
+
if i == 0 and not skip_update_display:
|
|
652
|
+
self.set_display(self.raw_image)
|
|
653
|
+
self.channel = None
|
|
654
|
+
# print('original', self.img.metadata)
|
|
655
|
+
else:
|
|
656
|
+
if not skip_update_display:
|
|
657
|
+
# print('modified0', self.img.metadata)
|
|
658
|
+
# I need a hack when the image is single channel yet I need several masks for it !!!
|
|
659
|
+
meta = None
|
|
660
|
+
try:
|
|
661
|
+
meta = self.raw_image
|
|
662
|
+
except:
|
|
663
|
+
pass
|
|
664
|
+
if self.multichannel_mode and i-1>=self.raw_image.shape[-1]:
|
|
665
|
+
channel_img = self.raw_image.imCopy(c=0) # if out of bonds load the first channel
|
|
666
|
+
else:
|
|
667
|
+
channel_img = self.raw_image.imCopy(c=i - 1) # it's here that it is affected
|
|
668
|
+
self.set_display(
|
|
669
|
+
channel_img, metadata=meta) # maybe do a set display instead rather --> easier to handle --> does a subest of the other
|
|
670
|
+
self.channel = i-1
|
|
671
|
+
# print('modified1', self.img.metadata)
|
|
672
|
+
# print('modified2', channel_img.metadata)
|
|
673
|
+
|
|
674
|
+
if self.multichannel_mode:
|
|
675
|
+
if self.raw_mask is not None:
|
|
676
|
+
# print('multichannel_mode', self.multichannel_mode)
|
|
677
|
+
try:
|
|
678
|
+
# print('self.raw_mask.shape',self.raw_mask.shape)
|
|
679
|
+
if len(self.raw_mask.shape)<=2:
|
|
680
|
+
self.raw_mask = self.raw_mask[..., np.newaxis]
|
|
681
|
+
self.set_mask(self.raw_mask[..., i-1])
|
|
682
|
+
except:
|
|
683
|
+
# print(i-1, self.raw_mask.shape[-1] , len(self.raw_mask.shape))
|
|
684
|
+
if i-1 >= self.raw_mask.shape[-1] and len(self.raw_mask.shape)>2:
|
|
685
|
+
# extend mask
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
if len(self.raw_mask.shape) > 2:
|
|
689
|
+
raw_mask2 = np.zeros_like(self.raw_mask, shape=(*self.raw_mask.shape[0:-1], i))
|
|
690
|
+
else:
|
|
691
|
+
raw_mask2 = np.zeros_like(self.raw_mask, shape=(*self.raw_mask.shape, i))
|
|
692
|
+
|
|
693
|
+
# copy existing mask to it
|
|
694
|
+
# print(raw_mask2.shape, self.raw_mask.shape)
|
|
695
|
+
for ch in range(self.raw_mask.shape[-1]):
|
|
696
|
+
raw_mask2[...,ch]=self.raw_mask[...,ch]
|
|
697
|
+
self.raw_mask = raw_mask2
|
|
698
|
+
self.set_mask(self.raw_mask[..., i - 1])
|
|
699
|
+
else:
|
|
700
|
+
traceback.print_exc()
|
|
701
|
+
else:
|
|
702
|
+
# create empty mask
|
|
703
|
+
if len(self.raw_image.shape)>2:
|
|
704
|
+
self.raw_mask = np.zeros(shape=(*self.raw_image.shape[0:-1],i))
|
|
705
|
+
else:
|
|
706
|
+
self.raw_mask = np.zeros(shape=(*self.raw_image.shape, i))
|
|
707
|
+
|
|
708
|
+
# print('doubs', self.raw_mask.shape, self.raw_image.shape)
|
|
709
|
+
self.set_mask(self.raw_mask[..., i - 1])
|
|
710
|
+
self.update()
|
|
711
|
+
else:
|
|
712
|
+
self.channel = None
|
|
713
|
+
# else:
|
|
714
|
+
# # logger.error("Not implemented yet TODO add support for channels in 3D viewer")
|
|
715
|
+
# # sdqdqsdsqdqsd
|
|
716
|
+
# self.loadVolume()
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def get_colors_drawn_over(self):
|
|
720
|
+
mask = self.get_user_drawing()
|
|
721
|
+
# print('I was called')
|
|
722
|
+
if mask is None:
|
|
723
|
+
return []
|
|
724
|
+
# print(mask)
|
|
725
|
+
|
|
726
|
+
# need get all the colors of the raw image that correspond to user selection
|
|
727
|
+
selected_colors =self.get_raw_image()[mask!=0]
|
|
728
|
+
selected_colors = RGB_to_int24(selected_colors)
|
|
729
|
+
|
|
730
|
+
selected_colors =set(selected_colors.ravel().tolist())
|
|
731
|
+
if 0xFFFFFF in selected_colors:
|
|
732
|
+
selected_colors.remove(0xFFFFFF)
|
|
733
|
+
selected_colors = list(selected_colors)
|
|
734
|
+
# this is the only way I found to reset the user drawing but this is dirty and I must find a better way
|
|
735
|
+
self.set_mask(np.zeros_like(mask))
|
|
736
|
+
|
|
737
|
+
self.update()
|
|
738
|
+
return selected_colors
|
|
739
|
+
|
|
740
|
+
def edit_drawing(self):
|
|
741
|
+
# if self.multichannel_mode and self.channel is None:
|
|
742
|
+
# logger.error('please select a channel first')
|
|
743
|
+
# return
|
|
744
|
+
|
|
745
|
+
# basic mask drawing edit
|
|
746
|
+
drawn_mask = self.get_mask()
|
|
747
|
+
if drawn_mask is None:
|
|
748
|
+
return
|
|
749
|
+
drawn_mask = np.copy(drawn_mask)
|
|
750
|
+
erased, drawn = self.get_user_drawing(show_erased=True)
|
|
751
|
+
# bounds = get_white_bounds([drawn, erased])
|
|
752
|
+
# if bounds is None:
|
|
753
|
+
# pass
|
|
754
|
+
# print(drawn_mask.shape)
|
|
755
|
+
# print(drawn_mask.max(), drawn_mask.min())
|
|
756
|
+
# print(drawn_mask.dtype)
|
|
757
|
+
|
|
758
|
+
drawn_mask[erased!=0]=0
|
|
759
|
+
drawn_mask[drawn!=0]=drawn[drawn!=0]
|
|
760
|
+
if self.multichannel_mode:
|
|
761
|
+
if self.raw_mask is not None:
|
|
762
|
+
if self.channel is not None:
|
|
763
|
+
# print('overwriting mask in image')
|
|
764
|
+
self.raw_mask[..., self.channel]=drawn_mask
|
|
765
|
+
# big bug here this stuff contains the bg image --> really huge bug
|
|
766
|
+
# Img(self.raw_mask, dimensions='hwc').save('/E/Sample_images/sample_images_PA/test_complete_wing_raphael/tst2.tif')
|
|
767
|
+
else:
|
|
768
|
+
# if nothing is specified assume ch 0
|
|
769
|
+
if len(self.raw_mask.shape)>2:
|
|
770
|
+
self.raw_mask[..., 0] = drawn_mask
|
|
771
|
+
else:
|
|
772
|
+
self.raw_mask = drawn_mask
|
|
773
|
+
|
|
774
|
+
# Img(drawn_mask, dimensions='hw').save('/E/Sample_images/sample_images_PA/test_complete_wing_raphael/tst.tif')
|
|
775
|
+
self.set_mask(Img(drawn_mask, dimensions='hw'))
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
# draws/removes bonds locally à la TA
|
|
779
|
+
def apply_drawing(self, minimal_cell_size=0):
|
|
780
|
+
# print('minimal_cell_size', minimal_cell_size)
|
|
781
|
+
|
|
782
|
+
drawn_mask = self.get_mask()
|
|
783
|
+
# print(drawn_mask)
|
|
784
|
+
# no image or nothing drawn --> return
|
|
785
|
+
if drawn_mask is None:
|
|
786
|
+
return
|
|
787
|
+
erased, drawn = self.get_user_drawing(show_erased=True)
|
|
788
|
+
bounds = get_white_bounds([drawn, erased])
|
|
789
|
+
if bounds is None:
|
|
790
|
+
# in case the guy presses shift apply with no mask then run it over the whole image --> can make sense to have this
|
|
791
|
+
if minimal_cell_size is not None and minimal_cell_size !=0:
|
|
792
|
+
# dirty way of changing size --> needs be improved
|
|
793
|
+
minimal_cell_size = self.minimal_cell_size
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
# print('minimal_cell_size2', minimal_cell_size)
|
|
797
|
+
drawn_mask = wshed(drawn_mask, seeds='mask', min_seed_area=minimal_cell_size)
|
|
798
|
+
self.set_mask(drawn_mask)
|
|
799
|
+
return
|
|
800
|
+
|
|
801
|
+
if np.count_nonzero(erased) != 0:
|
|
802
|
+
lab_erased = label(erased, connectivity=2, background=0)
|
|
803
|
+
rps_lab_erased = regionprops(lab_erased)
|
|
804
|
+
|
|
805
|
+
lab_drawn_mask = label(drawn_mask, connectivity=1, background=255)
|
|
806
|
+
rps_lab_drawn_mask = regionprops(lab_drawn_mask)
|
|
807
|
+
|
|
808
|
+
for erased_bond in rps_lab_erased:
|
|
809
|
+
ids = np.unique(lab_drawn_mask[erased_bond.coords[:, 0], erased_bond.coords[:, 1]])
|
|
810
|
+
for id in ids:
|
|
811
|
+
if id != 0:
|
|
812
|
+
cur_bounds = rps_lab_drawn_mask[id - 1].bbox
|
|
813
|
+
bounds[0] = min(bounds[0], cur_bounds[0])
|
|
814
|
+
bounds[1] = max(bounds[1], cur_bounds[2])
|
|
815
|
+
bounds[2] = min(bounds[2], cur_bounds[1])
|
|
816
|
+
bounds[3] = max(bounds[3], cur_bounds[3])
|
|
817
|
+
|
|
818
|
+
min_y, max_y, min_x, max_x = bounds
|
|
819
|
+
|
|
820
|
+
first_x = 128
|
|
821
|
+
first_y = 128
|
|
822
|
+
min_x = min_x - 128
|
|
823
|
+
max_x = max_x + 128
|
|
824
|
+
min_y = min_y - 128
|
|
825
|
+
max_y = max_y + 128
|
|
826
|
+
|
|
827
|
+
# TODO fix upper bond too
|
|
828
|
+
|
|
829
|
+
if min_x < 0:
|
|
830
|
+
first_x = 128 + min_x
|
|
831
|
+
min_x = 0
|
|
832
|
+
if min_y < 0:
|
|
833
|
+
first_y = 128 + min_y
|
|
834
|
+
min_y = 0
|
|
835
|
+
if max_y > drawn_mask.shape[0]:
|
|
836
|
+
max_y = drawn_mask.shape[0]
|
|
837
|
+
if max_x > drawn_mask.shape[1]:
|
|
838
|
+
max_x = drawn_mask.shape[1]
|
|
839
|
+
|
|
840
|
+
minished = wshed(drawn_mask[min_y:max_y, min_x:max_x], seeds='mask', min_seed_area=minimal_cell_size)
|
|
841
|
+
drawn_mask[min_y:max_y, min_x:max_x][first_y:min(first_y + (bounds[1] - bounds[0]) + 1, minished.shape[0]),first_x:min(first_x + (bounds[3] - bounds[2]) + 1, minished.shape[1])] = minished[first_y:min(first_y + (bounds[1] - bounds[0]) + 1, minished.shape[0]), first_x:min(first_x + (bounds[3] - bounds[2]) + 1, minished.shape[1])]
|
|
842
|
+
self.set_mask(drawn_mask)
|
|
843
|
+
|
|
844
|
+
def force_consistent_range(self, img, auto_convert_float_to_binary=None):
|
|
845
|
+
if auto_convert_float_to_binary is None:
|
|
846
|
+
auto_convert_float_to_binary = self.auto_convert_float_to_binary_threshold
|
|
847
|
+
# for ch in range(img.shape[-1]):
|
|
848
|
+
# tmp = img[...,ch]
|
|
849
|
+
# min = tmp.min()
|
|
850
|
+
# max = tmp.max()
|
|
851
|
+
# # if min!=max:
|
|
852
|
+
# # change range of the stuff
|
|
853
|
+
# # print('error in range')
|
|
854
|
+
# if max<=1.0:
|
|
855
|
+
# print('error in range')
|
|
856
|
+
# print(min, max)
|
|
857
|
+
# print(min, max)
|
|
858
|
+
# img =
|
|
859
|
+
return self.binarize(img, auto_convert_float_to_binary=auto_convert_float_to_binary, force=True)
|
|
860
|
+
|
|
861
|
+
def save_mask(self, multichannel_save=False, forced_nb_of_channels=None):
|
|
862
|
+
# print('saving to ....' + self.save_file_name)
|
|
863
|
+
if self.save_file_name is not None:
|
|
864
|
+
# print('really saving/...')
|
|
865
|
+
mask = self.get_mask()
|
|
866
|
+
if mask is not None:
|
|
867
|
+
logger.debug('saving mask to '+str(self.save_file_name))
|
|
868
|
+
try:
|
|
869
|
+
single_channel_image = len(self.raw_image.shape)==2
|
|
870
|
+
except:
|
|
871
|
+
single_channel_image = False
|
|
872
|
+
if multichannel_save == False or single_channel_image and (forced_nb_of_channels is None or forced_nb_of_channels ==0):
|
|
873
|
+
Img(mask, dimensions='hw').save(self.save_file_name)
|
|
874
|
+
else:
|
|
875
|
+
if self.raw_mask is not None:
|
|
876
|
+
# print('quick saving edited mask')
|
|
877
|
+
# Img(self.raw_mask, dimensions='hwc').save(self.save_file_name)
|
|
878
|
+
|
|
879
|
+
if len(self.raw_mask.shape)>2:
|
|
880
|
+
# I need check all channels have the same normalization
|
|
881
|
+
Img(self.force_consistent_range(self.raw_mask).astype(np.uint8)*255, dimensions='hwc').save(self.save_file_name)
|
|
882
|
+
else:
|
|
883
|
+
Img(self.raw_mask, dimensions='hw').save(self.save_file_name)
|
|
884
|
+
return
|
|
885
|
+
|
|
886
|
+
# print('test', self.channel )
|
|
887
|
+
# print('orig img channels', self.raw_image.shape[-1])
|
|
888
|
+
# if self.channel is None:
|
|
889
|
+
# logger.error('Please select a channel first')
|
|
890
|
+
# return
|
|
891
|
+
# BELOW IS CRAPPY CODE --> BEST is if multichannel_mode to create a black empty image
|
|
892
|
+
# if os.path.exists(self.save_file_name):
|
|
893
|
+
# try:
|
|
894
|
+
# mask_out = Img(self.save_file_name)
|
|
895
|
+
# if self.channel is not None:
|
|
896
|
+
# mask_out[...,self.channel]=mask
|
|
897
|
+
# else:
|
|
898
|
+
# # assume ch0
|
|
899
|
+
# mask_out[..., 0] = mask
|
|
900
|
+
# Img(mask_out, dimensions='hwc').save(self.save_file_name)
|
|
901
|
+
# return
|
|
902
|
+
# except:
|
|
903
|
+
# traceback.print_exc()
|
|
904
|
+
# print('le fichier existe')
|
|
905
|
+
#
|
|
906
|
+
# # on le charge
|
|
907
|
+
# # else:
|
|
908
|
+
# print('le fichier existe pas')
|
|
909
|
+
# mask_out = np.zeros_like(mask, shape=(*mask.shape, self.raw_image.shape[-1]))
|
|
910
|
+
# if self.channel is not None:
|
|
911
|
+
# mask_out[..., self.channel] = mask
|
|
912
|
+
# else:
|
|
913
|
+
# # assume ch0
|
|
914
|
+
# mask_out[..., 0] = mask
|
|
915
|
+
# Img(mask_out, dimensions='hwc').save(self.save_file_name)
|
|
916
|
+
# sinon on recup le nb de canaux de l'image finale
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
# def _update_channel(self, output, mask, channel):
|
|
920
|
+
# if channel is not None:
|
|
921
|
+
# mask_out = np.zeros_like(mask, shape=(*mask.shape, self.raw_image.shape[-1]))
|
|
922
|
+
# mask_out[..., self.channel] = mask
|
|
923
|
+
# Img(mask_out, dimensions='hwc').save(self.save_file_name)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
# this is the local seeded watershed à la TA
|
|
930
|
+
def manually_reseeded_wshed(self):
|
|
931
|
+
|
|
932
|
+
if self.get_raw_image() is None:
|
|
933
|
+
return
|
|
934
|
+
|
|
935
|
+
try:
|
|
936
|
+
if self.channel is None and self.get_raw_image().has_c():
|
|
937
|
+
logger.error('Please select a channel fisrt')
|
|
938
|
+
return
|
|
939
|
+
except:
|
|
940
|
+
print(self.get_raw_image().shape, self.channel)
|
|
941
|
+
if self.channel is None and len(self.get_raw_image().shape)>=3 and self.get_raw_image().shape[-1]>1:
|
|
942
|
+
logger.error('Please select a channel fisrt')
|
|
943
|
+
return
|
|
944
|
+
|
|
945
|
+
usr_drawing = self.get_user_drawing()
|
|
946
|
+
labs = label(usr_drawing, connectivity=2, background=0)
|
|
947
|
+
|
|
948
|
+
# pb here because the user drawing also contains the mask and I don't want it
|
|
949
|
+
# plt.imshow(labs)
|
|
950
|
+
# plt.show()
|
|
951
|
+
|
|
952
|
+
rps_user_drawing = regionprops(labs)
|
|
953
|
+
if len(rps_user_drawing) == 0:
|
|
954
|
+
return
|
|
955
|
+
|
|
956
|
+
drawn_mask = self.get_mask()
|
|
957
|
+
|
|
958
|
+
# plt.imshow(drawn_mask)
|
|
959
|
+
# plt.show()
|
|
960
|
+
|
|
961
|
+
drawn_mask[labs != 0] = 0
|
|
962
|
+
lab_cells_user_drawing = label(drawn_mask, connectivity=1, background=255)
|
|
963
|
+
rps_cells_user_drawing = regionprops(lab_cells_user_drawing)
|
|
964
|
+
|
|
965
|
+
min_y = 100000000
|
|
966
|
+
min_x = 100000000
|
|
967
|
+
max_y = 0
|
|
968
|
+
max_x = 0
|
|
969
|
+
for rps_user in rps_user_drawing:
|
|
970
|
+
label_id = lab_cells_user_drawing[rps_user.coords[0][0], rps_user.coords[0][1]]
|
|
971
|
+
bbox = rps_cells_user_drawing[label_id - 1].bbox
|
|
972
|
+
min_y = min(bbox[0], min_y)
|
|
973
|
+
min_x = min(bbox[1], min_x)
|
|
974
|
+
max_y = max(bbox[2], max_y)
|
|
975
|
+
max_x = max(bbox[3], max_x)
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
# here I need get the channel in fact
|
|
981
|
+
img = self.get_raw_image()[min_y:max_y + 1, min_x:max_x + 1]
|
|
982
|
+
if self.channel is not None:
|
|
983
|
+
img = img[..., self.channel]
|
|
984
|
+
minished = wshed(img,
|
|
985
|
+
seeds=labs[min_y:max_y + 1, min_x:max_x + 1], weak_blur=1.)
|
|
986
|
+
drawn_mask[min_y:max_y + 1, min_x:max_x + 1][minished != 0] = minished[minished != 0]
|
|
987
|
+
# plt.imshow(drawn_mask)
|
|
988
|
+
# plt.show()
|
|
989
|
+
self.set_mask(drawn_mask)
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def mousePressEvent(self, event):
|
|
993
|
+
if not self.hasMouseTracking() or not self.drawing_enabled:
|
|
994
|
+
return
|
|
995
|
+
self.clickCount = 1
|
|
996
|
+
if self.vdp.active:
|
|
997
|
+
self.vdp.mousePressEvent(event)
|
|
998
|
+
self.update()
|
|
999
|
+
return
|
|
1000
|
+
|
|
1001
|
+
if event.buttons() == QtCore.Qt.LeftButton or event.buttons() == QtCore.Qt.RightButton:
|
|
1002
|
+
self.drawing = True
|
|
1003
|
+
zoom_corrected_pos = event.position() / self.scale
|
|
1004
|
+
self.lastPoint = zoom_corrected_pos
|
|
1005
|
+
self.drawOnImage(event)
|
|
1006
|
+
else:
|
|
1007
|
+
self.drawing = False
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
# can I pass this event to something else to synchronize the mouse moves
|
|
1011
|
+
def mouseMoveEvent(self, event):
|
|
1012
|
+
if not self.hasMouseTracking() or not self.drawing_enabled:
|
|
1013
|
+
return
|
|
1014
|
+
else:
|
|
1015
|
+
if self.propagate_mouse_move == True:
|
|
1016
|
+
self.propagate_mouse_event(event)
|
|
1017
|
+
|
|
1018
|
+
# print('in mouse move', self.hasMouseTracking(), self.drawing, self.vdp.active)
|
|
1019
|
+
if self.statusBar:
|
|
1020
|
+
zoom_corrected_pos = event.position() / self.scale
|
|
1021
|
+
self.statusBar.showMessage('x=' + str(zoom_corrected_pos.x()) + ' y=' + str(
|
|
1022
|
+
zoom_corrected_pos.y()))
|
|
1023
|
+
if self.vdp.active:
|
|
1024
|
+
self.vdp.mouseMoveEvent(event)
|
|
1025
|
+
region = self.scrollArea.widget().visibleRegion()
|
|
1026
|
+
self.update(region)
|
|
1027
|
+
return
|
|
1028
|
+
self.drawOnImage(event)
|
|
1029
|
+
|
|
1030
|
+
def mouseReleaseEvent(self, event):
|
|
1031
|
+
self.drawing = False
|
|
1032
|
+
if not self.hasMouseTracking() or not self.drawing_enabled:
|
|
1033
|
+
return
|
|
1034
|
+
if self.vdp.active:
|
|
1035
|
+
self.vdp.mouseReleaseEvent(event)
|
|
1036
|
+
self.update() # required to update drawing
|
|
1037
|
+
return
|
|
1038
|
+
# if event.button == QtCore.Qt.LeftButton:
|
|
1039
|
+
# self.drawing = False
|
|
1040
|
+
if self.clickCount == 1:
|
|
1041
|
+
QTimer.singleShot(QApplication.instance().doubleClickInterval(),
|
|
1042
|
+
self.updateButtonCount)
|
|
1043
|
+
|
|
1044
|
+
def propagate_mouse_event(self, event):
|
|
1045
|
+
print(self,event)
|
|
1046
|
+
pass
|
|
1047
|
+
|
|
1048
|
+
def drawOnImage(self, event):
|
|
1049
|
+
# bug fix for image drawing when image is none
|
|
1050
|
+
if self.imageDraw is None:
|
|
1051
|
+
return
|
|
1052
|
+
zoom_corrected_pos = event.position() / self.scale
|
|
1053
|
+
if self.drawing and (event.buttons() == QtCore.Qt.LeftButton or event.buttons() == QtCore.Qt.RightButton):
|
|
1054
|
+
# now drawing or erasing over the image
|
|
1055
|
+
self._draw_on_image(self.imageDraw, event, zoom_corrected_pos)
|
|
1056
|
+
# this contains only the user drawing and nothing else --> can be useful to have too!!!
|
|
1057
|
+
self._draw_on_image(self.raw_user_drawing, event, zoom_corrected_pos, erase_color=self.eraseColor_visualizer)
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
# Drawing the cursor TODO add boolean to ask if drawing cursor should be shown
|
|
1062
|
+
painter = QtGui.QPainter(self.cursor)
|
|
1063
|
+
try:
|
|
1064
|
+
# We erase previous pointer
|
|
1065
|
+
r = QtCore.QRect(QtCore.QPoint(), self._clear_size * QtCore.QSize() * self.brushSize) # WHY DO I START FROM 0, 0 (empty point I should have taken old coord of self.lastPoint!!! MEGA TODO
|
|
1066
|
+
painter.save()
|
|
1067
|
+
r.moveCenter(self.lastPoint.toPoint())
|
|
1068
|
+
painter.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
|
|
1069
|
+
painter.eraseRect(r)
|
|
1070
|
+
painter.restore()
|
|
1071
|
+
# draw the new one
|
|
1072
|
+
|
|
1073
|
+
stroke_size = 2
|
|
1074
|
+
if self.brushSize<6:
|
|
1075
|
+
stroke_size = 1
|
|
1076
|
+
|
|
1077
|
+
# if is 1 --> I should really draw a point instead
|
|
1078
|
+
# I should also hide the cursor --> that would help
|
|
1079
|
+
painter.setPen(QtGui.QPen(self.cursorColor, stroke_size, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
|
|
1080
|
+
QtCore.Qt.RoundJoin))
|
|
1081
|
+
if self.brushSize >1:
|
|
1082
|
+
painter.drawEllipse(zoom_corrected_pos, int(self.brushSize / 2.),
|
|
1083
|
+
int(self.brushSize / 2.))
|
|
1084
|
+
else:
|
|
1085
|
+
painter.drawPoint(zoom_corrected_pos)
|
|
1086
|
+
except:
|
|
1087
|
+
traceback.print_exc()
|
|
1088
|
+
finally:
|
|
1089
|
+
painter.end()
|
|
1090
|
+
try:
|
|
1091
|
+
region = self.scrollArea.widget().visibleRegion()
|
|
1092
|
+
except:
|
|
1093
|
+
# assume no scroll area --> use self visible region
|
|
1094
|
+
region =self.visibleRegion()
|
|
1095
|
+
|
|
1096
|
+
self.update(region)
|
|
1097
|
+
|
|
1098
|
+
# required to erase mouse pointer
|
|
1099
|
+
self.lastPoint = zoom_corrected_pos
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
def _draw_on_image(self, image_to_be_drawn, event, zoom_corrected_pos, erase_color=None):
|
|
1103
|
+
painter = QtGui.QPainter(image_to_be_drawn)
|
|
1104
|
+
try:
|
|
1105
|
+
# small hack to allow use pen or fingers on touch screens to erase when Ctrl or shift is pressed along with the click --> MAYBE PUT THIS AS AN OPTION SOME DAY BECAUSE THIS MAY PERTURB PEOPLE
|
|
1106
|
+
if event.buttons() == QtCore.Qt.LeftButton and not (event.modifiers() == getCtrlModifier() or event.modifiers() ==QtCore.Qt.ShiftModifier):
|
|
1107
|
+
painter.setPen(QtGui.QPen(self.drawColor, self.brushSize, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
|
|
1108
|
+
QtCore.Qt.RoundJoin))
|
|
1109
|
+
else:
|
|
1110
|
+
if erase_color is None:
|
|
1111
|
+
erase_color = self.eraseColor
|
|
1112
|
+
painter.setPen(QtGui.QPen(erase_color, self.brushSize, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap,
|
|
1113
|
+
QtCore.Qt.RoundJoin))
|
|
1114
|
+
if self.lastPoint != zoom_corrected_pos:
|
|
1115
|
+
painter.drawLine(self.lastPoint, zoom_corrected_pos)
|
|
1116
|
+
else:
|
|
1117
|
+
# if zero length line then draw point instead
|
|
1118
|
+
painter.drawPoint(zoom_corrected_pos)
|
|
1119
|
+
except:
|
|
1120
|
+
traceback.print_exc()
|
|
1121
|
+
finally:
|
|
1122
|
+
painter.end()
|
|
1123
|
+
|
|
1124
|
+
# adds context/right click menu but only in vectorial mode
|
|
1125
|
+
def contextMenuEvent(self, event):
|
|
1126
|
+
if not self.vdp.active:
|
|
1127
|
+
return
|
|
1128
|
+
cmenu = QMenu(self)
|
|
1129
|
+
newAct = cmenu.addAction("New")
|
|
1130
|
+
opnAct = cmenu.addAction("Open")
|
|
1131
|
+
quitAct = cmenu.addAction("Quit")
|
|
1132
|
+
action = cmenu.exec_(self.mapToGlobal(event.pos()))
|
|
1133
|
+
if action == quitAct:
|
|
1134
|
+
sys.exit(0)
|
|
1135
|
+
|
|
1136
|
+
def updateButtonCount(self):
|
|
1137
|
+
self.clickCount = 1
|
|
1138
|
+
|
|
1139
|
+
def mouseDoubleClickEvent(self, event):
|
|
1140
|
+
self.clickCount = 2
|
|
1141
|
+
|
|
1142
|
+
# TODO maybe add controls to be sure we are in drawing mode with a pen ???
|
|
1143
|
+
# double click fill or delete
|
|
1144
|
+
if not self.vdp.active:
|
|
1145
|
+
zoom_corrected_pos = event.position() / self.scale
|
|
1146
|
+
if event.buttons() == QtCore.Qt.LeftButton:
|
|
1147
|
+
# double click fill
|
|
1148
|
+
if self.dialog_fill_erase('fill'):
|
|
1149
|
+
self.double_click_fill(zoom_corrected_pos)
|
|
1150
|
+
elif event.buttons() == QtCore.Qt.RightButton:
|
|
1151
|
+
# double click delete shape
|
|
1152
|
+
if self.dialog_fill_erase('erase'):
|
|
1153
|
+
self.double_click_erase(zoom_corrected_pos)
|
|
1154
|
+
|
|
1155
|
+
self.vdp.mouseDoubleClickEvent(event)
|
|
1156
|
+
|
|
1157
|
+
def dialog_fill_erase(self, action='fill'):
|
|
1158
|
+
choice = QMessageBox.question(self, 'Double click detected!',
|
|
1159
|
+
'Do you want to '+ action +' the shape?',
|
|
1160
|
+
QMessageBox.Yes | QMessageBox.No)
|
|
1161
|
+
if choice == QMessageBox.Yes:
|
|
1162
|
+
return True
|
|
1163
|
+
else:
|
|
1164
|
+
return False
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def double_click_fill(self, zoom_corrected_pos):
|
|
1168
|
+
try:
|
|
1169
|
+
# erased, drawn = self.get_user_drawing(show_erased=True)
|
|
1170
|
+
usr_drawing = self.get_user_drawing()
|
|
1171
|
+
labs = label(usr_drawing, connectivity=2, background=0)
|
|
1172
|
+
# do I really need that can I rather do np.unique or alike and will it be faster
|
|
1173
|
+
rps_user_drawing = regionprops(labs)
|
|
1174
|
+
if len(rps_user_drawing) == 0:
|
|
1175
|
+
return
|
|
1176
|
+
drawn_mask = self.get_mask()
|
|
1177
|
+
# cell clicked over
|
|
1178
|
+
cell_id_clicked_over = labs[int(zoom_corrected_pos.y()), int(zoom_corrected_pos.x())]
|
|
1179
|
+
if cell_id_clicked_over == 0:
|
|
1180
|
+
return
|
|
1181
|
+
# we remove the cell drawn over
|
|
1182
|
+
drawn_mask[labs==cell_id_clicked_over] = 0
|
|
1183
|
+
# maintenant j'inverse et je trouve la cellule à filler sur le negatif
|
|
1184
|
+
lab_cells_user_drawing = label(drawn_mask, connectivity=1, background=255)
|
|
1185
|
+
cell_id_clicked_over2 = lab_cells_user_drawing[int(zoom_corrected_pos.y()), int(zoom_corrected_pos.x())]
|
|
1186
|
+
drawn_mask[lab_cells_user_drawing==cell_id_clicked_over2]=255
|
|
1187
|
+
self.set_mask(drawn_mask)
|
|
1188
|
+
except:
|
|
1189
|
+
logger.error('Double click fill failed')
|
|
1190
|
+
traceback.print_exc()
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
def double_click_erase(self, zoom_corrected_pos):
|
|
1194
|
+
try:
|
|
1195
|
+
# erased, drawn = self.get_user_drawing(show_erased=True)
|
|
1196
|
+
erased, drawn = self.get_user_drawing(show_erased=True)
|
|
1197
|
+
labs = label(erased, connectivity=2, background=0)
|
|
1198
|
+
|
|
1199
|
+
# do I really need that can I rather do np.unique or alike and will it be faster
|
|
1200
|
+
rps_user_drawing = regionprops(labs)
|
|
1201
|
+
if len(rps_user_drawing) == 0:
|
|
1202
|
+
return
|
|
1203
|
+
drawn_mask = self.get_mask()
|
|
1204
|
+
# cell clicked over
|
|
1205
|
+
cell_id_clicked_over = labs[int(zoom_corrected_pos.y()), int(zoom_corrected_pos.x())]
|
|
1206
|
+
if cell_id_clicked_over==0:
|
|
1207
|
+
return
|
|
1208
|
+
# we refill the removed cell
|
|
1209
|
+
drawn_mask[labs==cell_id_clicked_over] = 255
|
|
1210
|
+
# maintenant j'inverse et je trouve la cellule à filler sur le negatif
|
|
1211
|
+
lab_cells_user_drawing = label(drawn_mask, connectivity=1, background=0)
|
|
1212
|
+
cell_id_clicked_over2 = lab_cells_user_drawing[int(zoom_corrected_pos.y()), int(zoom_corrected_pos.x())]
|
|
1213
|
+
drawn_mask[lab_cells_user_drawing==cell_id_clicked_over2]=0
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
self.set_mask(drawn_mask)
|
|
1217
|
+
except:
|
|
1218
|
+
logger.error('Double click delete failed')
|
|
1219
|
+
traceback.print_exc()
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
def paintEvent(self, event):
|
|
1223
|
+
# super(Createpaintwidget, self).paintEvent(event) # KEEP somehow tjis causes bugs --> do not uncomment it
|
|
1224
|
+
canvasPainter = QtGui.QPainter(self)
|
|
1225
|
+
try:
|
|
1226
|
+
|
|
1227
|
+
# the scrollpane visible region
|
|
1228
|
+
try:
|
|
1229
|
+
visibleRegion = self.scrollArea.widget().visibleRegion()
|
|
1230
|
+
except:
|
|
1231
|
+
#assume no scroll region --> visible region is self visible region
|
|
1232
|
+
visibleRegion = self.visibleRegion()
|
|
1233
|
+
# the corresponding rect
|
|
1234
|
+
visibleRect = visibleRegion.boundingRect()
|
|
1235
|
+
# the visibleRect taking zoom into account
|
|
1236
|
+
scaledVisibleRect = QRect(int(visibleRect.x() / self.scale), int(visibleRect.y() / self.scale),
|
|
1237
|
+
int(visibleRect.width() / self.scale), int(visibleRect.height() / self.scale))
|
|
1238
|
+
|
|
1239
|
+
if self.image is None:
|
|
1240
|
+
canvasPainter.eraseRect(visibleRect)
|
|
1241
|
+
# canvasPainter.end()
|
|
1242
|
+
return
|
|
1243
|
+
|
|
1244
|
+
canvasPainter.drawImage(visibleRect, self.image, scaledVisibleRect)
|
|
1245
|
+
if not self.vdp.active and self.maskVisible and self.imageDraw is not None:
|
|
1246
|
+
canvasPainter.drawImage(visibleRect, self.imageDraw, scaledVisibleRect)
|
|
1247
|
+
# should draw the cursor
|
|
1248
|
+
canvasPainter.drawImage(visibleRect, self.cursor, scaledVisibleRect)
|
|
1249
|
+
|
|
1250
|
+
# ça ajoute un texte --> pas mal mais le refresh on scroll est pas top --> dois je le dessiner plutot dans vdp --> another set of objects that are non interactible and also cannot be saved and local???
|
|
1251
|
+
|
|
1252
|
+
# if True:
|
|
1253
|
+
# print('adding text')
|
|
1254
|
+
# myQPen = QPen(QtCore.Qt.blue, 3)
|
|
1255
|
+
# canvasPainter.setPen(myQPen)
|
|
1256
|
+
# canvasPainter.setPen(QColor("Green"))
|
|
1257
|
+
# canvasPainter.setFont(QFont('Helvetica', 48))
|
|
1258
|
+
# # print('drawing text')
|
|
1259
|
+
# canvasPainter.drawText(visibleRect.x(), visibleRect.y() +10, 'this is a test of your system')
|
|
1260
|
+
# # pb here is that the images is not cleared --> creates bugs
|
|
1261
|
+
|
|
1262
|
+
if self.vdp.active:
|
|
1263
|
+
self.vdp.paintEvent(canvasPainter, scaledVisibleRect)
|
|
1264
|
+
except:
|
|
1265
|
+
traceback.print_exc()
|
|
1266
|
+
finally:
|
|
1267
|
+
canvasPainter.end()
|
|
1268
|
+
|
|
1269
|
+
if __name__ == '__main__':
|
|
1270
|
+
# TODO add a main method so it can be called directly
|
|
1271
|
+
# maybe just show a canvas and give it interesting props --> TODO --> really need fix that too!!!
|
|
1272
|
+
import sys
|
|
1273
|
+
from qtpy.QtWidgets import QApplication
|
|
1274
|
+
from matplotlib import pyplot as plt
|
|
1275
|
+
|
|
1276
|
+
# should probably have his own scroll bar embedded somewhere
|
|
1277
|
+
|
|
1278
|
+
app = QApplication(sys.argv)
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
# in fact that is easy to override methods before construction --> can easily do that in fact
|
|
1282
|
+
|
|
1283
|
+
# quite easy todo in fact
|
|
1284
|
+
class overriding_apply(Createpaintwidget):
|
|
1285
|
+
def apply(self):
|
|
1286
|
+
plt.imshow(self.get_mask())
|
|
1287
|
+
plt.show()
|
|
1288
|
+
|
|
1289
|
+
#Do not use show() in a GUI application.
|
|
1290
|
+
# If you have a FigureCanvas instance embedded in your app, call its
|
|
1291
|
+
# draw() method.
|
|
1292
|
+
# If you use pyplot.figure() to create a matplotlib window from your
|
|
1293
|
+
# app, call pyplot.draw().
|
|
1294
|
+
|
|
1295
|
+
# maybe add a figure canvas the to my code and plot to it and draw it --> quite easy and nice plotting capabilities then in python
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
# w = Createpaintwidget(enable_shortcuts=True)
|
|
1300
|
+
w = overriding_apply(enable_shortcuts=True)
|
|
1301
|
+
# w.set_image('/E/Sample_images/sample_images_PA/mini (copie)/focused_Series012.png')
|
|
1302
|
+
w.set_image('/E/Sample_images/sample_images_PA/mini/focused_Series012.png')
|
|
1303
|
+
# w.set_image('/E/Sample_images/wings/fluorescent_wings_spots_charroux/909dsRed/0.tif')
|
|
1304
|
+
# w.set_image('/E/Sample_images/wings/fluorescent_wings_spots_charroux/contoles_test_X1VK06/0.tif')
|
|
1305
|
+
# w.set_mask('/E/Sample_images/sample_images_PA/mini (copie)/focused_Series012/handCorrection.png')
|
|
1306
|
+
# w.set_image(Img('/E/Sample_images/sample_images_PA/mini (copie)/focused_Series012.png'))
|
|
1307
|
+
|
|
1308
|
+
# print(tst.max())
|
|
1309
|
+
# tst = tst[..., np.newaxis]
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
# tst = Img('/E/Sample_images/wings/fluorescent_wings_spots_charroux/909dsRed/0.tif')
|
|
1315
|
+
# tst = Img('/E/Sample_images/wings/fluorescent_wings_spots_charroux/contoles_test_X1VK06/0.tif')
|
|
1316
|
+
# tst = Img('/E/Sample_images/sample_images_PA/mini (copie)/focused_Series012.png')
|
|
1317
|
+
# tst = normalization(tst, method='Rescaling (min-max normalization)', range=[0,1], individual_channels=len(tst.shape)>2, clip=True)
|
|
1318
|
+
# tst = normalization(tst, method='Rescaling (min-max normalization)', range=[0,1], individual_channels=len(tst.shape)>2, clip=True)
|
|
1319
|
+
# tst = normalization(tst, method='Standardization', range=[0,1], individual_channels=True, clip=True)
|
|
1320
|
+
# tst = normalization(tst, method='Percentile', range=[0,1], individual_channels=len(tst.shape)>2, clip=True, normalization_minima_and_maxima=[1.,50.0])
|
|
1321
|
+
# tst = np.squeeze(tst)
|
|
1322
|
+
|
|
1323
|
+
# tst = auto_threshold(tst)
|
|
1324
|
+
# save_as_tiff(tst, output_name='/E/Sample_images/sample_images_PA/mini (copie)/tst_norm.tif')
|
|
1325
|
+
|
|
1326
|
+
# do I have a standar
|
|
1327
|
+
# tst = normalization(tst, method='standardization', range=[0,1], individual_channels=True)
|
|
1328
|
+
# print(tst.max())
|
|
1329
|
+
# w.set_image(tst)
|
|
1330
|
+
|
|
1331
|
+
# all is so easy to do this way
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
# ça marche --> tres facile a tester
|
|
1335
|
+
# --> how can I do a zoom
|
|
1336
|
+
# ask whether self scroll or not --> if scrollable
|
|
1337
|
+
|
|
1338
|
+
# mask = w.get_mask()
|
|
1339
|
+
#
|
|
1340
|
+
# plt.imshow(mask)
|
|
1341
|
+
# plt.show()
|
|
1342
|
+
|
|
1343
|
+
w.show()
|
|
1344
|
+
sys.exit(app.exec_())
|
|
1345
|
+
|