Skip to content

Note

Click here to download the full example code

Volumes#

Give a figure context without letting the neuropil steal it.

Volumes - neuropils, brain outlines, ROIs - are meshes, so most of what the mesh tutorial says applies here too. What makes them their own topic is their job: a neuropil is scenery. It has to say "here is where we are" and then get out of the way of the neuron you actually want people to look at.

NAVis treats them accordingly. A volume always sits at the very bottom of the stack, never joins the depth bins and never takes a halo - so you can hand plot2d a mixed scene and not think about ordering.

Parameter Effect
color / alpha As for any object; volumes carry a default colour of their own
volume_outlines Draw a contour instead of a surface (True), or both ("both")
volume_outlines_alpha Alpha-shape tightness for that contour; default 0.001
mesh_shade Light the surface - "ghost" is the interesting one here

The default: a translucent surface#

Volumes come with their own colour and alpha (a pale grey at 20% for the example volumes), so passing one straight to plot2d already gives you something usable:

import navis
import matplotlib.pyplot as plt

n = navis.example_neurons(1, kind="skeleton")
lh = navis.example_volume("LH")

fig, ax = navis.plot2d([n, lh], view=("x", "-z"), method="2d")
plt.tight_layout()

tutorial plotting 2d 02 volumes

The alpha is worth a note. A neuropil is nearly always translucent, and translucency is exactly the case a per-face renderer gets wrong: every triangle composites separately, so the surface reads as its own triangulation and folds come out darker than flat stretches. NAVis fills the union of the visible faces as a single path instead, so alpha means what it says:

fig, axes = plt.subplots(1, 3, figsize=(14, 5))
for ax, alpha in zip(axes, [0.1, 0.3, 0.7]):
    navis.plot2d([n, lh], ax=ax, method="2d", view=("x", "-z"), color={lh.name: (0.4, 0.5, 0.9, alpha)})
    ax.set_title(f"volume alpha={alpha}", fontsize=11, y=0.98)
    ax.set_axis_off()
plt.tight_layout()

volume alpha=0.1, volume alpha=0.3, volume alpha=0.7

Outlines instead of surfaces#

volume_outlines=True swaps the filled surface for a single contour - the cheapest possible way to give a figure context, and often the most legible:

fig, axes = plt.subplots(1, 3, figsize=(14, 5))
for ax, outlines in zip(axes, [False, True, "both"]):
    navis.plot2d([n, lh], ax=ax, method="2d", view=("x", "-z"), volume_outlines=outlines)
    ax.set_title(f"volume_outlines={outlines!r}", fontsize=11, y=0.98)
    ax.set_axis_off()
plt.tight_layout()

volume_outlines=False, volume_outlines=True, volume_outlines='both'

The contour is always drawn opaque, whatever alpha the volume carries. A volume's alpha is a fill alpha - it is there so you can see the neuron through the neuropil - and there is nothing to see through a line, so at the 10-20% volumes default to it would simply be invisible.

Outlines need shapely

The contour is an alpha shape of the projected vertices, which NAVis computes with shapely. Install it (pip install shapely) or you will get an error.

volume_outlines_alpha controls how tightly that shape wraps the projection: near zero gives you the convex hull, larger values let it follow concavities. On the whole-brain outline the difference is the difference between a blob and a fly brain:

brain = navis.example_volume("neuropil")

fig, axes = plt.subplots(1, 4, figsize=(17, 5))
for ax, a in zip(axes, [0.00001, 0.0001, 0.0003, 0.001]):
    navis.plot2d(
        brain, ax=ax, method="2d", view=("x", "-z"), color=(0.2, 0.3, 0.7),
        volume_outlines=True, volume_outlines_alpha=a,
    )
    ax.set_title(f"volume_outlines_alpha={a}", fontsize=11, y=0.98)
    ax.set_axis_off()
plt.tight_layout()

volume_outlines_alpha=1e-05, volume_outlines_alpha=0.0001, volume_outlines_alpha=0.0003, volume_outlines_alpha=0.001

The useful range depends on the mesh

alpha is a radius filter in data units: a triangle survives if its circumradius is below 1/alpha. The useful range therefore scales with your coordinate system, and no single value is right for every volume. 10 / (size of the volume) is a decent starting guess - which is where the default of 0.001 comes from, since these example meshes are ~10,000 units across.

Push it too far and the shape breaks into disconnected pieces. NAVis then retries at a tenth of the alpha and warns you - so if a large value seems to have no effect, check the log: it was probably rolled back.

Ghost shading#

The awkward thing about a filled neuropil is the choice it forces: opaque enough to read as a solid, or transparent enough to see the neuron inside. mesh_shade="ghost" sidesteps it by taking opacity from the grazing angle - dense where the surface turns away from you, clear where it faces you:

fig, axes = plt.subplots(1, 3, figsize=(14, 5))
for ax, (title, kwargs) in zip(
    axes,
    [
        ("flat", dict()),
        ("mesh_shade=True", dict(mesh_shade=True)),
        ('mesh_shade="ghost"', dict(mesh_shade="ghost")),
    ],
):
    navis.plot2d([n, lh], ax=ax, method="2d", view=("x", "-z"), **kwargs)
    ax.set_title(title, fontsize=11, y=0.98)
    ax.set_axis_off()
plt.tight_layout()

flat, mesh_shade=True, mesh_shade="ghost"

Look closely at the middle panel: plain mesh_shade=True needs one polygon per face, and on a translucent mesh that means every triangle composites on its own - so the 756-face LH shows its own wireframe. "ghost" varies alpha rather than brightness and reads far more cleanly. strength scales the term:

navis.plot2d(lh, mesh_shade={"mode": "ghost", "strength": 1.8})

Volumes are always scenery#

Three deliberate asymmetries between volumes and mesh neurons, all of them so that a mixed scene does the right thing without being told:

Mesh neuron Volume
z-order above skeletons by default always at the bottom
depth_sort joins the stack stays out of it
halo gets one never

Which means this is all you need for a figure with context:

nl = navis.example_neurons(3, kind="skeleton")

fig, ax = navis.plot2d(
    [nl, lh],
    view=("x", "-z"),
    method="2d",
    style="publication",
    volume_outlines="both",
    figsize=(7, 7),
)
plt.tight_layout()

tutorial plotting 2d 02 volumes

Several volumes at once#

Colour them the way you would any other object - a list, or a dict keyed by name:

fig, ax = navis.plot2d(
    [n, lh, brain],
    view=("x", "-z"),
    method="2d",
    color={lh.name: (0.9, 0.4, 0.4, 0.3), brain.name: (0.6, 0.6, 0.7, 0.08)},
    figsize=(7, 7),
)
plt.tight_layout()

tutorial plotting 2d 02 volumes

Volumes of your own

Anything you can turn into a navis.Volume works here - a .obj file, a trimesh.Trimesh, or vertices and faces you built yourself. See the mesh I/O tutorial.

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

Download Python source code: tutorial_plotting_2d_02_volumes.py

Download Jupyter notebook: tutorial_plotting_2d_02_volumes.ipynb

Gallery generated by mkdocs-gallery