From d33f05532281647e5c303c36d718c521f61ddb21 Mon Sep 17 00:00:00 2001 From: owinter Date: Wed, 21 Feb 2024 18:42:23 +0000 Subject: [PATCH 1/7] initial commit for zapit task --- iblrig_custom_tasks/_sp_passiveVideo/task.py | 12 ++++++++---- .../nate_optoBiasedChoiceWorld/task.py | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/iblrig_custom_tasks/_sp_passiveVideo/task.py b/iblrig_custom_tasks/_sp_passiveVideo/task.py index 98c1ead..00119f2 100644 --- a/iblrig_custom_tasks/_sp_passiveVideo/task.py +++ b/iblrig_custom_tasks/_sp_passiveVideo/task.py @@ -8,16 +8,20 @@ from collections import defaultdict import logging -import vlc import pandas as pd from pybpodapi.protocol import Bpod import iblrig.misc -from iblrig.base_tasks import BaseSession, BpodMixin - +from iblrig.base_tasks import BpodMixin _logger = logging.getLogger(__name__) +# this allows the CI and automated tests to import the file and make sure it is valid without having vlc +try: + import vlc +except ModuleNotFoundError: + _logger.error(f'VLC not installed. Please install VLC to use this task. {__file__}') + class Player: """A VLC player.""" @@ -95,7 +99,7 @@ def get_ended_time(self, repeat=-1): return ends[repeat] -class Session(BaseSession, BpodMixin): +class Session(BpodMixin): """Play a single video.""" protocol_name = '_sp_passiveVideo' diff --git a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py index 71ea719..d12f5f5 100644 --- a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py +++ b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py @@ -1,7 +1,8 @@ """ This task is a replica of BiasedChoiceWorldSession with the addition of optogenetic stimulation An `opto_stimulation` column is added to the trials_table, which is a boolean array of length NTRIALS_INIT -The PROBABILITY_OPTO_STIMULATION parameter is used to determine the probability of optogenetic stimulation for each trial +The PROBABILITY_OPTO_STIMULATION parameter is used to determine the probability of optogenetic stimulation +for each trial Additionally the state machine is modified to add output TTLs for optogenetic stimulation """ @@ -12,7 +13,7 @@ from typing import Literal from pybpodapi.protocol import StateMachine - +from iblrig.base_tasks import BaseSession, BpodMixin from iblrig.base_choice_world import BiasedChoiceWorldSession from iblutil.util import setup_logger import iblrig @@ -43,6 +44,19 @@ def add_state(self, **kwargs): super().add_state(**kwargs) +class ZapitMixin(BpodMixin): + + def zapit_start_laser(self): + self.logger.critical('zap') + + def zapit_stop_laser(self): + self.logger.critical('a plus zap') + + def softcode_dictionary(self): + softcode_dictionary = super().softcode_dictionary() + # Todo register softcodes in Bpod actions + + class Session(BiasedChoiceWorldSession): protocol_name = 'nate_optoBiasedChoiceWorld' extractor_tasks = ['TrialRegisterRaw', 'ChoiceWorldTrials', 'TrainingStatus'] From 4c87a3b3c4e8e198c01f6f5fa79bd755261e99dc Mon Sep 17 00:00:00 2001 From: owinter Date: Wed, 21 Feb 2024 22:52:28 +0000 Subject: [PATCH 2/7] task prototype with zapit softcodes --- .../nate_optoBiasedChoiceWorld/task.py | 75 ++++++++++++------- .../task_parameters.yaml | 3 +- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py index d12f5f5..b7fd5a0 100644 --- a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py +++ b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py @@ -13,7 +13,6 @@ from typing import Literal from pybpodapi.protocol import StateMachine -from iblrig.base_tasks import BaseSession, BpodMixin from iblrig.base_choice_world import BiasedChoiceWorldSession from iblutil.util import setup_logger import iblrig @@ -22,6 +21,7 @@ INTERACTIVE_DELAY = 1.0 NTRIALS_INIT = 2000 +SOFTCODE_STOP_ZAPIT = 5 # read defaults from task_parameters.yaml with open(Path(__file__).parent.joinpath('task_parameters.yaml')) as f: @@ -33,30 +33,20 @@ class OptoStateMachine(StateMachine): This class just adds output TTL on BNC2 for defined states """ - def __init__(self, bpod, is_opto_stimulation=False, states_opto_ttls=None): + def __init__(self, bpod, is_opto_stimulation=False, states_opto_ttls=None, states_opto_stop=None): super().__init__(bpod) self.is_opto_stimulation = is_opto_stimulation self.states_opto_ttls = states_opto_ttls or [] def add_state(self, **kwargs): - if self.is_opto_stimulation and kwargs['state_name'] in self.states_opto_ttls: - kwargs['output_actions'].append(('BNC2', 255)) + if self.is_opto_stimulation: + if kwargs['state_name'] in self.states_opto_ttls: + kwargs['output_actions'].append(('BNC2', 255)) + elif kwargs['state_name'] in self.states_opto_ttls: + kwargs['output_actions'].append(('SoftCode', SOFTCODE_STOP_ZAPIT)) super().add_state(**kwargs) -class ZapitMixin(BpodMixin): - - def zapit_start_laser(self): - self.logger.critical('zap') - - def zapit_stop_laser(self): - self.logger.critical('a plus zap') - - def softcode_dictionary(self): - softcode_dictionary = super().softcode_dictionary() - # Todo register softcodes in Bpod actions - - class Session(BiasedChoiceWorldSession): protocol_name = 'nate_optoBiasedChoiceWorld' extractor_tasks = ['TrialRegisterRaw', 'ChoiceWorldTrials', 'TrainingStatus'] @@ -66,15 +56,14 @@ def __init__( *args, probability_opto_stim: float = DEFAULTS['PROBABILITY_OPTO_STIM'], contrast_set_probability_type: Literal['skew_zero', 'uniform'] = DEFAULTS['CONTRAST_SET_PROBABILITY_TYPE'], - opto_stim_states: list[str] = DEFAULTS['OPTO_STIM_STATES'], + opto_ttl_states: list[str] = DEFAULTS['OPTO_TTL_STATES'], + opto_stop_states: list[str] = DEFAULTS['OPTO_STOP_STATES'], **kwargs, ): super().__init__(*args, **kwargs) - print(probability_opto_stim) - print(contrast_set_probability_type) - print(opto_stim_states) self.task_params['CONTRAST_SET_PROBABILITY_TYPE'] = contrast_set_probability_type - self.task_params['OPTO_STIM_STATES'] = opto_stim_states + self.task_params['OPTO_TTL_STATES'] = opto_ttl_states + self.task_params['OPTO_STOP_STATES'] = opto_stop_states self.task_params['PROBABILITY_OPTO_STIM'] = probability_opto_stim # generates the opto stimulation for each trial @@ -82,6 +71,20 @@ def __init__( [0, 1], p=[1 - probability_opto_stim, probability_opto_stim], size=NTRIALS_INIT ).astype(bool) + # add the softcodes for the zapit opto stimulation + soft_code_dict = self.bpod.softcodes + soft_code_dict.update({SOFTCODE_STOP_ZAPIT: self.zapit_stop_laser}) + self.bpod.register_softcodes(soft_code_dict) + + def zapit_init_laser(self): + pass + + def zapit_start_laser_trigger_mode(self): + pass + + def zapit_stop_laser(self): + self.logger.critical('a plus zap') + def _instantiate_state_machine(self, trial_number=None): """ We override this using the custom class OptoStateMachine that appends TTLs for optogenetic stimulation where needed @@ -89,8 +92,15 @@ def _instantiate_state_machine(self, trial_number=None): :return: """ is_opto_stimulation = self.trials_table.at[trial_number, 'opto_stimulation'] - states_opto_ttls = self.task_params['OPTO_STIM_STATES'] - return OptoStateMachine(self.bpod, is_opto_stimulation=is_opto_stimulation, states_opto_ttls=states_opto_ttls) + # we start the laser waiting for a TTL trigger before sending out the state machine on opto trials + if is_opto_stimulation: + self.zapit_start_laser_trigger_mode() + return OptoStateMachine( + self.bpod, + is_opto_stimulation=is_opto_stimulation, + states_opto_ttls=self.task_params['OPTO_TTL_STATES'], + states_opto_stop=self.task_params['OPTO_STOP_STATES'], + ) @staticmethod def extra_parser(): @@ -114,14 +124,23 @@ def extra_parser(): help=f'probability type for contrast set (default: {DEFAULTS["CONTRAST_SET_PROBABILITY_TYPE"]})', ) parser.add_argument( - '--opto_stim_states', - option_strings=['--opto_stim_states'], - dest='opto_stim_states', - default=DEFAULTS['OPTO_STIM_STATES'], + '--opto_ttl_states', + option_strings=['--opto_ttl_states'], + dest='opto_ttl_states', + default=DEFAULTS['OPTO_TTL_STATES'], nargs='+', type=str, help=f'list of the state machine states where opto stim should be delivered', ) + parser.add_argument( + '--opto_stop_states', + option_strings=['--opto_stop_states'], + dest='opto_stop_states', + default=DEFAULTS['OPTO_STOP_STATES'], + nargs='+', + type=str, + help=f'list of the state machine states where opto stim should be stopped', + ) return parser diff --git a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task_parameters.yaml b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task_parameters.yaml index c09aec1..eaf8396 100644 --- a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task_parameters.yaml +++ b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task_parameters.yaml @@ -1,6 +1,7 @@ 'CONTRAST_SET_PROBABILITY_TYPE': uniform # uniform or skew_zero: uniform makes the 0 contrast as likely as the others, while skew_zero makes it half as likely as other contrasts -'OPTO_STIM_STATES': # list of the state machine states where opto stim should be delivered +'OPTO_TTL_STATES': # list of the state machine states where opto stim should be delivered - trial_start +'OPTO_STOP_STATES': - no_go - error - reward From 80f7fd30cbd7c91d5c0ca3dcbadff3c453927c9a Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Thu, 22 Feb 2024 13:22:22 +0000 Subject: [PATCH 3/7] Update pyproject.toml --- pyproject.toml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5f09f62..cd6306f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,3 +20,42 @@ readme = { file = "README.md", content-type = "text/markdown" } [tool.setuptools.packages] find = {} + +[tool.ruff] +ignore = [ + "PLR0912", # Too many branches + "PLR0915", # Too many statements + "PLR2004", # Magic value used in comparison, consider replacing with a constant variable +] +exclude = [ + ".mypy_cache", + "dist", + "docs", + "iblrig/gui/*_rc.py", + "iblrig/gui/ui_*.py", + "venv", +] +indent-width = 4 +line-length = 130 +target-version = "py310" + +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "E", # pycodestyle + "F", # Pyflakes + "I", # isort + "N", # pep8-naming + "PL", # pylint + "SIM", # flake8-simplify + "UP", # pyupgrade +] + +[tool.ruff.format] +quote-style = "single" + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ruff.lint.isort] +known-first-party = [ "ibl*", "one*", "pybpod*" ] From 807e17c0d6b092088d05a49ce94abdac1f8eb0dc Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Thu, 22 Feb 2024 13:22:25 +0000 Subject: [PATCH 4/7] Update task.py --- .../nate_optoBiasedChoiceWorld/task.py | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py index b7fd5a0..0fc6bfa 100644 --- a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py +++ b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py @@ -7,21 +7,23 @@ Additionally the state machine is modified to add output TTLs for optogenetic stimulation """ -import numpy as np -import yaml from pathlib import Path from typing import Literal -from pybpodapi.protocol import StateMachine -from iblrig.base_choice_world import BiasedChoiceWorldSession -from iblutil.util import setup_logger +import numpy as np +import yaml + import iblrig +from iblrig.base_choice_world import SOFTCODE, BiasedChoiceWorldSession +from iblutil.util import setup_logger +from pybpodapi.protocol import StateMachine log = setup_logger(__name__) INTERACTIVE_DELAY = 1.0 NTRIALS_INIT = 2000 -SOFTCODE_STOP_ZAPIT = 5 +SOFTCODE_STOP_ZAPIT = max(SOFTCODE).value + 1 +SOFTCODE_FIRE_ZAPIT = max(SOFTCODE).value + 2 # read defaults from task_parameters.yaml with open(Path(__file__).parent.joinpath('task_parameters.yaml')) as f: @@ -33,17 +35,27 @@ class OptoStateMachine(StateMachine): This class just adds output TTL on BNC2 for defined states """ - def __init__(self, bpod, is_opto_stimulation=False, states_opto_ttls=None, states_opto_stop=None): + def __init__( + self, + bpod, + is_opto_stimulation=False, + states_opto_ttls=None, + states_opto_stop=None, + ): super().__init__(bpod) self.is_opto_stimulation = is_opto_stimulation self.states_opto_ttls = states_opto_ttls or [] + self.states_opto_stop = states_opto_stop or [] def add_state(self, **kwargs): if self.is_opto_stimulation: if kwargs['state_name'] in self.states_opto_ttls: - kwargs['output_actions'].append(('BNC2', 255)) - elif kwargs['state_name'] in self.states_opto_ttls: - kwargs['output_actions'].append(('SoftCode', SOFTCODE_STOP_ZAPIT)) + kwargs['output_actions'] += [ + ('SoftCode', SOFTCODE_FIRE_ZAPIT), + ('BNC2', 255), + ] + elif kwargs['state_name'] in self.states_opto_stop: + kwargs['output_actions'] += [('SoftCode', SOFTCODE_STOP_ZAPIT)] super().add_state(**kwargs) @@ -68,22 +80,30 @@ def __init__( # generates the opto stimulation for each trial self.trials_table['opto_stimulation'] = np.random.choice( - [0, 1], p=[1 - probability_opto_stim, probability_opto_stim], size=NTRIALS_INIT + [0, 1], + p=[1 - probability_opto_stim, probability_opto_stim], + size=NTRIALS_INIT, ).astype(bool) + def start_hardware(self): + super().start_hardware() # add the softcodes for the zapit opto stimulation soft_code_dict = self.bpod.softcodes soft_code_dict.update({SOFTCODE_STOP_ZAPIT: self.zapit_stop_laser}) + soft_code_dict.update({SOFTCODE_FIRE_ZAPIT: self.zapit_fire_laser}) self.bpod.register_softcodes(soft_code_dict) - def zapit_init_laser(self): - pass + def zapit_arm_laser(self): + log.warning('Arming laser') + # TODO: insert code for arming the laser here - def zapit_start_laser_trigger_mode(self): - pass + def zapit_fire_laser(self): + # just logging - actual firing will be triggered by the state machine via TTL + log.warning('Firing laser') def zapit_stop_laser(self): - self.logger.critical('a plus zap') + log.critical('Stopping laser') + # TODO: insert code for stopping the laser here def _instantiate_state_machine(self, trial_number=None): """ @@ -94,7 +114,7 @@ def _instantiate_state_machine(self, trial_number=None): is_opto_stimulation = self.trials_table.at[trial_number, 'opto_stimulation'] # we start the laser waiting for a TTL trigger before sending out the state machine on opto trials if is_opto_stimulation: - self.zapit_start_laser_trigger_mode() + self.zapit_arm_laser() return OptoStateMachine( self.bpod, is_opto_stimulation=is_opto_stimulation, @@ -130,7 +150,7 @@ def extra_parser(): default=DEFAULTS['OPTO_TTL_STATES'], nargs='+', type=str, - help=f'list of the state machine states where opto stim should be delivered', + help='list of the state machine states where opto stim should be delivered', ) parser.add_argument( '--opto_stop_states', @@ -139,7 +159,7 @@ def extra_parser(): default=DEFAULTS['OPTO_STOP_STATES'], nargs='+', type=str, - help=f'list of the state machine states where opto stim should be stopped', + help='list of the state machine states where opto stim should be stopped', ) return parser From 7bac5cab4703f9826d334c991a6067d82037f1e6 Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Thu, 22 Feb 2024 13:34:33 +0000 Subject: [PATCH 5/7] fix logging --- iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py index 0fc6bfa..a7287ae 100644 --- a/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py +++ b/iblrig_custom_tasks/nate_optoBiasedChoiceWorld/task.py @@ -6,7 +6,7 @@ Additionally the state machine is modified to add output TTLs for optogenetic stimulation """ - +import logging from pathlib import Path from typing import Literal @@ -15,10 +15,9 @@ import iblrig from iblrig.base_choice_world import SOFTCODE, BiasedChoiceWorldSession -from iblutil.util import setup_logger from pybpodapi.protocol import StateMachine -log = setup_logger(__name__) +log = logging.getLogger('iblrig.task') INTERACTIVE_DELAY = 1.0 NTRIALS_INIT = 2000 @@ -102,7 +101,7 @@ def zapit_fire_laser(self): log.warning('Firing laser') def zapit_stop_laser(self): - log.critical('Stopping laser') + log.warning('Stopping laser') # TODO: insert code for stopping the laser here def _instantiate_state_machine(self, trial_number=None): From f9ccf81281b8e9e252b831cfde8fcb4537061ae1 Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Fri, 8 Mar 2024 16:24:03 +0000 Subject: [PATCH 6/7] Update task.py --- iblrig_custom_tasks/_sp_passiveVideo/task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iblrig_custom_tasks/_sp_passiveVideo/task.py b/iblrig_custom_tasks/_sp_passiveVideo/task.py index 00119f2..655591d 100644 --- a/iblrig_custom_tasks/_sp_passiveVideo/task.py +++ b/iblrig_custom_tasks/_sp_passiveVideo/task.py @@ -19,7 +19,7 @@ # this allows the CI and automated tests to import the file and make sure it is valid without having vlc try: import vlc -except ModuleNotFoundError: +except (ModuleNotFoundError, FileNotFoundError): _logger.error(f'VLC not installed. Please install VLC to use this task. {__file__}') From 0cc43245f87863a90abcbb2750bd7d557b007ddb Mon Sep 17 00:00:00 2001 From: Florian Rau Date: Fri, 8 Mar 2024 16:48:12 +0000 Subject: [PATCH 7/7] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 54fafe1..f2c6690 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "project_extraction" -version = "0.2.1.post0" +version = "0.2.2" description = "Custom extractors for satellite tasks" dynamic = [ "readme" ] keywords = [ "IBL", "neuro-science" ]