Source code for allensdk.brain_observatory.ecephys.stimulus_analysis.natural_scenes

import numpy as np
import pandas as pd
import logging
import warnings

from .stimulus_analysis import StimulusAnalysis


warnings.simplefilter(action='ignore', category=FutureWarning)


logger = logging.getLogger(__name__)


[docs]class NaturalScenes(StimulusAnalysis): """ A class for computing single-unit metrics from the natural scenes stimulus of an ecephys session NWB file. To use, pass in a EcephysSession object:: session = EcephysSession.from_nwb_path('/path/to/my.nwb') ns_analysis = NaturalScenes(session) or, alternatively, pass in the file path:: ns_analysis = NaturalScenes('/path/to/my.nwb') You can also pass in a unit filter dictionary which will only select units with certain properties. For example to get only those units which are on probe C and found in the VISp area:: ns_analysis = NaturalScenes(session, filter={'location': 'probeC', 'ecephys_structure_acronym': 'VISp'}) To get a table of the individual unit metrics ranked by unit ID:: metrics_table_df = ns_analysis.metrics() """ def __init__(self, ecephys_session, col_image='frame', trial_duration=0.25, **kwargs): super(NaturalScenes, self).__init__(ecephys_session, trial_duration=trial_duration, **kwargs) self._images = None self._number_images = None self._images_nonblank = None self._number_nonblank = None # does not include Image number = -1. self._mean_sweep_events = None self._response_events = None self._response_trials = None self._metrics = None self._col_image = col_image if self._params is not None: self._params = self._params.get('natural_scenes', {}) self._stimulus_key = self._params.get('stimulus_key', None) # Overwrites parent value with argvars else: self._params = {} @property def name(self): return 'Natural Scenes' @property def images(self): """ Array of iamge labels """ if self._images is None: self._get_stim_table_stats() return self._images @property def images_nonblank(self): if self._images_nonblank is None: self._get_stim_table_stats() return self._images_nonblank @property def frames(self): # Required to deal with naming difference between NWB 1 and 2 return self.images @property def number_images(self): """ Number of images shown """ if self._images is None: self._get_stim_table_stats() return self._number_images @property def number_nonblank(self): """ Number of images shown (excluding blank condition) """ if self._number_nonblank is None: self._get_stim_table_stats() return self._number_nonblank @property def null_condition(self): """ Stimulus condition ID for null (blank) stimulus """ return self.stimulus_conditions[self.stimulus_conditions[self._col_image] == -1].index @property def METRICS_COLUMNS(self): return [('pref_image_ns', np.uint64), ('image_selectivity_ns', np.float64), ('firing_rate_ns', np.float64), ('fano_ns', np.float64), ('time_to_peak_ns', np.float64), ('lifetime_sparseness_ns', np.float64), ('run_pval_ns', np.float64), ('run_mod_ns', np.float64)] @property def metrics(self): if self._metrics is None: logger.info('Calculating metrics for ' + self.name) unit_ids = self.unit_ids metrics_df = self.empty_metrics_table() if len(self.stim_table) > 0: logger.info('Calculating metrics for ' + self.name) metrics_df['pref_image_ns'] = [self._get_preferred_condition(unit) for unit in unit_ids] metrics_df['pref_images_multi_ns'] = [ self._check_multiple_pref_conditions(unit_id, self._col_image, self.images_nonblank) for unit_id in unit_ids ] metrics_df['image_selectivity_ns'] = [self._get_image_selectivity(unit) for unit in unit_ids] metrics_df['firing_rate_ns'] = [self._get_overall_firing_rate(unit) for unit in unit_ids] metrics_df['fano_ns'] = [self._get_fano_factor(unit, self._get_preferred_condition(unit)) for unit in unit_ids] metrics_df['time_to_peak_ns'] = [self._get_time_to_peak(unit, self._get_preferred_condition(unit)) for unit in unit_ids] metrics_df['lifetime_sparseness_ns'] = [self._get_lifetime_sparseness(unit) for unit in unit_ids] metrics_df.loc[:, ['run_pval_ns', 'run_mod_ns']] = [ self._get_running_modulation(unit, self._get_preferred_condition(unit)) for unit in unit_ids] self._metrics = metrics_df return self._metrics
[docs] @classmethod def known_stimulus_keys(cls): return ['natural_scenes', 'Natural_Images', 'Natural Images']
def _get_stim_table_stats(self): """ Extract image labels from the stimulus table """ self._images = np.sort(self.stimulus_conditions[self._col_image].unique()).astype(np.int64) self._number_images = len(self._images) self._images_nonblank = self._images[self._images >= 0] self._number_nonblank = len(self._images_nonblank) def _get_image_selectivity(self, unit_id, num_steps=1000): """ Calculate the image selectivity for a given unit using spike means at every image""" unit_stats = self.conditionwise_statistics.loc[unit_id].drop(index=self.null_condition) return image_selectivity(unit_stats['spike_mean'].values, num_steps=num_steps)
[docs]def image_selectivity(spike_means, num_steps=1000): """Quantifies how selective a cell is for images, based on Quian Quiroga et al., 2007. A value of 0 indicates the cell responds the same no mater what the image. While if the neuron only responds to a single image it will have a selectivity of 1 - 2/N (1.0 and N goes to inf). Parameters ---------- spike_means : array of floats Averaged spiking responses to a series of images for a given neuron num_steps : int Number of threshold values used to build response distribution (default to 1000 as in Quian paper) Returns ------- selectivity : float selectivity of neuron to images """ if spike_means.size < 2 or num_steps < 2: # What is the selectivity of none of 0 spikes (by definition should be 0 and 1) return np.nan # Essentially creates a cumulative distribution function of responses at a given set of thresholds, finds the # area under the response distribution and normalizes between 0 and 1. fmin = spike_means.min() fmax = spike_means.max() if fmin == fmax: # A uniform response of none for each image, make sure to return 0 return 0.0 j = np.arange(num_steps) thresh = fmin + j*((fmax - fmin) / num_steps) rtj = [np.mean(spike_means > t) for t in thresh] return 1 - (2 * np.mean(rtj))