Skip to content

Commit 7696423

Browse files
authored
Merge pull request #1539 from AllenNeuralDynamics/production_testing
[update main] 2025-05-13
2 parents acbae82 + 599d6b5 commit 7696423

File tree

3 files changed

+146
-68
lines changed

3 files changed

+146
-68
lines changed

src/foraging_gui/Foraging.py

Lines changed: 84 additions & 62 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(
@@ -1552,12 +1556,30 @@ def _check_line_terminator(self, file_path):
15521556
def _LoadSchedule(self):
15531557
if os.path.exists(self.Settings["schedule_path"]):
15541558
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
15551575
self.schedule_mice = [
15561576
x
15571577
for x in schedule["Mouse ID"].unique()
15581578
if isinstance(x, str) and (len(x) > 3) and ("/" not in x)
15591579
]
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()
15611583
logging.info("Loaded behavior schedule")
15621584
else:
15631585
self.schedule_mice = None
@@ -2761,6 +2783,7 @@ def keyPressEvent(self, event=None, allow_reset=False):
27612783
"laser_2_calibration_power",
27622784
"laser_1_calibration_voltage",
27632785
"laser_2_calibration_voltage",
2786+
"hab_time_box"
27642787
}:
27652788
continue
27662789
if child.objectName() == "UncoupledReward":
@@ -4006,11 +4029,11 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0):
40064029
):
40074030
self._AddWaterLogResult(session)
40084031
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.")
40114034
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.")
40144037
except Exception as e:
40154038
logging.warning(
40164039
"Meta data is not saved!",
@@ -5457,6 +5480,7 @@ def _NewSession(self):
54575480

54585481
self.unsaved_data = False
54595482
self.ManualWaterVolume = [0, 0]
5483+
self.baseline_min_elapsed = 0 # variable to track baseline time elapsed before session for start/stop
54605484

54615485
# Clear Plots
54625486
if hasattr(self, "PlotM") and self.clear_figure_after_save:
@@ -5893,57 +5917,6 @@ def _Start(self):
58935917
elif self.behavior_session_model.allow_dirty_repo is None:
58945918
logging.error("Could not check for untracked local changes")
58955919

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-
59475920
# disable sound button
59485921
self.sound_button.setEnabled(False)
59495922

@@ -5960,6 +5933,9 @@ def _Start(self):
59605933
self.session_run = True # session has been started
59615934
self.keyPressEvent(allow_reset=True)
59625935

5936+
# set flag to perform habituation period
5937+
self.behavior_baseline_period.set()
5938+
59635939
else:
59645940
# Prompt user to confirm stopping trials
59655941
reply = QMessageBox.question(
@@ -5991,6 +5967,7 @@ def _Start(self):
59915967

59925968
self.session_end_tasks()
59935969
self.sound_button.setEnabled(True)
5970+
self.behavior_baseline_period.clear() # set flag to break out of habituation period
59945971

59955972
if (self.StartANewSession == 1) and (self.ANewTrial == 0):
59965973
# If we are starting a new session, we should wait for the last trial to finish
@@ -6137,12 +6114,21 @@ def _Start(self):
61376114
workerStartTrialLoop1 = self.workerStartTrialLoop1
61386115
worker_save = self.worker_save
61396116

6117+
# pause for specified habituation time
6118+
if self.baseline_min_elapsed <= self.hab_time_box.value():
6119+
self.wait_for_baseline()
6120+
61406121
# collecting the base signal for photometry. Only run once
61416122
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
61456126
):
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+
61466132
logging.info("Starting photometry baseline timer")
61476133
self.finish_Timer = 0
61486134
self.PhotometryRun = 1
@@ -6322,17 +6308,53 @@ def end_session_log(self) -> None:
63226308
else:
63236309
logging.info("No active session logger")
63246310

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+
63256343
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():
63296346
logging.info("ending trial loop")
6347+
return
6348+
6349+
logging.info("starting trial loop")
63306350

63316351
# Track elapsed time in case Bonsai Stalls
63326352
last_trial_start = time.time()
63336353
stall_iteration = 1
63346354
stall_duration = 5 * 60
63356355

6356+
logging.info(f"Starting session.")
6357+
63366358
while self.Start.isChecked():
63376359
QApplication.processEvents()
63386360
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> Habituation: </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: 40 additions & 6 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> Habituation: </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>
@@ -5016,4 +5050,4 @@
50165050
</tabstops>
50175051
<resources/>
50185052
<connections/>
5019-
</ui>
5053+
</ui>

0 commit comments

Comments
 (0)