celldetective 1.0.2.post1__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 (63) hide show
  1. celldetective/__main__.py +7 -21
  2. celldetective/events.py +2 -44
  3. celldetective/extra_properties.py +62 -52
  4. celldetective/filters.py +4 -5
  5. celldetective/gui/__init__.py +1 -1
  6. celldetective/gui/analyze_block.py +37 -10
  7. celldetective/gui/btrack_options.py +24 -23
  8. celldetective/gui/classifier_widget.py +62 -19
  9. celldetective/gui/configure_new_exp.py +32 -35
  10. celldetective/gui/control_panel.py +120 -81
  11. celldetective/gui/gui_utils.py +674 -396
  12. celldetective/gui/json_readers.py +7 -6
  13. celldetective/gui/layouts.py +756 -0
  14. celldetective/gui/measurement_options.py +98 -513
  15. celldetective/gui/neighborhood_options.py +322 -270
  16. celldetective/gui/plot_measurements.py +1114 -0
  17. celldetective/gui/plot_signals_ui.py +21 -20
  18. celldetective/gui/process_block.py +449 -169
  19. celldetective/gui/retrain_segmentation_model_options.py +27 -26
  20. celldetective/gui/retrain_signal_model_options.py +25 -24
  21. celldetective/gui/seg_model_loader.py +31 -27
  22. celldetective/gui/signal_annotator.py +2326 -2295
  23. celldetective/gui/signal_annotator_options.py +18 -16
  24. celldetective/gui/styles.py +16 -1
  25. celldetective/gui/survival_ui.py +67 -39
  26. celldetective/gui/tableUI.py +337 -48
  27. celldetective/gui/thresholds_gui.py +75 -71
  28. celldetective/gui/viewers.py +743 -0
  29. celldetective/io.py +247 -27
  30. celldetective/measure.py +43 -263
  31. celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
  32. celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
  33. celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
  34. celldetective/neighborhood.py +498 -27
  35. celldetective/preprocessing.py +1023 -0
  36. celldetective/scripts/analyze_signals.py +7 -0
  37. celldetective/scripts/measure_cells.py +12 -0
  38. celldetective/scripts/segment_cells.py +20 -4
  39. celldetective/scripts/track_cells.py +11 -0
  40. celldetective/scripts/train_segmentation_model.py +35 -34
  41. celldetective/segmentation.py +14 -9
  42. celldetective/signals.py +234 -329
  43. celldetective/tracking.py +2 -2
  44. celldetective/utils.py +602 -49
  45. celldetective-1.1.1.dist-info/METADATA +305 -0
  46. celldetective-1.1.1.dist-info/RECORD +84 -0
  47. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/top_level.txt +1 -0
  48. tests/__init__.py +0 -0
  49. tests/test_events.py +28 -0
  50. tests/test_filters.py +24 -0
  51. tests/test_io.py +70 -0
  52. tests/test_measure.py +141 -0
  53. tests/test_neighborhood.py +70 -0
  54. tests/test_preprocessing.py +37 -0
  55. tests/test_segmentation.py +93 -0
  56. tests/test_signals.py +135 -0
  57. tests/test_tracking.py +164 -0
  58. tests/test_utils.py +118 -0
  59. celldetective-1.0.2.post1.dist-info/METADATA +0 -221
  60. celldetective-1.0.2.post1.dist-info/RECORD +0 -66
  61. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/LICENSE +0 -0
  62. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/WHEEL +0 -0
  63. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.1.dist-info}/entry_points.txt +0 -0
@@ -21,8 +21,7 @@ from fonticon_mdi6 import MDI6
21
21
  from celldetective.gui.thresholds_gui import ThresholdNormalisation, ThresholdSpot
22
22
  from celldetective.utils import extract_experiment_channels, get_software_location
23
23
  from celldetective.io import interpret_tracking_configuration, load_frames, auto_load_number_of_frames
24
- from celldetective.measure import compute_haralick_features, contour_of_instance_segmentation, correct_image, \
25
- field_normalisation, normalise_by_cell
24
+ from celldetective.measure import compute_haralick_features, contour_of_instance_segmentation, normalise_by_cell
26
25
  import numpy as np
27
26
  from tifffile import imread
28
27
  import json
@@ -37,28 +36,33 @@ from pathlib import Path, PurePath
37
36
  import gc
38
37
  from stardist import fill_label_holes
39
38
 
39
+ from celldetective.gui.viewers import CellEdgeVisualizer
40
+ from celldetective.gui.layouts import ProtocolDesignerLayout, BackgroundFitCorrectionLayout, LocalCorrectionLayout, OperationLayout
41
+ from celldetective.gui.gui_utils import ThresholdLineEdit
42
+ from celldetective.gui import Styles
40
43
 
41
- class ConfigMeasurements(QMainWindow):
44
+ class ConfigMeasurements(QMainWindow, Styles):
42
45
  """
43
46
  UI to set measurement instructions.
44
47
 
45
48
  """
46
49
 
47
- def __init__(self, parent=None):
50
+ def __init__(self, parent_window=None):
48
51
 
49
52
  super().__init__()
50
- self.parent = parent
53
+
54
+ self.parent_window = parent_window
51
55
  self.setWindowTitle("Configure measurements")
52
56
  self.setWindowIcon(QIcon(os.sep.join(['celldetective', 'icons', 'mexican-hat.png'])))
53
- self.mode = self.parent.mode
54
- self.exp_dir = self.parent.exp_dir
57
+ self.mode = self.parent_window.mode
58
+ self.exp_dir = self.parent_window.exp_dir
55
59
  self.background_correction = []
56
60
  if self.mode == "targets":
57
61
  self.config_name = "btrack_config_targets.json"
58
- self.measure_instructions_path = self.parent.exp_dir + "configs/measurement_instructions_targets.json"
62
+ self.measure_instructions_path = self.parent_window.exp_dir + "configs/measurement_instructions_targets.json"
59
63
  elif self.mode == "effectors":
60
64
  self.config_name = "btrack_config_effectors.json"
61
- self.measure_instructions_path = self.parent.exp_dir + "configs/measurement_instructions_effectors.json"
65
+ self.measure_instructions_path = self.parent_window.exp_dir + "configs/measurement_instructions_effectors.json"
62
66
  self.soft_path = get_software_location()
63
67
  self.clear_previous = False
64
68
 
@@ -68,7 +72,7 @@ class ConfigMeasurements(QMainWindow):
68
72
  self.channel_names = np.array(self.channel_names)
69
73
  self.channels = np.array(self.channels)
70
74
 
71
- self.screen_height = self.parent.parent.parent.screen_height
75
+ self.screen_height = self.parent_window.parent_window.parent_window.screen_height
72
76
  center_window(self)
73
77
 
74
78
  self.onlyFloat = QDoubleValidator()
@@ -96,7 +100,20 @@ class ConfigMeasurements(QMainWindow):
96
100
 
97
101
  self.normalisation_frame = QFrame()
98
102
  self.normalisation_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
99
- self.populate_normalisation_tabs()
103
+
104
+ self.local_correction_layout = LocalCorrectionLayout(self)
105
+ self.fit_correction_layout = BackgroundFitCorrectionLayout(self)
106
+
107
+ self.protocol_layout = ProtocolDesignerLayout(parent_window=self,
108
+ tab_layouts=[ self.local_correction_layout, self.fit_correction_layout],
109
+ tab_names=['Local', 'Fit'],
110
+ title='BACKGROUND CORRECTION',
111
+ list_title='Corrections to apply:'
112
+ )
113
+
114
+ self.normalisation_frame.setLayout(self.protocol_layout)
115
+
116
+ #self.populate_normalisation_tabs()
100
117
  main_layout.addWidget(self.normalisation_frame)
101
118
 
102
119
  # first frame for FEATURES
@@ -120,7 +137,7 @@ class ConfigMeasurements(QMainWindow):
120
137
  main_layout.addWidget(self.clear_previous_btn)
121
138
 
122
139
  self.submit_btn = QPushButton('Save')
123
- self.submit_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
140
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
124
141
  self.submit_btn.clicked.connect(self.write_instructions)
125
142
  main_layout.addWidget(self.submit_btn)
126
143
 
@@ -186,14 +203,14 @@ class ConfigMeasurements(QMainWindow):
186
203
  radii_layout.addWidget(self.radii_lbl, 90)
187
204
 
188
205
  self.del_radius_btn = QPushButton("")
189
- self.del_radius_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
206
+ self.del_radius_btn.setStyleSheet(self.button_select_all)
190
207
  self.del_radius_btn.setIcon(icon(MDI6.trash_can, color="black"))
191
208
  self.del_radius_btn.setToolTip("Remove radius")
192
209
  self.del_radius_btn.setIconSize(QSize(20, 20))
193
210
  radii_layout.addWidget(self.del_radius_btn, 5)
194
211
 
195
212
  self.add_radius_btn = QPushButton("")
196
- self.add_radius_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
213
+ self.add_radius_btn.setStyleSheet(self.button_select_all)
197
214
  self.add_radius_btn.setIcon(icon(MDI6.plus, color="black"))
198
215
  self.add_radius_btn.setToolTip("Add radius")
199
216
  self.add_radius_btn.setIconSize(QSize(20, 20))
@@ -213,14 +230,14 @@ class ConfigMeasurements(QMainWindow):
213
230
  operation_layout.addWidget(self.op_lbl, 90)
214
231
 
215
232
  self.del_op_btn = QPushButton("")
216
- self.del_op_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
233
+ self.del_op_btn.setStyleSheet(self.button_select_all)
217
234
  self.del_op_btn.setIcon(icon(MDI6.trash_can, color="black"))
218
235
  self.del_op_btn.setToolTip("Remove operation")
219
236
  self.del_op_btn.setIconSize(QSize(20, 20))
220
237
  operation_layout.addWidget(self.del_op_btn, 5)
221
238
 
222
239
  self.add_op_btn = QPushButton("")
223
- self.add_op_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
240
+ self.add_op_btn.setStyleSheet(self.button_select_all)
224
241
  self.add_op_btn.setIcon(icon(MDI6.plus, color="black"))
225
242
  self.add_op_btn.setToolTip("Add operation")
226
243
  self.add_op_btn.setIconSize(QSize(20, 20))
@@ -244,13 +261,13 @@ class ConfigMeasurements(QMainWindow):
244
261
 
245
262
  self.feature_lbl = QLabel("Add features:")
246
263
  self.del_feature_btn = QPushButton("")
247
- self.del_feature_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
264
+ self.del_feature_btn.setStyleSheet(self.button_select_all)
248
265
  self.del_feature_btn.setIcon(icon(MDI6.trash_can, color="black"))
249
266
  self.del_feature_btn.setToolTip("Remove feature")
250
267
  self.del_feature_btn.setIconSize(QSize(20, 20))
251
268
 
252
269
  self.add_feature_btn = QPushButton("")
253
- self.add_feature_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
270
+ self.add_feature_btn.setStyleSheet(self.button_select_all)
254
271
  self.add_feature_btn.setIcon(icon(MDI6.filter_plus, color="black"))
255
272
  self.add_feature_btn.setToolTip("Add feature")
256
273
  self.add_feature_btn.setIconSize(QSize(20, 20))
@@ -276,22 +293,22 @@ class ConfigMeasurements(QMainWindow):
276
293
  contour_layout.addWidget(self.border_dist_lbl, 90)
277
294
 
278
295
  self.del_contour_btn = QPushButton("")
279
- self.del_contour_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
296
+ self.del_contour_btn.setStyleSheet(self.button_select_all)
280
297
  self.del_contour_btn.setIcon(icon(MDI6.trash_can, color="black"))
281
298
  self.del_contour_btn.setToolTip("Remove distance")
282
299
  self.del_contour_btn.setIconSize(QSize(20, 20))
283
300
  contour_layout.addWidget(self.del_contour_btn, 5)
284
301
 
285
302
  self.add_contour_btn = QPushButton("")
286
- self.add_contour_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
303
+ self.add_contour_btn.setStyleSheet(self.button_select_all)
287
304
  self.add_contour_btn.setIcon(icon(MDI6.plus, color="black"))
288
305
  self.add_contour_btn.setToolTip("Add distance")
289
306
  self.add_contour_btn.setIconSize(QSize(20, 20))
290
307
  contour_layout.addWidget(self.add_contour_btn, 5)
291
308
 
292
309
  self.view_contour_btn = QPushButton("")
293
- self.view_contour_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
294
- self.view_contour_btn.setIcon(icon(MDI6.eye_outline, color="black"))
310
+ self.view_contour_btn.setStyleSheet(self.button_select_all)
311
+ self.view_contour_btn.setIcon(icon(MDI6.eye_plus_outline, color="black"))
295
312
  self.view_contour_btn.setToolTip("View contour")
296
313
  self.view_contour_btn.setIconSize(QSize(20, 20))
297
314
  contour_layout.addWidget(self.view_contour_btn, 5)
@@ -354,7 +371,7 @@ class ConfigMeasurements(QMainWindow):
354
371
  self.haralick_percentile_min_le = QLineEdit('0.01')
355
372
  self.haralick_percentile_max_le = QLineEdit('99.9')
356
373
  self.haralick_normalization_mode_btn = QPushButton()
357
- self.haralick_normalization_mode_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
374
+ self.haralick_normalization_mode_btn.setStyleSheet(self.button_select_all)
358
375
  self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
359
376
  self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
360
377
  self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
@@ -371,12 +388,12 @@ class ConfigMeasurements(QMainWindow):
371
388
  self.haralick_hist_btn = QPushButton()
372
389
  self.haralick_hist_btn.clicked.connect(self.control_haralick_intensity_histogram)
373
390
  self.haralick_hist_btn.setIcon(icon(MDI6.poll, color="k"))
374
- self.haralick_hist_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
391
+ self.haralick_hist_btn.setStyleSheet(self.button_select_all)
375
392
 
376
393
  self.haralick_digit_btn = QPushButton()
377
394
  self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
378
395
  self.haralick_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
379
- self.haralick_digit_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
396
+ self.haralick_digit_btn.setStyleSheet(self.button_select_all)
380
397
 
381
398
  self.haralick_layout = QVBoxLayout()
382
399
  self.haralick_layout.setContentsMargins(20, 20, 20, 20)
@@ -492,7 +509,7 @@ class ConfigMeasurements(QMainWindow):
492
509
 
493
510
  print('Writing instructions...')
494
511
  measurement_options = {}
495
- background_correction = self.background_correction
512
+ background_correction = self.protocol_layout.protocols
496
513
  if not background_correction:
497
514
  background_correction = None
498
515
  measurement_options.update({'background_correction': background_correction})
@@ -581,18 +598,20 @@ class ConfigMeasurements(QMainWindow):
581
598
  measurement_instructions = json.load(f)
582
599
  print(measurement_instructions)
583
600
  if 'background_correction' in measurement_instructions:
584
- self.background_correction = measurement_instructions['background_correction']
585
- if (self.background_correction is not None) and len(self.background_correction) > 0:
586
- self.normalisation_list.clear()
587
- for norm_params in self.background_correction:
601
+ self.protocol_layout.protocols = measurement_instructions['background_correction']
602
+ if self.protocol_layout.protocols is None:
603
+ self.protocol_layout.protocols = []
604
+ if (self.protocol_layout.protocols is not None) and len(self.protocol_layout.protocols) > 0:
605
+ self.protocol_layout.protocol_list.clear()
606
+ for norm_params in self.protocol_layout.protocols:
588
607
  normalisation_description = ""
589
608
  for index, (key, value) in enumerate(norm_params.items()):
590
609
  if index > 0:
591
610
  normalisation_description += ", "
592
611
  normalisation_description += str(key) + " : " + str(value)
593
- self.normalisation_list.addItem(normalisation_description)
612
+ self.protocol_layout.protocol_list.addItem(normalisation_description)
594
613
  else:
595
- self.normalisation_list.clear()
614
+ self.protocol_layout.protocol_list.clear()
596
615
  if 'features' in measurement_instructions:
597
616
  features = measurement_instructions['features']
598
617
  if (features is not None) and len(features) > 0:
@@ -702,30 +721,32 @@ class ConfigMeasurements(QMainWindow):
702
721
  Load the first frame of the first movie found in the experiment folder as a sample.
703
722
  """
704
723
 
705
- movies = glob(self.parent.parent.pos + f"movie/{self.parent.parent.movie_prefix}*.tif")
706
- print(movies)
724
+ movies = glob(self.parent_window.parent_window.pos + os.sep.join(['movie', f"{self.parent_window.movie_prefix}*.tif"]))
725
+
707
726
  if len(movies) == 0:
708
727
  msgBox = QMessageBox()
709
728
  msgBox.setIcon(QMessageBox.Warning)
710
- msgBox.setText("No movies are detected in the experiment folder. Cannot load an image...")
729
+ msgBox.setText("Please select a position containing a movie...")
711
730
  msgBox.setWindowTitle("Warning")
712
731
  msgBox.setStandardButtons(QMessageBox.Ok)
713
732
  returnValue = msgBox.exec()
714
733
  if returnValue == QMessageBox.Ok:
715
- self.test_frame = None
734
+ self.current_stack = None
716
735
  return None
717
736
  else:
718
- self.stack0 = movies[0]
719
- n_channels = len(self.channels)
720
- len_movie_auto = auto_load_number_of_frames(self.stack0)
721
- if len_movie_auto is None:
722
- stack = imread(self.stack0)
723
- len_movie_auto = len(stack)
737
+ self.current_stack = movies[0]
738
+ self.stack_length = auto_load_number_of_frames(self.current_stack)
739
+
740
+ if self.stack_length is None:
741
+ stack = imread(self.current_stack)
742
+ self.stack_length = len(stack)
724
743
  del stack
725
744
  gc.collect()
726
- self.mid_time = len_movie_auto // 2
727
- self.test_frame = load_frames(n_channels * self.mid_time + np.arange(n_channels), self.stack0, scale=None,
728
- normalize_input=False)
745
+
746
+ self.mid_time = self.stack_length // 2
747
+ indices = self.mid_time + np.arange(len(self.channel_names))
748
+ self.test_frame = load_frames(list(indices.astype(int)),self.current_stack, normalize_input=False)
749
+
729
750
 
730
751
  def control_haralick_digitalization(self):
731
752
 
@@ -791,119 +812,21 @@ class ConfigMeasurements(QMainWindow):
791
812
  Show the ROI for the selected contour measurement on experimental data.
792
813
 
793
814
  """
794
-
795
- if self.parent.parent.position_list.currentText() == '*':
796
- msgBox = QMessageBox()
797
- msgBox.setIcon(QMessageBox.Warning)
798
- msgBox.setText("Please select a single position to visualize the border selection.")
799
- msgBox.setWindowTitle("Warning")
800
- msgBox.setStandardButtons(QMessageBox.Ok)
801
- returnValue = msgBox.exec()
802
- if returnValue == QMessageBox.Ok:
803
- return None
804
- else:
805
- return None
806
-
807
815
  self.locate_image()
808
816
 
809
- self.locate_mask()
810
- if self.test_mask is None:
811
- msgBox = QMessageBox()
812
- msgBox.setIcon(QMessageBox.Warning)
813
- msgBox.setText("The segmentation results could not be found for this position.")
814
- msgBox.setWindowTitle("Warning")
815
- msgBox.setStandardButtons(QMessageBox.Ok)
816
- returnValue = msgBox.exec()
817
- if returnValue == QMessageBox.Yes:
818
- return None
819
- else:
820
- return None
821
- # plt.imshow(self.test_frame[:,:,0])
822
- # plt.pause(2)
823
- # plt.close()
824
-
825
- # plt.imshow(self.test_mask)
826
- # plt.pause(2)
827
- # plt.close()
828
-
829
- if (self.test_frame is not None) and (self.test_mask is not None):
830
-
831
- values = self.contours_list.list_widget.selectedItems()
832
- if len(values) > 0:
833
- distance = values[0].text()
834
- if '-' in distance:
835
- if distance[0] != '-':
836
- border_dist = distance.split('-')
837
- border_dist = [float(d) for d in border_dist]
838
- else:
839
- border_dist = float(distance)
840
- elif distance.isnumeric():
841
- border_dist = float(distance)
842
-
843
- print(border_dist)
844
- border_label = contour_of_instance_segmentation(self.test_mask, border_dist)
845
-
846
- self.fig_contour, self.ax_contour = plt.subplots(figsize=(5, 5))
847
- self.imshow_contour = FigureCanvas(self.fig_contour, title="Contour measurement", interactive=True)
848
- self.ax_contour.clear()
849
- self.im_contour = self.ax_contour.imshow(self.test_frame[:, :, 0], cmap='gray')
850
- self.im_mask = self.ax_contour.imshow(np.ma.masked_where(border_label == 0, border_label),
851
- cmap='viridis', interpolation='none')
852
- self.ax_contour.set_xticks([])
853
- self.ax_contour.set_yticks([])
854
- self.ax_contour.set_title(border_dist)
855
- self.fig_contour.set_facecolor('none') # or 'None'
856
- self.fig_contour.canvas.setStyleSheet("background-color: transparent;")
857
- self.imshow_contour.canvas.draw()
858
-
859
- self.imshow_contour.layout.setContentsMargins(30, 30, 30, 30)
860
- self.channel_hbox_contour = QHBoxLayout()
861
- self.channel_hbox_contour.addWidget(QLabel('channel: '), 10)
862
- self.channel_cb_contour = QComboBox()
863
- self.channel_cb_contour.addItems(self.channel_names)
864
- self.channel_cb_contour.currentIndexChanged.connect(self.switch_channel_contour)
865
- self.channel_hbox_contour.addWidget(self.channel_cb_contour, 90)
866
- self.imshow_contour.layout.addLayout(self.channel_hbox_contour)
867
-
868
- self.contrast_hbox_contour = QHBoxLayout()
869
- self.contrast_hbox_contour.addWidget(QLabel('contrast: '), 10)
870
- self.contrast_slider_contour = QLabeledDoubleRangeSlider()
871
- self.contrast_slider_contour.setSingleStep(0.00001)
872
- self.contrast_slider_contour.setTickInterval(0.00001)
873
- self.contrast_slider_contour.setOrientation(1)
874
- self.contrast_slider_contour.setRange(np.amin(self.test_frame[:, :, 0]),
875
- np.amax(self.test_frame[:, :, 0]))
876
- self.contrast_slider_contour.setValue([np.percentile(self.test_frame[:, :, 0].flatten(), 1),
877
- np.percentile(self.test_frame[:, :, 0].flatten(), 99.99)])
878
- self.im_contour.set_clim(vmin=np.percentile(self.test_frame[:, :, 0].flatten(), 1),
879
- vmax=np.percentile(self.test_frame[:, :, 0].flatten(), 99.99))
880
- self.contrast_slider_contour.valueChanged.connect(self.contrast_im_contour)
881
- self.contrast_hbox_contour.addWidget(self.contrast_slider_contour, 90)
882
- self.imshow_contour.layout.addLayout(self.contrast_hbox_contour)
883
-
884
- self.alpha_mask_hbox_contour = QHBoxLayout()
885
- self.alpha_mask_hbox_contour.addWidget(QLabel('mask transparency: '), 10)
886
- self.transparency_slider = QLabeledDoubleSlider()
887
- self.transparency_slider.setSingleStep(0.001)
888
- self.transparency_slider.setTickInterval(0.001)
889
- self.transparency_slider.setOrientation(1)
890
- self.transparency_slider.setRange(0, 1)
891
- self.transparency_slider.setValue(0.5)
892
- self.transparency_slider.valueChanged.connect(self.make_contour_transparent)
893
- self.alpha_mask_hbox_contour.addWidget(self.transparency_slider, 90)
894
- self.imshow_contour.layout.addLayout(self.alpha_mask_hbox_contour)
895
-
896
- self.imshow_contour.show()
817
+ if self.current_stack is not None:
897
818
 
898
- else:
899
- msgBox = QMessageBox()
900
- msgBox.setIcon(QMessageBox.Warning)
901
- msgBox.setText("No contour was selected. Please first add a contour to the list.")
902
- msgBox.setWindowTitle("Warning")
903
- msgBox.setStandardButtons(QMessageBox.Ok)
904
- returnValue = msgBox.exec()
905
- if returnValue == QMessageBox.Yes:
906
- return None
819
+ self.viewer = CellEdgeVisualizer(cell_type=self.mode,
820
+ stack_path=self.current_stack,
821
+ parent_list_widget=self.contours_list.list_widget,
822
+ n_channels=len(self.channel_names),
823
+ target_channel=0,
824
+ window_title='Set an edge measurement',
825
+ channel_cb=True,
826
+ channel_names = self.channel_names,
827
+ PxToUm = 1,
828
+ )
829
+ self.viewer.show()
907
830
 
908
831
  def locate_mask(self):
909
832
 
@@ -911,7 +834,7 @@ class ConfigMeasurements(QMainWindow):
911
834
  Load the first mask of the detected movie.
912
835
  """
913
836
 
914
- labels_path = str(Path(self.stack0).parent.parent) + f'/labels_{self.mode}/'
837
+ labels_path = str(Path(self.current_stack).parent.parent) + os.sep+f'labels_{self.mode}'+os.sep
915
838
  masks = natsorted(glob(labels_path + '*.tif'))
916
839
  if len(masks) == 0:
917
840
  print('no mask found')
@@ -946,208 +869,6 @@ class ConfigMeasurements(QMainWindow):
946
869
  self.im_mask.set_alpha(value)
947
870
  self.fig_contour.canvas.draw_idle()
948
871
 
949
- def populate_normalisation_tabs(self):
950
- layout = QVBoxLayout(self.normalisation_frame)
951
- self.normalisation_lbl = QLabel("BACKGROUND CORRECTION")
952
- self.normalisation_lbl.setStyleSheet("""
953
- font-weight: bold;
954
- padding: 0px;
955
- """)
956
- layout.addWidget(self.normalisation_lbl, alignment=Qt.AlignCenter)
957
- self.tabs = QTabWidget()
958
- self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
959
- self.tab1 = QWidget()
960
- self.tab2 = QWidget()
961
- self.normalisation_list = QListWidget()
962
- self.tabs.addTab(self.tab1, 'Local')
963
- self.tabs.addTab(self.tab2, 'Field')
964
- self.tab1.setLayout(self.populate_local_norm_tab())
965
- self.tab2.setLayout(self.populate_field_norm_tab())
966
- layout.addWidget(self.tabs)
967
- self.norm_list_lbl = QLabel('Background correction to perform:')
968
- hbox = QHBoxLayout()
969
- hbox.addWidget(self.norm_list_lbl)
970
- self.del_norm_btn = QPushButton("")
971
- self.del_norm_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
972
- self.del_norm_btn.setIcon(icon(MDI6.trash_can, color="black"))
973
- self.del_norm_btn.setToolTip("Remove background correction")
974
- self.del_norm_btn.setIconSize(QSize(20, 20))
975
- hbox.addWidget(self.del_norm_btn, alignment=Qt.AlignRight)
976
- layout.addLayout(hbox)
977
- self.del_norm_btn.clicked.connect(self.remove_item_from_list)
978
- layout.addWidget(self.normalisation_list)
979
-
980
- def populate_local_norm_tab(self):
981
- tab1_layout = QGridLayout(self.tab1)
982
-
983
- channel_lbl = QLabel()
984
- channel_lbl.setText("Channel: ")
985
- tab1_layout.addWidget(channel_lbl, 0, 0)
986
-
987
- self.tab1_channel_dropdown = QComboBox()
988
- self.tab1_channel_dropdown.addItems(self.channel_names)
989
- tab1_layout.addWidget(self.tab1_channel_dropdown, 0, 1)
990
-
991
- tab1_lbl = QLabel()
992
- tab1_lbl.setText("Outer distance (in px): ")
993
- tab1_layout.addWidget(tab1_lbl, 1, 0)
994
-
995
- self.tab1_txt_distance = QLineEdit()
996
- tab1_layout.addWidget(self.tab1_txt_distance, 1, 1)
997
-
998
- self.tab1_vis_brdr = QPushButton()
999
- self.tab1_vis_brdr.setStyleSheet(self.parent.parent.parent.button_select_all)
1000
- self.tab1_vis_brdr.setIcon(icon(MDI6.eye_outline, color="black"))
1001
- self.tab1_vis_brdr.setToolTip("View contour")
1002
- self.tab1_vis_brdr.setIconSize(QSize(20, 20))
1003
- self.tab1_vis_brdr.clicked.connect(self.view_normalisation_contour)
1004
- tab1_layout.addWidget(self.tab1_vis_brdr, 1, 2)
1005
-
1006
- tab1_lbl_type = QLabel()
1007
- tab1_lbl_type.setText("Type: ")
1008
- tab1_layout.addWidget(tab1_lbl_type, 2, 0)
1009
-
1010
- self.tab1_dropdown = QComboBox()
1011
- self.tab1_dropdown.addItem("Mean")
1012
- self.tab1_dropdown.addItem("Median")
1013
- tab1_layout.addWidget(self.tab1_dropdown, 2, 1)
1014
-
1015
- self.tab1_subtract = QRadioButton('Subtract')
1016
- self.tab1_divide = QRadioButton('Divide')
1017
- tab1_layout.addWidget(self.tab1_subtract, 3, 0, alignment=Qt.AlignRight)
1018
- tab1_layout.addWidget(self.tab1_divide, 3, 1, alignment=Qt.AlignRight)
1019
-
1020
- tab1_submit = QPushButton()
1021
- tab1_submit.setText('Add channel')
1022
- tab1_submit.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
1023
- tab1_layout.addWidget(tab1_submit, 4, 0, 1, 3)
1024
- tab1_submit.clicked.connect(self.add_item_to_list)
1025
-
1026
- return tab1_layout
1027
-
1028
- def populate_field_norm_tab(self):
1029
-
1030
- tab2_layout = QGridLayout(self.tab2)
1031
-
1032
- channel_lbl = QLabel()
1033
- channel_lbl.setText("Channel: ")
1034
- tab2_layout.addWidget(channel_lbl, 0, 0)
1035
-
1036
- self.tab2_channel_dropdown = QComboBox()
1037
- self.tab2_channel_dropdown.addItems(self.channel_names)
1038
- tab2_layout.addWidget(self.tab2_channel_dropdown, 0, 1)
1039
-
1040
- tab2_lbl = QLabel()
1041
- tab2_lbl.setText("std threshold: ")
1042
- tab2_layout.addWidget(tab2_lbl, 1, 0)
1043
-
1044
- self.tab2_txt_threshold = QLineEdit()
1045
- tab2_layout.addWidget(self.tab2_txt_threshold, 1, 1)
1046
-
1047
- self.norm_digit_btn = QPushButton()
1048
- self.norm_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
1049
- self.norm_digit_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1050
-
1051
- self.norm_digit_btn.clicked.connect(self.show_threshold_visual)
1052
- tab2_layout.addWidget(self.norm_digit_btn, 1, 2)
1053
-
1054
- tab2_lbl_type = QLabel()
1055
- tab2_lbl_type.setText("Type: ")
1056
- tab2_layout.addWidget(tab2_lbl_type, 2, 0)
1057
-
1058
- self.tab2_dropdown = QComboBox()
1059
- self.tab2_dropdown.addItems(["Paraboloid", "Plane"])
1060
- tab2_layout.addWidget(self.tab2_dropdown, 2, 1)
1061
-
1062
- self.tab2_subtract = QRadioButton('Subtract')
1063
- self.tab2_divide = QRadioButton('Divide')
1064
- self.tab2_sd_btn_group = QButtonGroup(self)
1065
- self.tab2_sd_btn_group.addButton(self.tab2_subtract)
1066
- self.tab2_sd_btn_group.addButton(self.tab2_divide)
1067
- tab2_layout.addWidget(self.tab2_subtract, 3, 0, alignment=Qt.AlignRight)
1068
- tab2_layout.addWidget(self.tab2_divide, 3, 1, alignment=Qt.AlignRight)
1069
-
1070
- self.tab2_clip = QRadioButton('Clip')
1071
- self.tab2_no_clip = QRadioButton("Don't clip")
1072
- self.tab2_clip_group = QButtonGroup(self)
1073
- self.tab2_clip_group.addButton(self.tab2_clip)
1074
- self.tab2_clip_group.addButton(self.tab2_no_clip)
1075
- self.tab2_clip.setEnabled(False)
1076
- self.tab2_no_clip.setEnabled(False)
1077
- tab2_layout.addWidget(self.tab2_clip, 4, 0, alignment=Qt.AlignLeft)
1078
- tab2_layout.addWidget(self.tab2_no_clip, 4, 1, alignment=Qt.AlignLeft)
1079
- self.tab2_subtract.toggled.connect(self.show_clipping_options)
1080
- self.tab2_divide.toggled.connect(self.show_clipping_options)
1081
-
1082
- self.view_norm_btn = QPushButton("")
1083
- self.view_norm_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1084
- self.view_norm_btn.setIcon(icon(MDI6.eye_outline, color="black"))
1085
- self.view_norm_btn.setToolTip("View corrected image")
1086
- self.view_norm_btn.setIconSize(QSize(20, 20))
1087
- self.view_norm_btn.clicked.connect(self.preview_normalisation)
1088
- tab2_layout.addWidget(self.view_norm_btn, 4, 2)
1089
-
1090
- tab2_submit = QPushButton()
1091
- tab2_submit.setText('Add channel')
1092
- tab2_submit.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
1093
- tab2_layout.addWidget(tab2_submit, 5, 0, 1, 3)
1094
- tab2_submit.clicked.connect(self.add_item_to_list)
1095
-
1096
- return tab2_layout
1097
-
1098
- def show_threshold_visual(self):
1099
- min_threshold = self.tab2_txt_threshold.text()
1100
- if min_threshold == "":
1101
- min_threshold = 0
1102
- current_channel = self.tab2_channel_dropdown.currentIndex()
1103
- self.threshold_visual = ThresholdNormalisation(min_threshold=float(min_threshold),
1104
- current_channel=current_channel, parent=self)
1105
-
1106
- def show_clipping_options(self):
1107
- if self.tab2_subtract.isChecked():
1108
- for button in self.tab2_clip_group.buttons():
1109
- button.setEnabled(True)
1110
- if self.tab2_divide.isChecked():
1111
- self.tab2_clip_group.setExclusive(False)
1112
- for button in self.tab2_clip_group.buttons():
1113
- button.setChecked(False)
1114
- button.setEnabled(False)
1115
- self.tab2_clip_group.setExclusive(True)
1116
-
1117
- def add_item_to_list(self):
1118
- check = self.check_the_information()
1119
- if check is True:
1120
-
1121
- if self.tabs.currentIndex() == 0:
1122
-
1123
- norm_params = {"target channel": self.tab1_channel_dropdown.currentText(), "mode": "local",
1124
- "distance": self.tab1_txt_distance.text(),
1125
- "type": self.tab1_dropdown.currentText()}
1126
- if self.tab1_subtract.isChecked():
1127
- norm_params["operation"] = "Subtract"
1128
- else:
1129
- norm_params["operation"] = "Divide"
1130
- elif self.tabs.currentIndex() == 1:
1131
- norm_params = {"target channel": self.tab2_channel_dropdown.currentText(), "mode": "field",
1132
- "threshold": self.tab2_txt_threshold.text(),
1133
- "type": self.tab2_dropdown.currentText()}
1134
- if self.tab2_subtract.isChecked():
1135
- norm_params["operation"] = "Subtract"
1136
- if self.tab2_clip.isChecked():
1137
- norm_params["clip"] = True
1138
- else:
1139
- norm_params["clip"] = False
1140
- elif self.tab2_divide.isChecked():
1141
- norm_params["operation"] = "Divide"
1142
-
1143
- self.background_correction.append(norm_params)
1144
- normalisation_description = ""
1145
- for index, (key, value) in enumerate(norm_params.items()):
1146
- if index > 0:
1147
- normalisation_description += ", "
1148
- normalisation_description += str(key) + " : " + str(value)
1149
- self.normalisation_list.addItem(normalisation_description)
1150
-
1151
872
  def remove_item_from_list(self):
1152
873
  current_item = self.normalisation_list.currentRow()
1153
874
  if current_item > -1:
@@ -1155,6 +876,7 @@ class ConfigMeasurements(QMainWindow):
1155
876
  self.normalisation_list.takeItem(current_item)
1156
877
 
1157
878
  def check_the_information(self):
879
+
1158
880
  if self.tabs.currentIndex() == 0:
1159
881
  if self.background_correction is None:
1160
882
  self.background_correction = []
@@ -1167,38 +889,6 @@ class ConfigMeasurements(QMainWindow):
1167
889
  self.background_correction .remove(normalisation_opt)
1168
890
  self.normalisation_list.takeItem(index)
1169
891
  return True
1170
- if self.tab1_txt_distance.text() == "":
1171
- self.display_message_box('provide the outer distance')
1172
- return False
1173
- if not self.tab1_subtract.isChecked() and not self.tab1_divide.isChecked():
1174
- self.display_message_box('choose the operation')
1175
- return False
1176
- else:
1177
- return True
1178
-
1179
- elif self.tabs.currentIndex() == 1:
1180
- if self.background_correction is None:
1181
- self.background_correction = []
1182
- for index, normalisation_opt in enumerate(self.background_correction ):
1183
- if self.tab2_channel_dropdown.currentText() in normalisation_opt['target channel']:
1184
- result = self.channel_already_in_list()
1185
- if result != QMessageBox.Yes:
1186
- return False
1187
- else:
1188
- self.background_correction .remove(normalisation_opt)
1189
- self.normalisation_list.takeItem(index)
1190
- return True
1191
- if self.tab2_txt_threshold.text() == "":
1192
- self.display_message_box('provide the threshold')
1193
- return False
1194
- elif not self.tab2_divide.isChecked() and not self.tab2_subtract.isChecked():
1195
- self.display_message_box('choose subtraction or division')
1196
- return False
1197
- elif self.tab2_subtract.isChecked():
1198
- if not self.tab2_clip.isChecked() and not self.tab2_no_clip.isChecked():
1199
- self.display_message_box('provide the clipping mode')
1200
- return False
1201
- return True
1202
892
 
1203
893
  def display_message_box(self, missing_info):
1204
894
  QMessageBox.about(self, "Message Box Title", "Please " + missing_info + " for background correction")
@@ -1214,47 +904,6 @@ class ConfigMeasurements(QMainWindow):
1214
904
  def fun(self, x, y):
1215
905
  return x ** 2 + y
1216
906
 
1217
- def preview_normalisation(self):
1218
- plt.close('all')
1219
- plt.figure("Intensity Profiles",figsize=(10, 5))
1220
- self.locate_image()
1221
- diagonal_length = min(self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()].shape[0], self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()].shape[1])
1222
- if self.tab2_subtract.isChecked():
1223
- norm_operation='Subtract'
1224
- else:
1225
- norm_operation='Divide'
1226
- normalised, bg_fit = field_normalisation(self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()],
1227
- threshold=self.tab2_txt_threshold.text(),
1228
- normalisation_operation=norm_operation,
1229
- clip=self.tab2_clip.isChecked(),
1230
- mode=self.tab2_dropdown.currentText())
1231
- diagonal_original = [self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()][i, i] for i in
1232
- range(diagonal_length)]
1233
- diagonal_corrected = [normalised[i, i] for i in range(diagonal_length)]
1234
- diagonal_indices = np.arange(diagonal_length)
1235
-
1236
- plt.subplot(1, 2, 1)
1237
- plt.plot(diagonal_indices, diagonal_original, color='black', linewidth=0.2) # Adjust linewidth here
1238
- plt.title('Original Image')
1239
- plt.xlabel('Pixel Index along Diagonal')
1240
- plt.ylabel('Intensity')
1241
-
1242
- plt.subplot(1, 2, 2)
1243
- plt.plot(diagonal_indices, diagonal_corrected, color='black', linewidth=0.2) # Adjust linewidth here
1244
- plt.title('Corrected Image')
1245
- plt.xlabel('Pixel Index along Diagonal')
1246
- plt.ylabel('Intensity')
1247
-
1248
- plt.tight_layout()
1249
- plt.show()
1250
-
1251
- self.fig, self.ax = plt.subplots()
1252
- self.normalised_img = FigureCanvas(self.fig, "Corrected background image preview")
1253
- self.ax.clear()
1254
- self.ax.imshow(normalised, cmap='gray')
1255
- self.normalised_img.canvas.draw()
1256
- self.normalised_img.show()
1257
-
1258
907
  def view_normalisation_contour(self):
1259
908
 
1260
909
  """
@@ -1262,67 +911,27 @@ class ConfigMeasurements(QMainWindow):
1262
911
 
1263
912
  """
1264
913
 
1265
- if self.parent.parent.position_list.currentText() == '*':
1266
- msgBox = QMessageBox()
1267
- msgBox.setIcon(QMessageBox.Warning)
1268
- msgBox.setText("Please select a single position to visualize the border selection.")
1269
- msgBox.setWindowTitle("Warning")
1270
- msgBox.setStandardButtons(QMessageBox.Ok)
1271
- returnValue = msgBox.exec()
1272
- if returnValue == QMessageBox.Ok:
1273
- return None
1274
- else:
1275
- return None
1276
-
1277
914
  self.locate_image()
1278
915
 
1279
- self.locate_mask()
1280
- if self.test_mask is None:
1281
- msgBox = QMessageBox()
1282
- msgBox.setIcon(QMessageBox.Warning)
1283
- msgBox.setText("The segmentation results could not be found for this position.")
1284
- msgBox.setWindowTitle("Warning")
1285
- msgBox.setStandardButtons(QMessageBox.Ok)
1286
- returnValue = msgBox.exec()
1287
- if returnValue == QMessageBox.Yes:
1288
- return None
1289
- else:
1290
- return None
916
+ if self.current_stack is not None:
917
+
918
+ self.viewer = CellEdgeVisualizer(cell_type=self.mode,
919
+ stack_path=self.current_stack,
920
+ parent_le = self.tab1_txt_distance,
921
+ n_channels=len(self.channel_names),
922
+ target_channel=self.tab1_channel_dropdown.currentIndex(),
923
+ edge_range = (0,30),
924
+ initial_edge= self.tab1_txt_distance.get_threshold(),
925
+ invert=True,
926
+ window_title='Set an edge distance to estimate local intensity',
927
+ channel_cb=False,
928
+ PxToUm = 1,
929
+ )
930
+ self.viewer.show()
1291
931
 
1292
- if (self.test_frame is not None) and (self.test_mask is not None):
1293
- contour = float(self.tab1_txt_distance.text())
1294
- contour = contour * (-1)
1295
- border_label = contour_of_instance_segmentation(self.test_mask, contour)
1296
-
1297
- self.fig_contour, self.ax_contour = plt.subplots(figsize=(5, 5))
1298
- self.imshow_contour = FigureCanvas(self.fig_contour, title="Contour measurement", interactive=True)
1299
- self.ax_contour.clear()
1300
- self.im_contour = self.ax_contour.imshow(self.test_frame[:, :, self.tab1_channel_dropdown.currentIndex()],
1301
- cmap='gray')
1302
- self.im_mask = self.ax_contour.imshow(np.ma.masked_where(border_label == 0, border_label),
1303
- cmap='viridis', interpolation='none')
1304
- self.ax_contour.set_xticks([])
1305
- self.ax_contour.set_yticks([])
1306
- self.ax_contour.set_title(contour * (-1))
1307
- self.fig_contour.set_facecolor('none') # or 'None'
1308
- self.fig_contour.canvas.setStyleSheet("background-color: transparent;")
1309
- self.imshow_contour.canvas.draw()
1310
-
1311
- self.imshow_contour.show()
1312
-
1313
- # def enable_step_size(self):
1314
- # if self.radial_intensity_btn.isChecked():
1315
- # self.step_lbl.setEnabled(True)
1316
- # self.step_size.setEnabled(True)
1317
- # for checkbox in self.channel_chechkboxes:
1318
- # checkbox.setEnabled(True)
1319
- # else:
1320
- # self.step_lbl.setEnabled(False)
1321
- # self.step_size.setEnabled(False)
1322
- # for checkbox in self.channel_chechkboxes:
1323
- # checkbox.setEnabled(False)
1324
932
 
1325
933
  def populate_spot_detection(self):
934
+
1326
935
  layout = QGridLayout(self.spot_detection_frame)
1327
936
  self.spot_detection_lbl = QLabel("SPOT DETECTION")
1328
937
  self.spot_detection_lbl.setStyleSheet("""font-weight: bold;padding: 0px;""")
@@ -1353,7 +962,7 @@ class ConfigMeasurements(QMainWindow):
1353
962
  layout.addWidget(self.threshold_value, 4, 1)
1354
963
  self.preview_spot = QPushButton('Preview')
1355
964
  self.preview_spot.clicked.connect(self.spot_preview)
1356
- self.preview_spot.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
965
+ self.preview_spot.setStyleSheet(self.button_style_sheet_2)
1357
966
  layout.addWidget(self.preview_spot, 5, 0, 1, 2)
1358
967
  self.spot_channel.setEnabled(False)
1359
968
  self.spot_channel_lbl.setEnabled(False)
@@ -1379,31 +988,7 @@ class ConfigMeasurements(QMainWindow):
1379
988
  self.locate_mask()
1380
989
  if self.test_mask is not None:
1381
990
  self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
1382
- mask=self.test_mask, parent=self)
1383
- # for dictionary in self.background_correction:
1384
- # if self.spot_channel.currentText() in dictionary['target channel']:
1385
- # if dictionary['mode'] == 'field':
1386
- # if dictionary['operation'] == 'Divide':
1387
- # normalised, bg_fit = field_normalisation(
1388
- # self.test_frame[:, :, self.spot_channel.currentIndex()],
1389
- # threshold=dictionary['threshold'],
1390
- # normalisation_operation=dictionary['operation'],
1391
- # clip=False,
1392
- # mode=dictionary['type'])
1393
- # else:
1394
- # normalised, bg_fit = field_normalisation(
1395
- # self.test_frame[:, :, self.spot_channel.currentIndex()],
1396
- # threshold=dictionary['threshold'],
1397
- # normalisation_operation=dictionary['operation'],
1398
- # clip=dictionary['clip'],
1399
- # mode=dictionary['type'])
1400
- # self.test_frame[:, :, self.spot_channel.currentIndex()] = normalised
1401
- # if dictionary['mode'] == 'local':
1402
- # normalised_image = normalise_by_cell(self.test_frame[:, :, self.spot_channel.currentIndex()].copy(), self.test_mask,
1403
- # distance=int(dictionary['distance']), mode=dictionary['type'],
1404
- # operation=dictionary['operation'])
1405
- # self.test_frame[:, :, self.spot_channel.currentIndex()] = normalised_image
1406
-
991
+ mask=self.test_mask, parent_window=self)
1407
992
 
1408
993
  def enable_spot_detection(self):
1409
994
  if self.spot_check.isChecked():