def from_nwb(cls, nwbfile: NWBFile) -> "Presentations": # Note: using NwbApi class because ecephys uses this method # TODO figure out how behavior and ecephys can share this method nwbapi = NwbApi.from_nwbfile(nwbfile=nwbfile) df = nwbapi.get_stimulus_presentations() df['is_change'] = is_change_event(stimulus_presentations=df) df = cls._postprocess(presentations=df, fill_omitted_values=False) return Presentations(presentations=df)
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] }) obtained = is_change_event(stimulus_presentations=stimulus_presentations) expected = pd.Series([False, False, False, True], name='is_change') pd.testing.assert_series_equal(obtained, expected)
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] }) obtained = is_change_event(stimulus_presentations=stimulus_presentations) expected = pd.Series([False, False, False], name='is_change') pd.testing.assert_series_equal(obtained, expected)
def get_stimulus_presentations(self) -> pd.DataFrame: """Get stimulus presentation data. NOTE: Uses timestamps that do not account for monitor delay. :returns: pd.DataFrame -- Table whose rows are stimulus presentations (i.e. a given image, for a given duration, typically 250 ms) and whose columns are presentation characteristics. """ stimulus_timestamps = self.get_stimulus_timestamps() data = self._behavior_stimulus_file() raw_stim_pres_df = get_stimulus_presentations(data, stimulus_timestamps) # Fill in nulls for image_name # This makes two assumptions: # 1. Nulls in `image_name` should be "gratings_<orientation>" # 2. Gratings are only present (or need to be fixed) when all # values for `image_name` are null. if pd.isnull(raw_stim_pres_df["image_name"]).all(): if ~pd.isnull(raw_stim_pres_df["orientation"]).all(): raw_stim_pres_df["image_name"] = ( raw_stim_pres_df["orientation"].apply( lambda x: f"gratings_{x}")) else: raise ValueError("All values for 'orentation' and 'image_name'" " are null.") stimulus_metadata_df = get_stimulus_metadata(data) idx_name = raw_stim_pres_df.index.name stimulus_index_df = (raw_stim_pres_df.reset_index().merge( stimulus_metadata_df.reset_index(), on=["image_name"]).set_index(idx_name)) stimulus_index_df = (stimulus_index_df[[ "image_set", "image_index", "start_time", "phase", "spatial_frequency" ]].rename(columns={ "start_time": "timestamps" }).sort_index().set_index("timestamps", drop=True)) stim_pres_df = raw_stim_pres_df.merge(stimulus_index_df, left_on="start_time", right_index=True, how="left") if len(raw_stim_pres_df) != len(stim_pres_df): raise ValueError("Length of `stim_pres_df` should not change after" f" merge; was {len(raw_stim_pres_df)}, now " f" {len(stim_pres_df)}.") stim_pres_df['is_change'] = is_change_event( stimulus_presentations=stim_pres_df) # Sort columns then drop columns which contain only all NaN values return stim_pres_df[sorted(stim_pres_df)].dropna(axis=1, how='all')
def from_stimulus_file( cls, stimulus_file: StimulusFile, stimulus_timestamps: StimulusTimestamps, limit_to_images: Optional[List] = None) -> "Presentations": """Get stimulus presentation data. :param stimulus_file :param limit_to_images Only return images given by these image names :param stimulus_timestamps :returns: pd.DataFrame -- Table whose rows are stimulus presentations (i.e. a given image, for a given duration, typically 250 ms) and whose columns are presentation characteristics. """ stimulus_timestamps = stimulus_timestamps.value data = stimulus_file.data raw_stim_pres_df = get_stimulus_presentations(data, stimulus_timestamps) # Fill in nulls for image_name # This makes two assumptions: # 1. Nulls in `image_name` should be "gratings_<orientation>" # 2. Gratings are only present (or need to be fixed) when all # values for `image_name` are null. if pd.isnull(raw_stim_pres_df["image_name"]).all(): if ~pd.isnull(raw_stim_pres_df["orientation"]).all(): raw_stim_pres_df["image_name"] = ( raw_stim_pres_df["orientation"].apply( lambda x: f"gratings_{x}")) else: raise ValueError("All values for 'orentation' and 'image_name'" " are null.") stimulus_metadata_df = get_stimulus_metadata(data) idx_name = raw_stim_pres_df.index.name stimulus_index_df = (raw_stim_pres_df.reset_index().merge( stimulus_metadata_df.reset_index(), on=["image_name"]).set_index(idx_name)) stimulus_index_df = (stimulus_index_df[[ "image_set", "image_index", "start_time", "phase", "spatial_frequency" ]].rename(columns={ "start_time": "timestamps" }).sort_index().set_index("timestamps", drop=True)) stim_pres_df = raw_stim_pres_df.merge(stimulus_index_df, left_on="start_time", right_index=True, how="left") if len(raw_stim_pres_df) != len(stim_pres_df): raise ValueError("Length of `stim_pres_df` should not change after" f" merge; was {len(raw_stim_pres_df)}, now " f" {len(stim_pres_df)}.") stim_pres_df['is_change'] = is_change_event( stimulus_presentations=stim_pres_df) # Sort columns then drop columns which contain only all NaN values stim_pres_df = \ stim_pres_df[sorted(stim_pres_df)].dropna(axis=1, how='all') if limit_to_images is not None: stim_pres_df = \ stim_pres_df[stim_pres_df['image_name'].isin(limit_to_images)] stim_pres_df.index = pd.Int64Index(range(stim_pres_df.shape[0]), name=stim_pres_df.index.name) stim_pres_df = cls._postprocess(presentations=stim_pres_df) return Presentations(presentations=stim_pres_df)
def get_stimulus_presentations(self) -> pd.DataFrame: df = super().get_stimulus_presentations() df['is_change'] = is_change_event(stimulus_presentations=df) return df