Пример #1
0
    def __init__(self, xyz_picks, chn_depths=None, track_prev=None,
                 feature_prev=None, brain_atlas=None):

        if not brain_atlas:
            self.brain_atlas = atlas.AllenAtlas(25)
        else:
            self.brain_atlas = brain_atlas

        self.xyz_track, self.track_extent = self.get_insertion_track(xyz_picks)
        self.chn_depths = chn_depths
        if np.any(track_prev):
            self.track_init = track_prev
            self.feature_init = feature_prev
        else:
            self.track_init = np.copy(self.track_extent)
            self.feature_init = np.copy(self.track_extent)

        self.sampling_trk = np.arange(self.track_extent[0],
                                      self.track_extent[-1] - 10 * 1e-6, 10 * 1e-6)
        self.xyz_samples = histology.interpolate_along_track(self.xyz_track,
                                                             self.sampling_trk -
                                                             self.sampling_trk[0])

        self.region, self.region_label, self.region_colour, self.region_id\
            = self.get_histology_regions(self.xyz_samples, self.sampling_trk, self.brain_atlas)
Пример #2
0
 def __init__(self, brain_atlas=None, ap_um=0):
     self.ap_um = ap_um
     # load the brain atlas
     if brain_atlas is None:
         self.brain_atlas = atlas.AllenAtlas(res_um=25)
     else:
         self.brain_atlas = brain_atlas
Пример #3
0
def plot3d_all(trajectories, tracks, brain_atlas=None):
    """
    Plot all tracks on a single 2d slice
    :param trajectories: dictionary output of the Alyx REST query on trajectories
    :param tracks:
    :return:
    """
    from mayavi import mlab
    brain_atlas = brain_atlas or atlas.AllenAtlas()
    src = mlab.pipeline.scalar_field(brain_atlas.label)
    mlab.pipeline.iso_surface(src, contours=[
        0.5,
    ], opacity=0.3)

    pts = []
    for xyz in tracks['xyz']:
        mlapdv = brain_atlas.bc.xyz2i(xyz)
        pts.append(
            mlab.plot3d(mlapdv[:, 1], mlapdv[:, 0], mlapdv[:, 2],
                        line_width=3))

    plt_trj = []
    for trj in trajectories:
        ins = atlas.Insertion.from_dict(trj, brain_atlas=brain_atlas)
        mlapdv = brain_atlas.bc.xyz2i(ins.xyz)
        plt = mlab.plot3d(mlapdv[:, 1],
                          mlapdv[:, 0],
                          mlapdv[:, 2],
                          line_width=3,
                          color=(1., 0., 1.))
        plt_trj.append(plt)
Пример #4
0
def register_track_files(path_tracks,
                         one=None,
                         overwrite=False,
                         brain_atlas=None):
    """
    :param path_tracks: path to directory containing tracks; also works with a single file name
    :param one:
    :return:
    """
    brain_atlas = brain_atlas or atlas.AllenAtlas()
    glob_pattern = "*_probe*_pts*.csv"
    path_tracks = Path(path_tracks)

    if not path_tracks.is_dir():
        track_files = [path_tracks]
    else:
        track_files = list(path_tracks.rglob(glob_pattern))
        track_files.sort()

    assert path_tracks.exists()
    assert one

    ntracks = len(track_files)
    for ind, track_file in enumerate(track_files):
        # Nomenclature expected:
        # '{yyyy-mm-dd}}_{nickname}_{session_number}_{probe_label}_pts.csv'
        # beware: there may be underscores in the subject nickname

        search_filter = _parse_filename(track_file)
        probe = one.alyx.rest('insertions',
                              'list',
                              no_cache=True,
                              **search_filter)
        if len(probe) == 0:
            eid = one.search(subject=search_filter['subject'],
                             date_range=search_filter['date'],
                             number=search_filter['experiment_number'])
            if len(eid) == 0:
                raise Exception(f"No session found {track_file.name}")
            insertion = {'session': eid[0], 'name': search_filter['name']}
            probe = one.alyx.rest('insertions', 'create', data=insertion)
        elif len(probe) == 1:
            probe = probe[0]
        else:
            raise ValueError("Multiple probes found.")
        probe_id = probe['id']
        try:
            xyz_picks = load_track_csv(track_file, brain_atlas=brain_atlas)
            register_track(probe_id,
                           xyz_picks,
                           one=one,
                           overwrite=overwrite,
                           brain_atlas=brain_atlas)
        except Exception as e:
            _logger.error(str(track_file))
            raise e
        _logger.info(f"{ind + 1}/{ntracks}, {str(track_file)}")
Пример #5
0
def get_brain_regions(xyz, channels_positions=None, brain_atlas=None):
    """
    :param xyz: numpy array of 3D coordinates corresponding to a picked track or a trajectory
    the deepest point is assumed to be the tip.
    :param channels_positions:
    :param brain_atlas:
    :return: brain_regions (associated to each channel),
             insertion (object atlas.Insertion, defining 2 points of entries
             (tip and end of probe))
    """
    """
    this is the depth along the probe (from the first point which is the deepest labeled point)
    Due to the blockiness, depths may not be unique along the track so it has to be prepared
    """

    brain_atlas = brain_atlas or atlas.AllenAtlas(25)
    if channels_positions is None:
        geometry = trace_header(version=1)
        channels_positions = np.c_[geometry['x'], geometry['y']]

    xyz = xyz[np.argsort(xyz[:, 2]), :]
    d = atlas.cart2sph(xyz[:, 0] - xyz[0, 0], xyz[:, 1] - xyz[0, 1],
                       xyz[:, 2] - xyz[0, 2])[0]
    indsort = np.argsort(d)
    xyz = xyz[indsort, :]
    d = d[indsort]
    iduplicates = np.where(np.diff(d) == 0)[0]
    xyz = np.delete(xyz, iduplicates, axis=0)
    d = np.delete(d, iduplicates, axis=0)

    assert np.all(np.diff(d) > 0), "Depths should be strictly increasing"

    # Get the probe insertion from the coordinates
    insertion = atlas.Insertion.from_track(xyz, brain_atlas)

    # Interpolate channel positions along the probe depth and get brain locations
    TIP_SIZE_UM = 200
    xyz_channels = interpolate_along_track(
        xyz, (channels_positions[:, 1] + TIP_SIZE_UM) / 1e6)

    # get the brain regions
    brain_regions = brain_atlas.regions.get(
        brain_atlas.get_labels(xyz_channels))
    brain_regions['xyz'] = xyz_channels
    brain_regions['lateral'] = channels_positions[:, 0]
    brain_regions['axial'] = channels_positions[:, 1]
    assert np.unique([len(brain_regions[k]) for k in brain_regions]).size == 1

    return brain_regions, insertion
Пример #6
0
def register_aligned_track(probe_id,
                           xyz_channels,
                           chn_coords=None,
                           one=None,
                           overwrite=False,
                           channels=True,
                           brain_atlas=None):
    """
    Register ephys aligned trajectory and channel locations to Alyx
    Here we update Alyx models on the database in 2 steps
    1) The trajectory computed from the final electrode channel locations
    2) Channel locations are set to the trajectory
    """
    assert one
    brain_atlas = brain_atlas or atlas.AllenAtlas(25)
    if chn_coords is None:
        geometry = trace_header(version=1)
        chn_coords = np.c_[geometry['x'], geometry['y']]

    insertion = atlas.Insertion.from_track(xyz_channels, brain_atlas)
    tdict = create_trajectory_dict(probe_id,
                                   insertion,
                                   provenance='Ephys aligned histology track')

    hist_traj = one.alyx.rest('trajectories',
                              'list',
                              probe_insertion=probe_id,
                              provenance='Ephys aligned histology track',
                              no_cache=True)
    # if the trajectory exists, remove it, this will cascade delete existing channel locations
    if len(hist_traj):
        if overwrite:
            one.alyx.rest('trajectories', 'delete', id=hist_traj[0]['id'])
        else:
            raise FileExistsError(
                'The session already exists, however overwrite is set to False.'
                'If you want to overwrite, set overwrite=True.')
    hist_traj = one.alyx.rest('trajectories', 'create', data=tdict)

    if channels:
        brain_regions = brain_atlas.regions.get(
            brain_atlas.get_labels(xyz_channels))
        brain_regions['xyz'] = xyz_channels
        brain_regions['lateral'] = chn_coords[:, 0]
        brain_regions['axial'] = chn_coords[:, 1]
        assert np.unique([len(brain_regions[k])
                          for k in brain_regions]).size == 1
        channel_dict = create_channel_dict(hist_traj, brain_regions)
        one.alyx.rest('channels', 'create', data=channel_dict)
Пример #7
0
    def get_histology_regions(xyz_coords,
                              depth_coords,
                              brain_atlas=None,
                              mapping='Allen'):
        """
        Find all brain regions and their boundaries along the depth of probe or track
        :param xyz_coords: 3D coordinates of points along probe or track
        :type xyz_coords: np.array((n_points, 3)) n_points: no. of points
        :param depth_coords: depth along probe or track where each xyz_coord is located
        :type depth_coords: np.array((n_points))
        :return region: coordinates bounding each brain region
        :type region: np.array((n_bound, 2)) n_bound: no. of histology boundaries
        :return region_label: label for each brain region and coordinate of where to place label
        :type region_label: np.array((n_bound)) of tuples (coordinate - float, label - str)
        :return region_colour: allen atlas rgb colour for each brain region along track
        :type region_colour: np.array((n_bound, 3))
        :return region_id: allen atlas id for each brain region along track
        :type region_id: np.array((n_bound))
        """
        if not brain_atlas:
            brain_atlas = atlas.AllenAtlas(25)

        region_ids = brain_atlas.get_labels(xyz_coords, mapping=mapping)
        region_info = brain_atlas.regions.get(region_ids)
        boundaries = np.where(np.diff(region_info.id))[0]
        region = np.empty((boundaries.size + 1, 2))
        region_label = np.empty((boundaries.size + 1, 2), dtype=object)
        region_id = np.empty((boundaries.size + 1, 1), dtype=int)
        region_colour = np.empty((boundaries.size + 1, 3), dtype=int)
        for bound in np.arange(boundaries.size + 1):
            if bound == 0:
                _region = np.array([0, boundaries[bound]])
            elif bound == boundaries.size:
                _region = np.array(
                    [boundaries[bound - 1], region_info.id.size - 1])
            else:
                _region = np.array([boundaries[bound - 1], boundaries[bound]])
            _region_colour = region_info.rgb[_region[1]]
            _region_label = region_info.acronym[_region[1]]
            _region_id = region_info.id[_region[1]]
            _region = depth_coords[_region]
            _region_mean = np.mean(_region)
            region[bound, :] = _region
            region_colour[bound, :] = _region_colour
            region_id[bound, :] = _region_id
            region_label[bound, :] = (_region_mean, _region_label)

        return region, region_label, region_colour, region_id
Пример #8
0
def plot_probe_trajectory_atlas_sagittal(x,
                                         y,
                                         project='ibl_neuropixel_brainwide_01',
                                         remove_primary_axis=False):
    """Plot sagittal slice of Atlas along the planned probe trajectory.
    
    A tilted sagittal slice of Atlas is made along planned probe trajectory,
    at [x,y], from project.
    
    
    """
    from one.api import ONE
    import ibllib.atlas as atlas
    from ibllib.atlas import Insertion
    import atlaselectrophysiology.load_histology as hist
    import numpy as np

    import sys

    import matplotlib.pyplot as plt

    # connect to ONE
    one = ONE()

    # get list of all trajectories at planned [x,y], for project
    trajs = one.alyx.rest('trajectories',
                          'list',
                          x=x,
                          y=y,
                          project=project,
                          provenance='Planned')

    # get insertion object from ANY (the first) trajectory
    ins = Insertion.from_dict(trajs[0])

    axis_labels = np.array(['ml (µm)', 'dv (µm)', 'ap (µm)'])
    fig1, ax1 = plt.subplots()  # new figure and axes objects - SAGITTAL

    # get CCF brain atlas for generating the figures:
    brain_atlas = atlas.AllenAtlas(res_um=25)

    ax1 = brain_atlas.plot_tilted_slice(ins.xyz, axis=0, ax=ax1)  # SAGITTAL

    if remove_primary_axis:
        ax1.get_yaxis().set_visible(False)

    return fig1  # return the sagittal plot!
Пример #9
0
def coverage_grid(xyz_channels, spacing=500, ba=None):

    if ba is None:
        ba = atlas.AllenAtlas()

    def _get_scale_and_indices(v, bin, lim):
        _lim = [np.min(lim), np.max(lim)]
        # if bin is a nonzero scalar, this is a bin size: create scale and indices
        if np.isscalar(bin) and bin != 0:
            scale = np.arange(_lim[0], _lim[1] + bin / 2, bin)
            ind = (np.floor((v - _lim[0]) / bin)).astype(np.int64)
            if lim[0] > lim[1]:
                scale = scale[::-1]
                ind = (scale.size - 1) - ind
        # if bin == 0, aggregate over unique values
        else:
            scale, ind = np.unique(v, return_inverse=True)
        return scale, ind

    xlim = ba.bc.xlim
    ylim = ba.bc.ylim
    zlim = ba.bc.zlim

    xscale, xind = _get_scale_and_indices(xyz_channels[:, 0], spacing / 1e6,
                                          xlim)
    yscale, yind = _get_scale_and_indices(xyz_channels[:, 1], spacing / 1e6,
                                          ylim)
    zscale, zind = _get_scale_and_indices(xyz_channels[:, 2], spacing / 1e6,
                                          zlim)

    # aggregate by using bincount on absolute indices for a 2d array
    nx, ny, nz = [xscale.size, yscale.size, zscale.size]
    ind2d = np.ravel_multi_index(np.c_[yind, xind, zind].transpose(),
                                 dims=(ny, nx, nz))
    r = np.bincount(ind2d, minlength=nx * ny * nz).reshape(ny, nx, nz)

    # Make things analagous to AllenAtlas
    dxyz = spacing / 1e6 * np.array([1, -1, -1])
    dims2xyz = np.array([1, 0, 2])
    nxyz = np.array(r.shape)[dims2xyz]
    iorigin = (atlas.ALLEN_CCF_LANDMARKS_MLAPDV_UM['bregma'] / spacing)

    bc = atlas.BrainCoordinates(nxyz=nxyz, xyz0=(0, 0, 0), dxyz=dxyz)
    bc = atlas.BrainCoordinates(nxyz=nxyz, xyz0=-bc.i2xyz(iorigin), dxyz=dxyz)

    return r, bc
Пример #10
0
    def __init__(self,
                 one=None,
                 brain_atlas=None,
                 testing=False,
                 probe_id=None):
        self.one = one or ONE(base_url=ONE_BASE_URL)
        self.brain_atlas = brain_atlas or atlas.AllenAtlas(25)

        if testing:
            self.probe_id = probe_id
            self.chn_coords = SITES_COORDINATES
            self.chn_depths = SITES_COORDINATES[:, 1]
        else:
            from atlaselectrophysiology import qc_table
            self.qc = qc_table.EphysQC()
            self.brain_regions = self.one.alyx.rest('brain-regions', 'list')
            self.chn_coords = None
            self.chn_depths = None

        # Initialise all variables that get assigned
        self.sess_with_hist = None
        self.traj_ids = None
        self.traj_coords = None
        self.subjects = None
        self.sess = None
        self.eid = None
        self.lab = None
        self.n_sess = None
        self.probe_label = None
        self.traj_id = None
        self.date = None
        self.subj = None
        self.alignments = {}
        self.prev_align = None
        self.sess_path = None
        self.allen_id = None
        self.cluster_chns = None
        self.resolved = None
        self.alyx_str = None

        if probe_id is not None:
            self.sess = self.one.alyx.rest('trajectories',
                                           'list',
                                           provenance='Histology track',
                                           probe_insertion=probe_id)
Пример #11
0
def load_track_csv(file_track, brain_atlas=None):
    """
    Loads a lasagna track and convert to IBL-ALlen coordinate framework
    :param file_track:
    :return: xyz
    """

    brain_atlas = brain_atlas or atlas.AllenAtlas(25)
    # apmldv in the histology file is flipped along y direction
    file_track = Path(file_track)
    if file_track.stat().st_size == 0:
        return np.array([])
    ixiyiz = np.loadtxt(file_track, delimiter=',')[:, [1, 0, 2]]
    ixiyiz[:, 1] = 527 - ixiyiz[:, 1]
    ixiyiz = ixiyiz[np.argsort(ixiyiz[:, 2]), :]
    xyz = brain_atlas.bc.i2xyz(ixiyiz)
    # xyz[:, 0] = - xyz[:, 0]
    return xyz
Пример #12
0
def get_picked_tracks(histology_path,
                      glob_pattern="*_pts_transformed.csv",
                      brain_atlas=None):
    """
    This outputs reads in the Lasagna output and converts the picked tracks in the IBL coordinates
    :param histology_path: Path object: folder path containing tracks
    :return: xyz coordinates in
    """
    brain_atlas = brain_atlas or atlas.AllenAtlas()
    xyzs = []
    histology_path = Path(histology_path)
    if histology_path.is_file():
        files_track = [histology_path]
    else:
        files_track = list(histology_path.rglob(glob_pattern))
    for file_track in files_track:
        xyzs.append(load_track_csv(file_track, brain_atlas=brain_atlas))
    return {'files': files_track, 'xyz': xyzs}
Пример #13
0
    def __init__(self,
                 xyz_picks,
                 chn_depths=None,
                 track_prev=None,
                 feature_prev=None,
                 brain_atlas=None,
                 speedy=False):

        if not brain_atlas:
            self.brain_atlas = atlas.AllenAtlas(25)
        else:
            self.brain_atlas = brain_atlas

        self.xyz_track, self.track_extent = self.get_insertion_track(
            xyz_picks, speedy=speedy)

        self.chn_depths = chn_depths
        if np.any(track_prev):
            self.track_init = track_prev
            self.feature_init = feature_prev
        else:
            start_lims = 6000 / 1e6
            self.track_init = np.array([-1 * start_lims, start_lims])
            self.feature_init = np.array([-1 * start_lims, start_lims])

        self.sampling_trk = np.arange(self.track_extent[0],
                                      self.track_extent[-1] - 10 * 1e-6,
                                      10 * 1e-6)
        self.xyz_samples = histology.interpolate_along_track(
            self.xyz_track, self.sampling_trk - self.sampling_trk[0])
        # ensure none of the track is outside the y or x lim of atlas
        xlim = np.bitwise_and(
            self.xyz_samples[:, 0] > self.brain_atlas.bc.xlim[0],
            self.xyz_samples[:, 0] < self.brain_atlas.bc.xlim[1])
        ylim = np.bitwise_and(
            self.xyz_samples[:, 1] < self.brain_atlas.bc.ylim[0],
            self.xyz_samples[:, 1] > self.brain_atlas.bc.ylim[1])
        rem = np.bitwise_and(xlim, ylim)
        self.xyz_samples = self.xyz_samples[rem]

        self.region, self.region_label, self.region_colour, self.region_id\
            = self.get_histology_regions(self.xyz_samples, self.sampling_trk, self.brain_atlas)
Пример #14
0
def plot2d_all(trajectories, tracks, brain_atlas=None):
    """
    Plot all tracks on a single 2d slice
    :param trajectories: dictionary output of the Alyx REST query on trajectories
    :param tracks:
    :return:
    """
    brain_atlas = brain_atlas or atlas.AllenAtlas(25)
    plt.figure()
    axs = brain_atlas.plot_sslice(brain_atlas.bc.i2x(190),
                                  cmap=plt.get_cmap('bone'))
    plt.figure()
    axc = brain_atlas.plot_cslice(brain_atlas.bc.i2y(350))
    for xyz in tracks['xyz']:
        axc.plot(xyz[:, 0] * 1e3, xyz[:, 2] * 1e6, 'b')
        axs.plot(xyz[:, 1] * 1e3, xyz[:, 2] * 1e6, 'b')
    for trj in trajectories:
        ins = atlas.Insertion.from_dict(trj, brain_atlas=brain_atlas)
        xyz = ins.xyz
        axc.plot(xyz[:, 0] * 1e3, xyz[:, 2] * 1e6, 'r')
        axs.plot(xyz[:, 1] * 1e3, xyz[:, 2] * 1e6, 'r')
def _plot3d_traj(traj,
                 color,
                 label,
                 fig_handle,
                 ba=atlas.AllenAtlas(25),
                 line_width=3,
                 tube_radius=20):
    """
    Transform the traj into ins (atlas insertion), plot the track,
    setup label on top of track
    :param traj:
    :param color:
    :param label:
    :param fig_handle:
    :param ba:
    :param line_width:
    :param tube_radius:
    :return: mlapdv, ins
    """
    ins = atlas.Insertion.from_dict(traj)
    mlapdv = ba.xyz2ccf(ins.xyz)
    # Display the track
    mlab.plot3d(mlapdv[:, 1],
                mlapdv[:, 2],
                mlapdv[:, 0],
                line_width=line_width,
                color=color,
                tube_radius=tube_radius)

    # Setup the  label at the top of the planned trajectory
    mlab.text3d(mlapdv[0, 1],
                mlapdv[0, 2],
                mlapdv[0, 0] - 500,
                label,
                line_width=4,
                color=color,
                figure=fig_handle,
                scale=500)
    return mlapdv, ins
# Author: Olivier
# environment installation guide https://github.com/int-brain-lab/iblenv
# run "%qui qt" magic command from Ipython prompt for interactive mode
from mayavi import mlab
from one.api import ONE

from atlaselectrophysiology import rendering
import ibllib.atlas as atlas

one = ONE(base_url="https://alyx.internationalbrainlab.org")

fig = rendering.figure()
subject = 'KS003'
trajs = one.alyx.rest('trajectories', 'list', subject=subject)

ba_allen = atlas.AllenAtlas(25)
ba_needles = atlas.NeedlesAtlas(25)

plt_trj = []
for index, trj in enumerate(trajs):
    if trj['coordinate_system'] == 'IBL-Allen':
        brain_atlas = ba_allen
    elif trj['coordinate_system'] == 'Needles-Allen':
        brain_atlas = ba_needles
    ins = atlas.Insertion.from_dict(trj, brain_atlas=brain_atlas)
    ins = atlas.Insertion.from_dict(trj, brain_atlas=ba_allen)

    mlapdv = brain_atlas.xyz2ccf(ins.xyz)
    if trj['provenance'] == 'Micro-manipulator':
        color = (0., 1., 0.)  # Green
    elif trj['provenance'] == 'Histology track':
Пример #17
0
def plot_atlas_traj(x, y, 
                    atlas_ID = 'CCF',
                    provenance='Planned', 
                    subject_ID = 'Planned', 
                    remove_primary_axis = False,
                    project='ibl_neuropixel_brainwide_01', 
                    altas_borders = False, 
                    colour='y', linewidth=1,
                    axc = None, axs = None ):
    """Plot atlas trajectory
    
    Generates coronal and sagittal plots of the atlas along trajectory at x,y.

    Parameters
    ----------
    
    x : int
        x insertion coord in µm.  Eg. repeated site is -2243
    
    y : int
        y insertion coord in µm. Eg. repeated site is -2000.
    
    atlas_ID : str, optional
        Atlas data to plot channels on: 'CCF' - Allen CCF, or sample data. If 
        using sample data as atlas, must set this string to the subject ID
        to collect the data from flatiron. The default is 'CCF'.
    
    provenance : str, optional
        Trajectory provenance to use when generating the tilted slice. Choose 
        from: Planned, Micro-manipulator, Histology, E-phys aligned. The 
        default is 'Planned'.
    
    subject_ID : str, optional
        The subject which to retrieve the trajectory from. This trajectory 
        defines the tilted slice through the atlas. The default is 
        'Planned', which means the planned trajectory is used (ignoring the
        provenance option below!). Can be set to any valid subject_ID with
        a trajectory at x,y.
    
    remove_primary_axis : bool, optional
        Boolean whether to remove the primary y-axis labels from the plot.  Useful
        to remove if concatenating plots together.
    
    project : str, optional
        Project from which data should be retrieved. The default is 
        'ibl_neuropixel_brainwide_01'.
    
    atlas_borders : bool, optional
        Boolean whether to add the atlas borders to the atlas image. False by 
        default.
    
    axc : AxesSubplot, None
        MUST pass an AxesSubplot object for plotting to!  For coronal plot.
    
    axs : AxesSubplot, None
        MUST pass an AxesSubplot object for plotting to!  For sagittal plot.
    
    Returns
    -------
    
    fig_dict : dictionary
        Dictionary containing the pyplot Figure objects for coronal (cax) and
        cagittal (sax), and the insertion coordinates (x) and (y).
    
    """
    
    # TESTING:
    #x = -2243
    #y = -2000
    #atlas_ID = 'CSHL052'
    #provenance='Histology track'
    #remove_primary_axis = False
    #subject_ID = 'NYUU-12'
    #project='ibl_neuropixel_brainwide_01'
    #altas_borders = False
    #colour='y'
    #linewidth=1
    
    from one.api import ONE
    import ibllib.atlas as atlas
    from ibllib.atlas import Insertion
    import atlaselectrophysiology.load_histology as hist
    import numpy as np
    
    import sys
    
    import matplotlib.pyplot as plt
    
    # connect to ONE
    one = ONE()
    
    # get the PLANNED TRAJECTORY for x,y insertion
    trajs = one.alyx.rest('trajectories', 'list', x=x, y=y,  project=project)
    
    # generate pyplots of the brain:
    
    #fig1, ax1 = plt.subplots() # new figure and axes objects - CORONAL
    #fig2, ax2 = plt.subplots() # new figure and axes objects - SAGITTAL
    
    ax1 = axc
    ax2 = axs
    
    axis_labels = np.array(['ml (µm)', 'dv (µm)', 'ap (µm)'])
    
    if 'CCF' in atlas_ID: # want to use the CCF data for the plot
        
        if subject_ID == 'Planned':
            sidx=0
            provenance='Planned' # get the Planned trajectory from first subject!
        else:
            subjs = [sess['session']['subject'] for sess in trajs]
            labs = [sess['session']['lab'] for sess in trajs]
            sidx = subjs.index(subject_ID)
        
        # Fetch planned trajectory metadata for ANY of the traj in trajs (just use first index!):
        planned = one.alyx.rest('trajectories', 'list', session=trajs[sidx]['session']['id'],
                     probe=trajs[0]['probe_name'], provenance=provenance)
        
        # create insertion object from planned trajectory:
        ins = Insertion.from_dict(planned[0])
        
        # create a trajectory object from this insertion:
        traj = ins.trajectory
        
        # get CCF brain atlas for generating the figures:
        brain_atlas = atlas.AllenAtlas(res_um=25)
         # this is an instance of ibllib.atlas.atlas.AllenAtlas, sub-class of 
         # ibllib.atlas.atlas.BrainAtlas
         # contains:
         #        self.image: image volume (ap, ml, dv)
         #        self.label: label volume (ap, ml, dv)
         #        self.bc: atlas.BrainCoordinate object
         #        self.regions: atlas.BrainRegions object
         #        self.top: 2d np array (ap, ml) containing the z-coordinate (m) of the surface of the brain
         #        self.dims2xyz and self.zyz2dims: map image axis order to xyz coordinates order
        
        # use method in BrainAtlas to plot a tilted slice onto ax1:
        ax1 = brain_atlas.plot_tilted_slice(ins.xyz, axis=1, ax=ax1) # CORONAL
        ax2 = brain_atlas.plot_tilted_slice(ins.xyz, axis=0, ax=ax2) # SAGITTAL
        
        if remove_primary_axis:
            #ax1.get_xaxis().set_visible(False)
            ax1.get_yaxis().set_visible(False)
            #ax2.get_xaxis().set_visible(False)
            ax2.get_yaxis().set_visible(False)
        
        
    else: # hopefully atlas_ID matches an ID in subjs! in which case, use THIS SUBJECTS FLUOESCENCE DATA!
        
        subject_ID = atlas_ID # ensure subject and atlas are the SAME
        # keeping subjs and labs for look-up later if needed..
        subjs = [sess['session']['subject'] for sess in trajs]
        labs = [sess['session']['lab'] for sess in trajs]
        aidx = subjs.index(atlas_ID)
        sidx = subjs.index(subject_ID)
        
        # Fetch trajectory metadata for traj:
        traj = one.alyx.rest('trajectories', 'list', session=trajs[sidx]['session']['id'],
                     probe=trajs[sidx]['probe_name'], provenance=provenance)
        
        if traj == []:
            raise Exception("No trajectory found with provenance: " + provenance)
        
        # create insertion object from planned trajectory:
        ins = Insertion.from_dict(traj[0])
        
        # download the data - using Mayo's method!
        lab = labs[ aidx ] # this returns index in labs where atlas_ID is in subjs
        hist_paths = hist.download_histology_data(atlas_ID, lab)
        
        # create the brain atlases from the data
        ba_gr = atlas.AllenAtlas(hist_path=hist_paths[0]) # green histology channel autofl.
        ba_rd = atlas.AllenAtlas(hist_path=hist_paths[1]) # red histology channel cm-dii
        
        # implementing tilted slice here to modify its cmap
         # get tilted slice of the green and red channel brain atlases
          # using the .image data as this contains the signal
        gr_tslice, width, height, depth = ba_gr.tilted_slice(ins.xyz, 1, volume = ba_gr.image)
        rd_tslice, width, height, depth = ba_rd.tilted_slice(ins.xyz, 1, volume = ba_rd.image)
        
        width = width * 1e6
        height = height * 1e6
        depth = depth * 1e6
        
        cmap = plt.get_cmap('bone')
        
        # get the transfer function from y-axis to squeezed axis for second axe
        ab = np.linalg.solve(np.c_[height, height * 0 + 1], depth)
        height * ab[0] + ab[1]
        
         # linearly scale the values in 2d numpy arrays to between 0-255 (8bit)
          # Using gr_tslice min and max to scale the image
           # weirdly rd_in has very large min and max (problem with the original data acquisition?) so best to scale whole RGB with gr_in/1.5!
        gr_in = np.interp(gr_tslice, (gr_tslice.min(), gr_tslice.max()), (0, 255))
        rd_in = np.interp(rd_tslice, (gr_tslice.min(), gr_tslice.max()/1.5), (0, 255))
        
         # join together red, green, blue numpy arrays to form a RGB image ALONG A NEW DIMENSION
          # NOTE need a blue component, have added a set of zeros as blue channel should be BLANK
          # NOTE2: converted to unit8 bit, as pyplot imshow() method only reads this format
        Z = np.stack([ rd_in.astype(dtype=np.uint8), 
                       gr_in.astype(dtype=np.uint8), 
                       np.zeros(np.shape(gr_tslice)).astype(dtype=np.uint8) ])
         # transpose the columns to the FIRST one is LAST 
         # i.e the NEW DIMENSION [3] is the LAST DIMENSION
        Zt = np.transpose(Z, axes=[1,2,0])
        
         # can now add the RGB array to imshow()
        ax1.imshow(Zt, interpolation='none', aspect='auto', extent=np.r_[width, height], cmap=cmap, vmin=np.min(gr_in), vmax=np.max(gr_in) )
        
        sec_ax = ax1.secondary_yaxis('right', functions=(
                            lambda x: x * ab[0] + ab[1],
                            lambda y: (y - ab[1]) / ab[0]))
        ax1.set_xlabel(axis_labels[0], fontsize=8)
        ax1.set_ylabel(axis_labels[1], fontsize=8)
        sec_ax.set_ylabel(axis_labels[2], fontsize=8)
        
        #xmn = np.min(xCoords) - 500
        #xmz = np.max(xCoords) + 500
        xmn = np.min(ins.xyz[:, 0]) * 1e6 - 1000
        xmz = np.max(ins.xyz[:, 0]) *1e6 + 1000
        
        ax1.set_xlim(xmn, xmz)
         # ensure the resized xlim is not stretched!
        ax1.axes.set_aspect('equal')
        ax1.tick_params(axis='x', labelrotation = 90)
        
        ax1.tick_params(axis='x', labelsize = 8)
        ax1.tick_params(axis='y', labelsize = 8)
        sec_ax.tick_params(axis='y', labelsize = 8)
        
        if remove_primary_axis:
            #ax1.get_xaxis().set_visible(False)
            ax1.get_yaxis().set_visible(False)
         
         
        gr_tslice, width, height, depth = ba_gr.tilted_slice(ins.xyz, 0, volume = ba_gr.image)
        rd_tslice, width, height, depth = ba_rd.tilted_slice(ins.xyz, 0, volume = ba_rd.image)
        
        width = width * 1e6
        height = height * 1e6
        depth = depth * 1e6
        
        cmap = plt.get_cmap('bone')
        
        # get the transfer function from y-axis to squeezed axis for second axe
        ab = np.linalg.solve(np.c_[height, height * 0 + 1], depth)
        height * ab[0] + ab[1]
        
         # linearly scale the values in 2d numpy arrays to between 0-255 (8bit)
          # Using gr_tslice min and max to scale the image
           # weirdly rd_in has very large min and max (problem with the original data acquisition?) so best to scale whole RGB with gr_in/1.5!
        gr_in = np.interp(gr_tslice, (gr_tslice.min(), gr_tslice.max()), (0, 255))
        rd_in = np.interp(rd_tslice, (gr_tslice.min(), gr_tslice.max()/1.5), (0, 255))
        
         # join together red, green, blue numpy arrays to form a RGB image ALONG A NEW DIMENSION
          # NOTE need a blue component, have added a set of zeros as blue channel should be BLANK
          # NOTE2: converted to unit8 bit, as pyplot imshow() method only reads this format
        Z = np.stack([ rd_in.astype(dtype=np.uint8), 
                       gr_in.astype(dtype=np.uint8), 
                       np.zeros(np.shape(gr_tslice)).astype(dtype=np.uint8) ])
         # transpose the columns to the FIRST one is LAST 
         # i.e the NEW DIMENSION [3] is the LAST DIMENSION
        Zt = np.transpose(Z, axes=[1,2,0])
        
         # can now add the RGB array to imshow()
        ax2.imshow(Zt, interpolation='none', aspect='auto', extent=np.r_[width, height], cmap=cmap, vmin=np.min(gr_in), vmax=np.max(gr_in) )
        
        #start = ins.xyz[:, 1] * 1e6
        #end = ins.xyz[:, 2] * 1e6
        #xCoords = np.array([start[0], end[0]])
        
        sec_ax = ax2.secondary_yaxis('right', functions=(
                            lambda x: x * ab[0] + ab[1],
                            lambda y: (y - ab[1]) / ab[0]))
        ax2.set_xlabel(axis_labels[2], fontsize=8)
        ax2.set_ylabel(axis_labels[1], fontsize=8)
        sec_ax.set_ylabel(axis_labels[0], fontsize=8)
        
        xmn = np.min(ins.xyz[:, 1]) * 1e6 - 1000
        xmz = np.max(ins.xyz[:, 1]) *1e6 + 1000
        
        ax2.set_xlim(xmn, xmz)
         # ensure the resized xlim is not stretched!
        ax2.axes.set_aspect('equal')
        ax2.tick_params(axis='x', labelrotation = 90)
        
        ax2.tick_params(axis='x', labelsize = 8)
        ax2.tick_params(axis='y', labelsize = 8)
        sec_ax.tick_params(axis='y', labelsize = 8)
        
        if remove_primary_axis:
            #ax2.get_xaxis().set_visible(False)
            ax2.get_yaxis().set_visible(False)
        
    
    #plt.tight_layout() # tighten layout around xlabel & ylabel
    
    # add a line of the Insertion object onto ax1 (cax - coronal)
     # plotting PLANNED insertion 
    #ax1.plot(ins.xyz[:, 0] * 1e6, ins.xyz[:, 2] * 1e6, colour, linewidth=linewidth)
    #ax2.plot(ins.xyz[:, 1] * 1e6, ins.xyz[:, 2] * 1e6, colour, linewidth=linewidth)
    
    return  {'cax': ax1, 'sax': ax2, 'x': x, 'y': y, 
             'atlas_ID': atlas_ID, 'provenance': provenance, 
             'subject_id': subject_ID }
Пример #18
0
def register_track(probe_id,
                   picks=None,
                   one=None,
                   overwrite=False,
                   channels=True,
                   brain_atlas=None):
    """
    Register the user picks to a probe in Alyx
    Here we update Alyx models on the database in 3 steps
    1) The user picks converted to IBL coordinates will be stored in the json field of the
    corresponding probe insertion models
    2) The trajectory computed from the histology track is created or patched
    3) Channel locations are set in the table
    """
    assert one
    brain_atlas = brain_atlas or atlas.AllenAtlas()
    # 0) if it's an empty track, create a null trajectory and exit
    if picks is None or picks.size == 0:
        tdict = {
            'probe_insertion': probe_id,
            'x': None,
            'y': None,
            'z': None,
            'phi': None,
            'theta': None,
            'depth': None,
            'roll': None,
            'provenance': 'Histology track',
            'coordinate_system': 'IBL-Allen',
        }
        brain_locations = None
        # Update the insertion qc to CRITICAL
        hist_qc = base.QC(probe_id, one=one, endpoint='insertions')
        hist_qc.update_extended_qc({'tracing_exists': False})
        hist_qc.update('CRITICAL', namespace='tracing')
        insertion_histology = None
        # Here need to change the track qc to critical and also extended qc to zero
    else:
        brain_locations, insertion_histology = get_brain_regions(
            picks, brain_atlas=brain_atlas)
        # 1) update the alyx models, first put the picked points in the insertion json
        one.alyx.json_field_update(
            endpoint='insertions',
            uuid=probe_id,
            field_name='json',
            data={'xyz_picks': np.int32(picks * 1e6).tolist()})

        # Update the insertion qc to register tracing exits
        hist_qc = base.QC(probe_id, one=one, endpoint='insertions')
        hist_qc.update_extended_qc({'tracing_exists': True})
        # 2) patch or create the trajectory coming from histology track
        tdict = create_trajectory_dict(probe_id,
                                       insertion_histology,
                                       provenance='Histology track')

    hist_traj = one.alyx.get(
        '/trajectories?'
        f'&probe_insertion={probe_id}'
        '&provenance=Histology track',
        clobber=True)
    # if the trajectory exists, remove it, this will cascade delete existing channel locations
    if len(hist_traj):
        if overwrite:
            one.alyx.rest('trajectories', 'delete', id=hist_traj[0]['id'])
        else:
            raise FileExistsError(
                'The session already exists, however overwrite is set to False.'
                'If you want to overwrite, set overwrite=True.')
    hist_traj = one.alyx.rest('trajectories', 'create', data=tdict)

    if brain_locations is None:
        return brain_locations, None
    # 3) create channel locations
    if channels:
        channel_dict = create_channel_dict(hist_traj, brain_locations)
        one.alyx.rest('channels', 'create', data=channel_dict)

    return brain_locations, insertion_histology
Пример #19
0
def plot_histology_traj(subject_ID, x, y, 
                      provenance='Histology track', 
                      project='ibl_neuropixel_brainwide_01', 
                      atlas_type = 'sample-autofl', 
                      altas_borders = False, 
                      colour='y', linewidth=1):
    
    # TESTING:
    #subject_ID = 'NYU-12'
    #x = -2243
    #y = -2000
    #provenance='Histology track'
    #project='ibl_neuropixel_brainwide_01'
    #atlas_type = 'sample-autofl'
    #altas_borders = False
    #colour='y'
    #linewidth=1
    
    from one.api import ONE
    import ibllib.atlas as atlas
    from ibllib.atlas import Insertion
    import atlaselectrophysiology.load_histology as hist
    import numpy as np
    
    import matplotlib.pyplot as plt
    
    # connect to ONE
    one = ONE()
    
    # FIRST get the PLANNED TRAJECTORY for repeated site: x=2243, y=2000
     # first pass - to get a session id and probe name! - can retrieve from ANY trajectory!
    trajs = one.alyx.rest('trajectories', 'list',
                         x=x, y=y,  project=project)
    
    # get the lab string
    subjs = [sess['session']['subject'] for sess in trajs]
    labs = [sess['session']['lab'] for sess in trajs]
    
    lab = labs[ subjs.index(subject_ID)] # this returns index in labs where subject_ID is in subjs
    
    # Fetch Repeated Site planned trajectory metadata:
    planned = one.alyx.rest('trajectories', 'list', session=trajs[0]['session']['id'],
                 probe=trajs[0]['probe_name'], provenance='planned')
    
    # create insertion object of Repeated Site from planned trajectory:
    ins = Insertion.from_dict(planned[0])
    
    # create a trajectory object from this insertion:
    traj = ins.trajectory
    
    # generate pyplots of the brain:
    
    fig1, cax = plt.subplots() # new figure and axes objects - CORONAL
    fig2, sax = plt.subplots() # new figure and axes objects - SAGITTAL
    
    if 'sample-autofl' in atlas_type:
        
        # get just the autofl data as an atlas
        #TODO must modify this method!
        hist_paths = hist.download_histology_data(subject_ID, lab)
        
        ba_gr = atlas.AllenAtlas(hist_path=hist_paths[0]) # green histology channel autofl.
        #ba_rd = atlas.AllenAtlas(hist_path=hist_paths[1]) # red histology channel cm-dii
        
        # implementing tilted slice here to modify its cmap
         # get tilted slice of the green and red channel brain atlases
          # using the .image data as this contains the signal
        gr_tslice, width, height, depth = ba_gr.tilted_slice(ins.xyz, 1, volume = ba_gr.image)
        
        width = width * 1e6
        height = height * 1e6
        depth = depth * 1e6
        
        cmap = plt.get_cmap('bone')
        
        # get the transfer function from y-axis to squeezed axis for second axe
        ab = np.linalg.solve(np.c_[height, height * 0 + 1], depth)
        height * ab[0] + ab[1]
        
         # linearly scale the values in 2d numpy arrays to between 0-255 (8bit)
          # Using gr_tslice min and max to scale the image
           # weirdly rd_in has very large min and max (problem with the original data acquisition?) so best to scale whole RGB with gr_in!
        gr_in = np.interp(gr_tslice, (gr_tslice.min(), gr_tslice.max()), (0, 255))
        
         # join together red, green, blue numpy arrays to form a RGB image ALONG A NEW DIMENSION
          # NOTE need a blue component, have added a set of zeros as blue channel should be BLANK
          # NOTE2: converted to unit8 bit, as pyplot imshow() method only reads this format
        Z = np.stack([ np.zeros(np.shape(gr_tslice)).astype(dtype=np.uint8), 
                      gr_in.astype(dtype=np.uint8), 
                      np.zeros(np.shape(gr_tslice)).astype(dtype=np.uint8) ])
         # transpose the columns to the FIRST one is LAST 
         # i.e the NEW DIMENSION [3] is the LAST DIMENSION
        Zt = np.transpose(Z, axes=[1,2,0])
        
         # can now add the RGB array to imshow()
        cax.imshow(Zt, interpolation='none', aspect='auto', extent=np.r_[width, height], cmap=cmap, vmin=np.min(gr_in), vmax=np.max(gr_in) )

        
    elif 'sample-cci' in atlas_type:
        
        print('sample-cci')
        
        
    elif 'sample' in atlas_type:
        
        print('sample')
        
    else:
        # invalid atlas choice - return error:
        print("INVALID ATLAS CHOICE - must be 'CCF' 'sample-autofl', 'sample-dii', 'sample'")
        #return None
    
    
    # add a line of the Insertion object onto ax1 (cax - coronal)
     # plotting PLANNED insertion 
    cax.plot(ins.xyz[:, 0] * 1e6, ins.xyz[:, 2] * 1e6, colour, linewidth=linewidth)
    sax.plot(ins.xyz[:, 1] * 1e6, ins.xyz[:, 2] * 1e6, colour, linewidth=linewidth)
    
    return  {'cax': fig1, 'sax': fig2, 'x': x, 'y': y}
Пример #20
0
 def __init__(self):
     self.folder_path = []
     self.chn_coords = []
     self.sess_path = []
     self.brain_atlas = atlas.AllenAtlas(25)
Пример #21
0
                                    INCL_SESSIONS, INCL_NEURONS))))

# Exclude root
decoding_result = decoding_result.reset_index()
incl_regions = [i for i, j in enumerate(decoding_result['region']) if not j.islower()]
decoding_result = decoding_result.loc[incl_regions]

# Drop duplicates
decoding_result = decoding_result[~decoding_result.duplicated(subset=['region', 'eid', 'probe'])]

# Calculate accuracy over chance
decoding_result['acc_over_chance'] = (decoding_result['accuracy']
                                      - decoding_result['chance_accuracy']) * 100

# Remove cortical layers from brain region map
ba = atlas.AllenAtlas(25)
all_regions = combine_layers_cortex(ba.regions.acronym)

# Calculate average decoding performance per region
decode_regions = []
accuracy = []
for i, region in enumerate(decoding_result['region'].unique()):
    if np.sum(decoding_result['region'] == region) >= MIN_REC:
        decode_regions.append(region)
        accuracy.append(decoding_result.loc[decoding_result['region'] == region,
                                            'acc_over_chance'].mean())

f, axs1 = plt.subplots(1, 3, figsize=(30, 6))
figure_style(font_scale=2)
if CENTERED:
    plot_atlas(np.array(decode_regions), np.array(accuracy), ML, AP, DV, color_palette='RdBu_r',
Пример #22
0
    def get_slice_images(self, xyz_channels):
        # First see if the histology file exists before attempting to connect with FlatIron and
        # download
        hist_dir = Path(self.sess_path.parent.parent, 'histology')
        hist_path_rd = None
        hist_path_gr = None
        if hist_dir.exists():
            path_to_rd_image = glob.glob(str(hist_dir) + '/*RD.tif')
            if path_to_rd_image:
                hist_path_rd = tif2nrrd(Path(path_to_rd_image[0]))
            else:
                files = download_histology_data(self.subj, self.lab)
                if files is not None:
                    hist_path_rd = files[1]

            path_to_gr_image = glob.glob(str(hist_dir) + '/*GR.tif')
            if path_to_gr_image:
                hist_path_gr = tif2nrrd(Path(path_to_gr_image[0]))
            else:
                files = download_histology_data(self.subj, self.lab)
                if files is not None:
                    hist_path_gr = files[0]

        else:
            files = download_histology_data(self.subj, self.lab)
            if files is not None:
                hist_path_gr = files[0]
                hist_path_rd = files[1]

        index = self.brain_atlas.bc.xyz2i(
            xyz_channels)[:, self.brain_atlas.xyz2dims]
        ccf_slice = self.brain_atlas.image[index[:, 0], :, index[:, 2]]
        ccf_slice = np.swapaxes(ccf_slice, 0, 1)

        label_slice = self.brain_atlas._label2rgb(
            self.brain_atlas.label[index[:, 0], :, index[:, 2]])
        label_slice = np.swapaxes(label_slice, 0, 1)

        width = [self.brain_atlas.bc.i2x(0), self.brain_atlas.bc.i2x(456)]
        height = [
            self.brain_atlas.bc.i2z(index[0, 2]),
            self.brain_atlas.bc.i2z(index[-1, 2])
        ]

        if hist_path_rd:
            hist_atlas_rd = atlas.AllenAtlas(hist_path=hist_path_rd)
            hist_slice_rd = hist_atlas_rd.image[index[:, 0], :, index[:, 2]]
            hist_slice_rd = np.swapaxes(hist_slice_rd, 0, 1)
        else:
            print('Could not find red histology image for this subject')
            hist_slice_rd = np.copy(ccf_slice)

        if hist_path_gr:
            hist_atlas_gr = atlas.AllenAtlas(hist_path=hist_path_gr)
            hist_slice_gr = hist_atlas_gr.image[index[:, 0], :, index[:, 2]]
            hist_slice_gr = np.swapaxes(hist_slice_gr, 0, 1)
        else:
            print('Could not find green histology image for this subject')
            hist_slice_gr = np.copy(ccf_slice)

        slice_data = {
            'hist_rd':
            hist_slice_rd,
            'hist_gr':
            hist_slice_gr,
            'ccf':
            ccf_slice,
            'label':
            label_slice,
            'scale':
            np.array([(width[-1] - width[0]) / ccf_slice.shape[0],
                      (height[-1] - height[0]) / ccf_slice.shape[1]]),
            'offset':
            np.array([width[0], height[0]])
        }

        return slice_data
Пример #23
0
# import modules
from oneibl.one import ONE
from ibllib.pipes.ephys_alignment import EphysAlignment
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import ibllib.atlas as atlas
from pathlib import Path
# Instantiate brain atlas and one
brain_atlas = atlas.AllenAtlas(25)
one = ONE()

fig_path = Path('C:/Users/Mayo/Documents/PYTHON/alignment_figures/scale_factor')
# Find eid of interest
aligned_sess = one.alyx.rest('trajectories', 'list', provenance='Ephys aligned histology track',
                             django='probe_insertion__session__project__name__icontains,'
                                    'ibl_neuropixel_brainwide_01,'
                                    'probe_insertion__session__qc__lt,30')
eids = np.array([s['session']['id'] for s in aligned_sess])
probes = np.array([s['probe_name'] for s in aligned_sess])

json = [s['json'] for s in aligned_sess]
idx_none = [i for i, val in enumerate(json) if val is None]
json_val = np.delete(json, idx_none)
keys = [list(s.keys()) for s in json_val]

eids = np.delete(eids, idx_none)
probes = np.delete(probes, idx_none)

# Find index of json fields with 2 or more keys
len_key = [len(s) for s in keys]
Пример #24
0
from pathlib import Path
import logging

import matplotlib.pyplot as plt
import numpy as np

from ibllib.ephys.neuropixel import SITES_COORDINATES
from oneibl.one import ONE
import alf.io
import ibllib.atlas as atlas
from ibllib.ephys.spikes import probes_description as extract_probes

_logger = logging.getLogger('ibllib')

# origin Allen left, front, up
brain_atlas = atlas.AllenAtlas(res_um=25)


def load_track_csv(file_track):
    """
    Loads a lasagna track and convert to IBL-ALlen coordinate framework
    :param file_track:
    :return: xyz
    """
    # apmldv in the histology file is flipped along y direction
    file_track = Path(file_track)
    if file_track.stat().st_size == 0:
        return np.array([])
    ixiyiz = np.loadtxt(file_track, delimiter=',')[:, [1, 0, 2]]
    ixiyiz[:, 1] = 527 - ixiyiz[:, 1]
    ixiyiz = ixiyiz[np.argsort(ixiyiz[:, 2]), :]
Пример #25
0
def get_nearest_boundary(xyz_coords,
                         allen,
                         extent=100,
                         steps=8,
                         nGenerations=None,
                         depth=None,
                         brain_atlas=None):
    """
        Adapted from IBLLib/ephys_alignment 
        
        note: xyz coords is w.r.t. bregma IN METERS
        
        Finds distance to closest neighbouring brain region along trajectory. For each point in
        xyz_coords computes the plane passing through point and perpendicular to trajectory and
        finds all brain regions that lie in that plane up to a given distance extent from specified
        point. Additionally, if requested, computes distance between the parents of regions.
        :param xyz_coords: 3D coordinates of points along probe or track
        :type xyz_coords: np.array((n_points, 3)) n_points: no. of points
        :param allen: dataframe containing allen info. Loaded from allen_structure_tree in
        ibllib/atlas
        :type allen: pandas Dataframe
        :param extent: extent of plane in each direction from origin in (um)
        :type extent: float
        :param steps: no. of steps to discretise plane into
        :type steps: int
        :param parent: Whether to also compute nearest distance between parents of regions
        :type parent: bool
        :return nearest_bound: dict containing results
        :type nearest_bound: dict
        """
    if not brain_atlas:
        brain_atlas = atlas.AllenAtlas(25)

    vector = atlas.Insertion.from_track(
        xyz_coords, brain_atlas=brain_atlas).trajectory.vector
    nearest_bound = dict()
    nearest_bound['dist'] = np.zeros((xyz_coords.shape[0]))
    nearest_bound['id'] = np.zeros((xyz_coords.shape[0]))
    # nearest_bound['adj_id'] = np.zeros((xyz_coords.shape[0]))
    nearest_bound['col'] = []

    if nGenerations:
        nearest_bound['parent_gen%i_dist' % nGenerations] = np.zeros(
            (xyz_coords.shape[0]))
        nearest_bound['parent_gen%i_id' % nGenerations] = np.zeros(
            (xyz_coords.shape[0]))
        nearest_bound['parent_gen%i_col' % nGenerations] = []
    if depth:
        nearest_bound['parent_depth%i_dist' % depth] = np.zeros(
            (xyz_coords.shape[0]))
        nearest_bound['parent_depth%i_id' % depth] = np.zeros(
            (xyz_coords.shape[0]))
        nearest_bound['parent_depth%i_col' % depth] = []

    for iP, point in enumerate(xyz_coords):
        d = np.dot(vector, point)
        x_vals = np.r_[np.linspace(point[0] - extent / 1e6, point[0] +
                                   extent / 1e6, steps), point[0]]
        y_vals = np.r_[np.linspace(point[1] - extent / 1e6, point[1] +
                                   extent / 1e6, steps), point[1]]

        X, Y = np.meshgrid(x_vals, y_vals)
        Z = (d - vector[0] * X - vector[1] * Y) / vector[2]
        XYZ = np.c_[np.reshape(X, X.size),
                    np.reshape(Y, Y.size),
                    np.reshape(Z, Z.size)]
        dist = np.sqrt(np.sum((XYZ - point)**2,
                              axis=1))  # distance from each XYZ lookup point

        try:  # Performs a 3D lookup from real world coordinates to the volume labels and return the regions ids according to the mapping
            brain_id = brain_atlas.regions.get(brain_atlas.get_labels(XYZ))[
                'id']  # get brain_ids associated w/ each lookup point
        except Exception as err:
            print(err)
            continue

        dist_sorted = np.argsort(dist)
        brain_id_sorted = brain_id[dist_sorted]

        nearest_bound['id'][iP] = brain_id_sorted[0]
        nearest_bound['col'].append(allen['color_hex_triplet'][np.where(
            allen['id'] == brain_id_sorted[0])[0][0]])

        bound_idx = np.where(brain_id_sorted != brain_id_sorted[0])[0]
        if np.any(bound_idx):
            nearest_bound['dist'][iP] = dist[dist_sorted[bound_idx[0]]] * 1e6
            # nearest_bound['adj_id'][iP] = brain_id_sorted[bound_idx[0]]
        else:
            nearest_bound['dist'][iP] = np.max(dist) * 1e6
            # nearest_bound['adj_id'][iP] = brain_id_sorted[0]

        # to make sure we don't run into root
        root_id = allen[allen['name'] == 'root']['id'].item()
        allen.loc[allen['name'] == 'root', 'parent_structure_id'] = root_id
        id_allen = allen.set_index('id')

        if nGenerations:  # CSompute the parents up a certain number of generations
            # brain_parent = np.array([allen['parent_structure_id'][np.where(allen['id'] == br)[0][0]] for br in brain_id_sorted])

            try:
                brain_parent = get_parent_id_nGenerations(
                    id_allen, brain_id_sorted, nGenerations)
            except:
                print("Problem with: ", brain_id_sorted)
                return brain_id_sorted
                break
            brain_parent[np.isnan(brain_parent)] = 0

            nearest_bound['parent_gen%i_id' %
                          nGenerations][iP] = brain_parent[0]
            nearest_bound['parent_gen%i_col' % nGenerations].append(
                allen['color_hex_triplet'][np.where(
                    allen['id'] == brain_parent[0])[0][0]])

            parent_idx = np.where(brain_parent != brain_parent[0])[0]
            if np.any(parent_idx):
                nearest_bound[
                    'parent_gen%i_dist' %
                    nGenerations][iP] = dist[dist_sorted[parent_idx[0]]] * 1e6
            else:
                nearest_bound['parent_gen%i_dist' %
                              nGenerations][iP] = np.max(dist) * 1e6

        if depth:  # Compute the parents up a certain tree depth
            try:
                brain_parent = get_parent_id_depth(id_allen, brain_id_sorted,
                                                   depth)
            except:
                print("Problem with: ", brain_id_sorted)
                return brain_id_sorted
                break

            brain_parent[np.isnan(brain_parent)] = 0
            nearest_bound['parent_depth%i_id' % depth][iP] = brain_parent[0]
            nearest_bound['parent_depth%i_col' % depth].append(
                allen['color_hex_triplet'][np.where(
                    allen['id'] == brain_parent[0])[0][0]])

            parent_idx = np.where(brain_parent != brain_parent[0])[0]
            if np.any(parent_idx):
                nearest_bound[
                    'parent_depth%i_dist' %
                    depth][iP] = dist[dist_sorted[parent_idx[0]]] * 1e6
            else:
                nearest_bound['parent_depth%i_dist' %
                              depth][iP] = np.max(dist) * 1e6
    return nearest_bound
Пример #26
0
def detect_missing_histology_tracks(path_tracks=None,
                                    one=None,
                                    subject=None,
                                    brain_atlas=None):
    """
    Compares the number of probe insertions to the number of registered histology tracks to see if
    there is a discrepancy so that missing tracks can be properly logged in the database
    :param path_tracks: path to track files to be registered
    :param subject: subject nickname for which to detect missing tracks
    """

    brain_atlas = brain_atlas or atlas.AllenAtlas()
    if path_tracks:
        glob_pattern = "*_probe*_pts*.csv"

        path_tracks = Path(path_tracks)

        if not path_tracks.is_dir():
            track_files = [path_tracks]
        else:
            track_files = list(path_tracks.rglob(glob_pattern))
            track_files.sort()

        subjects = []
        for track_file in track_files:
            search_filter = _parse_filename(track_file)
            subjects.append(search_filter['subject'])

        unique_subjects = np.unique(subjects)
    elif not path_tracks and subject:
        unique_subjects = [subject]
    else:
        _logger.warning('Must specifiy either path_tracks or subject argument')
        return

    for subj in unique_subjects:
        insertions = one.alyx.rest('insertions',
                                   'list',
                                   subject=subj,
                                   no_cache=True)
        trajectories = one.alyx.rest('trajectories',
                                     'list',
                                     subject=subj,
                                     provenance='Histology track',
                                     no_cache=True)
        if len(insertions) != len(trajectories):
            ins_sess = np.array(
                [ins['session'] + ins['name'] for ins in insertions])
            traj_sess = np.array([
                traj['session']['id'] + traj['probe_name']
                for traj in trajectories
            ])
            miss_idx = np.where(np.isin(ins_sess, traj_sess, invert=True))[0]

            for idx in miss_idx:

                info = one.eid2path(ins_sess[idx][:36],
                                    query_type='remote').parts
                print(ins_sess[idx][:36])
                msg = f"Histology tracing missing for {info[-3]}, {info[-2]}, {info[-1]}," \
                      f" {ins_sess[idx][36:]}.\nEnter [y]es to register an empty track for " \
                      f"this insertion \nEnter [n]o, if tracing for this probe insertion will be "\
                      f"conducted at a later date \n>"
                resp = input(msg)
                resp = resp.lower()
                if resp == 'y' or resp == 'yes':
                    _logger.info(
                        'Histology track for this probe insertion registered as empty'
                    )
                    probe_id = insertions[idx]['id']
                    print(insertions[idx]['session'])
                    print(probe_id)
                    register_track(probe_id, one=one, brain_atlas=brain_atlas)
                else:
                    _logger.info(
                        'Histology track for this probe insertion will not be registered'
                    )
                    continue
Пример #27
0
def coverage(trajs, ba=None, dist_fcn=[100, 150]):
    """
    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
    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 len(trajs) > 20:
            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
        flat_ind = ba._lookup_inds(ixyz)
        full_coverage[flat_ind] += coverage

    full_coverage = full_coverage.reshape(ba.image.shape)
    full_coverage[ba.label == 0] = np.nan
    return full_coverage, np.mean(xyz, 0), flat_ind
Пример #28
0
def plot_probe_planned_histology(data_repeated):
    '''
    Plots the planned and histology probes on CCF
    
    This takes the output DataFrame from probe_displacement_planned_histology()
    function and plots both the planned and histology trajectories onto the
    CCF for visual inspection.
    
    '''

    one = ONE()

    brain_atlas = atlas.AllenAtlas(res_um=25)

    # generate subplot for atlas and data
    fig1, ax1 = plt.subplots()  # coronal slice
    fig2, ax2 = plt.subplots()  # sagittal slice
    fig3, ax3 = plt.subplots()  # horizontal slice at SURFACE
    fig4, ax4 = plt.subplots()  # horizontal slice at PROBE TIP

    # Get angle in coronal plane
    alpha_mean = np.abs(data_repeated['planned_theta'] -
                        np.abs(data_repeated['hist_coronal_angle']))

    #alpha_std = np.std(np.abs(data_repeated['planned_theta'] -
    #                      np.abs(data_repeated['hist_coronal_angle'])))

    # Get angle in sagittal plane
    beta_mean = np.mean(np.abs(data_repeated['hist_sagittal_angle']))
    #beta_std = np.std(np.abs(data_repeated['hist_sagittal_angle']))

    all_ins_entry = np.empty((0, 3))
    all_ins_exit = np.empty((0, 3))

    # Plot the planned trajectory
    phi_eid = data_repeated['eid'][0]
    phi_probe = data_repeated['probe'][0]
    phi_subj = data_repeated['subject'][0]

    phi_traj = one.alyx.rest('trajectories',
                             'list',
                             session=phi_eid,
                             provenance='Planned',
                             probe=phi_probe)[0]
    ins_plan = atlas.Insertion.from_dict(phi_traj)

    cax = brain_atlas.plot_tilted_slice(ins_plan.xyz, axis=1, ax=ax1)
    cax.plot(ins_plan.xyz[:, 0] * 1e6,
             ins_plan.xyz[:, 2] * 1e6,
             'b',
             linewidth=1)

    sax = brain_atlas.plot_tilted_slice(ins_plan.xyz, axis=0, ax=ax2)
    sax.plot(ins_plan.xyz[:, 1] * 1e6,
             ins_plan.xyz[:, 2] * 1e6,
             'b',
             linewidth=1)

    hax = brain_atlas.plot_hslice(ins_plan.xyz[0, 2] - 500 / 1e6, ax=ax3)
    hax.plot(ins_plan.xyz[0, 1] * 1e6,
             ins_plan.xyz[0, 0] * 1e6,
             color='b',
             marker="o",
             markersize=1)

    hax2 = brain_atlas.plot_hslice(ins_plan.xyz[1, 2], ax=ax4)
    hax2.plot(ins_plan.xyz[1, 1] * 1e6,
              ins_plan.xyz[1, 0] * 1e6,
              color='b',
              marker="o",
              markersize=1)

    # Plot the histology trajectory
    phi_traj = one.alyx.rest('trajectories',
                             'list',
                             session=phi_eid,
                             provenance='Histology track',
                             probe=phi_probe)[0]

    ins = atlas.Insertion.from_dict(phi_traj)

    all_ins_entry = np.vstack([all_ins_entry, ins.xyz[0, :]])
    all_ins_exit = np.vstack([all_ins_exit, ins.xyz[1, :]])

    cax.plot(ins.xyz[:, 0] * 1e6, ins.xyz[:, 2] * 1e6, 'y', alpha=0.8)

    sax.plot(ins.xyz[:, 1] * 1e6, ins.xyz[:, 2] * 1e6, 'y', alpha=0.8)

    hax.plot(ins.xyz[0, 1] * 1e6,
             ins.xyz[0, 0] * 1e6,
             color='y',
             marker="o",
             markersize=1,
             alpha=0.9)

    hax2.plot(ins.xyz[1, 1] * 1e6,
              ins.xyz[1, 0] * 1e6,
              color='y',
              marker="o",
              markersize=1,
              alpha=0.9)

    # Add targeting errors to the title of figures
    ax1.xaxis.label.set_size(16)
    ax1.tick_params(axis='x', labelsize=14)
    ax1.yaxis.label.set_size(16)
    ax1.tick_params(axis='y', labelsize=14)
    ax1.set_title('' + phi_subj + ' : Targeting error in coronal plane = ')
    ax1.yaxis.set_major_locator(plt.MaxNLocator(4))
    ax2.xaxis.label.set_size(16)
    ax2.tick_params(axis='x', labelsize=14)
    ax2.yaxis.label.set_size(16)
    ax2.tick_params(axis='y', labelsize=14)
    ax2.set_title('' + phi_subj + ' : Targeting error in sagittal plane')
    ax2.yaxis.set_major_locator(plt.MaxNLocator(4))

    ax3.set_xlabel('ap (um)', fontsize=14)
    ax3.tick_params(axis='x', labelsize=14)
    ax3.set_ylabel('ml (um)', fontsize=14)
    ax3.tick_params(axis='y', labelsize=14)
    ax3.set_title('' + phi_subj + ' : Targeting error at surface')
    ax3.yaxis.set_major_locator(plt.MaxNLocator(4))

    ax4.set_xlabel('ap (um)', fontsize=14)
    ax4.tick_params(axis='x', labelsize=14)
    ax4.set_ylabel('ml (um)', fontsize=14)
    ax4.tick_params(axis='y', labelsize=14)
    ax4.set_title('' + phi_subj + ' : Targeting error at probe tip')
    ax4.yaxis.set_major_locator(plt.MaxNLocator(4))

    plt.show()
As input, use the path to a probe track (_pts.csv).
environment installation guide https://github.com/int-brain-lab/iblenv
'''
# Author: Olivier Winter

import numpy as np

from ibllib.pipes import histology
import ibllib.atlas as atlas

# === Parameters section (edit) ===
track_file = "/Users/gaelle/Downloads/electrodetracks_lic3/2019-08-27_lic3_002_probe00_pts.csv"
FULL_BLOWN_GUI = True  # set to False for simple matplotlib view

# === Code (do not edit) ===
ba = atlas.AllenAtlas(res_um=25)
xyz_picks = histology.load_track_csv(track_file)
bl, ins = histology.get_brain_regions(xyz_picks)

if FULL_BLOWN_GUI:
    from iblapps.histology import atlas_mpl
    mw, cax = atlas_mpl.viewatlas(ba, ap_um=np.mean(ins.xyz[:, 1]) * 1e6)
else:
    cax = ba.plot_cslice(ap_coordinate=np.mean(ins.xyz[:, 1]))

cax.plot(ins.xyz[:, 0] * 1e6, ins.xyz[:, 2] * 1e6)
cax.plot(bl.xyz[:, 0] * 1e6, bl.xyz[:, 2] * 1e6, '*')
# cax.plot(ba.bc.xscale * 1e6, ba.top[ba.bc.y2i(np.mean(ins.xyz[:, 1])), :] * 1e6)

if FULL_BLOWN_GUI:
    mw.mpl_widget.draw()
Пример #30
0
def probe_displacement_planned_histology(eid, probe):
    """
    Returns PANDAS DataFrame of probe features for planned and histology
    insertions
    
    For a given eid and probe, this function will extract all data relating
    to the PLANNED insertion, and the final HISTOLOGY insertion, including:
        
        * XYZ Insertion: Coordinate on CCF surface where probe is inserted
        * XYZ TIP: Coordinate in CCF where probe ends.
        * Theta, Depth, Phi: Angles Theta and Phi, plus probe depth
        * Sagittal Angle: Angle of insertion in sagittal plane.
        * Coronal Angle: Angle of insertion in coronal plane.
        
    This dataframe can be passed to the plot_probe_planned_histology() 
    function for visualising the planned and histology insertions.
    
    """

    one = ONE()

    data = {
        'subject': [],
        'eid': [],
        'probe': [],
        'planned_x_insertion': [],
        'planned_y_insertion': [],
        'planned_z_insertion': [],
        'planned_x_tip': [],
        'planned_y_tip': [],
        'planned_z_tip': [],
        'planned_theta': [],
        'planned_depth': [],
        'planned_phi': [],
        'micro_x': [],
        'micro_y': [],
        'micro_z': [],
        'micro_theta': [],
        'micro_depth': [],
        'micro_phi': [],
        'hist_x_insertion': [],
        'hist_y_insertion': [],
        'hist_z_insertion': [],
        'hist_x_tip': [],
        'hist_y_tip': [],
        'hist_z_tip': [],
        'hist_theta': [],
        'hist_depth': [],
        'hist_phi': []
    }

    brain_atlas = atlas.AllenAtlas(res_um=25)

    #print(eid)
    #print(probe)
    insertion = one.alyx.rest('insertions', 'list', session=eid, name=probe)
    if insertion:
        tracing = np.array(insertion[0]['json'])
        if tracing and 'xyz_picks' in insertion[0]['json'].keys():
            planned = one.alyx.rest('trajectories',
                                    'list',
                                    session=eid,
                                    probe=probe,
                                    provenance='planned')
            ins_plan = atlas.Insertion.from_dict(planned[0])
            micro = one.alyx.rest('trajectories',
                                  'list',
                                  session=eid,
                                  probe=probe,
                                  provenance='Micro-manipulator')
            track = np.array(insertion[0]['json']['xyz_picks']) / 1e6
            track_coords = atlas.Insertion.from_track(track, brain_atlas)
            if not planned:
                planned = np.copy(micro)
            if not micro:
                micro = np.copy(planned)
            if planned:
                data['subject'].append(planned[0]['session']['subject'])
                data['eid'].append(eid)
                data['probe'].append(probe)
                data['planned_x_insertion'].append(planned[0]['x'])
                data['planned_y_insertion'].append(planned[0]['y'])
                data['planned_z_insertion'].append(planned[0]['z'])
                data['planned_x_tip'].append(ins_plan.xyz[1, 0] * 1e6)
                data['planned_y_tip'].append(ins_plan.xyz[1, 1] * 1e6)
                data['planned_z_tip'].append(ins_plan.xyz[1, 2] * 1e6)
                data['planned_depth'].append(planned[0]['depth'])
                data['planned_theta'].append(planned[0]['theta'])
                data['planned_phi'].append(planned[0]['phi'])
                data['micro_x'].append(micro[0]['x'])
                data['micro_y'].append(micro[0]['y'])
                data['micro_z'].append(micro[0]['z'])
                data['micro_depth'].append(micro[0]['depth'])
                data['micro_theta'].append(micro[0]['theta'])
                data['micro_phi'].append(micro[0]['phi'])
                data['hist_x_insertion'].append(track_coords.x * 1e6)
                data['hist_y_insertion'].append(track_coords.y * 1e6)
                data['hist_z_insertion'].append(track_coords.z * 1e6)
                data['hist_x_tip'].append(track_coords.xyz[1, 0] * 1e6)
                data['hist_y_tip'].append(track_coords.xyz[1, 1] * 1e6)
                data['hist_z_tip'].append(track_coords.xyz[1, 2] * 1e6)
                data['hist_depth'].append(track_coords.depth * 1e6)
                data['hist_theta'].append(track_coords.theta)
                data['hist_phi'].append(track_coords.phi)
            else:
                sys.exit('Planned does not exist')
        else:
            sys.exit('Tracing does not exist')
    else:
        sys.exit('Insertion does not exist')

    # PLANNED: Using phi and theta calculate angle in sagittal plane
    x = np.sin(np.array(data['planned_theta']) * np.pi / 180.) * \
        np.sin(np.array(data['planned_phi']) * np.pi / 180.)
    y = np.cos(np.array(data['planned_theta']) * np.pi / 180.)
    data['planned_sagittal_angle'] = np.arctan2(x, y) * 180 / np.pi

    # PLANNED:  Using phi and theta calculate angle in coronal plane
    x = np.sin(np.array(data['planned_theta']) * np.pi / 180.) * \
        np.cos(np.array(data['planned_phi']) * np.pi / 180.)
    y = np.cos(np.array(data['planned_theta']) * np.pi / 180.)
    data['planned_coronal_angle'] = np.arctan2(x, y) * 180 / np.pi

    # HISTOLOGY:  Using phi and theta calculate angle in sagittal plane
    x = np.sin(np.array(data['hist_theta']) * np.pi / 180.) * \
        np.sin(np.array(data['hist_phi']) * np.pi / 180.)
    y = np.cos(np.array(data['hist_theta']) * np.pi / 180.)
    data['hist_sagittal_angle'] = np.arctan2(x, y) * 180 / np.pi

    # HISTOLOGY: Using phi and theta calculate angle in coronal plane
    x = np.sin(np.array(data['hist_theta']) * np.pi / 180.) * \
        np.cos(np.array(data['hist_phi']) * np.pi / 180.)
    y = np.cos(np.array(data['hist_theta']) * np.pi / 180.)
    data['hist_coronal_angle'] = np.arctan2(x, y) * 180 / np.pi

    data_repeated = pd.DataFrame.from_dict(data)

    return data_repeated