celldetective 1.0.2__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/__init__.py +2 -0
- celldetective/__main__.py +432 -0
- celldetective/datasets/segmentation_annotations/blank +0 -0
- celldetective/datasets/signal_annotations/blank +0 -0
- celldetective/events.py +149 -0
- celldetective/extra_properties.py +100 -0
- celldetective/filters.py +89 -0
- celldetective/gui/__init__.py +20 -0
- celldetective/gui/about.py +44 -0
- celldetective/gui/analyze_block.py +563 -0
- celldetective/gui/btrack_options.py +898 -0
- celldetective/gui/classifier_widget.py +386 -0
- celldetective/gui/configure_new_exp.py +532 -0
- celldetective/gui/control_panel.py +438 -0
- celldetective/gui/gui_utils.py +495 -0
- celldetective/gui/json_readers.py +113 -0
- celldetective/gui/measurement_options.py +1425 -0
- celldetective/gui/neighborhood_options.py +452 -0
- celldetective/gui/plot_signals_ui.py +1042 -0
- celldetective/gui/process_block.py +1055 -0
- celldetective/gui/retrain_segmentation_model_options.py +706 -0
- celldetective/gui/retrain_signal_model_options.py +643 -0
- celldetective/gui/seg_model_loader.py +460 -0
- celldetective/gui/signal_annotator.py +2388 -0
- celldetective/gui/signal_annotator_options.py +340 -0
- celldetective/gui/styles.py +217 -0
- celldetective/gui/survival_ui.py +903 -0
- celldetective/gui/tableUI.py +608 -0
- celldetective/gui/thresholds_gui.py +1300 -0
- celldetective/icons/logo-large.png +0 -0
- celldetective/icons/logo.png +0 -0
- celldetective/icons/signals_icon.png +0 -0
- celldetective/icons/splash-test.png +0 -0
- celldetective/icons/splash.png +0 -0
- celldetective/icons/splash0.png +0 -0
- celldetective/icons/survival2.png +0 -0
- celldetective/icons/vignette_signals2.png +0 -0
- celldetective/icons/vignette_signals2.svg +114 -0
- celldetective/io.py +2050 -0
- celldetective/links/zenodo.json +561 -0
- celldetective/measure.py +1258 -0
- celldetective/models/segmentation_effectors/blank +0 -0
- celldetective/models/segmentation_generic/blank +0 -0
- celldetective/models/segmentation_targets/blank +0 -0
- celldetective/models/signal_detection/blank +0 -0
- celldetective/models/tracking_configs/mcf7.json +68 -0
- celldetective/models/tracking_configs/ricm.json +203 -0
- celldetective/models/tracking_configs/ricm2.json +203 -0
- celldetective/neighborhood.py +717 -0
- celldetective/scripts/analyze_signals.py +51 -0
- celldetective/scripts/measure_cells.py +275 -0
- celldetective/scripts/segment_cells.py +212 -0
- celldetective/scripts/segment_cells_thresholds.py +140 -0
- celldetective/scripts/track_cells.py +206 -0
- celldetective/scripts/train_segmentation_model.py +246 -0
- celldetective/scripts/train_signal_model.py +49 -0
- celldetective/segmentation.py +712 -0
- celldetective/signals.py +2826 -0
- celldetective/tracking.py +974 -0
- celldetective/utils.py +1681 -0
- celldetective-1.0.2.dist-info/LICENSE +674 -0
- celldetective-1.0.2.dist-info/METADATA +192 -0
- celldetective-1.0.2.dist-info/RECORD +66 -0
- celldetective-1.0.2.dist-info/WHEEL +5 -0
- celldetective-1.0.2.dist-info/entry_points.txt +2 -0
- celldetective-1.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QDialog, QHBoxLayout, QFileDialog, QVBoxLayout, QScrollArea, QCheckBox, QSlider, QGridLayout, QLabel, QLineEdit, QPushButton, QWidget
|
|
2
|
+
from PyQt5.QtGui import QIntValidator, QDoubleValidator
|
|
3
|
+
from celldetective.gui.gui_utils import center_window
|
|
4
|
+
from celldetective.gui.styles import Styles
|
|
5
|
+
from superqt import QLabeledSlider
|
|
6
|
+
from PyQt5.QtCore import Qt, QSize
|
|
7
|
+
from superqt.fonticon import icon
|
|
8
|
+
from fonticon_mdi6 import MDI6
|
|
9
|
+
from configparser import ConfigParser
|
|
10
|
+
import os
|
|
11
|
+
from shutil import copyfile
|
|
12
|
+
import time
|
|
13
|
+
from functools import partial
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
class ConfigNewExperiment(QMainWindow):
|
|
17
|
+
|
|
18
|
+
def __init__(self, parent=None):
|
|
19
|
+
|
|
20
|
+
super().__init__()
|
|
21
|
+
self.parent = parent
|
|
22
|
+
self.setWindowTitle("New experiment")
|
|
23
|
+
center_window(self)
|
|
24
|
+
self.setFixedWidth(500)
|
|
25
|
+
self.setMaximumHeight(int(0.8*self.parent.screen_height))
|
|
26
|
+
self.onlyFloat = QDoubleValidator()
|
|
27
|
+
|
|
28
|
+
self.Styles = Styles()
|
|
29
|
+
self.button_style = self.Styles.button_add
|
|
30
|
+
self.button_style_sheet = self.Styles.button_style_sheet
|
|
31
|
+
|
|
32
|
+
self.newExpFolder = str(QFileDialog.getExistingDirectory(self, 'Select directory'))
|
|
33
|
+
self.populate_widget()
|
|
34
|
+
|
|
35
|
+
def populate_widget(self):
|
|
36
|
+
|
|
37
|
+
# Create button widget and layout
|
|
38
|
+
self.scroll_area = QScrollArea(self)
|
|
39
|
+
button_widget = QWidget()
|
|
40
|
+
grid = QGridLayout()
|
|
41
|
+
button_widget.setLayout(grid)
|
|
42
|
+
|
|
43
|
+
grid.setContentsMargins(30,30,30,30)
|
|
44
|
+
grid.addWidget(QLabel("Folder:"), 0, 0, 1, 3)
|
|
45
|
+
self.supFolder = QLineEdit()
|
|
46
|
+
self.supFolder.setAlignment(Qt.AlignLeft)
|
|
47
|
+
self.supFolder.setEnabled(True)
|
|
48
|
+
self.supFolder.setText(self.newExpFolder)
|
|
49
|
+
grid.addWidget(self.supFolder, 1, 0, 1, 1)
|
|
50
|
+
|
|
51
|
+
self.browse_button = QPushButton("Browse...")
|
|
52
|
+
self.browse_button.clicked.connect(self.browse_experiment_folder)
|
|
53
|
+
self.browse_button.setIcon(icon(MDI6.folder, color="white"))
|
|
54
|
+
self.browse_button.setStyleSheet(self.parent.button_style_sheet)
|
|
55
|
+
#self.browse_button.setIcon(QIcon_from_svg(abs_path+f"/icons/browse.svg", color='white'))
|
|
56
|
+
grid.addWidget(self.browse_button, 1, 1, 1, 1)
|
|
57
|
+
|
|
58
|
+
grid.addWidget(QLabel("Experiment name:"), 2, 0, 1, 3)
|
|
59
|
+
|
|
60
|
+
self.expName = QLineEdit()
|
|
61
|
+
self.expName.setPlaceholderText('folder_name_for_the_experiment')
|
|
62
|
+
self.expName.setAlignment(Qt.AlignLeft)
|
|
63
|
+
self.expName.setEnabled(True)
|
|
64
|
+
self.expName.setFixedWidth(400)
|
|
65
|
+
self.expName.setText("Untitled_Experiment")
|
|
66
|
+
grid.addWidget(self.expName, 3, 0, 1, 3)
|
|
67
|
+
|
|
68
|
+
self.generate_movie_settings()
|
|
69
|
+
grid.addLayout(self.ms_grid,29,0,1,3)
|
|
70
|
+
|
|
71
|
+
self.generate_channel_params_box()
|
|
72
|
+
grid.addLayout(self.channel_grid,30,0,1,3)
|
|
73
|
+
|
|
74
|
+
self.validate_button = QPushButton("Submit")
|
|
75
|
+
self.validate_button.clicked.connect(self.create_config)
|
|
76
|
+
self.validate_button.setStyleSheet(self.parent.button_style_sheet)
|
|
77
|
+
#self.validate_button.setIcon(QIcon_from_svg(abs_path+f"/icons/process.svg", color='white'))
|
|
78
|
+
|
|
79
|
+
grid.addWidget(self.validate_button, 31, 0, 1, 3, alignment = Qt.AlignBottom)
|
|
80
|
+
button_widget.adjustSize()
|
|
81
|
+
|
|
82
|
+
self.scroll_area.setAlignment(Qt.AlignCenter)
|
|
83
|
+
self.scroll_area.setWidget(button_widget)
|
|
84
|
+
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
85
|
+
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
86
|
+
self.scroll_area.setWidgetResizable(True)
|
|
87
|
+
self.setCentralWidget(self.scroll_area)
|
|
88
|
+
self.show()
|
|
89
|
+
|
|
90
|
+
QApplication.processEvents()
|
|
91
|
+
self.adjustScrollArea()
|
|
92
|
+
|
|
93
|
+
def adjustScrollArea(self):
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
Auto-adjust scroll area to fill space
|
|
97
|
+
(from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
step = 5
|
|
101
|
+
while self.scroll_area.verticalScrollBar().isVisible() and self.height() < self.maximumHeight():
|
|
102
|
+
self.resize(self.width(), self.height() + step)
|
|
103
|
+
|
|
104
|
+
def generate_movie_settings(self):
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
Parameters related to the movie parameters
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
onlyInt = QIntValidator()
|
|
111
|
+
onlyInt.setRange(0, 100000)
|
|
112
|
+
|
|
113
|
+
self.ms_grid = QGridLayout()
|
|
114
|
+
self.ms_grid.setContentsMargins(21,30,20,30)
|
|
115
|
+
|
|
116
|
+
ms_lbl = QLabel("MOVIE SETTINGS")
|
|
117
|
+
ms_lbl.setStyleSheet("""
|
|
118
|
+
font-weight: bold;
|
|
119
|
+
""")
|
|
120
|
+
self.ms_grid.addWidget(ms_lbl, 0,0,1,3, alignment=Qt.AlignCenter)
|
|
121
|
+
|
|
122
|
+
self.number_of_wells = QLabel("Number of wells:")
|
|
123
|
+
self.ms_grid.addWidget(self.number_of_wells, 1, 0, 1, 3)
|
|
124
|
+
|
|
125
|
+
self.SliderWells = QLabeledSlider(Qt.Horizontal, self)
|
|
126
|
+
self.SliderWells.setMinimum(1)
|
|
127
|
+
self.SliderWells.setMaximum(32)
|
|
128
|
+
self.ms_grid.addWidget(self.SliderWells, 2, 0, 1, 3, alignment=Qt.AlignTop)
|
|
129
|
+
|
|
130
|
+
self.number_of_positions = QLabel("Number of positions per well:")
|
|
131
|
+
self.ms_grid.addWidget(self.number_of_positions, 3, 0, 1, 3)
|
|
132
|
+
|
|
133
|
+
self.SliderPos = QLabeledSlider(Qt.Horizontal, self)
|
|
134
|
+
self.SliderPos.setMinimum(1)
|
|
135
|
+
self.SliderPos.setMaximum(50)
|
|
136
|
+
|
|
137
|
+
self.ms_grid.addWidget(self.SliderPos, 4, 0, 1, 3)
|
|
138
|
+
|
|
139
|
+
self.ms_grid.addWidget(QLabel("Calibration from pixel to µm:"), 5, 0, 1, 3)
|
|
140
|
+
self.PxToUm_field = QLineEdit()
|
|
141
|
+
self.PxToUm_field.setValidator(self.onlyFloat)
|
|
142
|
+
self.PxToUm_field.setPlaceholderText('1 px = XXX µm')
|
|
143
|
+
self.PxToUm_field.setAlignment(Qt.AlignLeft)
|
|
144
|
+
self.PxToUm_field.setEnabled(True)
|
|
145
|
+
self.PxToUm_field.setFixedWidth(400)
|
|
146
|
+
self.PxToUm_field.setText("1,0")
|
|
147
|
+
self.ms_grid.addWidget(self.PxToUm_field, 6, 0, 1, 3)
|
|
148
|
+
|
|
149
|
+
self.ms_grid.addWidget(QLabel("Calibration from frame to minutes:"), 7, 0, 1, 3)
|
|
150
|
+
self.FrameToMin_field = QLineEdit()
|
|
151
|
+
self.FrameToMin_field.setAlignment(Qt.AlignLeft)
|
|
152
|
+
self.FrameToMin_field.setEnabled(True)
|
|
153
|
+
self.FrameToMin_field.setFixedWidth(400)
|
|
154
|
+
self.FrameToMin_field.setValidator(self.onlyFloat)
|
|
155
|
+
self.FrameToMin_field.setPlaceholderText('1 frame = XXX min')
|
|
156
|
+
self.FrameToMin_field.setText("1,0")
|
|
157
|
+
self.ms_grid.addWidget(self.FrameToMin_field, 8, 0, 1, 3)
|
|
158
|
+
|
|
159
|
+
self.movie_length = QLabel("Number of frames:")
|
|
160
|
+
self.movie_length.setToolTip('Optional: depending on how the movies are encoded, the automatic extraction of the number of frames can be difficult.\nThe software will then rely on this value.')
|
|
161
|
+
self.ms_grid.addWidget(self.movie_length,9, 0, 1, 3)
|
|
162
|
+
self.MovieLengthSlider = QLabeledSlider(Qt.Horizontal, self)
|
|
163
|
+
self.MovieLengthSlider.setMinimum(2)
|
|
164
|
+
self.MovieLengthSlider.setMaximum(128)
|
|
165
|
+
self.ms_grid.addWidget(self.MovieLengthSlider, 10, 0, 1, 3)
|
|
166
|
+
|
|
167
|
+
self.prefix_lbl = QLabel("Prefix for the movies:")
|
|
168
|
+
self.prefix_lbl.setToolTip('The stack file name must start with this prefix to be properly loaded.')
|
|
169
|
+
self.ms_grid.addWidget(self.prefix_lbl, 11, 0, 1, 3)
|
|
170
|
+
self.movie_prefix_field = QLineEdit()
|
|
171
|
+
self.movie_prefix_field.setAlignment(Qt.AlignLeft)
|
|
172
|
+
self.movie_prefix_field.setEnabled(True)
|
|
173
|
+
self.movie_prefix_field.setFixedWidth(400)
|
|
174
|
+
self.movie_prefix_field.setText("")
|
|
175
|
+
self.ms_grid.addWidget(self.movie_prefix_field, 12, 0, 1, 3)
|
|
176
|
+
|
|
177
|
+
self.ms_grid.addWidget(QLabel("X shape in pixels:"), 13, 0, 1, 3)
|
|
178
|
+
self.shape_x_field = QLineEdit()
|
|
179
|
+
self.shape_x_field.setValidator(onlyInt)
|
|
180
|
+
self.shape_x_field.setAlignment(Qt.AlignLeft)
|
|
181
|
+
self.shape_x_field.setEnabled(True)
|
|
182
|
+
self.shape_x_field.setFixedWidth(400)
|
|
183
|
+
self.shape_x_field.setText("2048")
|
|
184
|
+
self.ms_grid.addWidget(self.shape_x_field, 14, 0, 1, 3)
|
|
185
|
+
|
|
186
|
+
self.ms_grid.addWidget(QLabel("Y shape in pixels:"), 15, 0, 1, 3)
|
|
187
|
+
self.shape_y_field = QLineEdit()
|
|
188
|
+
self.shape_y_field.setValidator(onlyInt)
|
|
189
|
+
self.shape_y_field.setAlignment(Qt.AlignLeft)
|
|
190
|
+
self.shape_y_field.setEnabled(True)
|
|
191
|
+
self.shape_y_field.setFixedWidth(400)
|
|
192
|
+
self.shape_y_field.setText("2048")
|
|
193
|
+
self.ms_grid.addWidget(self.shape_y_field, 16, 0, 1, 3)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def generate_channel_params_box(self):
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
Parameters related to the movie channels
|
|
200
|
+
Rewrite all of it
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
self.channel_grid = QGridLayout()
|
|
205
|
+
self.channel_grid.setContentsMargins(21,30,20,30)
|
|
206
|
+
|
|
207
|
+
channel_lbl = QLabel("CHANNELS")
|
|
208
|
+
channel_lbl.setStyleSheet("""
|
|
209
|
+
font-weight: bold;
|
|
210
|
+
""")
|
|
211
|
+
self.channel_grid.addWidget(channel_lbl, 0,0,1,3, alignment=Qt.AlignCenter)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
self.channels = ['brightfield', "live nuclei channel\n(Hoechst, NucSpot®)", "dead nuclei channel\n(PI)", "effector fluorescence\n(CFSE)",
|
|
215
|
+
"adhesion\n(RICM, IRM)", "fluorescence miscellaneous 1\n(LAMP-1, Actin...)", "fluorescence miscellaneous 2\n(LAMP-1, Actin...)"]
|
|
216
|
+
self.channel_mapping = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel', 'effector_fluo_channel',
|
|
217
|
+
'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2']
|
|
218
|
+
self.checkBoxes = [QCheckBox() for i in range(len(self.channels))]
|
|
219
|
+
self.sliders = [QLabeledSlider(Qt.Orientation.Horizontal) for i in range(len(self.channels))]
|
|
220
|
+
|
|
221
|
+
for i in range(len(self.channels)):
|
|
222
|
+
|
|
223
|
+
self.checkBoxes[i].setText(self.channels[i])
|
|
224
|
+
self.checkBoxes[i].toggled.connect(partial(self.show_slider, i))
|
|
225
|
+
|
|
226
|
+
self.channel_grid.addWidget(self.checkBoxes[i], i+1, 0, 1, 1)
|
|
227
|
+
|
|
228
|
+
self.sliders[i].setMinimum(0)
|
|
229
|
+
self.sliders[i].setMaximum(len(self.channels))
|
|
230
|
+
self.sliders[i].setEnabled(False)
|
|
231
|
+
|
|
232
|
+
self.channel_grid.addWidget(self.sliders[i], i+1, 1, 1, 2, alignment = Qt.AlignRight)
|
|
233
|
+
|
|
234
|
+
# Add channel button
|
|
235
|
+
self.addChannelBtn = QPushButton('Add channel')
|
|
236
|
+
self.addChannelBtn.setIcon(icon(MDI6.plus,color="#000000"))
|
|
237
|
+
self.addChannelBtn.setIconSize(QSize(25, 25))
|
|
238
|
+
self.addChannelBtn.setStyleSheet(self.button_style)
|
|
239
|
+
self.addChannelBtn.clicked.connect(self.add_custom_channel)
|
|
240
|
+
self.channel_grid.addWidget(self.addChannelBtn, 1000, 0, 1, 1)
|
|
241
|
+
|
|
242
|
+
def add_custom_channel(self):
|
|
243
|
+
|
|
244
|
+
self.CustomChannelWidget = QWidget()
|
|
245
|
+
self.CustomChannelWidget.setWindowTitle("Custom channel")
|
|
246
|
+
layout = QVBoxLayout()
|
|
247
|
+
self.CustomChannelWidget.setLayout(layout)
|
|
248
|
+
|
|
249
|
+
self.name_le = QLineEdit('custom_channel')
|
|
250
|
+
hbox = QHBoxLayout()
|
|
251
|
+
hbox.addWidget(QLabel('channel name: '), 33)
|
|
252
|
+
hbox.addWidget(self.name_le, 66)
|
|
253
|
+
layout.addLayout(hbox)
|
|
254
|
+
|
|
255
|
+
self.createBtn = QPushButton('create')
|
|
256
|
+
self.createBtn.setStyleSheet(self.button_style_sheet)
|
|
257
|
+
self.createBtn.clicked.connect(self.write_custom_channel)
|
|
258
|
+
layout.addWidget(self.createBtn)
|
|
259
|
+
center_window(self.CustomChannelWidget)
|
|
260
|
+
self.CustomChannelWidget.show()
|
|
261
|
+
|
|
262
|
+
print('new channel added')
|
|
263
|
+
|
|
264
|
+
def write_custom_channel(self):
|
|
265
|
+
|
|
266
|
+
self.new_channel_name = self.name_le.text()
|
|
267
|
+
name_map = self.new_channel_name
|
|
268
|
+
name_map = name_map.replace('_channel','')
|
|
269
|
+
name_map = name_map.replace('channel','')
|
|
270
|
+
name_map = name_map.replace(' ','')
|
|
271
|
+
if not name_map.endswith('_channel'):
|
|
272
|
+
name_map += '_channel'
|
|
273
|
+
|
|
274
|
+
self.channels.append(self.new_channel_name)
|
|
275
|
+
self.channel_mapping.append(name_map)
|
|
276
|
+
self.checkBoxes.append(QCheckBox())
|
|
277
|
+
self.sliders.append(QLabeledSlider(Qt.Orientation.Horizontal))
|
|
278
|
+
self.CustomChannelWidget.close()
|
|
279
|
+
|
|
280
|
+
self.checkBoxes[-1].setText(self.channels[-1])
|
|
281
|
+
self.checkBoxes[-1].toggled.connect(partial(self.show_slider, len(self.channels)-1))
|
|
282
|
+
self.channel_grid.addWidget(self.checkBoxes[-1], len(self.channels)+1, 0, 1, 1)
|
|
283
|
+
|
|
284
|
+
self.sliders[-1].setMinimum(0)
|
|
285
|
+
for i in range(len(self.channels)):
|
|
286
|
+
self.sliders[i].setMaximum(len(self.channels))
|
|
287
|
+
self.sliders[-1].setEnabled(False)
|
|
288
|
+
self.channel_grid.addWidget(self.sliders[-1], len(self.channels)+1, 1, 1, 2, alignment = Qt.AlignRight)
|
|
289
|
+
|
|
290
|
+
def show_slider(self, index):
|
|
291
|
+
if self.checkBoxes[index].isChecked():
|
|
292
|
+
self.sliders[index].setEnabled(True)
|
|
293
|
+
else:
|
|
294
|
+
self.sliders[index].setEnabled(False)
|
|
295
|
+
|
|
296
|
+
def browse_experiment_folder(self):
|
|
297
|
+
|
|
298
|
+
"""
|
|
299
|
+
Set a new base directory.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
self.newExpFolder = str(QFileDialog.getExistingDirectory(self, 'Select directory'))
|
|
303
|
+
self.supFolder.setText(self.newExpFolder)
|
|
304
|
+
|
|
305
|
+
def create_config(self):
|
|
306
|
+
|
|
307
|
+
"""
|
|
308
|
+
Create the folder tree, the config, issue a warning if the experiment folder already exists.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
channel_indices = []
|
|
312
|
+
for i in range(len(self.channels)):
|
|
313
|
+
if self.checkBoxes[i].isChecked():
|
|
314
|
+
channel_indices.append(self.sliders[i].value())
|
|
315
|
+
if not channel_indices:
|
|
316
|
+
msgBox = QMessageBox()
|
|
317
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
318
|
+
msgBox.setText("Please set at least one channel before proceeding.")
|
|
319
|
+
msgBox.setWindowTitle("Warning")
|
|
320
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
321
|
+
returnValue = msgBox.exec()
|
|
322
|
+
if returnValue == QMessageBox.Ok:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
if len(channel_indices) != len(set(channel_indices)):
|
|
326
|
+
msgBox = QMessageBox()
|
|
327
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
328
|
+
msgBox.setText("Some channel indices are repeated. Please check your configuration.")
|
|
329
|
+
msgBox.setWindowTitle("Warning")
|
|
330
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
331
|
+
returnValue = msgBox.exec()
|
|
332
|
+
if returnValue == QMessageBox.Ok:
|
|
333
|
+
return None
|
|
334
|
+
|
|
335
|
+
sorted_list = set(channel_indices)
|
|
336
|
+
expected_list = set(np.arange(max(sorted_list)+1))
|
|
337
|
+
print(sorted_list, expected_list, sorted_list==expected_list)
|
|
338
|
+
|
|
339
|
+
if not sorted_list==expected_list:
|
|
340
|
+
msgBox = QMessageBox()
|
|
341
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
342
|
+
msgBox.setText("There is a gap in your channel indices. Please check your configuration.")
|
|
343
|
+
msgBox.setWindowTitle("Warning")
|
|
344
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
345
|
+
returnValue = msgBox.exec()
|
|
346
|
+
if returnValue == QMessageBox.Ok:
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
|
|
351
|
+
folder = self.supFolder.text()
|
|
352
|
+
folder = folder.replace('\\','/')
|
|
353
|
+
folder = rf"{folder}"
|
|
354
|
+
|
|
355
|
+
name = str(self.expName.text())
|
|
356
|
+
name = name.replace(' ','')
|
|
357
|
+
|
|
358
|
+
self.directory = os.sep.join([folder,name])
|
|
359
|
+
os.mkdir(self.directory)
|
|
360
|
+
os.chdir(self.directory)
|
|
361
|
+
self.create_subfolders()
|
|
362
|
+
self.annotate_wells()
|
|
363
|
+
|
|
364
|
+
except FileExistsError:
|
|
365
|
+
msgBox = QMessageBox()
|
|
366
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
367
|
+
msgBox.setText("This experiment already exists... Please select another name.")
|
|
368
|
+
msgBox.setWindowTitle("Warning")
|
|
369
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
370
|
+
returnValue = msgBox.exec()
|
|
371
|
+
if returnValue == QMessageBox.Ok:
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
def create_subfolders(self):
|
|
375
|
+
|
|
376
|
+
"""
|
|
377
|
+
Create the well folders and position folders within the wells.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
self.nbr_wells = self.SliderWells.value()
|
|
381
|
+
self.nbr_positions = self.SliderPos.value()
|
|
382
|
+
for k in range(self.nbr_wells):
|
|
383
|
+
well_name = f"W{k+1}"+os.sep
|
|
384
|
+
os.mkdir(well_name)
|
|
385
|
+
for p in range(self.nbr_positions):
|
|
386
|
+
position_name = well_name+f"{k+1}0{p}"+os.sep
|
|
387
|
+
os.mkdir(position_name)
|
|
388
|
+
os.mkdir(position_name+os.sep+"movie"+os.sep)
|
|
389
|
+
|
|
390
|
+
def annotate_wells(self):
|
|
391
|
+
self.w = SetupConditionLabels(self, self.nbr_wells)
|
|
392
|
+
self.w.show()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def create_config_file(self):
|
|
396
|
+
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
Write all user input parameters to a configuration file associated to an experiment.
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
config = ConfigParser()
|
|
403
|
+
|
|
404
|
+
# add a new section and some values
|
|
405
|
+
config.add_section('MovieSettings')
|
|
406
|
+
config.set('MovieSettings', 'PxToUm', self.PxToUm_field.text().replace(',','.'))
|
|
407
|
+
config.set('MovieSettings', 'FrameToMin', self.FrameToMin_field.text().replace(',','.'))
|
|
408
|
+
config.set('MovieSettings', 'len_movie', str(self.MovieLengthSlider.value()))
|
|
409
|
+
config.set('MovieSettings', 'shape_x', self.shape_x_field.text())
|
|
410
|
+
config.set('MovieSettings', 'shape_y', self.shape_y_field.text())
|
|
411
|
+
config.set('MovieSettings', 'movie_prefix', self.movie_prefix_field.text())
|
|
412
|
+
|
|
413
|
+
config.add_section('Channels')
|
|
414
|
+
for i in range(len(self.channels)):
|
|
415
|
+
if self.checkBoxes[i].isChecked():
|
|
416
|
+
config.set('Channels', self.channel_mapping[i], str(self.sliders[i].value()))
|
|
417
|
+
else:
|
|
418
|
+
config.set('Channels', self.channel_mapping[i], "nan")
|
|
419
|
+
|
|
420
|
+
config.add_section('Labels')
|
|
421
|
+
config.set('Labels', 'cell_types', self.cell_types)
|
|
422
|
+
config.set('Labels', 'antibodies', self.antibodies)
|
|
423
|
+
config.set('Labels', 'concentrations', self.concentrations)
|
|
424
|
+
config.set('Labels', 'pharmaceutical_agents', self.pharmaceutical_agents)
|
|
425
|
+
|
|
426
|
+
# save to a file
|
|
427
|
+
with open('config.ini', 'w') as configfile:
|
|
428
|
+
config.write(configfile)
|
|
429
|
+
|
|
430
|
+
self.parent.set_experiment_path(self.directory)
|
|
431
|
+
print(f'New experiment successfully configured in folder {self.directory}...')
|
|
432
|
+
self.close()
|
|
433
|
+
|
|
434
|
+
class SetupConditionLabels(QWidget):
|
|
435
|
+
def __init__(self, parent, n_wells):
|
|
436
|
+
super().__init__()
|
|
437
|
+
self.parent = parent
|
|
438
|
+
self.n_wells = n_wells
|
|
439
|
+
self.setWindowTitle("Well conditions")
|
|
440
|
+
self.layout = QVBoxLayout()
|
|
441
|
+
self.layout.setContentsMargins(30,30,30,30)
|
|
442
|
+
self.setLayout(self.layout)
|
|
443
|
+
self.onlyFloat = QDoubleValidator()
|
|
444
|
+
self.populate()
|
|
445
|
+
center_window(self)
|
|
446
|
+
|
|
447
|
+
def populate(self):
|
|
448
|
+
|
|
449
|
+
self.cell_type_cbs = [QLineEdit() for i in range(self.n_wells)]
|
|
450
|
+
self.antibodies_cbs = [QLineEdit() for i in range(self.n_wells)]
|
|
451
|
+
self.concentrations_cbs = [QLineEdit() for i in range(self.n_wells)]
|
|
452
|
+
self.pharmaceutical_agents_cbs = [QLineEdit() for i in range(self.n_wells)]
|
|
453
|
+
|
|
454
|
+
for i in range(self.n_wells):
|
|
455
|
+
hbox = QHBoxLayout()
|
|
456
|
+
hbox.setContentsMargins(15,5,15,5)
|
|
457
|
+
hbox.addWidget(QLabel(f'well {i+1}'), 5, alignment=Qt.AlignLeft)
|
|
458
|
+
hbox.addWidget(QLabel('cell type: '), 5)
|
|
459
|
+
hbox.addWidget(self.cell_type_cbs[i], 10)
|
|
460
|
+
self.cell_type_cbs[i].setPlaceholderText('e.g. T-cell, NK')
|
|
461
|
+
|
|
462
|
+
hbox.addWidget(QLabel('antibody: '), 5)
|
|
463
|
+
hbox.addWidget(self.antibodies_cbs[i], 10)
|
|
464
|
+
self.antibodies_cbs[i].setPlaceholderText('e.g. anti-CD4')
|
|
465
|
+
|
|
466
|
+
hbox.addWidget(QLabel('concentration: '), 5)
|
|
467
|
+
hbox.addWidget(self.concentrations_cbs[i], 10)
|
|
468
|
+
self.concentrations_cbs[i].setPlaceholderText('e.g. 100 (pM)')
|
|
469
|
+
self.concentrations_cbs[i].setValidator(self.onlyFloat)
|
|
470
|
+
|
|
471
|
+
hbox.addWidget(QLabel('pharmaceutical agents: '), 5)
|
|
472
|
+
hbox.addWidget(self.pharmaceutical_agents_cbs[i], 10)
|
|
473
|
+
self.pharmaceutical_agents_cbs[i].setPlaceholderText('e.g. dextran')
|
|
474
|
+
|
|
475
|
+
self.layout.addLayout(hbox)
|
|
476
|
+
|
|
477
|
+
btn_hbox = QHBoxLayout()
|
|
478
|
+
btn_hbox.setContentsMargins(0,20,0,0)
|
|
479
|
+
self.skip_btn = QPushButton('Skip')
|
|
480
|
+
self.skip_btn.setStyleSheet(self.parent.parent.button_style_sheet_2)
|
|
481
|
+
self.skip_btn.clicked.connect(self.set_default_values)
|
|
482
|
+
btn_hbox.addWidget(self.skip_btn)
|
|
483
|
+
|
|
484
|
+
self.submit_btn = QPushButton('Submit')
|
|
485
|
+
self.submit_btn.setStyleSheet(self.parent.parent.button_style_sheet)
|
|
486
|
+
self.submit_btn.clicked.connect(self.set_user_values)
|
|
487
|
+
btn_hbox.addWidget(self.submit_btn)
|
|
488
|
+
self.layout.addLayout(btn_hbox)
|
|
489
|
+
|
|
490
|
+
def set_default_values(self):
|
|
491
|
+
|
|
492
|
+
for i in range(self.n_wells):
|
|
493
|
+
self.cell_type_cbs[i].setText(str(i))
|
|
494
|
+
self.antibodies_cbs[i].setText(str(i))
|
|
495
|
+
self.concentrations_cbs[i].setText(str(i))
|
|
496
|
+
self.pharmaceutical_agents_cbs[i].setText(str(i))
|
|
497
|
+
self.set_attributes()
|
|
498
|
+
self.parent.create_config_file()
|
|
499
|
+
self.close()
|
|
500
|
+
|
|
501
|
+
def set_user_values(self):
|
|
502
|
+
for i in range(self.n_wells):
|
|
503
|
+
if self.cell_type_cbs[i].text()=='':
|
|
504
|
+
self.cell_type_cbs[i].setText(str(i))
|
|
505
|
+
if self.antibodies_cbs[i].text()=='':
|
|
506
|
+
self.antibodies_cbs[i].setText(str(i))
|
|
507
|
+
if self.concentrations_cbs[i].text()=='':
|
|
508
|
+
self.concentrations_cbs[i].setText(str(i))
|
|
509
|
+
if self.pharmaceutical_agents_cbs[i].text()=='':
|
|
510
|
+
self.pharmaceutical_agents_cbs[i].setText(str(i))
|
|
511
|
+
self.set_attributes()
|
|
512
|
+
self.parent.create_config_file()
|
|
513
|
+
self.close()
|
|
514
|
+
|
|
515
|
+
def set_attributes(self):
|
|
516
|
+
|
|
517
|
+
cell_type_text = [c.text() for c in self.cell_type_cbs]
|
|
518
|
+
self.parent.cell_types = ','.join(cell_type_text)
|
|
519
|
+
|
|
520
|
+
antibodies_text = [c.text() for c in self.antibodies_cbs]
|
|
521
|
+
self.parent.antibodies = ','.join(antibodies_text)
|
|
522
|
+
|
|
523
|
+
concentrations_text = [c.text() for c in self.concentrations_cbs]
|
|
524
|
+
self.parent.concentrations = ','.join(concentrations_text)
|
|
525
|
+
|
|
526
|
+
pharamaceutical_text = [c.text() for c in self.pharmaceutical_agents_cbs]
|
|
527
|
+
self.parent.pharmaceutical_agents = ','.join(pharamaceutical_text)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|