cellects 0.1.3__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 +365 -326
- cellects/gui/cellects.py +102 -91
- cellects/gui/custom_widgets.py +4 -3
- cellects/gui/first_window.py +226 -104
- cellects/gui/if_several_folders_window.py +117 -68
- cellects/gui/image_analysis_window.py +841 -450
- cellects/gui/required_output.py +100 -56
- cellects/gui/ui_strings.py +840 -0
- cellects/gui/video_analysis_window.py +317 -135
- 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 -105
- 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.3.dist-info/LICENSE.odt +0 -0
- cellects-0.1.3.dist-info/METADATA +0 -176
- cellects-0.1.3.dist-info/RECORD +0 -44
- {cellects-0.1.3.dist-info → cellects-0.2.6.dist-info}/WHEEL +0 -0
- {cellects-0.1.3.dist-info → cellects-0.2.6.dist-info}/entry_points.txt +0 -0
- {cellects-0.1.3.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
|
|
|
@@ -1165,8 +1328,8 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1165
1328
|
"QCheckBox:margin-left {0%}"
|
|
1166
1329
|
"QCheckBox:margin-right {0%}")
|
|
1167
1330
|
self.advanced_mode_cb.stateChanged.connect(self.advanced_mode_check)
|
|
1168
|
-
self.advanced_mode_label = FixedText(
|
|
1169
|
-
tip="
|
|
1331
|
+
self.advanced_mode_label = FixedText(IAW["Advanced_mode"]["label"], halign='l',
|
|
1332
|
+
tip=IAW["Advanced_mode"]["tips"],
|
|
1170
1333
|
night_mode=self.parent().po.all['night_mode'])
|
|
1171
1334
|
self.advanced_mode_label.setAlignment(QtCore.Qt.AlignTop)
|
|
1172
1335
|
self.advanced_mode_layout.addWidget(self.advanced_mode_cb)
|
|
@@ -1176,10 +1339,8 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1176
1339
|
self.edit_layout.addWidget(self.advanced_mode_widget)
|
|
1177
1340
|
|
|
1178
1341
|
self.csc_scroll_table = QtWidgets.QScrollArea() # QTableWidget() # Scroll Area which contains the widgets, set as the centralWidget
|
|
1179
|
-
# self.csc_scroll_table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
|
1180
1342
|
self.csc_scroll_table.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
|
|
1181
1343
|
self.csc_scroll_table.setMinimumHeight(self.parent().im_max_height - 100)
|
|
1182
|
-
# self.csc_scroll_table.setMinimumWidth(300)
|
|
1183
1344
|
self.csc_scroll_table.setFrameShape(QtWidgets.QFrame.NoFrame)
|
|
1184
1345
|
self.csc_scroll_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
|
1185
1346
|
self.csc_scroll_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
|
@@ -1190,30 +1351,20 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1190
1351
|
self.edit_labels_widget = QtWidgets.QWidget()
|
|
1191
1352
|
self.edit_labels_layout = QtWidgets.QHBoxLayout()
|
|
1192
1353
|
|
|
1193
|
-
self.space_label = FixedText('
|
|
1194
|
-
tip="
|
|
1354
|
+
self.space_label = FixedText(IAW["Color_combination"]["label"] + ':', halign='l',
|
|
1355
|
+
tip=IAW["Color_combination"]["tips"],
|
|
1195
1356
|
night_mode=self.parent().po.all['night_mode'])
|
|
1196
|
-
# self.c1 = FixedText(' C1', halign='c', tip="Increase if it increase cell detection", night_mode=self.parent().po.all['night_mode'])
|
|
1197
|
-
# self.c2 = FixedText(' C2', halign='c', tip="Increase if it increase cell detection", night_mode=self.parent().po.all['night_mode'])
|
|
1198
|
-
# self.c3 = FixedText(' C3', halign='c', tip="Increase if it increase cell detection", night_mode=self.parent().po.all['night_mode'])
|
|
1199
1357
|
|
|
1200
1358
|
self.edit_labels_layout.addWidget(self.space_label)
|
|
1201
|
-
# self.edit_labels_layout.addWidget(self.c1)
|
|
1202
|
-
# self.edit_labels_layout.addWidget(self.c2)
|
|
1203
|
-
# self.edit_labels_layout.addWidget(self.c3)
|
|
1204
1359
|
self.edit_labels_layout.addItem(self.horizontal_space)
|
|
1205
1360
|
self.space_label.setVisible(False)
|
|
1206
|
-
# self.c1.setVisible(False)
|
|
1207
|
-
# self.c2.setVisible(False)
|
|
1208
|
-
# self.c3.setVisible(False)
|
|
1209
1361
|
self.edit_labels_widget.setLayout(self.edit_labels_layout)
|
|
1210
|
-
# self.edit_layout.addWidget(self.edit_labels_widget)
|
|
1211
1362
|
self.csc_table_layout.addWidget(self.edit_labels_widget)
|
|
1212
1363
|
|
|
1213
1364
|
# 3) First CSC
|
|
1214
1365
|
self.first_csc_widget = QtWidgets.QWidget()
|
|
1215
1366
|
self.first_csc_layout = QtWidgets.QGridLayout()
|
|
1216
|
-
self.row1 = self.one_csc_editing()
|
|
1367
|
+
self.row1 = self.one_csc_editing(with_PCA=True)
|
|
1217
1368
|
self.row1[4].clicked.connect(self.display_row2)
|
|
1218
1369
|
self.row2 = self.one_csc_editing()
|
|
1219
1370
|
self.row2[4].clicked.connect(self.display_row3)
|
|
@@ -1223,8 +1374,7 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1223
1374
|
self.logical_operator_between_combination_result.setCurrentText(self.parent().po.vars['convert_for_motion']['logical'])
|
|
1224
1375
|
self.logical_operator_between_combination_result.currentTextChanged.connect(self.logical_op_changed)
|
|
1225
1376
|
self.logical_operator_between_combination_result.setFixedWidth(100)
|
|
1226
|
-
|
|
1227
|
-
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"],
|
|
1228
1378
|
night_mode=self.parent().po.all['night_mode'])
|
|
1229
1379
|
|
|
1230
1380
|
self.row21 = self.one_csc_editing()
|
|
@@ -1251,12 +1401,10 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1251
1401
|
self.first_csc_layout.addItem(self.horizontal_space, 0, 5, 3, 1)
|
|
1252
1402
|
self.first_csc_widget.setLayout(self.first_csc_layout)
|
|
1253
1403
|
self.csc_table_layout.addWidget(self.first_csc_widget)
|
|
1254
|
-
# self.edit_layout.addWidget(self.first_csc_widget)
|
|
1255
1404
|
|
|
1256
1405
|
# First filters
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
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"],
|
|
1260
1408
|
night_mode=self.parent().po.all['night_mode'])
|
|
1261
1409
|
self.csc_table_layout.addWidget(self.filter1_label)
|
|
1262
1410
|
self.filter1_widget = QtWidgets.QWidget()
|
|
@@ -1311,7 +1459,6 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1311
1459
|
self.logical_operator_label.setVisible(False)
|
|
1312
1460
|
self.logical_op_widget.setLayout(self.logical_op_layout)
|
|
1313
1461
|
self.csc_table_layout.addWidget(self.logical_op_widget)
|
|
1314
|
-
# self.edit_layout.addWidget(self.logical_op_widget)
|
|
1315
1462
|
|
|
1316
1463
|
# 5) Second CSC
|
|
1317
1464
|
self.second_csc_widget = QtWidgets.QWidget()
|
|
@@ -1331,13 +1478,12 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1331
1478
|
self.csc_table_widget.setLayout(self.csc_table_layout)
|
|
1332
1479
|
self.csc_scroll_table.setWidget(self.csc_table_widget)
|
|
1333
1480
|
self.csc_scroll_table.setWidgetResizable(True)
|
|
1334
|
-
# self.edit_layout.addWidget(self.second_csc_widget)
|
|
1335
1481
|
self.edit_layout.addWidget(self.csc_scroll_table)
|
|
1336
1482
|
self.edit_layout.addItem(self.vertical_space)
|
|
1337
1483
|
|
|
1338
1484
|
# Second filters
|
|
1339
|
-
self.filter2_label = FixedText('
|
|
1340
|
-
tip="
|
|
1485
|
+
self.filter2_label = FixedText(IAW["Filter"]["label"] + ': ', halign='l',
|
|
1486
|
+
tip=IAW["Filter"]["tips"],
|
|
1341
1487
|
night_mode=self.parent().po.all['night_mode'])
|
|
1342
1488
|
self.csc_table_layout.addWidget(self.filter2_label)
|
|
1343
1489
|
self.filter2_widget = QtWidgets.QWidget()
|
|
@@ -1367,7 +1513,6 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1367
1513
|
|
|
1368
1514
|
self.filter1_param2.valueChanged.connect(self.filter2_param2_changed)
|
|
1369
1515
|
self.filter2_layout.addWidget(self.filter2)
|
|
1370
|
-
# self.filter2_layout.addWidget(self.filter2_label)
|
|
1371
1516
|
self.filter2_layout.addItem(self.horizontal_space)
|
|
1372
1517
|
self.filter2_layout.addWidget(self.filter2_param1_label)
|
|
1373
1518
|
self.filter2_layout.addWidget(self.filter2_param1)
|
|
@@ -1379,15 +1524,15 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1379
1524
|
self.filter2_widget.setLayout(self.filter2_layout)
|
|
1380
1525
|
self.csc_table_layout.addWidget(self.filter2_widget)
|
|
1381
1526
|
|
|
1382
|
-
# 6) Open the
|
|
1383
|
-
self.
|
|
1384
|
-
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()
|
|
1385
1530
|
try:
|
|
1386
|
-
self.parent().po.vars["
|
|
1531
|
+
self.parent().po.vars["rolling_window_segmentation"]
|
|
1387
1532
|
except KeyError:
|
|
1388
|
-
self.parent().po.vars["
|
|
1389
|
-
self.
|
|
1390
|
-
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;"
|
|
1391
1536
|
"border-radius: 5px;border-style: solid;border-width: 1px;"
|
|
1392
1537
|
"border-color: rgb(100,100,100);}"
|
|
1393
1538
|
"QCheckBox::indicator:checked {background-color: rgb(70,130,180);}"
|
|
@@ -1395,19 +1540,18 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1395
1540
|
"QCheckBox:checked {background-color: transparent;}"
|
|
1396
1541
|
"QCheckBox:margin-left {0%}"
|
|
1397
1542
|
"QCheckBox:margin-right {-10%}")
|
|
1398
|
-
self.
|
|
1543
|
+
self.rolling_window_segmentation.stateChanged.connect(self.rolling_window_segmentation_option)
|
|
1399
1544
|
|
|
1400
|
-
self.
|
|
1401
|
-
tip="
|
|
1402
|
-
self.
|
|
1403
|
-
|
|
1404
|
-
self.grid_segmentation_label.setAlignment(QtCore.Qt.AlignLeft)
|
|
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)
|
|
1405
1549
|
|
|
1406
|
-
self.
|
|
1407
|
-
self.
|
|
1408
|
-
self.
|
|
1409
|
-
self.
|
|
1410
|
-
self.edit_layout.addWidget(self.
|
|
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)
|
|
1411
1555
|
|
|
1412
1556
|
# 6) Open the more_than_2_colors row layout
|
|
1413
1557
|
self.more_than_2_colors_widget = QtWidgets.QWidget()
|
|
@@ -1423,10 +1567,9 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1423
1567
|
"QCheckBox:margin-right {-10%}")
|
|
1424
1568
|
self.more_than_two_colors.stateChanged.connect(self.display_more_than_two_colors_option)
|
|
1425
1569
|
|
|
1426
|
-
self.more_than_two_colors_label = FixedText("
|
|
1427
|
-
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'])
|
|
1428
1572
|
self.more_than_two_colors_label.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
|
|
1429
|
-
# self.more_than_two_colors_label.setFixedWidth(300)
|
|
1430
1573
|
self.more_than_two_colors_label.setAlignment(QtCore.Qt.AlignLeft)
|
|
1431
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'])
|
|
1432
1575
|
|
|
@@ -1435,8 +1578,8 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1435
1578
|
self.more_than_two_colors.setVisible(False)
|
|
1436
1579
|
self.more_than_two_colors_label.setVisible(False)
|
|
1437
1580
|
self.distinct_colors_number.setVisible(False)
|
|
1438
|
-
self.
|
|
1439
|
-
self.
|
|
1581
|
+
self.rolling_window_segmentation.setVisible(False)
|
|
1582
|
+
self.rolling_window_segmentation_label.setVisible(False)
|
|
1440
1583
|
|
|
1441
1584
|
self.more_than_2_colors_layout.addWidget(self.more_than_two_colors)
|
|
1442
1585
|
self.more_than_2_colors_layout.addWidget(self.more_than_two_colors_label)
|
|
@@ -1447,77 +1590,172 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1447
1590
|
|
|
1448
1591
|
self.edit_widget.setLayout(self.edit_layout)
|
|
1449
1592
|
|
|
1450
|
-
def
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
widget_list.insert(i, Spinbox(min=-126, max=126, val=0, night_mode=self.parent().po.all['night_mode']))
|
|
1458
|
-
widget_list[i].setFixedWidth(45)
|
|
1459
|
-
widget_list.insert(i + 1, PButton("+", night_mode=self.parent().po.all['night_mode']))
|
|
1460
|
-
|
|
1461
|
-
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])
|
|
1462
1600
|
|
|
1463
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
|
+
"""
|
|
1464
1613
|
current_filter = self.filter1.currentText()
|
|
1465
1614
|
self.parent().po.vars['filter_spec']['filter1_type'] = current_filter
|
|
1466
1615
|
show_param1 = "Param1" in filter_dict[current_filter].keys()
|
|
1467
|
-
self.
|
|
1468
|
-
|
|
1616
|
+
if self.advanced_mode_cb.isChecked():
|
|
1617
|
+
self.filter1_param1_label.setVisible(show_param1)
|
|
1618
|
+
self.filter1_param1.setVisible(show_param1)
|
|
1469
1619
|
if show_param1:
|
|
1470
1620
|
self.filter1_param1_label.setText(filter_dict[current_filter]['Param1']['Name'])
|
|
1471
1621
|
self.filter1_param1.setMinimum(filter_dict[current_filter]['Param1']['Minimum'])
|
|
1472
1622
|
self.filter1_param1.setMaximum(filter_dict[current_filter]['Param1']['Maximum'])
|
|
1473
|
-
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'])
|
|
1474
1625
|
if 'Param2' in list(filter_dict[current_filter].keys()):
|
|
1475
1626
|
self.filter1_param2_label.setText(filter_dict[current_filter]['Param2']['Name'])
|
|
1476
1627
|
self.filter1_param2.setMinimum(filter_dict[current_filter]['Param2']['Minimum'])
|
|
1477
1628
|
self.filter1_param2.setMaximum(filter_dict[current_filter]['Param2']['Maximum'])
|
|
1478
|
-
self.filter1_param2.
|
|
1479
|
-
|
|
1480
|
-
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)
|
|
1481
1634
|
else:
|
|
1482
1635
|
self.filter1_param2_label.setVisible(False)
|
|
1483
1636
|
self.filter1_param2.setVisible(False)
|
|
1484
1637
|
|
|
1485
1638
|
def filter1_param1_changed(self):
|
|
1639
|
+
"""
|
|
1640
|
+
Save the first parameter (most often the lower bound) of the first filter.
|
|
1641
|
+
"""
|
|
1486
1642
|
self.parent().po.vars['filter_spec']['filter1_param'][0] = float(self.filter1_param1.value())
|
|
1487
1643
|
|
|
1488
1644
|
def filter1_param2_changed(self):
|
|
1645
|
+
"""
|
|
1646
|
+
Save the second parameter (most often the higher bound) of the first filter.
|
|
1647
|
+
"""
|
|
1489
1648
|
self.parent().po.vars['filter_spec']['filter1_param'][1] = float(self.filter1_param2.value())
|
|
1490
1649
|
|
|
1491
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
|
+
"""
|
|
1492
1662
|
current_filter = self.filter2.currentText()
|
|
1493
1663
|
self.parent().po.vars['filter_spec']['filter2_type'] = current_filter
|
|
1494
1664
|
show_param1 = "Param1" in filter_dict[current_filter].keys()
|
|
1495
|
-
self.
|
|
1496
|
-
|
|
1665
|
+
if self.advanced_mode_cb.isChecked():
|
|
1666
|
+
self.filter2_param1_label.setVisible(show_param1)
|
|
1667
|
+
self.filter2_param1.setVisible(show_param1)
|
|
1497
1668
|
if show_param1:
|
|
1498
1669
|
self.filter2_param1_label.setText(filter_dict[current_filter]['Param1']['Name'])
|
|
1499
1670
|
self.filter2_param1.setMinimum(filter_dict[current_filter]['Param1']['Minimum'])
|
|
1500
1671
|
self.filter2_param1.setMaximum(filter_dict[current_filter]['Param1']['Maximum'])
|
|
1501
|
-
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'])
|
|
1502
1674
|
if 'Param2' in list(filter_dict[current_filter].keys()):
|
|
1503
1675
|
self.filter2_param2_label.setText(filter_dict[current_filter]['Param2']['Name'])
|
|
1504
1676
|
self.filter2_param2.setMinimum(filter_dict[current_filter]['Param2']['Minimum'])
|
|
1505
1677
|
self.filter2_param2.setMaximum(filter_dict[current_filter]['Param2']['Maximum'])
|
|
1506
|
-
self.filter2_param2.
|
|
1507
|
-
|
|
1508
|
-
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)
|
|
1509
1683
|
else:
|
|
1510
1684
|
self.filter2_param2_label.setVisible(False)
|
|
1511
1685
|
self.filter2_param2.setVisible(False)
|
|
1512
1686
|
|
|
1513
1687
|
def filter2_param1_changed(self):
|
|
1688
|
+
"""
|
|
1689
|
+
Save the first parameter (most often the lower bound) of the second filter.
|
|
1690
|
+
"""
|
|
1514
1691
|
self.parent().po.vars['filter_spec']['filter2_param'][0] = float(self.filter2_param1.value())
|
|
1515
1692
|
|
|
1516
1693
|
def filter2_param2_changed(self):
|
|
1694
|
+
"""
|
|
1695
|
+
Save the second parameter (most often the higher bound) of the second filter.
|
|
1696
|
+
"""
|
|
1517
1697
|
self.parent().po.vars['filter_spec']['filter2_param'][1] = float(self.filter2_param2.value())
|
|
1518
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
|
+
|
|
1519
1754
|
def logical_op_changed(self):
|
|
1520
|
-
|
|
1755
|
+
"""
|
|
1756
|
+
Handles the visibility and values of UI elements based on the current
|
|
1757
|
+
logical operator selection in a combination result dropdown.
|
|
1758
|
+
"""
|
|
1521
1759
|
if self.logical_operator_between_combination_result.currentText() == 'None':
|
|
1522
1760
|
self.row21[0].setVisible(False)
|
|
1523
1761
|
self.row21[0].setCurrentIndex(0)
|
|
@@ -1549,71 +1787,99 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1549
1787
|
self.row21[i1 + 1].setVisible(self.parent().po.all['expert_mode'])
|
|
1550
1788
|
|
|
1551
1789
|
def display_logical_operator(self):
|
|
1790
|
+
"""
|
|
1791
|
+
Displays the logical operator UI elements based on expert mode setting.
|
|
1792
|
+
"""
|
|
1552
1793
|
self.logical_operator_between_combination_result.setVisible(self.parent().po.all['expert_mode'])
|
|
1553
1794
|
self.logical_operator_label.setVisible(self.parent().po.all['expert_mode'])
|
|
1554
1795
|
|
|
1555
1796
|
def display_row2(self):
|
|
1797
|
+
"""
|
|
1798
|
+
Display or hide the second row of the csc editing widgets based on expert mode.
|
|
1799
|
+
"""
|
|
1556
1800
|
self.row1[4].setVisible(False)
|
|
1557
1801
|
for i in range(5):
|
|
1558
1802
|
self.row2[i].setVisible(self.parent().po.all['expert_mode'])
|
|
1559
1803
|
self.display_logical_operator()
|
|
1560
1804
|
|
|
1561
1805
|
def display_row3(self):
|
|
1806
|
+
"""
|
|
1807
|
+
Display or hide the third row of the csc editing widgets based on expert mode.
|
|
1808
|
+
"""
|
|
1562
1809
|
self.row2[4].setVisible(False)
|
|
1563
1810
|
for i in range(4):
|
|
1564
1811
|
self.row3[i].setVisible(self.parent().po.all['expert_mode'])
|
|
1565
1812
|
self.display_logical_operator()
|
|
1566
1813
|
|
|
1567
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
|
+
"""
|
|
1568
1818
|
self.row21[4].setVisible(False)
|
|
1569
1819
|
for i in range(5):
|
|
1570
1820
|
self.row22[i].setVisible(self.parent().po.all['expert_mode'])
|
|
1571
1821
|
self.display_logical_operator()
|
|
1572
1822
|
|
|
1573
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
|
+
"""
|
|
1574
1827
|
self.row22[4].setVisible(False)
|
|
1575
1828
|
for i in range(4):
|
|
1576
1829
|
self.row23[i].setVisible(self.parent().po.all['expert_mode'])
|
|
1577
1830
|
self.display_logical_operator()
|
|
1578
1831
|
|
|
1579
1832
|
def update_csc_editing_display(self):
|
|
1580
|
-
|
|
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
|
+
"""
|
|
1581
1841
|
remaining_c_spaces = []
|
|
1582
1842
|
row_number1 = 0
|
|
1583
1843
|
row_number2 = 0
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
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
|
|
1604
1863
|
else:
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
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'])
|
|
1617
1883
|
|
|
1618
1884
|
# If not all color space combinations are filled, put None and 0 in boxes
|
|
1619
1885
|
if row_number1 < 3:
|
|
@@ -1674,6 +1940,9 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1674
1940
|
self.message.setText(f'')
|
|
1675
1941
|
|
|
1676
1942
|
def save_user_defined_csc(self):
|
|
1943
|
+
"""
|
|
1944
|
+
Save user-defined combination of color spaces and channels.
|
|
1945
|
+
"""
|
|
1677
1946
|
self.csc_dict = {}
|
|
1678
1947
|
spaces = np.array((self.row1[0].currentText(), self.row2[0].currentText(), self.row3[0].currentText()))
|
|
1679
1948
|
channels = np.array(
|
|
@@ -1699,20 +1968,24 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1699
1968
|
for i, space in enumerate(spaces):
|
|
1700
1969
|
if space != "None" and space != "None2":
|
|
1701
1970
|
self.csc_dict[space] = channels[i, :]
|
|
1702
|
-
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):
|
|
1703
1972
|
self.csc_dict_is_empty = True
|
|
1704
1973
|
else:
|
|
1705
1974
|
self.csc_dict_is_empty = False
|
|
1706
1975
|
|
|
1707
|
-
def
|
|
1708
|
-
|
|
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()
|
|
1709
1981
|
|
|
1710
1982
|
def display_more_than_two_colors_option(self):
|
|
1711
|
-
"""
|
|
1983
|
+
"""
|
|
1984
|
+
Display the More Than Two Colors Options
|
|
1712
1985
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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
|
+
"""
|
|
1716
1989
|
if self.bio_masks_number > 0 and self.advanced_mode_cb.isChecked():
|
|
1717
1990
|
self.more_than_two_colors.setVisible(True)
|
|
1718
1991
|
self.more_than_two_colors_label.setVisible(True)
|
|
@@ -1734,9 +2007,25 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1734
2007
|
# self.parent().po.vars["color_number"] = 2
|
|
1735
2008
|
|
|
1736
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
|
+
"""
|
|
1737
2017
|
self.parent().po.vars["color_number"] = int(self.distinct_colors_number.value())
|
|
1738
2018
|
|
|
1739
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
|
+
"""
|
|
1740
2029
|
if not self.thread['CropScaleSubtractDelineate'].isRunning():
|
|
1741
2030
|
self.message.setText("Looking for each arena contour, wait...")
|
|
1742
2031
|
self.thread['CropScaleSubtractDelineate'].start()
|
|
@@ -1745,13 +2034,10 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1745
2034
|
|
|
1746
2035
|
self.yes.setVisible(False)
|
|
1747
2036
|
self.no.setVisible(False)
|
|
1748
|
-
# self.times_clicked_yes += 1
|
|
1749
2037
|
self.reinitialize_bio_and_back_legend()
|
|
1750
2038
|
self.user_drawn_lines_label.setVisible(False)
|
|
1751
2039
|
self.cell.setVisible(False)
|
|
1752
2040
|
self.background.setVisible(False)
|
|
1753
|
-
# self.sample_number.setVisible(False)
|
|
1754
|
-
# self.sample_number_label.setVisible(False)
|
|
1755
2041
|
self.one_blob_per_arena.setVisible(False)
|
|
1756
2042
|
self.one_blob_per_arena_label.setVisible(False)
|
|
1757
2043
|
self.set_spot_shape.setVisible(False)
|
|
@@ -1764,14 +2050,17 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1764
2050
|
self.advanced_mode_cb.setVisible(False)
|
|
1765
2051
|
self.advanced_mode_label.setVisible(False)
|
|
1766
2052
|
self.generate_analysis_options.setVisible(False)
|
|
1767
|
-
self.
|
|
1768
|
-
self.
|
|
2053
|
+
self.network_shaped.setVisible(False)
|
|
2054
|
+
self.basic.setVisible(False)
|
|
1769
2055
|
self.visualize.setVisible(False)
|
|
1770
2056
|
self.visualize_label.setVisible(False)
|
|
1771
2057
|
self.select_option.setVisible(False)
|
|
1772
2058
|
self.select_option_label.setVisible(False)
|
|
1773
2059
|
|
|
1774
|
-
def delineate_is_done(self, message):
|
|
2060
|
+
def delineate_is_done(self, message: str):
|
|
2061
|
+
"""
|
|
2062
|
+
Update GUI after delineation is complete.
|
|
2063
|
+
"""
|
|
1775
2064
|
logging.info("Delineation is done, update GUI")
|
|
1776
2065
|
self.message.setText(message)
|
|
1777
2066
|
self.arena_shape_label.setVisible(False)
|
|
@@ -1793,6 +2082,12 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1793
2082
|
self.asking_delineation_flag = True
|
|
1794
2083
|
|
|
1795
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
|
+
"""
|
|
1796
2091
|
# Remove this flag to not draw it again next time UpdateImage runs for another reason
|
|
1797
2092
|
self.delineation_done = False
|
|
1798
2093
|
self.auto_delineation_flag = False
|
|
@@ -1802,22 +2097,31 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1802
2097
|
self.arena_shape_label.setVisible(True)
|
|
1803
2098
|
self.arena_shape.setVisible(True)
|
|
1804
2099
|
|
|
1805
|
-
self.decision_label.setText('Is
|
|
2100
|
+
self.decision_label.setText('Is arena delineation correct?')
|
|
2101
|
+
self.decision_label.setToolTip(IAW["Video_delimitation"]["tips"])
|
|
1806
2102
|
self.decision_label.setVisible(True)
|
|
1807
|
-
# self.message.setText('If not, restart the analysis (Previous) or manually draw each arena (No)')
|
|
1808
2103
|
self.user_drawn_lines_label.setText('Draw each arena on the image')
|
|
1809
2104
|
self.yes.setVisible(True)
|
|
1810
2105
|
self.no.setVisible(True)
|
|
1811
|
-
try:
|
|
1812
|
-
self.thread["UpdateImage"].message_when_thread_finished.disconnect()
|
|
1813
|
-
except RuntimeError:
|
|
1814
|
-
pass
|
|
1815
2106
|
|
|
1816
|
-
|
|
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
|
+
"""
|
|
1817
2118
|
self.message.setText(text_from_thread)
|
|
1818
2119
|
|
|
1819
2120
|
def starting_differs_from_growing_check(self):
|
|
1820
|
-
|
|
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:
|
|
1821
2125
|
self.parent().po.vars['origin_state'] = 'invisible'
|
|
1822
2126
|
else:
|
|
1823
2127
|
if self.starting_differs_from_growing_cb.isChecked():
|
|
@@ -1826,16 +2130,39 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1826
2130
|
self.parent().po.vars['origin_state'] = 'fluctuating'
|
|
1827
2131
|
|
|
1828
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
|
+
"""
|
|
1829
2138
|
if not self.is_image_analysis_running:
|
|
1830
2139
|
# self.message.setText('Loading, wait...')
|
|
1831
2140
|
self.decision_tree(True)
|
|
1832
2141
|
|
|
1833
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
|
+
"""
|
|
1834
2148
|
if not self.is_image_analysis_running:
|
|
1835
|
-
# self.message.setText('Loading, wait...')
|
|
1836
2149
|
self.decision_tree(False)
|
|
1837
2150
|
|
|
1838
|
-
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
|
+
"""
|
|
1839
2166
|
color_analysis = not self.parent().po.vars['already_greyscale']
|
|
1840
2167
|
if self.is_first_image_flag:
|
|
1841
2168
|
if self.asking_first_im_parameters_flag:
|
|
@@ -1851,6 +2178,7 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1851
2178
|
|
|
1852
2179
|
# Is automatic Video delineation correct?
|
|
1853
2180
|
elif self.asking_delineation_flag:
|
|
2181
|
+
self.decision_label.setToolTip("")
|
|
1854
2182
|
if not is_yes:
|
|
1855
2183
|
self.asking_slower_or_manual_delineation()
|
|
1856
2184
|
else:
|
|
@@ -1859,6 +2187,7 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1859
2187
|
|
|
1860
2188
|
# Slower or manual delineation?
|
|
1861
2189
|
elif self.asking_slower_or_manual_delineation_flag:
|
|
2190
|
+
self.back1_bio2 = 0
|
|
1862
2191
|
if not is_yes:
|
|
1863
2192
|
self.manual_delineation()
|
|
1864
2193
|
else:
|
|
@@ -1887,17 +2216,19 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1887
2216
|
f"{self.arena_masks_number} arenas are drawn over the {self.parent().po.sample_number} expected")
|
|
1888
2217
|
|
|
1889
2218
|
elif self.asking_last_image_flag:
|
|
2219
|
+
self.decision_label.setToolTip("")
|
|
1890
2220
|
self.parent().po.first_image.im_combinations = None
|
|
1891
2221
|
self.select_option.clear()
|
|
1892
2222
|
self.arena_shape.setVisible(False)
|
|
1893
2223
|
self.arena_shape_label.setVisible(False)
|
|
1894
2224
|
if is_yes:
|
|
1895
2225
|
self.start_last_image()
|
|
1896
|
-
# if self.parent().po.vars['origin_state'] != 'invisible':
|
|
1897
|
-
# self.parent().po.vars['origin_state'] = "constant"
|
|
1898
2226
|
else:
|
|
1899
|
-
|
|
1900
|
-
|
|
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}
|
|
1901
2232
|
self.parent().po.vars['convert_for_origin'] = deepcopy(self.csc_dict)
|
|
1902
2233
|
self.parent().po.vars['convert_for_motion'] = deepcopy(self.csc_dict)
|
|
1903
2234
|
self.go_to_next_widget()
|
|
@@ -1908,13 +2239,18 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1908
2239
|
self.go_to_next_widget()
|
|
1909
2240
|
|
|
1910
2241
|
def first_im_parameters(self):
|
|
1911
|
-
"""
|
|
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
|
+
"""
|
|
1912
2250
|
self.step = 1
|
|
1913
2251
|
self.decision_label.setText("Adjust settings, draw more cells and background, and try again")
|
|
1914
2252
|
self.yes.setVisible(False)
|
|
1915
2253
|
self.no.setVisible(False)
|
|
1916
|
-
# self.one_blob_per_arena.setVisible(True)
|
|
1917
|
-
# self.one_blob_per_arena_label.setVisible(True)
|
|
1918
2254
|
self.set_spot_shape.setVisible(True)
|
|
1919
2255
|
self.spot_shape_label.setVisible(True)
|
|
1920
2256
|
self.spot_shape.setVisible(self.parent().po.all['set_spot_shape'])
|
|
@@ -1922,14 +2258,22 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1922
2258
|
self.spot_size_label.setVisible(self.one_blob_per_arena.isChecked())
|
|
1923
2259
|
self.spot_size.setVisible(
|
|
1924
2260
|
self.one_blob_per_arena.isChecked() and self.set_spot_size.isChecked())
|
|
1925
|
-
# self.arena_shape.setVisible(True)
|
|
1926
|
-
# self.arena_shape_label.setVisible(True)
|
|
1927
2261
|
self.auto_delineation_flag = True
|
|
1928
2262
|
self.first_im_parameters_answered = True
|
|
1929
2263
|
|
|
1930
2264
|
def auto_delineation(self):
|
|
1931
|
-
"""
|
|
1932
|
-
|
|
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)
|
|
1933
2277
|
# The automatic delineation algorithm cannot handle this situation
|
|
1934
2278
|
if self.parent().po.vars['several_blob_per_arena'] and self.parent().po.sample_number > 1:
|
|
1935
2279
|
self.manual_delineation()
|
|
@@ -1944,11 +2288,29 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1944
2288
|
self.visualize.setVisible(False)
|
|
1945
2289
|
|
|
1946
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
|
+
"""
|
|
1947
2303
|
self.asking_slower_or_manual_delineation_flag = True
|
|
1948
|
-
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")
|
|
1949
2305
|
self.message.setText(f"Clicking no will allow you to draw each arena manually")
|
|
1950
2306
|
|
|
1951
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
|
+
"""
|
|
1952
2314
|
self.decision_label.setText(f"")
|
|
1953
2315
|
self.arena_shape.setVisible(False)
|
|
1954
2316
|
self.arena_shape_label.setVisible(False)
|
|
@@ -1958,7 +2320,10 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1958
2320
|
self.start_crop_scale_subtract_delineate()
|
|
1959
2321
|
|
|
1960
2322
|
def manual_delineation(self):
|
|
1961
|
-
"""
|
|
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
|
+
"""
|
|
1962
2327
|
self.manual_delineation_flag = True
|
|
1963
2328
|
self.parent().po.cropping(is_first_image=True)
|
|
1964
2329
|
self.parent().po.get_average_pixel_size()
|
|
@@ -1978,8 +2343,8 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1978
2343
|
self.one_blob_per_arena.setVisible(False)
|
|
1979
2344
|
self.one_blob_per_arena_label.setVisible(False)
|
|
1980
2345
|
self.generate_analysis_options.setVisible(False)
|
|
1981
|
-
self.
|
|
1982
|
-
self.
|
|
2346
|
+
self.network_shaped.setVisible(False)
|
|
2347
|
+
self.basic.setVisible(False)
|
|
1983
2348
|
self.visualize.setVisible(False)
|
|
1984
2349
|
self.visualize_label.setVisible(False)
|
|
1985
2350
|
self.select_option.setVisible(False)
|
|
@@ -1987,28 +2352,43 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
1987
2352
|
self.user_drawn_lines_label.setText("Draw each arena")
|
|
1988
2353
|
self.user_drawn_lines_label.setVisible(True)
|
|
1989
2354
|
self.decision_label.setText(
|
|
1990
|
-
f"Hold click to draw {self.parent().po.sample_number}
|
|
1991
|
-
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.')
|
|
1992
2357
|
|
|
1993
2358
|
def last_image_question(self):
|
|
1994
|
-
"""
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
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
|
+
|
|
2002
2366
|
self.image_number.setVisible(False)
|
|
2003
2367
|
self.image_number_label.setVisible(False)
|
|
2004
2368
|
self.read.setVisible(False)
|
|
2005
|
-
self.asking_last_image_flag = True
|
|
2006
|
-
# self.title_label.setVisible(False)
|
|
2007
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)
|
|
2008
2382
|
|
|
2009
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
|
+
"""
|
|
2010
2391
|
self.is_first_image_flag = False
|
|
2011
|
-
# self.parent().po.vars["color_number"] = 2
|
|
2012
2392
|
self.decision_label.setText('')
|
|
2013
2393
|
self.yes.setVisible(False)
|
|
2014
2394
|
self.no.setVisible(False)
|
|
@@ -2020,11 +2400,9 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
2020
2400
|
if self.thread['SaveManualDelineation'].isRunning():
|
|
2021
2401
|
self.thread['SaveManualDelineation'].wait()
|
|
2022
2402
|
self.parent().po.cropping(is_first_image=False)
|
|
2023
|
-
# self.parent().po.last_image = OneImageAnalysis(self.parent().po.last_im)
|
|
2024
2403
|
self.reinitialize_image_and_masks(self.parent().po.last_image.bgr)
|
|
2025
2404
|
self.reinitialize_bio_and_back_legend()
|
|
2026
2405
|
self.parent().po.current_combination_id = 0
|
|
2027
|
-
# self.advanced_mode_cb.setChecked(True)
|
|
2028
2406
|
self.visualize_is_clicked()
|
|
2029
2407
|
self.user_drawn_lines_label.setText('Select and draw')
|
|
2030
2408
|
self.user_drawn_lines_label.setVisible(True)
|
|
@@ -2035,12 +2413,33 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
2035
2413
|
self.visualize_label.setVisible(True)
|
|
2036
2414
|
self.visualize.setVisible(True)
|
|
2037
2415
|
self.row1_widget.setVisible(False)
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
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.")
|
|
2041
2430
|
|
|
2042
2431
|
def go_to_next_widget(self):
|
|
2043
|
-
|
|
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():
|
|
2044
2443
|
|
|
2045
2444
|
self.popup = QtWidgets.QMessageBox()
|
|
2046
2445
|
self.popup.setWindowTitle("Info")
|
|
@@ -2056,14 +2455,14 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
2056
2455
|
|
|
2057
2456
|
self.message.setText(f"Final checks, wait... ")
|
|
2058
2457
|
self.parent().last_tab = "image_analysis"
|
|
2059
|
-
self.thread['
|
|
2458
|
+
self.thread['PrepareVideoAnalysis'].start()
|
|
2060
2459
|
if self.parent().po.vars["color_number"] > 2:
|
|
2061
2460
|
self.parent().videoanalysiswindow.select_option.clear()
|
|
2062
2461
|
self.parent().videoanalysiswindow.select_option.addItem(f"1) Kmeans")
|
|
2063
2462
|
self.parent().videoanalysiswindow.select_option.setCurrentIndex(0)
|
|
2064
2463
|
self.parent().po.all['video_option'] = 0
|
|
2065
2464
|
time.sleep(1 / 10)
|
|
2066
|
-
self.thread['
|
|
2465
|
+
self.thread['PrepareVideoAnalysis'].wait()
|
|
2067
2466
|
self.message.setText(f"")
|
|
2068
2467
|
|
|
2069
2468
|
self.video_tab.set_not_in_use()
|
|
@@ -2073,15 +2472,7 @@ class ImageAnalysisWindow(MainTabsType):
|
|
|
2073
2472
|
self.popup.close()
|
|
2074
2473
|
|
|
2075
2474
|
def closeEvent(self, event):
|
|
2475
|
+
"""
|
|
2476
|
+
Handle the close event for a QWidget.
|
|
2477
|
+
"""
|
|
2076
2478
|
event.accept
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
# if __name__ == "__main__":
|
|
2080
|
-
# from cellects.gui.cellects import CellectsMainWidget
|
|
2081
|
-
# import sys
|
|
2082
|
-
# app = QtWidgets.QApplication([])
|
|
2083
|
-
# parent = CellectsMainWidget()
|
|
2084
|
-
# session = ImageAnalysisWindow(parent, False)
|
|
2085
|
-
# parent.insertWidget(0, session)
|
|
2086
|
-
# parent.show()
|
|
2087
|
-
# sys.exit(app.exec())
|