Skeletons#
Often in working with neurons, you want to measure things along a linear dimension of a neuron. Length of axon, for example. Or number of branches. However, the EM segmentation is a 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 MICrONS dataset, including the proofread cells with the most complete axons.
There are several formats of EM skeleton that you may encounter in this workshop and tutorial material. See the subsections below for more about them. As a summary:
swc skeleton : files end in .swc
meshwork skeleton : objects saved in h5 file format
precomputed skeleton : a binary file for each skeleton, within a directory with metadata ‘info’ files on how to render them
All of these formats are composed of a set of vertices and edges which form the graph of the neuron’s branching structure. As well as information about the compartment type of the vertex, indicating if it is axon, dendrite, soma etc. And the radius of the branch, measured as half the cable thickness in micrometers.
However, the different formats may have different metadata attached to each vertex. Meshwork skeleton objects may include annotations about the input and output synapses, and provide functions to operate on the graph of the skeleton. Precomputed format may include arbitrary vertex properties that summarize connectivity information (for EM neurons) and CCF brain area (for exaSPIM neurons; see the section on Single Cell Morphology for more)
Note
Any format of skeleton can be converted into a Meshwork skeleton object to make use of the graph functions.
How EM skeletons are generated#
In order to understand how these skeletons have been made, you have to understand how large scale EM data is represented. Portions of 3d space are broken up into chunks, such as the grid in the image above. Neurons, such as the cartoon green cell above, span many chunks. Components of the segmentation that live within a single chunk are called level 2 ids, this is because in fact the chunks get iteratively combined into larger chunks, until the chunks span the whole volume. We call this the PyChunkedGraph or PCG, after the library which we use to store and interact with this representation. Level 0 is the voxels, level 1 refers to the grouping of voxels within the chunk (also known as supervoxels) and level 2 are the groups of supervoxels within a chunk. A segmentation result can be thought of as a graph at any of these levels, where the voxels, supervoxels, or level 2 ids that are part of the same object are connected. In the above diagram, the PCG level 2 graph is represented as the light red lines.
Such graphs are useful in that they track all the parts of the neuron that are connected to one another, but they aren’t skeletons, because the graph is not directed, and isn’t a simple branching structure.
In order to turn them into a skeleton we have to run an algorithm, that finds a tree like structure that covers the graph and gets close to every node. This is called the TEASAR algorithm and you can read more about how to skeletonize graphs in the MeshParty documentation.
The result of the algorithm, when applied to a level 2 graph, is a linear tree (like the dotted purple one shown above), where a subset of the level 2 chunks are vertices in that tree, but all the unused level 2 nodes “near” a vertex (red unfilled circles) on the graph are mapped to one of the skeleton vertices (black arrows). This means there are two sets of indices, mesh indices, and skeleton indices, which have a mapping between them (see diagram above).
The MeshParty library allows us to easily store these representations and helps us relate them to each other. A Meshwork object is a data structure that is designed to have three main components that are kept in sync with mesh and skeleton indices:
mesh: the graph of PCG level2 ID nodes that are skeletonized, stored as a standard meshparty mesh. Note that this is not the high resolution mesh that you see in neuroglancer. See EM Meshes for more
skeleton: a meshwork skeleton, derived from the simplification of the mesh
anno : is a class that holds dataframes and adds some extra info to keep track of indexing.
Working with Meshwork Objects#
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 children, 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.
Skeleton Properties and Methods#
To plot this skeleton in a more sophisticated way, you have to start thinking of it as a graph, and the meshwork object has a bunch of tools and properties to help you utilize the skeleton graph.
Let’s list some of the most useful ones below You access each of these with nrn.skeleton.* Use the ? to read more details about each one
Properties
branch_points
: a list of skeleton vertices which are branchesroot
: the skeleton vertex 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
Example Data Access#
Load precomputed skeleton (v1078)#
A precomputed skeleton is a simplified representation of a neuron where its shape is captured by a tree-like structure that passes through the center of the neuron. More specificy, a precomputed skeleton stores a neuron as a graph with vertices and edges in addition to a collection of vertex-attached attributes that capture morphological and anatomical information about the neuron.
The most recent MICrONS skeletons are those from data release version 1078, which includes skeletonization of all neurons with proofread axons. The precomputed format is flexible and scalable, and also can be rendered in Neuroglancer.
Note
SWDB 2024 relies on this format when working with the EM and exaSPIM skeletons. The EM skeletons will be available as part of the course’s resources, but also in the public repository below.
# cloudpath to the precomputed EM files
cv_skel_path = "precomputed://gs://allen_neuroglancer_ccf/em_minnie65_v1078"
import cloudvolume
import numpy as np
# Initialize cloud volume
cv_obj = cloudvolume.CloudVolume(cv_skel_path, use_https = True)
# Example skeleton (as of v1078)
skeleton_id = 864691135591041291
# load precomputed neuron skeleton
cv_sk = cv_obj.skeleton.get(skeleton_id)
print("This is a precomputed skeleton of an EM neuron...\n")
print(cv_sk)
This is a precomputed skeleton of an EM neuron...
Skeleton(segid=864691135591041291, vertices=(shape=9154, float32), edges=(shape=9153, uint32), radius=(9154, float32), compartment=(9154, float32), presyn_counts=(9154, float32), postsyn_counts=(9154, float32), presyn_size=(9154, float32), postsyn_size=(9154, float32), space='physical' transform=[[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]])
We will use MeshParty’s skeleton
object to help plot and analyze the precomputed skeletons. To do this, convert the vertices, edges, and radius of the precomputed format with the skeleton
module.
from meshparty import skeleton
sk = skeleton.Skeleton(cv_sk.vertices,
cv_sk.edges,
vertex_properties={'radius': cv_sk.radius,
'compartment': cv_sk.compartment},
root = len(cv_sk.edges), # set root as final entry
remove_zero_length_edges = False)
Plot Skeleton in 2D#
The package Skeleton-plot provides some handy utilities for plotting meshwork skeletons, including:
specifying the 2D orientation
annotating somas
labeling compartments by color
import skeleton_plot as skelplot
import matplotlib.pyplot as plt
%matplotlib inline
f, ax = plt.subplots(figsize=(7, 10))
skelplot.plot_tools.plot_skel(
sk,
title="Neuron with radius and compartments",
line_width=1,
plot_soma=True,
soma_size = 150,
pull_radius=True,
invert_y=True,
pull_compartment_colors=True,
x="x",
y="y",
skel_color_map = { 1: "olive", 2: "black", 3: "firebrick",4: "salmon", },
ax=ax,
)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.axis('off')
(7726840.5, 8438740.0, 1497648.0, 675351.375)
Vertices and Neighborhoods#
# Vertices
print(f"There are {len(sk.vertices)} vertices in this neuron", "\n")
# Branch points
print("Branch points are at the following vertices: \n",sk.branch_points, "\n")
# End points
print("End points are at the following vertices: \n",sk.end_points, "\n")
# Root - the point associated with the root node, which is the soma
print("Root is vertex: ", sk.root.item(), "\n")
# Select downstream nodes from one branch point
downstream_nodes = sk.downstream_nodes(sk.branch_points[3])
print("Nodes downstream of selected brach are: \n", downstream_nodes, "\n")
There are 9154 vertices in this neuron
Branch points are at the following vertices:
[ 522 738 1115 1256 1263 1428 1493 1523 1535 1540 1632 1866 1878 1902
1935 1951 1996 2030 2043 2074 2104 2170 2190 2243 2262 2271 2294 2304
2384 2425 2445 2449 2484 2611 2616 2653 2687 2736 2755 2760 2775 2848
2867 2972 3049 3057 3110 3176 3226 3302 3340 3386 3413 3423 3453 3513
3516 3550 3558 3622 3638 3665 3688 3703 3721 3823 3972 4016 4024 4065
4068 4096 4113 4161 4225 4285 4302 4366 4390 4466 4543 4718 4921 4959
4985 5161 5176 5509 5518 5727 5746 5925 6800 7798 7858 7920 8463 8492
8614 8632 8645 8755 8903 8933 9026 9057 9082 9153]
End points are at the following vertices:
[ 5 29 31 68 86 170 172 177 218 245 286 291 323 335
366 392 419 470 516 564 638 647 881 934 950 1286 1347 1351
1544 1562 1661 1662 1672 1682 1743 1778 1781 1811 2008 2042 2072 2096
2168 2259 2367 2370 2373 2470 2562 2622 2623 2638 2682 2756 2757 2842
2882 2937 3030 3073 3130 3150 3193 3296 3395 3519 3720 3864 4051 4394
4477 4502 4611 4679 4740 4771 4776 4813 4887 5131 5198 5547 5551 5591
5597 5618 5663 5685 5823 5870 5891 6128 6146 6171 6181 6268 6314 6352
6400 6426 6445 6546 6709 6780 6848 6874 6967 7028 7070 7093 7289 7531
7822 8064 8252 8266 8323 8490 8497 8745 8861 8872 8931 9012 9014 9042
9116 9152]
Root is vertex: 9153
Nodes downstream of selected brach are:
[ 470 471 505 506 507 516 518 549 551 552 559 560 561 605
606 607 614 615 643 645 646 648 652 694 695 696 701 749
750 752 758 811 813 814 815 816 818 820 823 824 866 868
908 909 910 912 948 951 952 953 1012 1014 1015 1076 1078 1079
1080 1154 1156 1157 1158 1159 1160 1161 1162 1163 1164 1240 1241 1242
1244 1245 1247 1249 1250 1251 1252 1253 1254 1255 1256 1325 1326 1327
1328 1329 1331 1333 1334 1335 1336 1395 1396 1397 1399 1402 1460 1461
1463 1465 1467 1468 1469 1471 1472 1473 1476 1536 1539 1596 1597]
Paths and Segments#
# Segments - path of nodes between two vertices i and j which are assumed to be a root, branch point, or endpoint.
print("The number of branch segments are: ",len(sk.segments), "\n")
# Path between - path_between() function finds the vertices on the path between two points on the skeleton graph.
nodes_between = sk.path_between(0,150)
print("Path between the given nodes: ", nodes_between, "\n")
# Path-length - path_length() function calculates the physical distance along the path for a neuron
full_pathlength = sk.path_length() / 1000 # Convert to microns
print("Path-length of the entire neuron: ", full_pathlength, ' um', "\n")
# Path-length for arbitrary segment
example_segment = 11
segment_pathlength = sk.path_length(sk.segments[example_segment]) / 1000 # Convert to microns
print("Path-length of one segment: ", segment_pathlength, ' um', "\n")
The number of branch segments are: 236
Path between the given nodes: [ 0 2 7 1 3 8 9 10 11 12 13 14 15 17
16 18 19 20 21 22 23 24 25 26 27 28 33 34
38 39 42 45 46 49 52 55 56 60 63 69 75 76
82 83 93 92 105 114 115 121 126 133 134 139 140 149
162 163 181 192 205 227 228 247 261 277 295 316 317 346
369 387 409 410 437 436 438 461 462 496 497 538 539 589
590 591 592 593 632 675 676 728 798 847 890 930 991 1050
1126 1214 1298 1373 1441 1509 1576 1622 1674 1675 1673 1755 1754 1844
1916 2003 2083 2156 2239 2346 2464 2463 2576 2704 2812 2925 2926 3042
3151 3271 3270 3382 3487 3588 3589 3673 3672 3746 3826 3825 3940 4037
4038 4138 4230 4229 4312 4390 4391 4313 4314 4231 4139 4140 4039 4040
3941 3827 3828 3830 3748 3750 3675 3676 3677 3592 3489 3490 3385 3273
3274 3153 3044 3045 2931 2821 2822 2711 2712 2582 2466 2467 2349 2350
2241 2159 2085 2086 2006 2007 1918 1847 1757 1758 1677 1678 1625 1579
1514 1516 1442 1374 1375 1299 1300 1215 1127 1051 992 931 891 848
799 729 730 677 678 633 594 540 498 463 439 440 413 411
412 388 371 370 347 348 318 296 279 280 278 262 248 229
230 206 193 182 165 164 150]
Path-length of the entire neuron: 20129.62 um
Path-length of one segment: 26.120412109375 um
Masking#
One of the most useful methods of the skeleton meshwork’s is the ability to mask or select only parts of the skeleton to work with at a time. The function apply_mask()
acts on the meshwork skeleton, and will apply in place if apply_mask(in_place=True).
Warning: be aware when setting a mask in place–mask operations are additive. To reset the mask completely, use reset_mask(in_place=True)
.
# Let's select a set of nodes from example segment 11
example_segment = 11
selected_nodes = sk.segments[example_segment]
print("Path of the selected nodes is: \n", selected_nodes, "\n")
# reset to make sure you have the full skeleton
sk.reset_mask(in_place=True)
# Apply a mask to these nodes
sk_masked = sk.apply_mask(selected_nodes)
# Check that this masked skeleton matches the pathlength calculated above
print("Path-length of masked segment: ", sk_masked.path_length() / 1000, ' um')
Path of the selected nodes is:
[291 290 308 306 304 331 332 358 379 380 401 428 452 484]
Path-length of masked segment: 26.120412109375 um
Critically, apply_mask() allows us to mask a neuron according to its compartment label: axon, dendrite, soma, etc.
Compartment label conventions (from standardized swc files www.neuromorpho.org )
0 - undefined
1 - soma
2 - axon
3 - (basal) dendrite
4 - apical dendrite
5+ - custom
print("The unique compartment labels in this skeleton are:", np.unique(sk.vertex_properties['compartment']), "\n")
# Select the indices associated with the dendrites
dendrite_inds = (sk.vertex_properties['compartment']==3) | (sk.vertex_properties['compartment']==4)| (sk.vertex_properties['compartment']==1) #soma is included here to connect the dendrite graphs
# create new skeleton that masks (selects) only the axon
sk_dendrite = sk.apply_mask(dendrite_inds)
print("Dendrite pathlength of all branches is : ", sk_dendrite.path_length() / 1000, ' um', "\n")
# Select the indices associated with the axon, find length
axon_inds = sk.vertex_properties['compartment']==2
sk_axon = sk.apply_mask(axon_inds)
print("Axon pathlength is : ", sk_axon.path_length() / 1000, ' um', "\n")
The unique compartment labels in this skeleton are: [1. 2. 3.]
Dendrite pathlength of all branches is : 5832.823 um
Axon pathlength is : 14280.873 um
# Render all the segments in the dendrite graph
fig, ax = plt.subplots(figsize=(4,3), dpi=150)
for ss in range(len(sk_dendrite.segments)):
seg = sk_dendrite.segments[ss]
ax.plot(sk_dendrite.vertices[seg][:,0],
sk_dendrite.vertices[seg][:,1], linewidth=0.5)
# add soma marker
ax.plot(sk.vertices[sk.root][0],
sk.vertices[sk.root][1], 'oc', markersize=5)
ax.invert_yaxis()
ax.axis('off')
(7915166.75, 8225504.25, 986368.134375, 660541.053125)
Load meshwork h5 file (v661)#
The skeletons of the meshes are calculated at specific timepoints. The most recent collection of all neurons in the dataset was at materialization version 661 (June, 2023).
Both the h5. meshwork neuron object and the .swc skeletons are available in the BossDB repository
# path to the skeleton and meshwork .h5 files
mesh_path = "s3://bossdb-open-data/iarpa_microns/minnie/minnie65/skeletons/v661/meshworks/"
import skeleton_plot.skel_io as skel_io
# Example skeleton (as of v661)
nucleus_id = 230650
segment_id = 864691134940133219
# load a pregenerated neuron skeleton
nrn = skel_io.load_mw(mesh_path, f"{segment_id}_{nucleus_id}.h5")
Annotations#
nrn.anno
has a 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 consistent 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:
nrn.anno.post_syn.df.head(3)
ctr_pt_position | id | post_pt_level2_id | post_pt_mesh_ind | post_pt_position | post_pt_root_id | post_pt_supervoxel_id | pre_pt_position | pre_pt_root_id | pre_pt_supervoxel_id | size | post_pt_mesh_ind_filt | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | [167494, 178677, 22973] | 147824610 | 159893593257935774 | 6887 | [167518, 178672, 22972] | 864691134940133219 | 87835999220528052 | [167440, 178598, 22980] | 864691135947747425 | 87765630476372999 | 3400 | 6887 |
1 | [138824, 196821, 23152] | 97189471 | 155955348832780498 | 2332 | [138796, 196792, 23151] | 864691134940133219 | 83897754795009976 | [138892, 196914, 23159] | 864691136785011310 | 83897823514489547 | 2012 | 2332 |
2 | [166258, 197684, 22697] | 140170915 | 159685029646041267 | 6778 | [166246, 197648, 22691] | 864691134940133219 | 87627435608336600 | [166200, 197720, 22703] | 864691135444655596 | 87627435608350744 | 4648 | 6778 |
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:
nrn.distance_to_root(nrn.anno.post_syn.mesh_index)
array([107741.13371277, 50763.05639648, 89650.54669189, ...,
53957.32983398, 67804.22151184, 52812.87652588])
Important
Note that the nrn.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:
syn_df = nrn.anno.pre_syn.df.copy()
syn_df['dist_to_root'] = nrn.distance_to_root(nrn.anno.pre_syn.mesh_index)
Filtering Annotations by Skeleton Arbor#
Each annotation table has a ‘filter_query’ method that takes a boolean mesh mask and returns only those rows of the dataframe associated with those particular locations on the mesh. Let’s use what we learned above in two examples: first, getting all input synapses within 50 microns of the root and second, getting all input synapses on one particular branch off of the soma.
dtr = nrn.distance_to_root() / 1_000 # Convert from nanometers to microns
nrn.anno.post_syn.filter_query( dtr < 50).df
ctr_pt_position | id | post_pt_level2_id | post_pt_mesh_ind | post_pt_position | post_pt_root_id | post_pt_supervoxel_id | pre_pt_position | pre_pt_root_id | pre_pt_supervoxel_id | size | post_pt_mesh_ind_filt | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
5 | [157726, 193046, 22788] | 127341851 | 158488142519730768 | 5752 | [157706, 193094, 22795] | 864691134940133219 | 86430548482153326 | [157682, 192972, 22780] | 864691136824031086 | 86430548482140967 | 11020 | 5752 |
8 | [151982, 191342, 23145] | 120502078 | 157713880242454752 | 4892 | [152038, 191390, 23149] | 864691134940133219 | 85656286204696158 | [151996, 191298, 23140] | 864691135489252804 | 85656286204685856 | 6928 | 4892 |
22 | [150364, 191008, 23443] | 112691936 | 157502705290445181 | 4655 | [150330, 190992, 23435] | 864691134940133219 | 85445111253025608 | [150438, 190954, 23442] | 864691136827445870 | 85445111253035274 | 5252 | 4655 |
25 | [154448, 194194, 23180] | 120218955 | 158066067560726725 | 5285 | [154378, 194200, 23178] | 864691134940133219 | 86008473522980712 | [154520, 194204, 23181] | 864691136757444974 | 86008473522981601 | 312 | 5285 |
29 | [154090, 190584, 22953] | 120378123 | 157995217713103485 | 5202 | [154072, 190652, 22947] | 864691134940133219 | 85937623675692998 | [154078, 190578, 22960] | 864691136713068398 | 85937623675711336 | 9116 | 5202 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
3431 | [156296, 195908, 23183] | 127450278 | 158347817415344328 | 5610 | [156264, 195978, 23183] | 864691134940133219 | 86290223377588084 | [156252, 195838, 23182] | 864691135210513856 | 86290223377588010 | 7036 | 5610 |
3434 | [147054, 185786, 23994] | 110381182 | 157079805697721156 | 3949 | [147026, 185772, 23992] | 864691134940133219 | 85022211660301024 | [147124, 185818, 24002] | 864691135658301314 | 85022211660316873 | 9124 | 3949 |
3436 | [154928, 196122, 23050] | 116358701 | 158136711182811181 | 5356 | [154940, 196164, 23050] | 864691134940133219 | 86079117144942192 | [154934, 196072, 23055] | 864691136032031675 | 86079117144945472 | 3968 | 5356 |
3437 | [150724, 186076, 23779] | 112536188 | 157572386906964425 | 4735 | [150760, 185998, 23771] | 864691134940133219 | 85514792869314896 | [150720, 186090, 23783] | 864691136195284556 | 85514861588802988 | 508 | 4735 |
3438 | [149372, 190040, 23289] | 112691418 | 157361830363136488 | 4400 | [149382, 190088, 23280] | 864691134940133219 | 85304236325525055 | [149360, 190010, 23283] | 864691136195284556 | 85304236325524912 | 2124 | 4400 |
473 rows × 12 columns
We can also use one set of annotations as an input to filter query from another set of annotations. For example, due to errors in segmentation or mistakes in synapse detection, there can be synaptic outputs on the dendrite. However, if we have an is_axon
annotation that simply contains a collection of vertices that correspond to the cell’s axon. We can use this annotation to create a mask and filter out all of the synapses that are not on the axon.
axon_mask = nrn.anno.is_axon.mesh_mask
nrn.anno.pre_syn.filter_query(~axon_mask).df # The "~" is a logical not operation that flips True and False
ctr_pt_position | id | post_pt_position | post_pt_root_id | post_pt_supervoxel_id | pre_pt_level2_id | pre_pt_mesh_ind | pre_pt_position | pre_pt_root_id | pre_pt_supervoxel_id | size | pre_pt_mesh_ind_filt | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
65 | [162422, 198658, 23978] | 134156279 | [162390, 198688, 23981] | 864691136785500782 | 87134991972525815 | 159192586009969622 | 6439 | [162464, 198636, 23985] | 864691134940133219 | 87134991972537317 | 972 | 6439 |
166 | [157086, 198862, 23150] | 127450957 | [157010, 198908, 23151] | 864691135952299043 | 86361004438641166 | 158418529756905664 | 5697 | [157164, 198808, 23156] | 864691134940133219 | 86360935719148064 | 668 | 5697 |
444 | [174372, 191708, 23157] | 155609310 | [174306, 191714, 23158] | 864691136210173628 | 88752579668012861 | 160810104986271912 | 7366 | [174362, 191656, 23156] | 864691134940133219 | 88752510948535116 | 2888 | 7366 |
456 | [147822, 191848, 24173] | 110894360 | [147772, 191806, 24171] | 864691135393532914 | 85093405104959624 | 157150999142727859 | 4073 | [147868, 191768, 24170] | 864691134940133219 | 85093405104962373 | 5068 | 4073 |
486 | [134460, 194490, 22866] | 84031640 | [134458, 194454, 22867] | 864691135528294084 | 83264161152691689 | 155321755190165812 | 1665 | [134424, 194532, 22865] | 864691134940133219 | 83264161152685771 | 4112 | 1665 |
490 | [175468, 203358, 22627] | 158325494 | [175478, 203300, 22634] | 864691134988925306 | 88894828917712054 | 160952422955484181 | 7444 | [175454, 203396, 22626] | 864691134940133219 | 88894828917704472 | 1308 | 7444 |
517 | [135018, 192856, 23553] | 86645797 | [135062, 192872, 23550] | 864691136210178236 | 83334323805717938 | 155391917910130691 | 1731 | [135004, 192878, 23557] | 864691134940133219 | 83334323872216719 | 292 | 1731 |
604 | [148492, 207248, 23239] | 112357047 | [148486, 207244, 23240] | 864691135853648185 | 85165835299321100 | 157223429336990100 | 4211 | [148462, 207276, 23241] | 864691134940133219 | 85165835299314732 | 2144 | 4211 |
1017 | [156432, 189900, 23033] | 125728175 | [156470, 189928, 23033] | 864691133262391923 | 86289398677197625 | 158346992714515502 | 5599 | [156402, 189886, 23034] | 864691134940133219 | 86289398677193502 | 2652 | 5599 |
1067 | [128170, 199156, 23519] | 71166059 | [128102, 199184, 23512] | 864691136012791778 | 82420354765068733 | 154477948802433940 | 902 | [128160, 199106, 23519] | 864691134940133219 | 82420354765076207 | 1584 | 902 |
1293 | [156188, 191520, 22873] | 127340911 | [156258, 191546, 22880] | 864691136210178236 | 86289604835470780 | 158276830128767512 | 5520 | [156152, 191638, 22877] | 864691134940133219 | 86219236091245799 | 1632 | 5520 |
As a sanity check, we can use nglui
to see if these synapses we have labeled as being on the axon are all where we expect.
from caveclient import CAVEclient
from nglui.statebuilder.helpers import make_synapse_neuroglancer_link
client = CAVEclient('minnie65_public')
client.materialize.version = 661 # version at which skeletons were calculated
make_synapse_neuroglancer_link(
nrn.anno.pre_syn.filter_query(axon_mask).df,
client,
return_as="html"
)
(Click one of the synapse annotations to load the neuron mesh).
Another common example might be to pick one of the child nodes of the soma and get all of the synapses on that branch. We can do this by using the nrn.skeleton.get_child_nodes
function to get the skeleton vertex indices of the child nodes and then use that to filter the synapses.
branch_index = 0 # Let's just use the first child vertex of the root node, which is at the soma by default.
branch_inds = nrn.downstream_of(nrn.child_index(nrn.root)[branch_index])
branch_mask = branch_inds.to_mesh_mask
make_synapse_neuroglancer_link(
nrn.anno.post_syn.filter_query(branch_mask).df,
client,
return_as="html"
)
Load swc skeleton (v661)#
The skeletons of the meshes are calculated at specific timepoints. The most recent collection of all neurons in the dataset was at materialization version 661 (June, 2023).
Both the h5. meshwork neuron object and the .swc skeletons are available in the BossDB repository
# path to the skeleton .swc files
skel_path = "s3://bossdb-open-data/iarpa_microns/minnie/minnie65/skeletons/v661/skeletons/"
import skeleton_plot.skel_io as skel_io
# Example skeleton (as of v661)
nucleus_id = 230650
segment_id = 864691134940133219
# load the .swc skeleton
sk = skel_io.read_skeleton(skel_path, f"{segment_id}_{nucleus_id}.swc")
To get a more accurate understanding of the neuron’s morphology, you can pull in information about the radius and compartment labels into your visualization.
Here the axon is colored black, basal dendrites ‘firebrick’ red, apical dendrites ‘salmon’ orange, and the soma a green ‘olive’.
Compartment label conventions (from from standardized swc files www.neuromorpho.org )
0 - undefined
1 - soma
2 - axon
3 - (basal) dendrite
4 - apical dendrite
5+ - custom
f, ax = plt.subplots(figsize=(7, 10))
skelplot.plot_tools.plot_skel(
sk,
title=nucleus_id,
line_width=1,
plot_soma=True,
invert_y=True,
pull_compartment_colors=True,
x="z",
y="y",
skel_color_map = { 3: "firebrick",4: "salmon",2: "black",1: "olive" },
)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['top'].set_visible(False)
ax.spines['bottom'].set_visible(False)