def linear_response(filt, stim, nsamples_after=0): """ Compute the response of a linear filter to a stimulus. Parameters ---------- filt : array_like The linear filter whose response is to be computed. The array should have shape ``(t, ...)``, where ``t`` is the number of time points in the filter and the ellipsis indicates any remaining spatial dimenions. The number of dimensions and the sizes of the spatial dimensions must match that of ``stim``. stim : array_like The stimulus to which the predicted response is computed. The array should have shape ``(T,...)``, where ``T`` is the number of time points in the stimulus and the ellipsis indicates any remaining spatial dimensions. The number of dimensions and the sizes of the spatial dimenions must match that of ``filt``. nsamples_after : int, optional The number of acausal points in the filter. Defaults to 0. Returns ------- pred : array_like The predicted linear response, of shape ``(t,)``. Raises ------ ValueError : If the number of dimensions of ``stim`` and ``filt`` do not match, or if the spatial dimensions differ. Notes ----- Note that the first parameter is a *linear filter*. The values returned by ``filtertools.sta`` and ``filtertools.revcorr`` are proportional to the time-reverse of the linear filter, so to use those values in this function, they must be flipped along the first dimension. Both ``filtertools.sta`` and ``filtertools.revcorr`` can estimate "acausal" components, such as points in the stimulus occuring *after* a spike. The value passed as parameter ``nsamples_after`` must match that value used when calling ``filtertools.sta`` or ``filtertools.revcorr``. """ if (filt.ndim != stim.ndim) or (filt.shape[1:] != stim.shape[1:]): raise ValueError("The filter and stimulus must have the same " "number of dimensions and match in size along " "spatial dimensions") if (nsamples_after >= filt.shape[0]): raise ValueError("Cannot compute the response of a " "filter with no causal points.") padded = np.concatenate(( np.zeros((filt.shape[0] - nsamples_after - 1,) + stim.shape[1:]), stim, np.zeros((nsamples_after,) + stim.shape[1:])), axis=0) slices = np.fliplr(slicestim(padded, filt.shape[0] - nsamples_after, nsamples_after)) return np.einsum('tx,x->t', flat2d(slices), filt.ravel())
def linear_response(filt, stim, nsamples_after=0): """ Compute the response of a linear filter to a stimulus. Parameters ---------- filt : array_like The linear filter whose response is to be computed. The array should have shape ``(t, ...)``, where ``t`` is the number of time points in the filter and the ellipsis indicates any remaining spatial dimenions. The number of dimensions and the sizes of the spatial dimensions must match that of ``stim``. stim : array_like The stimulus to which the predicted response is computed. The array should have shape (T,...), where ``T`` is the number of time points in the stimulus and the ellipsis indicates any remaining spatial dimensions. The number of dimensions and the sizes of the spatial dimenions must match that of ``filt``. nsamples_after : int, optional The number of acausal points in the filter. Defaults to 0. Returns ------- pred : array_like The predicted linear response. The shape is ``(T - t + 1,)`` where ``T`` is the number of time points in the stimulus, and ``t`` is the number of time points in the filter. This is the valid portion of the convolution between the stimulus and filter. Raises ------ ValueError : If the number of dimensions of ``stim`` and ``filt`` do not match, or if the spatial dimensions differ. Notes ----- Both ``filtertools.sta`` and ``filtertools.revcorr`` can estimate "acausal" components, such as points in the stimulus occuring *after* a spike. The value passed as parameter ``nsamples_after`` must match that value used when calling ``filtertools.sta`` or ``filtertools.revcorr``. """ if (filt.ndim != stim.ndim) or (filt.shape[1:] != stim.shape[1:]): raise ValueError("The filter and stimulus must have the same " + "number of dimensions and match in size along spatial dimensions") slices = slicestim(stim, filt.shape[0] - nsamples_after, nsamples_after) return np.einsum('tx,x->t', flat2d(slices), filt.ravel())
def revcorr(response, stimulus, filter_length): """ Compute the reverse-correlation between a stimulus and a response. This returns the best-fitting linear filter which predicts the given response from the stimulus. It is analogous to the spike-triggered average for continuous variables. ``response`` is most often a membrane potential. Parameters ---------- response : array_like A continuous output response correlated with the stimulus. Must be one-dimensional. stimulus : array_like A input stimulus correlated with the ``response``. Must be of shape ``(t, ...)``, where ``t`` is the time and ``...`` indicates any spatial dimensions. filter_length : int The length of the returned filter, in samples of the ``stimulus`` and ``response`` arrays. Returns ------- filt : array_like An array of shape ``(filter_length, ...)`` containing the best-fitting linear filter which predicts the response from the stimulus. The ellipses indicates spatial dimensions of the filter. Raises ------ ValueError : If the ``stimulus`` and ``response`` arrays are of different shapes. Notes ----- The ``response`` and ``stimulus`` arrays must share the same sampling rate. As the stimulus often has a lower sampling rate, one can use ``stimulustools.upsamplestim`` to upsample it. """ if response.ndim > 1: raise ValueError("The `response` must be 1-dimensional") if response.size != (stimulus.shape[0] - filter_length + 1): msg = "`stimulus` must have {:#d} time points (`response.size` + `filter_length`)" raise ValueError(msg.format(response.size + filter_length + 1)) slices = slicestim(stimulus, filter_length) recovered = np.einsum('tx,t->x', flat2d(slices), response) return recovered.reshape(slices.shape[1:])
def linear_prediction(filt, stim): """ Compute the predicted linear response of a receptive field to a stimulus. Parameters ---------- filt : array_like The linear filter whose response is to be computed. The array should have shape ``(t, ...)``, where ``t`` is the number of time points in the filter and the ellipsis indicates any remaining spatial dimenions. The number of dimensions and the sizes of the spatial dimensions must match that of ``stim``. stim : array_like The stimulus to which the predicted response is computed. The array should have shape (T,...), where ``T`` is the number of time points in the stimulus and the ellipsis indicates any remaining spatial dimensions. The number of dimensions and the sizes of the spatial dimenions must match that of ``filt``. Returns ------- pred : array_like The predicted linear response. The shape is ``(T - t + 1,)`` where ``T`` is the number of time points in the stimulus, and ``t`` is the number of time points in the filter. This is the valid portion of the convolution between the stimulus and filter Raises ------ ValueError : If the number of dimensions of ``stim`` and ``filt`` do not match, or if the spatial dimensions differ. """ if (filt.ndim != stim.ndim) or (filt.shape[1:] != stim.shape[1:]): raise ValueError( "The filter and stimulus must have the same " + "number of dimensions and match in size along spatial dimensions") slices = slicestim(stim, filt.shape[0]) return np.einsum('tx,x->t', flat2d(slices), filt.ravel())
def cov(stimulus, history, nsamples=None, verbose=False): """ Computes a stimulus covariance matrix .. warning:: This is computationally expensive for large stimuli Parameters ---------- stimulus : array_like The spatiotemporal or temporal stimulus to slices. Should have shape (t, ...), where the ellipses indicate any spatial dimensions. history : int Integer number of time points to keep in each slice. Returns ------ stim_cov : array_like Covariance matrix """ stim = slicestim(stimulus, history) return np.cov(flat2d(stim).T)
def test_flat2d(): x = np.random.randn(10, 2, 3, 4) x_ = x.reshape(x.shape[0], -1).copy() assert np.allclose(x_, flat2d(x))
def revcorr(stimulus, response, nsamples_before, nsamples_after=0): """ Compute the reverse-correlation between a stimulus and a response. Parameters ---------- stimulus : array_like A input stimulus correlated with the ``response``. Must be of shape ``(t, ...)``, where ``t`` is the time and ``...`` indicates any spatial dimensions. response : array_like A continuous output response correlated with ``stimulus``. Must be one-dimensional, of size ``t``, the same size as ``stimulus`` along the first axis. Note that the first ``history`` points of the response are ignored, where ``history = nsamples_before + nsamples_after``, in order to only return the portion of the correlation during which the ``stimulus`` and ``response`` completely overlap. nsamples_before : int The maximum negative lag for the correlation between stimulus and response, in samples. nsamples_after : int, optional The maximum positive lag for the correlation between stimulus and response, in samples. Defaults to 0. Returns ------- rc : array_like An array of shape ``(nsamples_before + nsamples_after, ...)`` containing the best-fitting linear filter which predicts the response from the stimulus. The ellipses indicates spatial dimensions of the filter. lags : array_like An array of shape ``(nsamples_before + nsamples_after,)``, which gives the lags, in samples, between ``stimulus`` and ``response`` for the correlation returned in ``rc``. This can be converted to an axis of time (like that returned from ``filtertools.sta``) by multiplying by the sampling period. Raises ------ ValueError : If the ``stimulus`` and ``response`` arrays do not match in size along the first dimension. Notes ----- The ``response`` and ``stimulus`` arrays must share the same sampling rate. As the stimulus often has a lower sampling rate, one can use ``stimulustools.upsample`` to upsample it. Reverse correlation is a method analogous to spike-triggered averaging for continuous response variables, such as a membrane voltage recording. It estimates the stimulus feature that most strongly correlates with the response on average. It is the time-reverse of the standard cross-correlation function, and is defined as: .. math:: c[-k] = \\sum_{n} s[n] r[n - k] The parameter ``k`` is the lag between the two signals in samples. The range of lags computed in this method are determined by ``nsamples_before`` and ``nsamples_after``. Note that, as with ``filtertools.sta``, the values (samples) in the ``lags`` array increase with increasing array index. This means that time is moving forward with increasing array index. Also note that this method assumes an uncorrelated stimulus. If the stimulus is correlated, those will bias the estimated reverse correlation. """ history = nsamples_before + nsamples_after if response.ndim > 1: raise ValueError("The `response` must be 1-dimensional") if response.size != stimulus.shape[0]: raise ValueError('`stimulus` and `response` must match in ' + 'size along the first axis') slices = slicestim(stimulus, nsamples_before, nsamples_after) recovered = np.einsum('tx,t->x', flat2d(slices), response[history - 1:]).reshape(slices.shape[1:]) lags = np.arange(-nsamples_before + 1, nsamples_after + 1) return recovered, lags
def lowranksta(sta_orig, k=10): """ Constructs a rank-k approximation to the given spatiotemporal STA. This is useful for estimating a spatial and temporal kernel for an STA or for denoising. Parameters ---------- sta_orig : array_like 3D STA to be separated, shaped as ``(time, space, space)``. k : int Number of components to keep (rank of the reduced STA). Returns ------- sk : array_like The rank-k estimate of the original STA. u : array_like The top ``k`` temporal components (each column is a component). s : array_like The top ``k`` singular values. v : array_like The top ``k`` spatial components (each row is a component). These components have all spatial dimensions collapsed to one. Notes ----- This method requires that the STA be 3D. To decompose a STA into a temporal and 1-dimensional spatial component, simply promote the STA to 3D before calling this method. Despite the name this method accepts both an STA or a linear filter. The components estimated for one will be flipped versions of the other. """ # work with a copy of the STA (prevents corrupting the input) f = sta_orig.copy() - sta_orig.mean() # Compute the SVD of the full STA assert f.ndim >= 2, "STA must be at least 2-D" u, s, v = np.linalg.svd(flat2d(f), full_matrices=False) # Keep the top k components k = np.min([k, s.size]) u = u[:, :k] s = s[:k] v = v[:k, :] # Compute the rank-k STA sk = (u.dot(np.diag(s).dot(v))).reshape(f.shape) # Ensure that the computed STA components have the correct sign. # The full STA should have positive projection onto first temporal # component of the low-rank STA. sign = np.sign(np.tensordot(u[:, 0], f, axes=1).sum()) u *= sign v *= sign # Return the rank-k approximate STA, and the SVD components return sk, u, s, v