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 }