def interp_filt_position(x, y, tm, box_xlen=1 * pq.m, box_ylen=1 * pq.m, pos_fs=100 * pq.Hz, f_cut=10 * pq.Hz): """ Calculeate head direction in angles or radians for time t Parameters ---------- x : quantities.Quantity array in m 1d vector of x positions y : quantities.Quantity array in m 1d vector of y positions tm : quantities.Quantity array in s 1d vector of times at x, y positions pos_fs : quantities scalar in Hz return radians Returns ------- out : angles, resized t """ import scipy.signal as ss assert len(x) == len(y) == len(tm), 'x, y, t must have same length' is_quantities([x, y, tm], 'vector') is_quantities([pos_fs, box_xlen, box_ylen, f_cut], 'scalar') spat_dim = x.units t = np.arange(tm.min(), tm.max() + 1. / pos_fs, 1. / pos_fs) * tm.units x = np.interp(t, tm, x) y = np.interp(t, tm, y) # rapid head movements will contribute to velocity artifacts, # these can be removed by low-pass filtering # see http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1876586/ # code addapted from Espen Hagen b, a = ss.butter(N=1, Wn=f_cut * 2 / pos_fs) # zero phase shift filter x = ss.filtfilt(b, a, x) * spat_dim y = ss.filtfilt(b, a, y) * spat_dim # we tolerate small interpolation errors x[(x > -1e-3) & (x < 0.0)] = 0.0 * spat_dim y[(y > -1e-3) & (y < 0.0)] = 0.0 * spat_dim if np.isnan(x).any() and np.isnan(y).any(): raise ValueError('nans found in position, ' + 'x nans = %i, y nans = %i' % (sum(np.isnan(x)), sum(np.isnan(y)))) if (x.min() < 0 or x.max() > box_xlen or y.min() < 0 or y.max() > box_ylen): raise ValueError( "Interpolation produces path values " + "outside box: min [x, y] = [{}, {}], ".format(x.min(), y.min()) + "max [x, y] = [{}, {}]".format(x.max(), y.max())) R = np.sqrt(np.diff(x)**2 + np.diff(y)**2) V = R / np.diff(t) print('Maximum speed {}'.format(V.max())) return x, y, t
def rescale_linear_track_2d_to_1d(x, y, end_0=[], end_1=[]): """ Take x, y coordinates of linear track data, rescale to 1-d. Parameters ---------- x : quantities.Quantity array in m 1d vector of x positions y : quantities.Quantity array in m 1d vector of x positions t : quantities.Quantity array in s 1d vector of times at x, y positions end_0: quantities.Quantity array in m linear track endpoint 1, in x, y end_1: quantities.Quantity array in m linear track endpoint 2, in x, y Returns ------- out : 1d vector """ from exana.misc.tools import is_quantities if not all([len(var) == len(var2) for var in [x, y] for var2 in [x, y]]): raise ValueError('x, y, t must have same number of elements') is_quantities([x, y], 'vector') x = x.rescale('m').magnitude y = y.rescale('m').magnitude if len(end_0) != 2 or len(end_1) != 2: raise ValueError('end_0 and end_1 must be 2d vectors') end_0 = end_0.rescale('m').magnitude end_1 = end_1.rescale('m').magnitude # shift coordinate system to have end_0 as origin x -= end_0[0] y -= end_0[1] # calculate angle of track v_x_axis = np.array([1, 0]) theta = angle_between_vectors(end_1-end_0, v_x_axis) # rotate clockwise rot_mat = np.array([[np.cos(-theta), -np.sin(-theta)], [np.sin(-theta), np.cos(-theta)]]) x_rot = [] for x_i, y_i in zip(x, y): [x_rot_i, _] = np.dot(rot_mat, np.array([[x_i], [y_i]])) x_rot.append(x_rot_i.item()) # shift x_rot so that np.min(x_rot) == 0 x_rot -= np.min(x_rot) # only consider x_rot in output return x_rot*pq.m
def select_best_position(x1, y1, t1, x2, y2, t2, speed_filter=5 * pq.m / pq.s): """ selects position data with least nan after speed filtering Parameters ---------- x1 : quantities.Quantity array in m 1d vector of x positions from LED 1 y1 : quantities.Quantity array in m 1d vector of x positions from LED 1 t1 : quantities.Quantity array in s 1d vector of times from LED 1 at x, y positions x2 : quantities.Quantity array in m 1d vector of x positions from LED 2 y2 : quantities.Quantity array in m 1d vector of x positions from LED 2 t2 : quantities.Quantity array in s 1d vector of times from LED 2 at x, y positions speed_filter : None or quantities in m/s threshold filter for translational speed """ is_quantities([x1, y1, t1, x2, y2, t2], 'vector') x1, y1, t1, x2, y2, t2 = _cut_to_same_len(x1, y1, t1, x2, y2, t2) is_quantities(speed_filter, 'scalar') measurements1 = len(x1) measurements2 = len(x2) x1, y1, t1 = rm_nans(x1, y1, t1) x2, y2, t2 = rm_nans(x2, y2, t2) if speed_filter is not None: x1, y1, t1 = velocity_threshold(x1, y1, t1, speed_filter) x2, y2, t2 = velocity_threshold(x2, y2, t2, speed_filter) if len(x1) > len(x2): print('Removed %.2f %% invalid measurements in path' % ((1. - len(x1) / float(measurements1)) * 100.)) x = x1 y = y1 t = t1 else: print('Removed %.2f %% invalid measurements in path' % ((1. - len(x2) / float(measurements2)) * 100.)) x = x2 y = y2 t = t2 return x, y, t
def velocity_threshold(x, y, t, threshold): """ Removes values above threshold Parameters ---------- x : quantities.Quantity array in m 1d vector of x positions y : quantities.Quantity array in m 1d vector of y positions t : quantities.Quantity array in s 1d vector of times at x, y positions threshold : float """ assert len(x) == len(y) == len(t), 'x, y, t must have same length' is_quantities([x, y, t], 'vector') is_quantities(threshold, 'scalar') r = np.sqrt(np.diff(x)**2 + np.diff(y)**2) v = np.divide(r, np.diff(t)) speed_lim = np.concatenate(([False], v > threshold), axis=0) x[speed_lim] = np.nan * x.units y[speed_lim] = np.nan * y.units x, y, t = rm_nans(x, y, t) return x, y, t
def gridness(rate_map, box_xlen, box_ylen, return_acorr=False, step_size=0.1*pq.m): '''Calculates gridness of a rate map. Calculates the normalized autocorrelation (A) of a rate map B where A is given as A = 1/n\Sum_{x,y}(B - \bar{B})^{2}/\sigma_{B}^{2}. Further, the Pearsson's product-moment correlation coefficients is calculated between A and A_{rot} rotated 30 and 60 degrees. Finally the gridness is calculated as the difference between the minimum of coefficients at 60 degrees and the maximum of coefficients at 30 degrees i.e. gridness = min(r60) - max(r30). In order to focus the analysis on symmetry of A the the central and the outer part of the gridness is maximized by increasingly mask A at steps of ``step_size``. This function is inspired by Lukas Solankas gridcells package from Matt Nolans lab. Parameters ---------- rate_map : numpy.ndarray box_xlen : quantities scalar in m side length of quadratic box step_size : quantities scalar in m step size in masking return_acorr : bool return autocorrelation map or not Returns ------- out : gridness, (autocorrelation map) ''' from scipy.ndimage.interpolation import rotate import numpy.ma as ma from exana.misc.tools import (is_quantities, fftcorrelate2d, masked_corrcoef2d) is_quantities([box_xlen, box_ylen, step_size], 'scalar') box_xlen = box_xlen.rescale('m').magnitude box_ylen = box_ylen.rescale('m').magnitude step_size = step_size.rescale('m').magnitude tmp_map = rate_map.copy() tmp_map[~np.isfinite(tmp_map)] = 0 acorr = fftcorrelate2d(tmp_map, tmp_map, mode='full', normalize=True) rows, cols = acorr.shape b_x = np.linspace(-box_xlen/2., box_xlen/2., rows) b_y = np.linspace(-box_ylen/2., box_ylen/2., cols) B_x, B_y = np.meshgrid(b_x, b_y) grids = [] acorrs = [] # TODO find size of middle gaussian and exclude for outer in np.arange(box_xlen/4, box_xlen/2, step_size): m_acorr = ma.masked_array(acorr, mask=np.sqrt(B_x**2 + B_y**2) > outer) for inner in np.arange(0, box_xlen/4, step_size): m_acorr = \ ma.masked_array(m_acorr, mask=np.sqrt(B_x**2 + B_y**2) < inner) angles = range(30, 180+30, 30) corr = [] # Rotate and compute correlation coefficient for angle in angles: rot_acorr = rotate(m_acorr, angle, reshape=False) corr.append(masked_corrcoef2d(rot_acorr, m_acorr)[0, 1]) r60 = corr[1::2] r30 = corr[::2] grids.append(np.min(r60) - np.max(r30)) acorrs.append(m_acorr) if return_acorr: return max(grids), acorr, # acorrs[grids.index(max(grids))] else: return max(grids)
def spatial_rate_map(x, y, t, sptr, binsize=0.01*pq.m, box_xlen=1*pq.m, box_ylen=1*pq.m, mask_unvisited=True, convolve=True, return_bins=False, smoothing=0.02): """Divide a 2D space in bins of size binsize**2, count the number of spikes in each bin and divide by the time spent in respective bins. The map can then be convolved with a gaussian kernel of size csize determined by the smoothing factor, binsize and box_xlen. Parameters ---------- sptr : neo.SpikeTrain x : quantities.Quantity array in m 1d vector of x positions y : quantities.Quantity array in m 1d vector of y positions t : quantities.Quantity array in s 1d vector of times at x, y positions binsize : float spatial binsize box_xlen : quantities scalar in m side length of quadratic box mask_unvisited: bool mask bins which has not been visited by nans convolve : bool convolve the rate map with a 2D Gaussian kernel Returns ------- out : rate map if return_bins = True out : rate map, xbins, ybins """ from exana.misc.tools import is_quantities if not all([len(var) == len(var2) for var in [x,y,t] for var2 in [x,y,t]]): raise ValueError('x, y, t must have same number of elements') if box_xlen < x.max() or box_ylen < y.max(): raise ValueError('box length must be larger or equal to max path length') from decimal import Decimal as dec decimals = 1e10 remainderx = dec(float(box_xlen)*decimals) % dec(float(binsize)*decimals) remaindery = dec(float(box_ylen)*decimals) % dec(float(binsize)*decimals) if remainderx != 0 or remaindery != 0: raise ValueError('the remainder should be zero i.e. the ' + 'box length should be an exact multiple ' + 'of the binsize') is_quantities([x, y, t], 'vector') is_quantities(binsize, 'scalar') t = t.rescale('s') box_xlen = box_xlen.rescale('m').magnitude box_ylen = box_ylen.rescale('m').magnitude binsize = binsize.rescale('m').magnitude x = x.rescale('m').magnitude y = y.rescale('m').magnitude # interpolate one extra timepoint t_ = np.array(t.tolist() + [t.max() + np.median(np.diff(t))]) * pq.s spikes_in_bin, _ = np.histogram(sptr.times, t_) time_in_bin = np.diff(t_.magnitude) xbins = np.arange(0, box_xlen + binsize, binsize) ybins = np.arange(0, box_ylen + binsize, binsize) ix = np.digitize(x, xbins, right=True) iy = np.digitize(y, ybins, right=True) spike_pos = np.zeros((xbins.size, ybins.size)) time_pos = np.zeros((xbins.size, ybins.size)) for n in range(len(x)): spike_pos[ix[n], iy[n]] += spikes_in_bin[n] time_pos[ix[n], iy[n]] += time_in_bin[n] # correct for shifting of map since digitize returns values at right edges spike_pos = spike_pos[1:, 1:] time_pos = time_pos[1:, 1:] with np.errstate(divide='ignore', invalid='ignore'): rate = np.divide(spike_pos, time_pos) if convolve: rate[np.isnan(rate)] = 0. # for convolution from astropy.convolution import Gaussian2DKernel, convolve_fft csize = (box_xlen / binsize) * smoothing kernel = Gaussian2DKernel(csize) rate = convolve_fft(rate, kernel) # TODO edge correction if mask_unvisited: was_in_bin = np.asarray(time_pos, dtype=bool) rate[np.invert(was_in_bin)] = np.nan if return_bins: return rate.T, xbins, ybins else: return rate.T
def nvisits_map(x, y, t, binsize=0.01*pq.m, box_xlen=1*pq.m, box_ylen=1*pq.m, return_bins=False): '''Divide a 2D space in bins of size binsize**2, count the number of visits in each bin. The map can be convolved with a gaussian kernel of size determined by the smoothing factor, binsize and box_xlen. Parameters ---------- x : quantities.Quantity array in m 1d vector of x positions y : quantities.Quantity array in m 1d vector of y positions t : quantities.Quantity array in s 1d vector of times at x, y positions binsize : float spatial binsize box_xlen : quantities scalar in m side length of quadratic box Returns ------- nvisits_map : numpy.ndarray if return_bins = True out : nvisits_map, xbins, ybins ''' from exana.misc.tools import is_quantities if not all([len(var) == len(var2) for var in [ x, y, t] for var2 in [x, y, t]]): raise ValueError('x, y, t must have same number of elements') if box_xlen < x.max() or box_ylen < y.max(): raise ValueError( 'box length must be larger or equal to max path length') from decimal import Decimal as dec decimals = 1e10 remainderx = dec(float(box_xlen)*decimals) % dec(float(binsize)*decimals) remaindery = dec(float(box_ylen)*decimals) % dec(float(binsize)*decimals) if remainderx != 0 or remaindery != 0: raise ValueError('the remainder should be zero i.e. the ' + 'box length should be an exact multiple ' + 'of the binsize') is_quantities([x, y, t], 'vector') is_quantities(binsize, 'scalar') t = t.rescale('s') box_xlen = box_xlen.rescale('m').magnitude box_ylen = box_ylen.rescale('m').magnitude binsize = binsize.rescale('m').magnitude x = x.rescale('m').magnitude y = y.rescale('m').magnitude xbins = np.arange(0, box_xlen + binsize, binsize) ybins = np.arange(0, box_ylen + binsize, binsize) ix = np.digitize(x, xbins, right=True) iy = np.digitize(y, ybins, right=True) nvisits_map = np.zeros((xbins.size, ybins.size)) for n in range(len(x)): if n == 0: nvisits_map[ix[n], iy[n]] = 1 else: if ix[n-1] != ix[n] or iy[n-1] != iy[n]: nvisits_map[ix[n], iy[n]] += 1 # correct for shifting of map since digitize returns values at right edges nvisits_map = nvisits_map[1:, 1:] if return_bins: return nvisits_map.T, xbins, ybins else: return nvisits_map.T