123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import numpy as np
- import scipy.ndimage as ndi
- from scipy import signal
- from skimage.registration import phase_cross_correlation
- from skimage.transform import warp_polar, rotate
- def bins2meters(x, y, xy_range, bin_size=0.02):
- x_in_m = x*bin_size - (xy_range[1] - xy_range[0])/2
- y_in_m = y*bin_size - (xy_range[3] - xy_range[2])/2
- return x_in_m, y_in_m
- def cart2pol(x, y):
- rho = np.sqrt(x**2 + y**2)
- phi = np.arctan2(y, x)
- return rho, phi
- def pol2cart(rho, phi):
- x = rho * np.cos(phi)
- y = rho * np.sin(phi)
- return (x, y)
- def gaussian_kernel_2D(sigma=0.1):
- lin_profile = np.linspace(-10, 10, 50)
- bump = np.exp(-sigma * lin_profile**2)
- bump /= np.trapz(bump) # normalize to 1
- return bump[:, np.newaxis] * bump[np.newaxis, :]
- def place_field_2D(pos, pos_firing, sampling_rate, bin_size=0.02, sigma=0.15, xy_range=None):
- """
- :param pos: x, y positions sampled at sampling_rate
- :param pos_firing: positions when spikes occured
- :param sampling_rate: sampling rate of above in Hz
- :param bin_size: size of the squared bin to calculate firing / occupancy,
- same units as in pos, e.g. meters
- :param sigma: standard deviation, for smoothing
- :param range: [xmin, xmax, ymin, ymax] - array of X and Y boundaries to limit the map
- :return:
- occupancy_map, spiking_map, firing_map, s_firing_map
- """
- x_min = xy_range[0] if xy_range is not None else pos[:, 0].min()
- x_max = xy_range[1] if xy_range is not None else pos[:, 0].max()
- y_min = xy_range[2] if xy_range is not None else pos[:, 1].min()
- y_max = xy_range[3] if xy_range is not None else pos[:, 1].max()
- x_range = x_max - x_min
- y_range = y_max - y_min
- y_bin_count = int(np.ceil(y_range / bin_size))
- x_bin_count = int(np.ceil(x_range / bin_size))
- pos_range = np.array([[x_min, x_max], [y_min, y_max]])
- occup, x_edges, y_edges = np.histogram2d(pos[:, 0], pos[:, 1], bins=[x_bin_count, y_bin_count], range=pos_range)
- occupancy_map = occup / sampling_rate
- # spiking map
- spiking_map, xs_edges, ys_edges = np.histogram2d(pos_firing[:, 0], pos_firing[:, 1], bins=[x_bin_count, y_bin_count], range=pos_range)
- # firing = spiking / occupancy
- firing_map = np.divide(spiking_map, occupancy_map, out=np.zeros_like(spiking_map, dtype=float), where=occupancy_map!=0)
- # apply gaussial smoothing
- kernel = gaussian_kernel_2D(sigma)
- s_firing_map = signal.convolve2d(firing_map, kernel, mode='same')
- occupancy_map = signal.convolve2d(occupancy_map, kernel, mode='same')
- return occupancy_map, spiking_map, firing_map, s_firing_map
- def map_stats(f_map, o_map):
- """
- f_map: 2D matrix of unit firing rate map
- o_map: 2D matrix of animal occupancy map
- """
- o_map_norm = o_map / o_map.sum()
- meanrate = np.nansum(np.nansum(np.multiply(f_map, o_map_norm)))
- meansquarerate = np.nansum(np.nansum(np.multiply(f_map ** 2, o_map_norm)))
- maxrate = np.max(f_map)
- sparsity = 0 if meansquarerate == 0 else meanrate**2 / meansquarerate
- selectivity = 0 if meanrate == 0 else maxrate / meanrate
- peak_FR = f_map.max()
- spatial_info = 0 # default value
- if peak_FR > 0:
- f_map_norm = f_map / meanrate
- tmp = np.multiply(o_map_norm, f_map_norm)
- tmp = np.multiply(tmp, np.log2(f_map_norm, where=f_map_norm > 0))
- spatial_info = np.nansum(tmp)
- return sparsity, selectivity, spatial_info, peak_FR
- def get_field_patches(f_map, threshold=0.5):
- """
- f_map: 2D matrix of unit firing rate map
- return: sorted field patches, where the largest field has index 1
- se also: https://exeter-data-analytics.github.io/python-data/skimage.html
- """
- patch_idxs = f_map > threshold*f_map.max()
- # label individual patches
- p_labels, _ = ndi.label(patch_idxs)
- # sort patches according to the patch size
- sort_idxs = (-1*np.bincount(p_labels.flat)[1:]).argsort()
- p_idxs = np.unique(p_labels)[1:]
- p_labels_sorted = np.zeros(p_labels.shape, dtype=np.int32)
- for i, idx in enumerate(sort_idxs):
- p_labels_sorted[p_labels == p_idxs[idx]] = i + 1
- return p_labels_sorted
- def best_match_rotation_polar(map_A, map_B):
- """ From
- https://scikit-image.org/docs/stable/auto_examples/registration/plot_register_rotation.html
- TODO explore log-polar transform
- """
- radius = map_A.shape[0]/2
-
- A_polar = warp_polar(map_A, radius=radius)
- B_polar = warp_polar(map_B, radius=radius)
-
- # this way doesn't work good
- #shifts, error, phasediff = phase_cross_correlation(A_polar, B_polar)
-
- ccf = signal.correlate(A_polar.T, B_polar.T, mode='same').sum(axis=0)
-
- return ccf, -(np.argmax(ccf) - 180)
- def best_match_rotation_pearson(map_A, map_B, delta=6):
- if not int(delta) == delta:
- raise ValueError('delta should be of type int')
-
- n = map_A.shape[0]
- a, b, r = n/2, n/2, int(0.93*n/2)
- y,x = np.ogrid[-a:n-a, -b:n-b]
- mask = x*x + y*y <= r*r
-
- angles_num = int(360/delta)
- angles = np.linspace(0, (angles_num - 1) * delta, angles_num) # in degrees
-
- corrs = np.zeros(len(angles))
- for i, alpha in enumerate(angles):
- corr_2D = np.corrcoef(map_A, ndi.rotate(map_B, alpha, reshape=False))
- #print(corr_2D.shape, mask.shape)
- #corrs[i] = corr_2D[mask].mean()
- corrs[i] = corr_2D.mean()
- # normalize before correlation?
-
- return angles, corrs, np.argmax(corrs)*6 # actual value
- def get_positions_relative_to(pos_alo, HD, poi):
- """
- pos_allo 2D array of alloentric animal positions X, Y (shape Nx2)
- HD vector of HD angles (in rad.) for each pos_allo (shape N)
- poi point of interest (x, y) relative to which to compute egocentric coords.
- """
- pos_poi = poi - pos_alo
- R = np.linalg.norm(pos_poi, axis=1) # distance from animal to the POI
- phi_alo = (np.degrees(np.arctan2(pos_poi[:, 1], pos_poi[:, 0])) - HD) % 360 # angle to POI in allocentric frame, in deg.
- phi_ego = phi_alo - np.rad2deg(HD) # angle to POI in egocentric frame, in deg.
- phi_ego = np.deg2rad(phi_ego)
- return np.array([np.multiply(R, np.cos(phi_ego)), np.multiply(R, np.sin(phi_ego))]).T
|