def _freq_vector(f, b, typ='lp'): """ Returns a frequency modulated vector for filtering :param f: frequency vector, uniform and monotonic :param b: 2 bounds array :return: amplitude modulated frequency vector """ filc = fcn_cosine(b)(f) if typ == 'hp': return filc elif typ == 'lp': return 1 - filc
def coverage(trajs, ba=None): """ Computes a coverage volume from :param trajs: dictionary of trajectories from Alyx rest endpoint (one.alyx.rest...) :param ba: ibllib.atlas.BrainAtlas instance :return: 3D np.array the same size as the volume provided in the brain atlas """ # in um. Coverage = 1 below the first value, 0 after the second, cosine taper in between DIST_FCN = [100, 150] ACTIVE_LENGTH_UM = 3.5 * 1e3 MAX_DIST_UM = DIST_FCN[ 1] # max distance around the probe to be searched for if ba is None: ba = atlas.AllenAtlas() def crawl_up_from_tip(ins, d): return (ins.entry - ins.tip) * ( d[:, np.newaxis] / np.linalg.norm(ins.entry - ins.tip)) + ins.tip full_coverage = np.zeros(ba.image.shape, dtype=np.float32).flatten() for p in np.arange(len(trajs)): if p % 20 == 0: print(p / len(trajs)) traj = trajs[p] ins = atlas.Insertion.from_dict(traj) # those are the top and bottom coordinates of the active part of the shank extended # to maxdist d = (np.array([ ACTIVE_LENGTH_UM + MAX_DIST_UM * np.sqrt(2), -MAX_DIST_UM * np.sqrt(2) ]) + TIP_SIZE_UM) top_bottom = crawl_up_from_tip(ins, d / 1e6) # this is the axis that has the biggest deviation. Almost always z axis = np.argmax(np.abs(np.diff(top_bottom, axis=0))) if axis != 2: _logger.warning( f"This works only for 45 degree or vertical tracks so far, skipping" f" {ins}") continue # sample the active track path along this axis tbi = ba.bc.xyz2i(top_bottom) nz = tbi[1, axis] - tbi[0, axis] + 1 ishank = np.round( np.array([ np.linspace(tbi[0, i], tbi[1, i], nz) for i in np.arange(3) ]).T).astype(np.int32) # creates a flattened "column" of candidate volume indices around the track # around each sample get an horizontal square slice of nx *2 +1 and ny *2 +1 samples # flatten the corresponding xyz indices and compute the min distance to the track only # for those nx = int( np.floor(MAX_DIST_UM / 1e6 / np.abs(ba.bc.dxyz[0]) * np.sqrt(2) / 2)) * 2 + 1 ny = int( np.floor(MAX_DIST_UM / 1e6 / np.abs(ba.bc.dxyz[1]) * np.sqrt(2) / 2)) * 2 + 1 ixyz = np.stack([ v.flatten() for v in np.meshgrid(np.arange(-nx, nx + 1), np.arange(-ny, ny + 1), np.arange(nz)) ]).T ixyz[:, 0] = ishank[ixyz[:, 2], 0] + ixyz[:, 0] ixyz[:, 1] = ishank[ixyz[:, 2], 1] + ixyz[:, 1] ixyz[:, 2] = ishank[ixyz[:, 2], 2] # if any, remove indices that lie outside of the volume bounds iok = np.logical_and(0 <= ixyz[:, 0], ixyz[:, 0] < ba.bc.nx) iok &= np.logical_and(0 <= ixyz[:, 1], ixyz[:, 1] < ba.bc.ny) iok &= np.logical_and(0 <= ixyz[:, 2], ixyz[:, 2] < ba.bc.nz) ixyz = ixyz[iok, :] # get the minimum distance to the trajectory, to which is applied the cosine taper xyz = np.c_[ba.bc.xscale[ixyz[:, 0]], ba.bc.yscale[ixyz[:, 1]], ba.bc.zscale[ixyz[:, 2]]] sites_bounds = crawl_up_from_tip( ins, (np.array([ACTIVE_LENGTH_UM, 0]) + TIP_SIZE_UM) / 1e6) mdist = ins.trajectory.mindist(xyz, bounds=sites_bounds) coverage = 1 - fcn_cosine(np.array(DIST_FCN) / 1e6)(mdist) # remap to the coverage volume full_coverage[ba._lookup_inds(ixyz)] += coverage full_coverage = full_coverage.reshape(ba.image.shape) full_coverage[ba.label == 0] = np.nan return full_coverage