# Generate a raster plot of spiking
from netpyne import __gui__
if __gui__:
import matplotlib.patches as mpatches
from ..analysis.utils import exception # , loadData
from ..analysis.tools import loadData
from .plotter import ScatterPlotter
[docs]
@exception
def plotRaster(
rasterData=None,
axis=None,
timeRange=None,
maxSpikes=1e8,
orderBy='gid',
popRates=True,
popNumCells=None,
popLabels=None,
popColors=None,
syncLines=False,
colorbyPhase = None,
legend=True,
colorList=None,
orderInverse=False,
returnPlotter=False,
**kwargs
):
"""Function to produce a raster plot of cell spiking
NetPyNE Options
---------------
include : str, int, list
Cells and/or NetStims to return information from.
*Default:* ``['allCells']`` includes all cells and no NetStims
*Options:*
(1) ``'all'`` includes all cells and all NetStims,
(2) ``'allNetStims'`` includes all NetStims but no cells,
(3) a *str* which matches a popLabel includes all cells in that pop,
(4) a *str* which matches a NetStim name includes that NetStim,
(5) an *int* includes the cell with that global identifier (GID),
(6) a *list* of *ints* includes the cells with those GIDS,
(7) a *list* with two items, the first of which is a *str* matching a popLabel and the second of which is an *int* (or a *list* of *ints*), includes the relative cell(s) from that population (e.g. (``['popName', [0, 1]]``) includes the first two cells in popName.
sim : NetPyNE sim object
The *sim object* from which to get data.
*Default:* ``None`` uses the current NetPyNE sim object
Parameters
----------
rasterData : list, tuple, dict, str
The data necessary to plot the raster (spike times and spike indices, at minimum).
*Default:* ``None`` uses ``analysis.prepareRaster`` to produce ``rasterData`` using the current NetPyNE sim object.
*Options:* if a *list* or a *tuple*, the first item must be a *list* of spike times and the second item must be a *list* the same length of spike indices (the id of the cell corresponding to that spike time). Optionally, a third item may be a *list* of *ints* representing the number of cells in each population (in lieu of ``popNumCells``). Optionally, a fourth item may be a *list* of *strs* representing the population names (in lieu of ``popLabels``).
If a *dict* it must have keys ``'spkTimes'`` and ``'spkInds'`` and may optionally include ``'popNumCells'`` and ``'popLabels'``.
If a *str* it must represent a file path to previously saved data.
axis : matplotlib axis
The axis to plot into, allowing overlaying of plots.
*Default:* ``None`` produces a new figure and axis.
timeRange : list
Time range to include in the raster: ``[min, max]``.
*Default:* ``None`` uses the entire simulation
maxSpikes : int
The maximum number of spikes to include (by reducing the max time range).
*Default:* ``1e8``
orderBy : str
How to order the cells along the y-axis.
*Default:* ``'gid'`` orders cells by their index
*Options:* any NetPyNe cell tag, e.g. ``'pop'``, ``'x'``, ``'ynorm'`` .
popRates : bool
whether to include the spiking rates in the plot title and legend.
*Default:* ``True`` includes detailed pop information on plot.
*Options:*
``False`` only includes pop names.
``'minimal'`` includes minimal pop information.
popNumCells : list
A *list* of *ints* representing the number of cells in each population.
*Default:* ``None`` puts all cells into a single population.
popLabels : list
A *list* of *strs* of population names. Must be the same length as ``popNumCells``.
*Default:* ``None`` uses generic names.
popColors : dict
A *dict* of ``popLabels`` and their desired color.
*Default:* ``None`` draws from the NetPyNE default colorList.
syncLines : bool
Calculate synchrony measure and plot vertical lines for each spike to evidence synchrony if ``True``.
*Default:* ``False``
colorbyPhase : dict
Dictionary specifying conditions to plot spikes colored by the phase of a simultaneous signal, filtered in a given range
*Default:* ``None`` colors spikes according to other options (by populations)
*Dictionary entries:*
``'signal'`` specifies the signal. Options are: ``'LFP'``, which takes the signal from the local fiel potential generated in the ongoing simulation, a numpy array of scalars (for example, an external signal used for stimulation), or an external pickle file,
``'fs'`` is the sampling frequency, which should be specified when the signal is obtained from external sources (pickle file or numpy array). Otherwise, it is assumed to be 1000 Hz. If the signal is specified by ``'LFP'``, then the sampling rate is obtained from the internal simulation (cfg.recordStep),
``'electrode'`` selects the electrode from the LFP setup. Default is electrode 1,
``'filtFreq'`` is a list specifying the range for filtering the signal (band-pass). For example, ``[4,8]`` to select theta rhythm. The default is a very broadband filtering (essentially, the raw signal) ``[1,500]``,
``'filtOrder'`` is the filter order (Butterworth) to process the signal,
``'pop_background'`` is a boolean option to color each population alternately with a gray background, for better visualization. The default is False,
``'include_signal'`` is a boolean option to plot the filtered signal below the raster plot. The default is False.
legend : bool
Whether or not to add a legend to the plot.
*Default:* ``True`` adds a legend.
colorList : list
A *list* of colors to draw from when plotting.
*Default:* ``None`` uses the default NetPyNE colorList.
orderInverse : bool
Whether or not to invert the y axis (useful if populations are defined top-down).
*Default:* ``False`` does not invert the y-axis.
returnPlotter : bool
Whether to return the figure or the NetPyNE MetaFig object.
*Default:* ``False`` returns the figure.
Plot Options
------------
showFig : bool
Whether to show the figure.
*Default:* ``False``
saveFig : bool
Whether to save the figure.
*Default:* ``False``
overwrite : bool
whether to overwrite existing figure files.
*Default:* ``True`` overwrites the figure file
*Options:* ``False`` adds a number to the file name to prevent overwriting
legendKwargs : dict
a *dict* containing any or all legend kwargs. These include ``'title'``, ``'loc'``, ``'fontsize'``, ``'bbox_to_anchor'``, ``'borderaxespad'``, and ``'handlelength'``.
rcParams : dict
a *dict* containing any or all matplotlib rcParams. To see all options, execute ``import matplotlib; print(matplotlib.rcParams)`` in Python. Any options in this *dict* will be used for this current figure and then returned to their prior settings.
title : str
the axis title
xlabel : str
label for x-axis
ylabel : str
label for y-axis
s : int
marker size
marker : str
marker symbol
linewidth : int
line width
Returns
-------
rasterPlot : *matplotlib figure*
By default, returns the *figure*. If ``returnPlotter`` is ``True``, instead returns the NetPyNE MetaFig.
"""
# If there is no input data, get the data from the NetPyNE sim object
if rasterData is None:
if 'sim' not in kwargs:
from .. import sim
else:
sim = kwargs['sim']
rasterData = sim.analysis.prepareRaster(
timeRange=timeRange, maxSpikes=maxSpikes, orderBy=orderBy,
popRates=popRates, colorbyPhase=colorbyPhase, **kwargs
)
print('Plotting raster...')
# If input is a file name, load data from the file
if type(rasterData) == str:
rasterData = loadData(rasterData)
popsOfCellsByGid = zip([], [])
# If input is a dictionary, pull the data out of it
if type(rasterData) == dict:
spkTimes = rasterData['spkTimes']
spkInds = rasterData['spkInds']
spkGids = rasterData['spkGids']
if colorbyPhase:
spkPhases = rasterData['spkPhases']
if not popNumCells:
popNumCells = rasterData.get('popNumCells')
if not popLabels:
popLabels = rasterData.get('popLabels')
axisArgs = rasterData.get('axisArgs')
legendLabels = rasterData.get('legendLabels')
popsOfCellsByGid = zip( # ordered the same
rasterData.get('cellGids', []),
rasterData.get('cellPops', []))
# If input is a list or tuple, the first item is spike times, the second is spike indices
elif type(rasterData) == list or type(rasterData) == tuple:
spkTimes = rasterData[0]
spkInds = rasterData[1]
axisArgs = None
legendLabels = None
# If there is a third item, it should be popNumCells
if not popNumCells:
try:
popNumCells = rasterData[2]
except:
pass
# If there is a fourth item, it should be popLabels
if not popLabels:
try:
popLabels = rasterData[3]
except:
pass
# If there is no info about pops, generate info for a single pop
if not popNumCells:
popNumCells = [max(spkInds)]
if popLabels:
popLabels = [str(popLabels[0])]
else:
popLabels = ['population']
# If there is info about pop numbers, but not labels, generate the labels
elif not popLabels:
popLabels = ['pop_' + str(index) for index, pop in enumerate(popNumCells)]
# If there is info about pop numbers and labels, make sure they are the same size
if len(popNumCells) != len(popLabels):
raise Exception(
'In plotRaster, popNumCells ('
+ str(len(popNumCells))
+ ') and popLabels ('
+ str(len(popLabels))
+ ') must be the same size'
)
# Set the time range appropriately
if 'timeRange' in kwargs:
timeRange = kwargs['timeRange']
elif 'timeRange' in rasterData:
timeRange = rasterData['timeRange']
else:
import numpy as np
timeRange = [0, np.ceil(max(spkTimes))]
# Set features for raster plot colored by phase
if colorbyPhase:
spkColors = spkPhases
legend = False
kwargs['colorbar'] = {'vmin': -180, 'vmax': 180}
kwargs['background'] = False
if 'pop_background' in colorbyPhase:
if colorbyPhase['pop_background'] == True:
kwargs['background'] = {'popLabels': popLabels, 'popNumCells': popNumCells, 'timeRange': timeRange}
else:
# Create a dictionary with the color for each pop
if not colorList:
from .plotter import colorList
popColorsTemp = {popLabel: colorList[ipop % len(colorList)] for ipop, popLabel in enumerate(popLabels)}
if popColors:
popColorsTemp.update(popColors)
popColors = popColorsTemp
if orderBy == 'gid':
# Create a list to link cell indices to their populations
indPop = []
for popLabel, popNumCell in zip(popLabels, popNumCells):
indPop.extend(int(popNumCell) * [popLabel])
def color(_, ind):
return popColors[indPop[int(ind)]]
else:
popByGid = {gid: pop for (gid, pop) in popsOfCellsByGid}
def color(gid, _):
pop = popByGid.get(gid)
if not pop:
return [.0, .0, .0] # default to black
return popColors.get(pop)
# Create a list of spkColors to be fed into the scatter plot
spkColors = [color(gid, ind) for gid, ind in zip(spkGids, spkInds)]
# Create a dictionary with the inputs for a scatter plot
scatterData = {}
scatterData['x'] = spkTimes
scatterData['y'] = spkInds
scatterData['c'] = spkColors
scatterData['s'] = 5
scatterData['marker'] = '|'
scatterData['markersize'] = 5
scatterData['linewidth'] = 2
scatterData['norm'] = None
scatterData['alpha'] = None
scatterData['linewidths'] = None
scatterData['cmap'] = None
if colorbyPhase:
scatterData['cmap'] = 'hsv'
if 'include_signal' in colorbyPhase and colorbyPhase['include_signal']==True:
scatterData['time'] = rasterData.get('time')
scatterData['signal'] = rasterData.get('signal')
scatterData['timeRange'] = timeRange
# If a kwarg matches a scatter input key, use the kwarg value instead of the default
for kwarg in list(kwargs.keys()):
if kwarg in scatterData:
scatterData[kwarg] = kwargs[kwarg]
kwargs.pop(kwarg)
# Create a dictionary to hold axis inputs
if not axisArgs:
axisArgs = {}
axisArgs['title'] = 'Raster Plot of Spiking'
axisArgs['xlabel'] = 'Time (ms)'
axisArgs['ylabel'] = 'Cells'
# It is often useful to invert the ordering of cells, so positions match the legend
if orderInverse:
axisArgs['invert_yaxis'] = True
# If a kwarg matches an axis input key, use the kwarg value instead of the default
for kwarg in list(kwargs.keys()):
if kwarg in axisArgs.keys():
axisArgs[kwarg] = kwargs[kwarg]
kwargs.pop(kwarg)
# create Plotter object
if 'signal' in scatterData:
rasterPlotter = ScatterPlotter(data=scatterData, kind='raster&signal', axis=axis, **axisArgs, **kwargs)
else:
rasterPlotter = ScatterPlotter(data=scatterData, kind='raster', axis=axis, **axisArgs, **kwargs)
metaFig = rasterPlotter.metafig
# add spike lines
if syncLines:
cellInds = list(set(spkInds))
rasterPlotter.axis.vlines(spkTimes, 0, len(cellInds), 'red', linewidth=0.1)
# add legend
if legend:
# Set up a dictionary of population colors
if not popColors:
colorList = colorList
popColors = {popLabel: colorList[ipop % len(colorList)] for ipop, popLabel in enumerate(popLabels)}
# Create the labels and handles for the legend
# (use rectangles instead of markers because some markers don't show up well)
leg_labels = []
handles = []
for popIndex, popLabel in enumerate(popLabels):
if legendLabels:
leg_labels.append(legendLabels[popIndex])
else:
leg_labels.append(popLabel)
handles.append(mpatches.Rectangle((0, 0), 1, 1, fc=popColors[popLabel]))
# Set up the default legend settings
legendKwargs = {}
legendKwargs['title'] = 'Populations'
legendKwargs['bbox_to_anchor'] = (1.025, 1)
legendKwargs['loc'] = 2
legendKwargs['borderaxespad'] = 0.0
legendKwargs['handlelength'] = 0.5
legendKwargs['fontsize'] = 'small'
# Add the legend
rasterPlotter.addLegend(handles, leg_labels, **legendKwargs, **kwargs)
# Adjust the plot to make room for the legend
rightOffset = 0.8
maxLabelLen = max([len(label) for label in popLabels])
rasterPlotter.fig.subplots_adjust(right=(rightOffset - 0.012 * maxLabelLen))
# Generate the figure
rasterPlot = rasterPlotter.plot(**axisArgs, **kwargs)
# Default is to return the figure, but you can also return the metaFig
if returnPlotter:
return metaFig
else:
return rasterPlot