Skip to content

Commit 0927606

Browse files
authored
Merge pull request #193 from GEECS-BELLA/add-optimization-to-gui
Add optimization to gui
2 parents 441ad59 + 9f84587 commit 0927606

File tree

9 files changed

+1590
-867
lines changed

9 files changed

+1590
-867
lines changed

GEECS-Scanner-GUI/geecs_scanner/app/geecs_scanner.py

Lines changed: 180 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,71 @@ def __init__(self):
5252

5353
self.ui = Ui_MainWindow()
5454
self.ui.setupUi(self)
55+
from PyQt5.QtWidgets import QButtonGroup
56+
57+
# --- make radios mutually exclusive
58+
self.modeGroup = QButtonGroup(self)
59+
for rb in (
60+
self.ui.backgroundRadioButton,
61+
self.ui.noscanRadioButton,
62+
self.ui.scanRadioButton,
63+
getattr(self.ui, "optimizationRadioButton", None),
64+
):
65+
if rb is not None:
66+
self.modeGroup.addButton(rb)
67+
68+
# --- find stack page indices by widget (orders can change in Designer)
69+
idx_no = self.ui.stackScanMode.indexOf(self.ui.pageNoScan)
70+
idx_scan = self.ui.stackScanMode.indexOf(self.ui.page1DScan)
71+
idx_opt = self.ui.stackScanMode.indexOf(self.ui.pageOptimize)
72+
73+
# sanity fallback (shouldn’t happen if pages exist)
74+
if min(idx_no, idx_scan, idx_opt) == -1:
75+
raise RuntimeError("Scan mode pages not found in stackScanMode")
76+
77+
def _apply_mode():
78+
"""Switch the stacked page based on which radio is checked."""
79+
if getattr(self.ui, "optimizationRadioButton", None) and self.ui.optimizationRadioButton.isChecked():
80+
self.ui.stackScanMode.setCurrentIndex(idx_opt)
81+
# optimizer usually controls shots, disable manual shots
82+
if hasattr(self.ui, "lineNumShots"):
83+
self.ui.lineNumShots.setEnabled(False)
84+
self.ui.startStopStepWidget.setEnabled(True)
85+
if hasattr(self, "populate_optimization_configs"):
86+
self.populate_optimization_configs()
87+
88+
elif self.ui.scanRadioButton.isChecked():
89+
self.ui.stackScanMode.setCurrentIndex(idx_scan)
90+
if hasattr(self.ui, "lineNumShots"):
91+
self.ui.lineNumShots.setEnabled(False) # derived in 1D mode
92+
if hasattr(self, "calculate_num_shots"):
93+
self.calculate_num_shots()
94+
self.ui.startStopStepWidget.setEnabled(True)
95+
else:
96+
# NoScan or Background both use the NoScan page
97+
self.ui.stackScanMode.setCurrentIndex(idx_no)
98+
if hasattr(self.ui, "lineNumShots"):
99+
self.ui.lineNumShots.setEnabled(True)
100+
self.ui.startStopStepWidget.setEnabled(True)
101+
102+
# --- connect radios; only switch when a radio becomes checked=True
103+
def _connect_toggle(rb):
104+
if rb is None:
105+
return
106+
rb.toggled.connect(lambda checked: _apply_mode() if checked else None)
107+
108+
_connect_toggle(self.ui.backgroundRadioButton)
109+
_connect_toggle(self.ui.noscanRadioButton)
110+
_connect_toggle(self.ui.scanRadioButton)
111+
_connect_toggle(getattr(self.ui, "optimizationRadioButton", None))
112+
113+
# --- pick an initial radio and apply once
114+
# Prefer keeping whatever Designer set; otherwise default to NoScan
115+
if not any(rb.isChecked() for rb in self.modeGroup.buttons()):
116+
self.ui.noscanRadioButton.setChecked(True)
117+
118+
_apply_mode()
119+
55120
self.setWindowTitle(f"GEECS Scanner - {CURRENT_VERSION}")
56121
self.setWindowIcon(QIcon(":/application_icon.ico"))
57122

@@ -730,6 +795,25 @@ def handle_returned_timing_configuration(self, specified_configuration):
730795

731796
# # # # # Functions that work with the scan parameter section of the GUI # # # # #
732797

798+
799+
def populate_optimization_configs(self):
800+
"""Populate the optimization config dropdown with *.yml / *.yaml files."""
801+
if not hasattr(self.ui, 'comboOptimizationConfig'):
802+
return
803+
combo = self.ui.comboOptimizationConfig
804+
combo.clear()
805+
try:
806+
d = self.app_paths.optimizer_configs()
807+
files = []
808+
if d is not None and d.exists():
809+
files = sorted([p for p in d.iterdir() if p.suffix.lower() in {'.yml', '.yaml'}])
810+
for pth in files:
811+
combo.addItem(pth.name, str(pth))
812+
if not files:
813+
combo.addItem('(no configs found)', '')
814+
except Exception as e:
815+
combo.addItem(f'(error: {e})', '')
816+
733817
def update_scan_edit_state(self):
734818
"""Depending on which radio button is selected, enable/disable text boxes for if this scan is a noscan or a
735819
variable scan. Previous values are saved so the user can switch between the two scan modes easily."""
@@ -748,8 +832,12 @@ def update_scan_edit_state(self):
748832
self.ui.lineNumShots.setText(str(self.noscan_num))
749833
self.ui.toolbuttonStepList.setEnabled(False)
750834
self.ui.toolbuttonStepList.setVisible(False)
835+
# # Hide optimization widgets when not in optimization mode
836+
# if hasattr(self.ui, 'labelOptimizationConfig'): self.ui.labelOptimizationConfig.setVisible(False)
837+
# if hasattr(self.ui, 'comboOptimizationConfig'): self.ui.comboOptimizationConfig.setVisible(False)
838+
if hasattr(self.ui, 'btnRefreshOptConfigs'): self.ui.btnRefreshOptConfigs.setVisible(False)
751839

752-
else:
840+
elif self.ui.scanRadioButton.isChecked():
753841
self.ui.lineScanVariable.setEnabled(True)
754842
self.ui.lineScanVariable.setText(self.scan_variable)
755843
self.ui.lineStartValue.setEnabled(True)
@@ -764,6 +852,40 @@ def update_scan_edit_state(self):
764852
self.calculate_num_shots()
765853
self.ui.toolbuttonStepList.setEnabled(True)
766854
self.ui.toolbuttonStepList.setVisible(True)
855+
# Hide optimization widgets when not in optimization mode
856+
# if hasattr(self.ui, 'labelOptimizationConfig'): self.ui.labelOptimizationConfig.setVisible(False)
857+
# if hasattr(self.ui, 'comboOptimizationConfig'): self.ui.comboOptimizationConfig.setVisible(False)
858+
if hasattr(self.ui, 'btnRefreshOptConfigs'): self.ui.btnRefreshOptConfigs.setVisible(False)
859+
860+
elif hasattr(self.ui, 'optimizationRadioButton') and self.ui.optimizationRadioButton.isChecked():
861+
# Disable/hide scan fields
862+
self.ui.lineScanVariable.setEnabled(False); self.ui.lineScanVariable.setText("")
863+
self.ui.lineStartValue.setEnabled(True); self.ui.lineStartValue.setText("")
864+
self.ui.lineStopValue.setEnabled(True); self.ui.lineStopValue.setText("")
865+
self.ui.lineStepSize.setEnabled(True); self.ui.lineStepSize.setText("")
866+
self.ui.lineShotStep.setEnabled(True); self.ui.lineShotStep.setText("")
867+
self.ui.toolbuttonStepList.setEnabled(False); self.ui.toolbuttonStepList.setVisible(False)
868+
# Hide optimization widgets when not in optimization mode
869+
# if hasattr(self.ui, 'labelOptimizationConfig'): self.ui.labelOptimizationConfig.setVisible(False)
870+
# if hasattr(self.ui, 'comboOptimizationConfig'): self.ui.comboOptimizationConfig.setVisible(False)
871+
# if hasattr(self.ui, 'btnRefreshOptConfigs'): self.ui.btnRefreshOptConfigs.setVisible(False)
872+
# # NumShots controlled by optimizer; disable
873+
self.ui.lineNumShots.setEnabled(False)
874+
# # Show optimization widgets
875+
self.calculate_num_shots()
876+
if hasattr(self.ui, 'labelOptimizationConfig'):
877+
self.ui.labelOptimizationConfig.setVisible(True)
878+
if hasattr(self.ui, 'comboOptimizationConfig'):
879+
self.ui.comboOptimizationConfig.setVisible(True)
880+
# if hasattr(self.ui, 'btnRefreshOptConfigs'):
881+
# self.ui.btnRefreshOptConfigs.setVisible(True)
882+
# Ensure dropdown is populated
883+
self.populate_optimization_configs()
884+
885+
else:
886+
pass
887+
888+
767889

768890
def populate_scan_variable_lists(self):
769891
"""Generates a list of found scan devices from the scan_devices.yaml file"""
@@ -1179,6 +1301,12 @@ def initialize_and_start_scan(self):
11791301
'experiment': self.experiment,
11801302
'description': self.ui.textEditScanInfo.toPlainText().replace('\n', ' ')
11811303
}
1304+
if hasattr(self.ui, 'optimizationRadioButton') and self.ui.optimizationRadioButton.isChecked():
1305+
try:
1306+
scan_information['optimization_config'] = selected_path if selected_path else self.ui.comboOptimizationConfig.currentData()
1307+
except Exception:
1308+
scan_information['optimization_config'] = self.ui.comboOptimizationConfig.currentData() if hasattr(self.ui, 'comboOptimizationConfig') else ''
1309+
11821310

11831311
if self.ui.scanRadioButton.isChecked():
11841312
scan_variable_tag = self.read_device_tag_from_nickname(self.scan_variable)
@@ -1194,38 +1322,62 @@ def initialize_and_start_scan(self):
11941322
scan_config = ScanConfig(
11951323
wait_time = (self.noscan_num + 0.5)/self.repetition_rate,
11961324
scan_mode = ScanMode.NOSCAN
1325+
)
11971326

1327+
elif hasattr(self.ui, 'optimizationRadioButton') and self.ui.optimizationRadioButton.isChecked():
1328+
# Optimization run: provide a minimal ScanConfig; the backend should inspect 'optimization_config' in scan_info
1329+
logging.info(f'ScanMode: {ScanMode}')
1330+
try:
1331+
scan_mode_opt = getattr(ScanMode, 'OPTIMIZATION', ScanMode.NOSCAN)
1332+
except Exception:
1333+
scan_mode_opt = ScanMode.NOSCAN
1334+
scan_config = ScanConfig(
1335+
start=self.scan_start,
1336+
end=self.scan_stop,
1337+
step=self.scan_step_size,
1338+
wait_time=(self.scan_shot_per_step + 0.5) / self.repetition_rate,
1339+
scan_mode = scan_mode_opt,
1340+
optimizer_config_path = scan_information['optimization_config']
11981341
)
1199-
else:
1200-
scan_config = None
1201-
scan_config.background = str(self.ui.backgroundRadioButton.isChecked())
12021342

1203-
option_dict = {
1204-
"rep_rate_hz": self.repetition_rate,
1205-
"randomized_beeps": self.ui.actionRandomizedBeeps.isChecked()
1206-
}
1207-
for opt in self.all_options:
1208-
option_dict[opt.get_name()] = opt.get_value()
1343+
# Attach selected optimization config path into scan_information (created below)
1344+
try:
1345+
selected_path = self.ui.comboOptimizationConfig.currentData() or ''
1346+
except Exception:
1347+
selected_path = ''
1348+
# We'll add to scan_information after it's created
12091349

1210-
run_config = {
1211-
'Devices': save_device_list,
1212-
'scan_info': scan_information,
1213-
'options': option_dict,
1214-
}
1350+
else:
1351+
scan_config = None
12151352

1216-
if list_of_setup_steps:
1217-
setup_action_steps = {'steps': list_of_setup_steps}
1218-
run_config['setup_action'] = setup_action_steps
1219-
if list_of_closeout_steps:
1220-
closeout_action_steps = {'steps': list_of_closeout_steps}
1221-
run_config['closeout_action'] = closeout_action_steps
1222-
1223-
success = self.RunControl.submit_run(config_dictionary=run_config, scan_config=scan_config)
1224-
if not success:
1225-
QMessageBox.critical(self, "Device Error",
1226-
f"Device reinitialization failed. Check log for problem device(s)")
1227-
self.is_starting = False
1228-
self.current_scan_number += 1
1353+
scan_config.background = str(self.ui.backgroundRadioButton.isChecked())
1354+
1355+
option_dict = {
1356+
"rep_rate_hz": self.repetition_rate,
1357+
"randomized_beeps": self.ui.actionRandomizedBeeps.isChecked()
1358+
}
1359+
for opt in self.all_options:
1360+
option_dict[opt.get_name()] = opt.get_value()
1361+
1362+
run_config = {
1363+
'Devices': save_device_list,
1364+
'scan_info': scan_information,
1365+
'options': option_dict,
1366+
}
1367+
1368+
if list_of_setup_steps:
1369+
setup_action_steps = {'steps': list_of_setup_steps}
1370+
run_config['setup_action'] = setup_action_steps
1371+
if list_of_closeout_steps:
1372+
closeout_action_steps = {'steps': list_of_closeout_steps}
1373+
run_config['closeout_action'] = closeout_action_steps
1374+
1375+
success = self.RunControl.submit_run(config_dictionary=run_config, scan_config=scan_config)
1376+
if not success:
1377+
QMessageBox.critical(self, "Device Error",
1378+
f"Device reinitialization failed. Check log for problem device(s)")
1379+
self.is_starting = False
1380+
self.current_scan_number += 1
12291381

12301382
@staticmethod
12311383
def combine_elements(dict_element1, dict_element2):

0 commit comments

Comments
 (0)