celldetective 1.0.2__py3-none-any.whl → 1.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.
- celldetective/__main__.py +2 -2
- celldetective/events.py +2 -44
- celldetective/filters.py +4 -5
- celldetective/gui/__init__.py +1 -1
- celldetective/gui/analyze_block.py +37 -10
- celldetective/gui/btrack_options.py +24 -23
- celldetective/gui/classifier_widget.py +62 -19
- celldetective/gui/configure_new_exp.py +32 -35
- celldetective/gui/control_panel.py +115 -81
- celldetective/gui/gui_utils.py +674 -396
- celldetective/gui/json_readers.py +7 -6
- celldetective/gui/layouts.py +755 -0
- celldetective/gui/measurement_options.py +168 -487
- celldetective/gui/neighborhood_options.py +322 -270
- celldetective/gui/plot_measurements.py +1114 -0
- celldetective/gui/plot_signals_ui.py +20 -20
- celldetective/gui/process_block.py +449 -169
- celldetective/gui/retrain_segmentation_model_options.py +27 -26
- celldetective/gui/retrain_signal_model_options.py +25 -24
- celldetective/gui/seg_model_loader.py +31 -27
- celldetective/gui/signal_annotator.py +2326 -2295
- celldetective/gui/signal_annotator_options.py +18 -16
- celldetective/gui/styles.py +16 -1
- celldetective/gui/survival_ui.py +61 -39
- celldetective/gui/tableUI.py +60 -23
- celldetective/gui/thresholds_gui.py +68 -66
- celldetective/gui/viewers.py +596 -0
- celldetective/io.py +234 -23
- celldetective/measure.py +37 -32
- celldetective/neighborhood.py +495 -27
- celldetective/preprocessing.py +683 -0
- celldetective/scripts/analyze_signals.py +7 -0
- celldetective/scripts/measure_cells.py +12 -0
- celldetective/scripts/segment_cells.py +5 -0
- celldetective/scripts/track_cells.py +11 -0
- celldetective/signals.py +221 -98
- celldetective/tracking.py +0 -1
- celldetective/utils.py +178 -36
- celldetective-1.1.0.dist-info/METADATA +305 -0
- celldetective-1.1.0.dist-info/RECORD +80 -0
- {celldetective-1.0.2.dist-info → celldetective-1.1.0.dist-info}/top_level.txt +1 -0
- tests/__init__.py +0 -0
- tests/test_events.py +28 -0
- tests/test_filters.py +24 -0
- tests/test_io.py +70 -0
- tests/test_measure.py +141 -0
- tests/test_neighborhood.py +70 -0
- tests/test_segmentation.py +93 -0
- tests/test_signals.py +135 -0
- tests/test_tracking.py +164 -0
- tests/test_utils.py +71 -0
- celldetective-1.0.2.dist-info/METADATA +0 -192
- celldetective-1.0.2.dist-info/RECORD +0 -66
- {celldetective-1.0.2.dist-info → celldetective-1.1.0.dist-info}/LICENSE +0 -0
- {celldetective-1.0.2.dist-info → celldetective-1.1.0.dist-info}/WHEEL +0 -0
- {celldetective-1.0.2.dist-info → celldetective-1.1.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from celldetective.io import auto_load_number_of_frames, load_frames
|
|
3
|
+
from celldetective.filters import *
|
|
4
|
+
from celldetective.segmentation import filter_image
|
|
5
|
+
from celldetective.measure import contour_of_instance_segmentation
|
|
6
|
+
from celldetective.utils import _get_img_num_per_channel
|
|
7
|
+
from tifffile import imread
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
from stardist import fill_label_holes
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from natsort import natsorted
|
|
12
|
+
from glob import glob
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget
|
|
16
|
+
from PyQt5.QtCore import Qt, QSize
|
|
17
|
+
from celldetective.gui.gui_utils import FigureCanvas, QuickSliderLayout, center_window
|
|
18
|
+
from celldetective.gui import Styles
|
|
19
|
+
from superqt import QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider
|
|
20
|
+
from superqt.fonticon import icon
|
|
21
|
+
from fonticon_mdi6 import MDI6
|
|
22
|
+
from matplotlib_scalebar.scalebar import ScaleBar
|
|
23
|
+
import gc
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StackVisualizer(QWidget, Styles):
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
A widget around an imshow and accompanying sliders.
|
|
30
|
+
"""
|
|
31
|
+
def __init__(self, stack=None, stack_path=None, frame_slider=True, contrast_slider=True, channel_cb=False, channel_names=None, n_channels=1, target_channel=0, window_title='View', PxToUm=None, background_color='transparent',imshow_kwargs={}):
|
|
32
|
+
super().__init__()
|
|
33
|
+
|
|
34
|
+
#self.setWindowTitle(window_title)
|
|
35
|
+
self.window_title = window_title
|
|
36
|
+
|
|
37
|
+
self.stack = stack
|
|
38
|
+
self.stack_path = stack_path
|
|
39
|
+
self.create_frame_slider = frame_slider
|
|
40
|
+
self.background_color = background_color
|
|
41
|
+
self.create_contrast_slider = contrast_slider
|
|
42
|
+
self.create_channel_cb = channel_cb
|
|
43
|
+
self.n_channels = n_channels
|
|
44
|
+
self.channel_names = channel_names
|
|
45
|
+
self.target_channel = target_channel
|
|
46
|
+
self.imshow_kwargs = imshow_kwargs
|
|
47
|
+
self.PxToUm = PxToUm
|
|
48
|
+
self.init_contrast = False
|
|
49
|
+
|
|
50
|
+
self.load_stack() # need to get stack, frame etc
|
|
51
|
+
self.generate_figure_canvas()
|
|
52
|
+
if self.create_channel_cb:
|
|
53
|
+
self.generate_channel_cb()
|
|
54
|
+
if self.create_contrast_slider:
|
|
55
|
+
self.generate_contrast_slider()
|
|
56
|
+
if self.create_frame_slider:
|
|
57
|
+
self.generate_frame_slider()
|
|
58
|
+
|
|
59
|
+
center_window(self)
|
|
60
|
+
self.canvas.layout.setContentsMargins(15,15,15,30)
|
|
61
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
62
|
+
|
|
63
|
+
def show(self):
|
|
64
|
+
self.canvas.show()
|
|
65
|
+
|
|
66
|
+
def load_stack(self):
|
|
67
|
+
|
|
68
|
+
if self.stack is not None:
|
|
69
|
+
|
|
70
|
+
if isinstance(self.stack, list):
|
|
71
|
+
self.stack = np.array(self.stack)
|
|
72
|
+
|
|
73
|
+
if self.stack.ndim==3:
|
|
74
|
+
print('No channel axis found...')
|
|
75
|
+
self.stack = self.stack[:,:,:,np.newaxis]
|
|
76
|
+
self.target_channel = 0
|
|
77
|
+
|
|
78
|
+
self.mode = 'direct'
|
|
79
|
+
self.stack_length = len(self.stack)
|
|
80
|
+
self.mid_time = self.stack_length // 2
|
|
81
|
+
self.init_frame = self.stack[self.mid_time,:,:,self.target_channel]
|
|
82
|
+
self.last_frame = self.stack[-1,:,:,self.target_channel]
|
|
83
|
+
else:
|
|
84
|
+
self.mode = 'virtual'
|
|
85
|
+
assert isinstance(self.stack_path, str)
|
|
86
|
+
assert self.stack_path.endswith('.tif')
|
|
87
|
+
self.locate_image_virtual()
|
|
88
|
+
|
|
89
|
+
def locate_image_virtual(self):
|
|
90
|
+
|
|
91
|
+
self.stack_length = auto_load_number_of_frames(self.stack_path)
|
|
92
|
+
if self.stack_length is None:
|
|
93
|
+
stack = imread(self.stack_path)
|
|
94
|
+
self.stack_length = len(stack)
|
|
95
|
+
del stack
|
|
96
|
+
gc.collect()
|
|
97
|
+
|
|
98
|
+
self.mid_time = self.stack_length // 2
|
|
99
|
+
self.img_num_per_channel = _get_img_num_per_channel(np.arange(self.n_channels), self.stack_length, self.n_channels)
|
|
100
|
+
|
|
101
|
+
self.init_frame = load_frames(self.img_num_per_channel[self.target_channel, self.mid_time],
|
|
102
|
+
self.stack_path,
|
|
103
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
104
|
+
self.last_frame = load_frames(self.img_num_per_channel[self.target_channel, self.stack_length-1],
|
|
105
|
+
self.stack_path,
|
|
106
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
107
|
+
|
|
108
|
+
def generate_figure_canvas(self):
|
|
109
|
+
|
|
110
|
+
self.fig, self.ax = plt.subplots(tight_layout=True) #figsize=(5, 5)
|
|
111
|
+
self.canvas = FigureCanvas(self.fig, title=self.window_title, interactive=True)
|
|
112
|
+
self.ax.clear()
|
|
113
|
+
self.im = self.ax.imshow(self.init_frame, cmap='gray', interpolation='none', **self.imshow_kwargs)
|
|
114
|
+
if self.PxToUm is not None:
|
|
115
|
+
scalebar = ScaleBar(self.PxToUm,
|
|
116
|
+
"um",
|
|
117
|
+
length_fraction=0.25,
|
|
118
|
+
location='upper right',
|
|
119
|
+
border_pad=0.4,
|
|
120
|
+
box_alpha=0.95,
|
|
121
|
+
color='white',
|
|
122
|
+
box_color='black',
|
|
123
|
+
)
|
|
124
|
+
if self.PxToUm==1:
|
|
125
|
+
scalebar = ScaleBar(1,
|
|
126
|
+
"px",
|
|
127
|
+
dimension="pixel-length",
|
|
128
|
+
length_fraction=0.25,
|
|
129
|
+
location='upper right',
|
|
130
|
+
border_pad=0.4,
|
|
131
|
+
box_alpha=0.95,
|
|
132
|
+
color='white',
|
|
133
|
+
box_color='black',
|
|
134
|
+
)
|
|
135
|
+
self.ax.add_artist(scalebar)
|
|
136
|
+
self.ax.set_xticks([])
|
|
137
|
+
self.ax.set_yticks([])
|
|
138
|
+
self.fig.set_facecolor('none') # or 'None'
|
|
139
|
+
self.fig.canvas.setStyleSheet(f"background-color: {self.background_color};")
|
|
140
|
+
self.canvas.canvas.draw()
|
|
141
|
+
|
|
142
|
+
def generate_channel_cb(self):
|
|
143
|
+
|
|
144
|
+
assert self.channel_names is not None
|
|
145
|
+
assert len(self.channel_names)==self.n_channels
|
|
146
|
+
|
|
147
|
+
channel_layout = QHBoxLayout()
|
|
148
|
+
channel_layout.setContentsMargins(15,0,15,0)
|
|
149
|
+
channel_layout.addWidget(QLabel('Channel: '), 25)
|
|
150
|
+
|
|
151
|
+
self.channels_cb = QComboBox()
|
|
152
|
+
self.channels_cb.addItems(self.channel_names)
|
|
153
|
+
self.channels_cb.currentIndexChanged.connect(self.set_channel_index)
|
|
154
|
+
channel_layout.addWidget(self.channels_cb, 75)
|
|
155
|
+
self.canvas.layout.addLayout(channel_layout)
|
|
156
|
+
|
|
157
|
+
def generate_contrast_slider(self):
|
|
158
|
+
|
|
159
|
+
self.contrast_slider = QLabeledDoubleRangeSlider()
|
|
160
|
+
contrast_layout = QuickSliderLayout(
|
|
161
|
+
label='Contrast: ',
|
|
162
|
+
slider=self.contrast_slider,
|
|
163
|
+
slider_initial_value=[np.nanpercentile(self.init_frame, 1),np.nanpercentile(self.init_frame, 99.99)],
|
|
164
|
+
slider_range=(np.nanmin(self.init_frame),np.nanmax(self.init_frame)),
|
|
165
|
+
decimal_option=True,
|
|
166
|
+
precision=1.0E-05,
|
|
167
|
+
)
|
|
168
|
+
contrast_layout.setContentsMargins(15,0,15,0)
|
|
169
|
+
self.im.set_clim(vmin=np.nanpercentile(self.init_frame, 1),vmax=np.nanpercentile(self.init_frame, 99.99))
|
|
170
|
+
self.contrast_slider.valueChanged.connect(self.change_contrast)
|
|
171
|
+
self.canvas.layout.addLayout(contrast_layout)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def generate_frame_slider(self):
|
|
176
|
+
|
|
177
|
+
self.frame_slider = QLabeledSlider()
|
|
178
|
+
frame_layout = QuickSliderLayout(
|
|
179
|
+
label='Frame: ',
|
|
180
|
+
slider=self.frame_slider,
|
|
181
|
+
slider_initial_value=int(self.mid_time),
|
|
182
|
+
slider_range=(0,self.stack_length-1),
|
|
183
|
+
decimal_option=False,
|
|
184
|
+
)
|
|
185
|
+
frame_layout.setContentsMargins(15,0,15,0)
|
|
186
|
+
self.frame_slider.valueChanged.connect(self.change_frame)
|
|
187
|
+
self.canvas.layout.addLayout(frame_layout)
|
|
188
|
+
|
|
189
|
+
def set_target_channel(self, value):
|
|
190
|
+
|
|
191
|
+
self.target_channel = value
|
|
192
|
+
self.change_frame(self.frame_slider.value())
|
|
193
|
+
|
|
194
|
+
def change_contrast(self, value):
|
|
195
|
+
|
|
196
|
+
vmin = value[0]
|
|
197
|
+
vmax = value[1]
|
|
198
|
+
self.im.set_clim(vmin=vmin, vmax=vmax)
|
|
199
|
+
self.fig.canvas.draw_idle()
|
|
200
|
+
|
|
201
|
+
def set_channel_index(self, value):
|
|
202
|
+
|
|
203
|
+
self.target_channel = value
|
|
204
|
+
self.init_contrast = True
|
|
205
|
+
if self.mode == 'direct':
|
|
206
|
+
self.last_frame = self.stack[-1,:,:,self.target_channel]
|
|
207
|
+
elif self.mode == 'virtual':
|
|
208
|
+
self.last_frame = load_frames(self.img_num_per_channel[self.target_channel, self.stack_length-1],
|
|
209
|
+
self.stack_path,
|
|
210
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
211
|
+
self.change_frame(self.frame_slider.value())
|
|
212
|
+
self.init_contrast = False
|
|
213
|
+
|
|
214
|
+
def change_frame(self, value):
|
|
215
|
+
|
|
216
|
+
if self.mode=='virtual':
|
|
217
|
+
|
|
218
|
+
self.init_frame = load_frames(self.img_num_per_channel[self.target_channel, value],
|
|
219
|
+
self.stack_path,
|
|
220
|
+
normalize_input=False
|
|
221
|
+
).astype(float)[:,:,0]
|
|
222
|
+
elif self.mode=='direct':
|
|
223
|
+
self.init_frame = self.stack[value,:,:,self.target_channel].copy()
|
|
224
|
+
|
|
225
|
+
self.im.set_data(self.init_frame)
|
|
226
|
+
|
|
227
|
+
if self.init_contrast:
|
|
228
|
+
self.im.autoscale()
|
|
229
|
+
I_min, I_max = self.im.get_clim()
|
|
230
|
+
self.contrast_slider.setRange(np.nanmin([self.init_frame,self.last_frame]),np.nanmax([self.init_frame,self.last_frame]))
|
|
231
|
+
self.contrast_slider.setValue((I_min,I_max))
|
|
232
|
+
|
|
233
|
+
if self.create_contrast_slider:
|
|
234
|
+
self.change_contrast(self.contrast_slider.value())
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def closeEvent(self, event):
|
|
238
|
+
self.canvas.close()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class ThresholdedStackVisualizer(StackVisualizer):
|
|
242
|
+
|
|
243
|
+
"""
|
|
244
|
+
A widget around an imshow and accompanying sliders.
|
|
245
|
+
"""
|
|
246
|
+
def __init__(self, preprocessing=None, parent_le=None, initial_threshold=5, initial_mask_alpha=0.5, *args, **kwargs):
|
|
247
|
+
|
|
248
|
+
super().__init__(*args, **kwargs)
|
|
249
|
+
self.preprocessing = preprocessing
|
|
250
|
+
self.thresh = initial_threshold
|
|
251
|
+
self.mask_alpha = initial_mask_alpha
|
|
252
|
+
self.parent_le = parent_le
|
|
253
|
+
self.compute_mask(self.thresh)
|
|
254
|
+
self.generate_mask_imshow()
|
|
255
|
+
self.generate_threshold_slider()
|
|
256
|
+
self.generate_opacity_slider()
|
|
257
|
+
if isinstance(self.parent_le, QLineEdit):
|
|
258
|
+
self.generate_apply_btn()
|
|
259
|
+
|
|
260
|
+
def generate_apply_btn(self):
|
|
261
|
+
|
|
262
|
+
apply_hbox = QHBoxLayout()
|
|
263
|
+
self.apply_threshold_btn = QPushButton('Apply')
|
|
264
|
+
self.apply_threshold_btn.clicked.connect(self.set_threshold_in_parent_le)
|
|
265
|
+
self.apply_threshold_btn.setStyleSheet(self.button_style_sheet)
|
|
266
|
+
apply_hbox.addWidget(QLabel(''),33)
|
|
267
|
+
apply_hbox.addWidget(self.apply_threshold_btn, 33)
|
|
268
|
+
apply_hbox.addWidget(QLabel(''),33)
|
|
269
|
+
self.canvas.layout.addLayout(apply_hbox)
|
|
270
|
+
|
|
271
|
+
def set_threshold_in_parent_le(self):
|
|
272
|
+
self.parent_le.set_threshold(self.threshold_slider.value())
|
|
273
|
+
self.close()
|
|
274
|
+
|
|
275
|
+
def generate_mask_imshow(self):
|
|
276
|
+
|
|
277
|
+
self.im_mask = self.ax.imshow(np.ma.masked_where(self.mask==0, self.mask), alpha=self.mask_alpha, interpolation='none')
|
|
278
|
+
self.canvas.canvas.draw()
|
|
279
|
+
|
|
280
|
+
def generate_threshold_slider(self):
|
|
281
|
+
|
|
282
|
+
self.threshold_slider = QLabeledDoubleSlider()
|
|
283
|
+
thresh_layout = QuickSliderLayout(label='Threshold: ',
|
|
284
|
+
slider=self.threshold_slider,
|
|
285
|
+
slider_initial_value=self.thresh,
|
|
286
|
+
slider_range=(0,30),
|
|
287
|
+
decimal_option=True,
|
|
288
|
+
precision=1.0E-05,
|
|
289
|
+
)
|
|
290
|
+
thresh_layout.setContentsMargins(15,0,15,0)
|
|
291
|
+
self.threshold_slider.valueChanged.connect(self.change_threshold)
|
|
292
|
+
self.canvas.layout.addLayout(thresh_layout)
|
|
293
|
+
|
|
294
|
+
def generate_opacity_slider(self):
|
|
295
|
+
|
|
296
|
+
self.opacity_slider = QLabeledDoubleSlider()
|
|
297
|
+
opacity_layout = QuickSliderLayout(label='Opacity: ',
|
|
298
|
+
slider=self.opacity_slider,
|
|
299
|
+
slider_initial_value=0.5,
|
|
300
|
+
slider_range=(0,1),
|
|
301
|
+
decimal_option=True,
|
|
302
|
+
precision=1.0E-03
|
|
303
|
+
)
|
|
304
|
+
opacity_layout.setContentsMargins(15,0,15,0)
|
|
305
|
+
self.opacity_slider.valueChanged.connect(self.change_mask_opacity)
|
|
306
|
+
self.canvas.layout.addLayout(opacity_layout)
|
|
307
|
+
|
|
308
|
+
def change_mask_opacity(self, value):
|
|
309
|
+
|
|
310
|
+
self.mask_alpha = value
|
|
311
|
+
self.im_mask.set_alpha(self.mask_alpha)
|
|
312
|
+
self.canvas.canvas.draw_idle()
|
|
313
|
+
|
|
314
|
+
def change_threshold(self, value):
|
|
315
|
+
|
|
316
|
+
self.thresh = value
|
|
317
|
+
self.compute_mask(self.thresh)
|
|
318
|
+
mask = np.ma.masked_where(self.mask == 0, self.mask)
|
|
319
|
+
self.im_mask.set_data(mask)
|
|
320
|
+
self.canvas.canvas.draw_idle()
|
|
321
|
+
|
|
322
|
+
def change_frame(self, value):
|
|
323
|
+
|
|
324
|
+
super().change_frame(value)
|
|
325
|
+
self.change_threshold(self.threshold_slider.value())
|
|
326
|
+
|
|
327
|
+
def compute_mask(self, threshold_value):
|
|
328
|
+
|
|
329
|
+
self.preprocess_image()
|
|
330
|
+
self.mask = self.processed_image > threshold_value
|
|
331
|
+
self.mask = fill_label_holes(self.mask).astype(int)
|
|
332
|
+
|
|
333
|
+
def preprocess_image(self):
|
|
334
|
+
|
|
335
|
+
if self.preprocessing is not None:
|
|
336
|
+
|
|
337
|
+
assert isinstance(self.preprocessing, list)
|
|
338
|
+
self.processed_image = filter_image(self.init_frame.copy(),filters=self.preprocessing)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class CellEdgeVisualizer(StackVisualizer):
|
|
342
|
+
|
|
343
|
+
"""
|
|
344
|
+
A widget around an imshow and accompanying sliders.
|
|
345
|
+
"""
|
|
346
|
+
def __init__(self, cell_type="effectors", edge_range=(-30,30), invert=False, parent_list_widget=None, parent_le=None, labels=None, initial_edge=5, initial_mask_alpha=0.5, *args, **kwargs):
|
|
347
|
+
|
|
348
|
+
super().__init__(*args, **kwargs)
|
|
349
|
+
self.edge_size = initial_edge
|
|
350
|
+
self.mask_alpha = initial_mask_alpha
|
|
351
|
+
self.cell_type = cell_type
|
|
352
|
+
self.labels = labels
|
|
353
|
+
self.edge_range = edge_range
|
|
354
|
+
self.invert = invert
|
|
355
|
+
self.parent_list_widget = parent_list_widget
|
|
356
|
+
self.parent_le = parent_le
|
|
357
|
+
|
|
358
|
+
self.load_labels()
|
|
359
|
+
self.generate_label_imshow()
|
|
360
|
+
self.generate_edge_slider()
|
|
361
|
+
self.generate_opacity_slider()
|
|
362
|
+
if isinstance(self.parent_list_widget, QListWidget):
|
|
363
|
+
self.generate_add_to_list_btn()
|
|
364
|
+
if isinstance(self.parent_le, QLineEdit):
|
|
365
|
+
self.generate_add_to_le_btn()
|
|
366
|
+
|
|
367
|
+
def load_labels(self):
|
|
368
|
+
|
|
369
|
+
if self.labels is not None:
|
|
370
|
+
|
|
371
|
+
if isinstance(self.labels, list):
|
|
372
|
+
self.labels = np.array(self.labels)
|
|
373
|
+
|
|
374
|
+
assert self.labels.ndim==3,'Wrong dimensions for the provided labels, expect TXY'
|
|
375
|
+
assert len(self.labels)==self.stack_length
|
|
376
|
+
|
|
377
|
+
self.mode = 'direct'
|
|
378
|
+
self.init_label = self.labels[self.mid_time,:,:]
|
|
379
|
+
else:
|
|
380
|
+
self.mode = 'virtual'
|
|
381
|
+
assert isinstance(self.stack_path, str)
|
|
382
|
+
assert self.stack_path.endswith('.tif')
|
|
383
|
+
self.locate_labels_virtual()
|
|
384
|
+
|
|
385
|
+
self.compute_edge_labels()
|
|
386
|
+
|
|
387
|
+
def locate_labels_virtual(self):
|
|
388
|
+
|
|
389
|
+
labels_path = str(Path(self.stack_path).parent.parent) + os.sep + f'labels_{self.cell_type}' + os.sep
|
|
390
|
+
self.mask_paths = natsorted(glob(labels_path + '*.tif'))
|
|
391
|
+
|
|
392
|
+
if len(self.mask_paths) == 0:
|
|
393
|
+
|
|
394
|
+
msgBox = QMessageBox()
|
|
395
|
+
msgBox.setIcon(QMessageBox.Critical)
|
|
396
|
+
msgBox.setText("No labels were found for the selected cells. Abort.")
|
|
397
|
+
msgBox.setWindowTitle("Critical")
|
|
398
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
399
|
+
returnValue = msgBox.exec()
|
|
400
|
+
self.close()
|
|
401
|
+
|
|
402
|
+
self.init_label = imread(self.mask_paths[self.frame_slider.value()])
|
|
403
|
+
|
|
404
|
+
def generate_add_to_list_btn(self):
|
|
405
|
+
|
|
406
|
+
add_hbox = QHBoxLayout()
|
|
407
|
+
self.add_measurement_btn = QPushButton('Add measurement')
|
|
408
|
+
self.add_measurement_btn.clicked.connect(self.set_measurement_in_parent_list)
|
|
409
|
+
self.add_measurement_btn.setIcon(icon(MDI6.plus,color="white"))
|
|
410
|
+
self.add_measurement_btn.setIconSize(QSize(20, 20))
|
|
411
|
+
self.add_measurement_btn.setStyleSheet(self.button_style_sheet)
|
|
412
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
413
|
+
add_hbox.addWidget(self.add_measurement_btn, 33)
|
|
414
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
415
|
+
self.canvas.layout.addLayout(add_hbox)
|
|
416
|
+
|
|
417
|
+
def generate_add_to_le_btn(self):
|
|
418
|
+
|
|
419
|
+
add_hbox = QHBoxLayout()
|
|
420
|
+
self.set_measurement_btn = QPushButton('Set')
|
|
421
|
+
self.set_measurement_btn.clicked.connect(self.set_measurement_in_parent_le)
|
|
422
|
+
self.set_measurement_btn.setStyleSheet(self.button_style_sheet)
|
|
423
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
424
|
+
add_hbox.addWidget(self.set_measurement_btn, 33)
|
|
425
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
426
|
+
self.canvas.layout.addLayout(add_hbox)
|
|
427
|
+
|
|
428
|
+
def set_measurement_in_parent_le(self):
|
|
429
|
+
|
|
430
|
+
self.parent_le.setText(str(int(self.edge_slider.value())))
|
|
431
|
+
self.close()
|
|
432
|
+
|
|
433
|
+
def set_measurement_in_parent_list(self):
|
|
434
|
+
|
|
435
|
+
self.parent_list_widget.addItems([str(self.edge_slider.value())])
|
|
436
|
+
self.close()
|
|
437
|
+
|
|
438
|
+
def generate_label_imshow(self):
|
|
439
|
+
|
|
440
|
+
self.im_mask = self.ax.imshow(np.ma.masked_where(self.edge_labels==0, self.edge_labels), alpha=self.mask_alpha, interpolation='none', cmap="viridis")
|
|
441
|
+
self.canvas.canvas.draw()
|
|
442
|
+
|
|
443
|
+
def generate_edge_slider(self):
|
|
444
|
+
|
|
445
|
+
self.edge_slider = QLabeledSlider()
|
|
446
|
+
edge_layout = QuickSliderLayout(label='Edge: ',
|
|
447
|
+
slider=self.edge_slider,
|
|
448
|
+
slider_initial_value=self.edge_size,
|
|
449
|
+
slider_range=self.edge_range,
|
|
450
|
+
decimal_option=False,
|
|
451
|
+
)
|
|
452
|
+
edge_layout.setContentsMargins(15,0,15,0)
|
|
453
|
+
self.edge_slider.valueChanged.connect(self.change_edge_size)
|
|
454
|
+
self.canvas.layout.addLayout(edge_layout)
|
|
455
|
+
|
|
456
|
+
def generate_opacity_slider(self):
|
|
457
|
+
|
|
458
|
+
self.opacity_slider = QLabeledDoubleSlider()
|
|
459
|
+
opacity_layout = QuickSliderLayout(label='Opacity: ',
|
|
460
|
+
slider=self.opacity_slider,
|
|
461
|
+
slider_initial_value=0.5,
|
|
462
|
+
slider_range=(0,1),
|
|
463
|
+
decimal_option=True,
|
|
464
|
+
precision=1.0E-03
|
|
465
|
+
)
|
|
466
|
+
opacity_layout.setContentsMargins(15,0,15,0)
|
|
467
|
+
self.opacity_slider.valueChanged.connect(self.change_mask_opacity)
|
|
468
|
+
self.canvas.layout.addLayout(opacity_layout)
|
|
469
|
+
|
|
470
|
+
def change_mask_opacity(self, value):
|
|
471
|
+
|
|
472
|
+
self.mask_alpha = value
|
|
473
|
+
self.im_mask.set_alpha(self.mask_alpha)
|
|
474
|
+
self.canvas.canvas.draw_idle()
|
|
475
|
+
|
|
476
|
+
def change_edge_size(self, value):
|
|
477
|
+
|
|
478
|
+
self.edge_size = value
|
|
479
|
+
self.compute_edge_labels()
|
|
480
|
+
mask = np.ma.masked_where(self.edge_labels == 0, self.edge_labels)
|
|
481
|
+
self.im_mask.set_data(mask)
|
|
482
|
+
self.canvas.canvas.draw_idle()
|
|
483
|
+
|
|
484
|
+
def change_frame(self, value):
|
|
485
|
+
|
|
486
|
+
super().change_frame(value)
|
|
487
|
+
|
|
488
|
+
if self.mode=='virtual':
|
|
489
|
+
self.init_label = imread(self.mask_paths[value])
|
|
490
|
+
elif self.mode=='direct':
|
|
491
|
+
self.init_label = self.labels[value,:,:]
|
|
492
|
+
|
|
493
|
+
self.compute_edge_labels()
|
|
494
|
+
mask = np.ma.masked_where(self.edge_labels == 0, self.edge_labels)
|
|
495
|
+
self.im_mask.set_data(mask)
|
|
496
|
+
|
|
497
|
+
def compute_edge_labels(self):
|
|
498
|
+
|
|
499
|
+
if self.invert:
|
|
500
|
+
edge_size = - self.edge_size
|
|
501
|
+
else:
|
|
502
|
+
edge_size = self.edge_size
|
|
503
|
+
|
|
504
|
+
self.edge_labels = contour_of_instance_segmentation(self.init_label, edge_size)
|
|
505
|
+
|
|
506
|
+
class CellSizeViewer(StackVisualizer):
|
|
507
|
+
|
|
508
|
+
"""
|
|
509
|
+
A widget around an imshow and accompanying sliders.
|
|
510
|
+
"""
|
|
511
|
+
def __init__(self, initial_diameter=40, set_radius_in_list=False, diameter_slider_range=(0,200), parent_le=None, parent_list_widget=None, *args, **kwargs):
|
|
512
|
+
|
|
513
|
+
super().__init__(*args, **kwargs)
|
|
514
|
+
self.diameter = initial_diameter
|
|
515
|
+
self.parent_le = parent_le
|
|
516
|
+
self.diameter_slider_range = diameter_slider_range
|
|
517
|
+
self.parent_list_widget = parent_list_widget
|
|
518
|
+
self.set_radius_in_list = set_radius_in_list
|
|
519
|
+
self.generate_circle()
|
|
520
|
+
self.generate_diameter_slider()
|
|
521
|
+
|
|
522
|
+
if isinstance(self.parent_le, QLineEdit):
|
|
523
|
+
self.generate_set_btn()
|
|
524
|
+
if isinstance(self.parent_list_widget, QListWidget):
|
|
525
|
+
self.generate_add_to_list_btn()
|
|
526
|
+
|
|
527
|
+
def generate_circle(self):
|
|
528
|
+
|
|
529
|
+
self.circ = plt.Circle((self.init_frame.shape[0]//2,self.init_frame.shape[1]//2), self.diameter//2, ec="tab:red",fill=False)
|
|
530
|
+
self.ax.add_patch(self.circ)
|
|
531
|
+
|
|
532
|
+
self.ax.callbacks.connect('xlim_changed',self.on_xlims_or_ylims_change)
|
|
533
|
+
self.ax.callbacks.connect('ylim_changed', self.on_xlims_or_ylims_change)
|
|
534
|
+
|
|
535
|
+
def generate_add_to_list_btn(self):
|
|
536
|
+
|
|
537
|
+
add_hbox = QHBoxLayout()
|
|
538
|
+
self.add_measurement_btn = QPushButton('Add measurement')
|
|
539
|
+
self.add_measurement_btn.clicked.connect(self.set_measurement_in_parent_list)
|
|
540
|
+
self.add_measurement_btn.setIcon(icon(MDI6.plus,color="white"))
|
|
541
|
+
self.add_measurement_btn.setIconSize(QSize(20, 20))
|
|
542
|
+
self.add_measurement_btn.setStyleSheet(self.button_style_sheet)
|
|
543
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
544
|
+
add_hbox.addWidget(self.add_measurement_btn, 33)
|
|
545
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
546
|
+
self.canvas.layout.addLayout(add_hbox)
|
|
547
|
+
|
|
548
|
+
def set_measurement_in_parent_list(self):
|
|
549
|
+
if self.set_radius_in_list:
|
|
550
|
+
val = int(self.diameter_slider.value()//2)
|
|
551
|
+
else:
|
|
552
|
+
val = int(self.diameter_slider.value())
|
|
553
|
+
|
|
554
|
+
self.parent_list_widget.addItems([str(val)])
|
|
555
|
+
self.close()
|
|
556
|
+
|
|
557
|
+
def on_xlims_or_ylims_change(self, event_ax):
|
|
558
|
+
|
|
559
|
+
xmin,xmax = event_ax.get_xlim()
|
|
560
|
+
ymin,ymax = event_ax.get_ylim()
|
|
561
|
+
self.circ.center = np.mean([xmin,xmax]), np.mean([ymin,ymax])
|
|
562
|
+
|
|
563
|
+
def generate_set_btn(self):
|
|
564
|
+
|
|
565
|
+
apply_hbox = QHBoxLayout()
|
|
566
|
+
self.apply_threshold_btn = QPushButton('Set')
|
|
567
|
+
self.apply_threshold_btn.clicked.connect(self.set_threshold_in_parent_le)
|
|
568
|
+
self.apply_threshold_btn.setStyleSheet(self.button_style_sheet)
|
|
569
|
+
apply_hbox.addWidget(QLabel(''),33)
|
|
570
|
+
apply_hbox.addWidget(self.apply_threshold_btn, 33)
|
|
571
|
+
apply_hbox.addWidget(QLabel(''),33)
|
|
572
|
+
self.canvas.layout.addLayout(apply_hbox)
|
|
573
|
+
|
|
574
|
+
def set_threshold_in_parent_le(self):
|
|
575
|
+
self.parent_le.set_threshold(self.diameter_slider.value())
|
|
576
|
+
self.close()
|
|
577
|
+
|
|
578
|
+
def generate_diameter_slider(self):
|
|
579
|
+
|
|
580
|
+
self.diameter_slider = QLabeledDoubleSlider()
|
|
581
|
+
diameter_layout = QuickSliderLayout(label='Diameter: ',
|
|
582
|
+
slider=self.diameter_slider,
|
|
583
|
+
slider_initial_value=self.diameter,
|
|
584
|
+
slider_range=self.diameter_slider_range,
|
|
585
|
+
decimal_option=True,
|
|
586
|
+
precision=1.0E-05,
|
|
587
|
+
)
|
|
588
|
+
diameter_layout.setContentsMargins(15,0,15,0)
|
|
589
|
+
self.diameter_slider.valueChanged.connect(self.change_diameter)
|
|
590
|
+
self.canvas.layout.addLayout(diameter_layout)
|
|
591
|
+
|
|
592
|
+
def change_diameter(self, value):
|
|
593
|
+
|
|
594
|
+
self.diameter = value
|
|
595
|
+
self.circ.set_radius(self.diameter//2)
|
|
596
|
+
self.canvas.canvas.draw_idle()
|