@@ -243,6 +243,10 @@ def __init__(self, parent=None, box_number=1, start_bonsai_ide=True):
243
243
# initialize thread lock
244
244
self .data_lock = threading .Lock ()
245
245
246
+ # intialize behavior baseline time flag
247
+ self .behavior_baseline_period = threading .Event ()
248
+ self .baseline_min_elapsed = 0
249
+
246
250
# create bias indicator
247
251
self .bias_n_size = 200
248
252
self .bias_indicator = BiasIndicator (
@@ -1552,12 +1556,30 @@ def _check_line_terminator(self, file_path):
1552
1556
def _LoadSchedule (self ):
1553
1557
if os .path .exists (self .Settings ["schedule_path" ]):
1554
1558
schedule = pd .read_csv (self .Settings ["schedule_path" ])
1559
+
1560
+ # Find the correct week on the schedule
1561
+ dividers = schedule [[isinstance (x ,str )and ('/' in x ) for x in schedule ['Mouse ID' ].values ]]
1562
+ today = datetime .now ().strftime ('%m/%d/%Y' )
1563
+
1564
+ # Multiple weeks on the schedule
1565
+ if len (dividers ) > 1 :
1566
+ first = dividers .iloc [0 ]['Mouse ID' ]
1567
+ if datetime .strptime (today , "%m/%d/%Y" ) < datetime .strptime (first ,"%m/%d/%Y" ):
1568
+ # Use last weeks schedule
1569
+ schedule = schedule .loc [dividers .index .values [1 ]:]
1570
+ else :
1571
+ # Use this weeks schedule
1572
+ schedule = schedule .loc [0 :dividers .index .values [1 ]]
1573
+
1574
+ # Determine what mice are on the schedule
1555
1575
self .schedule_mice = [
1556
1576
x
1557
1577
for x in schedule ["Mouse ID" ].unique ()
1558
1578
if isinstance (x , str ) and (len (x ) > 3 ) and ("/" not in x )
1559
1579
]
1560
- self .schedule = schedule .dropna (subset = ["Mouse ID" , "Box" ]).copy ()
1580
+
1581
+ # Clear rows without a mouse
1582
+ self .schedule = schedule .dropna (subset = ["Mouse ID" ]).copy ()
1561
1583
logging .info ("Loaded behavior schedule" )
1562
1584
else :
1563
1585
self .schedule_mice = None
@@ -2761,6 +2783,7 @@ def keyPressEvent(self, event=None, allow_reset=False):
2761
2783
"laser_2_calibration_power" ,
2762
2784
"laser_1_calibration_voltage" ,
2763
2785
"laser_2_calibration_voltage" ,
2786
+ "hab_time_box"
2764
2787
}:
2765
2788
continue
2766
2789
if child .objectName () == "UncoupledReward" :
@@ -4006,11 +4029,11 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0):
4006
4029
):
4007
4030
self ._AddWaterLogResult (session )
4008
4031
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." )
4032
+ logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims"
4033
+ f" due do unrecorded weight information." )
4011
4034
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." )
4035
+ logging .warning (f"Waterlog for mouse { self .behavior_session_model .subject } cannot be added to slims"
4036
+ f" due do metadata generation failure." )
4014
4037
except Exception as e :
4015
4038
logging .warning (
4016
4039
"Meta data is not saved!" ,
@@ -5457,6 +5480,7 @@ def _NewSession(self):
5457
5480
5458
5481
self .unsaved_data = False
5459
5482
self .ManualWaterVolume = [0 , 0 ]
5483
+ self .baseline_min_elapsed = 0 # variable to track baseline time elapsed before session for start/stop
5460
5484
5461
5485
# Clear Plots
5462
5486
if hasattr (self , "PlotM" ) and self .clear_figure_after_save :
@@ -5893,57 +5917,6 @@ def _Start(self):
5893
5917
elif self .behavior_session_model .allow_dirty_repo is None :
5894
5918
logging .error ("Could not check for untracked local changes" )
5895
5919
5896
- if self .PhotometryB .currentText () == "on" and (
5897
- not self .FIP_started
5898
- ):
5899
- reply = QMessageBox .critical (
5900
- self ,
5901
- "Box {}, Start" .format (self .box_letter ),
5902
- 'Photometry is set to "on", but the FIP workflow has not been started' ,
5903
- QMessageBox .Ok ,
5904
- )
5905
- self .Start .setChecked (False )
5906
- logging .info (
5907
- "Cannot start session without starting FIP workflow"
5908
- )
5909
- return
5910
-
5911
- # Check if photometry excitation is running or not
5912
- if self .PhotometryB .currentText () == "on" and (
5913
- not self .StartExcitation .isChecked ()
5914
- ):
5915
- logging .warning (
5916
- 'photometry is set to "on", but excitation is not running'
5917
- )
5918
-
5919
- reply = QMessageBox .question (
5920
- self ,
5921
- "Box {}, Start" .format (self .box_letter ),
5922
- 'Photometry is set to "on", but excitation is not running. Start excitation now?' ,
5923
- QMessageBox .Yes | QMessageBox .No ,
5924
- QMessageBox .Yes ,
5925
- )
5926
- if reply == QMessageBox .Yes :
5927
- self .StartExcitation .setChecked (True )
5928
- logging .info ("User selected to start excitation" )
5929
- started = self ._StartExcitation ()
5930
- if started == 0 :
5931
- reply = QMessageBox .critical (
5932
- self ,
5933
- "Box {}, Start" .format (self .box_letter ),
5934
- "Could not start excitation, therefore cannot start the session" ,
5935
- QMessageBox .Ok ,
5936
- )
5937
- logging .info (
5938
- "could not start session, due to failure to start excitation"
5939
- )
5940
- self .Start .setChecked (False )
5941
- return
5942
- else :
5943
- logging .info ("User selected not to start excitation" )
5944
- self .Start .setChecked (False )
5945
- return
5946
-
5947
5920
# disable sound button
5948
5921
self .sound_button .setEnabled (False )
5949
5922
@@ -5960,6 +5933,9 @@ def _Start(self):
5960
5933
self .session_run = True # session has been started
5961
5934
self .keyPressEvent (allow_reset = True )
5962
5935
5936
+ # set flag to perform habituation period
5937
+ self .behavior_baseline_period .set ()
5938
+
5963
5939
else :
5964
5940
# Prompt user to confirm stopping trials
5965
5941
reply = QMessageBox .question (
@@ -5991,6 +5967,7 @@ def _Start(self):
5991
5967
5992
5968
self .session_end_tasks ()
5993
5969
self .sound_button .setEnabled (True )
5970
+ self .behavior_baseline_period .clear () # set flag to break out of habituation period
5994
5971
5995
5972
if (self .StartANewSession == 1 ) and (self .ANewTrial == 0 ):
5996
5973
# If we are starting a new session, we should wait for the last trial to finish
@@ -6137,12 +6114,21 @@ def _Start(self):
6137
6114
workerStartTrialLoop1 = self .workerStartTrialLoop1
6138
6115
worker_save = self .worker_save
6139
6116
6117
+ # pause for specified habituation time
6118
+ if self .baseline_min_elapsed <= self .hab_time_box .value ():
6119
+ self .wait_for_baseline ()
6120
+
6140
6121
# collecting the base signal for photometry. Only run once
6141
6122
if (
6142
- self .Start .isChecked ()
6143
- and self .PhotometryB .currentText () == "on"
6144
- and self .PhotometryRun == 0
6123
+ self .Start .isChecked ()
6124
+ and self .PhotometryB .currentText () == "on"
6125
+ and self .PhotometryRun == 0
6145
6126
):
6127
+ # check if workflow is running and start photometry timer
6128
+ if not self .photometry_workflow_running ():
6129
+ self .Start .setChecked (False )
6130
+ return
6131
+
6146
6132
logging .info ("Starting photometry baseline timer" )
6147
6133
self .finish_Timer = 0
6148
6134
self .PhotometryRun = 1
@@ -6322,17 +6308,53 @@ def end_session_log(self) -> None:
6322
6308
else :
6323
6309
logging .info ("No active session logger" )
6324
6310
6311
+ def wait_for_baseline (self ) -> None :
6312
+ """
6313
+ Function to wait for a baseline time before behavior
6314
+ """
6315
+
6316
+ # pause for specified habituation time
6317
+ start_time = time .time ()
6318
+
6319
+ # create habituation timer label and update every minute
6320
+ hab_lab = QLabel ()
6321
+ hab_lab .setStyleSheet (f"color: { self .default_warning_color } ;" )
6322
+ self .warning_widget .layout ().insertWidget (0 , hab_lab )
6323
+ update_hab_timer = QtCore .QTimer (
6324
+ timeout = lambda : hab_lab .setText (f"Time elapsed: "
6325
+ f"{ round ((self .baseline_min_elapsed * 60 ) // 60 )} minutes"
6326
+ f" { round ((self .baseline_min_elapsed * 60 ) % 60 )} seconds" ),
6327
+ interval = 1000 )
6328
+ update_hab_timer .start ()
6329
+
6330
+ logging .info (f"Waiting { round (self .hab_time_box .value () - self .baseline_min_elapsed )} min before starting "
6331
+ f"session." )
6332
+
6333
+ elapsed = self .baseline_min_elapsed
6334
+ while self .baseline_min_elapsed < self .hab_time_box .value () and self .behavior_baseline_period .is_set ():
6335
+ QApplication .processEvents ()
6336
+ # update baseline time elapsed before session for start/stop logic
6337
+ self .baseline_min_elapsed = ((time .time () - start_time ) / 60 ) + elapsed
6338
+
6339
+ update_hab_timer .stop ()
6340
+ self .behavior_baseline_period .clear ()
6341
+
6342
+
6325
6343
def _StartTrialLoop (self , GeneratedTrials , worker1 , worker_save ):
6326
- if self .Start .isChecked ():
6327
- logging .info ("starting trial loop" )
6328
- else :
6344
+
6345
+ if not self .Start .isChecked ():
6329
6346
logging .info ("ending trial loop" )
6347
+ return
6348
+
6349
+ logging .info ("starting trial loop" )
6330
6350
6331
6351
# Track elapsed time in case Bonsai Stalls
6332
6352
last_trial_start = time .time ()
6333
6353
stall_iteration = 1
6334
6354
stall_duration = 5 * 60
6335
6355
6356
+ logging .info (f"Starting session." )
6357
+
6336
6358
while self .Start .isChecked ():
6337
6359
QApplication .processEvents ()
6338
6360
if (
0 commit comments