@@ -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