celldetective 1.0.2.post1__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.
Files changed (56) hide show
  1. celldetective/__main__.py +2 -2
  2. celldetective/events.py +2 -44
  3. celldetective/filters.py +4 -5
  4. celldetective/gui/__init__.py +1 -1
  5. celldetective/gui/analyze_block.py +37 -10
  6. celldetective/gui/btrack_options.py +24 -23
  7. celldetective/gui/classifier_widget.py +62 -19
  8. celldetective/gui/configure_new_exp.py +32 -35
  9. celldetective/gui/control_panel.py +115 -81
  10. celldetective/gui/gui_utils.py +674 -396
  11. celldetective/gui/json_readers.py +7 -6
  12. celldetective/gui/layouts.py +755 -0
  13. celldetective/gui/measurement_options.py +168 -487
  14. celldetective/gui/neighborhood_options.py +322 -270
  15. celldetective/gui/plot_measurements.py +1114 -0
  16. celldetective/gui/plot_signals_ui.py +20 -20
  17. celldetective/gui/process_block.py +449 -169
  18. celldetective/gui/retrain_segmentation_model_options.py +27 -26
  19. celldetective/gui/retrain_signal_model_options.py +25 -24
  20. celldetective/gui/seg_model_loader.py +31 -27
  21. celldetective/gui/signal_annotator.py +2326 -2295
  22. celldetective/gui/signal_annotator_options.py +18 -16
  23. celldetective/gui/styles.py +16 -1
  24. celldetective/gui/survival_ui.py +61 -39
  25. celldetective/gui/tableUI.py +60 -23
  26. celldetective/gui/thresholds_gui.py +68 -66
  27. celldetective/gui/viewers.py +596 -0
  28. celldetective/io.py +234 -23
  29. celldetective/measure.py +37 -32
  30. celldetective/neighborhood.py +495 -27
  31. celldetective/preprocessing.py +683 -0
  32. celldetective/scripts/analyze_signals.py +7 -0
  33. celldetective/scripts/measure_cells.py +12 -0
  34. celldetective/scripts/segment_cells.py +5 -0
  35. celldetective/scripts/track_cells.py +11 -0
  36. celldetective/signals.py +221 -98
  37. celldetective/tracking.py +0 -1
  38. celldetective/utils.py +178 -36
  39. celldetective-1.1.0.dist-info/METADATA +305 -0
  40. celldetective-1.1.0.dist-info/RECORD +80 -0
  41. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.0.dist-info}/top_level.txt +1 -0
  42. tests/__init__.py +0 -0
  43. tests/test_events.py +28 -0
  44. tests/test_filters.py +24 -0
  45. tests/test_io.py +70 -0
  46. tests/test_measure.py +141 -0
  47. tests/test_neighborhood.py +70 -0
  48. tests/test_segmentation.py +93 -0
  49. tests/test_signals.py +135 -0
  50. tests/test_tracking.py +164 -0
  51. tests/test_utils.py +71 -0
  52. celldetective-1.0.2.post1.dist-info/METADATA +0 -221
  53. celldetective-1.0.2.post1.dist-info/RECORD +0 -66
  54. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.0.dist-info}/LICENSE +0 -0
  55. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.0.dist-info}/WHEEL +0 -0
  56. {celldetective-1.0.2.post1.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()