Overview¶
NAVis contains functions for 2D and 3D plotting. These functions use matplotlib for 2D, and vispy, plotly or k3d for 3D.
Which plotting method (2D/3D) and which backend (plotly, vispy, etc.) to use depends on what you are after (e.g. publication quality figures vs interactive inspection) and your environment (Jupyter or terminal). Here’s a quick summary:
backend |
Pros |
Cons |
---|---|---|
matplotlib ( |
|
|
vispy ( |
|
|
plotly ( |
|
|
k3d ( |
|
|
In theory there is feature parity across backends but due to some built-in limitations there are some differences. Also note that the k3d backend is a recent addition and the underlying library has to be installed separately (pip3 install k3d -U
).
2D plots¶
This uses matplotlib to generate 2D plots. The big advantage is that you can save these plots as vector graphics. Unfortunately, matplotlib’s capabilities regarding 3D data are limited. The main problem is that depth (z) is only simulated by trying to layer objects according to their z-order rather than doing proper rendering. You have several options to deal with this: see the method
parameter in navis.plot2d()
. It is important to be aware of this issue as e.g. neuron A might be plotted in front of neuron B even though it is actually spatially behind it. The more busy your plot and the more neurons intertwine, the more likely this is to happen.
Let’s start with a simple example using default settings:
import navis
import matplotlib.pyplot as plt
nl = navis.example_neurons(kind='skeleton')
# Plot using default settings
fig, ax = nl.plot2d() # equivalent to `navis.plot2d(nl)`
plt.show()
Above plot used the default matplotlib 2D plot. You might notice that the plot looks rather “flat” - i.e. neurons seem to be layered on top of each other without intertwining. That is one of the limitations of matplotlib’s 3d backend. We can try to ameliorate this by adjust the method
parameter:
# Plot settings for more complex scenes - comes at a performance cut though
fig, ax = nl.plot2d(method='3d_complex')
plt.show()
Looks better now, doesn’t it? Now what if we wanted to adjust the perspective?
# Plot again
fig, ax = nl.plot2d(method='3d_complex')
# Adjust `azim` (azimuth) and `elev` (elevation) to change view
ax.azim, ax.elev = -90, -90
# Zoom in a bit
ax.dist = 6
plt.show()
Note
If the plot is rendered in a separate window (e.g. if you run Python from terminal), you can change the perspective by dragging the image.
We can use this to generate small animations:
# Render 3D rotation
for i in range(0, 360, 10):
# Change rotation
ax.azim = i
# Save each incremental rotation as frame
plt.savefig('frame_{0}.png'.format(i), dpi=200)
Plotting meshes¶
We can also plot meshes (MeshNeurons
, trimesh
, or Volumes
) using matplotlib. To illustrate, we will use one of the neuropil meshes that ship with NAVis:
# Load the Lateral Horn ("LH") volume
lh = navis.example_volume('LH')
# Adjust color and alpha if you like
lh.color = (0, 1, 0, .1)
# Plot
fig, ax = navis.plot2d([nl ,lh], method='3d_complex')
ax.elev = ax.azim = -90
ax.dist = 6
plt.show()
Note how this time we used navis.plot2d()
instead of the NeuronList
short-hand method n.plot3d
to plot multiple objects at a time.
Fine-tuning plots¶
These 2D plots (and also the 3D plots further down) can be adjusted and fine-tuned to your liking.
Changing colors¶
NAVis plot3d()
and plot2d()
accept various ways to specify colors for neurons. We’ll demonstrate this using some examples.
Same color for all neurons:
fig, ax = navis.plot2d(nl, color=(1, 0, 0))
ax.axim = ax.elev = -90
plt.show()
Passing a list of colors (one for each neuron in sequence):
fig, ax = navis.plot2d(nl, color=['r', 'g', 'b', 'm', 'c'], method='3d_complex')
ax.axim = ax.elev = -90
plt.show()
You can map colors to neurons by either using the neuron itself, its .id
or its .name
attribute.
cmap = {nl[0]: 'r',
nl[1].id: (0, 1, 0),
nl[2].name: 'g',
nl[3]: 'm',
nl[4]: 'k'}
fig, ax = navis.plot2d(nl, color=cmap, method='3d_complex')
ax.axim = ax.elev = -90
plt.show()
navis.Volume
colors work similarly with the difference that the fallback color is defined by the .color
attribute of the volume:
lh = navis.example_volume('LH')
# Set color implicitly
lh.color = (.8, .8, .8, .2)
fig, ax = navis.plot2d([nl, lh])
ax.axim = ax.elev = -90
plt.show()
Adjusting lines¶
plot2d()
accepts a range of parameters to fine-tune plots. See these examples:
Change linewidth
fig, ax = navis.plot2d(nl, linewidth=2, method='3d_complex')
ax.axim = ax.elev = -90
plt.show()
Change line style
fig, ax = navis.plot2d(nl, linewidth=1.5, linestyle='--', method='3d_complex')
ax.axim = ax.elev = -90
plt.show()
Change alpha
fig, ax = navis.plot2d(nl, linewidth=1.5, alpha=.25, method='3d_complex')
ax.axim = ax.elev = -90
plt.show()
Depth-coloring¶
To facilitate perception of depth in 2D plots, plot2d()
can color neurons based on their z-depth. This is only available for method="2d"
and method="3d"
, not method="3d_complex"
.
With method="2d"
:
fig, ax = navis.plot2d(nl, method='2d',
linewidth=1.5,
depth_coloring=True, view=('x', '-y'))
plt.show()
Shading¶
In the above example we colored individual nodes based on their z-depth. We can use the same mechanics to apply our own color scheme. For example, let’s color branches based on their Strahler order.
# Fist calculate Strahler indices
n = nl[0].reroot(nl[0].soma)
navis.strahler_index(n)
# Note the new column in the node table
n.nodes.head()
node_id | label | x | y | z | radius | parent_id | type | strahler_index | |
---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 15784.0 | 37250.0 | 28062.0 | 10.000000 | 2 | end | 1 |
1 | 2 | 0 | 15764.0 | 37230.0 | 28082.0 | 18.284300 | 3 | slab | 1 |
2 | 3 | 0 | 15744.0 | 37190.0 | 28122.0 | 34.721401 | 4 | slab | 1 |
3 | 4 | 0 | 15744.0 | 37150.0 | 28202.0 | 34.721401 | 5 | slab | 1 |
4 | 5 | 0 | 15704.0 | 37130.0 | 28242.0 | 34.721401 | 6 | slab | 1 |
Both plot2d
and plot3d
accept color_by
and shade_by
parameters. These can either be arrays (same length as node table) or point to a column in the node table. They can deal with numeric and categorical variables which will be translated into colors using a given palette
and alpha values, respectively.
fig, ax = navis.plot2d(n, method='3d',
color_by='strahler_index', # color based on Strahler index column
shade_by='strahler_index', # shade (alpha) based on Strahler index column
palette='cool', # accepts any matplotlib palette
linewidth=1.5)
ax.elev = ax.azim = -90
ax.dist = 6
plt.show()
3D plots¶
As laid out at the top of this page: for 3D plots, we are using either vispy, plotly or k3d. In brief:
backend |
Jupyter |
terminal |
---|---|---|
plotly |
yes |
yes but only export to html |
vispy |
no |
yes |
k3d |
yes |
no |
By default, NAVis will detect whether you are working in a Jupyter notebook or not, and choose a backend automatically: vispy for terminal, plotly for Jupyter. Because only plotly allows us to embed interactive plots into these docs, we will focus mainly on this backend but we will briefly demo the others too:
Using Vispy¶
Our first two example use Vispy (for demonstration only and will NOT work in Jupyter):
# Plot using Vispy (will open 3D viewer)
viewer = nl.plot3d(backend='vispy')
The navis.Viewer
(viewer
here) is persistent and survives simply closing the window. Calling plot3d()
again will add objects to the canvas and open it again.
# Clear existing viewer
navis.close3d()
# Add neurons to viewer
navis.plot3d(nl, backend='vispy')
# Add volume
navis.plot3d(lh, backend='vispy')
# Clear viewer again...
navis.clear3d()
# ... or close altogether
navis.close3d()
Alternatively, you can also work on the viewer directly:
# Clear viewer
viewer.clear()
# Add an object (e.g. a NeuronList)
viewer.add(nl)
If working with multiple viewers, you can specify which navis.Viewer
to add the neurons to.
# Open 2 viewers
v1 = navis.Viewer()
v2 = navis.Viewer()
# Add neurons to each one separately
v1.add(nl[0])
v2.add(nl[1])
# Clear first viewer
v1.clear()
# Close the second viewer
v2.close()
If you’ve lost track of your viewer, simply use navis.get_viewer()
to get it back:
v = navis.get_viewer()
Using k3d¶
k3d plots work in Jupyter (and only there) but unlike plotly don’t persist across sessions. Hence we will only briefly demo them using static screenshots and then move on to plotly. Almost everything you can do with the plotly backend can also be done with k3d (or vispy for that matter)!
p = navis.plot3d(nl, backend='k3d')
Using plotly¶
Now let’s have a look at Plotly as backend:
# Using plotly as backend generates "inline" plots by default (i.e. they are rendered right away)
fig = nl.plot3d(backend='plotly', connectors=False)
Instead of inline plotting, you can also export your plotly plot as 3D plot that can be opened in any browser:
import plotly
# Prevent inline plotting
fig = nl.plot3d(backend='plotly', connectors=False, width=1400, height=1000, inline=False)
# Save figure to html file
plotly.offline.plot(fig, filename='~/Documents/3d_plot.html')
So far, we’ve been plotting skeletons (i.e. TreeNeuron
). MeshNeuron
work pretty much the same:
ml = navis.example_neurons(kind='mesh')
fig = ml.plot3d(backend='plotly', connectors=False)
Adding volumes¶
plot3d()
allows plotting of volumes (e.g. neuropil meshes). There is a custom class for Volumes, navis.Volume
which has some neat methods, including loading from different file formats - check out the documentation.
# Load volumes
vols = [navis.example_volume('LH'),
navis.example_volume('neuropil')]
fig = navis.plot3d([nl, *vols], backend='plotly')
Fine-tuning plots¶
Just like the 2D plots, there are plenty ways to adjust these 3D plots. In fact, pretty much everything (except some of the line styling) show-cased earlier works just the same way for 3D plots:
Changing colors:
n = navis.example_neurons(1, kind='mesh')
fig = navis.plot3d(n, color='r', backend='plotly')
Coloring by e.g. Strahler index:
# This adds an `.strahler_index` array with the values to this MeshNeuron
navis.strahler_index(n)
fig = navis.plot3d(n, color_by='strahler_index', palette='viridis', backend='plotly')
1D plots¶
This visualization technique is based on Cuntz et al (2010) that turns a neuron’s branching pattern into a “barcode”. Only works with TreeNeuron
.
fig, ax = plt.subplots(figsize=(18, 3))
ax = navis.plot1d(nl[:3], ax=ax)
plt.show()
Dendrograms & more¶
Skeletons in navis
are hierarchical trees (hence the name TreeNeuron
). As such they can be visualized as dendrograms and flat graph-like plots using navis.plot_flat()
. This is useful to unravel otherwise complicated, overlapping branching patterns - e.g. when you want to show compartments or synapse positions within the neuron. Let’s demo some layouts!
First up: the subway
layout. This one is nice for side-by-side comparisons of neurons.
# Convert example neurons from voxels to nanometers
nl_nm = nl.convert_units('nm')
# Reroot to soma
nl_nm[nl_nm.soma != None].reroot(nl_nm[nl_nm.soma != None].soma, inplace=True)
# Generate one axis for each neuron
fig, axes = plt.subplots(3, 1, figsize=(15, 10), sharex=True)
navis.plot_flat(nl_nm[0], layout='subway', plot_connectors=True, ax=axes[0])
navis.plot_flat(nl_nm[1], layout='subway', plot_connectors=True, ax=axes[1])
navis.plot_flat(nl_nm[3], layout='subway', plot_connectors=True, ax=axes[2])
# Clean up the axes
for ax in axes[:-1]:
ax.set_axis_off()
for sp in ['left', 'right', 'top']:
axes[-1].spines[sp].set_visible(False)
axes[-1].set_yticks([])
_ = axes[-1].set_xlabel('distance [nm]')
plt.show()
For the other layouts you will need to have pygraphviz and the underlying graphviz
library installed. For details on the layout, check out graphviz’ docs.
dot
and twopi
are dendrogram layouts. They (should) preserve branch lengths similar to subway
.
ax = navis.plot_flat(nl_nm[0], layout='dot', plot_connectors=True, color='lightgrey')
plt.show()
INFO : Calculating node positions. (navis)
INFO : Plotting tree. (navis)
n = nl_nm[0]
ax = navis.plot_flat(n, layout='twopi', plot_connectors=True, color='lightgrey')
plt.show()
INFO : Calculating node positions. (navis)
INFO : Plotting tree. (navis)
You can also highlight specific connectors by their ID (here we just use the first 100):
highlight = n.connectors.connector_id[:100]
ax = navis.plot_flat(nl_nm[0], layout='dot',
highlight_connectors=highlight,
color='lightgrey',
syn_marker_size=2)
plt.show()
INFO : Calculating node positions. (navis)
INFO : Plotting tree. (navis)
neato
, fdp
and sfdp
are force-directed layouts.
# Some layouts (like "neato" & "fdp") can be quite expensive to calculate in which case it's worth
# downsampling your neuron before plotting
ds = navis.downsample_neuron(nl_nm[0], 10, preserve_nodes='connectors')
ax = navis.plot_flat(ds, layout='neato', plot_connectors=True, color='lightgrey')
plt.show()
INFO : Calculating node positions. (navis)
INFO : Plotting tree. (navis)
ax = navis.plot_flat(nl[0], layout='sfdp', plot_connectors=True, color='lightgrey')
plt.show()
INFO : Calculating node positions. (navis)
INFO : Plotting tree. (navis)
Videos¶
Above we demo’ed making a little GIF using matplotlib. While that’s certainly fun, it’s not very high production value. For high quality videos (and renderings) I recommend you check out the tutorial on navis’ Blender interface. Here’s a little taster:
XKCD¶
If you don’t already know: matplotlib
has a xkcd mode. This also works surprisingly well with neurons:
# Get the neuropil volume
m = navis.example_volume('neuropil')
# Make it fairly transparent
m.color = (0, 0, 0, .02)
# Plot in xkcd style
with plt.xkcd(scale=5, randomness=10, length=200):
fig, ax = navis.plot2d([nl, m], method='2d', c='k', view=('x', '-z'), lw=1.2, volume_outlines='both')
plt.show()
References¶
An overview of the most important plotting functions used above:
|
Generate 3D plot. |
|
Generate 2D plots of neurons and neuropils. |
|
Plot neuron topology in 1D according to Cuntz et al. (2010). |
|
Plot neuron as flat diagrams. |
|
Clear viewer 3D canvas. |
|
Close existing vispy 3D canvas (wipes memory). |
Grab active 3D viewer. |
|
|
Save a screenshot of active vispy 3D canvas. |
|
Mesh consisting of vertices and faces. |
|
Vispy 3D viewer. |