celldetective 1.1.0__py3-none-any.whl → 1.1.1__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 (32) hide show
  1. celldetective/__main__.py +5 -19
  2. celldetective/extra_properties.py +62 -52
  3. celldetective/gui/control_panel.py +5 -0
  4. celldetective/gui/layouts.py +1 -0
  5. celldetective/gui/measurement_options.py +13 -109
  6. celldetective/gui/plot_signals_ui.py +1 -0
  7. celldetective/gui/process_block.py +1 -1
  8. celldetective/gui/survival_ui.py +7 -1
  9. celldetective/gui/tableUI.py +280 -28
  10. celldetective/gui/thresholds_gui.py +7 -5
  11. celldetective/gui/viewers.py +169 -22
  12. celldetective/io.py +14 -5
  13. celldetective/measure.py +13 -238
  14. celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
  15. celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
  16. celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
  17. celldetective/neighborhood.py +4 -1
  18. celldetective/preprocessing.py +483 -143
  19. celldetective/scripts/segment_cells.py +15 -4
  20. celldetective/scripts/train_segmentation_model.py +35 -34
  21. celldetective/segmentation.py +14 -9
  22. celldetective/signals.py +13 -231
  23. celldetective/tracking.py +2 -1
  24. celldetective/utils.py +437 -26
  25. {celldetective-1.1.0.dist-info → celldetective-1.1.1.dist-info}/METADATA +1 -1
  26. {celldetective-1.1.0.dist-info → celldetective-1.1.1.dist-info}/RECORD +32 -28
  27. tests/test_preprocessing.py +37 -0
  28. tests/test_utils.py +48 -1
  29. {celldetective-1.1.0.dist-info → celldetective-1.1.1.dist-info}/LICENSE +0 -0
  30. {celldetective-1.1.0.dist-info → celldetective-1.1.1.dist-info}/WHEEL +0 -0
  31. {celldetective-1.1.0.dist-info → celldetective-1.1.1.dist-info}/entry_points.txt +0 -0
  32. {celldetective-1.1.0.dist-info → celldetective-1.1.1.dist-info}/top_level.txt +0 -0
@@ -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 around an imshow and accompanying sliders.
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 around an imshow and accompanying sliders.
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
- self.mask = self.processed_image > threshold_value
331
- self.mask = fill_label_holes(self.mask).astype(int)
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 around an imshow and accompanying sliders.
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 around an imshow and accompanying sliders.
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
@@ -1949,6 +1949,8 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
1949
1949
 
1950
1950
  """
1951
1951
 
1952
+
1953
+
1952
1954
  mf = multichannel_frame.copy().astype(float)
1953
1955
  assert mf.ndim==3,f'Wrong shape for the multichannel frame: {mf.shape}.'
1954
1956
  if percentiles is None:
@@ -1960,12 +1962,13 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
1960
1962
  values = [values]*mf.shape[-1]
1961
1963
  assert len(values)==mf.shape[-1],'Mismatch between the normalization values provided and the number of channels.'
1962
1964
 
1965
+ mf_new = []
1963
1966
  for c in range(mf.shape[-1]):
1964
1967
  if values is not None:
1965
1968
  v = values[c]
1966
1969
  else:
1967
1970
  v = None
1968
- mf[:,:,c] = normalize(mf[:,:,c].copy(),
1971
+ norm = normalize(mf[:,:,c].copy(),
1969
1972
  percentiles=percentiles[c],
1970
1973
  values=v,
1971
1974
  ignore_gray_value=ignore_gray_value,
@@ -1973,7 +1976,9 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
1973
1976
  amplification=amplification,
1974
1977
  dtype=dtype,
1975
1978
  )
1976
- return mf
1979
+ mf_new.append(norm)
1980
+
1981
+ return np.moveaxis(mf_new,0,-1)
1977
1982
 
1978
1983
  def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=float, normalize_kwargs={"percentiles": (0.,99.99)}):
1979
1984
 
@@ -2029,7 +2034,7 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
2029
2034
  """
2030
2035
 
2031
2036
  try:
2032
- frames = skio.imread(stack_path, img_num=img_nums, plugin="tifffile")
2037
+ frames = skio.imread(stack_path, key=img_nums, plugin="tifffile")
2033
2038
  except Exception as e:
2034
2039
  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
2040
  return None
@@ -2038,13 +2043,17 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
2038
2043
  # Systematically move channel axis to the end
2039
2044
  channel_axis = np.argmin(frames.shape)
2040
2045
  frames = np.moveaxis(frames, channel_axis, -1)
2046
+
2041
2047
  if frames.ndim==2:
2042
2048
  frames = frames[:,:,np.newaxis].astype(float)
2049
+
2043
2050
  if normalize_input:
2044
2051
  frames = normalize_multichannel(frames, **normalize_kwargs)
2052
+
2045
2053
  if scale is not None:
2046
- frames = zoom(frames, [scale,scale,1], order=3, prefilter=False)
2047
-
2054
+ frames = [zoom(frames[:,:,c].copy(), [scale,scale], order=3, prefilter=False) for c in range(frames.shape[-1])]
2055
+ frames = np.moveaxis(frames,0,-1)
2056
+
2048
2057
  # add a fake pixel to prevent auto normalization errors on images that are uniform
2049
2058
  # to revisit
2050
2059
  for k in range(frames.shape[2]):