@@ -243,6 +243,10 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True):
243243 # initialize thread lock
244244 self .data_lock = threading .Lock ()
245245
246+ # intialize behavior baseline time flag
247+ self .behavior_baseline_period = threading .Event ()
248+ self .baseline_min_elapsed = 0
249+
246250 # create bias indicator
247251 self .bias_n_size = 200
248252 self .bias_indicator = BiasIndicator (
@@ -2761,6 +2765,7 @@ def keyPressEvent(self, event=None, allow_reset=False):
27612765 "laser_2_calibration_power" ,
27622766 "laser_1_calibration_voltage" ,
27632767 "laser_2_calibration_voltage" ,
2768+ "hab_time_box"
27642769 }:
27652770 continue
27662771 if child .objectName () == "UncoupledReward" :
@@ -4006,11 +4011,11 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0):
40064011 ):
40074012 self ._AddWaterLogResult (session )
40084013 elif self .BaseWeight .text () == "" or self .WeightAfter .text () == "" :
4009- logging .error (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims "
4010- f" due do unrecorded weight information." )
4014+ logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims"
4015+ f" due do unrecorded weight information." )
40114016 elif session is None :
4012- logging .error (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims "
4013- f" due do metadata generation failure." )
4017+ logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims"
4018+ f" due do metadata generation failure." )
40144019 except Exception as e :
40154020 logging .warning (
40164021 "Meta data is not saved!" ,
@@ -5456,6 +5461,7 @@ def _NewSession(self):
54565461
54575462 self .unsaved_data = False
54585463 self .ManualWaterVolume = [0 , 0 ]
5464+ self .baseline_min_elapsed = 0 # variable to track baseline time elapsed before session for start/stop
54595465
54605466 # Clear Plots
54615467 if hasattr (self , "PlotM" ) and self .clear_figure_after_save :
@@ -5892,57 +5898,6 @@ def _Start(self):
58925898 elif self .behavior_session_model .allow_dirty_repo is None :
58935899 logging .error ("Could not check for untracked local changes" )
58945900
5895- if self .PhotometryB .currentText () == "on" and (
5896- not self .FIP_started
5897- ):
5898- reply = QMessageBox .critical (
5899- self ,
5900- "Box {}, Start" .format (self .box_letter ),
5901- 'Photometry is set to "on", but the FIP workflow has not been started' ,
5902- QMessageBox .Ok ,
5903- )
5904- self .Start .setChecked (False )
5905- logging .info (
5906- "Cannot start session without starting FIP workflow"
5907- )
5908- return
5909-
5910- # Check if photometry excitation is running or not
5911- if self .PhotometryB .currentText () == "on" and (
5912- not self .StartExcitation .isChecked ()
5913- ):
5914- logging .warning (
5915- 'photometry is set to "on", but excitation is not running'
5916- )
5917-
5918- reply = QMessageBox .question (
5919- self ,
5920- "Box {}, Start" .format (self .box_letter ),
5921- 'Photometry is set to "on", but excitation is not running. Start excitation now?' ,
5922- QMessageBox .Yes | QMessageBox .No ,
5923- QMessageBox .Yes ,
5924- )
5925- if reply == QMessageBox .Yes :
5926- self .StartExcitation .setChecked (True )
5927- logging .info ("User selected to start excitation" )
5928- started = self ._StartExcitation ()
5929- if started == 0 :
5930- reply = QMessageBox .critical (
5931- self ,
5932- "Box {}, Start" .format (self .box_letter ),
5933- "Could not start excitation, therefore cannot start the session" ,
5934- QMessageBox .Ok ,
5935- )
5936- logging .info (
5937- "could not start session, due to failure to start excitation"
5938- )
5939- self .Start .setChecked (False )
5940- return
5941- else :
5942- logging .info ("User selected not to start excitation" )
5943- self .Start .setChecked (False )
5944- return
5945-
59465901 # disable sound button
59475902 self .sound_button .setEnabled (False )
59485903
@@ -5959,6 +5914,9 @@ def _Start(self):
59595914 self .session_run = True # session has been started
59605915 self .keyPressEvent (allow_reset = True )
59615916
5917+ # set flag to perform habituation period
5918+ self .behavior_baseline_period .set ()
5919+
59625920 else :
59635921 # Prompt user to confirm stopping trials
59645922 reply = QMessageBox .question (
@@ -5990,6 +5948,7 @@ def _Start(self):
59905948
59915949 self .session_end_tasks ()
59925950 self .sound_button .setEnabled (True )
5951+ self .behavior_baseline_period .clear () # set flag to break out of habituation period
59935952
59945953 if (self .StartANewSession == 1 ) and (self .ANewTrial == 0 ):
59955954 # If we are starting a new session, we should wait for the last trial to finish
@@ -6136,12 +6095,21 @@ def _Start(self):
61366095 workerStartTrialLoop1 = self .workerStartTrialLoop1
61376096 worker_save = self .worker_save
61386097
6098+ # pause for specified habituation time
6099+ if self .baseline_min_elapsed <= self .hab_time_box .value ():
6100+ self .wait_for_baseline ()
6101+
61396102 # collecting the base signal for photometry. Only run once
61406103 if (
6141- self .Start .isChecked ()
6142- and self .PhotometryB .currentText () == "on"
6143- and self .PhotometryRun == 0
6104+ self .Start .isChecked ()
6105+ and self .PhotometryB .currentText () == "on"
6106+ and self .PhotometryRun == 0
61446107 ):
6108+ # check if workflow is running and start photometry timer
6109+ if not self .photometry_workflow_running ():
6110+ self .Start .setChecked (False )
6111+ return
6112+
61456113 logging .info ("Starting photometry baseline timer" )
61466114 self .finish_Timer = 0
61476115 self .PhotometryRun = 1
@@ -6184,6 +6152,62 @@ def _Start(self):
61846152 except Exception :
61856153 logging .error (traceback .format_exc ())
61866154
6155+ def photometry_workflow_running (self ) -> bool or None :
6156+ """
6157+ If fiber photometery is configured for session, check if work flow is running
6158+
6159+ :returns: boolean indicating if workflow is running or not. If None, fip is not configured
6160+ """
6161+
6162+ if self .PhotometryB .currentText () == "on" and (
6163+ not self .FIP_started
6164+ ):
6165+ reply = QMessageBox .critical (
6166+ self ,
6167+ "Box {}, Start" .format (self .box_letter ),
6168+ 'Photometry is set to "on", but the FIP workflow has not been started' ,
6169+ QMessageBox .Ok ,
6170+ )
6171+
6172+ logging .info (
6173+ "Cannot start session without starting FIP workflow"
6174+ )
6175+ return False
6176+
6177+ # Check if photometry excitation is running or not
6178+ if self .PhotometryB .currentText () == "on" and not self .StartExcitation .isChecked ():
6179+ logging .warning ('photometry is set to "on", but excitation is not running' )
6180+
6181+ reply = QMessageBox .question (
6182+ self ,
6183+ "Box {}, Start" .format (self .box_letter ),
6184+ 'Photometry is set to "on", but excitation is not running. Start excitation now?' ,
6185+ QMessageBox .Yes | QMessageBox .No ,
6186+ QMessageBox .Yes ,
6187+ )
6188+ if reply == QMessageBox .Yes :
6189+ self .StartExcitation .setChecked (True )
6190+ logging .info ("User selected to start excitation" )
6191+ started = self ._StartExcitation ()
6192+ if started == 0 :
6193+ reply = QMessageBox .critical (
6194+ self ,
6195+ "Box {}, Start" .format (self .box_letter ),
6196+ "Could not start excitation, therefore cannot start the session" ,
6197+ QMessageBox .Ok ,
6198+ )
6199+ logging .info (
6200+ "could not start session, due to failure to start excitation"
6201+ )
6202+ self .Start .setChecked (False )
6203+ return False
6204+ else :
6205+ logging .info ("User selected not to start excitation" )
6206+ self .Start .setChecked (False )
6207+ return False
6208+
6209+ return True
6210+
61876211 def session_end_tasks (self ):
61886212 """
61896213 Data cleanup and saving that needs to be done at end of session.
@@ -6265,17 +6289,53 @@ def end_session_log(self) -> None:
62656289 else :
62666290 logging .info ("No active session logger" )
62676291
6292+ def wait_for_baseline (self ) -> None :
6293+ """
6294+ Function to wait for a baseline time before behavior
6295+ """
6296+
6297+ # pause for specified habituation time
6298+ start_time = time .time ()
6299+
6300+ # create habituation timer label and update every minute
6301+ hab_lab = QLabel ()
6302+ hab_lab .setStyleSheet (f"color: { self .default_warning_color } ;" )
6303+ self .warning_widget .layout ().insertWidget (0 , hab_lab )
6304+ update_hab_timer = QtCore .QTimer (
6305+ timeout = lambda : hab_lab .setText (f"Time elapsed: "
6306+ f"{ round ((self .baseline_min_elapsed * 60 ) // 60 )} minutes"
6307+ f" { round ((self .baseline_min_elapsed * 60 ) % 60 )} seconds" ),
6308+ interval = 1000 )
6309+ update_hab_timer .start ()
6310+
6311+ logging .info (f"Waiting { round (self .hab_time_box .value () - self .baseline_min_elapsed )} min before starting "
6312+ f"session." )
6313+
6314+ elapsed = self .baseline_min_elapsed
6315+ while self .baseline_min_elapsed < self .hab_time_box .value () and self .behavior_baseline_period .is_set ():
6316+ QApplication .processEvents ()
6317+ # update baseline time elapsed before session for start/stop logic
6318+ self .baseline_min_elapsed = ((time .time () - start_time ) / 60 ) + elapsed
6319+
6320+ update_hab_timer .stop ()
6321+ self .behavior_baseline_period .clear ()
6322+
6323+
62686324 def _StartTrialLoop (self , GeneratedTrials , worker1 , worker_save ):
6269- if self .Start .isChecked ():
6270- logging .info ("starting trial loop" )
6271- else :
6325+
6326+ if not self .Start .isChecked ():
62726327 logging .info ("ending trial loop" )
6328+ return
6329+
6330+ logging .info ("starting trial loop" )
62736331
62746332 # Track elapsed time in case Bonsai Stalls
62756333 last_trial_start = time .time ()
62766334 stall_iteration = 1
62776335 stall_duration = 5 * 60
62786336
6337+ logging .info (f"Starting session." )
6338+
62796339 while self .Start .isChecked ():
62806340 QApplication .processEvents ()
62816341 if (
0 commit comments