from caveclient import CAVEclient
import numpy as np
import pandas as pd
= CAVEclient("minnie65_public")
client
# specify the materialization version, for consistency across time",
= 1412
client.version
# Example: pyramidal cell in v1412
= 864691135572530981 example_cell_id
Download Skeletons
This tutorial has recently been updated to materialization version 1412 (from 1300).
We have released a new public version 1412, as part of our quarterly release schedule. See details at Release Manifests: 1412.
Connected morphological representations: skeletons
Often in thinking about neurons, you want to measure things along a linear dimension of a neuron.
However, the segmentation and meshes are a full complex 3d shape that makes this non-trivial. There are methods for reducing the shape of a segmented neuron down to a linear tree like structure usually referred to as a skeleton. We have precalculated skeletons for a large number of cells in the dataset, and make the skeleton generation available on our server, on demand.
The meshes that one sees in Neuroglancer are available to download through the python client cloud-volume, and can be loaded for analysis and visualization in other tools.
The MICrONS data can be representing and rendered in multiple formats, at different levels of abstraction from the original imagery.
What is a Precomputed skeleton?
These skeletons are stored in the cloud as a bytes-IO object, which can be viewed in Neuroglancer, or downloaded with CAVEclient.skeleton module. These precomputed skeletons also contain annotations on the skeletons that have the synapses, which skeleton nodes are axon and which are dendrite, and which are likely the apical dendrite of excitatory neurons.
For more on how skeletons are generated from the mesh objects, and additional tools for generating, cacheing, and downloading meshes, see the Skeleton Service documentation
CAVEclient to download skeletons
Retrieve a skeleton using get_skeleton()
. The available output_formats (described below) are:
dict
containing vertices, edges, radius, compartment, and various metadata (default if unspecified)swc
a Pandas Dataframe in the SWC format, with ordered vertices, edges, compartment labels, and radius.
Note: if the skeleton doesn’t exist in the server cache, it may take 20-60 seconds to generate the skeleton before it is returned. This function will block during that time. Any subsequent retrieval of the same skeleton should go very quickly however.
Before using any programmatic access to the data, you first need to set up your CAVEclient token.
= client.skeleton.get_skeleton(example_cell_id, output_format='swc')
sk_df
sk_df.head()
id | type | x | y | z | radius | parent | |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1365.120 | 763.456 | 812.60 | 3.643 | -1 |
1 | 1 | 3 | 1368.200 | 755.760 | 811.24 | 0.300 | 0 |
2 | 2 | 3 | 1368.200 | 758.552 | 811.68 | 1.915 | 1 |
3 | 3 | 3 | 1368.200 | 758.560 | 812.12 | 1.915 | 2 |
4 | 4 | 3 | 1368.192 | 758.560 | 812.44 | 2.815 | 3 |
Skeletons are “tree-like”, where every vertex (except the root vertex) has a single parent that is closer to the root than it, and any number of child vertices. Because of this, for a skeleton there are well-defined directions “away from root” and “towards root” and few types of vertices have special names:
You can see these vertices and edges represented in the alternative dict
skeleton output.
= client.skeleton.get_skeleton(example_cell_id, output_format='dict')
sk_dict
sk_dict.keys()
dict_keys(['meta', 'edges', 'mesh_to_skel_map', 'root', 'vertices', 'compartment', 'radius'])
Alternately, you can query a set of root_ids, for example all of the proofread cells in the dataset, and bulk download the skeletons
= client.materialize.tables.proofreading_status_and_strategy(
prf_root_ids ='t').query()['pt_root_id']
status_axon3) prf_root_ids.head(
0 864691135308929350
1 864691135163673901
2 864691136812081779
Name: pt_root_id, dtype: int64
= client.skeleton.get_bulk_skeletons(
all_sk_dict 3),
prf_root_ids.head(=True,
generate_missing_skeletons='dict',
output_format=4)
skeleton_version
all_sk_dict.keys()
dict_keys(['864691135308929350', '864691135163673901', '864691136812081779'])
'864691135308929350'].keys() all_sk_dict[
dict_keys(['meta', 'edges', 'mesh_to_skel_map', 'root', 'vertices', 'compartment', 'radius', 'lvl2_ids'])
convert dictionary to meshwork skeleton
We use the python package Meshparty to convert between mesh representations of neurons, and skeleton representations.
See Download Meshes | MeshParty for more details.
Skeletons are “tree-like”, where every vertex (except the root vertex) has a single parent that is closer to the root than it, and any number of child vertices. Because of this, for a skeleton there are well-defined directions “away from root” and “towards root” and few types of vertices have special names:
Converting the pregenerated skeleton sk_dict
to a meshwork object sk
converts the skeleton to a graph object, for convenient representation and analysis.
from meshparty.skeleton import Skeleton
= Skeleton.from_dict(sk_dict) sk
Plot with skeleton_plot
Our convenience package skeleton_plot renders the skeleton in aligned, 2D views.
pip install skeleton_plot
Our convenience package skeleton_plot renders the skeleton in aligned, 2D views, with the compartments labeled in different colors. Here, dendrite is red, axon is blue, and soma (the root of the connected graph) is olive
from skeleton_plot.plot_tools import plot_skel
=sk,
plot_skel(sk=True,
invert_y=True,
pull_compartment_colors=True,
plot_soma )
Download or generate skeletons with pcg_skel
pcg_skel
is a package used to rapidly build neuronal skeletons from electron microscopy data in the CAVE ecosystem. It integrates structural data, connectivity data, and local features stored across many aspects of a CAVE system, and creates objects as Meshparty meshes, skeletons, and MeshWork files for subsequent analysis.
By harnessing the way the structural data is stored, you can build skeletons for even very large neurons quickly and with little memory use.
To install pcg_skel, use pip.
pip install pcg_skel
By loading a skeleton as a meshwork skeleton, you can easily ‘rehydrate’ the mesh features of the neuron (annotations such as synapses and compartment labels) and MeshParty functions (such as masking and pathlength)
from pcg_skel import get_meshwork_from_client
= get_meshwork_from_client(example_cell_id,
sk_mesh =client,
client=True,
synapses=True,
restore_graph=True,
restore_properties=4,
skeleton_version
) sk_mesh
<meshparty.meshwork.meshwork.Meshwork at 0x29f79b4cc50>
Skeletons are “tree-like”, where every vertex (except the root vertex) has a single parent that is closer to the root than it, and any number of child vertices. Because of this, for a skeleton there are well-defined directions “away from root” and “towards root” and few types of vertices have special names:
Branch point
: vertices with two or more children, where a neuronal process splits.End point
: vertices with no childen, where a neuronal process ends.Root point
: The one vertex with no parent node. By convention, we typically set the root vertex at the cell body, so these are equivalent to “away from soma” and “towards soma”.Segment
: A collection of vertices along an unbranched region, between one branch point and the next end point or branch point downstream.
MeshWork skeletons represent the neuron as a graph, with tools and properties to utilize the skeleton csgraph
.
Properties
branch_points
: a list of skeleton vertices which are branchesroot
: the skeleton vertice which is the somadistance_to_root
: an array the length of vertices which tells you how far away from the root each vertex isroot_position
: the position of the root node in nanometersend_points
: the tips of the neuroncover_paths
: a list of arrays containing vertex indices that describe individual paths that in total cover the neuron without repeating a vertex. Each path starts at an end point and continues toward root, stopping once it gets to a vertex already listed in a previously defined path. Paths are ordered to start with the end points farthest from root first. Each skeleton vertex appears in exactly one cover path.csgraph
: a scipy.sparse.csr.csr_matrix containing a graph representation of the skeleton. Useful to do more advanced graph operations and algorithms. https://docs.scipy.org/doc/scipy/reference/sparse.csgraph.htmlkdtree
: a scipy.spatial.ckdtree.cKDTree containing the vertices of skeleton as a kdtree. Useful for quickly finding points that are nearby. https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.cKDTree.html
Methods
path_length(paths=None)
: the path length of the whole neuron if no arguments, or pass a list of paths to get the path length of that. A path is just a list of vertices which are connected by edges.path_to_root(vertex_index)
: returns the path to the root from the passed vertexpath_between(source_index, target_index)
: the shortest path between the source vertex index and the target vertex indexchild_nodes(vertex_indices)
: a list of arrays listing the children of the vertex indices passed inparent_nodes(vertex_indices)
: an array listing the parent of the vertex indices passed in
You access each of these properties and methods with sk.skeleton.*
Use the ? to read more details about each one
Annotation properties
sk.anno
has set of annotation tables containing some additional information for analysis. Each annotation table has both a Pandas DataFrame object storing data and additional information that allow the rows of the DataFrame to be mapped to mesh and skeleton vertices. For the neurons that have been pre-computed, there is a consisent set of annotation tables:
post_syn
: Postsynaptic sites (inputs) for the cellpre_syn
: Presynaptic sites (outputs) for the cellis_axon
: List of vertices that have been labeled as part of the axonlvl2_ids
: Gives the PCG level 2 id for each mesh vertex, largely for book-keeping reasons.segment_properties
: For each vertex, information about the approximate radius, surface area, volume, and length of the segment it is on.vol_prop
: For every vertex, information about the volume and surface area of the level 2 id it is associated with.
To access one of the DataFrames, use the name of the table as an attribute of the anno
object and then get the .df
property. For example, to get the postsynaptic sites, we can do:
3) sk_mesh.anno.post_syn.df.head(
id | size | pre_pt_supervoxel_id | post_pt_supervoxel_id | post_pt_root_id | pre_pt_position | post_pt_position | ctr_pt_position | post_pt_level2_id | post_pt_mesh_ind | post_pt_mesh_ind_filt | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 456611088 | 5140 | 113239596703504810 | 113239596703516355 | 864691135572530981 | [1410648.0, 729000.0, 850240.0] | [1410800.0, 728960.0, 850520.0] | [1410672.0, 728960.0, 850280.0] | 185297190741082374 | 11880 | 11880 |
1 | 460786295 | 1904 | 114084296310348680 | 114084365029814954 | 864691135572530981 | [1434984.0, 737808.0, 794360.0] | [1435488.0, 738120.0, 794120.0] | [1435224.0, 737856.0, 794400.0] | 186141959067271764 | 13325 | 13325 |
2 | 415745273 | 14532 | 110214290325399938 | 110214290325398722 | 864691135572530981 | [1322336.0, 744768.0, 807360.0] | [1322032.0, 744560.0, 807440.0] | [1322120.0, 744656.0, 807360.0] | 182271884363039168 | 3695 | 3695 |
However, in addition to these rows, you can also easily get the mesh vertex index or skeleton vertex index that a row corresponds to with nrn.anno.post_syn.mesh_index
or nrn.anno.post_syn.skel_index
respectively. This seems small, but it allows you to integrate skeleton-like measurements with annotations trivially.
For example, to get the distance from the cell body for each postsynaptic site, we can do:
sk_mesh.distance_to_root(sk_mesh.anno.post_syn.mesh_index)
array([104938.90991211, 90399.17230225, 52673.86270142, ...,
70199.43048096, 212153.28248978, 91076.815979 ])
Note that the sk.distance_to_root
, like all basic meshwork object functions, expects a mesh vertex index rather than a skeleton vertex index.
A common pattern is to copy a synapse dataframe and then add columns to it. For example, to add the distance to root to the postsynaptic sites, we can do:
= sk_mesh.anno.pre_syn.df.copy()
syn_df 'dist_to_root'] = sk_mesh.distance_to_root(sk_mesh.anno.pre_syn.mesh_index)
syn_df[3)[['id','size','dist_to_root']] syn_df.head(
id | size | dist_to_root | |
---|---|---|---|
0 | 395248437 | 9532 | 550625.977657 |
1 | 440616430 | 4432 | 59338.621429 |
2 | 439592768 | 576 | 426225.854660 |
This gives us the path-distance of every synapse from the root
in nanometers (nm).
Calculate path_length()
of axon and dendrite
Given the skeleton graph, it is trivial to estimate the pathlength (sum of all branch lengths) for a neuron:
/ 1e6 # to convert from nm to mm sk_mesh.path_length()
np.float32(14.846251)
However, the axon and dendrite compartments differ dramatically in their geometric and physiological properties. You generally will want to treat these seperately.
Fortunately the meshwork skeleton includes annotations as to whether the compartment is likely axon or not:
sk_mesh.anno.is_axon
and you can mask the neuron skeleton by the type of comparment simply by using these annotations as an index:
# Apply a mask to the mesh using the axon labels
sk_mesh.apply_mask(sk_mesh.anno.is_axon.mesh_mask)
print (f'Axon path length is {sk_mesh.path_length() / 1e6} mm')
# Reset the mask
sk_mesh.reset_mask()
print (f'Total path length is {sk_mesh.path_length() / 1e6} mm')
Axon path length is 7.599738121032715 mm
Total path length is 14.846250534057617 mm