Skip to content

Note

Click here to download the full example code

Axon-Dendrite Splits#

This tutorial shows you how to split a neuron into its axonal and dendritic compartment.

Background#

Neurons, generally, have two main compartments: axon and dendrites. In textbooks, the axon is the part of the neuron that sends signals to other neurons, while the dendrites are the parts that receive signals from other neurons.

Created in https://BioRender.com

In reality, the distinction between axon and dendrite is not always so clear-cut: axons can also receive signals and dendrites can also send signals. In particular invertebrate neurons can be very mixed with the postsynapses (inputs) and presynapses (outputs) almost evenly distributed across the whole neuron. Whether a connection between two neurons is axo-dendritic or axo-axonic likely makes a big difference.

So how do we determine what's axon and what's dendrite?

Two Methods#

NAVis currently implements two methods for axon-dendrite splits:

  1. Flow-based splits via navis.split_axon_dendrite
  2. Label propagation via navis.split_axon_dendrite_prop

Both methods work on neuron skeletons (TreeNeurons) as well meshes (MeshNeuron), and require the neuron to have pre- and postsynapses as .connectors. Please see also the table at the bottom for a direct comparison.

Synapse Flow Centrality#

Splitting a neuron into axon and dendrite using synapse flow centrality (SFC) was first proposed by Schneider-Mizell et al. (2016). In a nutshell: we draw paths from all inputs (postsynapses) to all outputs (presynapses). For each segment in the neuron, we counts the paths that go across it. If we split the neuron at the segment(s) with the highest SFC, we separate it into axon and dendrite.

Synapse Flow Centrality Split

Note

navis.split_axon_dendrite also implements a number of other flow-based methods. See the metric parameter for details!

This method is very fast and works well for "normal" neurons. But see for yourself:

import navis
import numpy as np

# Load an example neuron. This is a mesh but the same process works for skeletons as well.
n = navis.example_neurons(1, kind="mesh")

Like all example neurons, this one also comes with a connector table containing both pre- and postsynapses:

n.connectors.head()
connector_id type x y z roi confidence
0 0 pre 6444 21608 14516 LH(R) 0.959
1 1 pre 6457 21634 14474 LH(R) 0.997
2 2 pre 4728 23538 14179 LH(R) 0.886
3 3 pre 5296 22059 16048 LH(R) 0.967
4 4 pre 4838 23114 15146 LH(R) 0.990
fig, ax = navis.plot2d(n, connectors=True, view=("x", "-z"), color="k")

tutorial morpho 03 ad split

In above plot, the cyan dots are postsynapses and red dots are presynapses. This is a unipolar invertebrate neuron: a single process extends from the soma and branches into a proximal dendrite and a distal axon. Note how both compartments contain both pre- and postsynapses?

Let's try to split it!

splits = navis.split_axon_dendrite(n)
splits
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (469.6KiB)
type name id units n_vertices n_faces
0 navis.MeshNeuron DA1_lPN_R 1734350788 8 nanometer 3561 7680
1 navis.MeshNeuron DA1_lPN_R 1734350788 8 nanometer 1246 2474
2 navis.MeshNeuron DA1_lPN_R 1734350788 8 nanometer 1500 2868

By default, split_axon_dendrite will take one or more neurons and try to break them into axon and dendrite which it returns as a NeuronList. But wait: why are there three pieces in that neuronlist?

That's because the more-or-less synapse-free bit that connects the axon and the dendrites is returned as a separate "linker":

splits.compartment

Out:

array(['dendrite', 'linker', 'axon'], dtype='<U8')

So far so good but what do those splits look like? For your convenience, the split function has added not just a .compartment but also a .color property to the splits - makes visualisation easy:

fig, ax = navis.plot2d(splits, color=splits.color, view=("x", "-z"))

tutorial morpho 03 ad split

The axon is red, dendrites are blue and the linker is grey. Looks reasonable!

If you like, you can use this to calculate the "segregation index" - also from Schneider-Mizell et al. - which tells you how well pre- and postsynapses separate between axon and dendrite:

navis.segregation_index(splits[splits.compartment != "linker"])

Out:

0.27864085872781685

If you're working with skeletons, you can also just label the neuron instead of splitting it:

s = navis.example_neurons(1, kind="skeleton")
_ = navis.split_axon_dendrite(s, label_only=True)
s.nodes[["compartment"]]
compartment
0 linker
1 linker
2 linker
3 linker
4 linker
... ...
4460 linker
4461 linker
4462 linker
4463 linker
4464 linker

4465 rows × 1 columns

Label Propagation#

The second method is based on label propagation. In a nutshell: we're using the locations of pre- and postsynapses as sparse initial labels for "axon" and "dendrite" and propagate those labels across the neurons.

Label Propagation Split

By doing that, we fill in both missing labels but also allow the initial labels to be refined - for example, if there is a single postsynapse surrounded by many presynapses , we probably want to ignore that postsynapse and consider this part of the axon. For a more detailed explanation, check out the tutorial on label propagation.

Let's give it a shot!

splits = navis.split_axon_dendrite_prop(n)
fig, ax = navis.plot2d(splits, color=splits.color, view=("x", "-z"))

tutorial morpho 03 ad split

Alread looks good! However, we're not actually making use of the biggest advantage of this method: it gives us probabilities!

To get probabilities, we need to use label-only mode:

n = navis.split_axon_dendrite_prop(n, label_only=True)

# The compartment is given on a per-vertex or per-node basis
n.compartment

Out:

array(['dendrite', 'dendrite', 'dendrite', ..., nan, 'axon', 'axon'],
      shape=(6309,), dtype=object)

Note how there is at least one nan among the compartments? Nodes/vertices that weren't reached because they are either disconnected from the initial set of labels or because we didn't run enough iterations (see the max_iter parameter) will not be given a compartment.

# Same for probabilities
n.compartment_prob

Out:

array([0.7288271 , 0.71798974, 0.7180464 , ...,        nan, 0.9128577 ,
       0.9128962 ], shape=(6309,), dtype=float32)

Let's try visualising those probabilities!

# The probabilities
axon_prob = n.compartment_prob.copy()

# Unvisited (i.e. likely disconnected) nodes will have NaN as probability
axon_prob[np.isnan(axon_prob)] = 0

# Where the predicted compartment is "dendrite", the probablity for the axon is 1 - probability
axon_prob[n.compartment == "dendrite"] = 1 - axon_prob[n.compartment == "dendrite"]

# Plot
fig, ax = navis.plot2d(n, color_by=axon_prob, palette="Reds", view=("x", "-z"))
_ = ax.set_title("axon probability")

axon probability

# Same for the dendrites
dend_prob = n.compartment_prob.copy()
dend_prob[np.isnan(dend_prob)] = 0
dend_prob[n.compartment == "axon"] = 1 - dend_prob[n.compartment == "axon"]

fig, ax = navis.plot2d(n, color_by=dend_prob, palette="Blues", view=("x", "-z"))
_ = ax.set_title("dendrite probability")

dendrite probability

As you can see there is a smooth transition between axon and dendrite. You could use that to e.g. refine the axon/dendrite assignment into high and low-confidence, or try to define a linker.

Comparison#

Below table compares flow- and propagation-based functions:

split_axon_dendrite (flow) split_axon_dendrite_prop (propagation)
Works on TreeNeurons? Yes Yes
Works on MeshNeurons? Yes but still operates on the skeleton Yes
Returns probabilities? No Yes
Defines a "linker"? Yes Not directly
Finds cell body fibers? Yes No
Which method is faster? Faster Slower (but depends on parameters)

Total running time of the script: ( 0 minutes 5.376 seconds)

Download Python source code: tutorial_morpho_03_ad_split.py

Download Jupyter notebook: tutorial_morpho_03_ad_split.ipynb

Gallery generated by mkdocs-gallery