diff --git a/src/foraging_gui/Calibration.ui b/src/foraging_gui/Calibration.ui
index 0566557a1..ce8a0a7ed 100644
--- a/src/foraging_gui/Calibration.ui
+++ b/src/foraging_gui/Calibration.ui
@@ -126,9 +126,9 @@
10
- 10
+ 0
330
- 60
+ 80
@@ -138,7 +138,7 @@
40
- 20
+ 15
100
25
@@ -154,7 +154,7 @@
180
- 20
+ 15
100
25
@@ -166,6 +166,38 @@
true
+
+
+
+ 40
+ 50
+ 100
+ 25
+
+
+
+ Open left 5ml
+
+
+ true
+
+
+
+
+
+ 180
+ 50
+ 100
+ 25
+
+
+
+ Open right 5ml
+
+
+ true
+
+
diff --git a/src/foraging_gui/Dialogs.py b/src/foraging_gui/Dialogs.py
index 78f17a1b7..2e6a28dda 100644
--- a/src/foraging_gui/Dialogs.py
+++ b/src/foraging_gui/Dialogs.py
@@ -12,7 +12,7 @@
import numpy as np
import pandas as pd
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
-from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QHBoxLayout, QMessageBox
+from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout, QHBoxLayout, QMessageBox
from PyQt5.QtWidgets import QLabel, QDialogButtonBox,QFileDialog,QInputDialog, QLineEdit
from PyQt5 import QtWidgets, uic, QtGui
from PyQt5.QtCore import QThreadPool,Qt, QAbstractTableModel, QItemSelectionModel, QObject, QTimer
@@ -332,16 +332,39 @@ def __init__(self, MainWindow,parent=None):
child.setDefault(False)
child.setAutoDefault(False)
- # setup flags to keep lines open
- self.left_valve_open_timer = QTimer(timeout=lambda: self.reopen_valve('Left'), interval=10000)
- self.right_valve_open_timer = QTimer(timeout=lambda: self.reopen_valve('Right'), interval=10000)
+ # setup QTimers to keep lines open
+ self.left_open_timer = QTimer(timeout=lambda: self.reopen_valve('Left'), interval=10000)
+ self.right_open_timer = QTimer(timeout=lambda: self.reopen_valve('Right'), interval=10000)
+ # setup QTimers to keep close lines after 5ml
+ self.left_close_timer = QTimer(timeout=lambda: self.OpenLeft5ml.setChecked(False)) # trigger _ToggleValve call
+ self.right_close_timer = QTimer(timeout=lambda: self.OpenRight5ml.setChecked(False))
+
+ # setup Qtimers for updating text countdown
+ self.left_text_timer = QTimer(timeout=lambda:
+ self.OpenLeft5ml.setText(f'Open left 5ml: {round(self.left_close_timer.remainingTime()/1000)}s'),
+ interval=1000)
+ self.right_text_timer = QTimer(timeout=lambda:
+ self.OpenRight5ml.setText(f'Open right 5ml: {round(self.right_close_timer.remainingTime()/1000)}s'),
+ interval=1000)
def _connectSignalsSlots(self):
self.SpotCheckLeft.clicked.connect(self._SpotCheckLeft)
self.SpotCheckRight.clicked.connect(self._SpotCheckRight)
- self.OpenLeftForever.clicked.connect(self._OpenLeftForever)
- self.OpenRightForever.clicked.connect(self._OpenRightForever)
+
+ # Set up OpenLeftForever button
+ self.OpenLeftForever.clicked.connect(lambda: self._ToggleValve(self.OpenLeftForever, 'Left'))
+ self.OpenLeftForever.clicked.connect(lambda: self.OpenLeft5ml.setDisabled(self.OpenLeftForever.isChecked()))
+ # Set up OpenRightForever button
+ self.OpenRightForever.clicked.connect(lambda: self._ToggleValve(self.OpenRightForever, 'Right'))
+ self.OpenRightForever.clicked.connect(lambda: self.OpenRight5ml.setDisabled(self.OpenRightForever.isChecked()))
+ # Set up OpenLeft5ml button
+ self.OpenLeft5ml.toggled.connect(lambda val: self._ToggleValve(self.OpenLeft5ml, 'Left'))
+ self.OpenLeft5ml.toggled.connect(lambda val: self.OpenLeftForever.setDisabled(val))
+ # Set up OpenRight5ml button
+ self.OpenRight5ml.toggled.connect(lambda val: self._ToggleValve(self.OpenRight5ml, 'Right'))
+ self.OpenRight5ml.toggled.connect(lambda val: self.OpenRightForever.setDisabled(val))
+
self.SaveLeft.clicked.connect(self._SaveLeft)
self.SaveRight.clicked.connect(self._SaveRight)
self.StartCalibratingLeft.clicked.connect(self._StartCalibratingLeft)
@@ -877,53 +900,52 @@ def _UpdateFigure(self):
self.ToInitializeVisual=0
self.PlotM._Update()
- def _OpenLeftForever(self):
- '''Open the left valve forever'''
- self.MainWindow._ConnectBonsai()
- if self.MainWindow.InitializeBonsaiSuccessfully==0:
- return
- if self.OpenLeftForever.isChecked():
- # change button color
- self.OpenLeftForever.setStyleSheet("background-color : green;")
- self.MainWindow.Channel.LeftValue(float(1000) * 1000) # set the valve open time
- self.MainWindow.Channel3.ManualWater_Left(int(1)) # set valve initially open
- self.left_valve_open_timer.start()
- else:
- # change button color
- self.OpenLeftForever.setStyleSheet("background-color : none")
- self.left_valve_open_timer.stop()
- # close the valve
- self.MainWindow.Channel.LeftValue(float(0.001)*1000)
- time.sleep(0.01)
- self.MainWindow.Channel3.ManualWater_Left(int(1))
- # set the default valve open time
- time.sleep(0.01)
- self.MainWindow.Channel.LeftValue(float(self.MainWindow.LeftValue.text())*1000)
+ def _ToggleValve(self, button, valve: Literal['Left', 'Right']):
+ """
+ Toggle open/close state of specified valve and set up logic based on button pressed
+ :param button: button that was pressed
+ :param valve: which valve to open. Restricted to Right or Left
+ """
- def _OpenRightForever(self):
- '''Open the right valve forever'''
self.MainWindow._ConnectBonsai()
if self.MainWindow.InitializeBonsaiSuccessfully==0:
return
- if self.OpenRightForever.isChecked():
- # change button color
- self.OpenRightForever.setStyleSheet("background-color : green;")
- self.MainWindow.Channel.RightValue(float(1000) * 1000) # set the valve open time
- self.MainWindow.Channel3.ManualWater_Right(int(1)) # set valve initially open
- self.right_valve_open_timer.start()
- else:
+ set_valve_time = getattr(self.MainWindow.Channel, f'{valve}Value')
+ toggle_valve_state = getattr(self.MainWindow.Channel3, f'ManualWater_{valve}')
+ open_timer = getattr(self, f'{valve.lower()}_open_timer')
+ close_timer = getattr(self, f'{valve.lower()}_close_timer')
+ text_timer = getattr(self, f'{valve.lower()}_text_timer')
+
+ if button.isChecked(): # open valve
+ button.setStyleSheet("background-color : green;")
+ set_valve_time(float(1000) * 1000) # set the valve open time to max value
+ toggle_valve_state(int(1)) # set valve initially open
+
+ if button.text() == f'Open {valve.lower()} 5ml': # set up additional logic to only open for 5ml
+ five_ml_time_ms = round(self._VolumeToTime(5000, valve) * 1000) # calculate time for valve to stay open
+ close_timer.setInterval(five_ml_time_ms) # set interval of valve close time to be five_ml_time_ms
+ close_timer.setSingleShot(True) # only trigger once when 5ml has been expelled
+ text_timer.start() # start timer to update text
+ close_timer.start()
+
+ open_timer.start()
+
+ else: # close open valve
# change button color
- self.OpenRightForever.setStyleSheet("background-color : none")
- self.right_valve_open_timer.stop()
+ button.setStyleSheet("background-color : none")
+ open_timer.stop()
+ if f'Open {valve.lower()} 5ml' in button.text():
+ close_timer.stop()
+ text_timer.stop()
+ button.setText(f'Open {valve.lower()} 5ml')
# close the valve
- self.MainWindow.Channel.RightValue(float(0.001)*1000)
- time.sleep(0.01)
- self.MainWindow.Channel3.ManualWater_Right(int(1))
- # set the default valve open time
+ toggle_valve_state(int(1))
+
+ # reset the default valve open time
time.sleep(0.01)
- self.MainWindow.Channel.RightValue(float(self.MainWindow.RightValue.text())*1000)
+ set_valve_time(float(getattr(self.MainWindow, f'{valve}Value').text())*1000)
def reopen_valve(self, valve: Literal['Left', 'Right']):
"""Function to reopen the right or left water line open. Valve must be open prior to calling this function.
@@ -940,9 +962,14 @@ def _TimeRemaining(self,i, cycles, opentime, interval):
seconds = int(np.ceil(np.mod(total_seconds,60)))
return '{}:{:02}'.format(minutes, seconds)
- def _VolumeToTime(self,volume,valve):
- # x = (y-b)/m
- if hasattr(self.MainWindow, 'latest_fitting'):
+ def _VolumeToTime(self, volume, valve: Literal['Left', 'Right'] ):
+ """
+ Function to return the amount of time(s) it takes for water line to flush specified volume of water (mg)
+ :param volume: volume to flush in mg
+ :param valve: string specifying right or left valve
+ """
+ # x = (y-b)/m
+ if hasattr(self.MainWindow, 'latest_fitting') and self.MainWindow.latest_fitting != {}:
fit = self.MainWindow.latest_fitting[valve]
m = fit[0]
b = fit[1]
diff --git a/src/foraging_gui/Foraging.py b/src/foraging_gui/Foraging.py
index 4c22af404..a6971839a 100644
--- a/src/foraging_gui/Foraging.py
+++ b/src/foraging_gui/Foraging.py
@@ -1099,6 +1099,7 @@ def _GetSettings(self):
'manifest'),
'auto_engage':True,
'clear_figure_after_save':True,
+ 'add_default_project_name':True
}
# Try to load Settings_box#.csv
@@ -1186,6 +1187,7 @@ def _GetSettings(self):
self.save_each_trial = self.Settings['save_each_trial']
self.auto_engage = self.Settings['auto_engage']
self.clear_figure_after_save = self.Settings['clear_figure_after_save']
+ self.add_default_project_name = self.Settings['add_default_project_name']
if not is_absolute_path(self.project_info_file):
self.project_info_file = os.path.join(self.SettingFolder,self.project_info_file)
# Also stream log info to the console if enabled
@@ -3701,6 +3703,28 @@ def _set_metadata_enabled(self, enable: bool):
self.ID.setEnabled(enable)
self.Experimenter.setEnabled(enable)
+ def _set_default_project(self):
+ '''Set default project information'''
+ project_name = 'Behavior Platform'
+ logging.info('Setting Project name: {}'.format('Behavior Platform'))
+ projects = [self.Metadata_dialog.ProjectName.itemText(i)
+ for i in range(self.Metadata_dialog.ProjectName.count())]
+ index = np.where(np.array(projects) == 'Behavior Platform')[0]
+ if len(index) > 0:
+ index = index[0]
+ self.Metadata_dialog.ProjectName.setCurrentIndex(index)
+ self.Metadata_dialog._show_project_info()
+ else:
+ project_info = {
+ 'Funding Institution': ['Allen Institute'],
+ 'Grant Number': ['nan'],
+ 'Investigators': ['Jeremiah Cohen'],
+ 'Fundee': ['nan'],
+ }
+ self.Metadata_dialog.project_info = project_info
+ self.Metadata_dialog.ProjectName.addItems([project_name])
+ return project_name
+
def _Start(self):
'''start trial loop'''
@@ -3906,8 +3930,8 @@ def _Start(self):
logging.info('Setting IACUC Protocol: {}'.format(protocol))
# Set Project Name in metadata based on schedule
+ add_default=True
project_name = self._GetInfoFromSchedule(mouse_id, 'Project Name')
- add_default = True
if project_name is not None:
projects = [self.Metadata_dialog.ProjectName.itemText(i)
for i in range(self.Metadata_dialog.ProjectName.count())]
@@ -3918,27 +3942,11 @@ def _Start(self):
self.Metadata_dialog._show_project_info()
logging.info('Setting Project name: {}'.format(project_name))
add_default = False
- if add_default:
- project_name = 'Behavior Platform'
- logging.info('Setting Project name: {}'.format('Behavior Platform'))
- projects = [self.Metadata_dialog.ProjectName.itemText(i)
- for i in range(self.Metadata_dialog.ProjectName.count())]
- index = np.where(np.array(projects) == 'Behavior Platform')[0]
- if len(index) > 0:
- index = index[0]
- self.Metadata_dialog.ProjectName.setCurrentIndex(index)
- self.Metadata_dialog._show_project_info()
- else:
- project_info = {
- 'Funding Institution': ['Allen Institute'],
- 'Grant Number': ['nan'],
- 'Investigators': ['Jeremiah Cohen'],
- 'Fundee': ['nan'],
- }
- self.Metadata_dialog.project_info = project_info
- self.Metadata_dialog.ProjectName.addItems([project_name])
- self.project_name = project_name
+ if self.add_default_project_name and add_default:
+ project_name=self._set_default_project()
+
+ self.project_name = project_name
self.session_run = True # session has been started
self.keyPressEvent(allow_reset=True)
diff --git a/src/foraging_gui/__init__.py b/src/foraging_gui/__init__.py
index d49d067f7..94cc064b6 100644
--- a/src/foraging_gui/__init__.py
+++ b/src/foraging_gui/__init__.py
@@ -1,2 +1,2 @@
-__version__ = "1.6.2"
+__version__ = "1.6.4"
diff --git a/src/setting_templates/ForagingSettings.json b/src/setting_templates/ForagingSettings.json
index 732d93c5c..1f0854022 100644
--- a/src/setting_templates/ForagingSettings.json
+++ b/src/setting_templates/ForagingSettings.json
@@ -18,5 +18,6 @@
"go_cue_decibel_box1":73,
"go_cue_decibel_box2":73,
"go_cue_decibel_box3":73,
- "go_cue_decibel_box4":73
+ "go_cue_decibel_box4":73,
+ "add_default_project_name":true
}