An add-on for Blender, to create 3D activity animations of NEURON models.
- Easlily bring morphologies from NEURON to Blender 💻
- Animate dynamics of membare voltage in space and time ⚡
- Color by voltage using matplotlib and seaborn stunning colormaps 🌈
- Install Blender 3.3 or newer: https://www.blender.org/download/
- Download the latest
BlenderSpike.zip
from the Releases section - In Blender go to Edit > Preferences... > Add-ons > Install... open the downloaded ZIP file
- Enable the addon by going to Edit > Preferences... > Add-ons and enabling "Add Mesh: BlenderSpike"
If you wish to run your own simulations, you will need to install a copy of NEURON (see instructions). After this, install the Python companion module by running:
pip install git+https://github.com/ArtemKirsanov/BlenderSpike.git
(install it to the same environment that contains NEURON)
Note For more detailed examples see
demos
directory
BlenderSpike constists of 2 parts.
I) blenderspike_py is a Python companion module which makes it easy to record voltage across all segments in your NEURON model and save the data as a .pickle
file.
II) BlenderSpike addon adds a set of UI panels, which let you load the resulting .pickle
file and bring your morphology into Blender along with voltage animation.
BlenderSpike pulls geometry and 3D location data from NEURON objects and operates with nmn.Section
instances (see documentation). This implies that you use Python to initialize and run NEURON models using the neuron.h
module.
-
Set up your model morphology and biophysics in NEURON (but don't run the simulation yet)
-
Create an instance of
CellRecorder
class by passing a list of all Sections, which constitute the modelled cell -
Run the NEURON model (using
h.continuerun
or any other way) -
Export the model and its activity by calling
CellRecorder.save_pickle()
-
In the Blender 3D view window navigate to the BlenderSpike panel (press
n
to show the menu) -
In the "Neuron Bulder" tab click on the folder icon (Path to file), select the
.pickle
you created and click "Build a neuron" -
To add a voltage material, navigate to "Shading Manager" panel, choose a colormap and voltage color limits
-
Select the neuron parent object and click "Create a voltage coloring"
BlenderSpike uses internal coordinates of NEURON (eg. nrn.Section.x3d
) to position neurons in the scene. These often be way off (especially for reconstructed morphologies), so two options are provided during the neuron creation in Blender.
- To make sure that soma is located in the center of the scene
(0,0,0)
check the "Center at origin" option - To adjust for different scales (for example NEURON units can be much larger / smaller than Blender's) change the Downscale factor slider.
Note
Coordinates from
.pickle
will be divided (not multiplied) by the downscale factor
BlenderSpike allows you to control spatial resolution of both the morphology and voltage animation with a single segmentation parameter. More a more detailed explation of what it stands for, see [Segmentations and linear interpolation](###Segmentations and linear interpolation).
On a high level – bigger segmentation values make the morphology and the voltage dynamics look more detailed and visually pleasing, but require more time to render and may cause more crashes.
Thickness factor of branches is controlled by the branch thickness slider. Depending on the relationship between units used in NEURON and Blender optimal values may vary.
For the sake of visualization, BlenderSpike allows you to control the displayed "homogeneity" of branch thickness. This does not affect the simulation result and is used only to see thin branches better.
Be careful, since increasing the homogeneity will probably require to tweak the thickness factor. Just play around with thickness homogeneity and baseline branch thickness to find the optimal balance ;)
In order to color the neuron according to the values of membrane voltage you need to choose a colormap and set voltage limits. To set a colormap type its name in the text field and press "Create a voltage coloring" (make sure that the parent empty object is selected).
Available colormaps: Colormaps are parsed by name using seaborn.color_palette()
, so please refer to the documentation
There is also an option to create a sub-map from a portion of an existing colormap by specifying normalized start color
and stop color
(from a range between 0 and 1)
Note, that when you save a Blender file as .blend
, meshes and metadata are saved, but the Python objects and frame_change_post
handlers are discarded when the application is closed. This is why after each opening the .blend
file you will need to manually reload the animation handlers by selecting the NEURON parent Null object and clicking on "Reload animation data" in the BlenderSpike panel.
For some reason during rendering long animations (1000s of frames) and/or handling scenes with large number of segments Blender often crashes. At the moment I'm not sure what causes this (maybe not enough video memory or something). Just keep this in mind and remember to save your files frequently 🙂
In Python, CellRecorder
class initializes an array of h.Vector
instances to record the voltage of each segment along the neuron tree over the entire simulation.
When you call save_pickle()
, the CellRecorder
iterates over all its sections and for each, constructs the following dictionary:
ID
: an integer number serving as a unique identifier used by Blender. Typically, it is equal to the index of the particular section in the list of all sections.type
: a String specifying the section's type (Soma, Dendrite, Axon etc.). Only used in Blender for display purposes. The data for the type is parsed from thename
attribute of the corresponding NEURON section object.X
: an array with lengthsection.n3d()
, containing X-coordinates of points, specifying section's shapeY
: an array with lengthsection.n3d()
, containing Y-coordinates of points, specifying section's shapeZ
: an array with lengthsection.n3d()
, containing Z-coordinates of points, specifying section's shapeDIAM
: an array with lengthsection.n3d()
, containing the diameters of anchor points, specifying section's shapeVoltage
: a dictionary with frame-wise animation data of the form{FRAME: VOLTAGE_ARRAY}
, whereVOLTAGE_ARRAY
is an array withsection.nseg
points, specifying the voltage profile along the segments for a given frame. Note, that the maximum number of frames is given byFRAME_NUM
argument, when calling.save_pickle()
A list of such section dictionaries is what is dumped into the pickle.
Frame-wise voltage is obtained by resampling source voltage array with FRAME_NUM
points.
- A neuron is built branch-by-branch using Blender's Bezier splines, where the
.co
and.radius
attributes ofspline.bezier_points
is controlled by the morphology data, exported asX
,Y
,Z
andDIAM
arrays in.pickle
as well as the [segmentation](###Segmentations and linear interpolation). - Soma is built as a sphere if "Simplify soma" is checked. If unchecked – soma has a cylidrical shape as is built similarly to the branches.
- Branches are converted from splines into
Mesh
objects - Both soma and branches are nested inside a parent Empty object (an empty axes in Blender), which holds metadata (such as path to the pickle) is custom object properties.
Inside Blender the spatial profile of voltage is represented as a custom Vertex attribute (you could see it in Blender's Spreadsheet tab)
Updating the voltage profile in time relies on using frame_change_post
handler.
Every time you build a neuron, internally an instance of BlenderNeuron
class is created. It has a voltage_handler
method that loops over all the sections and sets the voltage attribute according to a current frame (as a lookup from the .pickle file). To be automatically called every time the frame changes, the voltage_handler
method of a given neuron should be appended to the list of Blender's frame_change_post
handlers.
Because Blender doesn't save the handler objects in the .blend
file, every time you close and open Blender, you need to re-create the handlers (see Reloading data)
Under the hood, NEURON typically ignores how the morphology is laid out in 3D space (as specified by .x3d()
, .y3d()
etc). It only uses the section lengths, radii and the topology (which sections are connected to which) to run the simulations. This results in 2 types of discrectization:
-
Spatial discrectization of the actual morphology (points to specify 3D positions and angles of sections)
$N=\text{n3d()}$ -
Spatial discrectization for numerical simulations (each
Section
is broken up into.nseg
segments that are treated as cylinders to run biophysical calculations) –$N=\text{nseg}$
To bring the model into Blender interpolation is performed to contstuct a one-to-one mapping between the two discrectization for each Section.
- Array of coordinates (containing
$N=\text{n3d()}$ elements) is linearly interpolated to$N_\text{segmentation}$ elements - Voltage array for each section (
$N=\text{nseg}$ ) is interpolated to$N_\text{segmentation}$ elements
Resulting interpolated coordinates are using during branches contruction, while interpolated voltage arrays are used to set point attributes of branch vertices on every frame in a consistent manner.
Thus, the segmentation
parameter controls both the morphological detailization and the detailization of voltage profile simultaneously.
Warning
Be aware that increasing the
segmentation
parameter in Blender will not make the result more detailed than what is stored in.pickle
. So make sure you simulate the model with enoughnseg
and with the detailed enough.swc
morphology to begin with.
MIT