Note
Click here to download the full example code
Lists of Neurons This tutorial will show you how to use NeuronLists to efficiently work with many neurons at a time.
NAVis will typically collect multiple neurons into a navis.NeuronList
as container. This container behaves like a mix of lists, numpy arrays and pandas dataframes, and allows you to quickly sort, filter and manipulate neurons.
Overview import navis
# Grab three example skeletons (TreeNeurons) as a NeuronList
nl = navis . example_neurons ( n = 3 )
nl
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (875.1KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:44.474568 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R 1734350908 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:44.481975 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc 2 navis.TreeNeuron DA1_lPN_R 722817260 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:44.488600 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
Note
Note how just printing nl
at the end of the cell will produce a nice summary table? If you want to get this table as pandas DataFrame, use the summary()
method:
import matplotlib.pyplot as plt
navis . plot2d ( nl , view = ( 'x' , '-z' ), method = '2d' )
plt . tight_layout ()
Creating NeuronLists To create a NeuronList
from scratch simply pass a list of neurons to the constructor:
n = navis . example_neurons ( n = 1 )
nl = navis . NeuronList ([ n , n , n ]) # a list with 3x the same neuron
nl
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (821.3KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.875 4177 8 nanometer 2024-10-24 11:20:44.758232 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.875 4177 8 nanometer 2024-10-24 11:20:44.758232 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 2 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.875 4177 8 nanometer 2024-10-24 11:20:44.758232 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc
Accessing Neuron Attributes NeuronLists
give you quick and easy access to data and across all neurons:
# Get the number of nodes in the first skeleton
nl = navis . example_neurons ( n = 3 )
nl [ 0 ] . n_nodes
Out:
Use the NeuronList
to collect number of nodes across all neurons:
Out:
array([ 4465 , 4847 , 4332 ])
This works on any neuron attribute:
Out:
array([ 266476 .88, 304332 .66, 274703 .38] , dtype = float32)
Note
The n_{attribute}
pattern works with any "countable" neuron attributes like nodes, connectors, etc.
If the neuron attribute is a dataframe, the NeuronList
will concatenate them and add a new column with the neuron ID:
nl . nodes # note the `neuron` column
label node_id parent_id radius type x y z neuron 0 0 1 -1 10.000000 root 15784.0 37250.0 28062.0 1734350788 1 0 2 1 18.284300 slab 15764.0 37230.0 28082.0 1734350788 2 0 3 2 34.721401 slab 15744.0 37190.0 28122.0 1734350788 3 0 4 3 34.721401 slab 15744.0 37150.0 28202.0 1734350788 4 0 5 4 34.721401 slab 15704.0 37130.0 28242.0 1734350788 ... ... ... ... ... ... ... ... ... ... 13639 6 4328 4242 33.000000 end 5112.0 21598.0 15786.0 722817260 13640 6 4329 1461 33.000000 end 4958.0 23622.0 15170.0 722817260 13641 6 4330 4035 51.225399 end 4716.0 23490.0 15676.0 722817260 13642 6 4331 2603 33.000000 end 4980.0 23226.0 14994.0 722817260 13643 6 4332 1971 33.000000 end 5156.0 23204.0 15148.0 722817260
13644 rows × 9 columns
NeuronLists
can also contain a mix of different neuron types:
nl_mix = navis . example_neurons ( n = 2 , kind = 'mix' )
nl_mix
<class 'navis.core.neuronlist.NeuronList'> containing 2 neurons (875.5KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file n_vertices n_faces 0 navis.TreeNeuron DA1_lPN_R 1734350788 4465.0 2705 599.0 618.0 266476.875 4177 8 nanometer 2024-10-24 11:20:44.814833 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 4465 NaN 1 navis.MeshNeuron DA1_lPN_R 1734350788 NaN 2705 NaN NaN NA NA 8 nanometer NA NA NA 6309 13054.0
Note how nl_mix
contains a TreeNeuron
and a MeshNeuron
?
In such cases you have to be a bit more careful about asking for attributes that are not shared across all neurons:
# This will throw an error because MeshNeurons
# don't have a `cable_length` attribute
nl_mix . cable_length
# Instead use the `get_neuron_attributes()` method with a default value:
nl_mix . get_neuron_attributes ( 'cable_length' , None )
Out:
array([ 266476 .88, None] , dtype = object)
Indexing NeuronLists A NeuronList
works similar to normal lists with a bunch of additional perks:
# Get the first neuron in the list
nl = navis . example_neurons ( n = 3 )
nl [ 0 ]
type navis.TreeNeuron name DA1_lPN_R id 1734350788 n_nodes 4465 n_connectors 2705 n_branches 599 n_leafs 618 cable_length 266476.875 soma 4177 units 8 nanometer created_at 2024-10-24 11:20:44.854445 origin /home/runner/work/navis/navis/navis/data/swc/1... file 1734350788.swc
Index by position NeuronLists
are designed to behave similar to numpy arrays in that they allow some fancing indexing.
You've already seen how to extract a single neuron from a NeuronList
using a single integer index. Like with numpy arrays, this also works for lists of indices...
nl = navis . example_neurons ( n = 3 )
nl [[ 0 , 2 ]]
<class 'navis.core.neuronlist.NeuronList'> containing 2 neurons (571.4KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.875 4177.0 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R 722817260 4332 3136 633 656 274703.375 NaN 8 nanometer 2024-10-24 11:20:44.899015 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
... or slices
<class 'navis.core.neuronlist.NeuronList'> containing 2 neurons (577.6KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.87500 4177 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R 1734350908 4847 3042 735 761 304332.65625 6 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Index by attributes You can index NeuronLists
by boolean numpy.arrays
- that includes neuron attributes, e.g. n_nodes
, cable_length
, soma
, etc.
Index using node count:
subset = nl [ nl . n_branches > 700 ]
subset
<class 'navis.core.neuronlist.NeuronList'> containing 1 neurons (303.8KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R 1734350908 4847 3042 735 761 304332.65625 6 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Here is an example where we subset to neurons that have a soma:
subset = nl [ nl . soma != None ] # Index by boolean array
subset
<class 'navis.core.neuronlist.NeuronList'> containing 2 neurons (577.6KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.87500 4177 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R 1734350908 4847 3042 735 761 304332.65625 6 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Index by name navis.TreeNeuron
can (but don't have to) have names (.name
). If you, for example, import neurons from SWC files they will inherit their name from the file by default.
Our example neurons all have the same name, so to demo this feature we will need to make those names unique:
for i , n in enumerate ( nl ):
n . name = n . name + str ( i + 1 )
nl
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (875.1KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc 2 navis.TreeNeuron DA1_lPN_R3 722817260 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:44.899015 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
You can index by single...
<class 'navis.core.neuronlist.NeuronList'> containing 1 neurons (273.8KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.875 4177 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc
... or multiple names:
nl [[ "DA1_lPN_R1" , "DA1_lPN_R2" ]]
<class 'navis.core.neuronlist.NeuronList'> containing 2 neurons (577.6KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.87500 4177 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Using regex Under the hood NAVis uses re.fullmatch
to match neuron names - so you can use regex!
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (875.1KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc 2 navis.TreeNeuron DA1_lPN_R3 722817260 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:44.899015 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
Index by ID All neurons have an ID - even if you don't explicitly assign one, a UUID will assigned under the hood.
Out:
Neuron lists can be indexed by their ID (similar to .loc[]
in pandas DataFrames) by using the .idx
indexer:
type navis.TreeNeuron name DA1_lPN_R2 id 1734350908 n_nodes 4847 n_connectors 3042 n_branches 735 n_leafs 761 cable_length 304332.65625 soma 6 units 8 nanometer created_at 2024-10-24 11:20:44.892089 origin /home/runner/work/navis/navis/navis/data/swc/1... file 1734350908.swc
Neuron Math NAVis implements a very simple and intuitive syntax to add and remove items from a navis.NeuronList
:
Addition To merge two lists in Python, you can simply add them:
Out:
navis.NeuronList
works exactly the same:
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (875.1KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc 2 navis.TreeNeuron DA1_lPN_R3 722817260 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:44.899015 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
This also works on with two single navis.TreeNeurons
! You can use that to combine them into a list:
<class 'navis.core.neuronlist.NeuronList'> containing 2 neurons (577.6KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.87500 4177 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Substraction To remove an item from a Python list, you would call the .pop()
method:
Out:
For navis.NeuronList
you can use substraction:
<class 'navis.core.neuronlist.NeuronList'> containing 2 neurons (577.6KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.87500 4177 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Bitwise AND To find the intersection between two lists, you would use sets
and the &
operator:
set ([ 0 , 1 , 2 ]) & set ([ 2 , 3 , 4 ])
Out:
navis.NeuronList
work similarly:
<class 'navis.core.neuronlist.NeuronList'> containing 1 neurons (303.8KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Bitwise OR To generate the union between two lists, you would use sets
and the |
operator:
set ([ 0 , 1 , 2 ]) | set ([ 2 , 3 , 4 ])
Out:
navis.NeuronLists
work similarly:
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (875.1KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R1 1734350788 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:44.884704 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R2 1734350908 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:44.892089 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc 2 navis.TreeNeuron DA1_lPN_R3 722817260 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:44.899015 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
Important
Be aware that bitwise AND and OR will likely change the order of the neurons in the list.
Multiplication and Division So far, all operations have led to changes in the structure of the navis.NeuronList
. Multiplication and division are different ! Just like multiplying/dividing individual neurons by a number, multiplying/dividing a navis.NeuronList
will change the coordinates of nodes, vertices, etc. (including associated data such as radii or connector positions) of the neurons in the list:
nl . units # our neurons are originally in 8x8x8 nm voxels
Magnitude [8 8 8] Units nanometer
nl_um = nl * 8 / 1000 # convert neurons: voxels -> nm -> um
nl_um . units
Magnitude [1.0 1.0 1.0] Units micrometer
The above will have changed the coordinates for all neurons in the list.
Comparing NeuronLists navis.NeuronList
implements some of the basic arithmetic and comparison operators that you might know from standard lists
or numpy.arrays
. Most this should be fairly intuitive (I hope) but there are a few things you should be aware of. The following examples will illustrate that.
In Python the ==
operator compares two elements:
Out:
Out:
For navis.TreeNeuron
this is comparison done by looking at the neurons' attribues: morphologies (soma & root nodes, cable length, etc) and meta data (name).
Out:
Out:
To find out which attributes are compared, check out:
navis . TreeNeuron . EQ_ATTRIBUTES
Out:
[ 'n_nodes' , 'n_connectors' , 'soma' , 'root' , 'n_branches' , 'n_leafs' , 'cable_length' , 'name' ]
Edit this list to establish your own criteria for equality.
For NeuronList
, we do the same comparison pairwise between the neurons in both lists:
Out:
Out:
Because the comparison is done pairwise and in order , shuffling a NeuronList
will result in a failed comparison:
Out:
Comparisons are safe against copying but making any changes to the neurons will cause inequality:
Out:
nl [ 0 ] == nl [ 0 ] . downsample ( 2 , inplace = False )
Out:
You can also ask if a neuron is in a given NeuronList
:
Out:
Out:
Operating on NeuronLists With very few exceptions, all NAVis functions that work on individual neurons also work on navis.NeuronList
.
Note
In general, NAVis functions expect multiple neurons to be passed as a NeuronList
- not as a list of neurons:
n1 , n2 = navis . example_neurons ( 2 ) # grab two individual neurons
# This will raise an error
navis . downsample_neuron ([ n1 , n2 ], 2 )
# This will work
navis . downsample_neuron ( navis . NeuronList ([ n1 , n2 ]), 2 )
NeuronList methods Similar to individual neurons, navis.NeuronLists
have a number of methods that allow you to manipulate the neurons in the list. In fact, (almost) all shorthand methods on individual neurons also work on neuron lists:
In addition navis.NeuronLists
have a number of specialised methods:
nl = navis . example_neurons ( 3 ) # load a neuron list
df = nl . summary () # get a summary table with all neurons
df . head ()
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron DA1_lPN_R 1734350788 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:45.112151 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron DA1_lPN_R 1734350908 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:45.119233 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc 2 navis.TreeNeuron DA1_lPN_R 722817260 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:45.125959 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
# Quickly map new attributes onto the neurons
nl . set_neuron_attributes ([ 'Huey' , 'Dewey' , 'Louie' ], name = 'name' )
nl . set_neuron_attributes ([ 'Nephew1' , 'Nephew2' , 'Nephew3' ], name = 'id' )
nl
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (875.1KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron Huey Nephew1 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:45.112151 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 1 navis.TreeNeuron Dewey Nephew2 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:45.119233 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc 2 navis.TreeNeuron Louie Nephew3 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:45.125959 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc
# Sort the neurons by their name
nl . sort_values ( 'name' ) # this is always done inplace
nl
<class 'navis.core.neuronlist.NeuronList'> containing 3 neurons (875.1KiB)
type name id n_nodes n_connectors n_branches n_leafs cable_length soma units created_at origin file 0 navis.TreeNeuron Louie Nephew3 4332 3136 633 656 274703.37500 NaN 8 nanometer 2024-10-24 11:20:45.125959 /home/runner/work/navis/navis/navis/data/swc/7... 722817260.swc 1 navis.TreeNeuron Huey Nephew1 4465 2705 599 618 266476.87500 4177.0 8 nanometer 2024-10-24 11:20:45.112151 /home/runner/work/navis/navis/navis/data/swc/1... 1734350788.swc 2 navis.TreeNeuron Dewey Nephew2 4847 3042 735 761 304332.65625 6.0 8 nanometer 2024-10-24 11:20:45.119233 /home/runner/work/navis/navis/navis/data/swc/1... 1734350908.swc
Of course there are also a number of NeuronList
-specific properties:
is_mixed
: returns True
if list contains more than one neuron type is_degenerated
: returns True
if list contains neurons with non-unique IDs types
: tuple with all types of neurons in the list shape
: size of neuronlist (N, )
All attributes and methods are accessible through auto-completion.
What next? Total running time of the script: ( 0 minutes 0.696 seconds)
Download Python source code: tutorial_basic_02_neuronlists.py
Download Jupyter notebook: tutorial_basic_02_neuronlists.ipynb
Gallery generated by mkdocs-gallery