Skip to content

Commit 9754b58

Browse files
committed
Merge branch 'develop' into log_mem
2 parents 8b204f9 + a43d617 commit 9754b58

File tree

13 files changed

+518
-177
lines changed

13 files changed

+518
-177
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,8 @@ lock.py
127127
# config ini files
128128
*/configs/*.ini
129129

130+
# study config
131+
ulc_mm_package/study_configurations/*.toml
132+
130133
#logging config
131134
/log_config

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def readme():
4040
"py_cameras @ git+https://github.com/czbiohub/pyCameras@master",
4141
"pymotors @ git+https://github.com/czbiohub/PyMotors@master",
4242
"stats_utils @ git+https://github.com/czbiohub-sf/[email protected]",
43+
"tomli==2.0.1",
4344
"typer==0.4.1",
4445
"tqdm==4.63.0",
4546
"transitions==0.8.11",

ulc_mm_package/QtGUI/form_gui.py

Lines changed: 40 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@
1818
QDesktopWidget,
1919
)
2020
from PyQt5.QtGui import QIcon
21-
from PyQt5.QtCore import pyqtSignal, QDate, QTime
21+
from PyQt5.QtCore import pyqtSignal
2222

2323
from ulc_mm_package.scope_constants import EXPERIMENT_METADATA_KEYS
2424
from ulc_mm_package.image_processing.processing_constants import TARGET_FLOWRATE
25-
from PyQt5.QtWidgets import QDateEdit, QTimeEdit
2625
from ulc_mm_package.QtGUI.gui_constants import (
2726
ICON_PATH,
2827
SITE_ENV_VAR,
2928
SITE_LIST,
3029
SAMPLE_LIST,
3130
TOOLBAR_OFFSET,
3231
)
32+
import ulc_mm_package.QtGUI.study_metadata_form as study_form
3333

3434

3535
class FormGUI(QDialog):
@@ -42,22 +42,20 @@ def __init__(self):
4242

4343
self._load_ui()
4444

45+
def _study_select(self) -> None:
46+
text = self.study_select_val.currentText()
47+
if text != "":
48+
self.start_btn.setText("Enter additional study metadata")
49+
else:
50+
self.start_btn.setText("Start")
51+
4552
def closeEvent(self, event):
4653
if event.spontaneous():
4754
self.close_event.emit()
4855
event.ignore()
4956
else:
5057
event.accept()
5158

52-
def _validate_temperature(self):
53-
text = self.sample_storage_temp.text()
54-
if not text.endswith(("C", "F", "c", "f")) or not text[:-1].isdigit():
55-
self.sample_storage_temp.setStyleSheet("border: 1px solid red;")
56-
self.start_btn.setEnabled(False)
57-
else:
58-
self.sample_storage_temp.setStyleSheet("")
59-
self.start_btn.setEnabled(True)
60-
6159
def _load_ui(self):
6260
self.setWindowTitle("Experiment form")
6361

@@ -87,35 +85,16 @@ def _load_ui(self):
8785

8886
# Labels
8987
self.operator_lbl = QLabel("Operator ID")
90-
self.participant_lbl = QLabel("Non-identifying participant ID")
91-
self.sample_collection_date_lbl = QLabel("Sample collection date")
92-
self.sample_collection_time_lbl = QLabel("Sample collection time")
93-
self.sample_storage_temp_lbl = QLabel("Sample storage temperature (°C or °F)")
88+
self.sample_id_lbl = QLabel("Non-identifying sample ID")
89+
self.study_select_lbl = QLabel("Select study")
9490
self.flowcell_lbl = QLabel("Flowcell ID")
9591
self.notes_lbl = QLabel("Other notes")
9692
self.site_lbl = QLabel("Site")
9793
self.sample_lbl = QLabel("Sample type")
9894

9995
# Text boxes
10096
self.operator_val = QLineEdit()
101-
self.participant_val = QLineEdit()
102-
103-
# Sample collection date
104-
self.sample_collection_date = QDateEdit(QDate.currentDate())
105-
self.sample_collection_date.setCalendarPopup(True)
106-
self.sample_collection_date.setDisplayFormat("yyyy-MMM-dd")
107-
108-
# Sample collection time
109-
self.sample_collection_time = QTimeEdit(QTime.currentTime())
110-
self.sample_collection_time.setDisplayFormat("hh:mm AP")
111-
self.sample_collection_time.setCalendarPopup(True)
112-
113-
# Sample storage temperature
114-
self.sample_storage_temp = QLineEdit()
115-
self.sample_storage_temp.setPlaceholderText(
116-
"Enter temperature (e.g. 25C or 77F)"
117-
)
118-
self.sample_storage_temp.textChanged.connect(self._validate_temperature)
97+
self.sample_id_val = QLineEdit()
11998

12099
self.flowcell_val = QLineEdit()
121100
self.notes_val = QPlainTextEdit()
@@ -127,9 +106,15 @@ def _load_ui(self):
127106
# Dropdown menus
128107
self.site_val = QComboBox()
129108
self.sample_val = QComboBox()
109+
self.study_select_val = QComboBox()
110+
self.study_select_val.currentTextChanged.connect(self._study_select)
130111

131112
self.site_val.addItems(SITE_LIST)
132113
self.sample_val.addItems(SAMPLE_LIST)
114+
self.study_select_val.addItems(
115+
list(study_form.list_available_studies().keys()) + [""]
116+
)
117+
self.study_select_val.setCurrentIndex(-1)
133118

134119
if SITE_ENV_VAR is not None:
135120
self.site_val.setEnabled(False)
@@ -139,21 +124,17 @@ def _load_ui(self):
139124

140125
# Place widgets
141126
self.main_layout.addWidget(self.operator_lbl, 0, 0)
142-
self.main_layout.addWidget(self.participant_lbl, 1, 0)
143-
self.main_layout.addWidget(self.sample_collection_date_lbl, 2, 0)
144-
self.main_layout.addWidget(self.sample_collection_time_lbl, 3, 0)
145-
self.main_layout.addWidget(self.sample_storage_temp_lbl, 4, 0)
127+
self.main_layout.addWidget(self.sample_id_lbl, 1, 0)
128+
self.main_layout.addWidget(self.study_select_lbl, 2, 0)
146129
self.main_layout.addWidget(self.flowcell_lbl, 5, 0)
147130
self.main_layout.addWidget(self.site_lbl, 6, 0)
148131
self.main_layout.addWidget(self.sample_lbl, 7, 0)
149132
self.main_layout.addWidget(self.notes_lbl, 8, 0)
150133
self.main_layout.addWidget(self.exit_btn, 9, 0)
151134

152135
self.main_layout.addWidget(self.operator_val, 0, 1)
153-
self.main_layout.addWidget(self.participant_val, 1, 1)
154-
self.main_layout.addWidget(self.sample_collection_date, 2, 1)
155-
self.main_layout.addWidget(self.sample_collection_time, 3, 1)
156-
self.main_layout.addWidget(self.sample_storage_temp, 4, 1)
136+
self.main_layout.addWidget(self.sample_id_val, 1, 1)
137+
self.main_layout.addWidget(self.study_select_val, 2, 1)
157138
self.main_layout.addWidget(self.flowcell_val, 5, 1)
158139
self.main_layout.addWidget(self.site_val, 6, 1)
159140
self.main_layout.addWidget(self.sample_val, 7, 1)
@@ -165,33 +146,24 @@ def _load_ui(self):
165146
self.start_btn.setDefault(True)
166147

167148
def get_form_input(self) -> dict:
168-
# Determine the sample age from the current time and the sample collection time
169-
current_date = QDate.currentDate()
170-
current_time = QTime.currentTime()
171-
172-
sample_date = self.sample_collection_date.date()
173-
sample_time = self.sample_collection_time.time()
174-
175-
date_diff_in_hours = sample_date.daysTo(current_date) * 24
176-
time_diff_in_hours = sample_time.secsTo(current_time) / 3600
177-
178-
sample_age_hours = round(date_diff_in_hours + time_diff_in_hours, 2)
149+
study_name = self.study_select_val.currentText()
150+
if study_name != "":
151+
study_id = study_form.get_study_id_from_name(study_name)
152+
else:
153+
study_id = None
179154

180155
form_metadata = {
181156
"operator_id": self.operator_val.text(),
182-
"participant_id": self.participant_val.text(),
157+
"sample_id": self.sample_id_val.text(),
183158
"flowcell_id": self.flowcell_val.text(),
184-
"sample_collection_date": self.sample_collection_date.text(),
185-
"sample_collection_time": self.sample_collection_time.text(),
186-
"sample_age_hours": sample_age_hours,
187-
"sample_storage_temp": self.sample_storage_temp.text(),
188159
"target_flowrate": (
189160
TARGET_FLOWRATE.name.capitalize(),
190161
TARGET_FLOWRATE.value,
191162
), # fixed flowrate
192163
"site": self.site_val.currentText(),
193164
"sample_type": self.sample_val.currentText(),
194165
"notes": self.notes_val.toPlainText(),
166+
"study_id": study_id,
195167
}
196168

197169
if not all(key in EXPERIMENT_METADATA_KEYS for key in form_metadata):
@@ -201,15 +173,25 @@ def get_form_input(self) -> dict:
201173

202174
def reset_parameters(self) -> None:
203175
"""Clear specific inputs which are expected to be unique for the next run."""
204-
self.participant_val.setText("")
176+
self.sample_id_val.setText("")
205177
self.flowcell_val.setText("")
206178
self.notes_val.setPlainText("")
207179

180+
def sf(self) -> None:
181+
metadata = self.get_form_input()
182+
cfg = study_form.get_cfg_from_study_id(metadata["study_id"])
183+
if cfg is not None:
184+
sf = study_form.StudyMetadata(cfg, gui)
185+
sf.show()
186+
else:
187+
raise ValueError(f"Was unable to fetch {metadata['study_id']}")
188+
208189

209190
if __name__ == "__main__":
210191
app = QApplication(sys.argv)
211192
gui = FormGUI()
212193
gui.exit_btn.clicked.connect(gui.close)
194+
gui.start_btn.clicked.connect(gui.sf)
213195

214196
print(gui.get_form_input())
215197

ulc_mm_package/QtGUI/liveview_gui.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def closeEvent(self, event):
9191
def update_experiment(self, metadata: dict):
9292
# TODO standardize dict input
9393
self.operator_val.setText(f"{metadata['operator_id']}")
94-
self.participant_val.setText(f"{metadata['participant_id']}")
94+
self.sample_val.setText(f"{metadata['sample_id']}")
9595
self.flowcell_val.setText(f"{metadata['flowcell_id']}")
9696
self.target_flowrate_val.setText(f"{metadata['target_flowrate'][0]}")
9797
self.site_val.setText(f"{metadata['site']}")
@@ -560,35 +560,35 @@ def _load_metadata_ui(self):
560560

561561
# Populate metadata tab
562562
self.operator_lbl = QLabel("Operator ID")
563-
self.participant_lbl = QLabel("Participant ID")
563+
self.sample_lbl = QLabel("Sample ID")
564564
self.flowcell_lbl = QLabel("Flowcell ID")
565565
self.target_flowrate_lbl = QLabel("Flowrate")
566566
self.site_lbl = QLabel("Site")
567567
self.notes_lbl = QLabel("Other notes")
568568

569569
self.operator_val = QLineEdit()
570-
self.participant_val = QLineEdit()
570+
self.sample_val = QLineEdit()
571571
self.flowcell_val = QLineEdit()
572572
self.target_flowrate_val = QLineEdit()
573573
self.site_val = QLineEdit()
574574
self.notes_val = QPlainTextEdit()
575575

576576
self.operator_val.setReadOnly(True)
577-
self.participant_val.setReadOnly(True)
577+
self.sample_val.setReadOnly(True)
578578
self.flowcell_val.setReadOnly(True)
579579
self.target_flowrate_val.setReadOnly(True)
580580
self.site_val.setReadOnly(True)
581581
self.notes_val.setReadOnly(True)
582582

583583
self.metadata_layout.addWidget(self.operator_lbl, 1, 1)
584-
self.metadata_layout.addWidget(self.participant_lbl, 2, 1)
584+
self.metadata_layout.addWidget(self.sample_lbl, 2, 1)
585585
self.metadata_layout.addWidget(self.flowcell_lbl, 3, 1)
586586
self.metadata_layout.addWidget(self.target_flowrate_lbl, 4, 1)
587587
self.metadata_layout.addWidget(self.site_lbl, 5, 1)
588588
self.metadata_layout.addWidget(self.notes_lbl, 6, 1)
589589

590590
self.metadata_layout.addWidget(self.operator_val, 1, 2)
591-
self.metadata_layout.addWidget(self.participant_val, 2, 2)
591+
self.metadata_layout.addWidget(self.sample_val, 2, 2)
592592
self.metadata_layout.addWidget(self.flowcell_val, 3, 2)
593593
self.metadata_layout.addWidget(self.target_flowrate_val, 4, 2)
594594
self.metadata_layout.addWidget(self.site_val, 5, 2)
@@ -601,7 +601,7 @@ def _load_metadata_ui(self):
601601

602602
experiment_metadata = {
603603
"operator_id": "1234",
604-
"participant_id": "567",
604+
"sample_id": "567",
605605
"flowcell_id": "A2",
606606
"target_flowrate": ("Fast", 15),
607607
"site": "Uganda",

ulc_mm_package/QtGUI/oracle.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
)
3636
from PyQt5.QtCore import Qt, QThread, pyqtSignal
3737
from PyQt5.QtGui import QIcon, QPixmap
38+
from PyQt5.QtWidgets import QPushButton
3839

3940
from ulc_mm_package.scope_constants import (
4041
LOCKFILE,
@@ -68,6 +69,7 @@
6869
FAIL_MSG,
6970
TERMINATED_MSG,
7071
)
72+
import ulc_mm_package.QtGUI.study_metadata_form as study_form
7173
from ulc_mm_package.neural_nets.neural_network_constants import (
7274
AUTOFOCUS_MODEL_DIR,
7375
YOGO_MODEL_DIR,
@@ -77,7 +79,6 @@
7779
from ulc_mm_package.QtGUI.scope_op import ScopeOp
7880
from ulc_mm_package.QtGUI.form_gui import FormGUI
7981
from ulc_mm_package.QtGUI.liveview_gui import LiveviewGUI
80-
from PyQt5.QtWidgets import QPushButton
8182

8283

8384
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
@@ -209,6 +210,7 @@ def _check_lock(self):
209210
def _set_variables(self):
210211
# Instantiate metadata dicts
211212
self.form_metadata = None
213+
self.study_metadata = None
212214
self.experiment_metadata = {key: None for key in EXPERIMENT_METADATA_KEYS}
213215

214216
self.liveview_window.set_infopanel_vals()
@@ -268,7 +270,7 @@ def _init_states(self):
268270

269271
def _init_sigslots(self):
270272
# Connect experiment form buttons
271-
self.form_window.start_btn.clicked.connect(self.save_form)
273+
self.form_window.start_btn.clicked.connect(self._maybe_start_study_form)
272274
self.form_window.exit_btn.clicked.connect(self.form_exit_handler)
273275
self.form_window.close_event.connect(self.close_handler)
274276

@@ -428,7 +430,7 @@ def general_pause_handler(
428430
(
429431
"The CAP module can now be removed."
430432
"\n\nPlease empty both reservoirs and reload 12 uL of fresh "
431-
"diluted blood (from the same participant) into the sample reservoir. Make sure to close the lid after."
433+
"diluted blood (from the same sample) into the sample reservoir. Make sure to close the lid after."
432434
'\n\nAfter reloading the reservoir and closing the lid, click "OK" to resume this run.'
433435
),
434436
buttons=Buttons.OK,
@@ -626,6 +628,26 @@ def _end_setup(self, *args):
626628
def _start_form(self, *args):
627629
self.form_window.showMaximized()
628630

631+
def _maybe_start_study_form(self):
632+
form_metadata = self.form_window.get_form_input()
633+
study_id = form_metadata["study_id"]
634+
if study_id != "":
635+
cfg = study_form.get_cfg_from_study_id(study_id)
636+
if cfg is not None:
637+
self.study_form_dialog = study_form.StudyMetadata(cfg, self.form_window)
638+
self.study_form_dialog.btn_start.clicked.connect(
639+
self.get_study_metadata
640+
)
641+
self.study_form_dialog.showMaximized()
642+
else:
643+
self.save_form()
644+
else:
645+
self.save_form()
646+
647+
def get_study_metadata(self):
648+
self.study_metadata = self.study_form_dialog.get_form_input()
649+
self.save_form()
650+
629651
def save_form(self):
630652
self.form_metadata = self.form_window.get_form_input()
631653
self.form_window.reset_parameters()
@@ -635,10 +657,6 @@ def save_form(self):
635657
for key in self.form_metadata:
636658
self.experiment_metadata[key] = self.form_metadata[key]
637659

638-
# DATA-TODO verify if user input satisfies required format
639-
# -> if data fails verification, prompt user for correction using "display_message" (defined above)
640-
# -> if data passes verification, call "self.next_state" to open liveview
641-
642660
# Assign other metadata parameters
643661
self.experiment_metadata["scope"] = socket.gethostname()
644662
self.experiment_metadata["camera"] = CAMERA_SELECTION.name
@@ -688,6 +706,7 @@ def save_form(self):
688706
"",
689707
self.datetime_str,
690708
self.experiment_metadata,
709+
self.study_metadata,
691710
PER_IMAGE_METADATA_KEYS,
692711
)
693712

0 commit comments

Comments
 (0)