celldetective 1.4.2__py3-none-any.whl → 1.5.0b0__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 (151) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +304 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/measure_cells.py +565 -0
  81. celldetective/processes/segment_cells.py +760 -0
  82. celldetective/processes/track_cells.py +435 -0
  83. celldetective/processes/train_segmentation_model.py +694 -0
  84. celldetective/processes/train_signal_model.py +265 -0
  85. celldetective/processes/unified_process.py +292 -0
  86. celldetective/regionprops/_regionprops.py +358 -317
  87. celldetective/relative_measurements.py +987 -710
  88. celldetective/scripts/measure_cells.py +313 -212
  89. celldetective/scripts/measure_relative.py +90 -46
  90. celldetective/scripts/segment_cells.py +165 -104
  91. celldetective/scripts/segment_cells_thresholds.py +96 -68
  92. celldetective/scripts/track_cells.py +198 -149
  93. celldetective/scripts/train_segmentation_model.py +324 -201
  94. celldetective/scripts/train_signal_model.py +87 -45
  95. celldetective/segmentation.py +844 -749
  96. celldetective/signals.py +3514 -2861
  97. celldetective/tracking.py +30 -15
  98. celldetective/utils/__init__.py +0 -0
  99. celldetective/utils/cellpose_utils/__init__.py +133 -0
  100. celldetective/utils/color_mappings.py +42 -0
  101. celldetective/utils/data_cleaning.py +630 -0
  102. celldetective/utils/data_loaders.py +450 -0
  103. celldetective/utils/dataset_helpers.py +207 -0
  104. celldetective/utils/downloaders.py +197 -0
  105. celldetective/utils/event_detection/__init__.py +8 -0
  106. celldetective/utils/experiment.py +1782 -0
  107. celldetective/utils/image_augmenters.py +308 -0
  108. celldetective/utils/image_cleaning.py +74 -0
  109. celldetective/utils/image_loaders.py +926 -0
  110. celldetective/utils/image_transforms.py +335 -0
  111. celldetective/utils/io.py +62 -0
  112. celldetective/utils/mask_cleaning.py +348 -0
  113. celldetective/utils/mask_transforms.py +5 -0
  114. celldetective/utils/masks.py +184 -0
  115. celldetective/utils/maths.py +351 -0
  116. celldetective/utils/model_getters.py +325 -0
  117. celldetective/utils/model_loaders.py +296 -0
  118. celldetective/utils/normalization.py +380 -0
  119. celldetective/utils/parsing.py +465 -0
  120. celldetective/utils/plots/__init__.py +0 -0
  121. celldetective/utils/plots/regression.py +53 -0
  122. celldetective/utils/resources.py +34 -0
  123. celldetective/utils/stardist_utils/__init__.py +104 -0
  124. celldetective/utils/stats.py +90 -0
  125. celldetective/utils/types.py +21 -0
  126. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
  127. celldetective-1.5.0b0.dist-info/RECORD +187 -0
  128. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
  129. tests/gui/test_new_project.py +129 -117
  130. tests/gui/test_project.py +127 -79
  131. tests/test_filters.py +39 -15
  132. tests/test_notebooks.py +8 -0
  133. tests/test_tracking.py +232 -13
  134. tests/test_utils.py +123 -77
  135. celldetective/gui/base_components.py +0 -23
  136. celldetective/gui/layouts.py +0 -1602
  137. celldetective/gui/processes/compute_neighborhood.py +0 -594
  138. celldetective/gui/processes/measure_cells.py +0 -360
  139. celldetective/gui/processes/segment_cells.py +0 -499
  140. celldetective/gui/processes/track_cells.py +0 -303
  141. celldetective/gui/processes/train_segmentation_model.py +0 -270
  142. celldetective/gui/processes/train_signal_model.py +0 -108
  143. celldetective/gui/table_ops/merge_groups.py +0 -118
  144. celldetective/gui/viewers.py +0 -1354
  145. celldetective/io.py +0 -3663
  146. celldetective/utils.py +0 -3108
  147. celldetective-1.4.2.dist-info/RECORD +0 -123
  148. /celldetective/{gui/processes → processes}/downloader.py +0 -0
  149. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
@@ -2,47 +2,90 @@ import json
2
2
  import os
3
3
  from glob import glob
4
4
 
5
- import matplotlib.pyplot as plt
6
5
  import numpy as np
7
- import pandas as pd
8
- import scipy.ndimage as ndi
9
- from PyQt5.QtCore import Qt, QSize
6
+ from PyQt5.QtCore import Qt, QSize, QThread
10
7
  from PyQt5.QtGui import QDoubleValidator, QIntValidator
11
- from PyQt5.QtWidgets import QAction, QMenu, QMessageBox, QLabel, QFileDialog, QHBoxLayout, \
12
- QGridLayout, QLineEdit, QScrollArea, QVBoxLayout, QComboBox, QPushButton, QApplication, QRadioButton, QButtonGroup
8
+ from PyQt5.QtWidgets import (
9
+ QAction,
10
+ QMenu,
11
+ QMessageBox,
12
+ QLabel,
13
+ QFileDialog,
14
+ QHBoxLayout,
15
+ QGridLayout,
16
+ QLineEdit,
17
+ QScrollArea,
18
+ QVBoxLayout,
19
+ QComboBox,
20
+ QPushButton,
21
+ QApplication,
22
+ QRadioButton,
23
+ QButtonGroup,
24
+ )
13
25
  from fonticon_mdi6 import MDI6
14
- from skimage.measure import regionprops_table
26
+
15
27
  from superqt import QLabeledSlider, QLabeledDoubleRangeSlider
16
28
  from superqt.fonticon import icon
17
29
 
18
- from celldetective.gui import CelldetectiveMainWindow, CelldetectiveWidget
19
- from celldetective.gui.gui_utils import PreprocessingLayout, generic_message
20
- from celldetective.gui.gui_utils import center_window, FigureCanvas, color_from_class, help_generic
21
- from celldetective.gui.viewers import ThresholdedStackVisualizer
22
- from celldetective.io import load_frames
23
- from celldetective.segmentation import identify_markers_from_binary, apply_watershed
24
- from celldetective.utils import get_software_location, extract_experiment_channels, rename_intensity_column
30
+ from celldetective.gui.gui_utils import PreprocessingLayout
31
+ from celldetective.gui.base.components import (
32
+ generic_message,
33
+ CelldetectiveMainWindow,
34
+ CelldetectiveWidget,
35
+ )
36
+ from celldetective.gui.gui_utils import color_from_class, help_generic
37
+ from celldetective.gui.base.figure_canvas import FigureCanvas
38
+ from celldetective.gui.viewers.threshold_viewer import ThresholdedStackVisualizer
39
+ from celldetective.utils.image_loaders import load_frames
40
+
41
+ from celldetective import (
42
+ get_software_location,
43
+ )
44
+ from celldetective.utils.data_cleaning import rename_intensity_column
45
+ from celldetective.utils.experiment import extract_experiment_channels
46
+ import logging
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ class BackgroundLoader(QThread):
52
+ def run(self):
53
+ logger.info("Loading background packages...")
54
+ try:
55
+ from celldetective.segmentation import (
56
+ identify_markers_from_binary,
57
+ apply_watershed,
58
+ )
59
+ from scipy.ndimage._measurements import label
60
+ import pandas as pd
61
+ from celldetective.regionprops._regionprops import regionprops_table
62
+ except Exception:
63
+ logger.error("Background packages not loaded...")
64
+ logger.info("Background packages loaded...")
25
65
 
26
66
 
27
67
  class ThresholdConfigWizard(CelldetectiveMainWindow):
28
68
  """
29
- UI to create a threshold pipeline for segmentation.
69
+ UI to create a threshold pipeline for segmentation.
30
70
 
31
- """
71
+ """
32
72
 
33
73
  def __init__(self, parent_window=None):
34
74
 
35
75
  super().__init__()
36
76
  self.parent_window = parent_window
37
- self.screen_height = self.parent_window.parent_window.parent_window.parent_window.screen_height
38
- self.screen_width = self.parent_window.parent_window.parent_window.parent_window.screen_width
77
+ self.screen_height = (
78
+ self.parent_window.parent_window.parent_window.parent_window.screen_height
79
+ )
80
+ self.screen_width = (
81
+ self.parent_window.parent_window.parent_window.parent_window.screen_width
82
+ )
39
83
  self.setMinimumWidth(int(0.8 * self.screen_width))
40
84
  self.setMinimumHeight(int(0.8 * self.screen_height))
41
85
  self.setWindowTitle("Threshold configuration wizard")
42
- center_window(self)
43
86
 
44
87
  self._createActions()
45
- self._createMenuBar()
88
+ self._create_menu_bar()
46
89
 
47
90
  self.mode = self.parent_window.mode
48
91
  self.pos = self.parent_window.parent_window.parent_window.pos
@@ -52,9 +95,17 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
52
95
  self.min_dist = 30
53
96
  self.onlyFloat = QDoubleValidator()
54
97
  self.onlyInt = QIntValidator()
55
- self.cell_properties = ['centroid', 'area', 'perimeter', 'eccentricity', 'intensity_mean', 'solidity']
98
+ self.cell_properties = [
99
+ "centroid",
100
+ "area",
101
+ "perimeter",
102
+ "eccentricity",
103
+ "intensity_mean",
104
+ "solidity",
105
+ ]
56
106
  self.edge = None
57
107
  self.filters = []
108
+ self.fill_holes = True
58
109
 
59
110
  self.locate_stack()
60
111
  self.generate_viewer()
@@ -70,12 +121,15 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
70
121
  self.populate_widget()
71
122
  self.setAttribute(Qt.WA_DeleteOnClose)
72
123
 
73
- def _createMenuBar(self):
74
- menuBar = self.menuBar()
124
+ self.bg_loader = BackgroundLoader()
125
+ self.bg_loader.start()
126
+
127
+ def _create_menu_bar(self):
128
+ menu_bar = self.menuBar()
75
129
  # Creating menus using a QMenu object
76
- fileMenu = QMenu("&File", self)
77
- fileMenu.addAction(self.openAction)
78
- menuBar.addMenu(fileMenu)
130
+ file_menu = QMenu("&File", self)
131
+ file_menu.addAction(self.openAction)
132
+ menu_bar.addMenu(file_menu)
79
133
 
80
134
  # Creating menus using a title
81
135
  # editMenu = menuBar.addMenu("&Edit")
@@ -90,11 +144,10 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
90
144
  self.openAction.triggered.connect(self.load_previous_config)
91
145
 
92
146
  def populate_widget(self):
93
-
94
147
  """
95
- Create the multibox design.
148
+ Create the multibox design.
96
149
 
97
- """
150
+ """
98
151
  self.button_widget = CelldetectiveWidget()
99
152
  main_layout = QHBoxLayout()
100
153
  self.button_widget.setLayout(main_layout)
@@ -140,11 +193,22 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
140
193
  threshold_title_grid = QHBoxLayout()
141
194
  section_threshold = QLabel("Threshold")
142
195
  section_threshold.setStyleSheet("font-weight: bold;")
143
- threshold_title_grid.addWidget(section_threshold, 90, alignment=Qt.AlignCenter)
196
+ threshold_title_grid.addWidget(section_threshold, 80, alignment=Qt.AlignCenter)
197
+
198
+ self.fill_holes_btn = QPushButton("")
199
+ self.fill_holes_btn.setIcon(icon(MDI6.format_color_fill, color="white"))
200
+ self.fill_holes_btn.setIconSize(QSize(20, 20))
201
+ self.fill_holes_btn.setStyleSheet(self.button_select_all)
202
+ self.fill_holes_btn.setToolTip("Fill holes in binary mask")
203
+ self.fill_holes_btn.setCheckable(True)
204
+ self.fill_holes_btn.setChecked(True)
205
+ self.fill_holes_btn.clicked.connect(self.toggle_fill_holes)
206
+ threshold_title_grid.addWidget(self.fill_holes_btn, 5)
144
207
 
145
208
  self.ylog_check = QPushButton("")
146
209
  self.ylog_check.setIcon(icon(MDI6.math_log, color="black"))
147
210
  self.ylog_check.setStyleSheet(self.button_select_all)
211
+ self.ylog_check.setCheckable(True)
148
212
  self.ylog_check.clicked.connect(self.switch_to_log)
149
213
  threshold_title_grid.addWidget(self.ylog_check, 5)
150
214
 
@@ -166,8 +230,13 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
166
230
  self.threshold_slider.setTickInterval(0.00001)
167
231
  self.threshold_slider.setOrientation(Qt.Horizontal)
168
232
  self.threshold_slider.setDecimals(5)
169
- self.threshold_slider.setRange(np.amin(self.img[self.img == self.img]), np.amax(self.img[self.img == self.img]))
170
- self.threshold_slider.setValue([np.percentile(self.img.flatten(), 90), np.amax(self.img)])
233
+ self.threshold_slider.setRange(
234
+ np.amin(self.img[self.img == self.img]),
235
+ np.amax(self.img[self.img == self.img]),
236
+ )
237
+ self.threshold_slider.setValue(
238
+ [np.percentile(self.img.ravel(), 90), np.amax(self.img)]
239
+ )
171
240
  self.threshold_slider.valueChanged.connect(self.threshold_changed)
172
241
 
173
242
  # self.initialize_histogram()
@@ -186,24 +255,38 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
186
255
  # FINAL SAVE BTN#
187
256
  #################
188
257
 
189
- self.save_btn = QPushButton('Save')
258
+ self.save_btn = QPushButton("Save")
190
259
  self.save_btn.setStyleSheet(self.button_style_sheet)
191
260
  self.save_btn.clicked.connect(self.write_instructions)
192
261
  self.left_panel.addWidget(self.save_btn)
193
262
 
194
- self.properties_box_widgets = [self.propscanvas, *self.features_cb,
195
- self.property_query_le, self.submit_query_btn, self.save_btn]
263
+ self.properties_box_widgets = [
264
+ self.propscanvas,
265
+ *self.features_cb,
266
+ self.property_query_le,
267
+ self.submit_query_btn,
268
+ self.save_btn,
269
+ ]
196
270
  for p in self.properties_box_widgets:
197
271
  p.setEnabled(False)
198
272
 
199
- def help_prefilter(self):
273
+ # Force initial update after all UI elements are created
274
+ self.threshold_changed(self.threshold_slider.value())
200
275
 
276
+ def help_prefilter(self):
277
+ """
278
+ Helper for prefiltering strategy
201
279
  """
202
- Helper for prefiltering strategy
203
- """
204
280
 
205
281
  dict_path = os.sep.join(
206
- [get_software_location(), 'celldetective', 'gui', 'help', 'prefilter-for-segmentation.json'])
282
+ [
283
+ get_software_location(),
284
+ "celldetective",
285
+ "gui",
286
+ "help",
287
+ "prefilter-for-segmentation.json",
288
+ ]
289
+ )
207
290
 
208
291
  with open(dict_path) as f:
209
292
  d = json.load(f)
@@ -214,8 +297,10 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
214
297
  message_box = QMessageBox()
215
298
  message_box.setIcon(QMessageBox.Information)
216
299
  message_box.setTextFormat(Qt.RichText)
217
- message_box.setText(f"The suggested technique is to {suggestion}.\nSee a tutorial <a "
218
- f"href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>.")
300
+ message_box.setText(
301
+ f"The suggested technique is to {suggestion}.\nSee a tutorial <a "
302
+ f"href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>."
303
+ )
219
304
  message_box.setWindowTitle("Info")
220
305
  message_box.setStandardButtons(QMessageBox.Ok)
221
306
  return_value = message_box.exec()
@@ -227,22 +312,24 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
227
312
  marker_box = QVBoxLayout()
228
313
  marker_box.setContentsMargins(30, 30, 30, 30)
229
314
 
230
- marker_lbl = QLabel('Objects')
315
+ marker_lbl = QLabel("Objects")
231
316
  marker_lbl.setStyleSheet("font-weight: bold;")
232
317
  marker_box.addWidget(marker_lbl, alignment=Qt.AlignCenter)
233
318
 
234
319
  object_option_hbox = QHBoxLayout()
235
- self.marker_option = QRadioButton('markers')
236
- self.all_objects_option = QRadioButton('all non-contiguous objects')
320
+ self.marker_option = QRadioButton("markers")
321
+ self.all_objects_option = QRadioButton("all non-contiguous objects")
237
322
  self.marker_option_group = QButtonGroup()
238
323
  self.marker_option_group.addButton(self.marker_option)
239
324
  self.marker_option_group.addButton(self.all_objects_option)
240
325
  object_option_hbox.addWidget(self.marker_option, 50, alignment=Qt.AlignCenter)
241
- object_option_hbox.addWidget(self.all_objects_option, 50, alignment=Qt.AlignCenter)
326
+ object_option_hbox.addWidget(
327
+ self.all_objects_option, 50, alignment=Qt.AlignCenter
328
+ )
242
329
  marker_box.addLayout(object_option_hbox)
243
330
 
244
331
  hbox_footprint = QHBoxLayout()
245
- hbox_footprint.addWidget(QLabel('Footprint: '), 20)
332
+ hbox_footprint.addWidget(QLabel("Footprint: "), 20)
246
333
  self.footprint_slider = QLabeledSlider()
247
334
  self.footprint_slider.setSingleStep(1)
248
335
  self.footprint_slider.setOrientation(Qt.Horizontal)
@@ -250,11 +337,11 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
250
337
  self.footprint_slider.setValue(self.footprint)
251
338
  self.footprint_slider.valueChanged.connect(self.set_footprint)
252
339
  hbox_footprint.addWidget(self.footprint_slider, 30)
253
- hbox_footprint.addWidget(QLabel(''), 50)
340
+ hbox_footprint.addWidget(QLabel(""), 50)
254
341
  marker_box.addLayout(hbox_footprint)
255
342
 
256
343
  hbox_distance = QHBoxLayout()
257
- hbox_distance.addWidget(QLabel('Min distance: '), 20)
344
+ hbox_distance.addWidget(QLabel("Min distance: "), 20)
258
345
  self.min_dist_slider = QLabeledSlider()
259
346
  self.min_dist_slider.setSingleStep(1)
260
347
  self.min_dist_slider.setOrientation(Qt.Horizontal)
@@ -262,7 +349,7 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
262
349
  self.min_dist_slider.setValue(self.min_dist)
263
350
  self.min_dist_slider.valueChanged.connect(self.set_min_dist)
264
351
  hbox_distance.addWidget(self.min_dist_slider, 30)
265
- hbox_distance.addWidget(QLabel(''), 50)
352
+ hbox_distance.addWidget(QLabel(""), 50)
266
353
  marker_box.addLayout(hbox_distance)
267
354
 
268
355
  hbox_marker_btns = QHBoxLayout()
@@ -303,26 +390,27 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
303
390
  properties_box = QVBoxLayout()
304
391
  properties_box.setContentsMargins(30, 30, 30, 30)
305
392
 
306
- properties_lbl = QLabel('Filter on properties')
307
- properties_lbl.setStyleSheet('font-weight: bold;')
393
+ properties_lbl = QLabel("Filter on properties")
394
+ properties_lbl.setStyleSheet("font-weight: bold;")
308
395
  properties_box.addWidget(properties_lbl, alignment=Qt.AlignCenter)
309
396
 
310
397
  properties_box.addWidget(self.propscanvas)
311
398
 
312
- self.features_cb = [QComboBox() for i in range(2)]
399
+ self.features_cb = [QComboBox() for _ in range(2)]
313
400
  for i in range(2):
314
401
  hbox_feat = QHBoxLayout()
315
- hbox_feat.addWidget(QLabel(f'feature {i}: '), 20)
402
+ hbox_feat.addWidget(QLabel(f"feature {i}: "), 20)
316
403
  hbox_feat.addWidget(self.features_cb[i], 80)
317
404
  properties_box.addLayout(hbox_feat)
318
405
 
319
406
  hbox_classify = QHBoxLayout()
320
- hbox_classify.addWidget(QLabel('remove: '), 10)
407
+ hbox_classify.addWidget(QLabel("remove: "), 10)
321
408
  self.property_query_le = QLineEdit()
322
409
  self.property_query_le.setPlaceholderText(
323
- 'eliminate points using a query such as: area > 100 or eccentricity > 0.95')
410
+ "eliminate points using a query such as: area > 100 or eccentricity > 0.95"
411
+ )
324
412
  hbox_classify.addWidget(self.property_query_le, 70)
325
- self.submit_query_btn = QPushButton('Submit...')
413
+ self.submit_query_btn = QPushButton("Submit...")
326
414
  self.submit_query_btn.setStyleSheet(self.button_style_sheet)
327
415
  self.submit_query_btn.clicked.connect(self.apply_property_query)
328
416
  hbox_classify.addWidget(self.submit_query_btn, 20)
@@ -331,33 +419,49 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
331
419
  self.left_panel.addLayout(properties_box)
332
420
 
333
421
  def generate_viewer(self):
334
- self.viewer = ThresholdedStackVisualizer(preprocessing=self.filters, show_opacity_slider=False,
335
- show_threshold_slider=False, stack_path=self.stack_path,
336
- frame_slider=True, contrast_slider=True, channel_cb=True,
337
- channel_names=self.channel_names, n_channels=self.nbr_channels,
338
- target_channel=0, PxToUm=None, initial_threshold=None)
422
+ self.viewer = ThresholdedStackVisualizer(
423
+ preprocessing=self.filters,
424
+ show_opacity_slider=False,
425
+ show_threshold_slider=False,
426
+ stack_path=self.stack_path,
427
+ frame_slider=True,
428
+ contrast_slider=True,
429
+ channel_cb=True,
430
+ channel_names=self.channel_names,
431
+ n_channels=self.nbr_channels,
432
+ target_channel=0,
433
+ PxToUm=None,
434
+ initial_threshold=None,
435
+ fill_holes=self.fill_holes,
436
+ )
339
437
 
340
438
  def populate_right_panel(self):
341
439
  self.right_panel.addWidget(self.viewer.canvas)
342
440
 
343
441
  def locate_stack(self):
344
-
345
442
  """
346
- Locate the target movie.
443
+ Locate the target movie.
347
444
 
348
- """
445
+ """
349
446
 
350
447
  if isinstance(self.pos, str):
351
- movies = glob(self.pos + f"movie/{self.parent_window.parent_window.parent_window.movie_prefix}*.tif")
448
+ movies = glob(
449
+ self.pos
450
+ + f"movie/{self.parent_window.parent_window.parent_window.movie_prefix}*.tif"
451
+ )
352
452
 
353
453
  else:
354
- generic_message('Please select a unique position before launching the wizard...')
454
+ generic_message(
455
+ "Please select a unique position before launching the wizard..."
456
+ )
355
457
  self.img = None
356
458
  self.close()
357
459
  return None
358
460
 
359
461
  if len(movies) == 0:
360
- generic_message('No movies are detected in the experiment folder. Cannot load an image to test Haralick.')
462
+ generic_message(
463
+ "No movies are detected in the experiment folder. Cannot load an image to test Haralick."
464
+ )
361
465
  self.img = None
362
466
  self.close()
363
467
  else:
@@ -367,16 +471,18 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
367
471
  self.nbr_channels = len(self.channel_names)
368
472
 
369
473
  def initalize_props_scatter(self):
370
-
371
474
  """
372
- Define properties scatter.
373
- """
475
+ Define properties scatter.
476
+ """
374
477
 
375
- self.fig_props, self.ax_props = plt.subplots(tight_layout=True)
478
+ from matplotlib.figure import Figure
479
+
480
+ self.fig_props = Figure(tight_layout=True)
481
+ self.ax_props = self.fig_props.add_subplot(111)
376
482
  self.propscanvas = FigureCanvas(self.fig_props, interactive=True)
377
- self.fig_props.set_facecolor('none')
483
+ self.fig_props.set_facecolor("none")
378
484
  self.fig_props.canvas.setStyleSheet("background-color: transparent;")
379
- self.scat_props = self.ax_props.scatter([], [], color='k', alpha=0.75)
485
+ self.scat_props = self.ax_props.scatter([], [], color="k", alpha=0.75)
380
486
  self.propscanvas.canvas.draw_idle()
381
487
  self.propscanvas.canvas.setMinimumHeight(self.screen_height // 5)
382
488
 
@@ -384,66 +490,93 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
384
490
 
385
491
  self.img = self.viewer.init_frame
386
492
 
387
- self.fig_hist, self.ax_hist = plt.subplots(tight_layout=True)
493
+ from matplotlib.figure import Figure
494
+
495
+ self.fig_hist = Figure(tight_layout=True)
496
+ self.ax_hist = self.fig_hist.add_subplot(111)
388
497
  self.canvas_hist = FigureCanvas(self.fig_hist, interactive=False)
389
- self.fig_hist.set_facecolor('none')
498
+ self.fig_hist.set_facecolor("none")
390
499
  self.fig_hist.canvas.setStyleSheet("background-color: transparent;")
391
500
 
392
501
  # self.ax_hist.clear()
393
502
  # self.ax_hist.cla()
394
- self.ax_hist.patch.set_facecolor('none')
395
- self.hist_y, x, _ = self.ax_hist.hist(self.img.flatten(), density=True, bins=300, color="k")
503
+ self.ax_hist.patch.set_facecolor("none")
504
+ self.hist_y, x, _ = self.ax_hist.hist(
505
+ self.img.ravel(), density=True, bins=300, color="k"
506
+ )
396
507
  # self.ax_hist.set_xlim(np.amin(self.img),np.amax(self.img))
397
- self.ax_hist.set_xlabel('intensity [a.u.]')
398
- self.ax_hist.spines['top'].set_visible(False)
399
- self.ax_hist.spines['right'].set_visible(False)
508
+ self.ax_hist.set_xlabel("intensity [a.u.]")
509
+ self.ax_hist.spines["top"].set_visible(False)
510
+ self.ax_hist.spines["right"].set_visible(False)
400
511
  # self.ax_hist.set_yticks([])
401
- self.ax_hist.set_xlim(np.amin(self.img[self.img == self.img]), np.amax(self.img[self.img == self.img]))
512
+ self.ax_hist.set_xlim(
513
+ np.amin(self.img[self.img == self.img]),
514
+ np.amax(self.img[self.img == self.img]),
515
+ )
402
516
  self.ax_hist.set_ylim(0, self.hist_y.max())
403
517
 
404
- self.threshold_slider.setRange(np.amin(self.img[self.img == self.img]), np.amax(self.img[self.img == self.img]))
405
- self.threshold_slider.setValue([np.nanpercentile(self.img.flatten(), 90), np.amax(self.img)])
518
+ self.threshold_slider.setRange(
519
+ np.amin(self.img[self.img == self.img]),
520
+ np.amax(self.img[self.img == self.img]),
521
+ )
522
+ self.threshold_slider.setValue(
523
+ [np.nanpercentile(self.img.ravel(), 90), np.amax(self.img)]
524
+ )
406
525
  self.add_hist_threshold()
407
526
 
408
527
  self.canvas_hist.canvas.draw_idle()
409
528
  self.canvas_hist.canvas.setMinimumHeight(self.screen_height // 8)
410
529
 
411
530
  def update_histogram(self):
412
-
413
531
  """
414
- Redraw the histogram after an update on the image.
415
- Move the threshold slider accordingly.
532
+ Redraw the histogram after an update on the image.
533
+ Move the threshold slider accordingly.
416
534
 
417
- """
535
+ """
418
536
 
419
537
  self.ax_hist.clear()
420
- self.ax_hist.patch.set_facecolor('none')
421
- self.hist_y, x, _ = self.ax_hist.hist(self.img.flatten(), density=True, bins=300, color="k")
422
- self.ax_hist.set_xlabel('intensity [a.u.]')
423
- self.ax_hist.spines['top'].set_visible(False)
424
- self.ax_hist.spines['right'].set_visible(False)
538
+ self.ax_hist.patch.set_facecolor("none")
539
+ self.hist_y, x, _ = self.ax_hist.hist(
540
+ self.img.ravel(), density=True, bins=300, color="k"
541
+ )
542
+ self.ax_hist.set_xlabel("intensity [a.u.]")
543
+ self.ax_hist.spines["top"].set_visible(False)
544
+ self.ax_hist.spines["right"].set_visible(False)
425
545
  # self.ax_hist.set_yticks([])
426
- self.ax_hist.set_xlim(np.amin(self.img[self.img == self.img]), np.amax(self.img[self.img == self.img]))
546
+ self.ax_hist.set_xlim(
547
+ np.amin(self.img[self.img == self.img]),
548
+ np.amax(self.img[self.img == self.img]),
549
+ )
427
550
  self.ax_hist.set_ylim(0, self.hist_y.max())
428
551
  self.add_hist_threshold()
429
552
  self.canvas_hist.canvas.draw()
430
553
 
431
- self.threshold_slider.setRange(np.amin(self.img[self.img == self.img]), np.amax(self.img[self.img == self.img]))
432
- self.threshold_slider.setValue([np.nanpercentile(self.img.flatten(), 90), np.amax(self.img)])
554
+ self.threshold_slider.setRange(
555
+ np.amin(self.img[self.img == self.img]),
556
+ np.amax(self.img[self.img == self.img]),
557
+ )
558
+ self.threshold_slider.setValue(
559
+ [np.nanpercentile(self.img.ravel(), 90), np.amax(self.img)]
560
+ )
433
561
  self.threshold_changed(self.threshold_slider.value())
434
562
 
435
563
  def add_hist_threshold(self):
436
564
 
437
565
  ymin, ymax = self.ax_hist.get_ylim()
438
- self.min_intensity_line, = self.ax_hist.plot(
439
- [self.threshold_slider.value()[0], self.threshold_slider.value()[0]], [0, ymax], c="tab:purple")
440
- self.max_intensity_line, = self.ax_hist.plot(
441
- [self.threshold_slider.value()[1], self.threshold_slider.value()[1]], [0, ymax], c="tab:purple")
566
+ (self.min_intensity_line,) = self.ax_hist.plot(
567
+ [self.threshold_slider.value()[0], self.threshold_slider.value()[0]],
568
+ [0, ymax],
569
+ c="tab:purple",
570
+ )
571
+ (self.max_intensity_line,) = self.ax_hist.plot(
572
+ [self.threshold_slider.value()[1], self.threshold_slider.value()[1]],
573
+ [0, ymax],
574
+ c="tab:purple",
575
+ )
442
576
 
443
577
  # self.canvas_hist.canvas.draw_idle()
444
578
 
445
579
  def reload_frame(self):
446
-
447
580
  """
448
581
  Load the frame from the current channel and time choice. Show imshow, update histogram.
449
582
  """
@@ -454,45 +587,51 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
454
587
  self.update_histogram()
455
588
 
456
589
  def preprocess_image(self):
457
-
458
590
  """
459
- Reload the frame, apply the filters, update imshow and histogram.
591
+ Reload the frame, apply the filters, update imshow and histogram.
460
592
 
461
- """
593
+ """
462
594
 
463
595
  self.filters = self.preprocessing.list.items
464
596
  self.reload_frame()
465
597
  self.update_histogram()
466
598
 
467
599
  def threshold_changed(self, value):
468
-
469
600
  """
470
- Move the threshold values on histogram, when slider is moved.
471
- """
472
-
601
+ Move the threshold values on histogram, when slider is moved.
602
+ """
473
603
  self.clear_post_threshold_options()
474
604
  self.viewer.change_threshold(value)
475
605
 
476
606
  ymin, ymax = self.ax_hist.get_ylim()
477
- self.min_intensity_line.set_data([value[0],value[0]], [0, ymax])
478
- self.max_intensity_line.set_data([value[1],value[1]], [0, ymax])
607
+ self.min_intensity_line.set_data([value[0], value[0]], [0, ymax])
608
+ self.max_intensity_line.set_data([value[1], value[1]], [0, ymax])
479
609
  self.canvas_hist.canvas.draw_idle()
480
610
 
481
611
  def switch_to_log(self):
482
-
483
612
  """
484
- Switch threshold histogram to log scale. Auto adjust.
485
- """
613
+ Switch threshold histogram to log scale. Auto adjust.
614
+ """
486
615
 
487
- if self.ax_hist.get_yscale() == 'linear':
488
- self.ax_hist.set_yscale('log')
616
+ if self.ax_hist.get_yscale() == "linear":
617
+ self.ax_hist.set_yscale("log")
618
+ self.ylog_check.setIcon(icon(MDI6.math_log, color="white"))
489
619
  else:
490
- self.ax_hist.set_yscale('linear')
620
+ self.ax_hist.set_yscale("linear")
621
+ self.ylog_check.setIcon(icon(MDI6.math_log, color="black"))
491
622
 
492
623
  # self.ax_hist.autoscale()
493
- self.ax_hist.set_ylim(0, self.hist_y.max())
624
+ ymin = 0 if self.ax_hist.get_yscale() == "linear" else 1e-1
625
+ self.ax_hist.set_ylim(ymin, self.hist_y.max())
494
626
  self.canvas_hist.canvas.draw_idle()
495
627
 
628
+ def toggle_fill_holes(self):
629
+ self.fill_holes = self.fill_holes_btn.isChecked()
630
+ self.viewer.fill_holes = self.fill_holes
631
+ self.viewer.change_threshold(self.threshold_slider.value())
632
+ color = "white" if self.fill_holes else "black"
633
+ self.fill_holes_btn.setIcon(icon(MDI6.format_color_fill, color=color))
634
+
496
635
  def set_footprint(self):
497
636
  self.footprint = self.footprint_slider.value()
498
637
 
@@ -510,9 +649,15 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
510
649
  if self.viewer.mask.ndim == 3:
511
650
  self.viewer.mask = np.squeeze(self.viewer.mask)
512
651
 
513
- self.coords, self.edt_map = identify_markers_from_binary(self.viewer.mask, self.min_dist,
514
- footprint_size=self.footprint, footprint=None,
515
- return_edt=True)
652
+ from celldetective.segmentation import identify_markers_from_binary
653
+
654
+ self.coords, self.edt_map = identify_markers_from_binary(
655
+ self.viewer.mask,
656
+ self.min_dist,
657
+ footprint_size=self.footprint,
658
+ footprint=None,
659
+ return_edt=True,
660
+ )
516
661
  if len(self.coords) > 0:
517
662
  self.viewer.scat_markers.set_offsets(self.coords[:, [1, 0]])
518
663
  self.viewer.scat_markers.set_visible(True)
@@ -524,14 +669,24 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
524
669
 
525
670
  def apply_watershed_to_selection(self):
526
671
 
672
+ import scipy.ndimage as ndi
673
+ from celldetective.segmentation import apply_watershed
674
+
527
675
  if self.marker_option.isChecked():
528
- self.labels = apply_watershed(self.viewer.mask, self.coords, self.edt_map)
676
+ self.labels = apply_watershed(
677
+ self.viewer.mask, self.coords, self.edt_map, fill_holes=self.fill_holes
678
+ )
529
679
  else:
530
- self.labels, _ = ndi.label(self.viewer.mask.astype(int))
680
+ from scipy.ndimage._measurements import label
531
681
 
532
- self.viewer.change_frame(self.viewer.frame_slider.value())
533
- self.viewer.im_mask.set_cmap('tab20c')
534
- self.viewer.im_mask.set_data(np.ma.masked_where(self.labels == 0., self.labels))
682
+ self.labels, _ = label(self.viewer.mask.astype(int))
683
+
684
+ self.viewer.channel_trigger = True
685
+ self.viewer.change_frame_from_channel_switch(self.viewer.frame_slider.value())
686
+ self.viewer.im_mask.set_cmap("tab20c")
687
+ self.viewer.im_mask.set_data(
688
+ np.ma.masked_where(self.labels == 0.0, self.labels)
689
+ )
535
690
  self.viewer.im_mask.autoscale()
536
691
  self.viewer.canvas.canvas.draw_idle()
537
692
 
@@ -544,67 +699,104 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
544
699
 
545
700
  def compute_features(self):
546
701
 
702
+ import pandas as pd
703
+ from skimage.measure import regionprops_table
704
+
547
705
  # Run regionprops to have properties for filtering
548
706
  intensity_image_idx = [self.nbr_channels * self.viewer.frame_slider.value()]
549
707
  for i in range(self.nbr_channels - 1):
550
708
  intensity_image_idx += [intensity_image_idx[-1] + 1]
551
709
 
552
710
  # Load channels at time t
553
- multichannel = load_frames(intensity_image_idx, self.stack_path, normalize_input=False)
711
+ multichannel = load_frames(
712
+ intensity_image_idx, self.stack_path, normalize_input=False
713
+ )
554
714
  self.props = pd.DataFrame(
555
- regionprops_table(self.labels, intensity_image=multichannel, properties=self.cell_properties))
715
+ regionprops_table(
716
+ self.labels,
717
+ intensity_image=multichannel,
718
+ properties=self.cell_properties,
719
+ )
720
+ )
556
721
  self.props = rename_intensity_column(self.props, self.channel_names)
557
- self.props['radial_distance'] = np.sqrt((self.props['centroid-1'] - self.img.shape[0] / 2) ** 2 + (
558
- self.props['centroid-0'] - self.img.shape[1] / 2) ** 2)
722
+ self.props["radial_distance"] = np.sqrt(
723
+ (self.props["centroid-1"] - self.img.shape[0] / 2) ** 2
724
+ + (self.props["centroid-0"] - self.img.shape[1] / 2) ** 2
725
+ )
726
+
727
+ self.props["class"] = 1
559
728
 
560
729
  for i in range(2):
561
730
  self.features_cb[i].clear()
562
731
  self.features_cb[i].addItems(list(self.props.columns))
563
732
  self.features_cb[i].setCurrentIndex(i)
564
- self.props["class"] = 1
565
733
 
566
734
  self.update_props_scatter()
567
735
 
568
736
  def update_props_scatter(self):
569
737
 
570
- self.scat_props.set_offsets(
571
- self.props[[self.features_cb[1].currentText(), self.features_cb[0].currentText()]].to_numpy())
572
- self.scat_props.set_facecolor([color_from_class(c) for c in self.props['class'].to_numpy()])
573
- self.ax_props.set_xlabel(self.features_cb[1].currentText())
574
- self.ax_props.set_ylabel(self.features_cb[0].currentText())
575
-
576
- self.viewer.scat_markers.set_offsets(self.props[['centroid-1', 'centroid-0']].to_numpy())
577
- self.viewer.scat_markers.set_color(['k'] * len(self.props))
578
- self.viewer.scat_markers.set_facecolor([color_from_class(c) for c in self.props['class'].to_numpy()])
579
-
580
- self.ax_props.set_xlim(0.75 * self.props[self.features_cb[1].currentText()].min(),
581
- 1.05 * self.props[self.features_cb[1].currentText()].max())
582
- self.ax_props.set_ylim(0.75 * self.props[self.features_cb[0].currentText()].min(),
583
- 1.05 * self.props[self.features_cb[0].currentText()].max())
738
+ feat1 = self.features_cb[1].currentText()
739
+ feat0 = self.features_cb[0].currentText()
740
+
741
+ if feat1 == "" or feat0 == "":
742
+ return
743
+
744
+ self.scat_props.set_offsets(self.props[[feat1, feat0]].to_numpy())
745
+ self.scat_props.set_facecolor(
746
+ [color_from_class(c) for c in self.props["class"].to_numpy()]
747
+ )
748
+ self.ax_props.set_xlabel(feat1)
749
+ self.ax_props.set_ylabel(feat0)
750
+
751
+ self.viewer.scat_markers.set_offsets(
752
+ self.props[["centroid-1", "centroid-0"]].to_numpy()
753
+ )
754
+ self.viewer.scat_markers.set_color(["k"] * len(self.props))
755
+ self.viewer.scat_markers.set_facecolor(
756
+ [color_from_class(c) for c in self.props["class"].to_numpy()]
757
+ )
758
+ self.viewer.scat_markers.set_visible(True)
759
+
760
+ if not self.props.empty:
761
+ min_f1, max_f1 = self.props[feat1].min(), self.props[feat1].max()
762
+ min_f0, max_f0 = self.props[feat0].min(), self.props[feat0].max()
763
+
764
+ if np.isfinite([min_f1, max_f1, min_f0, max_f0]).all():
765
+ self.ax_props.set_xlim(
766
+ 0.75 * min_f1,
767
+ 1.05 * max_f1,
768
+ )
769
+ self.ax_props.set_ylim(
770
+ 0.75 * min_f0,
771
+ 1.05 * max_f0,
772
+ )
584
773
  self.propscanvas.canvas.draw_idle()
585
- self.viewer.canvas.canvas.draw_idle()
774
+ self.viewer.canvas.canvas.draw()
775
+ logger.info(f"Update markers for {len(self.props)} objects.")
586
776
 
587
777
  def prep_cell_properties(self):
588
778
 
589
779
  self.cell_properties_options = list(np.copy(self.cell_properties))
590
780
  self.cell_properties_options.remove("centroid")
591
781
  for k in range(self.nbr_channels):
592
- self.cell_properties_options.append(f'intensity_mean-{k}')
593
- self.cell_properties_options.remove('intensity_mean')
782
+ self.cell_properties_options.append(f"intensity_mean-{k}")
783
+ self.cell_properties_options.remove("intensity_mean")
594
784
 
595
785
  def apply_property_query(self):
596
786
  query = self.property_query_le.text()
597
- self.props['class'] = 1
787
+ self.props["class"] = 1
598
788
 
599
- if query == '':
600
- print('empty query')
789
+ if query == "":
790
+ logger.warning("empty query")
601
791
  else:
602
792
  try:
603
793
  self.selection = self.props.query(query).index
604
- print(self.selection)
605
- self.props.loc[self.selection, 'class'] = 0
794
+ logger.info(f"{self.selection}")
795
+ self.props.loc[self.selection, "class"] = 0
606
796
  except Exception as e:
607
- generic_message(f"The query could not be understood. No filtering was applied. {e}")
797
+ generic_message(
798
+ f"The query could not be understood. No filtering was applied. {e}"
799
+ )
608
800
  return None
609
801
 
610
802
  self.update_props_scatter()
@@ -618,47 +810,56 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
618
810
  for i in range(2):
619
811
  try:
620
812
  self.features_cb[i].disconnect()
621
- except Exception as e:
813
+ except Exception as _:
622
814
  pass
623
815
  self.features_cb[i].clear()
624
816
 
625
- self.property_query_le.setText('')
817
+ self.property_query_le.setText("")
626
818
 
627
819
  self.viewer.change_threshold(self.threshold_slider.value())
628
- self.viewer.scat_markers.set_color('tab:red')
820
+ self.viewer.scat_markers.set_color("tab:red")
629
821
  self.viewer.scat_markers.set_visible(False)
630
822
 
631
823
  def write_instructions(self):
632
824
 
633
825
  instructions = {
634
- "target_channel": self.viewer.channels_cb.currentText(),
826
+ "target_channel": self.viewer.channel_cb.currentText(),
635
827
  # for now index but would be more universal to use name
636
828
  "thresholds": self.threshold_slider.value(),
637
829
  "filters": self.preprocessing.list.items,
638
830
  "marker_min_distance": self.min_dist,
639
831
  "marker_footprint_size": self.footprint,
640
832
  "feature_queries": [self.property_query_le.text()],
641
- "equalize_reference": [self.equalize_option, self.viewer.frame_slider.value()],
833
+ "equalize_reference": [
834
+ self.equalize_option,
835
+ self.viewer.frame_slider.value(),
836
+ ],
642
837
  "do_watershed": self.marker_option.isChecked(),
838
+ "fill_holes": self.fill_holes,
643
839
  }
644
840
 
645
- print('The following instructions will be written: ', instructions)
646
- self.instruction_file = \
647
- QFileDialog.getSaveFileName(self, "Save File", self.exp_dir + f'configs/threshold_config_{self.mode}.json',
648
- '.json')[0]
649
- if self.instruction_file != '':
841
+ logger.info(f"The following instructions will be written: {instructions}")
842
+ self.instruction_file = QFileDialog.getSaveFileName(
843
+ self,
844
+ "Save File",
845
+ self.exp_dir + f"configs/threshold_config_{self.mode}.json",
846
+ ".json",
847
+ )[0]
848
+ if self.instruction_file != "":
650
849
  json_object = json.dumps(instructions, indent=4)
651
850
  with open(self.instruction_file, "w") as outfile:
652
851
  outfile.write(json_object)
653
- print("Configuration successfully written in ", self.instruction_file)
852
+ logger.info(
853
+ f"Configuration successfully written in {self.instruction_file}"
854
+ )
654
855
 
655
856
  self.parent_window.filename = self.instruction_file
656
- self.parent_window.file_label.setText(self.instruction_file[:16] + '...')
857
+ self.parent_window.file_label.setText(self.instruction_file[:16] + "...")
657
858
  self.parent_window.file_label.setToolTip(self.instruction_file)
658
859
 
659
860
  self.close()
660
861
  else:
661
- print('The instruction file could not be written...')
862
+ logger.error("The instruction file could not be written...")
662
863
 
663
864
  def activate_histogram_equalizer(self):
664
865
 
@@ -672,42 +873,44 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
672
873
  self.equalize_option_btn.setIconSize(QSize(20, 20))
673
874
 
674
875
  def load_previous_config(self):
675
- self.previous_instruction_file = \
676
- QFileDialog.getOpenFileName(self, "Load config",
677
- self.exp_dir + f'configs/threshold_config_{self.mode}.json',
678
- "JSON (*.json)")[0]
679
- with open(self.previous_instruction_file, 'r') as f:
876
+ self.previous_instruction_file = QFileDialog.getOpenFileName(
877
+ self,
878
+ "Load config",
879
+ self.exp_dir + f"configs/threshold_config_{self.mode}.json",
880
+ "JSON (*.json)",
881
+ )[0]
882
+ with open(self.previous_instruction_file, "r") as f:
680
883
  threshold_instructions = json.load(f)
681
884
 
682
- target_channel = threshold_instructions['target_channel']
885
+ target_channel = threshold_instructions["target_channel"]
683
886
  index = self.viewer.channels_cb.findText(target_channel)
684
887
  self.viewer.channels_cb.setCurrentIndex(index)
685
888
 
686
- filters = threshold_instructions['filters']
687
- items_to_add = [f[0] + '_filter' for f in filters]
889
+ filters = threshold_instructions["filters"]
890
+ items_to_add = [f[0] + "_filter" for f in filters]
688
891
  self.preprocessing.list.list_widget.clear()
689
892
  self.preprocessing.list.list_widget.addItems(items_to_add)
690
893
  self.preprocessing.list.items = filters
691
894
  self.preprocessing.apply_btn.click()
692
895
 
693
- thresholds = threshold_instructions['thresholds']
896
+ thresholds = threshold_instructions["thresholds"]
694
897
  self.threshold_slider.setValue(thresholds)
695
898
 
696
- marker_footprint_size = threshold_instructions['marker_footprint_size']
899
+ marker_footprint_size = threshold_instructions["marker_footprint_size"]
697
900
  self.footprint_slider.setValue(marker_footprint_size)
698
901
 
699
- marker_min_dist = threshold_instructions['marker_min_distance']
902
+ marker_min_dist = threshold_instructions["marker_min_distance"]
700
903
  self.min_dist_slider.setValue(marker_min_dist)
701
904
 
702
905
  self.markers_btn.click()
703
906
  self.watershed_btn.click()
704
907
 
705
- feature_queries = threshold_instructions['feature_queries']
908
+ feature_queries = threshold_instructions["feature_queries"]
706
909
  self.property_query_le.setText(feature_queries[0])
707
910
  self.submit_query_btn.click()
708
911
 
709
- if 'do_watershed' in threshold_instructions:
710
- do_watershed = threshold_instructions['do_watershed']
912
+ if "do_watershed" in threshold_instructions:
913
+ do_watershed = threshold_instructions["do_watershed"]
711
914
  if do_watershed:
712
915
  self.marker_option.click()
713
916
  else: