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.
- cellects/__main__.py +65 -25
- cellects/config/all_vars_dict.py +18 -17
- cellects/core/cellects_threads.py +1034 -396
- cellects/core/motion_analysis.py +1664 -2010
- cellects/core/one_image_analysis.py +1082 -1061
- cellects/core/program_organizer.py +1687 -1316
- cellects/core/script_based_run.py +80 -76
- cellects/gui/advanced_parameters.py +390 -330
- cellects/gui/cellects.py +102 -91
- cellects/gui/custom_widgets.py +16 -33
- cellects/gui/first_window.py +226 -104
- cellects/gui/if_several_folders_window.py +117 -68
- cellects/gui/image_analysis_window.py +866 -454
- cellects/gui/required_output.py +104 -57
- cellects/gui/ui_strings.py +840 -0
- cellects/gui/video_analysis_window.py +333 -155
- cellects/image_analysis/cell_leaving_detection.py +64 -4
- cellects/image_analysis/image_segmentation.py +451 -22
- cellects/image_analysis/morphological_operations.py +2166 -1635
- cellects/image_analysis/network_functions.py +616 -253
- cellects/image_analysis/one_image_analysis_threads.py +94 -153
- cellects/image_analysis/oscillations_functions.py +131 -0
- cellects/image_analysis/progressively_add_distant_shapes.py +2 -3
- cellects/image_analysis/shape_descriptors.py +517 -466
- cellects/utils/formulas.py +169 -6
- cellects/utils/load_display_save.py +362 -109
- cellects/utils/utilitarian.py +86 -9
- cellects-0.2.6.dist-info/LICENSE +675 -0
- cellects-0.2.6.dist-info/METADATA +829 -0
- cellects-0.2.6.dist-info/RECORD +44 -0
- cellects/core/one_video_per_blob.py +0 -540
- cellects/image_analysis/cluster_flux_study.py +0 -102
- cellects-0.1.2.dist-info/LICENSE.odt +0 -0
- cellects-0.1.2.dist-info/METADATA +0 -132
- cellects-0.1.2.dist-info/RECORD +0 -44
- {cellects-0.1.2.dist-info → cellects-0.2.6.dist-info}/WHEEL +0 -0
- {cellects-0.1.2.dist-info → cellects-0.2.6.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
It
|
|
7
|
-
|
|
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,
|
|
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
|
-
|
|
72
|
-
|
|
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=
|
|
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("
|
|
84
|
-
tip="
|
|
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('
|
|
89
|
-
tip="
|
|
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('
|
|
95
|
-
tip="
|
|
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("
|
|
185
|
-
tip=
|
|
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,
|
|
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,
|
|
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("
|
|
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("
|
|
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.
|
|
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("
|
|
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("
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
self.
|
|
362
|
-
self.
|
|
363
|
-
self.
|
|
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.
|
|
372
|
-
self.sup_param_row1_layout.addWidget(self.
|
|
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(
|
|
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("
|
|
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['
|
|
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.
|
|
529
|
-
self.message.setText(f"Reading image n°{self.parent().po.
|
|
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
|
-
|
|
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.
|
|
625
|
-
self.
|
|
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
|
|
698
|
-
self.filter2_label.setVisible(is_checked and
|
|
699
|
-
has_param1 = is_checked and
|
|
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
|
|
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.
|
|
707
|
-
self.
|
|
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
|
-
|
|
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() != "
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
942
|
-
|
|
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
|
-
|
|
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('
|
|
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'] =
|
|
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
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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
|
-
|
|
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'] =
|
|
1141
|
+
self.parent().po.vars['convert_for_origin'] = self.csc_dict.copy()
|
|
1010
1142
|
else:
|
|
1011
|
-
self.parent().po.vars['convert_for_motion'] =
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1078
|
-
self.
|
|
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("
|
|
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.
|
|
1085
|
-
|
|
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
|
-
|
|
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
|
|
1236
|
+
def init_drawn_image(self, im_combinations: list=None):
|
|
1095
1237
|
"""
|
|
1096
|
-
|
|
1097
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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(
|
|
1162
|
-
tip="
|
|
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('
|
|
1187
|
-
tip="
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
1333
|
-
tip="
|
|
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
|
|
1376
|
-
self.
|
|
1377
|
-
self.
|
|
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["
|
|
1531
|
+
self.parent().po.vars["rolling_window_segmentation"]
|
|
1380
1532
|
except KeyError:
|
|
1381
|
-
self.parent().po.vars["
|
|
1382
|
-
self.
|
|
1383
|
-
self.
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
self.
|
|
1394
|
-
|
|
1395
|
-
self.
|
|
1396
|
-
self.
|
|
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("
|
|
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("
|
|
1406
|
-
tip="
|
|
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.
|
|
1418
|
-
self.
|
|
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
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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.
|
|
1447
|
-
|
|
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.
|
|
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.
|
|
1458
|
-
|
|
1459
|
-
self.
|
|
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.
|
|
1475
|
-
|
|
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.
|
|
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.
|
|
1486
|
-
|
|
1487
|
-
self.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
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
|
|
1687
|
-
|
|
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
|
-
"""
|
|
1983
|
+
"""
|
|
1984
|
+
Display the More Than Two Colors Options
|
|
1691
1985
|
|
|
1692
|
-
|
|
1693
|
-
|
|
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.
|
|
1747
|
-
self.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1879
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
1911
|
-
|
|
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
|
|
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
|
-
"""
|
|
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.
|
|
1961
|
-
self.
|
|
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}
|
|
1970
|
-
self.message.setText('
|
|
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
|
-
"""
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
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
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
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
|
-
|
|
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['
|
|
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['
|
|
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())
|