From bbee8b4bbbfe879f6fdec8e558cf52510c88cf88 Mon Sep 17 00:00:00 2001 From: Christopher Morrison Date: Mon, 21 Aug 2023 12:07:08 -0700 Subject: [PATCH] isort+black --- .../behavior/behavior_session.py | 24 +- .../data_objects/stimuli/presentations.py | 25 +- .../behavior/data_objects/trials/trials.py | 326 +++--- .../behavior/stimulus_processing.py | 75 +- .../behavior/data_objects/test_stimuli.py | 5 +- .../behavior/test_stimulus_processing.py | 1019 ++++++++++------- 6 files changed, 830 insertions(+), 644 deletions(-) diff --git a/allensdk/brain_observatory/behavior/behavior_session.py b/allensdk/brain_observatory/behavior/behavior_session.py index 8c2cfffe0..1e2b683da 100644 --- a/allensdk/brain_observatory/behavior/behavior_session.py +++ b/allensdk/brain_observatory/behavior/behavior_session.py @@ -285,7 +285,7 @@ def from_json( path=session_data["stim_table_file"], behavior_session_id=session_data["behavior_session_id"], exclude_columns=stimulus_presentation_exclude_columns, - trials=trials + trials=trials, ), templates=Templates.from_stimulus_file( stimulus_file=stimulus_file_lookup.behavior_stimulus_file @@ -358,7 +358,7 @@ def from_lims( date_of_acquisition: Optional[DateOfAcquisition] = None, eye_tracking_z_threshold: float = 3.0, eye_tracking_dilation_frames: int = 2, - load_stimulus_movie: bool = True + load_stimulus_movie: bool = True, ) -> "BehaviorSession": """ @@ -454,7 +454,7 @@ def from_lims( project_code=ProjectCode.from_lims( behavior_session_id=behavior_session_id.value, lims_db=lims_db ), - load_stimulus_movie=load_stimulus_movie + load_stimulus_movie=load_stimulus_movie, ) if date_of_acquisition is None: @@ -1115,8 +1115,10 @@ def stimulus_presentations(self) -> pd.DataFrame: table = table.drop(columns=["image_set", "index"], errors="ignore") table = table.rename(columns={"stop_time": "end_time"}) - if "trials_id" not in table.columns \ - and 'stimulus_block' in table.columns: + if ( + "trials_id" not in table.columns + and "stimulus_block" in table.columns + ): table["trials_id"] = compute_trials_id_for_stimulus( table, self.trials ) @@ -1173,9 +1175,7 @@ def stimulus_natural_movie_template(self) -> Optional[pd.DataFrame]: if self._stimuli.templates.fingerprint_movie_template_key is not None: return self._stimuli.templates.value[ self._stimuli.templates.fingerprint_movie_template_key - ].to_dataframe( - index_name='frame_number', - index_type='int') + ].to_dataframe(index_name="frame_number", index_type="int") else: return None @@ -1413,7 +1413,7 @@ def _read_stimuli( trials: Trials, stimulus_presentation_columns: Optional[List[str]] = None, project_code: Optional[ProjectCode] = None, - load_stimulus_movie: bool = False + load_stimulus_movie: bool = False, ) -> Stimuli: """ Construct the Stimuli data object for this session @@ -1432,7 +1432,7 @@ def _read_stimuli( presentation_columns=stimulus_presentation_columns, project_code=project_code, trials=trials, - load_stimulus_movie=load_stimulus_movie + load_stimulus_movie=load_stimulus_movie, ) @classmethod @@ -1512,7 +1512,7 @@ def _read_data_from_stimulus_file( include_stimuli: bool = True, stimulus_presentation_columns: Optional[List[str]] = None, project_code: Optional[ProjectCode] = None, - load_stimulus_movie: bool = False + load_stimulus_movie: bool = False, ): """Helper method to read data from stimulus file""" @@ -1549,7 +1549,7 @@ def _read_data_from_stimulus_file( trials=trials, stimulus_presentation_columns=stimulus_presentation_columns, project_code=project_code, - load_stimulus_movie=load_stimulus_movie + load_stimulus_movie=load_stimulus_movie, ) else: stimuli = None diff --git a/allensdk/brain_observatory/behavior/data_objects/stimuli/presentations.py b/allensdk/brain_observatory/behavior/data_objects/stimuli/presentations.py index a1692916e..0a36a240f 100644 --- a/allensdk/brain_observatory/behavior/data_objects/stimuli/presentations.py +++ b/allensdk/brain_observatory/behavior/data_objects/stimuli/presentations.py @@ -60,7 +60,7 @@ def __init__( columns_to_rename: Optional[Dict[str, str]] = None, column_list: Optional[List[str]] = None, sort_columns: bool = True, - trials: Optional[Trials] = None + trials: Optional[Trials] = None, ): """ @@ -102,13 +102,11 @@ def __init__( if "active" not in presentations.columns: # Add column marking where the mouse is engaged in active, # trained behavior. - presentations = add_active_flag( - presentations, trials.data - ) + presentations = add_active_flag(presentations, trials.data) if "trials_id" not in presentations.columns: # Add trials_id to presentations df to allow for joining of the # two tables. - presentations['trials_id'] = compute_trials_id_for_stimulus( + presentations["trials_id"] = compute_trials_id_for_stimulus( presentations, trials.data ) if "is_sham_change" not in presentations.columns: @@ -271,9 +269,9 @@ def from_nwb( if add_trials_dependent_values and nwbfile.trials is not None: trials = Trials.from_nwb(nwbfile) - return Presentations(presentations=table, - column_list=column_list, - trials=trials) + return Presentations( + presentations=table, column_list=column_list, trials=trials + ) @classmethod def from_stimulus_file( @@ -324,7 +322,8 @@ def from_stimulus_file( ) raw_stim_pres_df = raw_stim_pres_df.drop(columns=["index"]) raw_stim_pres_df = cls._check_for_errant_omitted_stimulus( - input_df=raw_stim_pres_df) + input_df=raw_stim_pres_df + ) # Fill in nulls for image_name # This makes two assumptions: @@ -428,9 +427,7 @@ def from_stimulus_file( ) return Presentations( - presentations=stim_pres_df, - column_list=column_list, - trials=trials + presentations=stim_pres_df, column_list=column_list, trials=trials ) @classmethod @@ -441,7 +438,7 @@ def from_path( exclude_columns: Optional[List[str]] = None, columns_to_rename: Optional[Dict[str, str]] = None, sort_columns: bool = True, - trials: Optional[Trials] = None + trials: Optional[Trials] = None, ) -> "Presentations": """ Reads the table directly from a precomputed csv @@ -578,7 +575,7 @@ def _postprocess( @staticmethod def _check_for_errant_omitted_stimulus( - input_df: pd.DataFrame + input_df: pd.DataFrame, ) -> pd.DataFrame: """Check if the first entry in the DataFrame is an omitted stimulus. diff --git a/allensdk/brain_observatory/behavior/data_objects/trials/trials.py b/allensdk/brain_observatory/behavior/data_objects/trials/trials.py index 51507ed87..39950ecd3 100644 --- a/allensdk/brain_observatory/behavior/data_objects/trials/trials.py +++ b/allensdk/brain_observatory/behavior/data_objects/trials/trials.py @@ -1,36 +1,44 @@ -from typing import List, Tuple, Optional +from typing import List, Optional, Tuple import numpy as np import pandas as pd -from pynwb import NWBFile - -from allensdk.core.dataframe_utils import ( - enforce_df_int_typing -) from allensdk.brain_observatory import dict_to_indexed_array from allensdk.brain_observatory.behavior.data_files import ( - BehaviorStimulusFile, SyncFile) -from allensdk.brain_observatory.behavior.data_objects.task_parameters import \ - TaskParameters -from allensdk.brain_observatory.behavior.dprime import get_hit_rate, \ - get_trial_count_corrected_hit_rate, get_false_alarm_rate, \ - get_trial_count_corrected_false_alarm_rate, get_rolling_dprime -from allensdk.core import DataObject + BehaviorStimulusFile, + SyncFile, +) +from allensdk.brain_observatory.behavior.data_files.stimulus_file import ( + StimulusFileReadableInterface, +) from allensdk.brain_observatory.behavior.data_objects import StimulusTimestamps -from allensdk.core import \ - NwbReadableInterface -from allensdk.brain_observatory.behavior.data_files.stimulus_file import \ - StimulusFileReadableInterface -from allensdk.core import \ - NwbWritableInterface from allensdk.brain_observatory.behavior.data_objects.licks import Licks from allensdk.brain_observatory.behavior.data_objects.rewards import Rewards +from allensdk.brain_observatory.behavior.data_objects.task_parameters import ( + TaskParameters, +) from allensdk.brain_observatory.behavior.data_objects.trials.trial import Trial +from allensdk.brain_observatory.behavior.dprime import ( + get_false_alarm_rate, + get_hit_rate, + get_rolling_dprime, + get_trial_count_corrected_false_alarm_rate, + get_trial_count_corrected_hit_rate, +) +from allensdk.core import ( + DataObject, + NwbReadableInterface, + NwbWritableInterface, +) +from allensdk.core.dataframe_utils import enforce_df_int_typing +from pynwb import NWBFile -class Trials(DataObject, StimulusFileReadableInterface, - NwbReadableInterface, NwbWritableInterface): - +class Trials( + DataObject, + StimulusFileReadableInterface, + NwbReadableInterface, + NwbWritableInterface, +): @classmethod def trial_class(cls): """ @@ -38,11 +46,7 @@ def trial_class(cls): """ return Trial - def __init__( - self, - trials: pd.DataFrame, - response_window_start: float - ): + def __init__(self, trials: pd.DataFrame, response_window_start: float): """ Parameters ---------- @@ -51,8 +55,8 @@ def __init__( [seconds] relative to the non-display-lag-compensated presentation of the change-image """ - trials = trials.rename(columns={'stimulus_change': 'is_change'}) - super().__init__(name='trials', value=None, is_value_self=True) + trials = trials.rename(columns={"stimulus_change": "is_change"}) + super().__init__(name="trials", value=None, is_value_self=True) trials = enforce_df_int_typing(trials, ["change_frame"]) self._trials = trials @@ -71,75 +75,75 @@ def trial_count(self) -> int: @property def go_trial_count(self) -> int: """Number of 'go' trials""" - return self._trials['go'].sum() + return self._trials["go"].sum() @property def catch_trial_count(self) -> int: """Number of 'catch' trials""" - return self._trials['catch'].sum() + return self._trials["catch"].sum() @property def hit_trial_count(self) -> int: """Number of trials with a hit behavior response""" - return self._trials['hit'].sum() + return self._trials["hit"].sum() @property def miss_trial_count(self) -> int: """Number of trials with a hit behavior response""" - return self._trials['miss'].sum() + return self._trials["miss"].sum() @property def false_alarm_trial_count(self) -> int: """Number of trials where the mouse had a false alarm behavior response""" - return self._trials['false_alarm'].sum() + return self._trials["false_alarm"].sum() @property def correct_reject_trial_count(self) -> int: """Number of trials with a correct reject behavior response""" - return self._trials['correct_reject'].sum() + return self._trials["correct_reject"].sum() def to_nwb(self, nwbfile: NWBFile) -> NWBFile: trials = self.data order = list(trials.index) - for _, row in trials[['start_time', 'stop_time']].iterrows(): + for _, row in trials[["start_time", "stop_time"]].iterrows(): row_dict = row.to_dict() nwbfile.add_trial(**row_dict) for c in trials.columns: - if c in ['start_time', 'stop_time']: + if c in ["start_time", "stop_time"]: continue index, data = dict_to_indexed_array(trials[c].to_dict(), order) - if data.dtype == ' "Trials": trials = nwbfile.trials.to_dataframe() - if 'lick_events' in trials.columns: - trials.drop('lick_events', inplace=True, axis=1) - trials.index = trials.index.rename('trials_id') + if "lick_events" in trials.columns: + trials.drop("lick_events", inplace=True, axis=1) + trials.index = trials.index.rename("trials_id") return cls( trials=trials, response_window_start=TaskParameters.from_nwb( nwbfile=nwbfile - ).response_window_sec[0] + ).response_window_sec[0], ) @classmethod @@ -147,21 +151,39 @@ def columns_to_output(cls) -> List[str]: """ Return the list of columns to be output in this table """ - return ['initial_image_name', 'change_image_name', - 'stimulus_change', 'change_time', - 'go', 'catch', 'lick_times', 'response_time', - 'response_latency', 'reward_time', 'reward_volume', - 'hit', 'false_alarm', 'miss', 'correct_reject', - 'aborted', 'auto_rewarded', 'change_frame', - 'start_time', 'stop_time', 'trial_length'] + return [ + "initial_image_name", + "change_image_name", + "stimulus_change", + "change_time", + "go", + "catch", + "lick_times", + "response_time", + "response_latency", + "reward_time", + "reward_volume", + "hit", + "false_alarm", + "miss", + "correct_reject", + "aborted", + "auto_rewarded", + "change_frame", + "start_time", + "stop_time", + "trial_length", + ] @classmethod - def from_stimulus_file(cls, stimulus_file: BehaviorStimulusFile, - stimulus_timestamps: StimulusTimestamps, - licks: Licks, - rewards: Rewards, - sync_file: Optional[SyncFile] = None - ) -> "Trials": + def from_stimulus_file( + cls, + stimulus_file: BehaviorStimulusFile, + stimulus_timestamps: StimulusTimestamps, + licks: Licks, + rewards: Rewards, + sync_file: Optional[SyncFile] = None, + ) -> "Trials": bsf = stimulus_file.data stimuli = bsf["items"]["behavior"]["stimuli"] @@ -174,20 +196,21 @@ def from_stimulus_file(cls, stimulus_file: BehaviorStimulusFile, for idx, trial in enumerate(trial_log): trial_start, trial_end = trial_bounds[idx] t = cls.trial_class()( - trial=trial, - start=trial_start, - end=trial_end, - behavior_stimulus_file=stimulus_file, - index=idx, - stimulus_timestamps=stimulus_timestamps, - licks=licks, rewards=rewards, - stimuli=stimuli, - sync_file=sync_file - ) + trial=trial, + start=trial_start, + end=trial_end, + behavior_stimulus_file=stimulus_file, + index=idx, + stimulus_timestamps=stimulus_timestamps, + licks=licks, + rewards=rewards, + stimuli=stimuli, + sync_file=sync_file, + ) all_trial_data[idx] = t.data - trials = pd.DataFrame(all_trial_data).set_index('trial') - trials.index = trials.index.rename('trials_id') + trials = pd.DataFrame(all_trial_data).set_index("trial") + trials.index = trials.index.rename("trials_id") # Order/Filter columns trials = trials[cls.columns_to_output()] @@ -196,7 +219,7 @@ def from_stimulus_file(cls, stimulus_file: BehaviorStimulusFile, trials=trials, response_window_start=TaskParameters.from_stimulus_file( stimulus_file=stimulus_file - ).response_window_sec[0] + ).response_window_sec[0], ) @staticmethod @@ -225,8 +248,8 @@ def _get_trial_bounds(trial_log: List) -> List[Tuple[int, int]]: for trial in trial_log: start_f = None - for event in trial['events']: - if event[0] == 'trial_start': + for event in trial["events"]: + if event[0] == "trial_start": start_f = event[-1] break if start_f is None: @@ -252,35 +275,35 @@ def index(self) -> pd.Index: @property def change_time(self) -> pd.Series: - return self.data['change_time'] + return self.data["change_time"] @property def lick_times(self) -> pd.Series: - return self.data['lick_times'] + return self.data["lick_times"] @property def start_time(self) -> pd.Series: - return self.data['start_time'] + return self.data["start_time"] @property def aborted(self) -> pd.Series: - return self.data['aborted'] + return self.data["aborted"] @property def hit(self) -> pd.Series: - return self.data['hit'] + return self.data["hit"] @property def miss(self) -> pd.Series: - return self.data['miss'] + return self.data["miss"] @property def false_alarm(self) -> pd.Series: - return self.data['false_alarm'] + return self.data["false_alarm"] @property def correct_reject(self) -> pd.Series: - return self.data['correct_reject'] + return self.data["correct_reject"] @property def rolling_performance(self) -> pd.DataFrame: @@ -323,69 +346,69 @@ def rolling_performance(self) -> pd.DataFrame: # Indices to build trial metrics dataframe: trials_index = self.data.index - not_aborted_index = \ - self.data[np.logical_not(self.aborted)].index + not_aborted_index = self.data[np.logical_not(self.aborted)].index # Initialize dataframe: performance_metrics_df = pd.DataFrame(index=trials_index) # Reward rate: - performance_metrics_df['reward_rate'] = \ - pd.Series(reward_rate, index=self.data.index) + performance_metrics_df["reward_rate"] = pd.Series( + reward_rate, index=self.data.index + ) # Hit rate raw: hit_rate_raw = get_hit_rate( - hit=self.hit, - miss=self.miss, - aborted=self.aborted) - performance_metrics_df['hit_rate_raw'] = \ - pd.Series(hit_rate_raw, index=not_aborted_index) + hit=self.hit, miss=self.miss, aborted=self.aborted + ) + performance_metrics_df["hit_rate_raw"] = pd.Series( + hit_rate_raw, index=not_aborted_index + ) # Hit rate with trial count correction: hit_rate = get_trial_count_corrected_hit_rate( - hit=self.hit, - miss=self.miss, - aborted=self.aborted) - performance_metrics_df['hit_rate'] = \ - pd.Series(hit_rate, index=not_aborted_index) + hit=self.hit, miss=self.miss, aborted=self.aborted + ) + performance_metrics_df["hit_rate"] = pd.Series( + hit_rate, index=not_aborted_index + ) # False-alarm rate raw: - false_alarm_rate_raw = \ - get_false_alarm_rate( - false_alarm=self.false_alarm, - correct_reject=self.correct_reject, - aborted=self.aborted) - performance_metrics_df['false_alarm_rate_raw'] = \ - pd.Series(false_alarm_rate_raw, index=not_aborted_index) + false_alarm_rate_raw = get_false_alarm_rate( + false_alarm=self.false_alarm, + correct_reject=self.correct_reject, + aborted=self.aborted, + ) + performance_metrics_df["false_alarm_rate_raw"] = pd.Series( + false_alarm_rate_raw, index=not_aborted_index + ) # False-alarm rate with trial count correction: - false_alarm_rate = \ - get_trial_count_corrected_false_alarm_rate( - false_alarm=self.false_alarm, - correct_reject=self.correct_reject, - aborted=self.aborted) - performance_metrics_df['false_alarm_rate'] = \ - pd.Series(false_alarm_rate, index=not_aborted_index) + false_alarm_rate = get_trial_count_corrected_false_alarm_rate( + false_alarm=self.false_alarm, + correct_reject=self.correct_reject, + aborted=self.aborted, + ) + performance_metrics_df["false_alarm_rate"] = pd.Series( + false_alarm_rate, index=not_aborted_index + ) # Rolling-dprime: - is_passive_session = ( - (self.data['reward_volume'] == 0).all() and - (self.data['lick_times'].apply(lambda x: len(x)) == 0).all() - ) + is_passive_session = (self.data["reward_volume"] == 0).all() and ( + self.data["lick_times"].apply(lambda x: len(x)) == 0 + ).all() if is_passive_session: # It does not make sense to calculate d' for a passive session # So just set it to zeros rolling_dprime = np.zeros(len(hit_rate)) else: rolling_dprime = get_rolling_dprime(hit_rate, false_alarm_rate) - performance_metrics_df['rolling_dprime'] = \ - pd.Series(rolling_dprime, index=not_aborted_index) + performance_metrics_df["rolling_dprime"] = pd.Series( + rolling_dprime, index=not_aborted_index + ) return performance_metrics_df - def _calculate_response_latency_list( - self - ) -> List: + def _calculate_response_latency_list(self) -> List: """per trial, determines a response latency Returns @@ -406,32 +429,36 @@ def _calculate_response_latency_list( (the two instance of monitor delay cancel out in the difference). """ - df = pd.DataFrame({'lick_times': self.lick_times, - 'change_time': self.change_time}) - df['valid_response_licks'] = df.apply( - lambda trial: [lt for lt in trial['lick_times'] - if lt - trial['change_time'] > - self._response_window_start], - axis=1) + df = pd.DataFrame( + {"lick_times": self.lick_times, "change_time": self.change_time} + ) + df["valid_response_licks"] = df.apply( + lambda trial: [ + lt + for lt in trial["lick_times"] + if lt - trial["change_time"] > self._response_window_start + ], + axis=1, + ) response_latency = df.apply( - lambda trial: trial['valid_response_licks'][0] - - trial['change_time'] - if len(trial['valid_response_licks']) > 0 else float('inf'), - axis=1) + lambda trial: trial["valid_response_licks"][0] + - trial["change_time"] + if len(trial["valid_response_licks"]) > 0 + else float("inf"), + axis=1, + ) return response_latency.tolist() def calculate_reward_rate( - self, - window=0.75, - trial_window=25, - initial_trials=10 + self, window=0.75, trial_window=25, initial_trials=10 ): response_latency = self._calculate_response_latency_list() starttime = self.start_time.values assert len(response_latency) == len(starttime) - df = pd.DataFrame({'response_latency': response_latency, - 'starttime': starttime}) + df = pd.DataFrame( + {"response_latency": response_latency, "starttime": starttime} + ) # adds a column called reward_rate to the input dataframe # the reward_rate column contains a rolling average of rewards/min @@ -447,7 +474,6 @@ def calculate_reward_rate( reward_rate[:initial_trials] = np.inf for trial_number in range(initial_trials, len(df)): - min_index = np.max((0, trial_number - trial_window)) max_index = np.min((trial_number + trial_window, len(df))) df_roll = df.iloc[min_index:max_index] @@ -456,20 +482,20 @@ def calculate_reward_rate( correct = len(df_roll[df_roll.response_latency < window]) # get the time elapsed over the trials - time_elapsed = df_roll.starttime.iloc[-1] - \ - df_roll.starttime.iloc[0] + time_elapsed = ( + df_roll.starttime.iloc[-1] - df_roll.starttime.iloc[0] + ) # calculate the reward rate, rewards/min reward_rate_on_this_lap = correct / time_elapsed * 60 reward_rate[trial_number] = reward_rate_on_this_lap - reward_rate[np.isinf(reward_rate)] = float('nan') + reward_rate[np.isinf(reward_rate)] = float("nan") return reward_rate def _get_engaged_trials( - self, - engaged_trial_reward_rate_threshold: float = 2.0 + self, engaged_trial_reward_rate_threshold: float = 2.0 ) -> pd.Series: """ Gets `Series` where each trial that is considered "engaged" is set to @@ -487,13 +513,13 @@ def _get_engaged_trials( """ rolling_performance = self.rolling_performance engaged_trial_mask = ( - rolling_performance['reward_rate'] > - engaged_trial_reward_rate_threshold) + rolling_performance["reward_rate"] + > engaged_trial_reward_rate_threshold + ) return engaged_trial_mask def get_engaged_trial_count( - self, - engaged_trial_reward_rate_threshold: float = 2.0 + self, engaged_trial_reward_rate_threshold: float = 2.0 ) -> int: """Gets count of trials considered "engaged" @@ -509,5 +535,7 @@ def get_engaged_trial_count( """ engaged_trials = self._get_engaged_trials( engaged_trial_reward_rate_threshold=( - engaged_trial_reward_rate_threshold)) + engaged_trial_reward_rate_threshold + ) + ) return engaged_trials.sum() diff --git a/allensdk/brain_observatory/behavior/stimulus_processing.py b/allensdk/brain_observatory/behavior/stimulus_processing.py index aa086ce0f..d28a6c386 100644 --- a/allensdk/brain_observatory/behavior/stimulus_processing.py +++ b/allensdk/brain_observatory/behavior/stimulus_processing.py @@ -646,7 +646,7 @@ def get_flashes_since_change( data=np.zeros(len(stimulus_presentations), dtype=float), index=stimulus_presentations.index, name="flashes_since_change", - dtype='int' + dtype="int", ) for idx, (pd_index, row) in enumerate(stimulus_presentations.iterrows()): omitted = row["omitted"] @@ -665,8 +665,7 @@ def get_flashes_since_change( def add_active_flag( - stim_pres_table: pd.DataFrame, - trials: pd.DataFrame + stim_pres_table: pd.DataFrame, trials: pd.DataFrame ) -> pd.DataFrame: """Mark the active stimuli by lining up the stimulus times with the trials times. @@ -691,17 +690,18 @@ def add_active_flag( index=stim_pres_table.index, name="active", ) - stim_mask = (stim_pres_table.start_time > trials.start_time.min()) & ( - stim_pres_table.start_time < trials.stop_time.max() - ) & (~stim_pres_table.image_name.isna()) + stim_mask = ( + (stim_pres_table.start_time > trials.start_time.min()) + & (stim_pres_table.start_time < trials.stop_time.max()) + & (~stim_pres_table.image_name.isna()) + ) active[stim_mask] = True - stim_pres_table['active'] = active + stim_pres_table["active"] = active return stim_pres_table def compute_trials_id_for_stimulus( - stim_pres_table: pd.DataFrame, - trials_table: pd.DataFrame + stim_pres_table: pd.DataFrame, trials_table: pd.DataFrame ) -> pd.Series: """Add an id to allow for merging of the stimulus presentations table with the trials table. @@ -733,10 +733,12 @@ def compute_trials_id_for_stimulus( data=np.full(len(stim_pres_table), INT_NULL, dtype=int), index=stim_pres_table.index, name="trials_id", - ).astype('int') + ).astype("int") # Return input frame if the stimulus_block or active is not available. - if "stimulus_block" not in stim_pres_table.columns \ - or "active" not in stim_pres_table.columns: + if ( + "stimulus_block" not in stim_pres_table.columns + or "active" not in stim_pres_table.columns + ): return trials_ids active_sorted = stim_pres_table.active @@ -746,9 +748,11 @@ def compute_trials_id_for_stimulus( # by only using the max time for all trials as the limit. max_trials_stop = trials_table.stop_time.max() for idx, trial in trials_table.iterrows(): - stim_mask = (stim_pres_table.start_time > trial.start_time) & ( - stim_pres_table.start_time < max_trials_stop - ) & (~stim_pres_table.image_name.isna()) + stim_mask = ( + (stim_pres_table.start_time > trial.start_time) + & (stim_pres_table.start_time < max_trials_stop) + & (~stim_pres_table.image_name.isna()) + ) trials_ids[stim_mask] = idx # The code below finds all stimulus blocks that contain images/trials @@ -864,8 +868,7 @@ def produce_stimulus_block_names( def compute_is_sham_change( - stim_df: pd.DataFrame, - trials: pd.DataFrame + stim_df: pd.DataFrame, trials: pd.DataFrame ) -> pd.DataFrame: """Add is_sham_change to stimulus presentation table. @@ -881,21 +884,24 @@ def compute_is_sham_change( stimulus_presentations : pandas.DataFrame Input ``stim_df`` DataFrame with the is_sham_change column added. """ - if "trials_id" not in stim_df.columns \ - or "active" not in stim_df.columns \ - or "stimulus_block" not in stim_df.columns: + if ( + "trials_id" not in stim_df.columns + or "active" not in stim_df.columns + or "stimulus_block" not in stim_df.columns + ): return stim_df - stim_trials = stim_df.merge(trials, - left_on='trials_id', - right_index=True, - how='left') - catch_frames = stim_trials[ - stim_trials['catch'].fillna(False)]['change_frame'].unique() - - stim_df['is_sham_change'] = False + stim_trials = stim_df.merge( + trials, left_on="trials_id", right_index=True, how="left" + ) + catch_frames = stim_trials[stim_trials["catch"].fillna(False)][ + "change_frame" + ].unique() + + stim_df["is_sham_change"] = False catch_flashes = stim_df[ - stim_df['start_frame'].isin(catch_frames)].index.values - stim_df.loc[catch_flashes, 'is_sham_change'] = True + stim_df["start_frame"].isin(catch_frames) + ].index.values + stim_df.loc[catch_flashes, "is_sham_change"] = True stim_blocks = stim_df.stimulus_block stim_image_names = stim_df.image_name @@ -914,11 +920,10 @@ def compute_is_sham_change( for passive_stim_block in passive_stim_blocks: passive_block_mask = stim_blocks == passive_stim_block if np.array_equal( - active_images, - stim_image_names[passive_block_mask].values + active_images, stim_image_names[passive_block_mask].values ): - stim_df.loc[passive_block_mask, 'is_sham_change'] = \ - stim_df[active_block_mask][ - 'is_sham_change'].values + stim_df.loc[ + passive_block_mask, "is_sham_change" + ] = stim_df[active_block_mask]["is_sham_change"].values return stim_df.sort_index() diff --git a/allensdk/test/brain_observatory/behavior/data_objects/test_stimuli.py b/allensdk/test/brain_observatory/behavior/data_objects/test_stimuli.py index 7d0b9db6d..c82ba4f51 100644 --- a/allensdk/test/brain_observatory/behavior/data_objects/test_stimuli.py +++ b/allensdk/test/brain_observatory/behavior/data_objects/test_stimuli.py @@ -39,7 +39,8 @@ def setup_class(cls): presentations=presentations ) cls.expected_templates = Templates( - templates={templates.image_set_name: templates}) + templates={templates.image_set_name: templates} + ) @pytest.mark.requires_bamboo def test_from_stimulus_file(self): @@ -53,7 +54,7 @@ def data(self): "start_time": [300.0, 330.0, 360.0], "stop_time": [330.0, 360.0, 360.0], "catch": [False, True, False], - "change_frame": [-99, 99, -99] + "change_frame": [-99, 99, -99], } ) diff --git a/allensdk/test/brain_observatory/behavior/test_stimulus_processing.py b/allensdk/test/brain_observatory/behavior/test_stimulus_processing.py index 6257d8721..be6b20643 100644 --- a/allensdk/test/brain_observatory/behavior/test_stimulus_processing.py +++ b/allensdk/test/brain_observatory/behavior/test_stimulus_processing.py @@ -3,14 +3,23 @@ import numpy as np import pandas as pd import pytest - +from allensdk.brain_observatory.behavior.data_objects.stimuli.stimulus_templates import ( # noqa: E501 + StimulusImage, +) from allensdk.brain_observatory.behavior.stimulus_processing import ( - get_stimulus_presentations, _get_stimulus_epoch, _get_draw_epochs, - get_visual_stimuli_df, get_stimulus_metadata, get_gratings_metadata, - get_stimulus_templates, is_change_event, compute_trials_id_for_stimulus, - produce_stimulus_block_names, add_active_flag, compute_is_sham_change) -from allensdk.brain_observatory.behavior.data_objects.stimuli\ - .stimulus_templates import StimulusImage + _get_draw_epochs, + _get_stimulus_epoch, + add_active_flag, + compute_is_sham_change, + compute_trials_id_for_stimulus, + get_gratings_metadata, + get_stimulus_metadata, + get_stimulus_presentations, + get_stimulus_templates, + get_visual_stimuli_df, + is_change_event, + produce_stimulus_block_names, +) from allensdk.test.brain_observatory.behavior.conftest import get_resources_dir @@ -23,33 +32,55 @@ def behavior_stimuli_time_fixture(request): timestamp_count = request.param["timestamp_count"] time_step = request.param["time_step"] - timestamps = np.array([time_step * i for i in range( - timestamp_count)]).astype('int64') + timestamps = np.array( + [time_step * i for i in range(timestamp_count)] + ).astype("int64") return timestamps @pytest.mark.parametrize( "behavior_stimuli_data_fixture,current_set_ix,start_frame," - "n_frames,expected", [ - ({'images_set_log': [ - ('Image', 'im065', 5.809955710916157, 0), - ('Image', 'im061', 314.06612555068784, 6), - ('Image', 'im062', 348.5941232265203, 12) - ], - 'images_draw_log': ([0] + [1] * 3 + [0] * 3) * 3 + [0]}, - 0, 0, 18, (0, 6)), - ({'images_set_log': [ - ('Image', 'im065', 5.809955710916157, 0), - ('Image', 'im061', 314.06612555068784, 6), - ('Image', 'im062', 348.5941232265203, 12) - ], - 'images_draw_log': ([0] + [1] * 3 + [0] * 3) * 3 + [0]}, - 2, 11, 18, (11, 18)) - ], indirect=["behavior_stimuli_data_fixture"] + "n_frames,expected", + [ + ( + { + "images_set_log": [ + ("Image", "im065", 5.809955710916157, 0), + ("Image", "im061", 314.06612555068784, 6), + ("Image", "im062", 348.5941232265203, 12), + ], + "images_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0], + }, + 0, + 0, + 18, + (0, 6), + ), + ( + { + "images_set_log": [ + ("Image", "im065", 5.809955710916157, 0), + ("Image", "im061", 314.06612555068784, 6), + ("Image", "im062", 348.5941232265203, 12), + ], + "images_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0], + }, + 2, + 11, + 18, + (11, 18), + ), + ], + indirect=["behavior_stimuli_data_fixture"], ) -def test_get_stimulus_epoch(behavior_stimuli_data_fixture, - current_set_ix, start_frame, n_frames, expected): +def test_get_stimulus_epoch( + behavior_stimuli_data_fixture, + current_set_ix, + start_frame, + n_frames, + expected, +): items = behavior_stimuli_data_fixture["items"] log = items["behavior"]["stimuli"]["images"]["set_log"] actual = _get_stimulus_epoch(log, current_set_ix, start_frame, n_frames) @@ -58,110 +89,166 @@ def test_get_stimulus_epoch(behavior_stimuli_data_fixture, @pytest.mark.parametrize( "behavior_stimuli_data_fixture,start_frame,stop_frame,expected," - "stimuli_type", [ - ({'images_set_log': [ - ('Image', 'im065', 5.809955710916157, 0), - ('Image', 'im061', 314.06612555068784, 6), - ('Image', 'im062', 348.5941232265203, 12) - ], - 'images_draw_log': ([0] + [1] * 3 + [0] * 3) * 3 + [0]}, - 0, 6, [(1, 4)], 'images'), - ({'images_set_log': [ - ('Image', 'im065', 5.809955710916157, 0), - ('Image', 'im061', 314.06612555068784, 6), - ('Image', 'im062', 348.5941232265203, 12) - ], - 'images_draw_log': ([0] + [1] * 3 + [0] * 3) * 3 + [0]}, - 0, 11, [(1, 4), (8, 11)], 'images'), - ({'images_set_log': [ - ('Image', 'im065', 5.809955710916157, 0), - ('Image', 'im061', 314.06612555068784, 6), - ('Image', 'im062', 348.5941232265203, 12) - ], - 'images_draw_log': ([0] + [1] * 3 + [0] * 3) * 3 + [0]}, - 0, 22, [(1, 4), (8, 11), (15, 18)], 'images'), - ({"grating_set_log": [ - ("Ori", 90, 3.585, 0), - ("Ori", 180, 40.847, 6), - ("Ori", 270, 62.633, 12) - ], - "grating_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0]}, - 0, 6, [(1, 4)], 'grating'), - ({"grating_set_log": [ - ("Ori", 90.0, 3.585, 0), - ("Ori", 180.0, 40.847, 6), - ("Ori", 270.0, 62.633, 12) - ], - "grating_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0]}, - 6, 11, [(8, 11)], 'grating') - ], indirect=['behavior_stimuli_data_fixture'] + "stimuli_type", + [ + ( + { + "images_set_log": [ + ("Image", "im065", 5.809955710916157, 0), + ("Image", "im061", 314.06612555068784, 6), + ("Image", "im062", 348.5941232265203, 12), + ], + "images_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0], + }, + 0, + 6, + [(1, 4)], + "images", + ), + ( + { + "images_set_log": [ + ("Image", "im065", 5.809955710916157, 0), + ("Image", "im061", 314.06612555068784, 6), + ("Image", "im062", 348.5941232265203, 12), + ], + "images_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0], + }, + 0, + 11, + [(1, 4), (8, 11)], + "images", + ), + ( + { + "images_set_log": [ + ("Image", "im065", 5.809955710916157, 0), + ("Image", "im061", 314.06612555068784, 6), + ("Image", "im062", 348.5941232265203, 12), + ], + "images_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0], + }, + 0, + 22, + [(1, 4), (8, 11), (15, 18)], + "images", + ), + ( + { + "grating_set_log": [ + ("Ori", 90, 3.585, 0), + ("Ori", 180, 40.847, 6), + ("Ori", 270, 62.633, 12), + ], + "grating_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0], + }, + 0, + 6, + [(1, 4)], + "grating", + ), + ( + { + "grating_set_log": [ + ("Ori", 90.0, 3.585, 0), + ("Ori", 180.0, 40.847, 6), + ("Ori", 270.0, 62.633, 12), + ], + "grating_draw_log": ([0] + [1] * 3 + [0] * 3) * 3 + [0], + }, + 6, + 11, + [(8, 11)], + "grating", + ), + ], + indirect=["behavior_stimuli_data_fixture"], ) -def test_get_draw_epochs(behavior_stimuli_data_fixture, - start_frame, stop_frame, expected, stimuli_type): - draw_log = (behavior_stimuli_data_fixture["items"]["behavior"] - ["stimuli"][stimuli_type]["draw_log"]) # noqa: E128 +def test_get_draw_epochs( + behavior_stimuli_data_fixture, + start_frame, + stop_frame, + expected, + stimuli_type, +): + draw_log = behavior_stimuli_data_fixture["items"]["behavior"]["stimuli"][ + stimuli_type + ][ + "draw_log" + ] # noqa: E128 actual = _get_draw_epochs(draw_log, start_frame, stop_frame) assert actual == expected -@pytest.mark.parametrize("behavior_stimuli_data_fixture", ({},), - indirect=["behavior_stimuli_data_fixture"]) +@pytest.mark.parametrize( + "behavior_stimuli_data_fixture", + ({},), + indirect=["behavior_stimuli_data_fixture"], +) def test_get_stimulus_templates(behavior_stimuli_data_fixture): - templates = get_stimulus_templates(behavior_stimuli_data_fixture, - grating_images_dict={}) + templates = get_stimulus_templates( + behavior_stimuli_data_fixture, grating_images_dict={} + ) - assert templates.image_set_name == 'test_image_set' + assert templates.image_set_name == "test_image_set" assert len(templates) == 1 - assert list(templates.keys()) == ['im065'] + assert list(templates.keys()) == ["im065"] for img in templates.values(): assert isinstance(img, StimulusImage) - expected_path = os.path.join(get_resources_dir(), 'stimulus_template', - 'expected') + expected_path = os.path.join( + get_resources_dir(), "stimulus_template", "expected" + ) - expected_unwarped_path = os.path.join( - expected_path, 'im065_unwarped.pkl') + expected_unwarped_path = os.path.join(expected_path, "im065_unwarped.pkl") expected_unwarped = pd.read_pickle(expected_unwarped_path) - expected_warped_path = os.path.join( - expected_path, 'im065_warped.pkl') + expected_warped_path = os.path.join(expected_path, "im065_warped.pkl") expected_warped = pd.read_pickle(expected_warped_path) for img_name in templates: img = templates[img_name] - assert np.allclose(a=expected_unwarped, - b=img.unwarped, equal_nan=True) - assert np.allclose(a=expected_warped, - b=img.warped, equal_nan=True) + assert np.allclose(a=expected_unwarped, b=img.unwarped, equal_nan=True) + assert np.allclose(a=expected_warped, b=img.warped, equal_nan=True) for img_name, img in templates.items(): img = templates[img_name] - assert np.allclose(a=expected_unwarped, - b=img.unwarped, equal_nan=True) - assert np.allclose(a=expected_warped, - b=img.warped, equal_nan=True) - - -@pytest.mark.parametrize(("behavior_stimuli_data_fixture, " - "grating_images_dict, expected"), [ - ({"has_images": False}, - {"gratings_90.0": {"warped": np.ones((2, 2)), - "unwarped": np.ones( - (2, 2)) * 2}}, - {}), - ], indirect=["behavior_stimuli_data_fixture"]) -def test_get_stimulus_templates_for_gratings(behavior_stimuli_data_fixture, - grating_images_dict, expected): - templates = get_stimulus_templates(behavior_stimuli_data_fixture, - grating_images_dict=grating_images_dict) - - assert templates.image_set_name == 'grating' - assert list(templates.keys()) == ['gratings_90.0'] - assert np.allclose(templates['gratings_90.0'].warped, - np.array([[1, 1], [1, 1]])) - assert np.allclose(templates['gratings_90.0'].unwarped, - np.array([[2, 2], [2, 2]])) + assert np.allclose(a=expected_unwarped, b=img.unwarped, equal_nan=True) + assert np.allclose(a=expected_warped, b=img.warped, equal_nan=True) + + +@pytest.mark.parametrize( + ("behavior_stimuli_data_fixture, " "grating_images_dict, expected"), + [ + ( + {"has_images": False}, + { + "gratings_90.0": { + "warped": np.ones((2, 2)), + "unwarped": np.ones((2, 2)) * 2, + } + }, + {}, + ), + ], + indirect=["behavior_stimuli_data_fixture"], +) +def test_get_stimulus_templates_for_gratings( + behavior_stimuli_data_fixture, grating_images_dict, expected +): + templates = get_stimulus_templates( + behavior_stimuli_data_fixture, grating_images_dict=grating_images_dict + ) + + assert templates.image_set_name == "grating" + assert list(templates.keys()) == ["gratings_90.0"] + assert np.allclose( + templates["gratings_90.0"].warped, np.array([[1, 1], [1, 1]]) + ) + assert np.allclose( + templates["gratings_90.0"].unwarped, np.array([[2, 2], [2, 2]]) + ) # def test_get_images_dict(): @@ -171,91 +258,115 @@ def test_get_stimulus_templates_for_gratings(behavior_stimuli_data_fixture, # # convert_filepath_caseinsensitive prevents using any tempdirs/tempfiles -@pytest.mark.parametrize("behavior_stimuli_data_fixture, remove_stimuli, " - "starting_index, expected_metadata", [ - ({ - "grating_set_log": [] - }, [], 0, - { - 'image_category': {}, - 'image_name': {}, - 'image_set': {}, - 'phase': {}, - 'spatial_frequency': {}, - 'orientation': {}, - 'image_index': {} - }), - ({}, [], 0, - { - 'image_category': {0: 'grating'}, - 'image_name': {0: 'gratings_90.0'}, - 'image_set': {0: 'grating'}, - 'phase': {0: None}, - 'spatial_frequency': {0: None}, - 'orientation': {0: 90}, - 'image_index': {0: 0} - }), - ({'grating_phase': 0.5, - 'grating_spatial_frequency': 12}, [], 0, - { - 'image_category': {0: 'grating'}, - 'image_name': {0: 'gratings_90.0'}, - 'image_set': {0: 'grating'}, - 'phase': {0: 0.5}, - 'spatial_frequency': {0: 12}, - 'orientation': {0: 90}, - 'image_index': {0: 0} - }), - ({"grating_set_log": [ - ("Ori", 90.0, 3.5, 0), - ("Ori", 270.0, 15, 6) - ], - "grating_phase": 0.5, - "grating_spatial_frequency": 12}, - [], 12, - { - 'image_category': {0: 'grating', - 1: 'grating'}, - 'image_name': {0: 'gratings_90.0', - 1: 'gratings_270.0'}, - 'image_set': {0: 'grating', - 1: 'grating'}, - 'phase': {0: 0.5, 1: 0.5}, - 'spatial_frequency': {0: 12, 1: 12}, - 'orientation': {0: 90, 1: 270}, - 'image_index': {0: 12, 1: 13} - }), - ({}, ['grating'], 0, - { - 'image_category': {}, - 'image_name': {}, - 'image_set': {}, - 'phase': {}, - 'spatial_frequency': {}, - 'orientation': {}, - 'image_index': {} - }), - ({"grating_set_log": - [ - ("Ori", 90, 3, 0) - ], - "grating_phase": 0.5, - "grating_spatial_frequency": 0.25}, - [], 0, - { - 'image_category': {0: 'grating'}, - 'image_name': {0: 'gratings_90.0'}, - 'image_set': {0: 'grating'}, - 'phase': {0: 0.5}, - 'spatial_frequency': {0: 0.25}, - 'orientation': {0: 90}, - 'image_index': {0: 0} - }) - ], - indirect=['behavior_stimuli_data_fixture']) -def test_get_gratings_metadata(behavior_stimuli_data_fixture, remove_stimuli, - starting_index, expected_metadata): - stimuli = behavior_stimuli_data_fixture['items']['behavior']['stimuli'] +@pytest.mark.parametrize( + "behavior_stimuli_data_fixture, remove_stimuli, " + "starting_index, expected_metadata", + [ + ( + {"grating_set_log": []}, + [], + 0, + { + "image_category": {}, + "image_name": {}, + "image_set": {}, + "phase": {}, + "spatial_frequency": {}, + "orientation": {}, + "image_index": {}, + }, + ), + ( + {}, + [], + 0, + { + "image_category": {0: "grating"}, + "image_name": {0: "gratings_90.0"}, + "image_set": {0: "grating"}, + "phase": {0: None}, + "spatial_frequency": {0: None}, + "orientation": {0: 90}, + "image_index": {0: 0}, + }, + ), + ( + {"grating_phase": 0.5, "grating_spatial_frequency": 12}, + [], + 0, + { + "image_category": {0: "grating"}, + "image_name": {0: "gratings_90.0"}, + "image_set": {0: "grating"}, + "phase": {0: 0.5}, + "spatial_frequency": {0: 12}, + "orientation": {0: 90}, + "image_index": {0: 0}, + }, + ), + ( + { + "grating_set_log": [ + ("Ori", 90.0, 3.5, 0), + ("Ori", 270.0, 15, 6), + ], + "grating_phase": 0.5, + "grating_spatial_frequency": 12, + }, + [], + 12, + { + "image_category": {0: "grating", 1: "grating"}, + "image_name": {0: "gratings_90.0", 1: "gratings_270.0"}, + "image_set": {0: "grating", 1: "grating"}, + "phase": {0: 0.5, 1: 0.5}, + "spatial_frequency": {0: 12, 1: 12}, + "orientation": {0: 90, 1: 270}, + "image_index": {0: 12, 1: 13}, + }, + ), + ( + {}, + ["grating"], + 0, + { + "image_category": {}, + "image_name": {}, + "image_set": {}, + "phase": {}, + "spatial_frequency": {}, + "orientation": {}, + "image_index": {}, + }, + ), + ( + { + "grating_set_log": [("Ori", 90, 3, 0)], + "grating_phase": 0.5, + "grating_spatial_frequency": 0.25, + }, + [], + 0, + { + "image_category": {0: "grating"}, + "image_name": {0: "gratings_90.0"}, + "image_set": {0: "grating"}, + "phase": {0: 0.5}, + "spatial_frequency": {0: 0.25}, + "orientation": {0: 90}, + "image_index": {0: 0}, + }, + ), + ], + indirect=["behavior_stimuli_data_fixture"], +) +def test_get_gratings_metadata( + behavior_stimuli_data_fixture, + remove_stimuli, + starting_index, + expected_metadata, +): + stimuli = behavior_stimuli_data_fixture["items"]["behavior"]["stimuli"] for remove_stim in remove_stimuli: del stimuli[remove_stim] grating_meta = get_gratings_metadata(stimuli, start_idx=starting_index) @@ -263,152 +374,181 @@ def test_get_gratings_metadata(behavior_stimuli_data_fixture, remove_stimuli, assert grating_meta.to_dict() == expected_metadata -@pytest.mark.parametrize("behavior_stimuli_data_fixture, remove_stimuli, " - "expected_metadata", [ - ({'grating_phase': 10.0, - 'grating_spatial_frequency': 90.0, - "grating_set_log": [ - ("Ori", 90.0, 3.585, 0), - ("Ori", 180.0, 40.847, 6), - ("Ori", 270.0, 62.633, 12)] - }, - ['images'], - {'image_index': [0, 1, 2, 3], - 'image_name': ['gratings_90.0', - 'gratings_180.0', - 'gratings_270.0', 'omitted'], - 'image_category': ['grating', - 'grating', 'grating', - 'omitted'], - 'image_set': ['grating', 'grating', - 'grating', 'omitted'], - 'phase': [10, 10, 10, None], - 'spatial_frequency': [90, 90, - 90, None], - 'orientation': [90, 180, 270, None]}), - ({}, ['images', 'grating'], - {'image_index': [0], - 'image_name': ['omitted'], - 'image_category': ['omitted'], - 'image_set': ['omitted'], - 'phase': [None], - 'spatial_frequency': [None], - 'orientation': [None]})], - indirect=['behavior_stimuli_data_fixture']) -def test_get_stimulus_metadata(behavior_stimuli_data_fixture, - remove_stimuli, expected_metadata): +@pytest.mark.parametrize( + "behavior_stimuli_data_fixture, remove_stimuli, " "expected_metadata", + [ + ( + { + "grating_phase": 10.0, + "grating_spatial_frequency": 90.0, + "grating_set_log": [ + ("Ori", 90.0, 3.585, 0), + ("Ori", 180.0, 40.847, 6), + ("Ori", 270.0, 62.633, 12), + ], + }, + ["images"], + { + "image_index": [0, 1, 2, 3], + "image_name": [ + "gratings_90.0", + "gratings_180.0", + "gratings_270.0", + "omitted", + ], + "image_category": ["grating", "grating", "grating", "omitted"], + "image_set": ["grating", "grating", "grating", "omitted"], + "phase": [10, 10, 10, None], + "spatial_frequency": [90, 90, 90, None], + "orientation": [90, 180, 270, None], + }, + ), + ( + {}, + ["images", "grating"], + { + "image_index": [0], + "image_name": ["omitted"], + "image_category": ["omitted"], + "image_set": ["omitted"], + "phase": [None], + "spatial_frequency": [None], + "orientation": [None], + }, + ), + ], + indirect=["behavior_stimuli_data_fixture"], +) +def test_get_stimulus_metadata( + behavior_stimuli_data_fixture, remove_stimuli, expected_metadata +): for key in remove_stimuli: # do this because at current images are not tested and there's a # hard coded path that prevents testing when this is fixed this can # be removed. - del behavior_stimuli_data_fixture['items']['behavior']['stimuli'][key] + del behavior_stimuli_data_fixture["items"]["behavior"]["stimuli"][key] stimulus_metadata = get_stimulus_metadata(behavior_stimuli_data_fixture) expected_df = pd.DataFrame.from_dict(expected_metadata) - expected_df.set_index(['image_index'], inplace=True, drop=True) + expected_df.set_index(["image_index"], inplace=True, drop=True) assert stimulus_metadata.equals(expected_df) -@pytest.mark.parametrize("behavior_stimuli_time_fixture," - "behavior_stimuli_data_fixture, " - "expected", [ - ({"timestamp_count": 15, "time_step": 1}, - {"images_set_log": [ - ('Image', 'im065', 5, 0), - ('Image', 'im064', 25, 6) - ], - "images_draw_log": (([0] * 2 + [1] * 2 + - [0] * 3) * 2 + [0]), - "grating_set_log": [ - ("Ori", 90, 3.5, 0), - ("Ori", 270, 15, 6) - ], - "grating_draw_log": ( - ([0] + [1] * 3 + [0] * 3) - * 2 + [0])}, - {"duration": [3.0, 2.0, 3.0, 2.0], - "end_frame": [4.0, 4.0, 11.0, 11.0], - "image_name": [np.NaN, 'im065', np.NaN, - 'im064'], - "index": [2, 0, 3, 1], - "omitted": [False, False, False, False], - "orientation": [90, np.NaN, 270, np.NaN], - "start_frame": [1.0, 2.0, 8.0, 9.0], - "start_time": [1, 2, 8, 9], - "stop_time": [4, 4, 11, 11]}) - ], indirect=['behavior_stimuli_time_fixture', - 'behavior_stimuli_data_fixture']) -def test_get_stimulus_presentations(behavior_stimuli_time_fixture, - behavior_stimuli_data_fixture, - expected): +@pytest.mark.parametrize( + "behavior_stimuli_time_fixture," + "behavior_stimuli_data_fixture, " + "expected", + [ + ( + {"timestamp_count": 15, "time_step": 1}, + { + "images_set_log": [ + ("Image", "im065", 5, 0), + ("Image", "im064", 25, 6), + ], + "images_draw_log": (([0] * 2 + [1] * 2 + [0] * 3) * 2 + [0]), + "grating_set_log": [("Ori", 90, 3.5, 0), ("Ori", 270, 15, 6)], + "grating_draw_log": (([0] + [1] * 3 + [0] * 3) * 2 + [0]), + }, + { + "duration": [3.0, 2.0, 3.0, 2.0], + "end_frame": [4.0, 4.0, 11.0, 11.0], + "image_name": [np.NaN, "im065", np.NaN, "im064"], + "index": [2, 0, 3, 1], + "omitted": [False, False, False, False], + "orientation": [90, np.NaN, 270, np.NaN], + "start_frame": [1.0, 2.0, 8.0, 9.0], + "start_time": [1, 2, 8, 9], + "stop_time": [4, 4, 11, 11], + }, + ) + ], + indirect=[ + "behavior_stimuli_time_fixture", + "behavior_stimuli_data_fixture", + ], +) +def test_get_stimulus_presentations( + behavior_stimuli_time_fixture, behavior_stimuli_data_fixture, expected +): presentations_df = get_stimulus_presentations( - behavior_stimuli_data_fixture, - behavior_stimuli_time_fixture) + behavior_stimuli_data_fixture, behavior_stimuli_time_fixture + ) expected_df = pd.DataFrame.from_dict(expected) - expected_df.index.name = 'stimulus_presentations_id' + expected_df.index.name = "stimulus_presentations_id" pd.testing.assert_frame_equal(presentations_df, expected_df) -@pytest.mark.parametrize("behavior_stimuli_time_fixture," - "behavior_stimuli_data_fixture," - "expected_data", [ - ({"timestamp_count": 15, "time_step": 1}, - {"images_set_log": [ - ('Image', 'im065', 5, 0), - ('Image', 'im064', 25, 6) - ], - "images_draw_log": (([0] * 2 + [1] * 2 + - [0] * 3) * 2 + [0]), - "grating_set_log": [ - ("Ori", 90, 3.5, 0), - ("Ori", 270, 15, 6) - ], - "grating_draw_log": ( - ([0] + [1] * 3 + [0] * 3) - * 2 + [0])}, - {"orientation": [90, None, 270, None], - "image_name": [None, 'im065', None, 'im064'], - "frame": [1.0, 2.0, 8.0, 9.0], - "end_frame": [4.0, 4.0, 11.0, 11.0], - "time": [1.0, 2.0, 8.0, 9.0], - "duration": [3.0, 2.0, 3.0, 2.0], - "omitted": [False, False, False, False]}), - - # test case with images and a static grating - ({"timestamp_count": 30, "time_step": 1}, - {"images_set_log": [ - ('Image', 'im065', 5, 0), - ('Image', 'im064', 25, 6) - ], - "images_draw_log": (([0] * 2 + [1] * 2 + - [0] * 3) * 2 + [ - 0] * 16), - "grating_set_log": [ - ("Ori", 90, -1, 12), - # -1 because that element is not used - ("Ori", 270, -1, 24) - ], - "grating_draw_log": ( - [0] * 17 + [1] * 11 + [0, 0])}, - {"orientation": [None, None, 90, 270], - "image_name": ['im065', 'im064', None, None], - "frame": [2.0, 9.0, 17.0, 24.0], - "end_frame": [4.0, 11.0, 24.0, 28.0], - "time": [2.0, 9.0, 17.0, 24.0], - "duration": [2.0, 2.0, 7.0, 4.0], - "omitted": [False, False, False, False]}) - ], - indirect=["behavior_stimuli_time_fixture", - "behavior_stimuli_data_fixture"]) -def test_get_visual_stimuli_df(behavior_stimuli_time_fixture, - behavior_stimuli_data_fixture, - expected_data): - stimuli_df = get_visual_stimuli_df(behavior_stimuli_data_fixture, - behavior_stimuli_time_fixture) - stimuli_df = stimuli_df.drop('index', axis=1) +@pytest.mark.parametrize( + "behavior_stimuli_time_fixture," + "behavior_stimuli_data_fixture," + "expected_data", + [ + ( + {"timestamp_count": 15, "time_step": 1}, + { + "images_set_log": [ + ("Image", "im065", 5, 0), + ("Image", "im064", 25, 6), + ], + "images_draw_log": (([0] * 2 + [1] * 2 + [0] * 3) * 2 + [0]), + "grating_set_log": [("Ori", 90, 3.5, 0), ("Ori", 270, 15, 6)], + "grating_draw_log": (([0] + [1] * 3 + [0] * 3) * 2 + [0]), + }, + { + "orientation": [90, None, 270, None], + "image_name": [None, "im065", None, "im064"], + "frame": [1.0, 2.0, 8.0, 9.0], + "end_frame": [4.0, 4.0, 11.0, 11.0], + "time": [1.0, 2.0, 8.0, 9.0], + "duration": [3.0, 2.0, 3.0, 2.0], + "omitted": [False, False, False, False], + }, + ), + # test case with images and a static grating + ( + {"timestamp_count": 30, "time_step": 1}, + { + "images_set_log": [ + ("Image", "im065", 5, 0), + ("Image", "im064", 25, 6), + ], + "images_draw_log": ( + ([0] * 2 + [1] * 2 + [0] * 3) * 2 + [0] * 16 + ), + "grating_set_log": [ + ("Ori", 90, -1, 12), + # -1 because that element is not used + ("Ori", 270, -1, 24), + ], + "grating_draw_log": ([0] * 17 + [1] * 11 + [0, 0]), + }, + { + "orientation": [None, None, 90, 270], + "image_name": ["im065", "im064", None, None], + "frame": [2.0, 9.0, 17.0, 24.0], + "end_frame": [4.0, 11.0, 24.0, 28.0], + "time": [2.0, 9.0, 17.0, 24.0], + "duration": [2.0, 2.0, 7.0, 4.0], + "omitted": [False, False, False, False], + }, + ), + ], + indirect=[ + "behavior_stimuli_time_fixture", + "behavior_stimuli_data_fixture", + ], +) +def test_get_visual_stimuli_df( + behavior_stimuli_time_fixture, behavior_stimuli_data_fixture, expected_data +): + stimuli_df = get_visual_stimuli_df( + behavior_stimuli_data_fixture, behavior_stimuli_time_fixture + ) + stimuli_df = stimuli_df.drop("index", axis=1) expected_df = pd.DataFrame.from_dict(expected_data) pd.testing.assert_frame_equal(stimuli_df, expected_df) @@ -416,49 +556,48 @@ def test_get_visual_stimuli_df(behavior_stimuli_time_fixture, def test_is_change_event_no_change(): """Test case for no change""" - stimulus_presentations = pd.DataFrame({ - 'image_name': ['A', 'A', 'A'], - 'omitted': [False, False, False] - }) + stimulus_presentations = pd.DataFrame( + {"image_name": ["A", "A", "A"], "omitted": [False, False, False]} + ) obtained = is_change_event(stimulus_presentations=stimulus_presentations) - expected = pd.Series([False, False, False], name='is_change') + expected = pd.Series([False, False, False], name="is_change") pd.testing.assert_series_equal(obtained, expected) def test_is_change_event_all_change(): """Test case for all change""" - stimulus_presentations = pd.DataFrame({ - 'image_name': ['A', 'B', 'C'], - 'omitted': [False, False, False] - }) + stimulus_presentations = pd.DataFrame( + {"image_name": ["A", "B", "C"], "omitted": [False, False, False]} + ) obtained = is_change_event(stimulus_presentations=stimulus_presentations) - expected = pd.Series([False, True, True], name='is_change') + expected = pd.Series([False, True, True], name="is_change") pd.testing.assert_series_equal(obtained, expected) def test_is_change_omission(): """Test case for single omission""" - stimulus_presentations = pd.DataFrame({ - 'image_name': ['A', 'B', 'C'], - 'omitted': [False, True, False] - }) + stimulus_presentations = pd.DataFrame( + {"image_name": ["A", "B", "C"], "omitted": [False, True, False]} + ) obtained = is_change_event(stimulus_presentations=stimulus_presentations) - expected = pd.Series([False, False, True], name='is_change') + expected = pd.Series([False, False, True], name="is_change") pd.testing.assert_series_equal(obtained, expected) def test_is_change_mult_omission(): """Test case for multiple omission""" - stimulus_presentations = pd.DataFrame({ - 'image_name': ['A', 'B', 'C', 'D'], - 'omitted': [False, True, True, False] - }) + stimulus_presentations = pd.DataFrame( + { + "image_name": ["A", "B", "C", "D"], + "omitted": [False, True, True, False], + } + ) obtained = is_change_event(stimulus_presentations=stimulus_presentations) - expected = pd.Series([False, False, False, True], name='is_change') + expected = pd.Series([False, False, False, True], name="is_change") pd.testing.assert_series_equal(obtained, expected) @@ -467,56 +606,58 @@ def test_compute_active(): presentations table. """ stimulus_presentations = pd.DataFrame( - data={'start_time': [1.1, 2.1, 3, 4, 5, 6, 7, 8], - 'image_name': ['A', 'B', None, None, 'A', 'A', 'A', 'B'], - 'stimulus_block': [0, 0, 1, 1, 2, 2, 3, 3]} - ) - trials = pd.DataFrame({ - 'start_time': [1., 2.], - 'stop_time': [2., 3.] - }) + data={ + "start_time": [1.1, 2.1, 3, 4, 5, 6, 7, 8], + "image_name": ["A", "B", None, None, "A", "A", "A", "B"], + "stimulus_block": [0, 0, 1, 1, 2, 2, 3, 3], + } + ) + trials = pd.DataFrame({"start_time": [1.0, 2.0], "stop_time": [2.0, 3.0]}) expected = pd.Series( - name='active', - data=[True, True, False, False, - False, False, False, False], + name="active", + data=[True, True, False, False, False, False, False, False], index=stimulus_presentations.index, - dtype='bool') + dtype="bool", + ) stimulus_presentations = add_active_flag(stimulus_presentations, trials) - pd.testing.assert_series_equal(stimulus_presentations['active'], - expected) + pd.testing.assert_series_equal(stimulus_presentations["active"], expected) def test_compute_trials_id_for_stimulus(): - """Test that a set of trials maps onto a stimulus table as expected. - """ + """Test that a set of trials maps onto a stimulus table as expected.""" stimulus_presentations = pd.DataFrame( - data={'start_time': [1.1, 2.1, 3, 4, 5, 6, 7, 8], - 'image_name': ['A', 'B', None, None, 'A', 'A', 'A', 'B'], - 'stimulus_block': [0, 0, 1, 1, 2, 2, 3, 3], - 'active': [True, True, False, False, - False, False, False, False]} - ) - trials = pd.DataFrame({ - 'start_time': [1., 2.], - 'stop_time': [2., 3.], - }) + data={ + "start_time": [1.1, 2.1, 3, 4, 5, 6, 7, 8], + "image_name": ["A", "B", None, None, "A", "A", "A", "B"], + "stimulus_block": [0, 0, 1, 1, 2, 2, 3, 3], + "active": [True, True, False, False, False, False, False, False], + } + ) + trials = pd.DataFrame( + { + "start_time": [1.0, 2.0], + "stop_time": [2.0, 3.0], + } + ) expected_trials_id = pd.Series( - name='trials_id', + name="trials_id", data=[0, 1, -99, -99, -99, -99, 0, 1], index=stimulus_presentations.index, - dtype='int') - output_trials_ids = compute_trials_id_for_stimulus(stimulus_presentations, - trials) - pd.testing.assert_series_equal(output_trials_ids, - expected_trials_id) + dtype="int", + ) + output_trials_ids = compute_trials_id_for_stimulus( + stimulus_presentations, trials + ) + pd.testing.assert_series_equal(output_trials_ids, expected_trials_id) # Test with explicit active block. - stimulus_presentations['active'] = np.array( - [True, True, False, False, False, False, False, False]) - output_trials_ids = compute_trials_id_for_stimulus(stimulus_presentations, - trials) - pd.testing.assert_series_equal(output_trials_ids, - expected_trials_id) + stimulus_presentations["active"] = np.array( + [True, True, False, False, False, False, False, False] + ) + output_trials_ids = compute_trials_id_for_stimulus( + stimulus_presentations, trials + ) + pd.testing.assert_series_equal(output_trials_ids, expected_trials_id) def test_compute_is_shame_change(): @@ -524,70 +665,84 @@ def test_compute_is_shame_change(): presentations table. """ stimulus_presentations = pd.DataFrame( - data={'start_time': [1.1, 2.1, 3, 4, 5, 6, 7, 8], - 'image_name': ['A', 'B', None, None, 'A', 'A', 'A', 'B'], - 'stimulus_block': [0, 0, 1, 1, 2, 2, 3, 3], - 'active': [True, True, False, False, - False, False, False, False], - 'trials_id': [0, 1, -99, -99, -99, -99, 0, 1], - 'start_frame': [0, 10, 20, 30, 40, 50, 60, 70]} - ) - trials = pd.DataFrame({ - 'start_time': [1., 2.], - 'stop_time': [2., 3.], - 'catch': [True, False], - 'change_frame': [10, -99] - }) + data={ + "start_time": [1.1, 2.1, 3, 4, 5, 6, 7, 8], + "image_name": ["A", "B", None, None, "A", "A", "A", "B"], + "stimulus_block": [0, 0, 1, 1, 2, 2, 3, 3], + "active": [True, True, False, False, False, False, False, False], + "trials_id": [0, 1, -99, -99, -99, -99, 0, 1], + "start_frame": [0, 10, 20, 30, 40, 50, 60, 70], + } + ) + trials = pd.DataFrame( + { + "start_time": [1.0, 2.0], + "stop_time": [2.0, 3.0], + "catch": [True, False], + "change_frame": [10, -99], + } + ) expected = pd.Series( - name='is_sham_change', - data=[False, True, False, False, - False, False, False, True], + name="is_sham_change", + data=[False, True, False, False, False, False, False, True], index=stimulus_presentations.index, - dtype='bool') - stimulus_presentations = compute_is_sham_change(stimulus_presentations, - trials) - pd.testing.assert_series_equal(stimulus_presentations['is_sham_change'], - expected) + dtype="bool", + ) + stimulus_presentations = compute_is_sham_change( + stimulus_presentations, trials + ) + pd.testing.assert_series_equal( + stimulus_presentations["is_sham_change"], expected + ) def test_produce_stimulus_block_names(): - """Test that a set of trials maps onto a stimulus table as expected. - """ + """Test that a set of trials maps onto a stimulus table as expected.""" stimulus_presentations = pd.DataFrame( - data={'stimulus_block': [0, 1, 2, 3]}, + data={"stimulus_block": [0, 1, 2, 3]}, ) expected_stimulus_block_names = pd.Series( - name='stimulus_block_name', - data=['initial_gray_screen_5min', - 'change_detection_behavior', - 'post_behavior_gray_screen_5min', - 'natural_movie_one'], - index=stimulus_presentations.index) - output_stim_names = produce_stimulus_block_names(stimulus_presentations, - 'OPHYS_1', - 'VisualBehaviorTask1B') - assert np.array_equal(output_stim_names['stimulus_block_name'].values, - expected_stimulus_block_names.values) + name="stimulus_block_name", + data=[ + "initial_gray_screen_5min", + "change_detection_behavior", + "post_behavior_gray_screen_5min", + "natural_movie_one", + ], + index=stimulus_presentations.index, + ) + output_stim_names = produce_stimulus_block_names( + stimulus_presentations, "OPHYS_1", "VisualBehaviorTask1B" + ) + assert np.array_equal( + output_stim_names["stimulus_block_name"].values, + expected_stimulus_block_names.values, + ) # Test with passive active block. expected_stimulus_block_names = pd.Series( - name='stimulus_block_name', - data=['initial_gray_screen_5min', - 'change_detection_passive', - 'post_behavior_gray_screen_5min', - 'natural_movie_one'], - index=stimulus_presentations.index) - output_stim_names = produce_stimulus_block_names(stimulus_presentations, - 'OPHYS_1_passive', - 'VisualBehaviorTask1B') - assert np.array_equal(output_stim_names['stimulus_block_name'].values, - expected_stimulus_block_names.values) + name="stimulus_block_name", + data=[ + "initial_gray_screen_5min", + "change_detection_passive", + "post_behavior_gray_screen_5min", + "natural_movie_one", + ], + index=stimulus_presentations.index, + ) + output_stim_names = produce_stimulus_block_names( + stimulus_presentations, "OPHYS_1_passive", "VisualBehaviorTask1B" + ) + assert np.array_equal( + output_stim_names["stimulus_block_name"].values, + expected_stimulus_block_names.values, + ) # Test with wrong project_code. stimulus_presentations = pd.DataFrame( - data={'stimulus_block': [0, 1, 2, 3]}, + data={"stimulus_block": [0, 1, 2, 3]}, + ) + output_stim_names = produce_stimulus_block_names( + stimulus_presentations, "OPHYS_1_passive", "NotAProject" ) - output_stim_names = produce_stimulus_block_names(stimulus_presentations, - 'OPHYS_1_passive', - 'NotAProject') - assert 'stimulus_block_name' not in output_stim_names.columns + assert "stimulus_block_name" not in output_stim_names.columns