Source code for netpyne.plotting.plotSpikeFreq

# Generate a spike frequency plot

from netpyne import __gui__
if __gui__:
    import matplotlib.patches as mpatches
import numpy as np
from numbers import Number
from ..analysis.utils import exception, _smooth1d
from ..analysis.tools import loadData
from .plotter import LinesPlotter


[docs] @exception def plotSpikeFreq( freqData=None, axis=None, timeRange=None, popNumCells=None, popLabels=None, popColors=None, binSize=5, norm=False, smooth=None, filtFreq=None, filtOrder=3, legend=True, colorList=None, returnPlotter=False, **kwargs ): """Function to produce a line plot of cell spiking frequency NetPyNE Options --------------- include : str, int, list Cells and/or NetStims to return information from. *Default:* ``['allCells', 'eachPop']`` includes average of all cells and each population of cells *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 ---------- freqData : 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 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. binSize : int Size of bin in ms to use for spike histogram. *Default:* ``5`` norm : bool Whether to normalize the data. *Default:* ``False`` smooth : int Window width for smoothing. *Default:* ``None`` does not smooth the data filtFreq : int or list Frequency for low-pass filter (int) or frequencies for bandpass filter in a list: [low, high] *Default:* ``None`` does not filter the data filtOrder : int Order of the filter defined by `filtFreq`. *Default:* ``3`` 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. 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 linewidth : int line width alpha : float line opacity (0-1) Returns ------- freqPlot : *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 freqData is None: if 'sim' not in kwargs: from .. import sim else: sim = kwargs['sim'] freqData = sim.analysis.prepareSpikeHist(timeRange=timeRange, binSize=binSize, **kwargs) # Ensure that include is a list if it is in kwargs if 'include' in kwargs: include = kwargs['include'] else: include = ['eachPop', 'allCells'] print('Plotting spike frequency...') # If input is a file name, load data from the file if type(freqData) == str: freqData = loadData(freqData) # If input is a dictionary, pull the data out of it if type(freqData) == dict: spkTimes = freqData['spkTimes'] spkInds = freqData['spkInds'] if not popNumCells: popNumCells = freqData.get('popNumCells') if not popLabels: popLabels = freqData.get('popLabels') numNetStims = freqData.get('numNetStims', 0) axisArgs = freqData.get('axisArgs') axisArgs['ylabel'] = 'Spike frequency (Hz)' legendLabels = freqData.get('legendLabels') # If input is a list or tuple, the first item is spike times, the second is spike indices elif type(freqData) == list or type(freqData) == tuple: spkTimes = freqData[0] spkInds = freqData[1] axisArgs = None legendLabels = None # If there is a third item, it should be popNumCells if not popNumCells: try: popNumCells = freqData[2] except: pass # If there is a fourth item, it should be popLabels if not popLabels: try: popLabels = freqData[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 plotSpikeHist, popNumCells (' + str(len(popNumCells)) + ') and popLabels (' + str(len(popLabels)) + ') must be the same size' ) # Replace 'eachPop' with list of pops if 'eachPop' in include: include.remove('eachPop') for popLabel in popLabels: include.append(popLabel) # 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 # Create a list to link cells to their populations indPop = [] popGids = [] for popLabel, popNumCell in zip(popLabels, popNumCells): popGids.append(np.arange(len(indPop), len(indPop) + int(popNumCell))) indPop.extend(int(popNumCell) * [popLabel]) # Create a dictionary to link cells to their population cellGids = list(set(spkInds)) gidPops = {cellGid: indPop[cellGid] for cellGid in cellGids} # Set the time range appropriately if 'timeRange' in freqData: timeRange = freqData['timeRange'] if timeRange is None: timeRange = [0, np.ceil(max(spkTimes))] # Bin the data using Numpy histoData = np.histogram(spkTimes, bins=np.arange(timeRange[0], timeRange[1], binSize)) histoBins = histoData[1] histoCount = histoData[0] histoTime = histoData[1][:-1] + binSize / 2 # Convert to firing frequency histoCount = histoCount * (1000.0 / binSize) / (len(cellGids) + numNetStims) # Optionally filter if filtFreq: from scipy import signal fs = 1000.0 / binSize nyquist = fs / 2.0 if isinstance(filtFreq, list): # bandpass Wn = [filtFreq[0] / nyquist, filtFreq[1] / nyquist] b, a = signal.butter(filtOrder, Wn, btype='bandpass') elif isinstance(filtFreq, Number): # lowpass Wn = filtFreq / nyquist b, a = signal.butter(filtOrder, Wn) histoCount = signal.filtfilt(b, a, histoCount) # Optionally normalize if norm: histoCount /= max(histoCount) # Optionally smooth if smooth: histoCount = _smooth1d(histoCount, smooth)[: len(histoT)] # Create a dictionary with the inputs for a lines plot plotData = {} plotData['x'] = histoTime plotData['y'] = histoCount plotData['color'] = popColors plotData['marker'] = None plotData['markersize'] = None plotData['linewidth'] = 1.0 plotData['alpha'] = 1.0 # If a kwarg matches a histogram input key, use the kwarg value instead of the default for kwarg in list(kwargs.keys()): if kwarg in plotData: plotData[kwarg] = kwargs[kwarg] kwargs.pop(kwarg) # Create a dictionary to hold axis inputs if not axisArgs: axisArgs = {} axisArgs['title'] = 'Spike frequency' axisArgs['xlabel'] = 'Time (ms)' axisArgs['ylabel'] = 'Frequency (Hz)' axisArgs['xlim'] = timeRange axisArgs['ylim'] = None # 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 linesPlotter = LinesPlotter(data=plotData, kind='spikeFreq', axis=axis, **axisArgs, **kwargs) metaFig = linesPlotter.metafig # 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) labels = [] handles = [] # Deal with the sum of all population spiking (allCells) if 'allCells' not in include: linesPlotter.y = [] linesPlotter.color = [] else: linesPlotter.y = [linesPlotter.y] allCellsColor = 'black' if 'allCellsColor' in kwargs: allCellsColor = kwargs['allCellsColor'] linesPlotter.color = [allCellsColor] labels.append('All cells') handles.append(mpatches.Rectangle((0, 0), 1, 1, fc=allCellsColor)) # Handle individual pops and grouped pops for subset in include: # if it's a single population if type(subset) not in [list, tuple]: for popIndex, popLabel in enumerate(popLabels): if popLabel == subset: # Get GIDs for this population currentGids = popGids[popIndex] # Use GIDs to get a spiketimes list for this population try: spkinds, spkts = list( zip(*[(spkgid, spkt) for spkgid, spkt in zip(spkInds, spkTimes) if spkgid in currentGids]) ) except: spkinds, spkts = [], [] # Bin the data using Numpy histoData = np.histogram(spkts, bins=np.arange(timeRange[0], timeRange[1], binSize)) histoCount = histoData[0] # Convert to firing frequency histoCount = histoCount * (1000.0 / binSize) / (len(currentGids)) # Optionally filter if filtFreq: from scipy import signal fs = 1000.0 / binSize nyquist = fs / 2.0 if isinstance(filtFreq, list): # bandpass Wn = [filtFreq[0] / nyquist, filtFreq[1] / nyquist] b, a = signal.butter(filtOrder, Wn, btype='bandpass') elif isinstance(filtFreq, Number): # lowpass Wn = filtFreq / nyquist b, a = signal.butter(filtOrder, Wn) histoCount = signal.filtfilt(b, a, histoCount) # Optionally normalize if norm: histoCount /= max(histoCount) # Optionally smooth if smooth: histoCount = _smooth1d(histoCount, smooth)[: len(histoT)] # Append the population spiketimes list to linesPlotter.x linesPlotter.y.append(histoCount) # Append the population color to linesPlotter.color linesPlotter.color.append(popColors[popLabel]) # Append the legend labels and handles if legendLabels: labels.append(legendLabels[popIndex]) else: labels.append(popLabel) handles.append(mpatches.Rectangle((0, 0), 1, 1, fc=popColors[popLabel])) # if it's a group of populations else: allGids = [] groupLabel = None groupColor = None for popIndex, popLabel in enumerate(popLabels): if popLabel in subset: # Get GIDs for this population currentGids = popGids[popIndex] allGids.extend(currentGids) if not groupLabel: groupLabel = popLabel else: groupLabel += ', ' + popLabel if not groupColor: groupColor = popColors[popLabel] # Use GIDs to get a spiketimes list for this population try: spkinds, spkts = list( zip(*[(spkgid, spkt) for spkgid, spkt in zip(spkInds, spkTimes) if spkgid in allGids]) ) except: spkinds, spkts = [], [] # Bin the data using Numpy histoData = np.histogram(spkts, bins=np.arange(timeRange[0], timeRange[1], binSize)) histoCount = histoData[0] # Convert to firing frequency histoCount = histoCount * (1000.0 / binSize) / (len(allGids)) # Optionally filter if filtFreq: from scipy import signal fs = 1000.0 / binSize nyquist = fs / 2.0 if isinstance(filtFreq, list): # bandpass Wn = [filtFreq[0] / nyquist, filtFreq[1] / nyquist] b, a = signal.butter(filtOrder, Wn, btype='bandpass') elif isinstance(filtFreq, Number): # lowpass Wn = filtFreq / nyquist b, a = signal.butter(filtOrder, Wn) histoCount = signal.filtfilt(b, a, histoCount) # Optionally normalize if norm: histoCount /= max(histoCount) # Optionally smooth if smooth: histoCount = _smooth1d(histoCount, smooth)[: len(histoT)] # Append the population spiketimes list to linesPlotter.x linesPlotter.y.append(histoCount) # Append the population color to linesPlotter.color linesPlotter.color.append(groupColor) # Append the legend labels and handles labels.append(groupLabel) handles.append(mpatches.Rectangle((0, 0), 1, 1, fc=groupColor)) # 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 legend if legend: # Add the legend linesPlotter.addLegend(handles, labels, **legendKwargs, **kwargs) # Adjust the plot to make room for the legend rightOffset = 0.8 maxLabelLen = max([len(label) for label in popLabels]) linesPlotter.fig.subplots_adjust(right=(rightOffset - 0.012 * maxLabelLen)) # Generate the figure freqPlot = linesPlotter.plot(**axisArgs, **kwargs) # Default is to return the figure, but you can also return the plotter if returnPlotter: return metaFig else: return freqPlot