celldetective 1.1.0__py3-none-any.whl → 1.1.1.post1__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 +5 -19
- celldetective/extra_properties.py +63 -53
- celldetective/filters.py +39 -11
- celldetective/gui/classifier_widget.py +56 -7
- celldetective/gui/control_panel.py +5 -0
- celldetective/gui/layouts.py +3 -2
- celldetective/gui/measurement_options.py +13 -109
- celldetective/gui/plot_signals_ui.py +1 -0
- celldetective/gui/process_block.py +1 -1
- celldetective/gui/survival_ui.py +7 -1
- celldetective/gui/tableUI.py +294 -28
- celldetective/gui/thresholds_gui.py +51 -10
- celldetective/gui/viewers.py +169 -22
- celldetective/io.py +41 -17
- celldetective/measure.py +13 -238
- celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
- celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
- celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
- celldetective/neighborhood.py +4 -1
- celldetective/preprocessing.py +483 -143
- celldetective/scripts/segment_cells.py +26 -7
- celldetective/scripts/train_segmentation_model.py +35 -34
- celldetective/segmentation.py +29 -20
- celldetective/signals.py +13 -231
- celldetective/tracking.py +2 -1
- celldetective/utils.py +440 -26
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/METADATA +1 -1
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/RECORD +34 -30
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/WHEEL +1 -1
- tests/test_preprocessing.py +37 -0
- tests/test_utils.py +48 -1
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/top_level.txt +0 -0
celldetective/gui/viewers.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from celldetective.io import auto_load_number_of_frames, load_frames
|
|
3
3
|
from celldetective.filters import *
|
|
4
|
-
from celldetective.segmentation import filter_image
|
|
4
|
+
from celldetective.segmentation import filter_image, threshold_image
|
|
5
5
|
from celldetective.measure import contour_of_instance_segmentation
|
|
6
|
-
from celldetective.utils import _get_img_num_per_channel
|
|
6
|
+
from celldetective.utils import _get_img_num_per_channel, estimate_unreliable_edge
|
|
7
7
|
from tifffile import imread
|
|
8
8
|
import matplotlib.pyplot as plt
|
|
9
9
|
from stardist import fill_label_holes
|
|
@@ -21,13 +21,46 @@ from superqt.fonticon import icon
|
|
|
21
21
|
from fonticon_mdi6 import MDI6
|
|
22
22
|
from matplotlib_scalebar.scalebar import ScaleBar
|
|
23
23
|
import gc
|
|
24
|
-
|
|
24
|
+
from celldetective.utils import mask_edges
|
|
25
25
|
|
|
26
26
|
class StackVisualizer(QWidget, Styles):
|
|
27
27
|
|
|
28
28
|
"""
|
|
29
|
-
A widget
|
|
29
|
+
A widget for visualizing image stacks with interactive sliders and channel selection.
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
- stack (numpy.ndarray or None): The stack of images.
|
|
33
|
+
- stack_path (str or None): The path to the stack of images if provided as a file.
|
|
34
|
+
- frame_slider (bool): Enable frame navigation slider.
|
|
35
|
+
- contrast_slider (bool): Enable contrast adjustment slider.
|
|
36
|
+
- channel_cb (bool): Enable channel selection dropdown.
|
|
37
|
+
- channel_names (list or None): Names of the channels if `channel_cb` is True.
|
|
38
|
+
- n_channels (int): Number of channels.
|
|
39
|
+
- target_channel (int): Index of the target channel.
|
|
40
|
+
- window_title (str): Title of the window.
|
|
41
|
+
- PxToUm (float or None): Pixel to micrometer conversion factor.
|
|
42
|
+
- background_color (str): Background color of the widget.
|
|
43
|
+
- imshow_kwargs (dict): Additional keyword arguments for imshow function.
|
|
44
|
+
|
|
45
|
+
Methods:
|
|
46
|
+
- show(): Display the widget.
|
|
47
|
+
- load_stack(): Load the stack of images.
|
|
48
|
+
- locate_image_virtual(): Locate the stack of images if provided as a file.
|
|
49
|
+
- generate_figure_canvas(): Generate the figure canvas for displaying images.
|
|
50
|
+
- generate_channel_cb(): Generate the channel dropdown if enabled.
|
|
51
|
+
- generate_contrast_slider(): Generate the contrast slider if enabled.
|
|
52
|
+
- generate_frame_slider(): Generate the frame slider if enabled.
|
|
53
|
+
- set_target_channel(value): Set the target channel.
|
|
54
|
+
- change_contrast(value): Change contrast based on slider value.
|
|
55
|
+
- set_channel_index(value): Set the channel index based on dropdown value.
|
|
56
|
+
- change_frame(value): Change the displayed frame based on slider value.
|
|
57
|
+
- closeEvent(event): Event handler for closing the widget.
|
|
58
|
+
|
|
59
|
+
Notes:
|
|
60
|
+
- This class provides a convenient interface for visualizing image stacks with frame navigation,
|
|
61
|
+
contrast adjustment, and channel selection functionalities.
|
|
30
62
|
"""
|
|
63
|
+
|
|
31
64
|
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
65
|
super().__init__()
|
|
33
66
|
|
|
@@ -61,10 +94,11 @@ class StackVisualizer(QWidget, Styles):
|
|
|
61
94
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
62
95
|
|
|
63
96
|
def show(self):
|
|
97
|
+
# Display the widget
|
|
64
98
|
self.canvas.show()
|
|
65
99
|
|
|
66
100
|
def load_stack(self):
|
|
67
|
-
|
|
101
|
+
# Load the stack of images
|
|
68
102
|
if self.stack is not None:
|
|
69
103
|
|
|
70
104
|
if isinstance(self.stack, list):
|
|
@@ -87,7 +121,7 @@ class StackVisualizer(QWidget, Styles):
|
|
|
87
121
|
self.locate_image_virtual()
|
|
88
122
|
|
|
89
123
|
def locate_image_virtual(self):
|
|
90
|
-
|
|
124
|
+
# Locate the stack of images if provided as a file
|
|
91
125
|
self.stack_length = auto_load_number_of_frames(self.stack_path)
|
|
92
126
|
if self.stack_length is None:
|
|
93
127
|
stack = imread(self.stack_path)
|
|
@@ -106,6 +140,7 @@ class StackVisualizer(QWidget, Styles):
|
|
|
106
140
|
normalize_input=False).astype(float)[:,:,0]
|
|
107
141
|
|
|
108
142
|
def generate_figure_canvas(self):
|
|
143
|
+
# Generate the figure canvas for displaying images
|
|
109
144
|
|
|
110
145
|
self.fig, self.ax = plt.subplots(tight_layout=True) #figsize=(5, 5)
|
|
111
146
|
self.canvas = FigureCanvas(self.fig, title=self.window_title, interactive=True)
|
|
@@ -140,6 +175,7 @@ class StackVisualizer(QWidget, Styles):
|
|
|
140
175
|
self.canvas.canvas.draw()
|
|
141
176
|
|
|
142
177
|
def generate_channel_cb(self):
|
|
178
|
+
# Generate the channel dropdown if enabled
|
|
143
179
|
|
|
144
180
|
assert self.channel_names is not None
|
|
145
181
|
assert len(self.channel_names)==self.n_channels
|
|
@@ -155,6 +191,7 @@ class StackVisualizer(QWidget, Styles):
|
|
|
155
191
|
self.canvas.layout.addLayout(channel_layout)
|
|
156
192
|
|
|
157
193
|
def generate_contrast_slider(self):
|
|
194
|
+
# Generate the contrast slider if enabled
|
|
158
195
|
|
|
159
196
|
self.contrast_slider = QLabeledDoubleRangeSlider()
|
|
160
197
|
contrast_layout = QuickSliderLayout(
|
|
@@ -173,6 +210,7 @@ class StackVisualizer(QWidget, Styles):
|
|
|
173
210
|
|
|
174
211
|
|
|
175
212
|
def generate_frame_slider(self):
|
|
213
|
+
# Generate the frame slider if enabled
|
|
176
214
|
|
|
177
215
|
self.frame_slider = QLabeledSlider()
|
|
178
216
|
frame_layout = QuickSliderLayout(
|
|
@@ -187,11 +225,13 @@ class StackVisualizer(QWidget, Styles):
|
|
|
187
225
|
self.canvas.layout.addLayout(frame_layout)
|
|
188
226
|
|
|
189
227
|
def set_target_channel(self, value):
|
|
228
|
+
# Set the target channel
|
|
190
229
|
|
|
191
230
|
self.target_channel = value
|
|
192
231
|
self.change_frame(self.frame_slider.value())
|
|
193
232
|
|
|
194
233
|
def change_contrast(self, value):
|
|
234
|
+
# Change contrast based on slider value
|
|
195
235
|
|
|
196
236
|
vmin = value[0]
|
|
197
237
|
vmax = value[1]
|
|
@@ -199,6 +239,7 @@ class StackVisualizer(QWidget, Styles):
|
|
|
199
239
|
self.fig.canvas.draw_idle()
|
|
200
240
|
|
|
201
241
|
def set_channel_index(self, value):
|
|
242
|
+
# Set the channel index based on dropdown value
|
|
202
243
|
|
|
203
244
|
self.target_channel = value
|
|
204
245
|
self.init_contrast = True
|
|
@@ -212,6 +253,7 @@ class StackVisualizer(QWidget, Styles):
|
|
|
212
253
|
self.init_contrast = False
|
|
213
254
|
|
|
214
255
|
def change_frame(self, value):
|
|
256
|
+
# Change the displayed frame based on slider value
|
|
215
257
|
|
|
216
258
|
if self.mode=='virtual':
|
|
217
259
|
|
|
@@ -235,16 +277,41 @@ class StackVisualizer(QWidget, Styles):
|
|
|
235
277
|
|
|
236
278
|
|
|
237
279
|
def closeEvent(self, event):
|
|
280
|
+
# Event handler for closing the widget
|
|
238
281
|
self.canvas.close()
|
|
239
282
|
|
|
240
283
|
|
|
241
284
|
class ThresholdedStackVisualizer(StackVisualizer):
|
|
242
|
-
|
|
285
|
+
|
|
243
286
|
"""
|
|
244
|
-
A widget
|
|
287
|
+
A widget for visualizing thresholded image stacks with interactive sliders and channel selection.
|
|
288
|
+
|
|
289
|
+
Parameters:
|
|
290
|
+
- preprocessing (list or None): A list of preprocessing filters to apply to the image before thresholding.
|
|
291
|
+
- parent_le: The parent QLineEdit instance to set the threshold value.
|
|
292
|
+
- initial_threshold (float): Initial threshold value.
|
|
293
|
+
- initial_mask_alpha (float): Initial mask opacity value.
|
|
294
|
+
- args, kwargs: Additional arguments to pass to the parent class constructor.
|
|
295
|
+
|
|
296
|
+
Methods:
|
|
297
|
+
- generate_apply_btn(): Generate the apply button to set the threshold in the parent QLineEdit.
|
|
298
|
+
- set_threshold_in_parent_le(): Set the threshold value in the parent QLineEdit.
|
|
299
|
+
- generate_mask_imshow(): Generate the mask imshow.
|
|
300
|
+
- generate_threshold_slider(): Generate the threshold slider.
|
|
301
|
+
- generate_opacity_slider(): Generate the opacity slider for the mask.
|
|
302
|
+
- change_mask_opacity(value): Change the opacity of the mask.
|
|
303
|
+
- change_threshold(value): Change the threshold value.
|
|
304
|
+
- change_frame(value): Change the displayed frame and update the threshold.
|
|
305
|
+
- compute_mask(threshold_value): Compute the mask based on the threshold value.
|
|
306
|
+
- preprocess_image(): Preprocess the image before thresholding.
|
|
307
|
+
|
|
308
|
+
Notes:
|
|
309
|
+
- This class extends the functionality of StackVisualizer to visualize thresholded image stacks
|
|
310
|
+
with interactive sliders for threshold and mask opacity adjustment.
|
|
245
311
|
"""
|
|
312
|
+
|
|
246
313
|
def __init__(self, preprocessing=None, parent_le=None, initial_threshold=5, initial_mask_alpha=0.5, *args, **kwargs):
|
|
247
|
-
|
|
314
|
+
# Initialize the widget and its attributes
|
|
248
315
|
super().__init__(*args, **kwargs)
|
|
249
316
|
self.preprocessing = preprocessing
|
|
250
317
|
self.thresh = initial_threshold
|
|
@@ -258,7 +325,7 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
258
325
|
self.generate_apply_btn()
|
|
259
326
|
|
|
260
327
|
def generate_apply_btn(self):
|
|
261
|
-
|
|
328
|
+
# Generate the apply button to set the threshold in the parent QLineEdit
|
|
262
329
|
apply_hbox = QHBoxLayout()
|
|
263
330
|
self.apply_threshold_btn = QPushButton('Apply')
|
|
264
331
|
self.apply_threshold_btn.clicked.connect(self.set_threshold_in_parent_le)
|
|
@@ -269,16 +336,17 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
269
336
|
self.canvas.layout.addLayout(apply_hbox)
|
|
270
337
|
|
|
271
338
|
def set_threshold_in_parent_le(self):
|
|
339
|
+
# Set the threshold value in the parent QLineEdit
|
|
272
340
|
self.parent_le.set_threshold(self.threshold_slider.value())
|
|
273
341
|
self.close()
|
|
274
342
|
|
|
275
343
|
def generate_mask_imshow(self):
|
|
276
|
-
|
|
344
|
+
# Generate the mask imshow
|
|
277
345
|
self.im_mask = self.ax.imshow(np.ma.masked_where(self.mask==0, self.mask), alpha=self.mask_alpha, interpolation='none')
|
|
278
346
|
self.canvas.canvas.draw()
|
|
279
347
|
|
|
280
348
|
def generate_threshold_slider(self):
|
|
281
|
-
|
|
349
|
+
# Generate the threshold slider
|
|
282
350
|
self.threshold_slider = QLabeledDoubleSlider()
|
|
283
351
|
thresh_layout = QuickSliderLayout(label='Threshold: ',
|
|
284
352
|
slider=self.threshold_slider,
|
|
@@ -292,7 +360,7 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
292
360
|
self.canvas.layout.addLayout(thresh_layout)
|
|
293
361
|
|
|
294
362
|
def generate_opacity_slider(self):
|
|
295
|
-
|
|
363
|
+
# Generate the opacity slider for the mask
|
|
296
364
|
self.opacity_slider = QLabeledDoubleSlider()
|
|
297
365
|
opacity_layout = QuickSliderLayout(label='Opacity: ',
|
|
298
366
|
slider=self.opacity_slider,
|
|
@@ -306,13 +374,13 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
306
374
|
self.canvas.layout.addLayout(opacity_layout)
|
|
307
375
|
|
|
308
376
|
def change_mask_opacity(self, value):
|
|
309
|
-
|
|
377
|
+
# Change the opacity of the mask
|
|
310
378
|
self.mask_alpha = value
|
|
311
379
|
self.im_mask.set_alpha(self.mask_alpha)
|
|
312
380
|
self.canvas.canvas.draw_idle()
|
|
313
381
|
|
|
314
382
|
def change_threshold(self, value):
|
|
315
|
-
|
|
383
|
+
# Change the threshold value
|
|
316
384
|
self.thresh = value
|
|
317
385
|
self.compute_mask(self.thresh)
|
|
318
386
|
mask = np.ma.masked_where(self.mask == 0, self.mask)
|
|
@@ -320,18 +388,18 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
320
388
|
self.canvas.canvas.draw_idle()
|
|
321
389
|
|
|
322
390
|
def change_frame(self, value):
|
|
323
|
-
|
|
391
|
+
# Change the displayed frame and update the threshold
|
|
324
392
|
super().change_frame(value)
|
|
325
393
|
self.change_threshold(self.threshold_slider.value())
|
|
326
394
|
|
|
327
395
|
def compute_mask(self, threshold_value):
|
|
328
|
-
|
|
396
|
+
# Compute the mask based on the threshold value
|
|
329
397
|
self.preprocess_image()
|
|
330
|
-
|
|
331
|
-
self.mask =
|
|
398
|
+
edge = estimate_unreliable_edge(self.preprocessing)
|
|
399
|
+
self.mask = threshold_image(self.processed_image, threshold_value, 1.0E06, foreground_value=1, edge_exclusion=edge).astype(int)
|
|
332
400
|
|
|
333
401
|
def preprocess_image(self):
|
|
334
|
-
|
|
402
|
+
# Preprocess the image before thresholding
|
|
335
403
|
if self.preprocessing is not None:
|
|
336
404
|
|
|
337
405
|
assert isinstance(self.preprocessing, list)
|
|
@@ -341,10 +409,42 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
341
409
|
class CellEdgeVisualizer(StackVisualizer):
|
|
342
410
|
|
|
343
411
|
"""
|
|
344
|
-
A widget
|
|
412
|
+
A widget for visualizing cell edges with interactive sliders and channel selection.
|
|
413
|
+
|
|
414
|
+
Parameters:
|
|
415
|
+
- cell_type (str): Type of cells ('effectors' by default).
|
|
416
|
+
- edge_range (tuple): Range of edge sizes (-30, 30) by default.
|
|
417
|
+
- invert (bool): Flag to invert the edge size (False by default).
|
|
418
|
+
- parent_list_widget: The parent QListWidget instance to add edge measurements.
|
|
419
|
+
- parent_le: The parent QLineEdit instance to set the edge size.
|
|
420
|
+
- labels (array or None): Array of labels for cell segmentation.
|
|
421
|
+
- initial_edge (int): Initial edge size (5 by default).
|
|
422
|
+
- initial_mask_alpha (float): Initial mask opacity value (0.5 by default).
|
|
423
|
+
- args, kwargs: Additional arguments to pass to the parent class constructor.
|
|
424
|
+
|
|
425
|
+
Methods:
|
|
426
|
+
- load_labels(): Load the cell labels.
|
|
427
|
+
- locate_labels_virtual(): Locate virtual labels.
|
|
428
|
+
- generate_add_to_list_btn(): Generate the add to list button.
|
|
429
|
+
- generate_add_to_le_btn(): Generate the set measurement button for QLineEdit.
|
|
430
|
+
- set_measurement_in_parent_le(): Set the edge size in the parent QLineEdit.
|
|
431
|
+
- set_measurement_in_parent_list(): Add the edge size to the parent QListWidget.
|
|
432
|
+
- generate_label_imshow(): Generate the label imshow.
|
|
433
|
+
- generate_edge_slider(): Generate the edge size slider.
|
|
434
|
+
- generate_opacity_slider(): Generate the opacity slider for the mask.
|
|
435
|
+
- change_mask_opacity(value): Change the opacity of the mask.
|
|
436
|
+
- change_edge_size(value): Change the edge size.
|
|
437
|
+
- change_frame(value): Change the displayed frame and update the edge labels.
|
|
438
|
+
- compute_edge_labels(): Compute the edge labels.
|
|
439
|
+
|
|
440
|
+
Notes:
|
|
441
|
+
- This class extends the functionality of StackVisualizer to visualize cell edges
|
|
442
|
+
with interactive sliders for edge size adjustment and mask opacity control.
|
|
345
443
|
"""
|
|
444
|
+
|
|
346
445
|
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
446
|
|
|
447
|
+
# Initialize the widget and its attributes
|
|
348
448
|
super().__init__(*args, **kwargs)
|
|
349
449
|
self.edge_size = initial_edge
|
|
350
450
|
self.mask_alpha = initial_mask_alpha
|
|
@@ -365,6 +465,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
365
465
|
self.generate_add_to_le_btn()
|
|
366
466
|
|
|
367
467
|
def load_labels(self):
|
|
468
|
+
# Load the cell labels
|
|
368
469
|
|
|
369
470
|
if self.labels is not None:
|
|
370
471
|
|
|
@@ -385,6 +486,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
385
486
|
self.compute_edge_labels()
|
|
386
487
|
|
|
387
488
|
def locate_labels_virtual(self):
|
|
489
|
+
# Locate virtual labels
|
|
388
490
|
|
|
389
491
|
labels_path = str(Path(self.stack_path).parent.parent) + os.sep + f'labels_{self.cell_type}' + os.sep
|
|
390
492
|
self.mask_paths = natsorted(glob(labels_path + '*.tif'))
|
|
@@ -402,6 +504,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
402
504
|
self.init_label = imread(self.mask_paths[self.frame_slider.value()])
|
|
403
505
|
|
|
404
506
|
def generate_add_to_list_btn(self):
|
|
507
|
+
# Generate the add to list button
|
|
405
508
|
|
|
406
509
|
add_hbox = QHBoxLayout()
|
|
407
510
|
self.add_measurement_btn = QPushButton('Add measurement')
|
|
@@ -415,6 +518,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
415
518
|
self.canvas.layout.addLayout(add_hbox)
|
|
416
519
|
|
|
417
520
|
def generate_add_to_le_btn(self):
|
|
521
|
+
# Generate the set measurement button for QLineEdit
|
|
418
522
|
|
|
419
523
|
add_hbox = QHBoxLayout()
|
|
420
524
|
self.set_measurement_btn = QPushButton('Set')
|
|
@@ -426,21 +530,25 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
426
530
|
self.canvas.layout.addLayout(add_hbox)
|
|
427
531
|
|
|
428
532
|
def set_measurement_in_parent_le(self):
|
|
533
|
+
# Set the edge size in the parent QLineEdit
|
|
429
534
|
|
|
430
535
|
self.parent_le.setText(str(int(self.edge_slider.value())))
|
|
431
536
|
self.close()
|
|
432
537
|
|
|
433
538
|
def set_measurement_in_parent_list(self):
|
|
539
|
+
# Add the edge size to the parent QListWidget
|
|
434
540
|
|
|
435
541
|
self.parent_list_widget.addItems([str(self.edge_slider.value())])
|
|
436
542
|
self.close()
|
|
437
543
|
|
|
438
544
|
def generate_label_imshow(self):
|
|
545
|
+
# Generate the label imshow
|
|
439
546
|
|
|
440
547
|
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
548
|
self.canvas.canvas.draw()
|
|
442
549
|
|
|
443
550
|
def generate_edge_slider(self):
|
|
551
|
+
# Generate the edge size slider
|
|
444
552
|
|
|
445
553
|
self.edge_slider = QLabeledSlider()
|
|
446
554
|
edge_layout = QuickSliderLayout(label='Edge: ',
|
|
@@ -454,6 +562,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
454
562
|
self.canvas.layout.addLayout(edge_layout)
|
|
455
563
|
|
|
456
564
|
def generate_opacity_slider(self):
|
|
565
|
+
# Generate the opacity slider for the mask
|
|
457
566
|
|
|
458
567
|
self.opacity_slider = QLabeledDoubleSlider()
|
|
459
568
|
opacity_layout = QuickSliderLayout(label='Opacity: ',
|
|
@@ -468,12 +577,14 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
468
577
|
self.canvas.layout.addLayout(opacity_layout)
|
|
469
578
|
|
|
470
579
|
def change_mask_opacity(self, value):
|
|
580
|
+
# Change the opacity of the mask
|
|
471
581
|
|
|
472
582
|
self.mask_alpha = value
|
|
473
583
|
self.im_mask.set_alpha(self.mask_alpha)
|
|
474
584
|
self.canvas.canvas.draw_idle()
|
|
475
585
|
|
|
476
586
|
def change_edge_size(self, value):
|
|
587
|
+
# Change the edge size
|
|
477
588
|
|
|
478
589
|
self.edge_size = value
|
|
479
590
|
self.compute_edge_labels()
|
|
@@ -482,6 +593,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
482
593
|
self.canvas.canvas.draw_idle()
|
|
483
594
|
|
|
484
595
|
def change_frame(self, value):
|
|
596
|
+
# Change the displayed frame and update the edge labels
|
|
485
597
|
|
|
486
598
|
super().change_frame(value)
|
|
487
599
|
|
|
@@ -495,6 +607,7 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
495
607
|
self.im_mask.set_data(mask)
|
|
496
608
|
|
|
497
609
|
def compute_edge_labels(self):
|
|
610
|
+
# Compute the edge labels
|
|
498
611
|
|
|
499
612
|
if self.invert:
|
|
500
613
|
edge_size = - self.edge_size
|
|
@@ -506,9 +619,33 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
506
619
|
class CellSizeViewer(StackVisualizer):
|
|
507
620
|
|
|
508
621
|
"""
|
|
509
|
-
A widget
|
|
622
|
+
A widget for visualizing cell size with interactive sliders and circle display.
|
|
623
|
+
|
|
624
|
+
Parameters:
|
|
625
|
+
- initial_diameter (int): Initial diameter of the circle (40 by default).
|
|
626
|
+
- set_radius_in_list (bool): Flag to set radius instead of diameter in the list (False by default).
|
|
627
|
+
- diameter_slider_range (tuple): Range of the diameter slider (0, 200) by default.
|
|
628
|
+
- parent_le: The parent QLineEdit instance to set the diameter.
|
|
629
|
+
- parent_list_widget: The parent QListWidget instance to add diameter measurements.
|
|
630
|
+
- args, kwargs: Additional arguments to pass to the parent class constructor.
|
|
631
|
+
|
|
632
|
+
Methods:
|
|
633
|
+
- generate_circle(): Generate the circle for visualization.
|
|
634
|
+
- generate_add_to_list_btn(): Generate the add to list button.
|
|
635
|
+
- set_measurement_in_parent_list(): Add the diameter to the parent QListWidget.
|
|
636
|
+
- on_xlims_or_ylims_change(event_ax): Update the circle position on axis limits change.
|
|
637
|
+
- generate_set_btn(): Generate the set button for QLineEdit.
|
|
638
|
+
- set_threshold_in_parent_le(): Set the diameter in the parent QLineEdit.
|
|
639
|
+
- generate_diameter_slider(): Generate the diameter slider.
|
|
640
|
+
- change_diameter(value): Change the diameter of the circle.
|
|
641
|
+
|
|
642
|
+
Notes:
|
|
643
|
+
- This class extends the functionality of StackVisualizer to visualize cell size
|
|
644
|
+
with interactive sliders for diameter adjustment and circle display.
|
|
510
645
|
"""
|
|
646
|
+
|
|
511
647
|
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):
|
|
648
|
+
# Initialize the widget and its attributes
|
|
512
649
|
|
|
513
650
|
super().__init__(*args, **kwargs)
|
|
514
651
|
self.diameter = initial_diameter
|
|
@@ -525,6 +662,7 @@ class CellSizeViewer(StackVisualizer):
|
|
|
525
662
|
self.generate_add_to_list_btn()
|
|
526
663
|
|
|
527
664
|
def generate_circle(self):
|
|
665
|
+
# Generate the circle for visualization
|
|
528
666
|
|
|
529
667
|
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
668
|
self.ax.add_patch(self.circ)
|
|
@@ -533,6 +671,7 @@ class CellSizeViewer(StackVisualizer):
|
|
|
533
671
|
self.ax.callbacks.connect('ylim_changed', self.on_xlims_or_ylims_change)
|
|
534
672
|
|
|
535
673
|
def generate_add_to_list_btn(self):
|
|
674
|
+
# Generate the add to list button
|
|
536
675
|
|
|
537
676
|
add_hbox = QHBoxLayout()
|
|
538
677
|
self.add_measurement_btn = QPushButton('Add measurement')
|
|
@@ -546,6 +685,8 @@ class CellSizeViewer(StackVisualizer):
|
|
|
546
685
|
self.canvas.layout.addLayout(add_hbox)
|
|
547
686
|
|
|
548
687
|
def set_measurement_in_parent_list(self):
|
|
688
|
+
# Add the diameter to the parent QListWidget
|
|
689
|
+
|
|
549
690
|
if self.set_radius_in_list:
|
|
550
691
|
val = int(self.diameter_slider.value()//2)
|
|
551
692
|
else:
|
|
@@ -555,12 +696,14 @@ class CellSizeViewer(StackVisualizer):
|
|
|
555
696
|
self.close()
|
|
556
697
|
|
|
557
698
|
def on_xlims_or_ylims_change(self, event_ax):
|
|
699
|
+
# Update the circle position on axis limits change
|
|
558
700
|
|
|
559
701
|
xmin,xmax = event_ax.get_xlim()
|
|
560
702
|
ymin,ymax = event_ax.get_ylim()
|
|
561
703
|
self.circ.center = np.mean([xmin,xmax]), np.mean([ymin,ymax])
|
|
562
704
|
|
|
563
705
|
def generate_set_btn(self):
|
|
706
|
+
# Generate the set button for QLineEdit
|
|
564
707
|
|
|
565
708
|
apply_hbox = QHBoxLayout()
|
|
566
709
|
self.apply_threshold_btn = QPushButton('Set')
|
|
@@ -572,10 +715,13 @@ class CellSizeViewer(StackVisualizer):
|
|
|
572
715
|
self.canvas.layout.addLayout(apply_hbox)
|
|
573
716
|
|
|
574
717
|
def set_threshold_in_parent_le(self):
|
|
718
|
+
# Set the diameter in the parent QLineEdit
|
|
719
|
+
|
|
575
720
|
self.parent_le.set_threshold(self.diameter_slider.value())
|
|
576
721
|
self.close()
|
|
577
722
|
|
|
578
723
|
def generate_diameter_slider(self):
|
|
724
|
+
# Generate the diameter slider
|
|
579
725
|
|
|
580
726
|
self.diameter_slider = QLabeledDoubleSlider()
|
|
581
727
|
diameter_layout = QuickSliderLayout(label='Diameter: ',
|
|
@@ -590,6 +736,7 @@ class CellSizeViewer(StackVisualizer):
|
|
|
590
736
|
self.canvas.layout.addLayout(diameter_layout)
|
|
591
737
|
|
|
592
738
|
def change_diameter(self, value):
|
|
739
|
+
# Change the diameter of the circle
|
|
593
740
|
|
|
594
741
|
self.diameter = value
|
|
595
742
|
self.circ.set_radius(self.diameter//2)
|
celldetective/io.py
CHANGED
|
@@ -1238,12 +1238,23 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
|
|
|
1238
1238
|
"""
|
|
1239
1239
|
position = position.replace('\\','/')
|
|
1240
1240
|
if population.lower()=="target" or population.lower()=="targets":
|
|
1241
|
-
|
|
1241
|
+
if os.path.exists(position+os.sep.join(['output','tables','napari_target_trajectories.npy'])):
|
|
1242
|
+
napari_data = np.load(position+os.sep.join(['output','tables','napari_target_trajectories.npy']), allow_pickle=True)
|
|
1243
|
+
else:
|
|
1244
|
+
napari_data = None
|
|
1242
1245
|
elif population.lower()=="effector" or population.lower()=="effectors":
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1246
|
+
if os.path.exists(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy'])):
|
|
1247
|
+
napari_data = np.load(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy']), allow_pickle=True)
|
|
1248
|
+
else:
|
|
1249
|
+
napari_data = None
|
|
1250
|
+
if napari_data is not None:
|
|
1251
|
+
data = napari_data.item()['data']
|
|
1252
|
+
properties = napari_data.item()['properties']
|
|
1253
|
+
graph = napari_data.item()['graph']
|
|
1254
|
+
else:
|
|
1255
|
+
data = None
|
|
1256
|
+
properties = None
|
|
1257
|
+
graph = None
|
|
1247
1258
|
if return_stack:
|
|
1248
1259
|
stack,labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
1249
1260
|
else:
|
|
@@ -1949,6 +1960,8 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
|
1949
1960
|
|
|
1950
1961
|
"""
|
|
1951
1962
|
|
|
1963
|
+
|
|
1964
|
+
|
|
1952
1965
|
mf = multichannel_frame.copy().astype(float)
|
|
1953
1966
|
assert mf.ndim==3,f'Wrong shape for the multichannel frame: {mf.shape}.'
|
|
1954
1967
|
if percentiles is None:
|
|
@@ -1960,20 +1973,27 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
|
1960
1973
|
values = [values]*mf.shape[-1]
|
|
1961
1974
|
assert len(values)==mf.shape[-1],'Mismatch between the normalization values provided and the number of channels.'
|
|
1962
1975
|
|
|
1976
|
+
mf_new = []
|
|
1963
1977
|
for c in range(mf.shape[-1]):
|
|
1964
1978
|
if values is not None:
|
|
1965
1979
|
v = values[c]
|
|
1966
1980
|
else:
|
|
1967
1981
|
v = None
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1982
|
+
|
|
1983
|
+
if np.all(mf[:,:,c]==0.):
|
|
1984
|
+
mf_new.append(mf[:,:,c].copy())
|
|
1985
|
+
else:
|
|
1986
|
+
norm = normalize(mf[:,:,c].copy(),
|
|
1987
|
+
percentiles=percentiles[c],
|
|
1988
|
+
values=v,
|
|
1989
|
+
ignore_gray_value=ignore_gray_value,
|
|
1990
|
+
clip=clip,
|
|
1991
|
+
amplification=amplification,
|
|
1992
|
+
dtype=dtype,
|
|
1993
|
+
)
|
|
1994
|
+
mf_new.append(norm)
|
|
1995
|
+
|
|
1996
|
+
return np.moveaxis(mf_new,0,-1)
|
|
1977
1997
|
|
|
1978
1998
|
def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=float, normalize_kwargs={"percentiles": (0.,99.99)}):
|
|
1979
1999
|
|
|
@@ -2029,7 +2049,7 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
|
|
|
2029
2049
|
"""
|
|
2030
2050
|
|
|
2031
2051
|
try:
|
|
2032
|
-
frames = skio.imread(stack_path,
|
|
2052
|
+
frames = skio.imread(stack_path, key=img_nums, plugin="tifffile")
|
|
2033
2053
|
except Exception as e:
|
|
2034
2054
|
print(f'Error in loading the frame {img_nums} {e}. Please check that the experiment channel information is consistent with the movie being read.')
|
|
2035
2055
|
return None
|
|
@@ -2038,13 +2058,17 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
|
|
|
2038
2058
|
# Systematically move channel axis to the end
|
|
2039
2059
|
channel_axis = np.argmin(frames.shape)
|
|
2040
2060
|
frames = np.moveaxis(frames, channel_axis, -1)
|
|
2061
|
+
|
|
2041
2062
|
if frames.ndim==2:
|
|
2042
2063
|
frames = frames[:,:,np.newaxis].astype(float)
|
|
2064
|
+
|
|
2043
2065
|
if normalize_input:
|
|
2044
2066
|
frames = normalize_multichannel(frames, **normalize_kwargs)
|
|
2067
|
+
|
|
2045
2068
|
if scale is not None:
|
|
2046
|
-
frames = zoom(frames, [scale,scale
|
|
2047
|
-
|
|
2069
|
+
frames = [zoom(frames[:,:,c].copy(), [scale,scale], order=3, prefilter=False) for c in range(frames.shape[-1])]
|
|
2070
|
+
frames = np.moveaxis(frames,0,-1)
|
|
2071
|
+
|
|
2048
2072
|
# add a fake pixel to prevent auto normalization errors on images that are uniform
|
|
2049
2073
|
# to revisit
|
|
2050
2074
|
for k in range(frames.shape[2]):
|