celldetective 1.3.3.post1__py3-none-any.whl → 1.3.4.post1__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.
- celldetective/__main__.py +30 -4
- celldetective/_version.py +1 -1
- celldetective/extra_properties.py +21 -0
- celldetective/filters.py +15 -2
- celldetective/gui/InitWindow.py +28 -34
- celldetective/gui/analyze_block.py +3 -498
- celldetective/gui/classifier_widget.py +1 -1
- celldetective/gui/control_panel.py +98 -27
- celldetective/gui/generic_signal_plot.py +35 -18
- celldetective/gui/gui_utils.py +143 -2
- celldetective/gui/layouts.py +7 -6
- celldetective/gui/measurement_options.py +3 -11
- celldetective/gui/plot_measurements.py +5 -13
- celldetective/gui/plot_signals_ui.py +30 -30
- celldetective/gui/process_block.py +61 -103
- celldetective/gui/signal_annotator.py +50 -32
- celldetective/gui/signal_annotator2.py +7 -4
- celldetective/gui/styles.py +13 -0
- celldetective/gui/survival_ui.py +8 -21
- celldetective/gui/tableUI.py +1 -2
- celldetective/gui/thresholds_gui.py +0 -6
- celldetective/gui/viewers.py +1 -5
- celldetective/io.py +31 -4
- celldetective/measure.py +8 -5
- celldetective/neighborhood.py +0 -2
- celldetective/scripts/measure_cells.py +21 -9
- celldetective/signals.py +78 -66
- celldetective/tracking.py +19 -13
- {celldetective-1.3.3.post1.dist-info → celldetective-1.3.4.post1.dist-info}/METADATA +2 -1
- {celldetective-1.3.3.post1.dist-info → celldetective-1.3.4.post1.dist-info}/RECORD +35 -35
- tests/test_qt.py +5 -3
- {celldetective-1.3.3.post1.dist-info → celldetective-1.3.4.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.3.3.post1.dist-info → celldetective-1.3.4.post1.dist-info}/WHEEL +0 -0
- {celldetective-1.3.3.post1.dist-info → celldetective-1.3.4.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.3.post1.dist-info → celldetective-1.3.4.post1.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import QMainWindow,
|
|
1
|
+
from PyQt5.QtWidgets import QMainWindow, QPushButton, QHBoxLayout, QLabel, QWidget, QGridLayout, QFrame, \
|
|
2
2
|
QTabWidget, QVBoxLayout, QMessageBox, QScrollArea, QDesktopWidget
|
|
3
3
|
from PyQt5.QtCore import Qt, QSize
|
|
4
|
-
from celldetective.gui.gui_utils import center_window, QHSeperationLine
|
|
4
|
+
from celldetective.gui.gui_utils import center_window, QHSeperationLine, QCheckableComboBox
|
|
5
5
|
from celldetective.utils import _extract_labels_from_config, ConfigSectionMap, extract_experiment_channels, extract_identity_col
|
|
6
6
|
from celldetective.gui import ConfigEditor, ProcessPanel, PreprocessingPanel, AnalysisPanel, NeighPanel
|
|
7
7
|
from celldetective.io import get_experiment_wells, get_config, get_spatial_calibration, get_temporal_calibration, get_experiment_concentrations, get_experiment_cell_types, get_experiment_antibodies, get_experiment_pharmaceutical_agents
|
|
@@ -18,6 +18,7 @@ from celldetective.utils import extract_experiment_channels
|
|
|
18
18
|
from celldetective.gui import Styles
|
|
19
19
|
import pandas as pd
|
|
20
20
|
|
|
21
|
+
|
|
21
22
|
class ControlPanel(QMainWindow, Styles):
|
|
22
23
|
|
|
23
24
|
def __init__(self, parent_window=None, exp_dir=""):
|
|
@@ -90,7 +91,7 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
90
91
|
self.initial_width = self.size().width()
|
|
91
92
|
self.screen_height = desktop.screenGeometry().height()
|
|
92
93
|
self.screen_width = desktop.screenGeometry().width()
|
|
93
|
-
self.scroll.setMinimumWidth(
|
|
94
|
+
self.scroll.setMinimumWidth(440)
|
|
94
95
|
|
|
95
96
|
def init_wells_and_positions(self):
|
|
96
97
|
|
|
@@ -138,23 +139,23 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
138
139
|
self.edit_config_button.clicked.connect(self.open_config_editor)
|
|
139
140
|
self.edit_config_button.setStyleSheet(self.button_select_all)
|
|
140
141
|
|
|
141
|
-
self.well_list =
|
|
142
|
+
self.well_list = QCheckableComboBox(obj='well', parent_window=self)
|
|
142
143
|
thresh = 32
|
|
143
|
-
self.well_truncated = [w[:thresh - 3]+'...' if len(w)>thresh else w for w in self.well_labels]
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
self.well_list.setItemData(i, self.well_labels[i], Qt.ToolTipRole)
|
|
147
|
-
self.well_list.addItems(["*"])
|
|
148
|
-
self.well_list.activated.connect(self.display_positions)
|
|
149
|
-
self.to_disable.append(self.well_list)
|
|
144
|
+
self.well_truncated = [w[:thresh - 3]+'...' if len(w)>thresh else w for w in self.well_labels]
|
|
145
|
+
for i in range(len(self.well_truncated)):
|
|
146
|
+
self.well_list.addItem(self.well_truncated[i], tooltip=self.well_labels[i])
|
|
150
147
|
|
|
151
|
-
self.position_list =
|
|
152
|
-
self.position_list.addItems(["*"])
|
|
148
|
+
self.position_list = QCheckableComboBox(obj='position', parent_window=self)
|
|
153
149
|
self.position_list.addItems(self.positions[0])
|
|
154
|
-
self.position_list.activated.connect(self.update_position_options)
|
|
155
150
|
self.to_disable.append(self.position_list)
|
|
156
151
|
#self.locate_selected_position()
|
|
157
152
|
|
|
153
|
+
self.well_list.activated.connect(self.display_positions)
|
|
154
|
+
self.well_list.setCurrentIndex(0)
|
|
155
|
+
|
|
156
|
+
self.position_list.activated.connect(self.update_position_options)
|
|
157
|
+
self.position_list.setCurrentIndex(0)
|
|
158
|
+
|
|
158
159
|
self.view_stack_btn = QPushButton()
|
|
159
160
|
self.view_stack_btn.setStyleSheet(self.button_select_all)
|
|
160
161
|
self.view_stack_btn.setIcon(icon(MDI6.image_check, color="black"))
|
|
@@ -163,6 +164,24 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
163
164
|
self.view_stack_btn.clicked.connect(self.view_current_stack)
|
|
164
165
|
self.view_stack_btn.setEnabled(False)
|
|
165
166
|
|
|
167
|
+
self.select_all_wells_btn = QPushButton()
|
|
168
|
+
self.select_all_wells_btn.setIcon(icon(MDI6.select_all,color="black"))
|
|
169
|
+
self.select_all_wells_btn.setIconSize(QSize(20, 20))
|
|
170
|
+
self.select_all_wells_btn.setToolTip("Select all wells.")
|
|
171
|
+
self.select_all_wells_btn.clicked.connect(self.select_all_wells)
|
|
172
|
+
self.select_all_wells_btn.setStyleSheet(self.button_select_all)
|
|
173
|
+
self.select_all_wells_option = False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
self.select_all_pos_btn = QPushButton()
|
|
177
|
+
self.select_all_pos_btn.setIcon(icon(MDI6.select_all,color="black"))
|
|
178
|
+
self.select_all_pos_btn.setIconSize(QSize(20, 20))
|
|
179
|
+
self.select_all_pos_btn.setToolTip("Select all positions.")
|
|
180
|
+
self.select_all_pos_btn.clicked.connect(self.select_all_positions)
|
|
181
|
+
self.select_all_pos_btn.setStyleSheet(self.button_select_all)
|
|
182
|
+
self.select_all_pos_option = False
|
|
183
|
+
|
|
184
|
+
|
|
166
185
|
well_lbl = QLabel('Well: ')
|
|
167
186
|
well_lbl.setAlignment(Qt.AlignRight)
|
|
168
187
|
|
|
@@ -196,20 +215,52 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
196
215
|
# Well row
|
|
197
216
|
well_hbox = QHBoxLayout()
|
|
198
217
|
well_hbox.addWidget(well_lbl, 25, alignment=Qt.AlignVCenter)
|
|
199
|
-
|
|
218
|
+
well_subhbox = QHBoxLayout()
|
|
219
|
+
well_subhbox.addWidget(self.well_list, 95)
|
|
220
|
+
well_subhbox.addWidget(self.select_all_wells_btn, 5)
|
|
221
|
+
well_hbox.addLayout(well_subhbox, 75)
|
|
200
222
|
vbox.addLayout(well_hbox)
|
|
201
223
|
|
|
202
224
|
# Position row
|
|
203
225
|
position_hbox = QHBoxLayout()
|
|
204
226
|
position_hbox.addWidget(pos_lbl, 25, alignment=Qt.AlignVCenter)
|
|
205
227
|
pos_subhbox = QHBoxLayout()
|
|
206
|
-
pos_subhbox.addWidget(self.position_list,
|
|
228
|
+
pos_subhbox.addWidget(self.position_list, 90)
|
|
229
|
+
pos_subhbox.addWidget(self.select_all_pos_btn, 5)
|
|
207
230
|
pos_subhbox.addWidget(self.view_stack_btn, 5)
|
|
208
231
|
position_hbox.addLayout(pos_subhbox, 75)
|
|
209
232
|
vbox.addLayout(position_hbox)
|
|
210
233
|
|
|
211
234
|
vbox.addWidget(hsep)
|
|
212
235
|
|
|
236
|
+
def select_all_wells(self):
|
|
237
|
+
|
|
238
|
+
if not self.select_all_wells_option:
|
|
239
|
+
self.well_list.selectAll()
|
|
240
|
+
self.select_all_wells_option = True
|
|
241
|
+
self.select_all_wells_btn.setIcon(icon(MDI6.select_all,color=self.celldetective_blue))
|
|
242
|
+
self.select_all_wells_btn.setIconSize(QSize(20, 20))
|
|
243
|
+
self.display_positions()
|
|
244
|
+
else:
|
|
245
|
+
self.well_list.unselectAll()
|
|
246
|
+
self.select_all_wells_option = False
|
|
247
|
+
self.select_all_wells_btn.setIcon(icon(MDI6.select_all,color="black"))
|
|
248
|
+
self.select_all_wells_btn.setIconSize(QSize(20, 20))
|
|
249
|
+
self.display_positions()
|
|
250
|
+
|
|
251
|
+
def select_all_positions(self):
|
|
252
|
+
|
|
253
|
+
if not self.select_all_pos_option:
|
|
254
|
+
self.position_list.selectAll()
|
|
255
|
+
self.select_all_pos_option = True
|
|
256
|
+
self.select_all_pos_btn.setIcon(icon(MDI6.select_all,color=self.celldetective_blue))
|
|
257
|
+
self.select_all_pos_btn.setIconSize(QSize(20, 20))
|
|
258
|
+
else:
|
|
259
|
+
self.position_list.unselectAll()
|
|
260
|
+
self.select_all_pos_option = False
|
|
261
|
+
self.select_all_pos_btn.setIcon(icon(MDI6.select_all,color="black"))
|
|
262
|
+
self.select_all_pos_btn.setIconSize(QSize(20, 20))
|
|
263
|
+
|
|
213
264
|
def locate_image(self):
|
|
214
265
|
|
|
215
266
|
"""
|
|
@@ -341,17 +392,30 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
341
392
|
Show the positions as the well is changed.
|
|
342
393
|
"""
|
|
343
394
|
|
|
344
|
-
if self.well_list.
|
|
395
|
+
if self.well_list.isMultipleSelection():
|
|
396
|
+
|
|
345
397
|
self.position_list.clear()
|
|
346
|
-
self.position_list.addItems(["*"])
|
|
347
398
|
position_linspace = np.linspace(0,len(self.positions[0])-1,len(self.positions[0]),dtype=int)
|
|
348
399
|
position_linspace = [str(s) for s in position_linspace]
|
|
349
400
|
self.position_list.addItems(position_linspace)
|
|
401
|
+
if self.select_all_pos_option:
|
|
402
|
+
self.select_all_pos_btn.click()
|
|
403
|
+
self.select_all_pos_btn.click()
|
|
404
|
+
|
|
405
|
+
elif not self.well_list.isAnySelected():
|
|
406
|
+
|
|
407
|
+
self.position_list.unselectAll()
|
|
408
|
+
if self.select_all_pos_option:
|
|
409
|
+
self.select_all_pos_btn.click()
|
|
410
|
+
|
|
350
411
|
else:
|
|
351
|
-
pos_index = self.well_list.
|
|
412
|
+
pos_index = self.well_list.getSelectedIndices()[0]
|
|
352
413
|
self.position_list.clear()
|
|
353
|
-
self.position_list.addItems(["*"])
|
|
354
414
|
self.position_list.addItems(self.positions[pos_index])
|
|
415
|
+
if self.select_all_pos_option:
|
|
416
|
+
self.select_all_pos_btn.click()
|
|
417
|
+
self.position_list.setCurrentIndex(0)
|
|
418
|
+
|
|
355
419
|
self.update_position_options()
|
|
356
420
|
|
|
357
421
|
def open_config_editor(self):
|
|
@@ -366,7 +430,7 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
366
430
|
|
|
367
431
|
"""
|
|
368
432
|
|
|
369
|
-
if self.well_list.
|
|
433
|
+
if self.well_list.isMultipleSelection():
|
|
370
434
|
msgBox = QMessageBox()
|
|
371
435
|
msgBox.setIcon(QMessageBox.Critical)
|
|
372
436
|
msgBox.setText("Please select a single well...")
|
|
@@ -376,12 +440,12 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
376
440
|
if returnValue == QMessageBox.Ok:
|
|
377
441
|
return False
|
|
378
442
|
else:
|
|
379
|
-
self.well_index = [self.well_list.currentIndex()]
|
|
443
|
+
self.well_index = self.well_list.getSelectedIndices() #[self.well_list.currentIndex()]
|
|
380
444
|
|
|
381
445
|
for w_idx in self.well_index:
|
|
382
446
|
|
|
383
447
|
pos = self.positions[w_idx]
|
|
384
|
-
if self.position_list.
|
|
448
|
+
if not self.position_list.isSingleSelection():
|
|
385
449
|
msgBox = QMessageBox()
|
|
386
450
|
msgBox.setIcon(QMessageBox.Critical)
|
|
387
451
|
msgBox.setText("Please select a single position...")
|
|
@@ -391,11 +455,12 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
391
455
|
if returnValue == QMessageBox.Ok:
|
|
392
456
|
return False
|
|
393
457
|
else:
|
|
394
|
-
pos_indices =
|
|
458
|
+
pos_indices = self.position_list.getSelectedIndices()
|
|
395
459
|
|
|
396
460
|
well = self.wells[w_idx]
|
|
397
461
|
|
|
398
462
|
for pos_idx in pos_indices:
|
|
463
|
+
|
|
399
464
|
self.pos = natsorted(glob(well+f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*{os.sep}"))[pos_idx]
|
|
400
465
|
if not os.path.exists(self.pos + 'output'):
|
|
401
466
|
os.mkdir(self.pos + 'output')
|
|
@@ -405,6 +470,7 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
405
470
|
return True
|
|
406
471
|
|
|
407
472
|
def create_config_dir(self):
|
|
473
|
+
|
|
408
474
|
self.config_folder = self.exp_dir+'configs'+os.sep
|
|
409
475
|
if not os.path.exists(self.config_folder):
|
|
410
476
|
os.mkdir(self.config_folder)
|
|
@@ -413,7 +479,8 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
413
479
|
|
|
414
480
|
self.pos = self.position_list.currentText()
|
|
415
481
|
panels = [self.ProcessEffectors, self.ProcessTargets]
|
|
416
|
-
|
|
482
|
+
|
|
483
|
+
if self.position_list.isMultipleSelection() or not self.position_list.isAnySelected():
|
|
417
484
|
|
|
418
485
|
for p in panels:
|
|
419
486
|
p.check_seg_btn.setEnabled(False)
|
|
@@ -443,7 +510,9 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
443
510
|
self.ProcessEffectors.delete_tracks_btn.hide()
|
|
444
511
|
|
|
445
512
|
self.view_stack_btn.setEnabled(False)
|
|
446
|
-
|
|
513
|
+
|
|
514
|
+
elif self.well_list.isMultipleSelection():
|
|
515
|
+
|
|
447
516
|
self.ProcessTargets.view_tab_btn.setEnabled(True)
|
|
448
517
|
self.ProcessEffectors.view_tab_btn.setEnabled(True)
|
|
449
518
|
self.NeighPanel.view_tab_btn.setEnabled(True)
|
|
@@ -455,7 +524,9 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
455
524
|
self.ProcessTargets.delete_tracks_btn.hide()
|
|
456
525
|
self.ProcessEffectors.delete_tracks_btn.hide()
|
|
457
526
|
else:
|
|
458
|
-
|
|
527
|
+
|
|
528
|
+
if self.well_list.isAnySelected() and self.position_list.isAnySelected():
|
|
529
|
+
|
|
459
530
|
self.locate_selected_position()
|
|
460
531
|
self.view_stack_btn.setEnabled(True)
|
|
461
532
|
# if os.path.exists(os.sep.join([self.pos,'labels_effectors', os.sep])):
|
|
@@ -4,12 +4,12 @@ from PyQt5.QtWidgets import QMessageBox,QGridLayout, QButtonGroup, \
|
|
|
4
4
|
from PyQt5.QtCore import Qt, QSize
|
|
5
5
|
from PyQt5.QtGui import QDoubleValidator
|
|
6
6
|
|
|
7
|
-
from celldetective.gui.gui_utils import center_window, FigureCanvas, ExportPlotBtn
|
|
7
|
+
from celldetective.gui.gui_utils import center_window, FigureCanvas, ExportPlotBtn, QuickSliderLayout
|
|
8
8
|
from celldetective.gui.tableUI import TableUI
|
|
9
9
|
from celldetective.io import collect_experiment_metadata
|
|
10
10
|
|
|
11
11
|
from superqt.fonticon import icon
|
|
12
|
-
from superqt import QLabeledSlider
|
|
12
|
+
from superqt import QLabeledSlider, QLabeledDoubleSlider
|
|
13
13
|
from fonticon_mdi6 import MDI6
|
|
14
14
|
import numpy as np
|
|
15
15
|
import json
|
|
@@ -81,16 +81,21 @@ class GenericSignalPlotWidget(QWidget, Styles):
|
|
|
81
81
|
radio_subhbox.addWidget(self.plot_options[i], 33, alignment=Qt.AlignCenter)
|
|
82
82
|
|
|
83
83
|
if self.parent_window.position_indices is not None:
|
|
84
|
+
# at least a position is selected
|
|
84
85
|
if len(self.parent_window.well_indices)>1 and len(self.parent_window.position_indices)==1:
|
|
86
|
+
# several wells but one position
|
|
85
87
|
self.plot_btn_group.buttons()[0].click()
|
|
86
|
-
for i in [1,2]:
|
|
87
|
-
|
|
88
|
+
# for i in [1,2]:
|
|
89
|
+
# self.plot_options[i].setEnabled(False)
|
|
88
90
|
elif len(self.parent_window.well_indices)>1:
|
|
89
91
|
self.plot_btn_group.buttons()[0].click()
|
|
90
92
|
elif len(self.parent_window.well_indices)==1 and len(self.parent_window.position_indices)==1:
|
|
91
93
|
self.plot_btn_group.buttons()[1].click()
|
|
92
94
|
for i in [0,2]:
|
|
93
95
|
self.plot_options[i].setEnabled(False)
|
|
96
|
+
elif len(self.parent_window.well_indices)==1 and len(self.parent_window.position_indices)>1:
|
|
97
|
+
# one well, several positions
|
|
98
|
+
self.plot_btn_group.buttons()[2].click()
|
|
94
99
|
else:
|
|
95
100
|
if len(self.parent_window.well_indices)>1:
|
|
96
101
|
self.plot_btn_group.buttons()[0].click()
|
|
@@ -212,17 +217,22 @@ class GenericSignalPlotWidget(QWidget, Styles):
|
|
|
212
217
|
# Rescale
|
|
213
218
|
self.cell_lines_alpha_wdg = QWidget()
|
|
214
219
|
alpha_hbox = QHBoxLayout()
|
|
215
|
-
self.cell_lines_alpha_wdg.setLayout(alpha_hbox)
|
|
216
220
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
+
self.alpha_slider = QLabeledDoubleSlider()
|
|
222
|
+
alpha_hbox = QuickSliderLayout(label='single-cell\nsignal alpha: ',
|
|
223
|
+
slider=self.alpha_slider,
|
|
224
|
+
slider_initial_value=0.8,
|
|
225
|
+
slider_range=(0,1),
|
|
226
|
+
decimal_option=True,
|
|
227
|
+
precision=1.0E-05,
|
|
228
|
+
)
|
|
229
|
+
self.alpha_slider.valueChanged.connect(self.submit_alpha)
|
|
230
|
+
self.cell_lines_alpha_wdg.setLayout(alpha_hbox)
|
|
221
231
|
|
|
222
|
-
self.submit_alpha_btn = QPushButton('submit')
|
|
223
|
-
self.submit_alpha_btn.setStyleSheet(self.button_style_sheet_2)
|
|
224
|
-
self.submit_alpha_btn.clicked.connect(self.submit_alpha)
|
|
225
|
-
alpha_hbox.addWidget(self.submit_alpha_btn, 10)
|
|
232
|
+
# self.submit_alpha_btn = QPushButton('submit')
|
|
233
|
+
# self.submit_alpha_btn.setStyleSheet(self.button_style_sheet_2)
|
|
234
|
+
# self.submit_alpha_btn.clicked.connect(self.submit_alpha)
|
|
235
|
+
# alpha_hbox.addWidget(self.submit_alpha_btn, 10)
|
|
226
236
|
self.layout.addWidget(self.cell_lines_alpha_wdg)
|
|
227
237
|
|
|
228
238
|
self.select_option = [QRadioButton() for i in range(2)]
|
|
@@ -263,9 +273,9 @@ class GenericSignalPlotWidget(QWidget, Styles):
|
|
|
263
273
|
self.generate_pos_selection_widget()
|
|
264
274
|
self.select_btn_group.buttons()[0].click()
|
|
265
275
|
|
|
266
|
-
def submit_alpha(self):
|
|
276
|
+
def submit_alpha(self, value):
|
|
267
277
|
|
|
268
|
-
alpha =
|
|
278
|
+
alpha = value
|
|
269
279
|
try:
|
|
270
280
|
alpha = float(alpha)
|
|
271
281
|
except:
|
|
@@ -329,17 +339,21 @@ class GenericSignalPlotWidget(QWidget, Styles):
|
|
|
329
339
|
if name+':' in lbl:
|
|
330
340
|
self.usable_well_labels.append(lbl)
|
|
331
341
|
|
|
342
|
+
thresh = 20
|
|
343
|
+
self.well_name_truncated = [w[:thresh - 3]+'...' if len(w)>thresh else w for w in self.usable_well_labels]
|
|
344
|
+
|
|
332
345
|
self.line_choice_widget = QWidget()
|
|
333
346
|
self.line_check_vbox = QGridLayout()
|
|
334
347
|
self.line_choice_widget.setLayout(self.line_check_vbox)
|
|
335
348
|
|
|
336
349
|
if len(self.parent_window.well_indices)>1:
|
|
337
|
-
self.well_display_options = [QCheckBox(self.
|
|
350
|
+
self.well_display_options = [QCheckBox(self.well_name_truncated[i]) for i in range(len(self.well_name_truncated))]
|
|
338
351
|
for i in range(len(self.well_names)):
|
|
339
|
-
self.line_check_vbox.addWidget(self.well_display_options[i], i,
|
|
352
|
+
self.line_check_vbox.addWidget(self.well_display_options[i], i%4, i//4, 1, 1, alignment=Qt.AlignCenter)
|
|
340
353
|
self.well_display_options[i].setChecked(True)
|
|
341
354
|
self.well_display_options[i].setStyleSheet("font-size: 12px;")
|
|
342
355
|
self.well_display_options[i].toggled.connect(self.select_lines)
|
|
356
|
+
self.well_display_options[i].setToolTip(self.usable_well_labels[i])
|
|
343
357
|
else:
|
|
344
358
|
self.pos_display_options = [QCheckBox(self.pos_names[i]) for i in range(len(self.pos_names))]
|
|
345
359
|
for i in range(len(self.pos_names)):
|
|
@@ -648,7 +662,10 @@ class GenericSignalPlotWidget(QWidget, Styles):
|
|
|
648
662
|
else:
|
|
649
663
|
self.ci_btn.setIcon(icon(MDI6.arrow_expand_horizontal,color=self.help_color))
|
|
650
664
|
self.show_ci = not self.show_ci
|
|
651
|
-
|
|
665
|
+
try:
|
|
666
|
+
self.plot_signals(0)
|
|
667
|
+
except Exception as e:
|
|
668
|
+
print(f"{e=}")
|
|
652
669
|
|
|
653
670
|
def switch_cell_lines(self):
|
|
654
671
|
|
celldetective/gui/gui_utils.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from PyQt5.QtWidgets import QApplication, QMessageBox, QFrame, QSizePolicy, QWidget, QLineEdit, QListWidget, QVBoxLayout, QComboBox, \
|
|
3
|
-
QPushButton, QLabel, QHBoxLayout, QCheckBox, QFileDialog
|
|
3
|
+
QPushButton, QLabel, QHBoxLayout, QCheckBox, QFileDialog, QToolButton, QMenu, QStylePainter, QStyleOptionComboBox, QStyle
|
|
4
4
|
from PyQt5.QtCore import Qt, QSize, QAbstractTableModel
|
|
5
|
-
from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
5
|
+
from PyQt5.QtGui import QDoubleValidator, QIntValidator, QStandardItemModel, QPalette
|
|
6
6
|
|
|
7
7
|
from celldetective.gui import Styles
|
|
8
8
|
from superqt.fonticon import icon
|
|
@@ -16,6 +16,147 @@ from inspect import getmembers, isfunction
|
|
|
16
16
|
from celldetective.filters import *
|
|
17
17
|
from os import sep
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
class QCheckableComboBox(QComboBox):
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
adapted from https://stackoverflow.com/questions/22775095/pyqt-how-to-set-combobox-items-be-checkable
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, obj='', parent_window=None, *args, **kwargs):
|
|
27
|
+
|
|
28
|
+
super().__init__(parent_window, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
self.setTitle('')
|
|
31
|
+
self.view().pressed.connect(self.handleItemPressed)
|
|
32
|
+
self.setModel(QStandardItemModel(self))
|
|
33
|
+
self.obj = obj
|
|
34
|
+
self.toolButton = QToolButton(parent_window)
|
|
35
|
+
self.toolButton.setText('')
|
|
36
|
+
self.toolMenu = QMenu(parent_window)
|
|
37
|
+
self.toolButton.setMenu(self.toolMenu)
|
|
38
|
+
self.toolButton.setPopupMode(QToolButton.InstantPopup)
|
|
39
|
+
self.anySelected = False
|
|
40
|
+
|
|
41
|
+
def clear(self):
|
|
42
|
+
|
|
43
|
+
self.unselectAll()
|
|
44
|
+
self.toolMenu.clear()
|
|
45
|
+
super().clear()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def handleItemPressed(self, index):
|
|
49
|
+
|
|
50
|
+
idx = index.row()
|
|
51
|
+
actions = self.toolMenu.actions()
|
|
52
|
+
|
|
53
|
+
item = self.model().itemFromIndex(index)
|
|
54
|
+
if item.checkState() == Qt.Checked:
|
|
55
|
+
item.setCheckState(Qt.Unchecked)
|
|
56
|
+
actions[idx].setChecked(False)
|
|
57
|
+
else:
|
|
58
|
+
item.setCheckState(Qt.Checked)
|
|
59
|
+
actions[idx].setChecked(True)
|
|
60
|
+
self.anySelected = True
|
|
61
|
+
|
|
62
|
+
options_checked = np.array([a.isChecked() for a in actions])
|
|
63
|
+
if len(options_checked[options_checked]) > 1:
|
|
64
|
+
self.setTitle(f'Multiple {self.obj+"s"} selected...')
|
|
65
|
+
elif len(options_checked[options_checked])==1:
|
|
66
|
+
idx_selected = np.where(options_checked)[0][0]
|
|
67
|
+
if idx_selected!=idx:
|
|
68
|
+
item = self.model().item(idx_selected)
|
|
69
|
+
self.setTitle(item.text())
|
|
70
|
+
elif len(options_checked[options_checked])==0:
|
|
71
|
+
self.setTitle(f"No {self.obj} selected...")
|
|
72
|
+
self.anySelected = False
|
|
73
|
+
|
|
74
|
+
def setCurrentIndex(self, index):
|
|
75
|
+
|
|
76
|
+
super().setCurrentIndex(index)
|
|
77
|
+
|
|
78
|
+
item = self.model().item(index)
|
|
79
|
+
modelIndex = self.model().indexFromItem(item)
|
|
80
|
+
|
|
81
|
+
self.handleItemPressed(modelIndex)
|
|
82
|
+
|
|
83
|
+
def selectAll(self):
|
|
84
|
+
|
|
85
|
+
actions = self.toolMenu.actions()
|
|
86
|
+
for i,a in enumerate(actions):
|
|
87
|
+
if not a.isChecked():
|
|
88
|
+
self.setCurrentIndex(i)
|
|
89
|
+
self.anySelected = True
|
|
90
|
+
|
|
91
|
+
def unselectAll(self):
|
|
92
|
+
|
|
93
|
+
actions = self.toolMenu.actions()
|
|
94
|
+
for i,a in enumerate(actions):
|
|
95
|
+
if a.isChecked():
|
|
96
|
+
self.setCurrentIndex(i)
|
|
97
|
+
self.anySelected = False
|
|
98
|
+
|
|
99
|
+
def title(self):
|
|
100
|
+
return self._title
|
|
101
|
+
|
|
102
|
+
def setTitle(self, title):
|
|
103
|
+
self._title = title
|
|
104
|
+
self.repaint()
|
|
105
|
+
|
|
106
|
+
def paintEvent(self, event):
|
|
107
|
+
|
|
108
|
+
painter = QStylePainter(self)
|
|
109
|
+
painter.setPen(self.palette().color(QPalette.Text))
|
|
110
|
+
opt = QStyleOptionComboBox()
|
|
111
|
+
self.initStyleOption(opt)
|
|
112
|
+
opt.currentText = self._title
|
|
113
|
+
painter.drawComplexControl(QStyle.CC_ComboBox, opt)
|
|
114
|
+
painter.drawControl(QStyle.CE_ComboBoxLabel, opt)
|
|
115
|
+
|
|
116
|
+
def addItem(self, item, tooltip=None):
|
|
117
|
+
|
|
118
|
+
super().addItem(item)
|
|
119
|
+
idx = self.findText(item)
|
|
120
|
+
if tooltip is not None:
|
|
121
|
+
self.setItemData(idx, tooltip, Qt.ToolTipRole)
|
|
122
|
+
item2 = self.model().item(idx, 0)
|
|
123
|
+
item2.setCheckState(Qt.Unchecked)
|
|
124
|
+
action = self.toolMenu.addAction(item)
|
|
125
|
+
action.setCheckable(True)
|
|
126
|
+
|
|
127
|
+
def addItems(self, items):
|
|
128
|
+
|
|
129
|
+
super().addItems(items)
|
|
130
|
+
|
|
131
|
+
for item in items:
|
|
132
|
+
|
|
133
|
+
idx = self.findText(item)
|
|
134
|
+
item2 = self.model().item(idx, 0)
|
|
135
|
+
item2.setCheckState(Qt.Unchecked)
|
|
136
|
+
action = self.toolMenu.addAction(item)
|
|
137
|
+
action.setCheckable(True)
|
|
138
|
+
|
|
139
|
+
def getSelectedIndices(self):
|
|
140
|
+
|
|
141
|
+
actions = self.toolMenu.actions()
|
|
142
|
+
options_checked = np.array([a.isChecked() for a in actions])
|
|
143
|
+
idx_selected = np.where(options_checked)[0]
|
|
144
|
+
|
|
145
|
+
return list(idx_selected)
|
|
146
|
+
|
|
147
|
+
def currentText(self):
|
|
148
|
+
return self.title()
|
|
149
|
+
|
|
150
|
+
def isMultipleSelection(self):
|
|
151
|
+
return self.currentText().startswith('Multiple')
|
|
152
|
+
|
|
153
|
+
def isSingleSelection(self):
|
|
154
|
+
return not self.currentText().startswith('Multiple') and not self.title().startswith('No')
|
|
155
|
+
|
|
156
|
+
def isAnySelected(self):
|
|
157
|
+
return not self.title().startswith('No')
|
|
158
|
+
|
|
159
|
+
|
|
19
160
|
class PandasModel(QAbstractTableModel):
|
|
20
161
|
|
|
21
162
|
"""
|
celldetective/gui/layouts.py
CHANGED
|
@@ -650,7 +650,8 @@ class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
|
650
650
|
|
|
651
651
|
def preview_correction(self):
|
|
652
652
|
|
|
653
|
-
if self.attr_parent.well_list.
|
|
653
|
+
if self.attr_parent.well_list.isMultipleSelection() or not self.attr_parent.well_list.isAnySelected() or self.attr_parent.position_list.isMultipleSelection() or not self.attr_parent.position_list.isAnySelected():
|
|
654
|
+
|
|
654
655
|
msgBox = QMessageBox()
|
|
655
656
|
msgBox.setIcon(QMessageBox.Critical)
|
|
656
657
|
msgBox.setText("Please select a single position...")
|
|
@@ -672,8 +673,8 @@ class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
|
672
673
|
clip = False
|
|
673
674
|
|
|
674
675
|
corrected_stack = correct_background_model(self.attr_parent.exp_dir,
|
|
675
|
-
well_option=self.attr_parent.well_list.
|
|
676
|
-
position_option=self.attr_parent.position_list.
|
|
676
|
+
well_option=self.attr_parent.well_list.getSelectedIndices(), #+1 ??
|
|
677
|
+
position_option=self.attr_parent.position_list.getSelectedIndices(), #+1??
|
|
677
678
|
target_channel=self.channels_cb.currentText(),
|
|
678
679
|
model = self.models_cb.currentText(),
|
|
679
680
|
threshold_on_std = self.threshold_le.get_threshold(),
|
|
@@ -1258,7 +1259,7 @@ class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
|
1258
1259
|
|
|
1259
1260
|
def preview_correction(self):
|
|
1260
1261
|
|
|
1261
|
-
if self.attr_parent.well_list.
|
|
1262
|
+
if self.attr_parent.well_list.isMultipleSelection() or not self.attr_parent.well_list.isAnySelected() or self.attr_parent.position_list.isMultipleSelection() or not self.attr_parent.position_list.isAnySelected():
|
|
1262
1263
|
msgBox = QMessageBox()
|
|
1263
1264
|
msgBox.setIcon(QMessageBox.Warning)
|
|
1264
1265
|
msgBox.setText("Please select a single position...")
|
|
@@ -1294,8 +1295,8 @@ class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
|
1294
1295
|
clip = False
|
|
1295
1296
|
|
|
1296
1297
|
corrected_stacks = correct_background_model_free(self.attr_parent.exp_dir,
|
|
1297
|
-
well_option=self.attr_parent.well_list.
|
|
1298
|
-
position_option=self.attr_parent.
|
|
1298
|
+
well_option=self.attr_parent.well_list.getSelectedIndices(), #+1 ??
|
|
1299
|
+
position_option=self.attr_parent.getSelectedIndices(), #+1??
|
|
1299
1300
|
target_channel=self.channels_cb.currentText(),
|
|
1300
1301
|
mode = mode,
|
|
1301
1302
|
threshold_on_std = self.threshold_le.get_threshold(),
|
|
@@ -24,7 +24,6 @@ from glob import glob
|
|
|
24
24
|
from natsort import natsorted
|
|
25
25
|
from tifffile import imread
|
|
26
26
|
from pathlib import Path
|
|
27
|
-
import gc
|
|
28
27
|
|
|
29
28
|
from celldetective.gui.viewers import CellEdgeVisualizer, SpotDetectionVisualizer
|
|
30
29
|
from celldetective.gui.layouts import ProtocolDesignerLayout, BackgroundFitCorrectionLayout, LocalCorrectionLayout
|
|
@@ -726,18 +725,10 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
726
725
|
else:
|
|
727
726
|
self.current_stack = movies[0]
|
|
728
727
|
self.stack_length = auto_load_number_of_frames(self.current_stack)
|
|
729
|
-
|
|
730
|
-
if self.stack_length is None:
|
|
731
|
-
stack = imread(self.current_stack)
|
|
732
|
-
self.stack_length = len(stack)
|
|
733
|
-
del stack
|
|
734
|
-
gc.collect()
|
|
735
|
-
|
|
736
728
|
self.mid_time = self.stack_length // 2
|
|
737
|
-
indices = self.mid_time + np.arange(len(self.channel_names))
|
|
729
|
+
indices = len(self.channel_names) * self.mid_time + np.arange(len(self.channel_names))
|
|
738
730
|
self.test_frame = load_frames(list(indices.astype(int)),self.current_stack, normalize_input=False)
|
|
739
731
|
|
|
740
|
-
|
|
741
732
|
def control_haralick_digitalization(self):
|
|
742
733
|
|
|
743
734
|
"""
|
|
@@ -746,9 +737,10 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
746
737
|
|
|
747
738
|
"""
|
|
748
739
|
|
|
749
|
-
self.locate_image()
|
|
740
|
+
self.locate_image() # pb here
|
|
750
741
|
self.extract_haralick_options()
|
|
751
742
|
if self.test_frame is not None:
|
|
743
|
+
|
|
752
744
|
digitized_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
|
|
753
745
|
channels=self.channel_names, return_digit_image_only=True,
|
|
754
746
|
**self.haralick_options
|
|
@@ -56,8 +56,8 @@ class ConfigMeasurementsPlot(QWidget,Styles):
|
|
|
56
56
|
|
|
57
57
|
print('Parent wells: ', self.wells)
|
|
58
58
|
|
|
59
|
-
self.well_option = self.parent_window.parent_window.well_list.
|
|
60
|
-
self.position_option = self.parent_window.parent_window.position_list.
|
|
59
|
+
self.well_option = self.parent_window.parent_window.well_list.getSelectedIndices()
|
|
60
|
+
self.position_option = self.parent_window.parent_window.position_list.getSelectedIndices()
|
|
61
61
|
self.interpret_pos_location()
|
|
62
62
|
# self.load_available_tables()
|
|
63
63
|
# self.config_path = self.exp_dir + self.config_name
|
|
@@ -526,18 +526,10 @@ class ConfigMeasurementsPlot(QWidget,Styles):
|
|
|
526
526
|
|
|
527
527
|
"""
|
|
528
528
|
|
|
529
|
-
self.well_option = self.parent_window.parent_window.well_list.
|
|
530
|
-
|
|
531
|
-
wo = '*'
|
|
532
|
-
else:
|
|
533
|
-
wo = self.well_option
|
|
534
|
-
self.position_option = self.parent_window.parent_window.position_list.currentIndex()
|
|
535
|
-
if self.position_option == 0:
|
|
536
|
-
po = '*'
|
|
537
|
-
else:
|
|
538
|
-
po = self.position_option - 1
|
|
529
|
+
self.well_option = self.parent_window.parent_window.well_list.getSelectedIndices()
|
|
530
|
+
self.position_option = self.parent_window.parent_window.position_list.getSelectedIndices()
|
|
539
531
|
|
|
540
|
-
self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=
|
|
532
|
+
self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option,
|
|
541
533
|
population=self.cbs[0].currentText(), return_pos_info=True)
|
|
542
534
|
|
|
543
535
|
if self.df is None:
|