Neuron types¶
Depending your data/workflows you will use different ways to represent neurons. If, for example, you work with light-level data you might end up with point clouds or skeletons whereas modern connectomes typically provide meshes.
To cater for these different data types neurons in navis
come in four flavours:
Neuron type |
Description |
Core data |
---|---|---|
A hierarchical skeleton consisting of nodes and edges. |
|
|
A mesh with faces and vertices. |
|
|
An image represented by either a 2d array of voxels or a 3d voxel grid. |
|
|
A cloud of points, each with an associated local vector. |
|
Note that some functions in navis
will work on some but not all neuron types (see this table in the API reference for details). If need be, navis
also offers ways to convert between the different neuron types (see further below). For details on how to load/import your neurons into navis
please see the other tutorials.
TreeNeurons¶
TreeNeurons
represent a neuron as a tree-like “skeleton” - effectively a directed acyclic graph, i.e. they consist of nodes and each node connects to at most 1 parent. This format is commonly used to describe a neuron’s topology and often shared using SWC files.
A TreeNeuron
is typically constructed from an SWC file (see navis.read_swc()
) but you can also use a pandas.DataFrame
or a networkx.DiGraph
. See the skeleton I/O tutorial for details.
navis
ships with a couple example Drosophila neurons from the Janelia hemibrain project published
in Scheffer et al. (2020) and available at https://neuprint.janelia.org (see also the neuPrint tutorial):
# Import navis
import navis
# Load one of the example neurons
sk = navis.example_neurons(n=1, kind='skeleton')
# Inspect the neuron
sk
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 | 2022-05-09 11:52:08.206993 |
id | 1734350788 |
origin | /Users/philipps/Google Drive/Cloudbox/Github/n... |
TreeNeuron
stores nodes and other data as attached DataFrames:
sk.nodes.head()
node_id | label | x | y | z | radius | parent_id | type | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 15784.0 | 37250.0 | 28062.0 | 10.000000 | -1 | root |
1 | 2 | 0 | 15764.0 | 37230.0 | 28082.0 | 18.284300 | 1 | slab |
2 | 3 | 0 | 15744.0 | 37190.0 | 28122.0 | 34.721401 | 2 | slab |
3 | 4 | 0 | 15744.0 | 37150.0 | 28202.0 | 34.721401 | 3 | slab |
4 | 5 | 0 | 15704.0 | 37130.0 | 28242.0 | 34.721401 | 4 | slab |
MeshNeurons¶
MeshNeurons
consist of vertices and faces, and are a typical output of e.g. image segmentation.
MeshNeuron
can be constructed from any object that has .vertices
and .faces
properties, a dictionary of vertices
and faces
or a file that can be parsed by trimesh.load
. See the mesh I/O tutorial for details.
Each of the example neurons in navis
also comes as mesh representation:
m = navis.example_neurons(n=1, kind='mesh')
m
type | navis.MeshNeuron |
---|---|
name | DA1_lPN_R |
id | 1734350788 |
units | 8 nanometer |
n_vertices | 6309 |
n_faces | 13054 |
MeshNeuron
stores vertices and faces as attached numpy arrays:
m.vertices, m.faces
(TrackedArray([[16384. , 34792.03125 , 24951.88085938],
[16384. , 36872.0625 , 25847.89453125],
[16384. , 36872.0625 , 25863.89453125],
...,
[ 5328.08105469, 21400.07617188, 16039.99414062],
[ 6872.10498047, 19560.04882812, 13903.96191406],
[ 6872.10498047, 19488.046875 , 13927.96191406]]),
TrackedArray([[3888, 3890, 3887],
[3890, 1508, 3887],
[1106, 1104, 1105],
...,
[5394, 5426, 5548],
[5852, 5926, 6017],
[ 207, 217, 211]]))
Dotprops¶
Dotprops
represent neurons as point clouds where each point is associated with a vector describing the local orientation. This simple representation often comes from e.g. light-level data or as direvative of skeletons/meshes (see navis.make_dotprops()
). Dotprops are used e.g. for NBLAST. See the dotprops I/O tutorial for details.
Dotprops
consist of .points
and associated .vect
(vectors). They are typically created from other types of neurons using navis.make_dotprops()
:
# Turn our above skeleton into dotprops
dp = navis.make_dotprops(sk, k=5)
dp
type | navis.Dotprops |
---|---|
name | DA1_lPN_R |
id | 1734350788 |
k | 5 |
units | 8 nanometer |
n_points | 4465 |
dp.points, dp.vect
(array([[15784., 37250., 28062.],
[15764., 37230., 28082.],
[15744., 37190., 28122.],
...,
[14544., 36430., 28422.],
[14944., 36510., 28282.],
[15264., 36870., 28282.]], dtype=float32),
array([[-0.3002053 , -0.39364937, 0.8688596 ],
[-0.10845336, -0.2113751 , 0.9713694 ],
[-0.0435693 , -0.45593134, 0.8889479 ],
...,
[-0.38446087, 0.44485292, -0.80888546],
[-0.9457323 , -0.1827982 , -0.26865458],
[-0.79947734, -0.5164282 , -0.30681902]], dtype=float32))
Check out the NBLAST tutorial for further details on dotprops!
VoxelNeurons¶
VoxelNeurons
represent neurons as either 3d image or x/y/z voxel coordinates typically obtained from e.g. light-level microscopy.
VoxelNeuron
consist of either a 3d (N, M, K)
array (=”grid”) or an 2d (N, 3)
array of voxel coordinates. You will probably find yourself loading these data from image files (e.g. .nrrd
via navis.read_nrrd()
). That said we can also “voxelize” other neuron types to produce VoxelNeurons
:
# Load an example mesh
m = navis.example_neurons(n=1, kind='mesh')
# Voxelize:
# - with a 0.5 micron voxel size
# - some Gaussian smoothing
# - use number of vertices (counts) for voxel values
vx = navis.voxelize(m, pitch='0.5 microns', smooth=2, counts=True)
vx
type | navis.VoxelNeuron |
---|---|
name | DA1_lPN_R |
id | 1734350788 |
units | 500.0 nanometer |
shape | (298, 392, 286) |
dtype | float32 |
# The grid representation of the neuron
vx.grid.shape
(297, 392, 286)
# The (N, 3) voxel + (N, ) values representation of the neuron
# -> this is the sparse representation of the neuron
vx.voxels.shape, vx.values.shape
((641384, 3), (641384,))
Note
You may have noticed that all neurons share some properties irrespective of their type,
for example .id
, .name
or .units
. These properties are optional and can
be set when you first create the neuron, or at a later point.
In particular the .id
property is important because many functions in navis
will return results that are indexed by the neurons’ IDs. If .id
is not set
explicitly, it will default to some rather cryptic random UUID - you have been warned!
Connectors¶
navis
was designed with connectivity data in mind! Therefore, each neuron - regardless of type - can have a .connectors
table. Connectors are meant to bundle all kinds of connections: pre- & postsynapses, electrical synapses, gap junctions and so on.
A connector table must minimally contain an x/y/z coordinate and a type for each connector:
n = navis.example_neurons(1)
n.connectors.head()
connector_id | node_id | type | x | y | z | roi | confidence | |
---|---|---|---|---|---|---|---|---|
0 | 0 | 1436 | pre | 6444 | 21608 | 14516 | LH(R) | 0.959 |
1 | 1 | 1436 | pre | 6457 | 21634 | 14474 | LH(R) | 0.997 |
2 | 2 | 2638 | pre | 4728 | 23538 | 14179 | LH(R) | 0.886 |
3 | 3 | 1441 | pre | 5296 | 22059 | 16048 | LH(R) | 0.967 |
4 | 4 | 1872 | pre | 4838 | 23114 | 15146 | LH(R) | 0.990 |
Connector tables aren’t just passive meta data: certain functions in navis
make use of or even require them. The most obvious example is probably for plotting.
# Plot neuron including its connectors
# Note we're increasing connector size and decreasing line width
# to illustrate emphasize connectors
fig, ax = navis.plot2d(n, connectors=True, color='k', cn_size=10, lw=.25)
ax.dist = 5

In above plot, red are presynapses (outputs) and cyan are postsynapses (inputs).
Somas¶
Unless a neuron is truncated, it should have a soma somewhere. Knowing where the soma is can be very useful, e.g. as point of reference for distance calculations or for plotting. Therefore, navis
neurons have a .soma
property:
n = navis.example_neurons(1)
n.soma
array([4177], dtype=int32)
In case of this exemplary TreeNeuron
, the .soma
points to an ID in the node table. We can also get the position:
n.soma_pos
array([[14957.1, 36540.7, 28432.4]], dtype=float32)
Other neuron types also support soma annotations but they may look slightly different. For a MeshNeuron
, annotating a node position makes little sense. Instead, we track the x/y/z position directly:
m = navis.example_neurons(1, kind='mesh')
m.soma_pos
[14957.1, 36540.7, 28432.4]
For the record: .soma
/ .soma_pos
can be set manually like any other property (there are some checks and balances to avoid issues) and can also be None
:
# Set the skeleton's soma on node with ID 1
n.soma = 1
n.soma
1
# Drop soma from MeshNeuron
m.soma_pos = None
Units¶
navis
supports assigning units to neurons. The neurons shipping with navis, for example, are in 8x8x8nm voxel space:
m = navis.example_neurons(1, kind='mesh')
m.units
To assign or change the units simply use a descriptive string:
m.units = '10 micrometers'
m.units
Tracking units is good practive but can also be very useful: some navis
functions let you pass quantities as unit strings:
# Load example neuron in 8x8x8nm
n = navis.example_neurons(1, kind='skeleton')
# Resample to 1 micrometer
rs = navis.resample_skeleton(n, resample_to='1 um')
Lists of Neurons¶
Multiple neurons are typically collected in a NeuronList
as container:
# Grab three example TreeNeurons
nl = navis.example_neurons(n=3)
nl
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
2 | navis.TreeNeuron | DA1_lPN_R | 722817260 | 4332 | 3136 | 633 | 656 | 274703.37500 | None | 8 nanometer |
A NeuronList
works similar to normal lists with a bunch of additional perks:
# Get the first neuron in the list
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 |
They also allow easy and fast access to data and across all neurons:
# Get the number of nodes in the first skeleton
nl[0].n_nodes
4465
# Use the neuronlist to collect number of nodes across all neurons
nl.n_nodes
array([4465, 4847, 4332])
# Works on any neuron attribute
nl.cable_length
array([266476.88, 304332.66, 274703.38], dtype=float32)
In addition to these attributes, both TreeNeuron
and NeuronList
have shortcuts (called methods) to other navis
functions. These lines of code are equivalent:
sk.reroot(sk.soma, inplace=True)
navis.reroot_skeleton(sk, sk.soma, inplace=True)
sk.plot3d(color='red')
navis.plot3d(sk, color='red')
lh = navis.example_volume('LH')
sk.prune_by_volume(lh, inplace=True)
navis.in_volume(sk, lh, inplace=True)
Note
- The
inplace
parameter is part of manynavis
functions and works like e.g. in the pandas library: if
inplace=True
operations are performed on the originalif
inplace=False
(default) operations are performed on a copy of the original which is then returned
# Load a neuron
n = navis.example_neurons(1)
# Load an example neuropil
lh = navis.example_volume('LH')
# Prune neuron to neurpil but leave original intact
n_lh = n.prune_by_volume(lh, inplace=False)
print(f'{n.n_nodes} nodes before and {n_lh.n_nodes} nodes after pruning')
4465 nodes before and 344 nodes after pruning
Index by position¶
NeuronList
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 for numpy arrays, this also works for list of indices…
nl = navis.example_neurons(n=3)
nl[[0, 2]]
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.875 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R | 722817260 | 4332 | 3136 | 633 | 656 | 274703.375 | None | 8 nanometer |
… or slices
nl[:2]
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
Index by attributes¶
You can index by NeuronList
by boolean numpy.arrays
- that includes TreeNeuron
attributes, e.g. n_nodes
, cable_length
, soma
, etc.
Index using node count:
subset = nl[nl.n_branches > 700]
subset
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
Here is an example where we subset to neurons that have a soma:
nl.soma
array([array([4177], dtype=int32), array([6], dtype=int32), None],
dtype=object)
nl.soma != None
array([ True, True, False])
subset = nl[nl.soma != None]
subset
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
Index by name¶
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
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R1 | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R2 | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
2 | navis.TreeNeuron | DA1_lPN_R3 | 722817260 | 4332 | 3136 | 633 | 656 | 274703.37500 | None | 8 nanometer |
You can index by single…
nl['DA1_lPN_R1']
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R1 | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.875 | [4177] | 8 nanometer |
… or multiple names:
nl[['DA1_lPN_R1', 'DA1_lPN_R2']]
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R1 | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R2 | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
Using regex¶
Under the hood navis
uses re.fullmatch
to match neuron names - so you can use regex!
nl['.*DA1.*']
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R1 | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R2 | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
2 | navis.TreeNeuron | DA1_lPN_R3 | 722817260 | 4332 | 3136 | 633 | 656 | 274703.37500 | None | 8 nanometer |
Index by ID¶
All neurons have an ID - even if you don’t explicitly assign one, a UUID will assigned under the hood.
nl[0].id
1734350788
Neuron lists can be indexed by their ID (similar to .loc[]
in pandas DataFrames) by using the .idx
indexer:
nl.idx[1734350908]
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 |
Neuron Math¶
navis
implements a very simple and intuitive syntax to add and remove items from a NeuronList:
Addition¶
To merge two lists in Python, you can simply add them:
[1] + [3]
[1, 3]
NeuronList
works exactly the same:
nl[:2] + nl[2:]
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R1 | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R2 | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
2 | navis.TreeNeuron | DA1_lPN_R3 | 722817260 | 4332 | 3136 | 633 | 656 | 274703.37500 | None | 8 nanometer |
This also works on with two single TreeNeurons
! You can use that to combine them into a list:
nl[0] + nl[1]
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R1 | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R2 | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
Substraction¶
To remove an item from a Python list, you would call the .pop()
method:
l = [1, 2, 3]
l.pop(2)
l
[1, 2]
For NeuronList
you can use substraction:
nl - nl[2]
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R1 | 1734350788 | 4465 | 2705 | 599 | 618 | 266476.87500 | [4177] | 8 nanometer |
1 | navis.TreeNeuron | DA1_lPN_R2 | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
Bitwise AND¶
To find the intersection between two lists, you would use sets
and the &
operator:
set([0, 1, 2]) & set([2, 3, 4])
{2}
NeuronList
work similarly:
nl[[0, 1]] & nl[[1, 2]]
type | name | id | n_nodes | n_connectors | n_branches | n_leafs | cable_length | soma | units | |
---|---|---|---|---|---|---|---|---|---|---|
0 | navis.TreeNeuron | DA1_lPN_R2 | 1734350908 | 4847 | 3042 | 735 | 761 | 304332.65625 | [6] | 8 nanometer |
Multiplication and Division¶
So far, all operations have led to changes in the structure of the NeuronList
. Multiplication and division are different! If you multiply/divide a TreeNeuron
or NeuronList
by a number, you will change the coordinates of nodes and connectors (including radii):
n = nl[0]
n.nodes.head()
node_id | label | x | y | z | radius | parent_id | type | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 15784.0 | 37250.0 | 28062.0 | 10.000000 | -1 | root |
1 | 2 | 0 | 15764.0 | 37230.0 | 28082.0 | 18.284300 | 1 | slab |
2 | 3 | 0 | 15744.0 | 37190.0 | 28122.0 | 34.721401 | 2 | slab |
3 | 4 | 0 | 15744.0 | 37150.0 | 28202.0 | 34.721401 | 3 | slab |
4 | 5 | 0 | 15704.0 | 37130.0 | 28242.0 | 34.721401 | 4 | slab |
n2 = n / 1000
n2.nodes.head()
node_id | label | x | y | z | radius | parent_id | type | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 15.784 | 37.250000 | 28.062000 | 0.010000 | -1 | root |
1 | 2 | 0 | 15.764 | 37.230000 | 28.082001 | 0.018284 | 1 | slab |
2 | 3 | 0 | 15.744 | 37.189999 | 28.122000 | 0.034721 | 2 | slab |
3 | 4 | 0 | 15.744 | 37.150002 | 28.202000 | 0.034721 | 3 | slab |
4 | 5 | 0 | 15.704 | 37.130001 | 28.242001 | 0.034721 | 4 | slab |
Note that this also automatically adjusts the neuron’s units (if it has any):
print('Before:', n.units)
print('After:', n2.units)
Before: 8 nanometer
After: 8.0 micrometer
Comparing Neuron/Lists¶
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.
Comparisons¶
In Python the ==
operator compares two elements:
1 == 1
True
2 == 1
False
For TreeNeuron
this is comparison done by looking at the neurons’ attribues: morphologies (soma & root nodes, cable length, etc) and meta data (name).
nl[0] == nl[0]
True
nl[0] == nl[1]
False
To find out which attributes are compared, check out:
navis.TreeNeuron.EQ_ATTRIBUTES
['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 neuronlists:
nl == nl
True
nl == nl[:2]
False
Because the comparison is done pairwise and in order, shuffling a NeuronList
will result in a failed comparison:
nl == nl[[2, 1, 0]]
False
Comparisons are safe against copying but making any changes to the neurons will cause inequality:
nl[0] == nl[0].copy()
True
nl[0] == nl[0].downsample(2, inplace=False)
False
You can also ask if a neuron is in a given NeuronList
:
nl[0] in nl
True
nl[0] in nl[1:]
False
Making custom changes¶
Under the hood navis
calculates certain properties when you load a neuron: e.g. it produces a graph representation (.graph
or .igraph
) and a list of linear segments (.segments
) for TreeNeurons
. These data are attached to a neuron and are crucial for many functions. Therefore navis
makes sure that any changes to a neuron automatically propagate into these derived properties. See this example:
n = navis.example_neurons(1, kind='skeleton')
print(f'Nodes in node table: {n.nodes.shape[0]}')
print(f'Nodes in graph: {len(n.graph.nodes)}')
Nodes in node table: 4465
Nodes in graph: 4465
Making changes will cause the graph representation to be regenerated:
n.prune_by_strahler(1, inplace=True)
print(f'Nodes in node table: {n.nodes.shape[0]}')
print(f'Nodes in graph: {len(n.graph.nodes)}')
Nodes in node table: 1770
Nodes in graph: 1770
If, however, you make changes to the neurons that do not use built-in functions there is a chance that navis
won’t know that things have changed and properties need to be regenerated!
n = navis.example_neurons(1)
print(f'Nodes in node table before: {n.nodes.shape[0]}')
print(f'Nodes in graph before: {len(n.graph.nodes)}')
# Truncate the node table by 55 nodes
n.nodes = n.nodes.iloc[:-55]
print(f'\nNodes in node table after: {n.nodes.shape[0]}')
print(f'Nodes in graph after: {len(n.graph.nodes)}')
Nodes in node table before: 4465
Nodes in graph before: 4465
Nodes in node table after: 4410
Nodes in graph after: 4410
Here, the changes to the node table automatically triggered a regeneration of the graph. This works because navis
generates and checks hash values for neurons to detect changes and because here the node table is the master. It would not work the other way around (i.e. changing the graph to change the node table).
Again: as long as you are using built-in functions, you don’t have to worry about this. If you do run some custom manipulation of neurons be aware that you might want to make sure that the data structure remains intact. If you ever need to manually trigger a regeneration you can do so like this:
# Clear temporary attributes of the neuron
n._clear_temp_attr()
Converting neuron types¶
navis
provides a couple functions to move between neuron types:
|
Produce dotprops from neurons or x/y/z points. |
|
Turn neuron into skeleton. |
|
Generate mesh from object(s). |
|
Turn neuron into voxels. |
In particular meshing and skeletonizing are non-trivial and you might have to play around with the parameters to optimize results with your data! Let’s demonstrate on some example:
# Start with a mesh neuron
m = navis.example_neurons(1, kind='mesh')
# Skeletonize the mesh
s = navis.skeletonize(m)
# Make dotprops (this works from any other neuron type
dp = navis.make_dotprops(s, k=5)
# Voxelize the mesh
vx = navis.voxelize(m, pitch='2 microns', smooth=1, counts=True)
# Mesh the voxels
mm = navis.mesh(vx.threshold(.5))
Inspect the results:
# Co-visualize the mesh and the skeleton
fig = navis.plot3d([m, s], color=[(1, 1, 1, .2), 'r'])