1010from PySide6 .QtCore import QObject , Signal
1111from PySide6 .QtWidgets import QFileDialog
1212
13+ from myogestic .gui .widgets .highlighter import WidgetHighlighter
1314from myogestic .gui .widgets .logger import LoggerLevel
1415from myogestic .gui .widgets .templates .output_system import OutputSystemTemplate
1516from myogestic .gui .widgets .templates .visual_interface import VisualInterface
@@ -71,6 +72,7 @@ def __init__(self, main_window: MyoGestic) -> None:
7172 super ().__init__ (main_window )
7273
7374 self ._main_window = main_window
75+ self ._highlighter = WidgetHighlighter (self )
7476
7577 self ._selected_visual_interface : VisualInterface | None = None
7678 self ._active_visual_interfaces : dict [str , VisualInterface ] = {}
@@ -111,6 +113,9 @@ def __init__(self, main_window: MyoGestic) -> None:
111113
112114 self ._output_systems__dict : dict [str , OutputSystemTemplate ] = {}
113115
116+ # Pending model path for auto-loading after training
117+ self ._pending_model_path : str | None = None
118+
114119 def _update_real_time_filter (self ) -> None :
115120 self ._model_interface .set_real_time_filter (self .real_time_filter_combo_box .currentText ())
116121
@@ -132,6 +137,9 @@ def _update_device_configuration(self, is_configured: bool) -> None:
132137
133138 self .online_load_model_push_button .setEnabled (True )
134139
140+ # Guide user to load a model
141+ self ._highlighter .highlight (self .online_load_model_push_button )
142+
135143 def online_emg_update (self , data : np .ndarray ) -> None :
136144 try :
137145 (
@@ -234,9 +242,11 @@ def _toggle_recording(self):
234242
235243 self .recording_start_time = time .time ()
236244 self .online_record_toggle_push_button .setText ("Stop Recording" )
245+ # Green when recording
246+ self .online_record_toggle_push_button .setStyleSheet ("background-color: #5cb85c; color: white;" )
237247 else :
238248 self .online_prediction_toggle_push_button .setEnabled (True )
239-
249+
240250 # Disconnect from the primary VI
241251 if self ._selected_visual_interface is not None :
242252 self ._selected_visual_interface .incoming_message_signal .disconnect (
@@ -245,6 +255,8 @@ def _toggle_recording(self):
245255 self ._selected_visual_interface .setup_interface_ui .disconnect_custom_signals ()
246256
247257 self .online_record_toggle_push_button .setText ("Start Recording" )
258+ # Red when not recording
259+ self .online_record_toggle_push_button .setStyleSheet ("background-color: #d9534f; color: white;" )
248260
249261 self ._save_data ()
250262
@@ -330,6 +342,9 @@ def _load_model(self) -> None:
330342 LoggerLevel .INFO ,
331343 )
332344
345+ # Highlight the Start Prediction button to guide user
346+ self ._highlighter .highlight (self .online_prediction_toggle_push_button )
347+
333348 if len (self .active_monitoring_widgets ) != 0 :
334349 self ._send_model_information ()
335350
@@ -359,6 +374,82 @@ def _load_model(self) -> None:
359374 k : v (self ._main_window , self ._model_interface .model .is_classifier ) for k , v in current_output_map .items ()
360375 }
361376
377+ def _auto_load_model (self , model_path : str ) -> None :
378+ """Load a model programmatically without file dialog.
379+
380+ This is used after training completes to automatically load the newly
381+ trained model into the Online protocol.
382+
383+ Args:
384+ model_path: Full path to the model .pkl file
385+ """
386+ if not model_path :
387+ return
388+
389+ # Stop any flashing on Load Model button since we're auto-loading
390+ self ._highlighter ._stop_widget_flash (self .online_load_model_push_button )
391+
392+ # Ensure model interface exists (device must be configured)
393+ if self ._model_interface is None :
394+ self ._main_window .logger .print (
395+ "Cannot auto-load model: device not configured" ,
396+ LoggerLevel .WARNING ,
397+ )
398+ return
399+
400+ try :
401+ self ._model_information__dict = self ._model_interface .load_model (model_path )
402+ except Exception as e :
403+ self ._main_window .logger .print (
404+ f"Error in auto-loading model: { e } " , LoggerLevel .ERROR
405+ )
406+ return
407+
408+ label = model_path .split ("/" )[- 1 ].split ("_" )[- 1 ].split ("." )[0 ]
409+
410+ self .online_model_label .setText (f"{ label } loaded!" )
411+
412+ self .online_commands_group_box .setEnabled (True )
413+ self .online_record_toggle_push_button .setEnabled (False )
414+
415+ self ._main_window .logger .print (
416+ f"Model auto-loaded. Label: { label } " ,
417+ LoggerLevel .INFO ,
418+ )
419+
420+ # Highlight the Start Prediction button to guide user
421+ self ._highlighter .highlight (self .online_prediction_toggle_push_button )
422+
423+ if len (self .active_monitoring_widgets ) != 0 :
424+ self ._send_model_information ()
425+
426+ # Get active VIs directly from main window to ensure we have current state
427+ active_vi_names = set (self ._main_window .active_visual_interfaces .keys ())
428+ trained_vi_name = self ._model_information__dict .get ("visual_interface" )
429+
430+ if trained_vi_name and active_vi_names and trained_vi_name not in active_vi_names :
431+ self ._main_window .logger .print (
432+ f"Warning: Model was trained with visual interface '{ trained_vi_name } ' "
433+ f"but it is not among the active interfaces: { active_vi_names } ." ,
434+ LoggerLevel .WARNING ,
435+ )
436+
437+ # Only instantiate output systems for VIs that are currently active
438+ current_output_map = {
439+ k : v for k , v in CONFIG_REGISTRY .output_systems_map .items ()
440+ if k in active_vi_names
441+ }
442+
443+ self ._main_window .logger .print (
444+ f"Creating output systems for: { list (current_output_map .keys ())} " ,
445+ LoggerLevel .INFO ,
446+ )
447+
448+ self ._output_systems__dict = {
449+ k : v (self ._main_window , self ._model_interface .model .is_classifier )
450+ for k , v in current_output_map .items ()
451+ }
452+
362453 def close_event (self , event ) -> None :
363454 for output_system in self ._output_systems__dict .values ():
364455 output_system .close_event (event )
@@ -402,6 +493,8 @@ def _setup_protocol_ui(self) -> None:
402493 "Record predictions and ground truth for analysis"
403494 )
404495 self .online_record_toggle_push_button .clicked .connect (self ._toggle_recording )
496+ # Red by default (not recording)
497+ self .online_record_toggle_push_button .setStyleSheet ("background-color: #d9534f; color: white;" )
405498
406499 self .online_prediction_toggle_push_button = self ._main_window .ui .onlinePredictionTogglePushButton
407500 self .online_prediction_toggle_push_button .setToolTip (
0 commit comments