***************************
SpikePlot Demo and Tutorial
***************************
This tutorial illustrates how to make spike raster plots using the
:mod:`neuronpy.graphics.spikeplot` module. This document also exists as an
interactive worksheet on the NEURON Sage server: https://nn.med.yale.edu:8000.
Log onto the server and search the published worksheets for "SpikePlot demo".
Introduction
============
Spike trains are formatted as single vectors
(Lists or 1D numpy arrays) or as 2D vectors (List of Lists or 2D numpy arrays). The
elements of the vectors are the spike times. Plots are such that time is in the horizontal
axis and each row is a given cell.
Basic example
=============
The following example shows the basic steps using some artificially created spikes. We
simply create a SpikePlot object then plot the spikes.
::
import numpy
from neuronpy.graphics import spikeplot
spikes = []
num_cells = 10
num_spikes_per_cell = 20
frequency = 20
# Make the spike data. Use a simple Poisson-like spike generator
# (just for illustrative purposes here. Better spike generators should
# be used in simulations).
for i in range(num_cells):
isi=numpy.random.poisson(frequency, num_spikes_per_cell)
spikes.append(numpy.cumsum(isi))
# spikes is now a list of lists where each cell has a list of spike
# times. Now, let's plot these spikes.
sp = spikeplot.SpikePlot(savefig=True)
sp.plot_spikes(spikes)
Let's step through the details.
Import the module
=================
First, import the module.
::
from neuronpy.graphics import spikeplot
Basic plot
==========
Plotting spike data requires an instance of a SpikePlot object. Instantiate the object and
call :py:func:`~neuronpy.graphics.spikeplot.SpikePlot.plot_spikes`. (The object uses a
``savefig=True``
option that makes drawing write to a file, which is required over the web. When using in a
dynamic mode with matplotlib, you can leave this at its default value, which is False.)
::
sp = spikeplot.SpikePlot(savefig=True)
sp.plot_spikes(spikes)
In addition to noting that the tick marks are by default black vertical bars that just
touches each other, there are a few things to note about this plot. In particular,
SpikePlot uses `matplotlib `_ Figures. As such, this
figure takes many Matplotlib.Figure and Axes defaults.
- The figure size is 8x6 inches.
- Axis tick marks are automatically generated.
- There are no axis labels, no title, and no legends.
We will add these elements and add/edit other properties of the spike plot as we progress
throug the tutorial.
SpikePlot variables
===================
SpikePlot has several different variables below that can be set and retrieved: :ref:`marker`_
* :ref:`marker`
* :ref:`markercolor`
* :ref:`markerscale`
* :ref:`markeredgewidth`
* :ref:`linestyle`
* :ref:`linewidth`
* :ref:`fig_name`
* :ref:`figsize`
* :ref:`figure and axes handles `
* :ref:`Adding shaded regions `
These variables are illustrated below. Python does not have the capability of hiding
variables, so these variables are accessible directly as ``_`` (note the
underscore in front of the variable name). However, the ``set_`` methods perform
error checking to help ensure that the proposed variable value is valid, so use the
accessor methods instead of setting the variable directly.
marker
------
By default, the marker is a vertical line. This can be changed to any valid
`Matplotlib.lines.Line2D marker \
`_.
For example, the marker can be set to circles.
::
# Make the marker filled circles.
sp=spikeplot.SpikePlot(savefig=True, marker='.')
sp.plot_spikes(spikes)
Note that this is equivalent to
::
sp=spikeplot.SpikePlot(savefig=True)
sp.set_marker('.') # Make all subsequent marks circles
sp.plot_spikes(spikes)
markercolor
-----------
The markercolor can be changed with any valid `Matplotlib color \
`_. This means strings like
'red' and 'blue' can be used as well as RGB tuples and html strings. This sets the
complete element to a solid color. There is no discrepancy between facecolor and edgecolor.
If horizontal lines are shown (described below), this also colors the line with the same
color.
::
sp=spikeplot.SpikePlot(savefig=True)
sp.set_markercolor('red')
sp.plot_spikes(spikes)
markerscale
-----------
The markerscale corresponds to the height of the rows. With a default value of 1 and when
vertical bars are used as the tick marks, each row just touches the other. With a value of
0.5, the tick marks would be half as tall. In the previous examples using circle tick marks,
the marks were quite large. Setting this value to a smaller value may help that issue.
Additionally, larger values than 1 will bleed across rows, which may be desired in some
situations.
::
sp=spikeplot.SpikePlot(savefig=True)
sp.set_markerscale(0.5)
sp.plot_spikes(spikes)
markeredgewidth
---------------
The markeredgewidth also defines the size of the tick mark. By default, this has a value
of 1. This can be made even smaller for sharper tick marks or larger to widen the mark.
For example, here we set the width to 10.
::
sp=spikeplot.SpikePlot(savefig=True)
sp.set_markeredgewidth(10)
sp.plot_spikes(spikes)
linestyle
---------
The linestyle defines the horizontal line that is drawn across each spike train. By
default, the linestyle is ``None`` so that no lines are drawn. This can accept any
linestyle defined by `Matplotlib.lines \
`_,
but for the most part, either ``None`` or ``'-'`` will be used.
::
sp=spikeplot.SpikePlot(savefig=True)
sp.set_linestyle('-')
sp.set_markerscale(0.5)
sp.plot_spikes(spikes)
linewidth
---------
When the linestyle is not ``None``, then a horizontal line is drawn. The width of that
line can be set to be different from it's default value of 0.75.
::
sp=spikeplot.SpikePlot(savefig=True)
sp.set_linestyle('-')
sp.set_linewidth(3)
sp.set_markerscale(0.5)
sp.plot_spikes(spikes)
fig_name
--------
The ``fig_name`` variable is "spikeplot.png" by default. Changing this to
"." allows other options. Allowable file formats are largely determined
by the graphics backend that is used, but for the most part, png, pdf, ps, eps and svg
extensions are permitted.
This parameter raises an interesting point. When ``savefig=True``,
:py:func:`~neuronpy.graphics.spikeplot.SpikePlot.plot_spikes` writes its output to
a file, but this can be modified, as discussed later.
::
sp=spikeplot.SpikePlot(savefig=True)
sp.set_fig_name('myplot.pdf')
sp.plot_spikes(spikes)
figsize
-------
The ``figsize`` parameter is a tuple of length 2 specifying the width and height of the
figure in inches. By default, this is 8x6 (or to the value specified in the Matplotlib
rc file). Avoid setting this value by getting the figure handle and setting the size. Use
the :py:func:`~neuronpy.graphics.spikeplot.SpikePlot.set_figsize` method, or start with
the desired output size as the parameter.
::
sp=spikeplot.SpikePlot(savefig=True, figsize=(6,2))
sp.plot_spikes(spikes)
.. _figaxes
figure and axes handle
----------------------
It is possible to pass a figure handle to the SpikePlot object as well as an axes object.
This can be useful for specifying titles, axis labels, tick marks, and general layout.
The following example creates a figure and axes, then sets an axes title, a x-axis label,
and removes the ticklabels and tick marks from the vertical axis.
::
from matplotlib import pyplot
# Pre-process some figure variables
fig_handle=pyplot.figure(figsize=(6,2))
ax=fig_handle.add_subplot(111)
ax.set_title('Pre-formatted figure')
ax.set_xlabel('$t$ (ms)') # Note LaTeX
ax.set_yticks([])
# Now pass the figure handle to SpikePlot.
# The spike_axes will be set to the first axes object assigned to the figure
sp=spikeplot.SpikePlot(savefig=True, fig=fig_handle)
sp.plot_spikes(spikes)
Well, the title is clipped and the xlabel is not even visible. Adjust the axes size.
::
fig_handle=pyplot.figure(figsize=(6,2))
ax=fig_handle.add_subplot(111)
ax.set_title('Pre-formatted figure')
ax.set_xlabel('$t$ (ms)') # Note LaTeX
ax.set_yticks([])
#### Next lines are new
pos=ax.get_position()
ax.set_position([pos.xmin, pos.ymin+.1, pos.width, pos.height-.15])
# Now pass the figure handle to SpikePlot.
# The spike_axes will be set to the first axes object assigned to the figure
sp=spikeplot.SpikePlot(savefig=True, fig=fig_handle)
sp.plot_spikes(spikes)
That's better.
We can also work backwards by plotting the spikes and then doing some post-processing
formatting. This requires getting the figure handle and spike_axes from the SpikePlot
object and manipulating them. After any manipulation to the axes, the SpikePlot object
needs to be told so that it can adjust the spike rasters properly. Additionally, we do not
draw the figure to a file when we call
:py:func:`~neuronpy.graphics.spikeplot.SpikePlot.plot_spikes`, which normally writes a
file. So we do not initialize SpikePlot with a ``savefig=True``, but then write the
file later.
::
sp=spikeplot.SpikePlot() # No need for SpikePlot to write a file yet
sp.plot_spikes(spikes)
# Post-processing
fig_handle=sp.get_fig()
fig_handle.set_size_inches(6,2)
ax=sp.get_raster_axes()
ax.set_title('Post-formatted figure')
ax.set_xlabel('$t$ (ms)') # Note LaTeX
ax.set_yticks([])
pos=ax.get_position()
ax.set_position([pos.xmin, pos.ymin+.1, pos.width, pos.height-.15])
# After modifying the spike_axes, be sure to update the SpikePlot object
sp.set_raster_axes(ax)
# Actually draw and write the file
fig_handle.savefig(sp.get_fig_name())
.. _shaded
Shaded regions
--------------
Another useful thing to do in post-processing the figure may be to add some shaded region
as in the following example.
::
from matplotlib import patches
sp=spikeplot.SpikePlot() # No need for SpikePlot to write a file yet
sp.plot_spikes(spikes)
# Post-processing
fig_handle=sp.get_fig()
fig_handle.set_size_inches(6,2)
ax=sp.get_raster_axes()
ax.set_title('Post-formatted figure')
ax.set_xlabel('$t$ (ms)') # Note LaTeX
ax.set_yticks([])
pos=ax.get_position()
ax.set_position([pos.xmin, pos.ymin+.1, pos.width, pos.height-.15])
#### Highlight
left = 100
bottom = 0
width = 200
ylim = ax.get_ylim()
height = ylim[1] - ylim[0] + 1
rect = patches.Rectangle( (left, bottom), width, height, linewidth=0, alpha=0.2, facecolor=(0,0,1))
ax.add_patch(rect)
# After modifying the spike_axes, be sure to update the SpikePlot object
sp.set_raster_axes(ax)
# Actually draw and write the file
fig_handle.savefig(sp.get_fig_name())
Multiple trains
===============
We can write multiple trains by either overlaying a second set of spikes on top of the
original set, or by stacking. This first example creates a second set of spikes and draws
them red, overlaying the black train. Under the hood, spike data and drawing formats are
overwritten with new rendering commands. This means if we called
:py:func:`~neuronpy.graphics.spikeplot.SpikePlot.plot_spikes` with more spikes, it would
erase the previous data. To get around this, we now call
:py:func:`~neuronpy.graphics.spikeplot.SpikePlot.plot_spikes` with different labels.
::
# Create a second list of spikes
spikes2 = []
num_cells = 10
num_spikes_per_cell = 20
frequency = 20
# Make the spike data. Use a simple Poisson-like spike generator
# (just for illustrative purposes here. Better spike generators should
# be used in simulations).
for i in range(num_cells):
isi=numpy.random.poisson(frequency, num_spikes_per_cell)
spikes2.append(numpy.cumsum(isi))
Plot with different labels.
::
# Draw the first spikes as before, but set draw to False so that we do
# not draw to the screen with this plot_spikes call, but wait until
# later, when we have drawn all spike traces to draw to the screen.
sp=spikeplot.SpikePlot(savefig=True)
sp.plot_spikes(spikes, label='black spikes', draw=False)
# Make subsequent marks red
sp.set_markercolor('red')
sp.plot_spikes(spikes2, label='red spikes')
To stack plots, you have to provide a ``cell_offset`` value on any subsequent sets.
::
# Make the marker filled circles.
sp=spikeplot.SpikePlot(savefig=True)
sp.set_markerscale(0.5)
sp.set_marker('.')
# Set draw to false so that we do not draw to the screen now, but
# wait until later, when we have drawn all spike traces.
# Make new marks black
sp.set_markercolor('black')
sp.plot_spikes(spikes, label='black', draw=False)
# Make subsequent marks red
sp.set_markercolor('red')
# Set refresh to False so that old marks are not erased.
sp.plot_spikes(spikes2, label='red', cell_offset=len(spikes))
.. _largedata
Large Data and telescoping
--------------------------
In general, if you are navigating large data, you may try and load it all into the figure
and then specify the subset of columns (a chunk of time) to see. This is possible using
:py:func:`~neuronpy.graphics.spikeplot.SpikePlot.update_xlim` method. The following example
illustrates this.
::
sp=spikeplot.SpikePlot(savefig=True)
sp.plot_spikes(spikes)
sp.update_xlim((100, 200)
.. ifconfig:: sagebuild
As a better example with large data, we can execute the following code that loads
spikes that have been pickled.
::
import pickle
with open('data/spikes.p', 'r') as pickle_file: # Open the file for reading
large_spikes = pickle.load(pickle_file) # Load the pickled contents
Plot that data and interact with it.
::
var('zoom')
var('translation')
zoom_list=[]
for i in range(21):
zoom_list.append(math.sqrt(2.)**i)
sp_large = spikeplot.SpikePlot(savefig=True)
sp_large.plot_spikes(large_spikes)
@interact
def _(zoom = slider(zoom_list), \
center=slider(range(sp_large._axes_lim[1]),default=\
(sp_large._axes_lim[1]-sp_large._axes_lim[0])/2+sp_large._axes_lim[0])):
range = sp_large._axes_lim[1]-sp_large._axes_lim[0]
window_width = range/zoom
dim_x=int(window_width/2)
x_min=center-dim_x
x_max=center+dim_x
sp_large.update_xlim((int(x_min),int(x_max)))
Spike time histograms
=====================
Adding a spike time histogram to an existing plot is quite easy.
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sp.plot_spikes(spikes)
Adding the argument ``sth_ratio=0.2`` in the code above creates a spike time histogram
and sets it to occupy 20% of the vertical space. Try other values, such as 0.5. The bins
are quantized by 1 ms by default. The code below grabs the spike time histogram object and
sets it's bars' timestep (dt) to 10 ms.
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sth = sp.get_sth()
sth.set_dt(10)
sp.plot_spikes(spikes)
There are four types of bar styles for the histogram: 'bar' (default), 'stepfilled',
'step', and 'lineto'.
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sth = sp.get_sth()
sth.set_dt(10)
sth.set_style('stepfilled')
sp.plot_spikes(spikes)
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sth = sp.get_sth()
sth.set_dt(10)
sth.set_style('step')
sp.plot_spikes(spikes)
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sth = sp.get_sth()
sth.set_dt(10)
sth.set_style('lineto')
sp.plot_spikes(spikes)
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sth = sp.get_sth()
sth.set_dt(10)
sth.set_style('step')
sp.plot_spikes(spikes, label='black spikes')
sp.set_markercolor('red')
sp.plot_spikes(spikes2, label='red spikes')
The histogram can also be considered a 1D vector of values. In this case, it can be
filtered for various effect. This is useful in cases where a small time window (dt) is used
for the histogram, but where you want to blur or smear the spikes in time with a gaussian
or linear function, say. The mechanism used to filter is a 1D kernel. Kernels should
sum to 1, but you can turn this flag off if you want. The following example makes a
linearly decaying kernel that makes each spike reach 0 in 10 ms. This example also only
uses a few spikes in one spike train so that you can see the effects of modifying dt and
the kernel origin, which can be in the range :math:`\left[-\mathrm{len}(\mathrm{kernel})/2,
\mathrm{len}(\mathrm{kernel})/2\right]`, or the values 'left', 'center', and 'right'.
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sp.set_markercolor('red')
sth = sp.get_sth()
sth.set_dt(.1) # Small time window
kernel = numpy.linspace(1, 0., 10/sth._dt) # Linear ramp over 10 ms
kernel = numpy.divide(kernel, numpy.sum(kernel)) # Normalize
sth.set_kernel(kernel)
sth.set_style('lineto')
sth.set_origin('left')
sp.plot_spikes([[5, 20, 35, 45]])
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sp.set_markercolor('red')
sth = sp.get_sth()
sth.set_dt(.1) # Small time window
kernel = numpy.linspace(1, 0., 10/sth._dt) # Linear ramp over 10 ms
kernel = numpy.divide(kernel, numpy.sum(kernel)) # Normalize
sth.set_kernel(kernel)
sth.set_style('lineto')
sth.set_origin('left')
sp.plot_spikes(spikes)
::
from neuronpy.math import kernel
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sp.set_markercolor('red')
sth = sp.get_sth()
dt = .1
sth.set_dt(dt) # Small time window
k = kernel.gauss_1d(2, dt)
sth.set_kernel(k)
sth.set_style('lineto')
sth.set_origin('center')
sp.plot_spikes(spikes)
::
sp=spikeplot.SpikePlot(sth_ratio=0.2, savefig=True, figsize=(8,4))
sth = sp.get_sth()
dt = .1
sth.set_dt(dt) # Small time window
k = kernel.gauss_1d(2, dt)
sth.set_kernel(k)
sth.set_style('lineto')
sth.set_origin('center')
sp.plot_spikes(spikes, label='black spikes')
sp.set_markercolor('red')
sp.plot_spikes(spikes2, label='red spikes')