Skip to content

Commit d91b941

Browse files
authored
Merge pull request #1515 from AllenNeuralDynamics/production_testing
[update main] 2025-04-29
2 parents a9a3970 + 96ada96 commit d91b941

File tree

3 files changed

+182
-66
lines changed

3 files changed

+182
-66
lines changed

src/foraging_gui/Foraging.py

Lines changed: 121 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (

src/foraging_gui/ForagingGUI.ui

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,28 @@
262262
</property>
263263
</widget>
264264
</item>
265+
<item row="2" column="3">
266+
<widget class="QSpinBox" name="hab_time_box">
267+
<property name="sizePolicy">
268+
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
269+
<horstretch>0</horstretch>
270+
<verstretch>0</verstretch>
271+
</sizepolicy>
272+
</property>
273+
<property name="minimum">
274+
<number>0</number>
275+
</property>
276+
<property name="maximum">
277+
<number>90</number>
278+
</property>
279+
<property name="suffix">
280+
<string> min</string>
281+
</property>
282+
<property name="prefix">
283+
<string> Baseline: </string>
284+
</property>
285+
</widget>
286+
</item>
265287
<item row="2" column="1">
266288
<widget class="QPushButton" name="Save">
267289
<property name="sizePolicy">

src/foraging_gui/ForagingGUI_Ephys.ui

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
<widget class="QComboBox" name="Task">
7070
<property name="geometry">
7171
<rect>
72-
<x>620</x>
72+
<x>650</x>
7373
<y>20</y>
7474
<width>152</width>
7575
<height>20</height>
@@ -2763,6 +2763,40 @@
27632763
<bool>false</bool>
27642764
</property>
27652765
</widget>
2766+
<widget class="QSpinBox" name="hab_time_box">
2767+
<property name="enabled">
2768+
<bool>true</bool>
2769+
</property>
2770+
<property name="geometry">
2771+
<rect>
2772+
<x>240</x>
2773+
<y>20</y>
2774+
<width>110</width>
2775+
<height>23</height>
2776+
</rect>
2777+
</property>
2778+
<property name="sizePolicy">
2779+
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
2780+
<horstretch>0</horstretch>
2781+
<verstretch>0</verstretch>
2782+
</sizepolicy>
2783+
</property>
2784+
<property name="styleSheet">
2785+
<string notr="true"/>
2786+
</property>
2787+
<property name="minimum">
2788+
<number>0</number>
2789+
</property>
2790+
<property name="maximum">
2791+
<number>90</number>
2792+
</property>
2793+
<property name="suffix">
2794+
<string> min</string>
2795+
</property>
2796+
<property name="prefix">
2797+
<string> Baseline: </string>
2798+
</property>
2799+
</widget>
27662800
<widget class="QPushButton" name="NewSession">
27672801
<property name="enabled">
27682802
<bool>true</bool>
@@ -3263,7 +3297,7 @@
32633297
<widget class="QLineEdit" name="ID">
32643298
<property name="geometry">
32653299
<rect>
3266-
<x>324</x>
3300+
<x>380</x>
32673301
<y>20</y>
32683302
<width>71</width>
32693303
<height>20</height>
@@ -3282,7 +3316,7 @@
32823316
<widget class="QLabel" name="label_72">
32833317
<property name="geometry">
32843318
<rect>
3285-
<x>300</x>
3319+
<x>350</x>
32863320
<y>20</y>
32873321
<width>21</width>
32883322
<height>20</height>
@@ -3658,7 +3692,7 @@
36583692
<widget class="QLabel" name="label_74">
36593693
<property name="geometry">
36603694
<rect>
3661-
<x>400</x>
3695+
<x>450</x>
36623696
<y>20</y>
36633697
<width>71</width>
36643698
<height>20</height>
@@ -3683,7 +3717,7 @@
36833717
<widget class="QLineEdit" name="Experimenter">
36843718
<property name="geometry">
36853719
<rect>
3686-
<x>474</x>
3720+
<x>524</x>
36873721
<y>20</y>
36883722
<width>121</width>
36893723
<height>20</height>

0 commit comments

Comments
 (0)