cellects 0.1.2__py3-none-any.whl → 0.2.6__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 (38) hide show
  1. cellects/__main__.py +65 -25
  2. cellects/config/all_vars_dict.py +18 -17
  3. cellects/core/cellects_threads.py +1034 -396
  4. cellects/core/motion_analysis.py +1664 -2010
  5. cellects/core/one_image_analysis.py +1082 -1061
  6. cellects/core/program_organizer.py +1687 -1316
  7. cellects/core/script_based_run.py +80 -76
  8. cellects/gui/advanced_parameters.py +390 -330
  9. cellects/gui/cellects.py +102 -91
  10. cellects/gui/custom_widgets.py +16 -33
  11. cellects/gui/first_window.py +226 -104
  12. cellects/gui/if_several_folders_window.py +117 -68
  13. cellects/gui/image_analysis_window.py +866 -454
  14. cellects/gui/required_output.py +104 -57
  15. cellects/gui/ui_strings.py +840 -0
  16. cellects/gui/video_analysis_window.py +333 -155
  17. cellects/image_analysis/cell_leaving_detection.py +64 -4
  18. cellects/image_analysis/image_segmentation.py +451 -22
  19. cellects/image_analysis/morphological_operations.py +2166 -1635
  20. cellects/image_analysis/network_functions.py +616 -253
  21. cellects/image_analysis/one_image_analysis_threads.py +94 -153
  22. cellects/image_analysis/oscillations_functions.py +131 -0
  23. cellects/image_analysis/progressively_add_distant_shapes.py +2 -3
  24. cellects/image_analysis/shape_descriptors.py +517 -466
  25. cellects/utils/formulas.py +169 -6
  26. cellects/utils/load_display_save.py +362 -109
  27. cellects/utils/utilitarian.py +86 -9
  28. cellects-0.2.6.dist-info/LICENSE +675 -0
  29. cellects-0.2.6.dist-info/METADATA +829 -0
  30. cellects-0.2.6.dist-info/RECORD +44 -0
  31. cellects/core/one_video_per_blob.py +0 -540
  32. cellects/image_analysis/cluster_flux_study.py +0 -102
  33. cellects-0.1.2.dist-info/LICENSE.odt +0 -0
  34. cellects-0.1.2.dist-info/METADATA +0 -132
  35. cellects-0.1.2.dist-info/RECORD +0 -44
  36. {cellects-0.1.2.dist-info → cellects-0.2.6.dist-info}/WHEEL +0 -0
  37. {cellects-0.1.2.dist-info → cellects-0.2.6.dist-info}/entry_points.txt +0 -0
  38. {cellects-0.1.2.dist-info → cellects-0.2.6.dist-info}/top_level.txt +0 -0
@@ -1,38 +1,88 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- Genereate the Image analysis window of the user interface of Cellects
4
-
5
- Cellects transforms the color images into grayscale images in a way that maximizes the contrast between the specimens and the background.
6
- It has an automatic procedure, processed by the one_image_analysis class. If this automatic procedure does not produce good enough results, the user can manually label some areas of the picture as “cell” or “background to help find a better color space combination. This is particularly useful when the background is heterogeneous, and Cellects can use this information in two ways: First; it can simply ignore the parts labeled as background (e.g. objects or manual writings). Second, it can use the manual annotation to train a more sophisticated segmentation method: A k-means algorithm to split the image into as many categories as necessary and use the “Cell” labelling to infer to what category the specimens are related to.
7
- Then, Cellects will take into account the user’s input as follows: For each of the segmentations created in the previous steps, it will count the amount of pixels labeled as specimens by the user that were correctly labeled as cell in the segmentation, and will select the segmentation that achieves the highest number. Then, it will do the same thing for the pixels labeled as background. Then, it will use the AND operator between the two results having the best match with the areas labeled as specimens, the AND operator between the two results having the best match with the areas labeled as background, and the OR operator between the result having the best match with the areas labeled as specimens and the result having the best match with the areas labeled as background. Therefore, this optional labeling adds three new segmentations that take into account the user-labeled regions. If the results are still unsatisfactory, the user can continue labeling more areas until one of the segmentations matches their expectations.
8
-
3
+ Image analysis GUI module for Cellects application
4
+
5
+ This module provides a user interface for configuring and performing image analysis with the Cellects system.
6
+ It allows users to adjust scaling parameters, manually label cell/background regions, select segmentation methods
7
+ (quick/careful), visualize results, and validate analysis outcomes through interactive decision prompts. The UI supports
8
+ manual arena delineation when automatic detection fails, using threaded operations for background processing.
9
+
10
+ Main Components
11
+ ImageAnalysisWindow : Main UI window for image analysis configuration and execution.
12
+
13
+ Includes parameter controls (scaling, spot shape/size), segmentation options (quick/careful/visualize)
14
+ Provides cell/background selection buttons with manual drawing capabilities
15
+ Features decision prompts via Yes/No buttons to validate intermediate results
16
+ Displays real-time image updates with user-defined annotations
17
+ Notes
18
+ Uses QThread for background operations to maintain UI responsiveness.
9
19
  """
10
20
  import logging
11
21
  import time
12
22
  from copy import deepcopy
13
23
  import numpy as np
14
24
  from PySide6 import QtWidgets, QtCore, QtGui
15
-
16
25
  from cellects.core.cellects_threads import (
17
26
  GetFirstImThread, GetLastImThread, FirstImageAnalysisThread,
18
- CropScaleSubtractDelineateThread, UpdateImageThread,
19
- LastImageAnalysisThread, SaveManualDelineationThread, FinalizeImageAnalysisThread)
27
+ CropScaleSubtractDelineateThread, UpdateImageThread, CompleteImageAnalysisThread,
28
+ LastImageAnalysisThread, SaveManualDelineationThread, PrepareVideoAnalysisThread)
29
+ from cellects.gui.ui_strings import IAW
20
30
  from cellects.gui.custom_widgets import (
21
31
  MainTabsType, InsertImage, FullScreenImage, PButton, Spinbox,
22
32
  Combobox, Checkbox, FixedText)
23
33
  from cellects.core.one_image_analysis import OneImageAnalysis
24
34
  from cellects.image_analysis.image_segmentation import filter_dict
35
+ from cellects.utils.formulas import bracket_to_uint8_image_contrast
25
36
 
26
37
 
27
38
  class ImageAnalysisWindow(MainTabsType):
28
- def __init__(self, parent, night_mode):
39
+ def __init__(self, parent: object, night_mode: bool):
40
+ """
41
+ Initialize the ImageAnalysis window with a parent widget and night mode setting.
42
+
43
+ Parameters
44
+ ----------
45
+ parent : QWidget
46
+ The parent widget to which this window will be attached.
47
+ night_mode : bool
48
+ A boolean indicating whether the night mode should be enabled.
49
+
50
+ Examples
51
+ --------
52
+ >>> from PySide6 import QtWidgets
53
+ >>> from cellects.gui.cellects import CellectsMainWidget
54
+ >>> from cellects.gui.image_analysis_window import ImageAnalysisWindow
55
+ >>> from cellects.core.program_organizer import ProgramOrganizer
56
+ >>> import numpy as np
57
+ >>> import sys
58
+ >>> app = QtWidgets.QApplication([])
59
+ >>> parent = CellectsMainWidget()
60
+ >>> parent.po = ProgramOrganizer()
61
+ >>> parent.po.update_variable_dict()
62
+ >>> parent.po.get_first_image(np.zeros((10, 10), dtype=np.uint8), 1)
63
+ >>> session = ImageAnalysisWindow(parent, False)
64
+ >>> session.true_init()
65
+ >>> parent.insertWidget(0, session)
66
+ >>> parent.show()
67
+ >>> sys.exit(app.exec())
68
+ """
29
69
  super().__init__(parent, night_mode)
30
70
  self.setParent(parent)
31
71
  self.csc_dict = self.parent().po.vars['convert_for_origin'] # To change
32
72
  self.manual_delineation_flag: bool = False
33
73
 
34
74
  def true_init(self):
75
+ """
76
+ Initialize the ImageAnalysisWindow class with default settings and UI components.
77
+
78
+ This function sets up the initial state of the ImageAnalysisWindow, including various flags,
79
+ labels, input fields, and layout configurations. It also initializes the display image
80
+ and connects UI elements to their respective event handlers.
35
81
 
82
+ Notes
83
+ -----
84
+ This method assumes that the parent widget has a 'po' attribute with specific settings and variables.
85
+ """
36
86
  logging.info("Initialize ImageAnalysisWindow")
37
87
  self.data_tab.set_not_in_use()
38
88
  self.image_tab.set_in_use()
@@ -68,31 +118,34 @@ class ImageAnalysisWindow(MainTabsType):
68
118
  self.display_image.mouseReleaseEvent = self.get_mouse_release_coordinates
69
119
 
70
120
  ## Title
71
- # self.title_label = FixedText('One Image Analysis', police=30, night_mode=self.parent().po.all['night_mode'])
72
- # self.title_label.setAlignment(QtCore.Qt.AlignHCenter)
73
- self.image_number_label = FixedText('Image number',
74
- tip="Change this number if cells are invisible on the first image, never otherwise\nIf they cannot be seen on the first image, increase this number and read until all cells have appeared.",
121
+ self.image_number_label = FixedText(IAW["Image_number"]["label"],
122
+ tip=IAW["Image_number"]["tips"],
75
123
  night_mode=self.parent().po.all['night_mode'])
76
124
  self.image_number_label.setAlignment(QtCore.Qt.AlignVCenter)
77
- self.image_number = Spinbox(min=1, max=self.parent().po.vars['img_number'], val=self.parent().po.all['first_detection_frame'], night_mode=self.parent().po.all['night_mode'])
125
+ self.image_number = Spinbox(min=0, max=self.parent().po.vars['img_number'] - 1, val=self.parent().po.vars['first_detection_frame'], night_mode=self.parent().po.all['night_mode'])
78
126
  self.read = PButton("Read", night_mode=self.parent().po.all['night_mode'])
79
127
  self.read.clicked.connect(self.read_is_clicked)
128
+ if self.parent().po.all["im_or_vid"] == 0 and len(self.parent().po.data_list) == 1:
129
+ # If there is only one image in the folder
130
+ self.image_number.setVisible(False)
131
+ self.image_number_label.setVisible(False)
132
+ self.read.setVisible(False)
80
133
 
81
134
  self.one_blob_per_arena = Checkbox(not self.parent().po.vars['several_blob_per_arena'])
82
135
  self.one_blob_per_arena.stateChanged.connect(self.several_blob_per_arena_check)
83
- self.one_blob_per_arena_label = FixedText("One cell/colony per arena", valign="c",
84
- tip="Check if there is always only one cell/colony per arena.\nUncheck if each experimental arena can contain several disconnected cells/colonies.",
136
+ self.one_blob_per_arena_label = FixedText(IAW["several_blob_per_arena"]["label"], valign="c",
137
+ tip=IAW["several_blob_per_arena"]["tips"],
85
138
  night_mode=self.parent().po.all['night_mode'])
86
139
 
87
140
 
88
- self.scale_with_label = FixedText('Scale with:', valign="c",
89
- tip="What, on the image, should be considered to calculate pixel size in mm",
141
+ self.scale_with_label = FixedText(IAW["Scale_with"]["label"] + ':', valign="c",
142
+ tip=IAW["Scale_with"]["tips"],
90
143
  night_mode=self.parent().po.all['night_mode'])
91
144
  self.scale_with = Combobox(["Image horizontal size", "Cell(s) horizontal size"], night_mode=self.parent().po.all['night_mode'])
92
145
  self.scale_with.setFixedWidth(280)
93
146
  self.scale_with.setCurrentIndex(self.parent().po.all['scale_with_image_or_cells'])
94
- self.scale_size_label = FixedText('Scale size:', valign="c",
95
- tip="True size (in mm) of the item(s) used for scaling",
147
+ self.scale_size_label = FixedText(IAW["Scale_size"]["label"] + ':', valign="c",
148
+ tip=IAW["Scale_size"]["tips"],
96
149
  night_mode=self.parent().po.all['night_mode'])
97
150
  if self.parent().po.all['scale_with_image_or_cells'] == 0:
98
151
  self.horizontal_size = Spinbox(min=0, max=100000,
@@ -122,50 +175,6 @@ class ImageAnalysisWindow(MainTabsType):
122
175
  self.row1_layout.addWidget(self.scale_size_label)
123
176
  self.row1_layout.addWidget(self.horizontal_size)
124
177
 
125
- # self.row1_widget = QtWidgets.QWidget()
126
- # self.row1_layout = QtWidgets.QHBoxLayout()
127
- # self.row1_col1_widget = QtWidgets.QWidget()
128
- # self.row1_col1_layout = QtWidgets.QVBoxLayout()
129
- # self.row1_col2_widget = QtWidgets.QWidget()
130
- # self.row1_col2_layout = QtWidgets.QVBoxLayout()
131
- #
132
- # self.im_number_widget = QtWidgets.QWidget()
133
- # self.im_number_layout = QtWidgets.QHBoxLayout()
134
- # self.im_number_layout.addWidget(self.image_number_label)
135
- # self.im_number_layout.addWidget(self.image_number)
136
- # self.im_number_layout.addWidget(self.read)
137
- # self.im_number_widget.setLayout(self.im_number_layout)
138
- # self.row1_col1_layout.addWidget(self.im_number_widget)
139
- #
140
- # self.specimen_number_widget = QtWidgets.QWidget()
141
- # self.specimen_number_layout = QtWidgets.QHBoxLayout()
142
- # self.specimen_number_layout.addWidget(self.one_blob_per_arena)
143
- # self.specimen_number_layout.addWidget(self.one_blob_per_arena_label)
144
- # self.specimen_number_widget.setLayout(self.specimen_number_layout)
145
- # self.row1_col1_layout.addWidget(self.specimen_number_widget)
146
- # self.row1_col1_widget.setLayout(self.row1_col1_layout)
147
- # self.row1_layout.addWidget(self.row1_col1_widget)
148
- #
149
- # # self.row1_layout.addItem(self.horizontal_space)
150
- # # self.row1_layout.addWidget(self.title_label)
151
- # self.row1_layout.addItem(self.horizontal_space)
152
- #
153
- # self.scale_with_widget = QtWidgets.QWidget()
154
- # self.scale_with_layout = QtWidgets.QHBoxLayout()
155
- # self.scale_with_layout.addWidget(self.scale_with_label)
156
- # self.scale_with_layout.addWidget(self.scale_with)
157
- # self.scale_with_widget.setLayout(self.scale_with_layout)
158
- # self.row1_col2_layout.addWidget(self.scale_with_widget)
159
- #
160
- # self.scale_size_widget = QtWidgets.QWidget()
161
- # self.scale_size_layout = QtWidgets.QHBoxLayout()
162
- # self.scale_size_layout.addWidget(self.scale_size_label)
163
- # self.scale_size_layout.addWidget(self.horizontal_size)
164
- # self.scale_size_widget.setLayout(self.scale_size_layout)
165
- # self.row1_col2_layout.addWidget(self.scale_size_widget)
166
- # self.row1_col2_widget.setLayout(self.row1_col2_layout)
167
- # self.row1_layout.addWidget(self.row1_col2_widget)
168
-
169
178
  self.row1_widget.setLayout(self.row1_layout)
170
179
  self.Vlayout.addItem(self.vertical_space)
171
180
  self.Vlayout.addWidget(self.row1_widget)
@@ -175,22 +184,23 @@ class ImageAnalysisWindow(MainTabsType):
175
184
  # 2) Open the central row layout
176
185
  self.central_row_widget = QtWidgets.QWidget()
177
186
  self.central_row_layout = QtWidgets.QGridLayout()
178
- # self.central_row_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
179
187
 
180
188
  # it will contain a) the user drawn lines, b) the image, c) the csc
181
189
  # 2)a) the user drawn lines
182
190
  self.user_drawn_lines_widget = QtWidgets.QWidget()
183
191
  self.user_drawn_lines_layout = QtWidgets.QVBoxLayout()
184
- self.user_drawn_lines_label = FixedText("Select and draw:",
185
- tip='By holding down mouse button on the image',
192
+ self.user_drawn_lines_label = FixedText(IAW["Select_and_draw"]["label"] + ":",
193
+ tip=IAW["Select_and_draw"]["tips"],
186
194
  night_mode=self.parent().po.all['night_mode'])
187
195
  self.user_drawn_lines_label.setAlignment(QtCore.Qt.AlignHCenter)
188
196
  self.user_drawn_lines_layout.addWidget(self.user_drawn_lines_label)
189
197
  self.pbuttons_widget = QtWidgets.QWidget()
190
198
  self.pbuttons_layout = QtWidgets.QHBoxLayout()
191
- self.cell = PButton("Cell", False, night_mode=self.parent().po.all['night_mode'])
199
+ self.cell = PButton("Cell", False, tip=IAW["Draw_buttons"]["tips"],
200
+ night_mode=self.parent().po.all['night_mode'])
192
201
  self.cell.setFixedWidth(150)
193
- self.background = PButton("Back", False, night_mode=self.parent().po.all['night_mode'])
202
+ self.background = PButton("Back", False, tip=IAW["Draw_buttons"]["tips"],
203
+ night_mode=self.parent().po.all['night_mode'])
194
204
  self.background.setFixedWidth(150)
195
205
  self.cell.clicked.connect(self.cell_is_clicked)
196
206
  self.background.clicked.connect(self.background_is_clicked)
@@ -204,23 +214,14 @@ class ImageAnalysisWindow(MainTabsType):
204
214
  self.pbuttons_tables_layout.setAlignment(QtCore.Qt.AlignHCenter)
205
215
  self.bio_pbuttons_table = QtWidgets.QScrollArea()#QTableWidget() # Scroll Area which contains the widgets, set as the centralWidget
206
216
  self.bio_pbuttons_table.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
207
- # self.bio_pbuttons_table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
208
217
  self.bio_pbuttons_table.setMinimumHeight(self.parent().im_max_height // 2)
209
218
  self.bio_pbuttons_table.setFrameShape(QtWidgets.QFrame.NoFrame)
210
219
  self.bio_pbuttons_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
211
- # self.bio_pbuttons_table.setColumnCount(1)
212
- # self.bio_pbuttons_table.verticalHeader().hide()
213
- # self.bio_pbuttons_table.horizontalHeader().hide()
214
220
  self.back_pbuttons_table = QtWidgets.QScrollArea()#QTableWidget() # Scroll Area which contains the widgets, set as the centralWidget
215
221
  self.back_pbuttons_table.setMinimumHeight(self.parent().im_max_height // 2)
216
222
  self.back_pbuttons_table.setFrameShape(QtWidgets.QFrame.NoFrame)
217
- # self.back_pbuttons_table.setShowGrid(False)
218
223
  self.back_pbuttons_table.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
219
- # self.back_pbuttons_table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
220
224
  self.back_pbuttons_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
221
- # self.back_pbuttons_table.setColumnCount(1)
222
- # self.back_pbuttons_table.verticalHeader().hide()
223
- # self.back_pbuttons_table.horizontalHeader().hide()
224
225
 
225
226
  self.bio_added_lines_widget = QtWidgets.QWidget()
226
227
  self.back_added_lines_widget = QtWidgets.QWidget()
@@ -238,35 +239,17 @@ class ImageAnalysisWindow(MainTabsType):
238
239
  self.pbuttons_tables_widget.setLayout(self.pbuttons_tables_layout)
239
240
  self.user_drawn_lines_layout.addWidget(self.pbuttons_tables_widget)
240
241
 
241
- # self.added_lines_widget = QtWidgets.QWidget()
242
- # self.added_lines_layout = QtWidgets.QHBoxLayout()
243
- # self.bio_added_lines_widget = QtWidgets.QWidget()
244
- # self.bio_added_lines_layout = QtWidgets.QVBoxLayout()
245
- # self.back_added_lines_widget = QtWidgets.QWidget()
246
- # self.back_added_lines_layout = QtWidgets.QVBoxLayout()
247
242
  # # Dynamically add the lines
248
243
  self.bio_lines = {}
249
244
  self.back_lines = {}
250
245
  self.arena_lines = {}
251
- # self.bio_added_lines_widget.setLayout(self.bio_added_lines_layout)
252
- # self.back_added_lines_widget.setLayout(self.back_added_lines_layout)
253
- # self.added_lines_layout.addWidget(self.bio_added_lines_widget)
254
- # self.added_lines_layout.addWidget(self.back_added_lines_widget)
255
- # self.added_lines_widget.setLayout(self.added_lines_layout)
256
- # self.user_drawn_lines_layout.addWidget(self.added_lines_widget)
257
- # self.user_drawn_lines_layout.addItem(self.vertical_space)
258
246
 
259
247
  self.user_drawn_lines_widget.setLayout(self.user_drawn_lines_layout)
260
- # self.user_drawn_lines_widget.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
261
- # self.user_drawn_lines_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
262
248
  self.user_drawn_lines_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
263
- # self.user_drawn_lines_widget.setFixedWidth(450)
264
249
  self.central_row_layout.addWidget(self.user_drawn_lines_widget, 0, 0)
265
250
 
266
251
  # 2)b) the image
267
- # self.central_row_layout.columnStretch(1)
268
252
  self.central_row_layout.addWidget(self.display_image, 0, 1)
269
- # self.central_row_layout.columnStretch(2)
270
253
 
271
254
  # Need to create this before self.generate_csc_editing()
272
255
  self.message = FixedText("", halign="r", night_mode=self.parent().po.all['night_mode'])
@@ -278,52 +261,43 @@ class ImageAnalysisWindow(MainTabsType):
278
261
  self.generate_csc_editing()
279
262
  self.central_right_layout.addWidget(self.edit_widget)
280
263
  self.central_right_widget.setLayout(self.central_right_layout)
281
- # self.central_right_widget.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
282
- # self.central_right_widget.setFixedWidth(450)
283
264
 
284
265
  self.central_row_layout.addWidget(self.central_right_widget, 0, 2)
285
266
  self.central_row_layout.setAlignment(QtCore.Qt.AlignLeft)
286
267
  self.central_row_layout.setAlignment(QtCore.Qt.AlignHCenter)
287
268
  # 2) Close the central row layout
288
269
  self.central_row_widget.setLayout(self.central_row_layout)
289
- # self.central_row_widget.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
290
- # self.central_row_widget.setFixedHeight(self.parent().im_max_height)
291
270
  self.Vlayout.addWidget(self.central_row_widget)
292
- # self.Vlayout.setSpacing(0)
293
271
  self.Vlayout.addItem(self.vertical_space)
294
272
 
295
273
  # 3) Add Set supplementary parameters row 1
296
274
  self.sup_param_row1_widget = QtWidgets.QWidget()
297
275
  self.sup_param_row1_layout = QtWidgets.QHBoxLayout()
298
- # self.sample_number = Spinbox(min=0, max=255, val=self.parent().po.all['first_folder_sample_number'],
299
- # decimals=0, night_mode=self.parent().po.all['night_mode'])
300
- # self.sample_number_label = FixedText("Arena per image", night_mode=self.parent().po.all['night_mode'])
301
- # self.sample_number.valueChanged.connect(self.sample_number_changed)
302
-
303
- #HERE
304
276
 
305
277
  # 4) Add Set supplementary parameters row2
306
278
  self.sup_param_row2_widget = QtWidgets.QWidget()
307
279
  self.sup_param_row2_layout = QtWidgets.QHBoxLayout()
308
280
 
309
- self.arena_shape_label = FixedText("Arena shape", night_mode=self.parent().po.all['night_mode'])
281
+ self.arena_shape_label = FixedText(IAW["Arena_shape"]["label"], tip=IAW["Arena_shape"]["tips"],
282
+ night_mode=self.parent().po.all['night_mode'])
310
283
  self.arena_shape = Combobox(['circle', 'rectangle'], night_mode=self.parent().po.all['night_mode'])
311
284
  self.arena_shape.setFixedWidth(160)
312
285
  self.arena_shape.setCurrentText(self.parent().po.vars['arena_shape'])
313
286
  self.arena_shape.currentTextChanged.connect(self.arena_shape_changed)
314
287
  self.set_spot_shape = Checkbox(self.parent().po.all['set_spot_shape'])
315
288
  self.set_spot_shape.stateChanged.connect(self.set_spot_shape_check)
316
- self.spot_shape_label = FixedText("Set spot shape", tip="horizontal size in mm", night_mode=self.parent().po.all['night_mode'])
289
+ self.spot_shape_label = FixedText(IAW["Spot_shape"]["label"], tip=IAW["Spot_shape"]["tips"], night_mode=self.parent().po.all['night_mode'])
317
290
  self.spot_shape = Combobox(['circle', 'rectangle'], night_mode=self.parent().po.all['night_mode'])
318
291
  self.spot_shape.setFixedWidth(160)
319
292
  if self.parent().po.all['starting_blob_shape'] is None:
320
- self.spot_shape.setCurrentText('circle')
293
+ self.spot_shape.setCurrentIndex(0)
321
294
  else:
322
295
  self.spot_shape.setCurrentText(self.parent().po.all['starting_blob_shape'])
323
296
  self.spot_shape.currentTextChanged.connect(self.spot_shape_changed)
324
297
  self.set_spot_size = Checkbox(self.parent().po.all['set_spot_size'])
325
298
  self.set_spot_size.stateChanged.connect(self.set_spot_size_check)
326
- self.spot_size_label = FixedText("Set spot size", night_mode=self.parent().po.all['night_mode'])
299
+ self.spot_size_label = FixedText(IAW["Spot_size"]["label"], tip=IAW["Spot_size"]["tips"],
300
+ night_mode=self.parent().po.all['night_mode'])
327
301
  self.spot_size = Spinbox(min=0, max=100000, val=self.parent().po.all['starting_blob_hsize_in_mm'], decimals=2,
328
302
  night_mode=self.parent().po.all['night_mode'])
329
303
  self.spot_size.valueChanged.connect(self.spot_size_changed)
@@ -341,8 +315,6 @@ class ImageAnalysisWindow(MainTabsType):
341
315
  self.Vlayout.addWidget(self.sup_param_row2_widget)
342
316
  self.Vlayout.setSpacing(0)
343
317
 
344
- # self.sample_number.setVisible(False)
345
- # self.sample_number_label.setVisible(False)
346
318
  self.one_blob_per_arena.setVisible(True)
347
319
  self.one_blob_per_arena_label.setVisible(True)
348
320
  self.set_spot_shape.setVisible(False)
@@ -355,12 +327,15 @@ class ImageAnalysisWindow(MainTabsType):
355
327
  self.spot_size.setVisible(False)
356
328
 
357
329
  # 5) Add the generate option row
358
- self.generate_analysis_options = FixedText("Generate analysis options: ", night_mode=self.parent().po.all['night_mode'])
359
- self.quickly = PButton("Quickly", night_mode=self.parent().po.all['night_mode'])
360
- self.carefully = PButton("Carefully", night_mode=self.parent().po.all['night_mode'])
361
- self.quickly.clicked.connect(self.quickly_is_clicked)
362
- self.carefully.clicked.connect(self.carefully_is_clicked)
363
- self.visualize = PButton('Visualize', night_mode=self.parent().po.all['night_mode'])
330
+ self.generate_analysis_options = FixedText(IAW["Generate_analysis_options"]["label"] + ": ",
331
+ tip=IAW["Generate_analysis_options"]["tips"],
332
+ night_mode=self.parent().po.all['night_mode'])
333
+ self.basic = PButton("Basic", night_mode=self.parent().po.all['night_mode'])
334
+ self.basic.clicked.connect(self.basic_is_clicked)
335
+ self.network_shaped = PButton("Network-shaped", night_mode=self.parent().po.all['night_mode'])
336
+ self.network_shaped.clicked.connect(self.network_shaped_is_clicked)
337
+ self.network_shaped.setVisible(False)
338
+ self.visualize = PButton('Apply current config', night_mode=self.parent().po.all['night_mode'])
364
339
  self.visualize.clicked.connect(self.visualize_is_clicked)
365
340
  if self.parent().po.vars['already_greyscale']:
366
341
  self.visualize_label = FixedText("Directly: ", night_mode=self.parent().po.all['night_mode'])
@@ -368,11 +343,9 @@ class ImageAnalysisWindow(MainTabsType):
368
343
  self.visualize_label = FixedText("Or directly: ", night_mode=self.parent().po.all['night_mode'])
369
344
 
370
345
  self.sup_param_row1_layout.addWidget(self.generate_analysis_options)
371
- self.sup_param_row1_layout.addWidget(self.quickly)
372
- self.sup_param_row1_layout.addWidget(self.carefully)
346
+ self.sup_param_row1_layout.addWidget(self.basic)
347
+ self.sup_param_row1_layout.addWidget(self.network_shaped)
373
348
  self.sup_param_row1_layout.addItem(self.horizontal_space)
374
- # self.sup_param_row1_layout.addWidget(self.sample_number)
375
- # self.sup_param_row1_layout.addWidget(self.sample_number_label)
376
349
  self.sup_param_row1_layout.addItem(self.horizontal_space)
377
350
  self.sup_param_row1_layout.addWidget(self.visualize_label)
378
351
  self.sup_param_row1_layout.addWidget(self.visualize)
@@ -384,13 +357,13 @@ class ImageAnalysisWindow(MainTabsType):
384
357
  # 6) Open the choose best option row layout
385
358
  self.options_row_widget = QtWidgets.QWidget()
386
359
  self.options_row_layout = QtWidgets.QHBoxLayout()
387
- self.select_option_label = FixedText('Select option to read', tip='Select the option allowing the best segmentation between the cell and the background',
360
+ self.select_option_label = FixedText(IAW["Select_option_to_read"]["label"],
361
+ tip=IAW["Select_option_to_read"]["tips"],
388
362
  night_mode=self.parent().po.all['night_mode'])
389
363
  self.select_option = Combobox([], night_mode=self.parent().po.all['night_mode'])
390
364
  if self.parent().po.vars['color_number'] == 2:
391
365
  self.select_option.setCurrentIndex(self.parent().po.all['video_option'])
392
366
  self.select_option.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
393
- # self.select_option.setFixedWidth(120)
394
367
  self.select_option.setMinimumWidth(145)
395
368
  self.select_option.currentTextChanged.connect(self.option_changed)
396
369
  self.n_shapes_detected = FixedText(f'', night_mode=self.parent().po.all['night_mode'])
@@ -431,7 +404,9 @@ class ImageAnalysisWindow(MainTabsType):
431
404
  self.special_cases_layout = QtWidgets.QHBoxLayout()
432
405
  self.starting_differs_from_growing_cb = Checkbox(self.parent().po.vars['origin_state'] == 'constant')
433
406
  self.starting_differs_from_growing_cb.stateChanged.connect(self.starting_differs_from_growing_check)
434
- self.starting_differs_from_growing_label = FixedText("Check if the starting area differs from the growing area", tip="This option is only relevant for experiments in which the medium\n(e.g. agar) on which the cells grow is heterogeneous.\nMore precisely when the exploration areas on which the cells will grow and/or move\nare not the same color as the one they were initially on.", night_mode=self.parent().po.all['night_mode'])
407
+ self.starting_differs_from_growing_label = FixedText(IAW["Start_differs_from_arena"]["label"],
408
+ tip=IAW["Start_differs_from_arena"]["tips"],
409
+ night_mode=self.parent().po.all['night_mode'])
435
410
  self.starting_differs_from_growing_cb.setVisible(False)
436
411
  self.starting_differs_from_growing_label.setVisible(False)
437
412
  self.special_cases_layout.addWidget(self.starting_differs_from_growing_cb)
@@ -448,12 +423,18 @@ class ImageAnalysisWindow(MainTabsType):
448
423
  self.previous.clicked.connect(self.previous_is_clicked)
449
424
  self.data_tab.clicked.connect(self.data_is_clicked)
450
425
  self.video_tab.clicked.connect(self.video_is_clicked)
426
+ self.complete_image_analysis = PButton(IAW["Save_image_analysis"]["label"],
427
+ tip=IAW["Save_image_analysis"]["tips"],
428
+ night_mode=self.parent().po.all['night_mode'])
429
+ self.complete_image_analysis.setVisible(False)
430
+ self.complete_image_analysis.clicked.connect(self.complete_image_analysis_is_clicked)
451
431
  self.next = PButton("Next", night_mode=self.parent().po.all['night_mode'])
452
432
  self.next.setVisible(False)
453
433
  self.next.clicked.connect(self.go_to_next_widget)
454
434
  self.last_row_layout.addWidget(self.previous)
455
435
  self.last_row_layout.addWidget(self.message)
456
436
  self.last_row_layout.addItem(self.horizontal_space)
437
+ self.last_row_layout.addWidget(self.complete_image_analysis)
457
438
  self.last_row_layout.addWidget(self.next)
458
439
  self.last_row_widget.setLayout(self.last_row_layout)
459
440
  self.Vlayout.addWidget(self.last_row_widget)
@@ -464,8 +445,6 @@ class ImageAnalysisWindow(MainTabsType):
464
445
 
465
446
  self.thread = {}
466
447
  self.thread["GetFirstIm"] = GetFirstImThread(self.parent())
467
- # self.thread["GetFirstIm"].start()
468
- # self.thread["GetFirstIm"].message_when_thread_finished.connect(self.first_im_read)
469
448
  self.reinitialize_image_and_masks(self.parent().po.first_im)
470
449
  self.thread["GetLastIm"] = GetLastImThread(self.parent())
471
450
  self.thread["GetLastIm"].start()
@@ -475,9 +454,17 @@ class ImageAnalysisWindow(MainTabsType):
475
454
  self.thread['UpdateImage'] = UpdateImageThread(self.parent())
476
455
  self.thread['CropScaleSubtractDelineate'] = CropScaleSubtractDelineateThread(self.parent())
477
456
  self.thread['SaveManualDelineation'] = SaveManualDelineationThread(self.parent())
478
- self.thread['FinalizeImageAnalysis'] = FinalizeImageAnalysisThread(self.parent())
457
+ self.thread['CompleteImageAnalysisThread'] = CompleteImageAnalysisThread(self.parent())
458
+ self.thread['PrepareVideoAnalysis'] = PrepareVideoAnalysisThread(self.parent())
479
459
 
480
460
  def previous_is_clicked(self):
461
+ """
462
+ Handles the logic for when a "Previous" button is clicked in the interface, leading to the FirstWindow.
463
+
464
+ This method resets various flags and variables related to image analysis
465
+ to their initial state. It is called when the "Previous" button is clicked,
466
+ preparing the application for new input and reinitialization.
467
+ """
481
468
  if self.is_image_analysis_running:
482
469
  self.message.setText("Wait for the analysis to end, or restart Cellects")
483
470
  else:
@@ -508,6 +495,15 @@ class ImageAnalysisWindow(MainTabsType):
508
495
  self.parent().change_widget(0) # First
509
496
 
510
497
  def data_is_clicked(self):
498
+ """
499
+ Handles the logic for when the "Data specifications" button is clicked in the interface,
500
+ leading to the FirstWindow.
501
+
502
+ Notes
503
+ -----
504
+ This function displays an error message when a thread relative to the current window is running.
505
+ This function also save the id of this tab for later use.
506
+ """
511
507
  if self.is_image_analysis_running:
512
508
  self.message.setText("Wait for the analysis to end, or restart Cellects")
513
509
  else:
@@ -515,7 +511,15 @@ class ImageAnalysisWindow(MainTabsType):
515
511
  self.parent().change_widget(0) # First
516
512
 
517
513
  def video_is_clicked(self):
514
+ """
515
+ Handles the logic for when the "Video tracking" button is clicked in the interface,
516
+ leading to the video analysis window.
518
517
 
518
+ Notes
519
+ -----
520
+ This function displays an error message when a thread relative to the current window is running.
521
+ This function also save the id of the following window for later use.
522
+ """
519
523
  if self.video_tab.state != "not_usable":
520
524
  if self.is_image_analysis_running:
521
525
  self.message.setText("Wait for the analysis to end, or restart Cellects")
@@ -524,16 +528,26 @@ class ImageAnalysisWindow(MainTabsType):
524
528
  self.parent().change_widget(3)
525
529
 
526
530
  def read_is_clicked(self):
531
+ """
532
+ Read an image (numbered using natural sorting) from the selected folder
533
+
534
+ This method handles the logic for starting image reading when the "Read" button is clicked.
535
+ It ensures that only one thread runs at a time, updates the UI with relevant messages,
536
+ and resets visual components once processing begins.
537
+ """
527
538
  if not self.thread["GetFirstIm"].isRunning():
528
- self.parent().po.all['first_detection_frame'] = int(self.image_number.value())
529
- self.message.setText(f"Reading image n°{self.parent().po.all['first_detection_frame']}")
539
+ self.parent().po.vars['first_detection_frame'] = int(self.image_number.value())
540
+ self.message.setText(f"Reading image n°{self.parent().po.vars['first_detection_frame']}")
530
541
  self.thread["GetFirstIm"].start()
531
- # self.thread["GetFirstIm"].message_when_thread_finished.connect(self.reinitialize_image_and_masks)
542
+ self.thread["GetFirstIm"].message_when_thread_finished.connect(self.reinitialize_image_and_masks)
532
543
  self.reinitialize_bio_and_back_legend()
533
544
  self.reinitialize_image_and_masks(self.parent().po.first_im)
534
-
545
+
535
546
 
536
547
  def several_blob_per_arena_check(self):
548
+ """
549
+ Checks or unchecks the option for having several blobs per arena.
550
+ """
537
551
  is_checked = self.one_blob_per_arena.isChecked()
538
552
  self.parent().po.vars['several_blob_per_arena'] = not is_checked
539
553
  self.set_spot_size.setVisible(is_checked)
@@ -541,18 +555,27 @@ class ImageAnalysisWindow(MainTabsType):
541
555
  self.spot_size.setVisible(is_checked and self.set_spot_size.isChecked())
542
556
 
543
557
  def set_spot_size_check(self):
558
+ """
559
+ Set the visibility of spot size based on checkbox state.
560
+ """
544
561
  is_checked = self.set_spot_size.isChecked()
545
562
  if self.step == 1:
546
563
  self.spot_size.setVisible(is_checked)
547
564
  self.parent().po.all['set_spot_size'] = is_checked
548
565
 
549
566
  def spot_size_changed(self):
567
+ """
568
+ Update the starting blob size and corresponding horizontal size based on user input.
569
+ """
550
570
  self.parent().po.all['starting_blob_hsize_in_mm'] = self.spot_size.value()
551
571
  if self.parent().po.all['scale_with_image_or_cells'] == 1:
552
572
  self.horizontal_size.setValue(self.parent().po.all['starting_blob_hsize_in_mm'])
553
573
  self.set_spot_size_check()
554
574
 
555
575
  def set_spot_shape_check(self):
576
+ """
577
+ Set the spot shape setting visibility.
578
+ """
556
579
  is_checked = self.set_spot_shape.isChecked()
557
580
  self.spot_shape.setVisible(is_checked)
558
581
  self.parent().po.all['set_spot_shape'] = is_checked
@@ -560,10 +583,27 @@ class ImageAnalysisWindow(MainTabsType):
560
583
  self.parent().po.all['starting_blob_shape'] = None
561
584
 
562
585
  def spot_shape_changed(self):
586
+ """
587
+ Save the user selection of shape.
588
+ """
563
589
  self.parent().po.all['starting_blob_shape'] = self.spot_shape.currentText()
564
590
  self.set_spot_shape_check()
565
591
 
566
592
  def arena_shape_changed(self):
593
+ """
594
+ Calculate and update the arena shape in response to user input and manage threading operations.
595
+
596
+ Extended Description
597
+ --------------------
598
+ This method updates the arena shape variable based on user selection from a dropdown menu.
599
+ It ensures that certain background threading operations are completed before proceeding with updates
600
+ and reinitializes necessary components to reflect the new arena shape.
601
+
602
+ Notes
603
+ -----
604
+ This method handles threading operations to ensure proper synchronization and updates.
605
+ It reinitializes the biological legend, image, and masks when the arena shape is changed.
606
+ """
567
607
  self.parent().po.vars['arena_shape'] = self.arena_shape.currentText()
568
608
  if self.asking_delineation_flag:
569
609
  if self.thread['CropScaleSubtractDelineate'].isRunning():
@@ -582,9 +622,14 @@ class ImageAnalysisWindow(MainTabsType):
582
622
  self.thread["UpdateImage"].start()
583
623
  self.thread["UpdateImage"].message_when_thread_finished.connect(self.automatic_delineation_display_done)
584
624
 
585
- # self.start_crop_scale_subtract_delineate()
586
-
587
625
  def reinitialize_bio_and_back_legend(self):
626
+ """
627
+ Reinitialize the bio and back legend.
628
+
629
+ Reinitializes the bio and back legends, removing all existing lines
630
+ and resetting counters for masks. This function ensures that the UI
631
+ components associated with bio and back lines are correctly cleaned up.
632
+ """
588
633
  lines_names_to_remove = []
589
634
  for line_number, back_line_dict in self.back_lines.items():
590
635
  line_name = u"\u00D7" + " Back" + str(line_number)
@@ -616,13 +661,22 @@ class ImageAnalysisWindow(MainTabsType):
616
661
  self.bio_masks_number = 0
617
662
  self.back_masks_number = 0
618
663
 
619
- def reinitialize_image_and_masks(self, image):
664
+ def reinitialize_image_and_masks(self, image: np.ndarray):
665
+ """
666
+ Reinitialize the image and masks for analysis.
667
+
668
+ This method reinitializes the current image and its associated masks
669
+ used in the analysis process. It checks if the input image is grayscale
670
+ and converts it to a 3-channel RGB image, stacking identical channels.
671
+ It also updates the visibility of various UI components based on
672
+ the image type and reinitializes masks to prepare for new analysis.
673
+ """
620
674
  if len(image.shape) == 2:
621
675
  self.parent().po.current_image = np.stack((image, image, image), axis=2)
622
676
 
623
677
  self.generate_analysis_options.setVisible(False)
624
- self.quickly.setVisible(False)
625
- self.carefully.setVisible(False)
678
+ self.network_shaped.setVisible(False)
679
+ self.basic.setVisible(False)
626
680
  self.select_option.setVisible(False)
627
681
  self.select_option_label.setVisible(False)
628
682
  self.visualize.setVisible(True)
@@ -631,12 +685,14 @@ class ImageAnalysisWindow(MainTabsType):
631
685
  self.parent().po.current_image = deepcopy(image)
632
686
  self.drawn_image = deepcopy(self.parent().po.current_image)
633
687
  self.display_image.update_image(self.parent().po.current_image)
634
-
635
688
  self.arena_mask = None
636
689
  self.bio_mask = np.zeros(self.parent().po.current_image.shape[:2], dtype=np.uint16)
637
690
  self.back_mask = np.zeros(self.parent().po.current_image.shape[:2], dtype=np.uint16)
638
691
 
639
692
  def scale_with_changed(self):
693
+ """
694
+ Modifies how the image scale is computed: using the image width or the blob unitary size (horizontal diameter).
695
+ """
640
696
  self.parent().po.all['scale_with_image_or_cells'] = self.scale_with.currentIndex()
641
697
  if self.parent().po.all['scale_with_image_or_cells'] == 0:
642
698
  self.horizontal_size.setValue(self.parent().po.all['image_horizontal_size_in_mm'])
@@ -644,6 +700,9 @@ class ImageAnalysisWindow(MainTabsType):
644
700
  self.horizontal_size.setValue(self.parent().po.all['starting_blob_hsize_in_mm'])
645
701
 
646
702
  def horizontal_size_changed(self):
703
+ """
704
+ Changes the horizontal size value of the image or of the blobs in the image, depending on user's choice.
705
+ """
647
706
  if self.parent().po.all['scale_with_image_or_cells'] == 0:
648
707
  self.parent().po.all['image_horizontal_size_in_mm'] = self.horizontal_size.value()
649
708
  else:
@@ -651,6 +710,12 @@ class ImageAnalysisWindow(MainTabsType):
651
710
  self.spot_size.setValue(self.parent().po.all['starting_blob_hsize_in_mm'])
652
711
 
653
712
  def advanced_mode_check(self):
713
+ """
714
+ Update widget visibility based on advanced mode check.
715
+
716
+ This function updates the visbility of various UI elements depending on
717
+ the state of the advanced mode check box and other conditions.
718
+ """
654
719
  is_checked = self.advanced_mode_cb.isChecked()
655
720
  color_analysis = is_checked and not self.parent().po.vars['already_greyscale']
656
721
  self.parent().po.all['expert_mode'] = is_checked
@@ -668,16 +733,10 @@ class ImageAnalysisWindow(MainTabsType):
668
733
  self.first_im_parameters_answered = True
669
734
 
670
735
  self.space_label.setVisible(color_analysis)
671
- # self.c1.setVisible(color_analysis)
672
- # self.c2.setVisible(color_analysis)
673
- # self.c3.setVisible(color_analysis)
674
736
  display_logical = self.logical_operator_between_combination_result.currentText() != 'None'
675
737
  self.logical_operator_between_combination_result.setVisible(color_analysis and display_logical)
676
738
  self.logical_operator_label.setVisible(color_analysis and display_logical)
677
739
 
678
- # if not self.parent().po.vars['already_greyscale']:
679
- # self.visualize.setVisible(is_checked)
680
- # self.visualize_label.setVisible(is_checked)
681
740
  at_least_one_line_drawn = self.bio_masks_number > 0
682
741
  self.more_than_two_colors.setVisible(is_checked and at_least_one_line_drawn)
683
742
  self.more_than_two_colors_label.setVisible(is_checked and at_least_one_line_drawn)
@@ -694,21 +753,23 @@ class ImageAnalysisWindow(MainTabsType):
694
753
  self.filter1_param2_label.setVisible(has_param2)
695
754
 
696
755
  # Check whether filter 2 and its potential parameters should be visible
697
- self.filter2.setVisible(is_checked and at_least_one_line_drawn)
698
- self.filter2_label.setVisible(is_checked and at_least_one_line_drawn)
699
- has_param1 = is_checked and at_least_one_line_drawn and 'Param1' in filter_dict[self.filter2.currentText()]
756
+ self.filter2.setVisible(is_checked and display_logical)
757
+ self.filter2_label.setVisible(is_checked and display_logical)
758
+ has_param1 = is_checked and display_logical and 'Param1' in filter_dict[self.filter2.currentText()]
700
759
  self.filter2_param1.setVisible(has_param1)
701
760
  self.filter2_param1_label.setVisible(has_param1)
702
- has_param2 = is_checked and at_least_one_line_drawn and 'Param2' in filter_dict[self.filter2.currentText()]
761
+ has_param2 = is_checked and display_logical and 'Param2' in filter_dict[self.filter2.currentText()]
703
762
  self.filter2_param2.setVisible(has_param2)
704
763
  self.filter2_param2_label.setVisible(has_param2)
705
764
 
706
- self.filter1_param1.setVisible(is_checked)
707
- self.grid_segmentation.setVisible(is_checked)
708
- self.grid_segmentation_label.setVisible(is_checked)
765
+ self.rolling_window_segmentation.setVisible(is_checked)
766
+ self.rolling_window_segmentation_label.setVisible(is_checked)
709
767
 
710
768
  for i in range(5):
711
- self.row1[i].setVisible(color_analysis and self.row1[0].currentText() != "None")
769
+ if i == 0:
770
+ self.row1[i].setVisible(color_analysis)
771
+ else:
772
+ self.row1[i].setVisible(color_analysis and not "PCA" in self.csc_dict)
712
773
  self.row21[i].setVisible(color_analysis and self.row21[0].currentText() != "None")
713
774
  self.row2[i].setVisible(color_analysis and self.row2[0].currentText() != "None")
714
775
  self.row22[i].setVisible(color_analysis and self.row22[0].currentText() != "None")
@@ -716,7 +777,7 @@ class ImageAnalysisWindow(MainTabsType):
716
777
  self.row3[i].setVisible(color_analysis and self.row3[0].currentText() != "None")
717
778
  self.row23[i].setVisible(color_analysis and self.row23[0].currentText() != "None")
718
779
  if color_analysis:
719
- if self.row1[0].currentText() != "None":
780
+ if self.row1[0].currentText() != "PCA":
720
781
  if self.row2[0].currentText() == "None":
721
782
  self.row1[4].setVisible(True)
722
783
  else:
@@ -733,6 +794,10 @@ class ImageAnalysisWindow(MainTabsType):
733
794
  self.row22[4].setVisible(False)
734
795
 
735
796
  def cell_is_clicked(self):
797
+ """
798
+ Handles the logic for when a "cell" button is clicked in the interface,
799
+ allowing the user to draw cells on the image.
800
+ """
736
801
  if self.back1_bio2 == 2:
737
802
  self.cell.night_mode_switch(night_mode=self.parent().po.all['night_mode'])
738
803
  self.back1_bio2 = 0
@@ -743,6 +808,10 @@ class ImageAnalysisWindow(MainTabsType):
743
808
  self.saved_coord = []
744
809
 
745
810
  def background_is_clicked(self):
811
+ """
812
+ Handles the logic for when a "back" button is clicked in the interface,
813
+ allowing the user to draw where there is background on the image.
814
+ """
746
815
  if self.back1_bio2 == 1:
747
816
  self.background.night_mode_switch(night_mode=self.parent().po.all['night_mode'])
748
817
  self.back1_bio2 = 0
@@ -753,6 +822,18 @@ class ImageAnalysisWindow(MainTabsType):
753
822
  self.saved_coord = []
754
823
 
755
824
  def get_click_coordinates(self, event):
825
+ """
826
+ Handle mouse click events to capture coordinate data or display an image.
827
+
828
+ This function determines the handling of click events based on various
829
+ flags and states, including whether image analysis is running or if a
830
+ manual delineation flag is set.
831
+
832
+ Parameters
833
+ ----------
834
+ event : QMouseEvent
835
+ The mouse event that triggered the function.
836
+ """
756
837
  if self.back1_bio2 > 0 or self.manual_delineation_flag:
757
838
  if not self.is_image_analysis_display_running and not self.thread["UpdateImage"].isRunning():
758
839
  self.hold_click_flag = True
@@ -760,18 +841,16 @@ class ImageAnalysisWindow(MainTabsType):
760
841
  else:
761
842
  self.popup_img = FullScreenImage(self.drawn_image, self.parent().screen_width, self.parent().screen_height)
762
843
  self.popup_img.show()
763
- # img = resize(self.drawn_image, (self.parent().screen_width, self.parent().screen_height))
764
- # cv2.imshow("Full screen image display", img)
765
- # waitKey(0)
766
- # destroyAllWindows()
767
844
 
768
845
  def get_mouse_move_coordinates(self, event):
769
- # if not self.is_image_analysis_display_running:
770
- # if self.back1_bio2 > 0 or self.manual_delineation_flag:
771
- # if not self.thread["UpdateImage"].isRunning() and len(self.saved_coord) > 0:
772
- # if self.saved_coord[0][0] != event.pos().y() and self.saved_coord[0][1] != event.pos().x():
773
- # self.temporary_mask_coord = [self.saved_coord[0], [event.pos().y(), event.pos().x()]]
774
- # self.thread["UpdateImage"].start()
846
+ """
847
+ Handles mouse movement events to update the temporary mask coordinate.
848
+
849
+ Parameters
850
+ ----------
851
+ event : QMouseEvent
852
+ The mouse event object containing position information.
853
+ """
775
854
  if self.hold_click_flag:
776
855
  if not self.thread["UpdateImage"].isRunning():
777
856
  if self.saved_coord[0][0] != event.pos().y() and self.saved_coord[0][1] != event.pos().x():
@@ -779,13 +858,27 @@ class ImageAnalysisWindow(MainTabsType):
779
858
  self.thread["UpdateImage"].start()
780
859
 
781
860
  def get_mouse_release_coordinates(self, event):
861
+ """
862
+ Process mouse release event to save coordinates and manage image update thread.
863
+
864
+ This method handles the logic for saving mouse release coordinates during
865
+ manual delineation, checks conditions to prevent exceeding the number of arenas,
866
+ and manages an image update thread for display purposes.
867
+
868
+ Parameters
869
+ ----------
870
+ event : QMouseEvent
871
+ The mouse event containing the release position.
872
+
873
+ Notes
874
+ -----
875
+ This method requires an active image update thread and assumes certain attributes
876
+ like `hold_click_flag`, `manual_delineation_flag`, etc., are part of the class
877
+ state.
878
+ """
782
879
  if self.hold_click_flag:
783
880
  if self.thread["UpdateImage"].isRunning():
784
881
  self.thread["UpdateImage"].wait()
785
- # self.saved_coord = []
786
- # self.background.night_mode_switch(night_mode=self.parent().po.all['night_mode'])
787
- # self.background.night_mode_switch(night_mode=self.parent().po.all['night_mode'])
788
- # self.back1_bio2 = 0
789
882
  self.temporary_mask_coord = []
790
883
  if self.manual_delineation_flag and len(self.parent().imageanalysiswindow.available_arena_names) == 0:
791
884
  self.message.setText(f"The total number of arenas are already drawn ({self.parent().po.sample_number})")
@@ -796,22 +889,19 @@ class ImageAnalysisWindow(MainTabsType):
796
889
  self.thread["UpdateImage"].message_when_thread_finished.connect(self.user_defined_shape_displayed)
797
890
  self.hold_click_flag = False
798
891
 
892
+ def user_defined_shape_displayed(self, when_finished: bool):
893
+ """
894
+ Display user-defined shapes or elements based on specific conditions and update the UI accordingly.
895
+
896
+ Parameters
897
+ ----------
898
+ when_finished : bool
899
+ A flag indicating whether a certain operation has finished.
799
900
 
800
- # if not self.is_image_analysis_display_running:
801
- # if self.back1_bio2 > 0 or self.manual_delineation_flag:
802
- # if len(self.saved_coord) > 0 and self.saved_coord[0][0] != event.pos().y() and self.saved_coord[0][1] != event.pos().x():
803
- # if self.thread["UpdateImage"].isRunning():
804
- # self.thread["UpdateImage"].wait()
805
- # self.temporary_mask_coord = []
806
- # if self.manual_delineation_flag and len(self.parent().imageanalysiswindow.available_arena_names) == 0:
807
- # self.message.setText(f"The total number of arenas are already drawn ({self.parent().po.sample_number})")
808
- # self.saved_coord = []
809
- # else:
810
- # self.saved_coord.append([event.pos().y(), event.pos().x()])
811
- # self.thread["UpdateImage"].start()
812
- # self.thread["UpdateImage"].message_when_thread_finished.connect(self.user_defined_shape_displayed)
813
-
814
- def user_defined_shape_displayed(self, boole):
901
+ Notes
902
+ -----
903
+ This method modifies the user interface by adding buttons and updating layouts based on the current state and conditions.
904
+ """
815
905
  if self.back1_bio2 == 1:
816
906
  back_name = self.parent().imageanalysiswindow.available_back_names[0]
817
907
  self.back_lines[back_name] = {}
@@ -846,12 +936,19 @@ class ImageAnalysisWindow(MainTabsType):
846
936
  self.available_arena_names = self.available_arena_names[1:]
847
937
  self.saved_coord = []
848
938
  self.back1_bio2 = 0
849
- try:
850
- self.thread["UpdateImage"].message_when_thread_finished.disconnect()
851
- except RuntimeError:
852
- pass
853
939
 
854
- def new_pbutton_on_the_left(self, pbutton_name):
940
+ self.thread["UpdateImage"].message_when_thread_finished.disconnect()
941
+
942
+ def new_pbutton_on_the_left(self, pbutton_name: str):
943
+ """
944
+ Create a styled PButton instance positioned on the left of the image.
945
+
946
+ Notes
947
+ -----
948
+ The button's appearance is customized based on the value of
949
+ `self.back1_bio2`, which affects its color. The button also has a fixed
950
+ size and specific font settings.
951
+ """
855
952
  pbutton = PButton(pbutton_name, False, night_mode=self.parent().po.all['night_mode'])
856
953
  pbutton.setFixedHeight(20)
857
954
  pbutton.setFixedWidth(100)
@@ -869,6 +966,13 @@ class ImageAnalysisWindow(MainTabsType):
869
966
  return pbutton
870
967
 
871
968
  def remove_line(self):
969
+ """
970
+ Remove the specified line from the image analysis display.
971
+
972
+ This method removes a line identified by its button name from the appropriate mask
973
+ and updates the layout and available names accordingly. It starts the image update thread
974
+ after removing the line.
975
+ """
872
976
  if not self.is_image_analysis_display_running and not self.thread["UpdateImage"].isRunning() and hasattr(self.sender(), 'text'):
873
977
  pbutton_name = self.sender().text()
874
978
  if pbutton_name[2:6] == "Back":
@@ -879,7 +983,6 @@ class ImageAnalysisWindow(MainTabsType):
879
983
  self.back_lines.pop(line_name)
880
984
  self.back_masks_number -= 1
881
985
  self.available_back_names = np.sort(np.concatenate(([line_name], self.available_back_names)))
882
- # self.back_pbuttons_table.removeRow(line_name - 1)
883
986
  elif pbutton_name[2:6] == "Cell":
884
987
  line_name = np.uint8(pbutton_name[6:])
885
988
  self.bio_mask[self.bio_mask == line_name] = 0
@@ -888,7 +991,6 @@ class ImageAnalysisWindow(MainTabsType):
888
991
  self.bio_lines.pop(line_name)
889
992
  self.bio_masks_number -= 1
890
993
  self.available_bio_names = np.sort(np.concatenate(([line_name], self.available_bio_names)))
891
- # self.bio_pbuttons_table.removeRow(line_name - 1)
892
994
  self.display_more_than_two_colors_option()
893
995
  else:
894
996
  line_name = np.uint8(pbutton_name[7:])
@@ -902,113 +1004,143 @@ class ImageAnalysisWindow(MainTabsType):
902
1004
 
903
1005
  self.arena_masks_number -= 1
904
1006
  self.available_arena_names = np.sort(np.concatenate(([line_name], self.available_arena_names)))
905
- # if line_name % 2 == 1:
906
- # self.bio_pbuttons_table.removeRow((line_name + 1) // 2)
907
- # else:
908
- # self.back_pbuttons_table.removeRow((line_name + 1) // 2)
909
- # if self.parent().po.first_image.im_combinations is not None:
910
- # if self.thread["UpdateImage"].isRunning():
911
- # self.thread["UpdateImage"].wait()
912
1007
  self.thread["UpdateImage"].start()
913
1008
 
914
- def quickly_is_clicked(self):
1009
+ def network_shaped_is_clicked(self):
1010
+ """
1011
+ Sets the GUI state for analyzing a network-shaped image when clicked.
1012
+
1013
+ This method triggers the analysis process for a network-shaped image. It ensures that image analysis is not
1014
+ already running, updates GUI elements accordingly, and starts the appropriate analysis function based on a flag.
1015
+ """
915
1016
  if not self.is_image_analysis_running:
916
1017
  self.is_image_analysis_running = True
917
1018
  self.message.setText('Loading, wait...')
918
- self.parent().po.carefully = False
919
1019
  self.parent().po.visualize = False
1020
+ self.parent().po.basic = False
1021
+ self.parent().po.network_shaped = True
920
1022
  if self.is_first_image_flag:
921
1023
  self.run_first_image_analysis()
922
1024
  else:
923
1025
  self.run_last_image_analysis()
924
1026
 
925
- def carefully_is_clicked(self):
1027
+ def basic_is_clicked(self):
1028
+ """
1029
+ Toggle image analysis mode and trigger appropriate image analysis process.
1030
+
1031
+ This method enables the image analysis mode, sets a loading message,
1032
+ and initiates either the first or last image analysis based on
1033
+ the current state.
1034
+ """
926
1035
  if not self.is_image_analysis_running:
927
1036
  self.is_image_analysis_running = True
928
1037
  self.message.setText('Loading, wait...')
929
- self.parent().po.carefully = True
930
1038
  self.parent().po.visualize = False
1039
+ self.parent().po.basic = True
1040
+ self.parent().po.network_shaped = False
931
1041
  if self.is_first_image_flag:
932
1042
  self.run_first_image_analysis()
933
1043
  else:
934
1044
  self.run_last_image_analysis()
935
1045
 
936
1046
  def visualize_is_clicked(self):
1047
+ """
1048
+ Instructs the system to perform an image analysis and updates the UI accordingly.
1049
+
1050
+ If image analysis is not currently running, this method triggers the analysis process
1051
+ and updates the UI message to indicate loading.
1052
+ """
937
1053
  if not self.is_image_analysis_running:
938
1054
  self.is_image_analysis_running = True
939
1055
  self.message.setText('Loading, wait...')
940
1056
  self.parent().po.visualize = True
941
- # if self.step == 0:
942
- # self.select_option_label.setVisible(False)
943
- # self.select_option.setVisible(False)
1057
+ self.parent().po.basic = False
1058
+ self.parent().po.network_shaped = False
944
1059
  if self.is_first_image_flag:
945
1060
  self.run_first_image_analysis()
946
1061
  else:
947
1062
  self.run_last_image_analysis()
948
1063
 
949
1064
  def run_first_image_analysis(self):
950
- # logging.info('runfim' +str(self.parent().po.sample_number))
1065
+ """
1066
+ Run the first image analysis.
1067
+
1068
+ This method performs a series of checks and updates based on user-defined parameters
1069
+ before running the first image analysis. If visualization is enabled, it saves user-defined
1070
+ combinations and checks for empty color selection dictionaries. It then starts the thread
1071
+ for image analysis.
1072
+
1073
+ Notes
1074
+ -----
1075
+ This method assumes that the parent object has already been initialized and contains all
1076
+ necessary variables for image analysis.
1077
+ """
951
1078
  if self.first_im_parameters_answered:
952
- # self.sample_number_changed()
953
1079
  self.several_blob_per_arena_check()
954
1080
  self.horizontal_size_changed()
955
1081
  self.spot_shape_changed()
956
1082
  self.arena_shape_changed()
957
- logging.info(self.parent().po.sample_number)
958
- logging.info(self.parent().po.vars['several_blob_per_arena'])
959
- logging.info(self.parent().po.all['starting_blob_shape'])
960
- logging.info(self.parent().po.vars['arena_shape'])
961
1083
 
962
1084
  if self.parent().po.visualize:
963
1085
  self.save_user_defined_csc()
964
1086
  self.parent().po.vars["color_number"] = int(self.distinct_colors_number.value())
965
1087
  if self.csc_dict_is_empty:
966
- self.message.setText('Choose a color space, modify a channel and visualize')
1088
+ self.message.setText('Select non null value(s) to combine colors')
967
1089
  self.message.setStyleSheet("color: rgb(230, 145, 18)")
1090
+ self.is_image_analysis_running = False
968
1091
  if not self.parent().po.visualize or not self.csc_dict_is_empty:
969
- self.parent().po.vars['convert_for_origin'] = deepcopy(self.csc_dict)
1092
+ self.parent().po.vars['convert_for_origin'] = self.csc_dict.copy()
970
1093
  self.thread["FirstImageAnalysis"].start()
971
1094
  self.thread["FirstImageAnalysis"].message_from_thread.connect(self.display_message_from_thread)
972
1095
  self.thread["FirstImageAnalysis"].message_when_thread_finished.connect(self.when_image_analysis_finishes)
973
1096
 
974
1097
  def run_last_image_analysis(self):
975
- logging.info('runlim')
976
- if self.parent().po.visualize:
977
- self.save_user_defined_csc()
978
- self.parent().po.vars["color_number"] = int(self.distinct_colors_number.value())
979
- if self.csc_dict_is_empty:
980
- self.message.setText('Choose a color space, increase a channel and visualize')
981
- self.message.setStyleSheet("color: rgb(230, 145, 18)")
982
- else:
983
- self.parent().po.vars['convert_for_motion'] = deepcopy(self.csc_dict)
984
- self.thread["LastImageAnalysis"].start()
985
- self.thread["LastImageAnalysis"].message_from_thread.connect(self.display_message_from_thread)
986
- self.thread["LastImageAnalysis"].message_when_thread_finished.connect(
987
- self.when_image_analysis_finishes)
1098
+ """
1099
+ Run the last image analysis thread.
1100
+
1101
+ This function updates relevant variables, saves user-defined color-space configurations (CSC),
1102
+ and manages thread operations for image analysis. The function does not handle any direct processing but
1103
+ prepares the environment by setting variables and starting threads.
1104
+ """
1105
+ self.save_user_defined_csc()
1106
+ self.parent().po.vars["color_number"] = int(self.distinct_colors_number.value())
1107
+ if not self.csc_dict_is_empty:
1108
+ self.parent().po.vars['convert_for_motion'] = self.csc_dict.copy()
1109
+ if self.parent().po.visualize and self.csc_dict_is_empty:
1110
+ self.message.setText('Select non null value(s) to combine colors')
1111
+ self.message.setStyleSheet("color: rgb(230, 145, 18)")
988
1112
  else:
989
1113
  self.thread["LastImageAnalysis"].start()
990
1114
  self.thread["LastImageAnalysis"].message_from_thread.connect(self.display_message_from_thread)
991
1115
  self.thread["LastImageAnalysis"].message_when_thread_finished.connect(self.when_image_analysis_finishes)
992
1116
 
993
1117
  def when_image_analysis_finishes(self):
994
- logging.info('im_finish' + str(self.parent().po.sample_number))
1118
+ """
1119
+ Logs the completion of an image analysis operation, updates the current combination ID,
1120
+ handles visualization settings, manages image combinations, and updates the display.
995
1121
 
1122
+ Notes
1123
+ -----
1124
+ - This method interacts with the parent object's properties and thread management.
1125
+ - The `is_first_image_flag` determines which set of image combinations to use.
1126
+ """
1127
+
1128
+ if self.is_first_image_flag:
1129
+ im_combinations = self.parent().po.first_image.im_combinations
1130
+ else:
1131
+ im_combinations = self.parent().po.last_image.im_combinations
1132
+ self.init_drawn_image(im_combinations)
996
1133
  if self.parent().po.visualize:
997
1134
  if self.parent().po.current_combination_id != self.select_option.currentIndex():
998
1135
  self.select_option.setCurrentIndex(self.parent().po.current_combination_id)
999
1136
  else:
1000
1137
  self.parent().po.current_combination_id = 0
1001
- if self.is_first_image_flag:
1002
- im_combinations = self.parent().po.first_image.im_combinations
1003
- else:
1004
- im_combinations = self.parent().po.last_image.im_combinations
1005
1138
  if len(im_combinations) > 0:
1006
1139
  self.csc_dict = im_combinations[self.parent().po.current_combination_id]["csc"]
1007
-
1008
1140
  if self.is_first_image_flag:
1009
- self.parent().po.vars['convert_for_origin'] = deepcopy(self.csc_dict)
1141
+ self.parent().po.vars['convert_for_origin'] = self.csc_dict.copy()
1010
1142
  else:
1011
- self.parent().po.vars['convert_for_motion'] = deepcopy(self.csc_dict)
1143
+ self.parent().po.vars['convert_for_motion'] = self.csc_dict.copy()
1012
1144
  option_number = len(im_combinations)
1013
1145
 
1014
1146
  if option_number > 1:
@@ -1019,6 +1151,7 @@ class ImageAnalysisWindow(MainTabsType):
1019
1151
  self.update_csc_editing_display()
1020
1152
  else:
1021
1153
  self.message.setText("No options could be generated automatically, use the advanced mode")
1154
+ self.is_image_analysis_running = False
1022
1155
 
1023
1156
  if self.parent().po.visualize or len(im_combinations) > 0:
1024
1157
  self.is_image_analysis_display_running = True
@@ -1029,28 +1162,38 @@ class ImageAnalysisWindow(MainTabsType):
1029
1162
  self.thread["UpdateImage"].message_when_thread_finished.connect(self.image_analysis_displayed)
1030
1163
 
1031
1164
  def image_analysis_displayed(self):
1165
+ """
1166
+ Display results of image analysis based on the current step and configuration.
1167
+
1168
+ Update the user interface elements based on the current step of image analysis,
1169
+ the detected number of shapes, and whether color analysis is enabled. Handles
1170
+ visibilities of buttons and labels to guide the user through the process.
1171
+
1172
+ Notes
1173
+ -----
1174
+ This method updates the user interface based on the current state of image analysis.
1175
+ """
1032
1176
  color_analysis = not self.parent().po.vars['already_greyscale']
1033
1177
  self.message.setText("")
1034
1178
 
1035
-
1036
1179
  if self.step < 2:
1037
1180
  detected_shape_nb = self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id][
1038
1181
  'shape_number']
1039
1182
  if detected_shape_nb == self.parent().po.sample_number or self.parent().po.vars['several_blob_per_arena']:
1040
1183
  self.decision_label.setText(
1041
- f"{detected_shape_nb} distinct spots detected in {self.parent().po.sample_number} arena(s). Does the color match the cell(s)?")
1184
+ f"{detected_shape_nb} distinct specimen(s) detected in {self.parent().po.sample_number} arena(s). Does the color match the cell(s)?")
1042
1185
  if self.step == 1:
1043
1186
  self.yes.setVisible(True)
1044
1187
  self.message.setText("If not, draw more Cell and Back ellipses on the image and retry")
1045
1188
  else:
1046
1189
  if self.no.isVisible():
1047
1190
  self.decision_label.setText(
1048
- f"{detected_shape_nb} distinct spots detected in {self.parent().po.sample_number} arena(s). Click Yes when satisfied, Click No to fill in more parameters")
1191
+ f"{detected_shape_nb} distinct specimen(s) detected in {self.parent().po.sample_number} arena(s). Click Yes when satisfied, Click No to fill in more parameters")
1049
1192
  self.yes.setVisible(True)
1050
1193
  self.no.setVisible(True)
1051
1194
  else:
1052
1195
  self.decision_label.setText(
1053
- f"{detected_shape_nb} distinct spots detected in {self.parent().po.sample_number} arena(s). Click Yes when satisfied")
1196
+ f"{detected_shape_nb} distinct specimen(s) detected in {self.parent().po.sample_number} arena(s). Click Yes when satisfied")
1054
1197
  self.yes.setVisible(True)
1055
1198
 
1056
1199
  if self.parent().po.vars['several_blob_per_arena'] and (detected_shape_nb == self.parent().po.sample_number):
@@ -1060,7 +1203,6 @@ class ImageAnalysisWindow(MainTabsType):
1060
1203
  self.select_option.setVisible(color_analysis)
1061
1204
  self.select_option_label.setVisible(color_analysis)
1062
1205
  if self.step == 0:
1063
- # self.decision_label.setText(f"Does the color correctly cover the cells? And, is {detected_shape_nb} the number of distinct arenas?")
1064
1206
  if self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id]['shape_number'] == 0:
1065
1207
  self.message.setText("Make sure that scaling metric and spot size are correct")
1066
1208
  self.decision_label.setVisible(True)
@@ -1068,41 +1210,40 @@ class ImageAnalysisWindow(MainTabsType):
1068
1210
  self.no.setVisible(True)
1069
1211
  self.arena_shape.setVisible(True)
1070
1212
  self.arena_shape_label.setVisible(True)
1071
- # self.select_option_label.setVisible(color_analysis)
1072
- # self.select_option.setVisible(color_analysis)
1073
1213
  self.n_shapes_detected.setVisible(True)
1074
1214
 
1075
1215
  elif self.step == 2:
1076
1216
  self.generate_analysis_options.setVisible(color_analysis)
1077
- self.quickly.setVisible(color_analysis)
1078
- self.carefully.setVisible(color_analysis)
1217
+ self.network_shaped.setVisible(True)
1218
+ self.basic.setVisible(color_analysis)
1079
1219
  self.visualize.setVisible(True)
1080
1220
 
1081
- self.decision_label.setText("Click next when color delimits the cell(s) correctly")
1221
+ self.decision_label.setText("Adjust parameters until the color delimits the specimen(s) correctly")
1082
1222
  self.yes.setVisible(False)
1083
1223
  self.no.setVisible(False)
1084
- self.message.setText('When the resulting segmentation of the last image seems good, click next.')
1085
- self.next.setVisible(True)
1224
+ if self.parent().po.all["im_or_vid"] == 1 or len(self.parent().po.data_list) > 1:
1225
+ self.next.setVisible(True)
1226
+ self.message.setText('When the resulting segmentation of the last image seems good, click next.')
1227
+ else:
1228
+ self.video_tab.set_not_usable()
1229
+ self.message.setText('When the resulting segmentation of the last image seems good, save image analysis.')
1230
+ self.complete_image_analysis.setVisible(True)
1086
1231
 
1087
- try:
1088
- self.thread["UpdateImage"].message_when_thread_finished.disconnect()
1089
- except RuntimeError:
1090
- pass
1232
+ self.thread["UpdateImage"].message_when_thread_finished.disconnect()
1091
1233
  self.is_image_analysis_running = False
1092
1234
  self.is_image_analysis_display_running = False
1093
1235
 
1094
- def option_changed(self):
1236
+ def init_drawn_image(self, im_combinations: list=None):
1095
1237
  """
1096
- Save the csc, change the image displayed, the csc editing
1097
- :return:
1238
+ Initialize the drawn image from a list of image combinations.
1239
+
1240
+ Parameters
1241
+ ----------
1242
+ im_combinations : list or None, optional
1243
+ List of image combinations to initialize the drawn image from.
1244
+ Each combination should be a dictionary containing 'csc' and
1245
+ 'converted_image'. If None, the current state is maintained.
1098
1246
  """
1099
- # Update the current image
1100
- if self.is_first_image_flag:
1101
- im_combinations = self.parent().po.first_image.im_combinations
1102
- else:
1103
- im_combinations = self.parent().po.last_image.im_combinations
1104
- self.parent().po.current_combination_id = self.select_option.currentIndex()
1105
- logging.info(im_combinations is None)
1106
1247
  if im_combinations is not None and len(im_combinations) > 0:
1107
1248
  if self.parent().po.current_combination_id + 1 > len(im_combinations):
1108
1249
  self.parent().po.current_combination_id = 0
@@ -1112,6 +1253,24 @@ class ImageAnalysisWindow(MainTabsType):
1112
1253
  im_combinations[self.parent().po.current_combination_id]['converted_image']), axis=2)
1113
1254
  self.drawn_image = deepcopy(self.parent().po.current_image)
1114
1255
 
1256
+ def option_changed(self):
1257
+ """
1258
+ Update the current image and related display information based on the selected image segmentation option.
1259
+
1260
+ Notes
1261
+ -----
1262
+ This function updates several properties of the parent object, including the current image,
1263
+ combination ID, and display settings. It also handles thread management for updating the
1264
+ image display.
1265
+ """
1266
+ # Update the current image
1267
+ self.parent().po.current_combination_id = self.select_option.currentIndex()
1268
+ if self.is_first_image_flag:
1269
+ im_combinations = self.parent().po.first_image.im_combinations
1270
+ else:
1271
+ im_combinations = self.parent().po.last_image.im_combinations
1272
+ self.init_drawn_image(im_combinations)
1273
+ if im_combinations is not None and len(im_combinations) > 0:
1115
1274
  # Update image display
1116
1275
  if self.thread["UpdateImage"].isRunning():
1117
1276
  self.thread["UpdateImage"].wait()
@@ -1122,33 +1281,37 @@ class ImageAnalysisWindow(MainTabsType):
1122
1281
  # Update the detected shape number
1123
1282
  if self.is_first_image_flag:
1124
1283
  self.parent().po.vars['convert_for_origin'] = im_combinations[self.parent().po.current_combination_id]["csc"]
1125
- detected_shape_nb = \
1126
- self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id]['shape_number']
1284
+ detected_shape_nb = im_combinations[self.parent().po.current_combination_id]['shape_number']
1127
1285
  if self.parent().po.vars['several_blob_per_arena']:
1128
1286
  if detected_shape_nb == self.parent().po.sample_number:
1129
1287
  self.message.setText("Beware: Contrary to what has been checked, there is one spot per arena")
1130
1288
  else:
1131
1289
  if detected_shape_nb == self.parent().po.sample_number:
1132
1290
  self.decision_label.setText(
1133
- f"{detected_shape_nb} distinct spots detected in {self.parent().po.sample_number} arena(s). Does the color match the cell(s)?")
1291
+ f"{detected_shape_nb} distinct specimen(s) detected in {self.parent().po.sample_number} arena(s). Does the color match the cell(s)?")
1134
1292
  self.yes.setVisible(True)
1135
1293
  else:
1136
1294
  self.decision_label.setText(
1137
- f"{detected_shape_nb} distinct spots detected in {self.parent().po.sample_number} arena(s). Adjust settings, draw more cells and background, and try again")
1295
+ f"{detected_shape_nb} distinct specimen(s) detected in {self.parent().po.sample_number} arena(s). Adjust settings, draw more cells and background, and try again")
1138
1296
  self.yes.setVisible(False)
1139
- # self.decision_label.setText(f"Does the color correctly cover the cells?")
1140
- # detected_shape_nb = \
1141
- # self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id]['shape_number']
1142
- # self.decision_label.setText(
1143
- # f"Does the color correctly cover the cells? And, is {detected_shape_nb} the number of distinct arenas?")
1144
- if self.parent().po.first_image.im_combinations[self.parent().po.current_combination_id]['shape_number'] == 0:
1297
+ if im_combinations[self.parent().po.current_combination_id]['shape_number'] == 0:
1145
1298
  self.message.setText("Make sure that scaling metric and spot size are correct")
1146
1299
  else:
1147
1300
  self.parent().po.vars['convert_for_motion'] = im_combinations[self.parent().po.current_combination_id]["csc"]
1301
+ if "filter_spec" in im_combinations[self.parent().po.current_combination_id]:
1302
+ self.parent().po.vars['filter_spec'] = im_combinations[self.parent().po.current_combination_id]["filter_spec"]
1303
+ self.parent().po.vars['rolling_window_segmentation']['do'] = im_combinations[self.parent().po.current_combination_id]["rolling_window"]
1304
+ self.update_filter_display()
1148
1305
  self.decision_label.setText("Do colored contours correctly match cell(s) contours?")
1149
1306
 
1150
1307
  def generate_csc_editing(self):
1151
- # self.edit_layout = QtWidgets.QGridLayout()
1308
+ """
1309
+ Create and configure a user interface for color space combination editing.
1310
+
1311
+ This method sets up the UI components needed to edit color space combinations,
1312
+ including checkboxes, labels, and drop-down menus. It also configures the layout
1313
+ and connections between components.
1314
+ """
1152
1315
  self.edit_widget = QtWidgets.QWidget()
1153
1316
  self.edit_layout = QtWidgets.QVBoxLayout()
1154
1317
 
@@ -1156,10 +1319,17 @@ class ImageAnalysisWindow(MainTabsType):
1156
1319
  self.advanced_mode_widget = QtWidgets.QWidget()
1157
1320
  self.advanced_mode_layout = QtWidgets.QHBoxLayout()
1158
1321
  self.advanced_mode_cb = Checkbox(self.parent().po.all['expert_mode'])
1159
- self.advanced_mode_cb.setStyleSheet("margin-left:0%; margin-right:0%;")
1322
+ self.advanced_mode_cb.setStyleSheet("QCheckBox::indicator {width: 12px;height: 12px;background-color: transparent;"
1323
+ "border-radius: 5px;border-style: solid;border-width: 1px;"
1324
+ "border-color: rgb(100,100,100);}"
1325
+ "QCheckBox::indicator:checked {background-color: rgb(70,130,180);}"
1326
+ "QCheckBox:checked, QCheckBox::indicator:checked {border-color: black black white white;}"
1327
+ "QCheckBox:checked {background-color: transparent;}"
1328
+ "QCheckBox:margin-left {0%}"
1329
+ "QCheckBox:margin-right {0%}")
1160
1330
  self.advanced_mode_cb.stateChanged.connect(self.advanced_mode_check)
1161
- self.advanced_mode_label = FixedText('Advanced mode', halign='l',
1162
- tip="Display the color space combination corresponding to the selected option",
1331
+ self.advanced_mode_label = FixedText(IAW["Advanced_mode"]["label"], halign='l',
1332
+ tip=IAW["Advanced_mode"]["tips"],
1163
1333
  night_mode=self.parent().po.all['night_mode'])
1164
1334
  self.advanced_mode_label.setAlignment(QtCore.Qt.AlignTop)
1165
1335
  self.advanced_mode_layout.addWidget(self.advanced_mode_cb)
@@ -1169,10 +1339,8 @@ class ImageAnalysisWindow(MainTabsType):
1169
1339
  self.edit_layout.addWidget(self.advanced_mode_widget)
1170
1340
 
1171
1341
  self.csc_scroll_table = QtWidgets.QScrollArea() # QTableWidget() # Scroll Area which contains the widgets, set as the centralWidget
1172
- # self.csc_scroll_table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
1173
1342
  self.csc_scroll_table.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
1174
1343
  self.csc_scroll_table.setMinimumHeight(self.parent().im_max_height - 100)
1175
- # self.csc_scroll_table.setMinimumWidth(300)
1176
1344
  self.csc_scroll_table.setFrameShape(QtWidgets.QFrame.NoFrame)
1177
1345
  self.csc_scroll_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
1178
1346
  self.csc_scroll_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
@@ -1183,30 +1351,20 @@ class ImageAnalysisWindow(MainTabsType):
1183
1351
  self.edit_labels_widget = QtWidgets.QWidget()
1184
1352
  self.edit_labels_layout = QtWidgets.QHBoxLayout()
1185
1353
 
1186
- self.space_label = FixedText('Color space:', halign='l',
1187
- tip="Color spaces are transformations of the original BGR (Blue Green Red) image\nInstead of defining an image by 3 colors,\n they transform it into 3 different visual properties\n - hsv: hue (color), saturation, value (lightness)\n - hls: hue (color), lightness, saturation\n - lab: Lightness, Red/Green, Blue/Yellow\n - luv and yuv: l and y are Lightness, u and v are related to colors\n",
1354
+ self.space_label = FixedText(IAW["Color_combination"]["label"] + ':', halign='l',
1355
+ tip=IAW["Color_combination"]["tips"],
1188
1356
  night_mode=self.parent().po.all['night_mode'])
1189
- # self.c1 = FixedText(' C1', halign='c', tip="Increase if it increase cell detection", night_mode=self.parent().po.all['night_mode'])
1190
- # self.c2 = FixedText(' C2', halign='c', tip="Increase if it increase cell detection", night_mode=self.parent().po.all['night_mode'])
1191
- # self.c3 = FixedText(' C3', halign='c', tip="Increase if it increase cell detection", night_mode=self.parent().po.all['night_mode'])
1192
1357
 
1193
1358
  self.edit_labels_layout.addWidget(self.space_label)
1194
- # self.edit_labels_layout.addWidget(self.c1)
1195
- # self.edit_labels_layout.addWidget(self.c2)
1196
- # self.edit_labels_layout.addWidget(self.c3)
1197
1359
  self.edit_labels_layout.addItem(self.horizontal_space)
1198
1360
  self.space_label.setVisible(False)
1199
- # self.c1.setVisible(False)
1200
- # self.c2.setVisible(False)
1201
- # self.c3.setVisible(False)
1202
1361
  self.edit_labels_widget.setLayout(self.edit_labels_layout)
1203
- # self.edit_layout.addWidget(self.edit_labels_widget)
1204
1362
  self.csc_table_layout.addWidget(self.edit_labels_widget)
1205
1363
 
1206
1364
  # 3) First CSC
1207
1365
  self.first_csc_widget = QtWidgets.QWidget()
1208
1366
  self.first_csc_layout = QtWidgets.QGridLayout()
1209
- self.row1 = self.one_csc_editing()
1367
+ self.row1 = self.one_csc_editing(with_PCA=True)
1210
1368
  self.row1[4].clicked.connect(self.display_row2)
1211
1369
  self.row2 = self.one_csc_editing()
1212
1370
  self.row2[4].clicked.connect(self.display_row3)
@@ -1216,8 +1374,7 @@ class ImageAnalysisWindow(MainTabsType):
1216
1374
  self.logical_operator_between_combination_result.setCurrentText(self.parent().po.vars['convert_for_motion']['logical'])
1217
1375
  self.logical_operator_between_combination_result.currentTextChanged.connect(self.logical_op_changed)
1218
1376
  self.logical_operator_between_combination_result.setFixedWidth(100)
1219
- # self.logical_operator_between_combination_result.cha
1220
- self.logical_operator_label = FixedText("Logical operator", tip="Between selected color space combinations",
1377
+ self.logical_operator_label = FixedText(IAW["Logical_operator"]["label"], tip=IAW["Logical_operator"]["tips"],
1221
1378
  night_mode=self.parent().po.all['night_mode'])
1222
1379
 
1223
1380
  self.row21 = self.one_csc_editing()
@@ -1244,12 +1401,10 @@ class ImageAnalysisWindow(MainTabsType):
1244
1401
  self.first_csc_layout.addItem(self.horizontal_space, 0, 5, 3, 1)
1245
1402
  self.first_csc_widget.setLayout(self.first_csc_layout)
1246
1403
  self.csc_table_layout.addWidget(self.first_csc_widget)
1247
- # self.edit_layout.addWidget(self.first_csc_widget)
1248
1404
 
1249
1405
  # First filters
1250
-
1251
- self.filter1_label = FixedText('Filter: ', halign='l',
1252
- tip="The filter to apply to the image before segmentation",
1406
+ self.filter1_label = FixedText(IAW["Filter"]["label"] + ': ', halign='l',
1407
+ tip=IAW["Filter"]["tips"],
1253
1408
  night_mode=self.parent().po.all['night_mode'])
1254
1409
  self.csc_table_layout.addWidget(self.filter1_label)
1255
1410
  self.filter1_widget = QtWidgets.QWidget()
@@ -1304,7 +1459,6 @@ class ImageAnalysisWindow(MainTabsType):
1304
1459
  self.logical_operator_label.setVisible(False)
1305
1460
  self.logical_op_widget.setLayout(self.logical_op_layout)
1306
1461
  self.csc_table_layout.addWidget(self.logical_op_widget)
1307
- # self.edit_layout.addWidget(self.logical_op_widget)
1308
1462
 
1309
1463
  # 5) Second CSC
1310
1464
  self.second_csc_widget = QtWidgets.QWidget()
@@ -1324,13 +1478,12 @@ class ImageAnalysisWindow(MainTabsType):
1324
1478
  self.csc_table_widget.setLayout(self.csc_table_layout)
1325
1479
  self.csc_scroll_table.setWidget(self.csc_table_widget)
1326
1480
  self.csc_scroll_table.setWidgetResizable(True)
1327
- # self.edit_layout.addWidget(self.second_csc_widget)
1328
1481
  self.edit_layout.addWidget(self.csc_scroll_table)
1329
1482
  self.edit_layout.addItem(self.vertical_space)
1330
1483
 
1331
1484
  # Second filters
1332
- self.filter2_label = FixedText('Filter: ', halign='l',
1333
- tip="The filter to apply to the image before segmentation",
1485
+ self.filter2_label = FixedText(IAW["Filter"]["label"] + ': ', halign='l',
1486
+ tip=IAW["Filter"]["tips"],
1334
1487
  night_mode=self.parent().po.all['night_mode'])
1335
1488
  self.csc_table_layout.addWidget(self.filter2_label)
1336
1489
  self.filter2_widget = QtWidgets.QWidget()
@@ -1360,7 +1513,6 @@ class ImageAnalysisWindow(MainTabsType):
1360
1513
 
1361
1514
  self.filter1_param2.valueChanged.connect(self.filter2_param2_changed)
1362
1515
  self.filter2_layout.addWidget(self.filter2)
1363
- # self.filter2_layout.addWidget(self.filter2_label)
1364
1516
  self.filter2_layout.addItem(self.horizontal_space)
1365
1517
  self.filter2_layout.addWidget(self.filter2_param1_label)
1366
1518
  self.filter2_layout.addWidget(self.filter2_param1)
@@ -1372,40 +1524,52 @@ class ImageAnalysisWindow(MainTabsType):
1372
1524
  self.filter2_widget.setLayout(self.filter2_layout)
1373
1525
  self.csc_table_layout.addWidget(self.filter2_widget)
1374
1526
 
1375
- # 6) Open the grid_segmentation row layout
1376
- self.grid_segmentation_widget = QtWidgets.QWidget()
1377
- self.grid_segmentation_layout = QtWidgets.QHBoxLayout()
1527
+ # 6) Open the rolling_window_segmentation row layout
1528
+ self.rolling_window_segmentation_widget = QtWidgets.QWidget()
1529
+ self.rolling_window_segmentation_layout = QtWidgets.QHBoxLayout()
1378
1530
  try:
1379
- self.parent().po.vars["grid_segmentation"]
1531
+ self.parent().po.vars["rolling_window_segmentation"]
1380
1532
  except KeyError:
1381
- self.parent().po.vars["grid_segmentation"] = False
1382
- self.grid_segmentation = Checkbox(self.parent().po.vars["grid_segmentation"])
1383
- self.grid_segmentation.setStyleSheet("margin-left:0%; margin-right:-10%;")
1384
- self.grid_segmentation.stateChanged.connect(self.grid_segmentation_option)
1385
-
1386
- self.grid_segmentation_label = FixedText("Grid segmentation",
1387
- tip="Segment small squares of the images to detect local intensity valleys\nThis method segment the image locally using otsu thresholding on a rolling window", night_mode=self.parent().po.all['night_mode'])
1388
- self.grid_segmentation_label.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
1389
- # self.more_than_two_colors_label.setFixedWidth(300)
1390
- self.grid_segmentation_label.setAlignment(QtCore.Qt.AlignLeft)
1391
-
1392
- self.grid_segmentation_layout.addWidget(self.grid_segmentation)
1393
- self.grid_segmentation_layout.addWidget(self.grid_segmentation_label)
1394
- self.grid_segmentation_layout.addItem(self.horizontal_space)
1395
- self.grid_segmentation_widget.setLayout(self.grid_segmentation_layout)
1396
- self.edit_layout.addWidget(self.grid_segmentation_widget)
1533
+ self.parent().po.vars["rolling_window_segmentation"] = False
1534
+ self.rolling_window_segmentation = Checkbox(self.parent().po.vars["rolling_window_segmentation"]['do'])
1535
+ self.rolling_window_segmentation.setStyleSheet("QCheckBox::indicator {width: 12px;height: 12px;background-color: transparent;"
1536
+ "border-radius: 5px;border-style: solid;border-width: 1px;"
1537
+ "border-color: rgb(100,100,100);}"
1538
+ "QCheckBox::indicator:checked {background-color: rgb(70,130,180);}"
1539
+ "QCheckBox:checked, QCheckBox::indicator:checked {border-color: black black white white;}"
1540
+ "QCheckBox:checked {background-color: transparent;}"
1541
+ "QCheckBox:margin-left {0%}"
1542
+ "QCheckBox:margin-right {-10%}")
1543
+ self.rolling_window_segmentation.stateChanged.connect(self.rolling_window_segmentation_option)
1544
+
1545
+ self.rolling_window_segmentation_label = FixedText(IAW["Rolling_window_segmentation"]["label"],
1546
+ tip=IAW["Rolling_window_segmentation"]["tips"], night_mode=self.parent().po.all['night_mode'])
1547
+ self.rolling_window_segmentation_label.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
1548
+ self.rolling_window_segmentation_label.setAlignment(QtCore.Qt.AlignLeft)
1549
+
1550
+ self.rolling_window_segmentation_layout.addWidget(self.rolling_window_segmentation)
1551
+ self.rolling_window_segmentation_layout.addWidget(self.rolling_window_segmentation_label)
1552
+ self.rolling_window_segmentation_layout.addItem(self.horizontal_space)
1553
+ self.rolling_window_segmentation_widget.setLayout(self.rolling_window_segmentation_layout)
1554
+ self.edit_layout.addWidget(self.rolling_window_segmentation_widget)
1397
1555
 
1398
1556
  # 6) Open the more_than_2_colors row layout
1399
1557
  self.more_than_2_colors_widget = QtWidgets.QWidget()
1400
1558
  self.more_than_2_colors_layout = QtWidgets.QHBoxLayout()
1401
1559
  self.more_than_two_colors = Checkbox(self.parent().po.all["more_than_two_colors"])
1402
- self.more_than_two_colors.setStyleSheet("margin-left:0%; margin-right:-10%;")
1560
+ self.more_than_two_colors.setStyleSheet("QCheckBox::indicator {width: 12px;height: 12px;background-color: transparent;"
1561
+ "border-radius: 5px;border-style: solid;border-width: 1px;"
1562
+ "border-color: rgb(100,100,100);}"
1563
+ "QCheckBox::indicator:checked {background-color: rgb(70,130,180);}"
1564
+ "QCheckBox:checked, QCheckBox::indicator:checked {border-color: black black white white;}"
1565
+ "QCheckBox:checked {background-color: transparent;}"
1566
+ "QCheckBox:margin-left {0%}"
1567
+ "QCheckBox:margin-right {-10%}")
1403
1568
  self.more_than_two_colors.stateChanged.connect(self.display_more_than_two_colors_option)
1404
1569
 
1405
- self.more_than_two_colors_label = FixedText("More than two colors",
1406
- tip="The program will split the image into categories\nand find the one corresponding to the cell(s)", night_mode=self.parent().po.all['night_mode'])
1570
+ self.more_than_two_colors_label = FixedText(IAW["Kmeans"]["label"],
1571
+ tip=IAW["Kmeans"]["tips"], night_mode=self.parent().po.all['night_mode'])
1407
1572
  self.more_than_two_colors_label.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
1408
- # self.more_than_two_colors_label.setFixedWidth(300)
1409
1573
  self.more_than_two_colors_label.setAlignment(QtCore.Qt.AlignLeft)
1410
1574
  self.distinct_colors_number = Spinbox(min=2, max=5, val=self.parent().po.vars["color_number"], night_mode=self.parent().po.all['night_mode'])
1411
1575
 
@@ -1414,8 +1578,8 @@ class ImageAnalysisWindow(MainTabsType):
1414
1578
  self.more_than_two_colors.setVisible(False)
1415
1579
  self.more_than_two_colors_label.setVisible(False)
1416
1580
  self.distinct_colors_number.setVisible(False)
1417
- self.grid_segmentation.setVisible(False)
1418
- self.grid_segmentation_label.setVisible(False)
1581
+ self.rolling_window_segmentation.setVisible(False)
1582
+ self.rolling_window_segmentation_label.setVisible(False)
1419
1583
 
1420
1584
  self.more_than_2_colors_layout.addWidget(self.more_than_two_colors)
1421
1585
  self.more_than_2_colors_layout.addWidget(self.more_than_two_colors_label)
@@ -1426,77 +1590,172 @@ class ImageAnalysisWindow(MainTabsType):
1426
1590
 
1427
1591
  self.edit_widget.setLayout(self.edit_layout)
1428
1592
 
1429
- def one_csc_editing(self):
1430
- widget_list = []
1431
- widget_list.insert(0, Combobox(["None", "bgr", "hsv", "hls", "lab", "luv", "yuv"],
1432
- night_mode=self.parent().po.all['night_mode']))
1433
- widget_list[0].setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
1434
- widget_list[0].setFixedWidth(100)
1435
- for i in [1, 2, 3]:
1436
- widget_list.insert(i, Spinbox(min=-126, max=126, val=0, night_mode=self.parent().po.all['night_mode']))
1437
- widget_list[i].setFixedWidth(45)
1438
- widget_list.insert(i + 1, PButton("+", night_mode=self.parent().po.all['night_mode']))
1439
-
1440
- return widget_list
1593
+ def update_filter_display(self):
1594
+ self.filter1.setCurrentText(self.parent().po.vars['filter_spec']['filter1_type'])
1595
+ self.filter1_param1.setValue(self.parent().po.vars['filter_spec']['filter1_param'][0])
1596
+ self.filter1_param2.setValue(self.parent().po.vars['filter_spec']['filter1_param'][1])
1597
+ self.filter2.setCurrentText(self.parent().po.vars['filter_spec']['filter2_type'])
1598
+ self.filter2_param1.setValue(self.parent().po.vars['filter_spec']['filter2_param'][0])
1599
+ self.filter2_param2.setValue(self.parent().po.vars['filter_spec']['filter2_param'][1])
1441
1600
 
1442
1601
  def filter1_changed(self):
1602
+ """
1603
+ Update the UI elements and internal state when the `filter1` selection changes.
1604
+
1605
+ This method updates labels, visibility, and values of filter parameters
1606
+ based on the currently selected filter type.
1607
+
1608
+ Parameters
1609
+ ----------
1610
+ self : object
1611
+ The instance of the class containing this method.
1612
+ """
1443
1613
  current_filter = self.filter1.currentText()
1444
1614
  self.parent().po.vars['filter_spec']['filter1_type'] = current_filter
1445
1615
  show_param1 = "Param1" in filter_dict[current_filter].keys()
1446
- self.filter1_param1_label.setVisible(show_param1)
1447
- self.filter1_param1.setVisible(show_param1)
1616
+ if self.advanced_mode_cb.isChecked():
1617
+ self.filter1_param1_label.setVisible(show_param1)
1618
+ self.filter1_param1.setVisible(show_param1)
1448
1619
  if show_param1:
1449
1620
  self.filter1_param1_label.setText(filter_dict[current_filter]['Param1']['Name'])
1450
1621
  self.filter1_param1.setMinimum(filter_dict[current_filter]['Param1']['Minimum'])
1451
1622
  self.filter1_param1.setMaximum(filter_dict[current_filter]['Param1']['Maximum'])
1452
- self.filter1_param1.setValue(filter_dict[current_filter]['Param1']['Default'])
1623
+ if self.filter1_param1.value() < filter_dict[current_filter]['Param1']['Minimum'] or self.filter1_param1.value() > filter_dict[current_filter]['Param1']['Maximum']:
1624
+ self.filter1_param1.setValue(filter_dict[current_filter]['Param1']['Default'])
1453
1625
  if 'Param2' in list(filter_dict[current_filter].keys()):
1454
1626
  self.filter1_param2_label.setText(filter_dict[current_filter]['Param2']['Name'])
1455
1627
  self.filter1_param2.setMinimum(filter_dict[current_filter]['Param2']['Minimum'])
1456
1628
  self.filter1_param2.setMaximum(filter_dict[current_filter]['Param2']['Maximum'])
1457
- self.filter1_param2.setValue(filter_dict[current_filter]['Param2']['Default'])
1458
- self.filter1_param2_label.setVisible(True)
1459
- self.filter1_param2.setVisible(True)
1629
+ if self.filter1_param2.value() < filter_dict[current_filter]['Param2']['Minimum'] or self.filter1_param2.value() > filter_dict[current_filter]['Param2']['Maximum']:
1630
+ self.filter1_param2.setValue(filter_dict[current_filter]['Param2']['Default'])
1631
+ if self.advanced_mode_cb.isChecked():
1632
+ self.filter1_param2_label.setVisible(True)
1633
+ self.filter1_param2.setVisible(True)
1460
1634
  else:
1461
1635
  self.filter1_param2_label.setVisible(False)
1462
1636
  self.filter1_param2.setVisible(False)
1463
1637
 
1464
1638
  def filter1_param1_changed(self):
1639
+ """
1640
+ Save the first parameter (most often the lower bound) of the first filter.
1641
+ """
1465
1642
  self.parent().po.vars['filter_spec']['filter1_param'][0] = float(self.filter1_param1.value())
1466
1643
 
1467
1644
  def filter1_param2_changed(self):
1645
+ """
1646
+ Save the second parameter (most often the higher bound) of the first filter.
1647
+ """
1468
1648
  self.parent().po.vars['filter_spec']['filter1_param'][1] = float(self.filter1_param2.value())
1469
1649
 
1470
1650
  def filter2_changed(self):
1651
+ """
1652
+ Update the UI elements and internal state when the `filter2` selection changes.
1653
+
1654
+ This method updates labels, visibility, and values of filter parameters
1655
+ based on the currently selected filter type.
1656
+
1657
+ Parameters
1658
+ ----------
1659
+ self : object
1660
+ The instance of the class containing this method.
1661
+ """
1471
1662
  current_filter = self.filter2.currentText()
1472
1663
  self.parent().po.vars['filter_spec']['filter2_type'] = current_filter
1473
1664
  show_param1 = "Param1" in filter_dict[current_filter].keys()
1474
- self.filter2_param1_label.setVisible(show_param1)
1475
- self.filter2_param1.setVisible(show_param1)
1665
+ if self.advanced_mode_cb.isChecked():
1666
+ self.filter2_param1_label.setVisible(show_param1)
1667
+ self.filter2_param1.setVisible(show_param1)
1476
1668
  if show_param1:
1477
1669
  self.filter2_param1_label.setText(filter_dict[current_filter]['Param1']['Name'])
1478
1670
  self.filter2_param1.setMinimum(filter_dict[current_filter]['Param1']['Minimum'])
1479
1671
  self.filter2_param1.setMaximum(filter_dict[current_filter]['Param1']['Maximum'])
1480
- self.filter2_param1.setValue(filter_dict[current_filter]['Param2']['Default'])
1672
+ if self.filter2_param1.value() < filter_dict[current_filter]['Param1']['Minimum'] or self.filter2_param1.value() > filter_dict[current_filter]['Param1']['Maximum']:
1673
+ self.filter2_param1.setValue(filter_dict[current_filter]['Param1']['Default'])
1481
1674
  if 'Param2' in list(filter_dict[current_filter].keys()):
1482
1675
  self.filter2_param2_label.setText(filter_dict[current_filter]['Param2']['Name'])
1483
1676
  self.filter2_param2.setMinimum(filter_dict[current_filter]['Param2']['Minimum'])
1484
1677
  self.filter2_param2.setMaximum(filter_dict[current_filter]['Param2']['Maximum'])
1485
- self.filter2_param2.setValue(filter_dict[current_filter]['Param2']['Default'])
1486
- self.filter2_param2_label.setVisible(True)
1487
- self.filter2_param2.setVisible(True)
1678
+ if self.filter2_param2.value() < filter_dict[current_filter]['Param2']['Minimum'] or self.filter2_param2.value() > filter_dict[current_filter]['Param2']['Maximum']:
1679
+ self.filter2_param2.setValue(filter_dict[current_filter]['Param2']['Default'])
1680
+ if self.advanced_mode_cb.isChecked():
1681
+ self.filter2_param2_label.setVisible(True)
1682
+ self.filter2_param2.setVisible(True)
1488
1683
  else:
1489
1684
  self.filter2_param2_label.setVisible(False)
1490
1685
  self.filter2_param2.setVisible(False)
1491
1686
 
1492
1687
  def filter2_param1_changed(self):
1688
+ """
1689
+ Save the first parameter (most often the lower bound) of the second filter.
1690
+ """
1493
1691
  self.parent().po.vars['filter_spec']['filter2_param'][0] = float(self.filter2_param1.value())
1494
1692
 
1495
1693
  def filter2_param2_changed(self):
1694
+ """
1695
+ Save the second parameter (most often the higher bound) of the second filter.
1696
+ """
1496
1697
  self.parent().po.vars['filter_spec']['filter2_param'][1] = float(self.filter2_param2.value())
1497
1698
 
1699
+ def one_csc_editing(self, with_PCA: bool=False):
1700
+ """
1701
+ Summary
1702
+ --------
1703
+ Edit the color space configuration and add widgets for PCA or other options.
1704
+
1705
+ Parameters
1706
+ ----------
1707
+ with_PCA : bool, optional
1708
+ Flag indicating whether to include PCA options.
1709
+ Default is False.
1710
+
1711
+ Returns
1712
+ -------
1713
+ list
1714
+ List of widgets for color space configuration.
1715
+ """
1716
+ widget_list = []
1717
+ if with_PCA:
1718
+ widget_list.insert(0, Combobox(["PCA", "bgr", "hsv", "hls", "lab", "luv", "yuv"],
1719
+ night_mode=self.parent().po.all['night_mode']))
1720
+ widget_list[0].currentTextChanged.connect(self.pca_changed)
1721
+ else:
1722
+ widget_list.insert(0, Combobox(["None", "bgr", "hsv", "hls", "lab", "luv", "yuv"],
1723
+ night_mode=self.parent().po.all['night_mode']))
1724
+ widget_list[0].setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
1725
+ widget_list[0].setFixedWidth(100)
1726
+ for i in [1, 2, 3]:
1727
+ widget_list.insert(i, Spinbox(min=-126, max=126, val=0, night_mode=self.parent().po.all['night_mode']))
1728
+ widget_list[i].setFixedWidth(45)
1729
+ widget_list.insert(i + 1, PButton("+", night_mode=self.parent().po.all['night_mode']))
1730
+ return widget_list
1731
+
1732
+ def pca_changed(self):
1733
+ """
1734
+ Handles the UI changes when 'PCA' is selected in dropdown menu.
1735
+
1736
+ Notes
1737
+ -----
1738
+ This function modifies the visibility of UI elements based on the selection in a dropdown menu.
1739
+ It is triggered when 'PCA' is selected, and hides elements related to logical operators.
1740
+ """
1741
+ if self.row1[0].currentText() == 'PCA':
1742
+ self.logical_operator_between_combination_result.setCurrentText('None')
1743
+ for i in range(1, 5):
1744
+ self.row1[i].setVisible(False)
1745
+ self.row2[i].setVisible(False)
1746
+ self.row3[i].setVisible(False)
1747
+ self.logical_operator_label.setVisible(False)
1748
+ self.logical_operator_between_combination_result.setVisible(False)
1749
+ else:
1750
+ for i in range(1, 5):
1751
+ self.row1[i].setVisible(True)
1752
+
1753
+
1498
1754
  def logical_op_changed(self):
1499
- # show = self.logical_operator_between_combination_result.currentText() != 'None'
1755
+ """
1756
+ Handles the visibility and values of UI elements based on the current
1757
+ logical operator selection in a combination result dropdown.
1758
+ """
1500
1759
  if self.logical_operator_between_combination_result.currentText() == 'None':
1501
1760
  self.row21[0].setVisible(False)
1502
1761
  self.row21[0].setCurrentIndex(0)
@@ -1528,71 +1787,99 @@ class ImageAnalysisWindow(MainTabsType):
1528
1787
  self.row21[i1 + 1].setVisible(self.parent().po.all['expert_mode'])
1529
1788
 
1530
1789
  def display_logical_operator(self):
1790
+ """
1791
+ Displays the logical operator UI elements based on expert mode setting.
1792
+ """
1531
1793
  self.logical_operator_between_combination_result.setVisible(self.parent().po.all['expert_mode'])
1532
1794
  self.logical_operator_label.setVisible(self.parent().po.all['expert_mode'])
1533
1795
 
1534
1796
  def display_row2(self):
1797
+ """
1798
+ Display or hide the second row of the csc editing widgets based on expert mode.
1799
+ """
1535
1800
  self.row1[4].setVisible(False)
1536
1801
  for i in range(5):
1537
1802
  self.row2[i].setVisible(self.parent().po.all['expert_mode'])
1538
1803
  self.display_logical_operator()
1539
1804
 
1540
1805
  def display_row3(self):
1806
+ """
1807
+ Display or hide the third row of the csc editing widgets based on expert mode.
1808
+ """
1541
1809
  self.row2[4].setVisible(False)
1542
1810
  for i in range(4):
1543
1811
  self.row3[i].setVisible(self.parent().po.all['expert_mode'])
1544
1812
  self.display_logical_operator()
1545
1813
 
1546
1814
  def display_row22(self):
1815
+ """
1816
+ Display or hide the second row (for the second image segmentation pipeline) of the csc editing widgets based on expert mode.
1817
+ """
1547
1818
  self.row21[4].setVisible(False)
1548
1819
  for i in range(5):
1549
1820
  self.row22[i].setVisible(self.parent().po.all['expert_mode'])
1550
1821
  self.display_logical_operator()
1551
1822
 
1552
1823
  def display_row23(self):
1824
+ """
1825
+ Display or hide the third row (for the second image segmentation pipeline) of the csc editing widgets based on expert mode.
1826
+ """
1553
1827
  self.row22[4].setVisible(False)
1554
1828
  for i in range(4):
1555
1829
  self.row23[i].setVisible(self.parent().po.all['expert_mode'])
1556
1830
  self.display_logical_operator()
1557
1831
 
1558
1832
  def update_csc_editing_display(self):
1559
- c_space_order = ["None", "bgr", "hsv", "hls", "lab", "luv", "yuv"]
1833
+ """
1834
+ Update the color space conversion (CSC) editing display.
1835
+
1836
+ This method updates the visibility and values of UI elements related to color
1837
+ space conversions based on the current state of `self.csc_dict`. It handles
1838
+ the display logic for different color spaces and their combinations, ensuring
1839
+ that the UI reflects the current configuration accurately.
1840
+ """
1560
1841
  remaining_c_spaces = []
1561
1842
  row_number1 = 0
1562
1843
  row_number2 = 0
1563
- for i, (k, v) in enumerate(self.csc_dict.items()):
1564
- if k != "logical":
1565
- if k[-1] != "2":
1566
- if row_number1 == 0:
1567
- row_to_change = self.row1
1568
- elif row_number1 == 1:
1569
- row_to_change = self.row2
1570
- elif row_number1 == 2:
1571
- row_to_change = self.row3
1572
- else:
1573
- remaining_c_spaces.append(k + " " + str(v))
1574
- row_number1 += 1
1575
- current_row_number = row_number1
1576
- else:
1577
- if row_number2 == 0:
1578
- row_to_change = self.row21
1579
- elif row_number2 == 1:
1580
- row_to_change = self.row22
1581
- elif row_number2 == 2:
1582
- row_to_change = self.row23
1844
+ if "PCA" in self.csc_dict.keys():
1845
+ self.row1[0].setCurrentIndex(0)
1846
+ for i in range(1, 4):
1847
+ self.row1[i].setVisible(False)
1848
+ else:
1849
+ c_space_order = ["PCA", "bgr", "hsv", "hls", "lab", "luv", "yuv"]
1850
+ for i, (k, v) in enumerate(self.csc_dict.items()):
1851
+ if k != "logical":
1852
+ if k[-1] != "2":
1853
+ if row_number1 == 0:
1854
+ row_to_change = self.row1
1855
+ elif row_number1 == 1:
1856
+ row_to_change = self.row2
1857
+ elif row_number1 == 2:
1858
+ row_to_change = self.row3
1859
+ else:
1860
+ remaining_c_spaces.append(k + " " + str(v))
1861
+ row_number1 += 1
1862
+ current_row_number = row_number1
1583
1863
  else:
1584
- remaining_c_spaces.append(k + " " + str(v))
1585
- row_number2 += 1
1586
- current_row_number = row_number2
1587
- k = k[:-1]
1588
- if current_row_number <= 3:
1589
- row_to_change[0].setCurrentIndex(np.nonzero(np.isin(c_space_order, k))[0][0])
1590
- row_to_change[0].setVisible(self.parent().po.all['expert_mode'])
1591
- for i1, i2 in zip([1, 2, 3], [0, 1, 2]):
1592
- row_to_change[i1].setValue(v[i2])
1593
- row_to_change[i1].setVisible(self.parent().po.all['expert_mode'])
1594
- if current_row_number < 3:
1595
- row_to_change[i1 + 1].setVisible(self.parent().po.all['expert_mode'])
1864
+ if row_number2 == 0:
1865
+ row_to_change = self.row21
1866
+ elif row_number2 == 1:
1867
+ row_to_change = self.row22
1868
+ elif row_number2 == 2:
1869
+ row_to_change = self.row23
1870
+ else:
1871
+ remaining_c_spaces.append(k + " " + str(v))
1872
+ row_number2 += 1
1873
+ current_row_number = row_number2
1874
+ k = k[:-1]
1875
+ if current_row_number <= 3:
1876
+ row_to_change[0].setCurrentIndex(np.nonzero(np.isin(c_space_order, k))[0][0])
1877
+ row_to_change[0].setVisible(self.parent().po.all['expert_mode'])
1878
+ for i1, i2 in zip([1, 2, 3], [0, 1, 2]):
1879
+ row_to_change[i1].setValue(v[i2])
1880
+ row_to_change[i1].setVisible(self.parent().po.all['expert_mode'])
1881
+ if current_row_number < 3:
1882
+ row_to_change[i1 + 1].setVisible(self.parent().po.all['expert_mode'])
1596
1883
 
1597
1884
  # If not all color space combinations are filled, put None and 0 in boxes
1598
1885
  if row_number1 < 3:
@@ -1653,6 +1940,9 @@ class ImageAnalysisWindow(MainTabsType):
1653
1940
  self.message.setText(f'')
1654
1941
 
1655
1942
  def save_user_defined_csc(self):
1943
+ """
1944
+ Save user-defined combination of color spaces and channels.
1945
+ """
1656
1946
  self.csc_dict = {}
1657
1947
  spaces = np.array((self.row1[0].currentText(), self.row2[0].currentText(), self.row3[0].currentText()))
1658
1948
  channels = np.array(
@@ -1678,20 +1968,24 @@ class ImageAnalysisWindow(MainTabsType):
1678
1968
  for i, space in enumerate(spaces):
1679
1969
  if space != "None" and space != "None2":
1680
1970
  self.csc_dict[space] = channels[i, :]
1681
- if len(self.csc_dict) == 1 or channels.sum() == 0:
1971
+ if not 'PCA' in self.csc_dict and (len(self.csc_dict) == 1 or np.absolute(channels).sum() == 0):
1682
1972
  self.csc_dict_is_empty = True
1683
1973
  else:
1684
1974
  self.csc_dict_is_empty = False
1685
1975
 
1686
- def grid_segmentation_option(self):
1687
- self.parent().po.vars["grid_segmentation"] = self.grid_segmentation.isChecked()
1976
+ def rolling_window_segmentation_option(self):
1977
+ """
1978
+ Set True the grid segmentation option for future image analysis.
1979
+ """
1980
+ self.parent().po.vars["rolling_window_segmentation"]['do'] = self.rolling_window_segmentation.isChecked()
1688
1981
 
1689
1982
  def display_more_than_two_colors_option(self):
1690
- """ should not do
1983
+ """
1984
+ Display the More Than Two Colors Options
1691
1985
 
1692
- self.parent().po.all["more_than_two_colors"] = self.more_than_two_colors.isChecked()
1693
- when init
1694
- """
1986
+ This method manages the visibility and state of UI elements related to selecting
1987
+ more than two colors for displaying biological masks in advanced mode.
1988
+ """
1695
1989
  if self.bio_masks_number > 0 and self.advanced_mode_cb.isChecked():
1696
1990
  self.more_than_two_colors.setVisible(True)
1697
1991
  self.more_than_two_colors_label.setVisible(True)
@@ -1713,9 +2007,25 @@ class ImageAnalysisWindow(MainTabsType):
1713
2007
  # self.parent().po.vars["color_number"] = 2
1714
2008
 
1715
2009
  def distinct_colors_number_changed(self):
2010
+ """
2011
+ Update the parent object's color number variable based on the current value of a distinct colors control.
2012
+
2013
+ Notes
2014
+ -----
2015
+ This function expects that the parent object has an attribute `po` with a dictionary-like 'vars' that can be updated.
2016
+ """
1716
2017
  self.parent().po.vars["color_number"] = int(self.distinct_colors_number.value())
1717
2018
 
1718
2019
  def start_crop_scale_subtract_delineate(self):
2020
+ """
2021
+ Start the crop, scale, subtract, and delineate process.
2022
+
2023
+ Extended Description
2024
+ --------------------
2025
+ This function initiates a background thread to perform the crop, scale,
2026
+ subtract, and delineate operations on the image. It also updates the
2027
+ UI elements to reflect the ongoing process.
2028
+ """
1719
2029
  if not self.thread['CropScaleSubtractDelineate'].isRunning():
1720
2030
  self.message.setText("Looking for each arena contour, wait...")
1721
2031
  self.thread['CropScaleSubtractDelineate'].start()
@@ -1724,13 +2034,10 @@ class ImageAnalysisWindow(MainTabsType):
1724
2034
 
1725
2035
  self.yes.setVisible(False)
1726
2036
  self.no.setVisible(False)
1727
- # self.times_clicked_yes += 1
1728
2037
  self.reinitialize_bio_and_back_legend()
1729
2038
  self.user_drawn_lines_label.setVisible(False)
1730
2039
  self.cell.setVisible(False)
1731
2040
  self.background.setVisible(False)
1732
- # self.sample_number.setVisible(False)
1733
- # self.sample_number_label.setVisible(False)
1734
2041
  self.one_blob_per_arena.setVisible(False)
1735
2042
  self.one_blob_per_arena_label.setVisible(False)
1736
2043
  self.set_spot_shape.setVisible(False)
@@ -1743,14 +2050,17 @@ class ImageAnalysisWindow(MainTabsType):
1743
2050
  self.advanced_mode_cb.setVisible(False)
1744
2051
  self.advanced_mode_label.setVisible(False)
1745
2052
  self.generate_analysis_options.setVisible(False)
1746
- self.quickly.setVisible(False)
1747
- self.carefully.setVisible(False)
2053
+ self.network_shaped.setVisible(False)
2054
+ self.basic.setVisible(False)
1748
2055
  self.visualize.setVisible(False)
1749
2056
  self.visualize_label.setVisible(False)
1750
2057
  self.select_option.setVisible(False)
1751
2058
  self.select_option_label.setVisible(False)
1752
2059
 
1753
- def delineate_is_done(self, message):
2060
+ def delineate_is_done(self, message: str):
2061
+ """
2062
+ Update GUI after delineation is complete.
2063
+ """
1754
2064
  logging.info("Delineation is done, update GUI")
1755
2065
  self.message.setText(message)
1756
2066
  self.arena_shape_label.setVisible(False)
@@ -1772,6 +2082,12 @@ class ImageAnalysisWindow(MainTabsType):
1772
2082
  self.asking_delineation_flag = True
1773
2083
 
1774
2084
  def automatic_delineation_display_done(self, boole):
2085
+ """
2086
+ Automatically handles the delineation display status for the user interface.
2087
+
2088
+ This function updates the visibility of various UI elements and resets
2089
+ certain flags to ensure that delineation is not redrawn unnecessarily.
2090
+ """
1775
2091
  # Remove this flag to not draw it again next time UpdateImage runs for another reason
1776
2092
  self.delineation_done = False
1777
2093
  self.auto_delineation_flag = False
@@ -1781,22 +2097,31 @@ class ImageAnalysisWindow(MainTabsType):
1781
2097
  self.arena_shape_label.setVisible(True)
1782
2098
  self.arena_shape.setVisible(True)
1783
2099
 
1784
- self.decision_label.setText('Is video delineation correct?')
2100
+ self.decision_label.setText('Is arena delineation correct?')
2101
+ self.decision_label.setToolTip(IAW["Video_delimitation"]["tips"])
1785
2102
  self.decision_label.setVisible(True)
1786
- # self.message.setText('If not, restart the analysis (Previous) or manually draw each arena (No)')
1787
2103
  self.user_drawn_lines_label.setText('Draw each arena on the image')
1788
2104
  self.yes.setVisible(True)
1789
2105
  self.no.setVisible(True)
1790
- try:
1791
- self.thread["UpdateImage"].message_when_thread_finished.disconnect()
1792
- except RuntimeError:
1793
- pass
1794
2106
 
1795
- def display_message_from_thread(self, text_from_thread):
2107
+ self.thread["UpdateImage"].message_when_thread_finished.disconnect()
2108
+
2109
+ def display_message_from_thread(self, text_from_thread: str):
2110
+ """
2111
+ Display a message from a thread.
2112
+
2113
+ Parameters
2114
+ ----------
2115
+ text_from_thread : str
2116
+ The message to display.
2117
+ """
1796
2118
  self.message.setText(text_from_thread)
1797
2119
 
1798
2120
  def starting_differs_from_growing_check(self):
1799
- if self.parent().po.all['first_detection_frame'] > 1:
2121
+ """
2122
+ Set the `origin_state` variable based on checkbox state and frame detection.
2123
+ """
2124
+ if self.parent().po.vars['first_detection_frame'] > 1:
1800
2125
  self.parent().po.vars['origin_state'] = 'invisible'
1801
2126
  else:
1802
2127
  if self.starting_differs_from_growing_cb.isChecked():
@@ -1805,16 +2130,39 @@ class ImageAnalysisWindow(MainTabsType):
1805
2130
  self.parent().po.vars['origin_state'] = 'fluctuating'
1806
2131
 
1807
2132
  def when_yes_is_clicked(self):
2133
+ """
2134
+ Handles the event when the 'Yes' button is clicked.
2135
+
2136
+ If image analysis is not running, trigger the decision tree process.
2137
+ """
1808
2138
  if not self.is_image_analysis_running:
1809
2139
  # self.message.setText('Loading, wait...')
1810
2140
  self.decision_tree(True)
1811
2141
 
1812
2142
  def when_no_is_clicked(self):
2143
+ """
2144
+ Handles the event when the 'No' button is clicked.
2145
+
2146
+ If image analysis is not running, trigger the decision tree process.
2147
+ """
1813
2148
  if not self.is_image_analysis_running:
1814
- # self.message.setText('Loading, wait...')
1815
2149
  self.decision_tree(False)
1816
2150
 
1817
- def decision_tree(self, is_yes):
2151
+ def decision_tree(self, is_yes: bool):
2152
+ """
2153
+ Determine the next step in image processing based on user interaction.
2154
+
2155
+ Parameters
2156
+ ----------
2157
+ is_yes : bool
2158
+ Boolean indicating the user's choice (Yes or No).
2159
+
2160
+ Notes
2161
+ -----
2162
+ This function handles various flags and states to determine the next step in
2163
+ image processing workflow. It updates internal state variables and triggers
2164
+ appropriate methods based on the user's input.
2165
+ """
1818
2166
  color_analysis = not self.parent().po.vars['already_greyscale']
1819
2167
  if self.is_first_image_flag:
1820
2168
  if self.asking_first_im_parameters_flag:
@@ -1830,6 +2178,7 @@ class ImageAnalysisWindow(MainTabsType):
1830
2178
 
1831
2179
  # Is automatic Video delineation correct?
1832
2180
  elif self.asking_delineation_flag:
2181
+ self.decision_label.setToolTip("")
1833
2182
  if not is_yes:
1834
2183
  self.asking_slower_or_manual_delineation()
1835
2184
  else:
@@ -1838,6 +2187,7 @@ class ImageAnalysisWindow(MainTabsType):
1838
2187
 
1839
2188
  # Slower or manual delineation?
1840
2189
  elif self.asking_slower_or_manual_delineation_flag:
2190
+ self.back1_bio2 = 0
1841
2191
  if not is_yes:
1842
2192
  self.manual_delineation()
1843
2193
  else:
@@ -1866,17 +2216,19 @@ class ImageAnalysisWindow(MainTabsType):
1866
2216
  f"{self.arena_masks_number} arenas are drawn over the {self.parent().po.sample_number} expected")
1867
2217
 
1868
2218
  elif self.asking_last_image_flag:
2219
+ self.decision_label.setToolTip("")
1869
2220
  self.parent().po.first_image.im_combinations = None
1870
2221
  self.select_option.clear()
1871
2222
  self.arena_shape.setVisible(False)
1872
2223
  self.arena_shape_label.setVisible(False)
1873
2224
  if is_yes:
1874
2225
  self.start_last_image()
1875
- # if self.parent().po.vars['origin_state'] != 'invisible':
1876
- # self.parent().po.vars['origin_state'] = "constant"
1877
2226
  else:
1878
- # if self.parent().po.vars['origin_state'] != 'invisible':
1879
- # self.parent().po.vars['origin_state'] = "fluctuating"
2227
+ if "PCA" in self.csc_dict:
2228
+ if self.parent().po.last_image.first_pc_vector is None:
2229
+ self.csc_dict = {"bgr": bracket_to_uint8_image_contrast(self.parent().po.first_image.first_pc_vector), "logical": None}
2230
+ else:
2231
+ self.csc_dict = {"bgr": bracket_to_uint8_image_contrast(self.parent().po.last_image.first_pc_vector), "logical": None}
1880
2232
  self.parent().po.vars['convert_for_origin'] = deepcopy(self.csc_dict)
1881
2233
  self.parent().po.vars['convert_for_motion'] = deepcopy(self.csc_dict)
1882
2234
  self.go_to_next_widget()
@@ -1887,13 +2239,18 @@ class ImageAnalysisWindow(MainTabsType):
1887
2239
  self.go_to_next_widget()
1888
2240
 
1889
2241
  def first_im_parameters(self):
1890
- """ Method called in the decision tree"""
2242
+ """
2243
+ Reset UI components and prepare for first image parameters adjustment.
2244
+
2245
+ This method resets various UI elements to their initial states, hides
2246
+ confirmation buttons, and shows controls for adjusting spot shapes and sizes.
2247
+ It also sets flags to indicate that the user has not yet answered the first
2248
+ image parameters prompt.
2249
+ """
1891
2250
  self.step = 1
1892
2251
  self.decision_label.setText("Adjust settings, draw more cells and background, and try again")
1893
2252
  self.yes.setVisible(False)
1894
2253
  self.no.setVisible(False)
1895
- # self.one_blob_per_arena.setVisible(True)
1896
- # self.one_blob_per_arena_label.setVisible(True)
1897
2254
  self.set_spot_shape.setVisible(True)
1898
2255
  self.spot_shape_label.setVisible(True)
1899
2256
  self.spot_shape.setVisible(self.parent().po.all['set_spot_shape'])
@@ -1901,14 +2258,22 @@ class ImageAnalysisWindow(MainTabsType):
1901
2258
  self.spot_size_label.setVisible(self.one_blob_per_arena.isChecked())
1902
2259
  self.spot_size.setVisible(
1903
2260
  self.one_blob_per_arena.isChecked() and self.set_spot_size.isChecked())
1904
- # self.arena_shape.setVisible(True)
1905
- # self.arena_shape_label.setVisible(True)
1906
2261
  self.auto_delineation_flag = True
1907
2262
  self.first_im_parameters_answered = True
1908
2263
 
1909
2264
  def auto_delineation(self):
1910
- """ Method called in the decision tree"""
1911
- # Do not proceed automatic delineation if there are more than one arena containing distinct spots
2265
+ """
2266
+ Auto delineation process for image analysis.
2267
+
2268
+ Automatically delineate or start manual delineation based on the number of arenas containing distinct specimen(s).
2269
+
2270
+ Notes
2271
+ -----
2272
+ - The automatic delineation algorithm cannot handle situations where there are more than one arena containing distinct specimen(s). In such cases, manual delineation is initiated.
2273
+ - This function updates the current mask and its stats, removes unnecessary memory, initiates image processing steps including cropping, scaling, subtracting, and delineating.
2274
+ - The visualization labels are hidden during this process.
2275
+ """
2276
+ # Do not proceed automatic delineation if there are more than one arena containing distinct specimen(s)
1912
2277
  # The automatic delineation algorithm cannot handle this situation
1913
2278
  if self.parent().po.vars['several_blob_per_arena'] and self.parent().po.sample_number > 1:
1914
2279
  self.manual_delineation()
@@ -1923,11 +2288,29 @@ class ImageAnalysisWindow(MainTabsType):
1923
2288
  self.visualize.setVisible(False)
1924
2289
 
1925
2290
  def asking_slower_or_manual_delineation(self):
2291
+ """
2292
+ Sets the asking_slower_or_manual_delineation_flag to True, updates decision_label and message.
2293
+
2294
+ Extended Description
2295
+ --------------------
2296
+ This method is used to prompt the user to choose between a slower but more efficient delineation algorithm and manual delineation.
2297
+
2298
+ Notes
2299
+ -----
2300
+ This function directly modifies instance attributes `asking_slower_or_manual_delineation_flag`, `decision_label`, and `message`.
2301
+
2302
+ """
1926
2303
  self.asking_slower_or_manual_delineation_flag = True
1927
- self.decision_label.setText(f"Click yes to try a slower but more efficient delineation algorithm, no to do it manually")
2304
+ self.decision_label.setText(f"Click 'yes' to try a slower but more efficient delineation algorithm. Click 'no' to do it manually")
1928
2305
  self.message.setText(f"Clicking no will allow you to draw each arena manually")
1929
2306
 
1930
2307
  def slower_delineation(self):
2308
+ """
2309
+ Perform slower delineation process and clear the decision label.
2310
+
2311
+ Execute a sequence of operations that prepare for a slower
2312
+ delineation process.
2313
+ """
1931
2314
  self.decision_label.setText(f"")
1932
2315
  self.arena_shape.setVisible(False)
1933
2316
  self.arena_shape_label.setVisible(False)
@@ -1937,7 +2320,10 @@ class ImageAnalysisWindow(MainTabsType):
1937
2320
  self.start_crop_scale_subtract_delineate()
1938
2321
 
1939
2322
  def manual_delineation(self):
1940
- """ Method called in the decision tree"""
2323
+ """
2324
+ Manually delineates the analysis arena on the image by enabling user interaction and
2325
+ preparing the necessary attributes for manual drawing of arenas on the image.
2326
+ """
1941
2327
  self.manual_delineation_flag = True
1942
2328
  self.parent().po.cropping(is_first_image=True)
1943
2329
  self.parent().po.get_average_pixel_size()
@@ -1957,8 +2343,8 @@ class ImageAnalysisWindow(MainTabsType):
1957
2343
  self.one_blob_per_arena.setVisible(False)
1958
2344
  self.one_blob_per_arena_label.setVisible(False)
1959
2345
  self.generate_analysis_options.setVisible(False)
1960
- self.quickly.setVisible(False)
1961
- self.carefully.setVisible(False)
2346
+ self.network_shaped.setVisible(False)
2347
+ self.basic.setVisible(False)
1962
2348
  self.visualize.setVisible(False)
1963
2349
  self.visualize_label.setVisible(False)
1964
2350
  self.select_option.setVisible(False)
@@ -1966,28 +2352,43 @@ class ImageAnalysisWindow(MainTabsType):
1966
2352
  self.user_drawn_lines_label.setText("Draw each arena")
1967
2353
  self.user_drawn_lines_label.setVisible(True)
1968
2354
  self.decision_label.setText(
1969
- f"Hold click to draw {self.parent().po.sample_number} arenas on the image")
1970
- self.message.setText('Click Yes when it is done')
2355
+ f"Hold click to draw {self.parent().po.sample_number} arena(s) on the image. Once done, click yes.")
2356
+ self.message.setText('An error? Hit one button on the left to remove any drawn arena.')
1971
2357
 
1972
2358
  def last_image_question(self):
1973
- """ Method called in the decision tree"""
1974
- self.decision_label.setText(
1975
- 'Do you want to check if the current parameters work for the last image:')
1976
- self.message.setText('Click Yes if the cell color may change during the analysis.')
1977
- self.yes.setVisible(True)
1978
- self.no.setVisible(True)
1979
- self.starting_differs_from_growing_cb.setVisible(True)
1980
- self.starting_differs_from_growing_label.setVisible(True)
2359
+ """
2360
+ Last image question.
2361
+
2362
+ Queries the user if they want to check parameters for the last image,
2363
+ informing them that the best segmentation pipeline may change during analysis.
2364
+ """
2365
+
1981
2366
  self.image_number.setVisible(False)
1982
2367
  self.image_number_label.setVisible(False)
1983
2368
  self.read.setVisible(False)
1984
- self.asking_last_image_flag = True
1985
- # self.title_label.setVisible(False)
1986
2369
  self.step = 2
2370
+ if self.parent().po.all["im_or_vid"] == 0 and len(self.parent().po.data_list) == 1:
2371
+ self.starting_differs_from_growing_cb.setChecked(False)
2372
+ self.start_last_image()
2373
+ else:
2374
+ self.asking_last_image_flag = True
2375
+ self.decision_label.setText("Click 'yes' to improve the segmentation using the last image")
2376
+ self.decision_label.setToolTip(IAW["Last_image_question"]["tips"])
2377
+ self.message.setText('This is useful when the specimen(s) is more visible.')
2378
+ self.starting_differs_from_growing_cb.setVisible(True)
2379
+ self.starting_differs_from_growing_label.setVisible(True)
2380
+ self.yes.setVisible(True)
2381
+ self.no.setVisible(True)
1987
2382
 
1988
2383
  def start_last_image(self):
2384
+ """
2385
+ Start the process of analyzing the last image in the time-lapse or the video.
2386
+
2387
+ This method initializes various UI elements, retrieves the last image,
2388
+ waits for any running threads to complete, processes the image without
2389
+ considering it as the first image, and updates the visualization.
2390
+ """
1989
2391
  self.is_first_image_flag = False
1990
- # self.parent().po.vars["color_number"] = 2
1991
2392
  self.decision_label.setText('')
1992
2393
  self.yes.setVisible(False)
1993
2394
  self.no.setVisible(False)
@@ -1999,11 +2400,9 @@ class ImageAnalysisWindow(MainTabsType):
1999
2400
  if self.thread['SaveManualDelineation'].isRunning():
2000
2401
  self.thread['SaveManualDelineation'].wait()
2001
2402
  self.parent().po.cropping(is_first_image=False)
2002
- # self.parent().po.last_image = OneImageAnalysis(self.parent().po.last_im)
2003
2403
  self.reinitialize_image_and_masks(self.parent().po.last_image.bgr)
2004
2404
  self.reinitialize_bio_and_back_legend()
2005
2405
  self.parent().po.current_combination_id = 0
2006
- # self.advanced_mode_cb.setChecked(True)
2007
2406
  self.visualize_is_clicked()
2008
2407
  self.user_drawn_lines_label.setText('Select and draw')
2009
2408
  self.user_drawn_lines_label.setVisible(True)
@@ -2014,12 +2413,33 @@ class ImageAnalysisWindow(MainTabsType):
2014
2413
  self.visualize_label.setVisible(True)
2015
2414
  self.visualize.setVisible(True)
2016
2415
  self.row1_widget.setVisible(False)
2017
- # self.title_label.setVisible(True)
2018
- # self.row1_col1_widget.setVisible(False)
2019
- # self.row1_col2_widget.setVisible(False)
2416
+
2417
+ def complete_image_analysis_is_clicked(self):
2418
+ """
2419
+ Completes the image analysis process if no listed threads are running.
2420
+ """
2421
+ if (not self.thread['SaveManualDelineation'].isRunning() or not self.thread[
2422
+ 'PrepareVideoAnalysis'].isRunning() or not self.thread['SaveData'].isRunning() or not
2423
+ self.thread['CompleteImageAnalysisThread'].isRunning()):
2424
+ self.message.setText(f"Analyzing and saving the segmentation result, wait... ")
2425
+ self.thread['CompleteImageAnalysisThread'].start()
2426
+ self.thread['CompleteImageAnalysisThread'].message_when_thread_finished.connect(self.complete_image_analysis_done)
2427
+
2428
+ def complete_image_analysis_done(self, res):
2429
+ self.message.setText(f"Complete image analysis done.")
2020
2430
 
2021
2431
  def go_to_next_widget(self):
2022
- if not self.thread['SaveManualDelineation'].isRunning() or not self.thread['FinalizeImageAnalysis'].isRunning() or not self.thread['SaveData'].isRunning():
2432
+ """
2433
+ Advances the user interface to the next widget after performing final checks.
2434
+
2435
+ Notes
2436
+ -----
2437
+ This function performs several actions in sequence:
2438
+ - Displays a message box to inform the user about final checks.
2439
+ - Waits for some background threads to complete their execution.
2440
+ - Advances the UI to the video analysis window if certain conditions are met.
2441
+ """
2442
+ if not self.thread['SaveManualDelineation'].isRunning() or not self.thread['PrepareVideoAnalysis'].isRunning() or not self.thread['SaveData'].isRunning():
2023
2443
 
2024
2444
  self.popup = QtWidgets.QMessageBox()
2025
2445
  self.popup.setWindowTitle("Info")
@@ -2035,14 +2455,14 @@ class ImageAnalysisWindow(MainTabsType):
2035
2455
 
2036
2456
  self.message.setText(f"Final checks, wait... ")
2037
2457
  self.parent().last_tab = "image_analysis"
2038
- self.thread['FinalizeImageAnalysis'].start()
2458
+ self.thread['PrepareVideoAnalysis'].start()
2039
2459
  if self.parent().po.vars["color_number"] > 2:
2040
2460
  self.parent().videoanalysiswindow.select_option.clear()
2041
2461
  self.parent().videoanalysiswindow.select_option.addItem(f"1) Kmeans")
2042
2462
  self.parent().videoanalysiswindow.select_option.setCurrentIndex(0)
2043
2463
  self.parent().po.all['video_option'] = 0
2044
2464
  time.sleep(1 / 10)
2045
- self.thread['FinalizeImageAnalysis'].wait()
2465
+ self.thread['PrepareVideoAnalysis'].wait()
2046
2466
  self.message.setText(f"")
2047
2467
 
2048
2468
  self.video_tab.set_not_in_use()
@@ -2052,15 +2472,7 @@ class ImageAnalysisWindow(MainTabsType):
2052
2472
  self.popup.close()
2053
2473
 
2054
2474
  def closeEvent(self, event):
2475
+ """
2476
+ Handle the close event for a QWidget.
2477
+ """
2055
2478
  event.accept
2056
-
2057
-
2058
- # if __name__ == "__main__":
2059
- # from cellects.gui.cellects import CellectsMainWidget
2060
- # import sys
2061
- # app = QtWidgets.QApplication([])
2062
- # parent = CellectsMainWidget()
2063
- # session = ImageAnalysisWindow(parent, False)
2064
- # parent.insertWidget(0, session)
2065
- # parent.show()
2066
- # sys.exit(app.exec())