Skip to content

Commit 47780dc

Browse files
Merge pull request #1623 from AllenNeuralDynamics/production_testing
[update main] 2025-09-30
2 parents 3ffdd64 + fb66d35 commit 47780dc

3 files changed

Lines changed: 103 additions & 94 deletions

File tree

src/foraging_gui/Calibration.ui

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -451,38 +451,6 @@
451451
<string/>
452452
</property>
453453
</widget>
454-
<widget class="QPushButton" name="SaveLeft">
455-
<property name="geometry">
456-
<rect>
457-
<x>580</x>
458-
<y>20</y>
459-
<width>80</width>
460-
<height>25</height>
461-
</rect>
462-
</property>
463-
<property name="text">
464-
<string>Save</string>
465-
</property>
466-
<property name="checkable">
467-
<bool>true</bool>
468-
</property>
469-
</widget>
470-
<widget class="QPushButton" name="SaveRight">
471-
<property name="geometry">
472-
<rect>
473-
<x>580</x>
474-
<y>55</y>
475-
<width>80</width>
476-
<height>25</height>
477-
</rect>
478-
</property>
479-
<property name="text">
480-
<string>Save</string>
481-
</property>
482-
<property name="checkable">
483-
<bool>true</bool>
484-
</property>
485-
</widget>
486454
</widget>
487455
<widget class="QCheckBox" name="multi_value_enable">
488456
<property name="geometry">
@@ -866,8 +834,6 @@
866834
<zorder>WeightAfterRight</zorder>
867835
<zorder>TotalWaterSingleLeft</zorder>
868836
<zorder>TotalWaterSingleRight</zorder>
869-
<zorder>SaveLeft</zorder>
870-
<zorder>SaveRight</zorder>
871837
<zorder>label_21</zorder>
872838
<zorder>CalibrationType</zorder>
873839
<zorder>IntervalRight_2</zorder>

src/foraging_gui/Dialogs.py

Lines changed: 85 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -572,8 +572,6 @@ def _connectSignalsSlots(self):
572572
lambda val: self.OpenRightForever.setDisabled(val)
573573
)
574574

575-
self.SaveLeft.clicked.connect(lambda: self._SaveValve("Left"))
576-
self.SaveRight.clicked.connect(lambda: self._SaveValve("Right"))
577575
self.StartCalibratingLeft.clicked.connect(self._StartCalibratingLeft)
578576
self.StartCalibratingRight.clicked.connect(self._StartCalibratingRight)
579577
self.Continue.clicked.connect(self._Continue)
@@ -670,8 +668,6 @@ def _SaveValve(self, valve: Literal["Left", "Right"]):
670668
save the calibration result of the single point calibration (left valve)
671669
:param valve: string specifying valve side
672670
"""
673-
save = getattr(self, f"Save{valve}")
674-
save.setStyleSheet("background-color : green;")
675671
QApplication.processEvents()
676672

677673
valve_open_time = str(getattr(self, f"Spot{valve}OpenTime"))
@@ -687,8 +683,6 @@ def _SaveValve(self, valve: Literal["Left", "Right"]):
687683
tube_weight=float(before_txt),
688684
append=True,
689685
)
690-
save.setStyleSheet("background-color : none")
691-
save.setChecked(False)
692686

693687
def _LoadCalibrationParameters(self):
694688
self.WaterCalibrationPar = {}
@@ -896,34 +890,42 @@ def _CalibrateLeftOne(self, repeat=False):
896890
return
897891
self.WeightAfterLeft.setText(str(final_tube_weight))
898892

899-
# Mark measurement as complete, save data, and update figure
900-
self.left_measurements[next_index] = True
901-
self._Save(
902-
valve="Left",
903-
valve_open_time=str(current_valve_opentime),
904-
valve_open_interval=str(self.params["Interval"]),
905-
cycle=str(self.params["Cycle"]),
906-
total_water=float(final_tube_weight),
907-
tube_weight=float(before_weight),
908-
)
909-
self._UpdateFigure()
910-
911-
# Direct user for next steps
912-
if np.all(self.left_measurements):
913-
self.Warning.setText(
914-
"Measurements recorded for all values. Please press Repeat, or Finished"
915-
)
916-
self.Repeat.setStyleSheet("color: black;background-color : none;")
917-
self.Finished.setStyleSheet(
918-
"color: white;background-color : mediumorchid;"
893+
if self.check_calibration_curve(float(final_tube_weight), float(before_weight)):
894+
# Mark measurement as complete, save data, and update figure
895+
self.left_measurements[next_index] = True
896+
self._Save(
897+
valve="Left",
898+
valve_open_time=str(current_valve_opentime),
899+
valve_open_interval=str(self.params["Interval"]),
900+
cycle=str(self.params["Cycle"]),
901+
total_water=float(final_tube_weight),
902+
tube_weight=float(before_weight),
919903
)
904+
self._UpdateFigure()
905+
906+
# Direct user for next steps
907+
if np.all(self.left_measurements):
908+
self.Warning.setText(
909+
"Measurements recorded for all values. Please press Repeat, or Finished"
910+
)
911+
self.Repeat.setStyleSheet("color: black;background-color : none;")
912+
self.Finished.setStyleSheet(
913+
"color: white;background-color : mediumorchid;"
914+
)
915+
else:
916+
self.Warning.setText("Please press Continue, Repeat, or Finished")
917+
self.Continue.setStyleSheet(
918+
"color: white;background-color : mediumorchid;"
919+
)
920+
self.Repeat.setStyleSheet("color: black;background-color : none;")
920921
else:
921-
self.Warning.setText("Please press Continue, Repeat, or Finished")
922-
self.Continue.setStyleSheet(
923-
"color: white;background-color : mediumorchid;"
922+
self.Warning.setText(
923+
"Recorded before and after tube weights do not pass checks. Please repeat check."
924+
"Value has not been saved."
924925
)
925926
self.Repeat.setStyleSheet("color: black;background-color : none;")
926927

928+
927929
def _StartCalibratingRight(self):
928930
"""start the calibration loop of right valve"""
929931

@@ -1083,31 +1085,59 @@ def _CalibrateRightOne(self, repeat=False):
10831085
return
10841086
self.WeightAfterRight.setText(str(final_tube_weight))
10851087

1086-
# Mark measurement as complete, save data, and update figure
1087-
self.right_measurements[next_index] = True
1088-
self._Save(
1089-
valve="Right",
1090-
valve_open_time=str(current_valve_opentime),
1091-
valve_open_interval=str(self.params["Interval"]),
1092-
cycle=str(self.params["Cycle"]),
1093-
total_water=float(final_tube_weight),
1094-
tube_weight=float(before_weight),
1095-
)
1096-
self._UpdateFigure()
1097-
1098-
# Direct user for next steps
1099-
if np.all(self.right_measurements):
1100-
self.Warning.setText(
1101-
"Measurements recorded for all values. Please press Repeat, or Finished"
1088+
if self.check_calibration_curve(float(final_tube_weight), float(before_weight)):
1089+
# Mark measurement as complete, save data, and update figure
1090+
self.right_measurements[next_index] = True
1091+
self._Save(
1092+
valve="Right",
1093+
valve_open_time=str(current_valve_opentime),
1094+
valve_open_interval=str(self.params["Interval"]),
1095+
cycle=str(self.params["Cycle"]),
1096+
total_water=float(final_tube_weight),
1097+
tube_weight=float(before_weight),
11021098
)
1103-
self.Repeat.setStyleSheet("color: black;background-color : none;")
1099+
self._UpdateFigure()
1100+
1101+
# Direct user for next steps
1102+
if np.all(self.right_measurements):
1103+
self.Warning.setText(
1104+
"Measurements recorded for all values. Please press Repeat, or Finished"
1105+
)
1106+
self.Repeat.setStyleSheet("color: black;background-color : none;")
1107+
else:
1108+
self.Warning.setText("Please press Continue, Repeat, or Finished")
1109+
self.Continue.setStyleSheet(
1110+
"color: white;background-color : mediumorchid;"
1111+
)
1112+
self.Repeat.setStyleSheet("color: black;background-color : none;")
11041113
else:
1105-
self.Warning.setText("Please press Continue, Repeat, or Finished")
1106-
self.Continue.setStyleSheet(
1107-
"color: white;background-color : mediumorchid;"
1114+
self.Warning.setText(
1115+
"Recorded before and after tube weights do not pass checks. Please repeat check."
1116+
"Value has not been saved."
11081117
)
11091118
self.Repeat.setStyleSheet("color: black;background-color : none;")
11101119

1120+
def check_calibration_curve(self,
1121+
after_tube_weight: float,
1122+
init_tube_weight: float) -> bool:
1123+
1124+
"""
1125+
Check that values are positive and will not result in negative curve
1126+
1127+
:param after_tube_weight: tube weight after calibration
1128+
:param init_tube_weight: tube weight before calibration
1129+
1130+
:returns boolean indicating whether values passed check
1131+
"""
1132+
1133+
if not after_tube_weight >= 0 or not init_tube_weight >= 0:
1134+
return False
1135+
1136+
if after_tube_weight <= init_tube_weight:
1137+
return False
1138+
1139+
return True
1140+
11111141
def _CalibrationStatus(self, opentime, weight_before, i, cycle, interval):
11121142
self.Warning.setText(
11131143
"Measuring left valve: {}s".format(opentime)
@@ -1174,6 +1204,7 @@ def _Save(
11741204
WaterCalibrationResults[date_str][valve][valve_open_time][
11751205
valve_open_interval
11761206
][cycle] = [np.round(total_water, 1)]
1207+
11771208
self.WaterCalibrationResults = WaterCalibrationResults.copy()
11781209

11791210
# save to the json file
@@ -1187,6 +1218,11 @@ def _Save(
11871218
# update the figure
11881219
self._UpdateFigure()
11891220

1221+
# update calibration parameters ui uses
1222+
self.MainWindow._GetWaterCalibration()
1223+
FittingResults = self.PlotM.FittingResults
1224+
self.MainWindow._GetLatestFitting(FittingResults)
1225+
11901226
def _UpdateFigure(self):
11911227
"""plot the calibration result"""
11921228
if self.ToInitializeVisual == 1: # only run once
@@ -1322,7 +1358,6 @@ def _SpotCheck(self, valve: Literal["Left", "Right"]):
13221358
"""
13231359

13241360
spot_check = getattr(self, f"SpotCheck{valve}")
1325-
save = getattr(self, f"Save{valve}")
13261361
total_water = getattr(self, f"TotalWaterSingle{valve}")
13271362
pre_weight = getattr(self, f"SpotCheckPreWeight{valve}")
13281363
volume = getattr(self, f"Spot{valve}Volume").text()
@@ -1331,7 +1366,6 @@ def _SpotCheck(self, valve: Literal["Left", "Right"]):
13311366
if self.MainWindow.InitializeBonsaiSuccessfully == 0:
13321367
spot_check.setChecked(False)
13331368
spot_check.setStyleSheet("background-color : none;")
1334-
save.setStyleSheet("color: black;background-color : none;")
13351369
total_water.setText("")
13361370
pre_weight.setText("")
13371371
return
@@ -1352,7 +1386,6 @@ def _SpotCheck(self, valve: Literal["Left", "Right"]):
13521386
self.Warning.setText("")
13531387
pre_weight.setText("")
13541388
total_water.setText("")
1355-
save.setStyleSheet("color: black;background-color : none;")
13561389
return
13571390

13581391
logging.info(f"starting spot check {valve.lower()}")
@@ -1380,7 +1413,6 @@ def _SpotCheck(self, valve: Literal["Left", "Right"]):
13801413
self.Warning.setText(f"Spot check {valve.lower()} cancelled")
13811414
pre_weight.setText("")
13821415
total_water.setText("")
1383-
save.setStyleSheet("color: black;background-color : none;")
13841416
return
13851417
pre_weight.setText(str(empty_tube_weight))
13861418

@@ -1490,9 +1522,6 @@ def _SpotCheck(self, valve: Literal["Left", "Right"]):
14901522
valve, error
14911523
)
14921524
)
1493-
save.setStyleSheet(
1494-
"color: white;background-color : mediumorchid;"
1495-
)
14961525
self.Warning.setText(
14971526
f"Measuring {valve.lower()} valve: {volume}uL"
14981527
+ "\nEmpty tube weight: {}g".format(empty_tube_weight)

src/foraging_gui/Foraging.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,12 +1569,17 @@ def _LoadSchedule(self):
15691569

15701570
# Find the correct week on the schedule
15711571
dividers = schedule[[isinstance(x,str)and('/' in x) for x in schedule['Mouse ID'].values]]
1572-
today = datetime.now().strftime('%m/%d/%Y')
1572+
today = datetime.now()
15731573

15741574
# Multiple weeks on the schedule
15751575
if len(dividers) > 1:
1576-
first = dividers.iloc[0]['Mouse ID']
1577-
if datetime.strptime(today, "%m/%d/%Y") < datetime.strptime(first,"%m/%d/%Y"):
1576+
first = datetime.strptime(dividers.iloc[0]['Mouse ID'], "%m/%d/%Y")
1577+
1578+
# switch schedule at Friday 5pm
1579+
cutoff = first - timedelta(days=3) # Go back to Friday
1580+
cutoff = cutoff.replace(hour=17, minute=0, second=0, microsecond=0) # 5 PM
1581+
1582+
if today < cutoff:
15781583
# Use last weeks schedule
15791584
schedule = schedule.loc[dividers.index.values[1]:]
15801585
else:
@@ -4111,8 +4116,17 @@ def _Save(self, ForceSave=0, SaveAs=0, SaveContinue=0, BackupSave=0):
41114116
with open(self.SaveFile, "w") as outfile:
41124117
json.dump(Obj2, outfile, indent=4, cls=NumpyEncoder)
41134118
elif self.SaveFile.endswith(".json"):
4114-
with open(self.SaveFile, "w") as outfile:
4119+
# Crashses during save can corupt a json file.
4120+
# Make tmp file to save to
4121+
tmp_file_name = self.SaveFile.split('.json')[0]
4122+
tmp_file_name = tmp_file_name + '_tmp.json'
4123+
with open(tmp_file_name, "w") as outfile:
41154124
json.dump(Obj, outfile, indent=4, cls=NumpyEncoder)
4125+
# After file is safely saved, remove the old save file
4126+
# and rewrite the new one.
4127+
if os.path.isfile(self.SaveFile):
4128+
os.remove(self.SaveFile)
4129+
os.rename(tmp_file_name,self.SaveFile)
41164130

41174131
# Toggle unsaved data to False
41184132
if BackupSave == 0:

0 commit comments

Comments
 (0)