def time_slice(self, t_start=None, t_stop=None, copy=False): """ Returns a view or a copied view of currently binned spike trains with ``(t_start, t_stop)`` time slice. Only valid (fully overlapping) bins are sliced. Parameters ---------- t_start, t_stop : pq.Quantity or None, optional Start and stop times or Nones. Default: None copy : bool, optional Copy the sparse matrix or not. Default: False Returns ------- BinnedSpikeTrainView A time slice of itself. """ if not is_time_quantity(t_start, t_stop, allow_none=True): raise TypeError("t_start and t_stop must be quantities") if t_start is None and t_stop is None and not copy: return self if t_start is None: start_index = 0 else: t_start = t_start.rescale(self.units).item() start_index = (t_start - self._t_start) / self._bin_size start_index = math.ceil(start_index) start_index = max(start_index, 0) if t_stop is None: stop_index = self.n_bins else: t_stop = t_stop.rescale(self.units).item() stop_index = (t_stop - self._t_start) / self._bin_size stop_index = round_binning_errors(stop_index, tolerance=self.tolerance) stop_index = min(stop_index, self.n_bins) stop_index = max(stop_index, start_index) spmat = self.sparse_matrix[:, start_index: stop_index] if copy: spmat = spmat.copy() t_start = self._t_start + start_index * self._bin_size t_stop = self._t_start + stop_index * self._bin_size bst = BinnedSpikeTrainView(t_start=t_start, t_stop=t_stop, bin_size=self._bin_size, units=self.units, sparse_matrix=spmat, tolerance=self.tolerance) return bst
def spike_contrast(spiketrains, t_start=None, t_stop=None, min_bin=10 * pq.ms, bin_shrink_factor=0.9, return_trace=False): """ Calculates the synchrony of spike trains. The spike trains can have different lengths. Original implementation by: Philipp Steigerwald [[email protected]] Parameters ---------- spiketrains : list of neo.SpikeTrain A list of input spike trains to calculate the synchrony from. t_start : pq.Quantity, optional The beginning of the spike train. If None, it's taken as the minimum value of `t_start`s of the input spike trains. Default: None t_stop : pq.Quantity, optional The end of the spike train. If None, it's taken as the maximum value of `t_stop` of the input spike trains. Default: None min_bin : pq.Quantity, optional Sets the minimum value for the `bin_min` that is calculated by the algorithm and defines the smallest bin size to compute the histogram of the input `spiketrains`. Default: 0.01 ms bin_shrink_factor : float, optional A multiplier to shrink the bin size on each iteration. The value must be in range `(0, 1)`. Default: 0.9 return_trace : bool, optional If set to True, returns a history of spike-contrast synchrony, computed for a range of different bin sizes, alongside with the maximum value of the synchrony. Default: False Returns ------- synchrony : float Returns the synchrony of the input spike trains. spike_contrast_trace : namedtuple If `return_trace` is set to True, a `SpikeContrastTrace` namedtuple is returned with the following attributes: `.contrast` - the average sum of differences of the number of spikes in subsuequent bins; `.active_spiketrains` - the average number of spikes per bin, weighted by the number of spike trains containing at least one spike inside the bin; `.synchrony` - the product of `contrast` and `active_spiketrains`. Raises ------ ValueError If `bin_shrink_factor` is not in (0, 1) range. If the input spike trains constist of a single spiketrain. If all input spike trains contain no more than 1 spike. TypeError If the input spike trains is not a list of neo.SpikeTrain objects. If `t_start`, `t_stop`, or `min_bin` are not time quantities. Examples -------- >>> import quantities as pq >>> from elephant.spike_train_generation import homogeneous_poisson_process >>> from elephant.spike_train_synchrony import spike_contrast >>> spiketrain_1 = homogeneous_poisson_process(rate=20*pq.Hz, ... t_stop=1000*pq.ms) >>> spiketrain_2 = homogeneous_poisson_process(rate=20*pq.Hz, ... t_stop=1000*pq.ms) >>> spike_contrast([spiketrain_1, spiketrain_2]) 0.4192546583850932 """ if not 0. < bin_shrink_factor < 1.: raise ValueError( "'bin_shrink_factor' ({}) must be in range (0, 1).".format( bin_shrink_factor)) if not len(spiketrains) > 1: raise ValueError("Spike contrast measure requires more than 1 input " "spiketrain.") if not all(isinstance(st, neo.SpikeTrain) for st in spiketrains): raise TypeError("Input spike trains must be a list of neo.SpikeTrain.") if not is_time_quantity(t_start, allow_none=True) \ or not is_time_quantity(t_stop, allow_none=True): raise TypeError("'t_start' and 't_stop' must be time quantities.") if not is_time_quantity(min_bin): raise TypeError("'min_bin' must be a time quantity.") if t_start is None: t_start = min(st.t_start for st in spiketrains) if t_stop is None: t_stop = max(st.t_stop for st in spiketrains) spiketrains = [ st.time_slice(t_start=t_start, t_stop=t_stop) for st in spiketrains ] # convert everything to seconds spiketrains = [st.simplified.magnitude for st in spiketrains] t_start = t_start.simplified.item() t_stop = t_stop.simplified.item() min_bin = min_bin.simplified.item() n_spiketrains = len(spiketrains) n_spikes_total = sum(map(len, spiketrains)) duration = t_stop - t_start bin_max = duration / 2 try: isi_min = min(np.diff(st).min() for st in spiketrains if len(st) > 1) except TypeError: raise ValueError("All input spiketrains contain no more than 1 spike.") bin_min = max(isi_min / 2, min_bin) contrast_list = [] active_spiketrains = [] synchrony_curve = [] bin_size = bin_max while bin_size >= bin_min: # Set new time boundaries t_start = -isi_min t_stop = duration + isi_min # Calculate Theta and n theta_k, n_k = _get_theta_and_n_per_bin(spiketrains, t_start=t_start, t_stop=t_stop, bin_size=bin_size) # calculate synchrony_curve = contrast * active_st active_st = (np.sum(n_k * theta_k) / np.sum(theta_k) - 1) / (n_spiketrains - 1) contrast = np.sum(np.abs(np.diff(theta_k))) / (2 * n_spikes_total) # Contrast: sum(|derivation|) / (2*#Spikes) synchrony = contrast * active_st contrast_list.append(contrast) active_spiketrains.append(active_st) synchrony_curve.append(synchrony) # New bin size bin_size *= bin_shrink_factor # Sync value is maximum of the cost function C synchrony = max(synchrony_curve) if return_trace: spike_contrast_trace = SpikeContrastTrace( contrast=contrast_list, active_spiketrains=active_spiketrains, synchrony=synchrony_curve) return synchrony, spike_contrast_trace return synchrony
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 fanofactor(spiketrains, warn_tolerance=0.1 * pq.ms): r""" Evaluates the empirical Fano factor F of the spike counts of a list of `neo.SpikeTrain` objects. Given the vector v containing the observed spike counts (one per spike train) in the time window [t0, t1], F is defined as: .. math:: F := \frac{var(v)}{mean(v)} The Fano factor is typically computed for spike trains representing the activity of the same neuron over different trials. The higher F, the larger the cross-trial non-stationarity. In theory for a time-stationary Poisson process, F=1. Parameters ---------- spiketrains : list List of `neo.SpikeTrain` or `pq.Quantity` or `np.ndarray` or list of spike times for which to compute the Fano factor of spike counts. warn_tolerance : pq.Quantity In case of a list of input neo.SpikeTrains, if their durations vary by more than `warn_tolerence` in their absolute values, throw a waring. Default: 0.1 ms. Returns ------- fano : float The Fano factor of the spike counts of the input spike trains. Returns np.NaN if an empty list is specified, or if all spike trains are empty. Raises ------ TypeError If the input spiketrains are neo.SpikeTrain objects, but `warn_tolerance` is not a quantity. """ # Build array of spike counts (one per spike train) spike_counts = np.array([len(st) for st in spiketrains]) # Compute FF if all(count == 0 for count in spike_counts): # empty list of spiketrains reaches this branch, and NaN is returned return np.nan if all(isinstance(st, neo.SpikeTrain) for st in spiketrains): if not is_time_quantity(warn_tolerance): raise TypeError("'warn_tolerance' must be a time quantity.") durations = [(st.t_stop - st.t_start).simplified.item() for st in spiketrains] durations_min = min(durations) durations_max = max(durations) if durations_max - durations_min > warn_tolerance.simplified.item(): warnings.warn("Fano factor calculated for spike trains of " "different duration (minimum: {_min}s, maximum " "{_max}s).".format(_min=durations_min, _max=durations_max)) fano = spike_counts.var() / spike_counts.mean() return fano
def mean_firing_rate(spiketrain, t_start=None, t_stop=None, axis=None): """ Return the firing rate of the spike train. The firing rate is calculated as the number of spikes in the spike train in the range `[t_start, t_stop]` divided by the time interval `t_stop - t_start`. See the description below for cases when `t_start` or `t_stop` is None. Accepts a `neo.SpikeTrain`, a `pq.Quantity` array, or a plain `np.ndarray`. If either a `neo.SpikeTrain` or `pq.Quantity` array is provided, the return value will be a `pq.Quantity` array, otherwise a plain `np.ndarray`. The units of the `pq.Quantity` array will be the inverse of the `spiketrain`. Parameters ---------- spiketrain : neo.SpikeTrain or pq.Quantity or np.ndarray The spike times. t_start : float or pq.Quantity, optional The start time to use for the interval. If None, retrieved from the `t_start` attribute of `spiketrain`. If that is not present, default to 0. All spiketrain's spike times below this value are ignored. Default: None. t_stop : float or pq.Quantity, optional The stop time to use for the time points. If not specified, retrieved from the `t_stop` attribute of `spiketrain`. If that is not present, default to the maximum value of `spiketrain`. All spiketrain's spike times above this value are ignored. Default: None. axis : int, optional The axis over which to do the calculation; has no effect when the input is a neo.SpikeTrain, because a neo.SpikeTrain is always a 1-d vector. If None, do the calculation over the flattened array. Default: None. Returns ------- float or pq.Quantity or np.ndarray The firing rate of the `spiketrain` Raises ------ TypeError If the input spiketrain is a `np.ndarray` but `t_start` or `t_stop` is `pq.Quantity`. If the input spiketrain is a `neo.SpikeTrain` or `pq.Quantity` but `t_start` or `t_stop` is not `pq.Quantity`. ValueError If the input spiketrain is empty. """ if isinstance(spiketrain, neo.SpikeTrain) and t_start is None \ and t_stop is None and axis is None: # a faster approach for a typical use case n_spikes = len(spiketrain) time_interval = spiketrain.t_stop - spiketrain.t_start time_interval = time_interval.rescale(spiketrain.units) rate = n_spikes / time_interval return rate if isinstance(spiketrain, pq.Quantity): # Quantity or neo.SpikeTrain if not is_time_quantity(t_start, allow_none=True): raise TypeError("'t_start' must be a Quantity or None") if not is_time_quantity(t_stop, allow_none=True): raise TypeError("'t_stop' must be a Quantity or None") units = spiketrain.units if t_start is None: t_start = getattr(spiketrain, 't_start', 0 * units) t_start = t_start.rescale(units).magnitude if t_stop is None: t_stop = getattr(spiketrain, 't_stop', np.max(spiketrain, axis=axis)) t_stop = t_stop.rescale(units).magnitude # calculate as a numpy array rates = mean_firing_rate(spiketrain.magnitude, t_start=t_start, t_stop=t_stop, axis=axis) rates = pq.Quantity(rates, units=1. / units) return rates elif isinstance(spiketrain, (np.ndarray, list, tuple)): if isinstance(t_start, pq.Quantity) or isinstance(t_stop, pq.Quantity): raise TypeError("'t_start' and 't_stop' cannot be quantities if " "'spiketrain' is not a Quantity.") spiketrain = np.asarray(spiketrain) if len(spiketrain) == 0: raise ValueError("Empty input spiketrain.") if t_start is None: t_start = 0 if t_stop is None: t_stop = np.max(spiketrain, axis=axis) time_interval = t_stop - t_start if axis and isinstance(t_stop, np.ndarray): t_stop = np.expand_dims(t_stop, axis) rates = np.sum((spiketrain >= t_start) & (spiketrain <= t_stop), axis=axis) / time_interval return rates else: raise TypeError("Invalid input spiketrain type: '{}'. Allowed: " "neo.SpikeTrain, Quantity, ndarray". format(type(spiketrain)))
def instantaneous_rate(spiketrains, 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. Visualization of this function is covered in Viziphant: :func:`viziphant.statistics.plot_instantaneous_rates_colormesh`. Parameters ---------- spiketrains : 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 2D matrix that contains the rate estimation in unit hertz (Hz) of shape ``(time, len(spiketrains))`` or ``(time, 1)`` in case of a single input spiketrain. `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. Notes ----- The resulting instantaneous firing rate values smaller than ``0``, which can happen due to machine precision errors, are clipped to zero. 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 >>> from elephant.spike_train_generation import homogeneous_poisson_process >>> spiketrain = homogeneous_poisson_process(rate=10*pq.Hz, t_stop=5*pq.s) >>> kernel = kernels.AlphaKernel(sigma=0.05*pq.s, invert=True) >>> rate = instantaneous_rate(spiketrain, sampling_period=2*pq.ms, ... kernel=kernel) """ 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) if isinstance(spiketrains, neo.SpikeTrain): if kernel == 'auto': kernel = optimal_kernel(spiketrains) spiketrains = [spiketrains] elif not isinstance(spiketrains, (list, tuple)): raise TypeError( "'spiketrains' must be a list of neo.SpikeTrain's or a single " "neo.SpikeTrain. Found: '{}'".format(type(spiketrains))) 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 not (isinstance(kernel, kernels.Kernel) or kernel == 'auto'): 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") check_neo_consistency(spiketrains, object_type=neo.SpikeTrain, t_start=t_start, t_stop=t_stop) if kernel == 'auto': if len(spiketrains) == 1: kernel = optimal_kernel(spiketrains[0]) else: raise ValueError("Cannot estimate a kernel for a list of spike " "trains. Please provide a kernel explicitly " "rather than 'auto'.") if t_start is None: t_start = spiketrains[0].t_start if t_stop is None: t_stop = spiketrains[0].t_stop units = pq.CompoundUnit("{}*s".format(sampling_period.rescale('s').item())) t_start = t_start.rescale(spiketrains[0].units) t_stop = t_stop.rescale(spiketrains[0].units) n_bins = int(((t_stop - t_start) / sampling_period).simplified) + 1 time_vectors = np.zeros((len(spiketrains), n_bins), dtype=np.float64) hist_range_end = t_stop + sampling_period.rescale(spiketrains[0].units) hist_range = (t_start.item(), hist_range_end.item()) for i, st in enumerate(spiketrains): time_vectors[i], _ = np.histogram(st.magnitude, bins=n_bins, range=hist_range) if cutoff < kernel.min_cutoff: cutoff = kernel.min_cutoff warnings.warn("The width of the kernel was adjusted to a minimally " "allowed width.") # An odd number of points correctly resolves the median index and the # fact that the peak of an instantaneous rate should be centered at t=0 # for symmetric kernels applied on a single spike at t=0. # See issue https://github.com/NeuralEnsemble/elephant/issues/360 n_half = math.ceil(cutoff * (kernel.sigma / sampling_period).simplified.item()) cutoff_sigma = cutoff * kernel.sigma.rescale(units).magnitude if center_kernel: # t_arr must be centered at the kernel median. # Not centering on the kernel median leads to underestimating the # instantaneous rate in cases when sampling_period >> kernel.sigma. median = kernel.icdf(0.5).rescale(units).item() else: median = 0 t_arr = np.linspace(-cutoff_sigma + median, stop=cutoff_sigma + median, num=2 * n_half + 1, endpoint=True) * 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' time_vectors = time_vectors.T # make it (time, units) kernel_arr = np.expand_dims(kernel(t_arr).rescale(pq.Hz).magnitude, axis=1) rate = scipy.signal.fftconvolve(time_vectors, kernel_arr, mode=fft_mode) # the convolution of non-negative vectors is non-negative rate = np.clip(rate, a_min=0, a_max=None, out=rate) if center_kernel: # account for the kernel asymmetry median_id = kernel.median_index(t_arr) # the size of kernel() output matches the input size, len(t_arr) kernel_array_size = len(t_arr) 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 * units t_stop = t_stop - (kernel_array_size - median_id) * units else: # FIXME: don't shrink the output array # (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] kernel_annotation = dict(type=type(kernel).__name__, sigma=str(kernel.sigma), invert=kernel.invert) rate = neo.AnalogSignal(signal=rate, sampling_period=sampling_period, units=pq.Hz, t_start=t_start, t_stop=t_stop, kernel=kernel_annotation) return rate