def test_instantaneous_rate_regression_245(self): # This test makes sure that the correct kernel width is chosen when # selecting 'auto' as kernel spiketrain = neo.SpikeTrain( range(1, 30) * pq.ms, t_start=0*pq.ms, t_stop=30*pq.ms) # This is the correct procedure to attain the kernel: first, the result # of sskernel retrieves the kernel bandwidth of an optimal Gaussian # kernel in terms of its standard deviation sigma, then uses this value # directly in the function for creating the Gaussian kernel kernel_width_sigma = es.sskernel( spiketrain.magnitude, tin=None, bootstrap=False)['optw'] kernel = kernels.GaussianKernel(kernel_width_sigma * spiketrain.units) result_target = es.instantaneous_rate( spiketrain, 10*pq.ms, kernel=kernel) # Here, we check if the 'auto' argument leads to the same operation. In # the regression, it was incorrectly assumed that the sskernel() # function returns the actual bandwidth of the kernel, which is defined # as approximately bandwidth = sigma * 5.5 = sigma * (2 * 2.75). # factor 2.0 connects kernel width with its half width, # factor 2.7 connects half width of Gaussian distribution with # 99% probability mass with its standard deviation. result_automatic = es.instantaneous_rate( spiketrain, 10*pq.ms, kernel='auto') assert_array_almost_equal(result_target, result_automatic)
def optimal_kernel(st): width_sigma = None if len(st) > 0: width_sigma = optimal_kernel_bandwidth(st.magnitude, times=None, bootstrap=False)['optw'] if width_sigma is None: raise ValueError("Unable to calculate optimal kernel width for " "instantaneous rate from input data.") return kernels.GaussianKernel(width_sigma * st.units)
def test_gaussian(self): def evaluate_old(t): t_units = t.units t = t.magnitude sigma = kernel.sigma.rescale(t_units).magnitude kernel_pdf = (1.0 / (math.sqrt(2.0 * math.pi) * sigma)) * np.exp( -0.5 * (t / sigma) ** 2) kernel_pdf = pq.Quantity(kernel_pdf, units=1 / t_units) return kernel_pdf for invert in (False, True): kernel = kernels.GaussianKernel(self.sigma, invert=invert) assert_array_almost_equal(kernel(self.time_input), evaluate_old(self.time_input))
def test_instantaneous_rate_grows_with_sampling_period(self): np.random.seed(0) rate_expected = 10 * pq.Hz spiketrain = homogeneous_poisson_process(rate=rate_expected, t_stop=10 * pq.s) kernel = kernels.GaussianKernel(sigma=100 * pq.ms) rates_mean = [] for sampling_period in np.linspace(1, 1000, num=10) * pq.ms: with self.subTest(sampling_period=sampling_period): rate = statistics.instantaneous_rate( spiketrain, sampling_period=sampling_period, kernel=kernel) rates_mean.append(rate.mean()) # rate means are greater or equal the expected rate assert_array_less(rate_expected, rates_mean) # check sorted self.assertTrue(np.all(rates_mean[:-1] < rates_mean[1:]))
def convert_one_population_to_rates(self, recordings_index, trial_index, brain_area): path = self.all_data_path + '/' + self.selected_recordings[ recordings_index] trials = np.load(path + '/' + 'trials.intervals.npy') spike_times_lst = self.get_spikes_of_one_population( recordings_index, brain_area) rates_lst = [] spk_tr_lst = [] for spk_tms_one_neuron in spike_times_lst: spks_range = np.bitwise_and( spk_tms_one_neuron >= trials[trial_index][0], spk_tms_one_neuron <= trials[trial_index][1]) subset = spk_tms_one_neuron[spks_range] #Create elephant SpikeTrain object spk_tr = neo.SpikeTrain(subset * pq.s, t_start=trials[trial_index][0] * pq.s, t_stop=trials[trial_index][1] * pq.s) #plt.eventplot(spk_tr) #plt.show() kernel = kernels.GaussianKernel(sigma=0.1 * pq.s, invert=True) #sampling_rate the same as behavior r = instantaneous_rate(spk_tr, t_start=trials[trial_index][0] * pq.s, t_stop=trials[trial_index][1] * pq.s, sampling_period=0.02524578 * pq.s, kernel=kernel) #cutoff=5.0) binned_spk_tr = conv.BinnedSpikeTrain( spk_tr, binsize=0.02524578 * pq.s, t_start=trials[trial_index][0] * pq.s) binned_spk_tr = binned_spk_tr.to_array() spk_tr_lst.append(binned_spk_tr) rates_lst.append(r.flatten()) rates_lst = np.array(rates_lst) spk_tr_lst = np.array(spk_tr_lst) print(spk_tr_lst) #print(rates_lst.shape) return rates_lst, spk_tr_lst
def correlate_to_stim(sp_neo, var, kernel_sigmas, mode='g', plot_tgl=False): ''' Calculate the correlation between a spike train and an analog signal. Smooths the observed spike train with a user defined kernel in order to get a continuous rate estimate :param sp_neo: a neo spiketrain :param var: a 1D numpy array, neo analog signal, or quantity to correlate the spike rate with :param kernel_sigmas: list or numpy array of sigma values defining the kernel to smooth the spike train :param mode: type of kernel to smooth the spike train with ['g': gaussian,'b'/'r': box or rectangular] :return corr_: A numpy array of correlation values between the spiketrain and desired variable. Each entry corresponds to a different smoothing parameter as indicated in 'kernel_sigmas' :return kernel_sigmas: The numpy array of kernel sigma values used ''' # map var to a numpy array if needed if type(var) == neo.core.AnalogSignal or type( var) == quantities.quantity.Quantity: var = var.magnitude # init correlation output corr_ = np.empty(kernel_sigmas.shape[0]) # loop over all sigma values for ii, sigma in enumerate(kernel_sigmas): # get the appropriate kernel with corresponding sigma if mode == 'b' or mode == 'r': kernel = kernels.RectangularKernel(sigma=sigma * pq.ms) elif mode == 'g': kernel = kernels.GaussianKernel(sigma=sigma * pq.ms) else: raise ValueError('Kernel mode not defined') r = instantaneous_rate(sp_neo, sampling_period=pq.ms, kernel=kernel) corr_[ii] = np.corrcoef(r.squeeze(), var)[0, 1] if plot_tgl: plt.plot(kernel_sigmas, corr_, 'k') return (corr_, kernel_sigmas)
def plot_one_trial_one_neuron(self, recordings_index, trial_index, neuron_index): ''' Plots spikes, rates and behavior over a specified trial and neuron. ''' path = self.all_data_path + '/' + self.selected_recordings[ recordings_index] #Neural data neuron_inds = np.load(path + '/' + 'spikes.clusters.npy') spk_tms = np.load(path + '/' + 'spikes.times.npy') trials = np.load(path + '/' + 'trials.intervals.npy') #Behavioral data mot_timestamps = np.load(path + '/' + 'face.timestamps.npy') mot_energy = np.load(path + '/' + 'face.motionEnergy.npy') spk_ids = np.where(neuron_inds == neuron_index) spk_tms_one_neuron = spk_tms[spk_ids] #Select spikes in the trial for the neuron that we care about spks_range = np.bitwise_and( spk_tms_one_neuron >= trials[trial_index][0], spk_tms_one_neuron <= trials[trial_index][1]) subset = spk_tms_one_neuron[spks_range] #Create elephant SpikeTrain object spk_tr = neo.SpikeTrain(subset * pq.s, t_start=trials[trial_index][0] * pq.s, t_stop=trials[trial_index][1] * pq.s) #print(spk_tr) print((trials[trial_index][1] - trials[trial_index][0])) #Plot spike train plt.eventplot(spk_tr) plt.title('Spike train for one trial. trial ' + str(trial_index) + ' ' + ', neuron: ' + str(neuron_index)) plt.show() #Plot instantaneous firing rate kernel = kernels.GaussianKernel(sigma=0.1 * pq.s, invert=True) #sampling_rate the same as behavior r = instantaneous_rate(spk_tr, t_start=trials[trial_index][0] * pq.s, t_stop=trials[trial_index][1] * pq.s, sampling_period=0.02524578 * pq.s, kernel=kernel) #cutoff=5.0) plt.plot(r) plt.title('Instantaneous rate for one trial') plt.show() print('r shape', r.shape) #Plot behavior motion energy beh_range = np.bitwise_and( mot_timestamps[:, 1] >= trials[trial_index][0], mot_timestamps[:, 1] <= trials[trial_index][1]) #print(np.where(beh_range==True)) #print(mot_timestamps[beh_range]) beh_subset = mot_energy[beh_range] plt.plot(mot_timestamps[beh_range][:, 1].flatten(), beh_subset) plt.title('Motion energy in trial') plt.show() print('beh shp', beh_subset.shape) rate = np.array(r).flatten() beh_subset_aligned = self.align_rate_and_behavior(beh_subset, rate).flatten() print('Correlation coefficient between rate and behavior: ' + str(np.corrcoef(beh_subset_aligned, rate)[0, 1]))
def instantaneous_rate(spiketrain, sampling_period, kernel='auto', cutoff=5.0, t_start=None, t_stop=None, trim=False): """ Estimates instantaneous firing rate by kernel convolution. Parameters ----------- spiketrain : 'neo.SpikeTrain' Neo object that contains spike times, the unit of the time stamps and t_start and t_stop of the spike train. sampling_period : Time Quantity Time stamp resolution of the spike times. The same resolution will be assumed for the kernel kernel : string 'auto' or callable object of :class:`Kernel` from module 'kernels.py'. Currently implemented kernel forms are rectangular, triangular, epanechnikovlike, gaussian, laplacian, exponential, and alpha function. Example: kernel = kernels.RectangularKernel(sigma=10*ms, invert=False) The kernel is used for convolution with the spike train and its standard deviation determines the time resolution of the instantaneous rate estimation. Default: 'auto'. In this case, the optimized kernel width for the rate estimation is calculated according to [1] and with this width a gaussian kernel is constructed. Automatized calculation of the kernel width is not available for other than gaussian kernel shapes. cutoff : float This factor determines the cutoff of the probability distribution of the kernel, i.e., the considered width of the kernel in terms of multiples of the standard deviation sigma. Default: 5.0 t_start : Time Quantity (optional) Start time of the interval used to compute the firing rate. If None assumed equal to spiketrain.t_start Default: None t_stop : Time Quantity (optional) End time of the interval used to compute the firing rate (included). If None assumed equal to spiketrain.t_stop Default: None trim : bool if False, the output of the Fast Fourier Transformation being a longer vector than the input vector by the size of the kernel is reduced back to the original size of the considered time interval of the spiketrain using the median of the kernel. if True, only the region of the convolved signal is returned, where there is complete overlap between kernel and spike train. This is achieved by reducing the length of the output of the Fast Fourier Transformation by a total of two times the size of the kernel, and t_start and t_stop are adjusted. Default: False Returns ------- rate : neo.AnalogSignal Contains the rate estimation in unit hertz (Hz). Has a property 'rate.times' which contains the time axis of the rate estimate. The unit of this property is the same as the resolution that is given via the argument 'sampling_period' to the function. Raises ------ TypeError: If `spiketrain` is not an instance of :class:`SpikeTrain` of Neo. If `sampling_period` is not a time quantity. If `kernel` is neither instance of :class:`Kernel` or string 'auto'. If `cutoff` is neither float nor int. If `t_start` and `t_stop` are neither None nor a time quantity. If `trim` is not bool. ValueError: If `sampling_period` is smaller than zero. Example -------- kernel = kernels.AlphaKernel(sigma = 0.05*s, invert = True) rate = instantaneous_rate(spiketrain, sampling_period = 2*ms, kernel) References ---------- ..[1] H. Shimazaki, S. Shinomoto, J Comput Neurosci (2010) 29:171–182. """ # Checks of input variables: if not isinstance(spiketrain, SpikeTrain): raise TypeError( "spiketrain must be instance of :class:`SpikeTrain` of Neo!\n" " Found: %s, value %s" % (type(spiketrain), str(spiketrain))) if not (isinstance(sampling_period, pq.Quantity) and sampling_period.dimensionality.simplified == pq.Quantity( 1, "s").dimensionality): raise TypeError("The sampling period must be a time quantity!\n" " Found: %s, value %s" % (type(sampling_period), str(sampling_period))) if sampling_period.magnitude < 0: raise ValueError("The sampling period must be larger than zero.") if kernel == 'auto': kernel_width = sskernel(spiketrain.magnitude, tin=None, bootstrap=True)['optw'] unit = spiketrain.units sigma = 1 / (2.0 * 2.7) * kernel_width * unit # factor 2.0 connects kernel width with its half width, # factor 2.7 connects half width of Gaussian distribution with # 99% probability mass with its standard deviation. kernel = kernels.GaussianKernel(sigma) elif not isinstance(kernel, kernels.Kernel): raise TypeError("kernel must be either instance of :class:`Kernel` " "or the string 'auto'!\n" " Found: %s, value %s" % (type(kernel), str(kernel))) if not (isinstance(cutoff, float) or isinstance(cutoff, int)): raise TypeError("cutoff must be float or integer!") if not (t_start is None or (isinstance(t_start, pq.Quantity) and t_start.dimensionality.simplified == pq.Quantity(1, "s").dimensionality)): raise TypeError("t_start must be a time quantity!") if not (t_stop is None or (isinstance(t_stop, pq.Quantity) and t_stop.dimensionality.simplified == pq.Quantity(1, "s").dimensionality)): raise TypeError("t_stop must be a time quantity!") if not (isinstance(trim, bool)): raise TypeError("trim must be bool!") # main function: units = pq.CompoundUnit("%s*s" % str(sampling_period.rescale('s').magnitude)) spiketrain = spiketrain.rescale(units) if t_start is None: t_start = spiketrain.t_start else: t_start = t_start.rescale(spiketrain.units) if t_stop is None: t_stop = spiketrain.t_stop else: t_stop = t_stop.rescale(spiketrain.units) time_vector = np.zeros(int((t_stop - t_start)) + 1) spikes_slice = spiketrain.time_slice(t_start, t_stop) \ if len(spiketrain) else np.array([]) for spike in spikes_slice: index = int((spike - t_start)) time_vector[index] += 1 if cutoff < kernel.min_cutoff: cutoff = kernel.min_cutoff warnings.warn("The width of the kernel was adjusted to a minimally " "allowed width.") t_arr = np.arange( -cutoff * kernel.sigma.rescale(units).magnitude, cutoff * kernel.sigma.rescale(units).magnitude + sampling_period.rescale(units).magnitude, sampling_period.rescale(units).magnitude) * units r = scipy.signal.fftconvolve(time_vector, kernel(t_arr).rescale(pq.Hz).magnitude, 'full') if np.any(r < 0): warnings.warn("Instantaneous firing rate approximation contains " "negative values, possibly caused due to machine " "precision errors.") if not trim: r = r[kernel.median_index(t_arr):-(kernel(t_arr).size - kernel.median_index(t_arr))] elif trim: r = r[2 * kernel.median_index(t_arr):-2 * (kernel(t_arr).size - kernel.median_index(t_arr))] t_start += kernel.median_index(t_arr) * spiketrain.units t_stop -= (kernel(t_arr).size - kernel.median_index(t_arr)) * spiketrain.units rate = neo.AnalogSignal(signal=r.reshape(r.size, 1), sampling_period=sampling_period, units=pq.Hz, t_start=t_start, t_stop=t_stop) return rate
] same_len_labels = [ str(label[0]) + '0' + str(label[1]) if len(label) < 3 else label for label in labels ] ordered_indices_for_ids = np.array(np.argsort(same_len_labels)) ordered_labels = np.array(labels)[ordered_indices_for_ids] print(1 / sampling_frequency * pq.s) sts = retrieve_spiketimes(file) label_spiketimes_map = make_label_spiketimes_map(ordered_labels, sts, dead_channels) spiketimes = label_spiketimes_map['L2'] / sampling_frequency spiketimes = spiketimes[spiketimes <= 900] kernel = kernels.GaussianKernel(sigma=1 * pq.s) # plt.plot(kernel) # plt.savefig('/home/lisa_ruth/lab_seminar/kernel.png') # spike_object = neo.SpikeTrain(spiketimes, units='sec', t_stop=900.0) # sr = elephant.statistics.instantaneous_rate(spike_object, 1/sampling_frequency * pq.s, kernel=kernel) # figure, ax = plt.subplots(1, 1, figsize=(12, 9)) # ax.plot(np.linspace(0, 900.0, len(sr)), sr) # print(np.linspace(0, 900.0, len(sr))[:5]) # # ax.set_xlim([0, 60]) # ax.set_xlabel('time [s]') # ax.set_ylabel('firing rate [Hz]') # ax.set_yticks(range(0, len(ordered_labels), 16)) # # ax.set_yticklabels(np.flip(ordered_labels)[::16]) # ax.spines['right'].set_visible(False) # ax.spines['top'].set_visible(False) # ax.get_xaxis().tick_bottom()
def instantaneous_rate(spiketrain, sampling_period, kernel='auto', cutoff=5.0, t_start=None, t_stop=None, trim=False, center_kernel=True): """ Estimates instantaneous firing rate by kernel convolution. Parameters ---------- spiketrain : neo.SpikeTrain or list of neo.SpikeTrain Neo object(s) that contains spike times, the unit of the time stamps, and `t_start` and `t_stop` of the spike train. sampling_period : pq.Quantity Time stamp resolution of the spike times. The same resolution will be assumed for the kernel. kernel : 'auto' or Kernel, optional The string 'auto' or callable object of class `kernels.Kernel`. The kernel is used for convolution with the spike train and its standard deviation determines the time resolution of the instantaneous rate estimation. Currently implemented kernel forms are rectangular, triangular, epanechnikovlike, gaussian, laplacian, exponential, and alpha function. If 'auto', the optimized kernel width for the rate estimation is calculated according to [1]_ and with this width a gaussian kernel is constructed. Automatized calculation of the kernel width is not available for other than gaussian kernel shapes. Default: 'auto'. cutoff : float, optional This factor determines the cutoff of the probability distribution of the kernel, i.e., the considered width of the kernel in terms of multiples of the standard deviation sigma. Default: 5.0. t_start : pq.Quantity, optional Start time of the interval used to compute the firing rate. If None, `t_start` is assumed equal to `t_start` attribute of `spiketrain`. Default: None. t_stop : pq.Quantity, optional End time of the interval used to compute the firing rate (included). If None, `t_stop` is assumed equal to `t_stop` attribute of `spiketrain`. Default: None. trim : bool, optional Accounts for the asymmetry of a kernel. If False, the output of the Fast Fourier Transformation being a longer vector than the input vector by the size of the kernel is reduced back to the original size of the considered time interval of the `spiketrain` using the median of the kernel. False (no trimming) is equivalent to 'same' convolution mode for symmetrical kernels. If True, only the region of the convolved signal is returned, where there is complete overlap between kernel and spike train. This is achieved by reducing the length of the output of the Fast Fourier Transformation by a total of two times the size of the kernel, and `t_start` and `t_stop` are adjusted. True (trimming) is equivalent to 'valid' convolution mode for symmetrical kernels. Default: False. center_kernel : bool, optional If set to True, the kernel will be translated such that its median is centered on the spike, thus putting equal weight before and after the spike. If False, no adjustment is performed such that the spike sits at the origin of the kernel. Default: True Returns ------- rate : neo.AnalogSignal Contains the rate estimation in unit hertz (Hz). In case a list of spike trains was given, this is the combined rate of all spike trains (not the average rate). `rate.times` contains the time axis of the rate estimate. The unit of this property is the same as the resolution that is given via the argument `sampling_period` to the function. Raises ------ TypeError If `spiketrain` is not an instance of `neo.SpikeTrain`. If `sampling_period` is not a `pq.Quantity`. If `sampling_period` is not larger than zero. If `kernel` is neither instance of `kernels.Kernel` nor string 'auto'. If `cutoff` is neither `float` nor `int`. If `t_start` and `t_stop` are neither None nor a `pq.Quantity`. If `trim` is not `bool`. ValueError If `sampling_period` is smaller than zero. If `kernel` is 'auto' and the function was unable to calculate optimal kernel width for instantaneous rate from input data. Warns ----- UserWarning If `cutoff` is less than `min_cutoff` attribute of `kernel`, the width of the kernel is adjusted to a minimally allowed width. If the instantaneous firing rate approximation contains negative values with respect to a tolerance (less than -1e-5), possibly due to machine precision errors. References ---------- .. [1] H. Shimazaki, & S. Shinomoto, "Kernel bandwidth optimization in spike rate estimation," J Comput Neurosci, vol. 29, pp. 171–182, 2010. Examples -------- >>> import quantities as pq >>> from elephant import kernels >>> kernel = kernels.AlphaKernel(sigma=0.05*pq.s, invert=True) >>> rate = instantaneous_rate(spiketrain, sampling_period=2*pq.ms, ... kernel=kernel) """ # Merge spike trains if list of spike trains given: if isinstance(spiketrain, list): _check_consistency_of_spiketrains( spiketrain, t_start=t_start, t_stop=t_stop) if t_start is None: t_start = spiketrain[0].t_start if t_stop is None: t_stop = spiketrain[0].t_stop spikes = np.concatenate([st.magnitude for st in spiketrain]) merged_spiketrain = SpikeTrain(np.sort(spikes), units=spiketrain[0].units, t_start=t_start, t_stop=t_stop) return instantaneous_rate(merged_spiketrain, sampling_period=sampling_period, kernel=kernel, cutoff=cutoff, t_start=t_start, t_stop=t_stop, trim=trim) # Checks of input variables: if not isinstance(spiketrain, SpikeTrain): raise TypeError( "'spiketrain' must be an instance of neo.SpikeTrain. \n" "Found: '{}'".format(type(spiketrain))) if not is_time_quantity(sampling_period): raise TypeError( "The 'sampling_period' must be a time Quantity. \n" "Found: {}".format(type(sampling_period))) if sampling_period.magnitude < 0: raise ValueError("The 'sampling_period' ({}) must be non-negative.". format(sampling_period)) if kernel == 'auto': kernel_width_sigma = None if len(spiketrain) > 0: kernel_width_sigma = optimal_kernel_bandwidth( spiketrain.magnitude, times=None, bootstrap=False)['optw'] if kernel_width_sigma is None: raise ValueError( "Unable to calculate optimal kernel width for " "instantaneous rate from input data.") kernel = kernels.GaussianKernel(kernel_width_sigma * spiketrain.units) elif not isinstance(kernel, kernels.Kernel): raise TypeError( "'kernel' must be either instance of class elephant.kernels.Kernel" " or the string 'auto'. Found: %s, value %s" % (type(kernel), str(kernel))) if not isinstance(cutoff, (float, int)): raise TypeError("'cutoff' must be float or integer") if not is_time_quantity(t_start, allow_none=True): raise TypeError("'t_start' must be a time Quantity") if not is_time_quantity(t_stop, allow_none=True): raise TypeError("'t_stop' must be a time Quantity") if not isinstance(trim, bool): raise TypeError("'trim' must be bool") # main function: units = pq.CompoundUnit( "{}*s".format(sampling_period.rescale('s').item())) spiketrain = spiketrain.rescale(units) if t_start is None: t_start = spiketrain.t_start else: t_start = t_start.rescale(spiketrain.units) if t_stop is None: t_stop = spiketrain.t_stop else: t_stop = t_stop.rescale(spiketrain.units) # float32 makes fftconvolve less precise which may result in nan time_vector = np.zeros(int(t_stop - t_start) + 1, dtype=np.float64) spikes_slice = spiketrain.time_slice(t_start, t_stop) bins_active = (spikes_slice.times - t_start).magnitude.astype(np.int32) bins_unique, bin_counts = np.unique(bins_active, return_counts=True) time_vector[bins_unique] = bin_counts if cutoff < kernel.min_cutoff: cutoff = kernel.min_cutoff warnings.warn("The width of the kernel was adjusted to a minimally " "allowed width.") t_arr = np.arange(-cutoff * kernel.sigma.rescale(units).magnitude, cutoff * kernel.sigma.rescale(units).magnitude + sampling_period.rescale(units).magnitude, sampling_period.rescale(units).magnitude) * units if center_kernel: # keep the full convolve range and do the trimming afterwards; # trimming is performed according to the kernel median index fft_mode = 'full' elif trim: # no median index trimming is involved fft_mode = 'valid' else: # no median index trimming is involved fft_mode = 'same' rate = scipy.signal.fftconvolve(time_vector, kernel(t_arr).rescale(pq.Hz).magnitude, mode=fft_mode) if np.any(rate < -1e-8): # abs tolerance in np.isclose warnings.warn("Instantaneous firing rate approximation contains " "negative values, possibly caused due to machine " "precision errors.") median_id = kernel.median_index(t_arr) # the size of kernel() output matches the input size kernel_array_size = len(t_arr) if center_kernel: # account for the kernel asymmetry if not trim: rate = rate[median_id: -kernel_array_size + median_id] else: rate = rate[2 * median_id: -2 * (kernel_array_size - median_id)] t_start = t_start + median_id * spiketrain.units t_stop = t_stop - (kernel_array_size - median_id ) * spiketrain.units else: # (to be consistent with center_kernel=True) # n points have n-1 intervals; # instantaneous rate is a list of intervals; # hence, the last element is excluded rate = rate[:-1] rate = neo.AnalogSignal(signal=np.expand_dims(rate, axis=1), sampling_period=sampling_period, units=pq.Hz, t_start=t_start, t_stop=t_stop) return rate
def myrate(spiketrain, sampling_period, kernel='auto', cutoff=5.0, t_start=None, t_stop=None, trim=False, center_kernel=True): """ Estimates instantaneous firing rate by kernel convolution. Modified by gryang from elephant.statistics.instantaneous_rate. Much faster for many spike trains. Parameters ---------- spiketrain : list of lists of spike times Neo object(s) that contains spike times, the unit of the time stamps, and `t_start` and `t_stop` of the spike train. sampling_period : float (s) Time stamp resolution of the spike times. The same resolution will be assumed for the kernel. The rest are the same as elephant.statistics.instantaneous_rate, abbreviated here. """ if kernel == 'auto': kernel_width_sigma = None if len(spiketrain) > 0: kernel_width_sigma = optimal_kernel_bandwidth( spiketrain.magnitude, times=None, bootstrap=False)['optw'] if kernel_width_sigma is None: raise ValueError("Unable to calculate optimal kernel width for " "instantaneous rate from input data.") kernel = kernels.GaussianKernel(kernel_width_sigma * spiketrain.units) elif not isinstance(kernel, kernels.Kernel): raise TypeError( "'kernel' must be either instance of class elephant.kernels.Kernel" " or the string 'auto'. Found: %s, value %s" % (type(kernel), str(kernel))) # TODO: do the single spike train case n_spiketrain = len(spiketrain) # Number of spike trains # main function: nbins = int((t_stop - t_start) / sampling_period) + 1 time_vectors = np.zeros((n_spiketrain, nbins)) ranges = (t_start, t_stop + sampling_period) times = np.arange(ranges[0], ranges[1], sampling_period) for i, st in enumerate(spiketrain): # See https://iscinumpy.gitlab.io/post/histogram-speeds-in-python/ time_vectors[i], _ = np.histogram(st, bins=nbins, range=ranges) # c = ((st[(st >= ranges[0]) & (st < ranges[1])] - ranges[0]) / # sampling_period).astype(np.int_) # time_vectors[i] = np.bincount(c) # This line is necessary to match elephant's original implementation time_vectors[:, -1] = 0 time_vectors = time_vectors.T # make it (time, units) time_vectors = time_vectors.astype(np.float64) # from elephant if cutoff < kernel.min_cutoff: cutoff = kernel.min_cutoff warnings.warn("The width of the kernel was adjusted to a minimally " "allowed width.") sigma = kernel.sigma.rescale(pq.s).magnitude t_arr = np.arange(-cutoff * sigma, cutoff * sigma + sampling_period, sampling_period) * pq.s if center_kernel: # keep the full convolve range and do the trimming afterwards; # trimming is performed according to the kernel median index fft_mode = 'full' elif trim: # no median index trimming is involved fft_mode = 'valid' else: # no median index trimming is involved fft_mode = 'same' _kernel = kernel(t_arr).rescale(pq.Hz).magnitude[:, np.newaxis] rates = scipy.signal.fftconvolve(time_vectors, _kernel, mode=fft_mode, axes=0) median_id = kernel.median_index(t_arr) # the size of kernel() output matches the input size kernel_array_size = len(t_arr) if center_kernel: # account for the kernel asymmetry if not trim: rates = rates[median_id:-kernel_array_size + median_id] else: rates = rates[2 * median_id:-2 * (kernel_array_size - median_id)] else: # (to be consistent with center_kernel=True) # n points have n-1 intervals; # instantaneous rate is a list of intervals; # hence, the last element is excluded rates = rates[:-1] return rates, times[:-1]
if __name__ == '__main__': import time from elephant.statistics import instantaneous_rate from neo.core import SpikeTrain sampling_period = 0.01 t_start = -2 t_stop = 2 X = [ np.random.uniform(-3, 3, size=(np.random.randint(9000, 11000), )) for i in range(100) ] kernel_sigma = 0.05 kernel = kernels.GaussianKernel(50 * pq.ms) t0 = time.time() Rate = list() for i in range(len(X)): spiketrain = SpikeTrain(X[i] * pq.s, t_start=-3 * pq.s, t_stop=3 * pq.s) rate = instantaneous_rate(spiketrain, sampling_period=0.01 * pq.s, t_start=-2 * pq.s, t_stop=2 * pq.s, kernel=kernel) Rate.append(rate.magnitude[:, 0]) Rate = np.array(Rate).T time_taken0 = (time.time() - t0)
def instantaneous_rate(spiketrain, sampling_period, kernel='auto', cutoff=5.0, t_start=None, t_stop=None, trim=False): """ Estimates instantaneous firing rate by kernel convolution. Parameters ----------- spiketrain : neo.SpikeTrain or list of neo.SpikeTrain Neo object(s) that contains spike times, the unit of the time stamps, and `t_start` and `t_stop` of the spike train. sampling_period : pq.Quantity Time stamp resolution of the spike times. The same resolution will be assumed for the kernel. kernel : str or `kernels.Kernel`, optional The string 'auto' or callable object of class `kernels.Kernel`. The kernel is used for convolution with the spike train and its standard deviation determines the time resolution of the instantaneous rate estimation. Currently implemented kernel forms are rectangular, triangular, epanechnikovlike, gaussian, laplacian, exponential, and alpha function. If 'auto', the optimized kernel width for the rate estimation is calculated according to [1]_ and with this width a gaussian kernel is constructed. Automatized calculation of the kernel width is not available for other than gaussian kernel shapes. Default: 'auto'. cutoff : float, optional This factor determines the cutoff of the probability distribution of the kernel, i.e., the considered width of the kernel in terms of multiples of the standard deviation sigma. Default: 5.0. t_start : pq.Quantity, optional Start time of the interval used to compute the firing rate. If None, `t_start` is assumed equal to `t_start` attribute of `spiketrain`. Default: None. t_stop : pq.Quantity, optional End time of the interval used to compute the firing rate (included). If None, `t_stop` is assumed equal to `t_stop` attribute of `spiketrain`. Default: None. trim : bool, optional If False, the output of the Fast Fourier Transformation being a longer vector than the input vector by the size of the kernel is reduced back to the original size of the considered time interval of the `spiketrain` using the median of the kernel. If True, only the region of the convolved signal is returned, where there is complete overlap between kernel and spike train. This is achieved by reducing the length of the output of the Fast Fourier Transformation by a total of two times the size of the kernel, and `t_start` and `t_stop` are adjusted. Default: False. Returns ------- rate : neo.AnalogSignal Contains the rate estimation in unit hertz (Hz). In case a list of spike trains was given, this is the combined rate of all spike trains (not the average rate). `rate.times` contains the time axis of the rate estimate. The unit of this property is the same as the resolution that is given via the argument `sampling_period` to the function. Raises ------ TypeError: If `spiketrain` is not an instance of `neo.SpikeTrain`. If `sampling_period` is not a `pq.Quantity`. If `sampling_period` is not larger than zero. If `kernel` is neither instance of `kernels.Kernel` nor string 'auto'. If `cutoff` is neither `float` nor `int`. If `t_start` and `t_stop` are neither None nor a `pq.Quantity`. If `trim` is not `bool`. ValueError: If `sampling_period` is smaller than zero. If `kernel` is 'auto' and the function was unable to calculate optimal kernel width for instantaneous rate from input data. Warns ----- UserWarning If `cutoff` is less than `min_cutoff` attribute of `kernel`, the width of the kernel is adjusted to a minimally allowed width. If the instantaneous firing rate approximation contains negative values with respect to a tolerance (less than -1e-8), possibly due to machine precision errors. References ---------- .. [1] H. Shimazaki, & S. Shinomoto, "Kernel bandwidth optimization in spike rate estimation," J Comput Neurosci, vol. 29, pp. 171–182, 2010. Examples -------- >>> import quantities as pq >>> from elephant import kernels >>> kernel = kernels.AlphaKernel(sigma=0.05*pq.s, invert=True) >>> rate = instantaneous_rate(spiketrain, sampling_period=2*pq.ms, ... kernel=kernel) """ # Merge spike trains if list of spike trains given: if isinstance(spiketrain, list): _check_consistency_of_spiketrainlist( spiketrain, t_start=t_start, t_stop=t_stop) if t_start is None: t_start = spiketrain[0].t_start if t_stop is None: t_stop = spiketrain[0].t_stop spikes = np.concatenate([st.magnitude for st in spiketrain]) merged_spiketrain = SpikeTrain(np.sort(spikes), units=spiketrain[0].units, t_start=t_start, t_stop=t_stop) return instantaneous_rate(merged_spiketrain, sampling_period=sampling_period, kernel=kernel, cutoff=cutoff, t_start=t_start, t_stop=t_stop, trim=trim) # Checks of input variables: if not isinstance(spiketrain, SpikeTrain): raise TypeError( "spiketrain must be instance of :class:`SpikeTrain` of Neo!\n" " Found: %s, value %s" % (type(spiketrain), str(spiketrain))) if not (isinstance(sampling_period, pq.Quantity) and sampling_period.dimensionality.simplified == pq.Quantity(1, "s").dimensionality): raise TypeError( "The sampling period must be a time quantity!\n" " Found: %s, value %s" % ( type(sampling_period), str(sampling_period))) if sampling_period.magnitude < 0: raise ValueError("The sampling period must be larger than zero.") if kernel == 'auto': kernel_width_sigma = sskernel( spiketrain.magnitude, tin=None, bootstrap=False)['optw'] if kernel_width_sigma is None: raise ValueError( "Unable to calculate optimal kernel width for " "instantaneous rate from input data.") kernel = kernels.GaussianKernel(kernel_width_sigma * spiketrain.units) elif not isinstance(kernel, kernels.Kernel): raise TypeError( "kernel must be either instance of :class:`Kernel` " "or the string 'auto'!\n" " Found: %s, value %s" % (type(kernel), str(kernel))) if not (isinstance(cutoff, float) or isinstance(cutoff, int)): raise TypeError("cutoff must be float or integer!") if not (t_start is None or (isinstance(t_start, pq.Quantity) and t_start.dimensionality.simplified == pq.Quantity(1, "s").dimensionality)): raise TypeError("t_start must be a time quantity!") if not (t_stop is None or (isinstance(t_stop, pq.Quantity) and t_stop.dimensionality.simplified == pq.Quantity(1, "s").dimensionality)): raise TypeError("t_stop must be a time quantity!") if not (isinstance(trim, bool)): raise TypeError("trim must be bool!") # main function: units = pq.CompoundUnit( "{}*s".format(sampling_period.rescale('s').item())) spiketrain = spiketrain.rescale(units) if t_start is None: t_start = spiketrain.t_start else: t_start = t_start.rescale(spiketrain.units) if t_stop is None: t_stop = spiketrain.t_stop else: t_stop = t_stop.rescale(spiketrain.units) time_vector = np.zeros(int((t_stop - t_start)) + 1) spikes_slice = spiketrain.time_slice(t_start, t_stop) \ if len(spiketrain) else np.array([]) for spike in spikes_slice: index = int((spike - t_start)) time_vector[index] += 1 if cutoff < kernel.min_cutoff: cutoff = kernel.min_cutoff warnings.warn("The width of the kernel was adjusted to a minimally " "allowed width.") t_arr = np.arange(-cutoff * kernel.sigma.rescale(units).magnitude, cutoff * kernel.sigma.rescale(units).magnitude + sampling_period.rescale(units).magnitude, sampling_period.rescale(units).magnitude) * units r = scipy.signal.fftconvolve(time_vector, kernel(t_arr).rescale(pq.Hz).magnitude, 'full') if np.any(r < -1e-8): # abs tolerance in np.isclose warnings.warn("Instantaneous firing rate approximation contains " "negative values, possibly caused due to machine " "precision errors.") if not trim: r = r[kernel.median_index(t_arr):-(kernel(t_arr).size - kernel.median_index(t_arr))] elif trim: r = r[2 * kernel.median_index(t_arr):-2 * (kernel(t_arr).size - kernel.median_index(t_arr))] t_start += kernel.median_index(t_arr) * spiketrain.units t_stop -= (kernel(t_arr).size - kernel.median_index(t_arr)) * spiketrain.units rate = neo.AnalogSignal(signal=r.reshape(r.size, 1), sampling_period=sampling_period, units=pq.Hz, t_start=t_start, t_stop=t_stop) return rate