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 (plot2d)

  • high quality

  • works in Jupyter and terminal

  • exports to vector graphics

  • myriads of ways to adjust plots

  • struggles with correct layering in complex scenes

  • not very dynamic (although you can adjust perspective)

  • slow for large scenes

  • not good for voxel data (i.e. images)

vispy (plot3d)

  • very interactive (can add/remove/colorize objects on-the-fly)

  • good render quality and performance

  • works only in terminal, not in Jupyter environments

  • can’t share plot (screenshots only)

plotly (plot3d)

  • works “inline” for Jupyter environments

  • persistent (i.e. get saved alongside notebook)

  • can produce “offline” HTML plots for sharing

  • somewhat interactive (can show/hide objects via legend)

  • not super fast for large scenes

  • large file sizes (i.e. makes for large notebooks)

  • horrendous for voxel data (i.e. images)

k3d (plot3d)

  • works “inline” for Jupyter environments

  • super fast and performant

  • in memory (i.e. does not increase notebook file size)

  • highly interactive (show/hide objects, add/adjust visuals)

  • great for voxel data (i.e. images)

  • does not work in terminal sessions

  • not persistent (i.e. dies with notebook kernel)

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()
../../_images/plotting_1_1.png

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()
../../_images/plotting_3_1.png

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()
../../_images/plotting_5_1.png

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)
rotation

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()
../../_images/plotting_10_1.png

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()
../../_images/plotting_13_1.png

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()
../../_images/plotting_15_1.png

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()
../../_images/plotting_17_1.png

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()
../../_images/plotting_19_1.png

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()
../../_images/plotting_21_1.png

Change line style

fig, ax = navis.plot2d(nl, linewidth=1.5, linestyle='--', method='3d_complex')
ax.axim = ax.elev = -90
plt.show()
../../_images/plotting_23_1.png

Change alpha

fig, ax = navis.plot2d(nl, linewidth=1.5, alpha=.25, method='3d_complex')
ax.axim = ax.elev = -90
plt.show()
../../_images/plotting_25_1.png

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()
../../_images/plotting_27_1.png

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()
../../_images/plotting_31_0.png

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')
../../_images/vispy_viewer.png



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')
../../_images/k3d_viewer.png



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)