Building a spike simulation pipeline

Now that we have used the SpikeProcessing class directly to perform spike detection, we are now going to build another pipeline.
Instead of generating random signals, we are going to simulate our own neural signals and add some artifacts! To plot the signals we are again using the pipeline.MplSweepDataDisplay class that we have used previously in example10.
To accomplish this task we first need to generate some random background noise and initialize the MCsweep object with it. After that, it is business as usual. All the objects that we need for this task are already there. We just have to connect them to build our pipeline.
A new object that we have not used is an object that contains the stimulation parameter that we need to simulate stimulation artifacts.
Yet again, we are building this pipeline step by step.

Generating an MCsweep object containing background noise is achieved by these lines:

sampleRate = 24414
numChannels = 32
sweepLen = int(sampleRate/2)

#generate random background noise
sigTupel=[tuple(0.000015*np.random.normal(0,1.0, numChannels*sweepLen))]
data=RZ2ephys.MCsweep(sigTupel,sweepLen,sampleRate,0)

And as before, we are going to generate multiple MCsweep objects and then plot the results.

Our first pipeline is very similar to example9. All we do here is setting up the pipeline to generate, filter, and simulate the data. Furthermore, here we are only going to simulate spike data. The artifacts will be added in the next pipeline.
This is the working code:

def example14():
    sampleRate = 24414
    numChannels = 32
    sweepLen = int(sampleRate/2)
    aDatasource = pipeline.Datasource()  
    
    aDatasource.setName("Eyphs PushDataSource")
    sweepSpikeFiltFiltPlugin = pipeline.SweepSpikeFiltFiltPlugin()
    sweepSpikeFiltFiltPlugin.setName("Eyphs SweepSpikeFiltFiltPlugin")
    
    aSpikeSimulationPlugin = pipeline.SpikeSimulationPlugin()   
    aSpikeSimulationPlugin.setName("Eyphs SpikeSimulationPlugin")
    aSpikeSimulationPlugin.loadSpikeTemplates("FilteredSpikeTemplates.npy")
    debugSpikeAmplitudes = [0.0001, 0.0001, 0.0001, 0.0001]
    debugSpikePositions = [0.1, 0.2, 0.3, 0.4]
    aSpikeSimulationPlugin.setDebug(False)
    aSpikeSimulationPlugin.setDebugSpikeAmplitudes(debugSpikeAmplitudes)
    aSpikeSimulationPlugin.setDebugSpikePositions(debugSpikePositions)
    
    aDisplay = pipeline.MplSweepDataDisplay(plt)
    
    aDatasink = pipeline.Datasink()
    aDatasink.setName("Eyphs DataSink")

    #assemble the pipeline
    aDatasource.setOutput(sweepSpikeFiltFiltPlugin)
    sweepSpikeFiltFiltPlugin.setOutput(aSpikeSimulationPlugin)
    aSpikeSimulationPlugin.setOutput(aDisplay)
    aDisplay.setOutput(aDatasink)
    
    for n in range(100):
        #generate multichannel data and random background noise
        sigTupel=tuple(0.000015*np.random.normal(0,1.0, numChannels*sweepLen))
        data=RZ2ephys.MCsweep(sigTupel,sweepLen,sampleRate,0)
        aDataframe = pipeline.Dataframe()
        aDataframe.setFrameNumber(n)
        aDataframe.setData(data)
        aDatasource.addDataframe(aDataframe)
        aDatasource.run()

The program structure is virtually identical to example9. However, the pipeline.SpikeSimulationPlugin() is new and deserves some explanation. The point of this class is to stimulate neural signals if no actual recording can be performed and or if the recording hardware is not available. A software simulation makes it possible to develop and test software that processes electrophysiological data without loading or recording actual data. A simulation also has the advantage of giving full control over various spike parameters which is very useful for developing and testing spike detection and sorting algorithms.  The SpikeSimulationPlugin has two modes of operation -- a regular and a debug mode. In the regular mode, the simulation is simply generating artificial spikes per channel with random amplitude and then adding them to the already existing signals. In our case, this just simulated background noise. However, in debug mode, we need to load actual spike templates, because some spike detection algorithms work better with real rather than simulated spikes. Furthermore, we can add spikes at specific points in time and positions. This is E.g. super helpful for checking if the spike times extracted by some detection algorithm are correct. Usually, the maximum number of spikes is set to a default of 10 spikes. But this value can be changed using setMaxNumSpikes() if needed.

Before adding the artifact simulation, here is the output of example14()

simulated spike signal
Fig24: This is a plot of a simulated 32 channel neural signal.

Let's continue with the code regarding the simulated electrical stimulation. For that, we have to add the relevant stimulation parameters and initialize the stimulation parameter object with them. For this example, we are using the clickTrainObject() from the ClickTrainLibrary. Note, that the ClickTrainLibrary offers various other objects and you need to take the one that fits the purpose of your experiment. We are creating a basic example here, so the clickTrainObject() will do the job. if you go all the way back to Fig5, you will see the role of the stimulation parameter object. You will also notice that we are going to attach it to the data frame.

import ClickTrainLibrary as stimulatorModule
    #set stimulation parameters     
    level_dB=10  # in dB in relation to 100 uA
    clickRate=50  # 300, 900, 1800
    ears='left' # other options are  'right' and 'both' Must be set to 'left' or 'right' during simulation
    duration = 0.001 # This defines the duration of the stimulation
    numClicks = 1
    nloop = 0
    reference_Simulation=0.100 # set reference to 100 uA
    levelRePeak = True  # normally we set levels relative to RMS If you want to set them relative to abs max instead, set this to True
    clickShape_CI_Simulation = np.array([0,1,0,-1,0]) # use a biphasic pulse
    
    #set up the stimulation parameter object    
    stimulatorModule.soundPlayer=None
    stim=stimulatorModule.clickTrainObject()
    stim.clickShape= clickShape_CI_Simulation
    stim.stimParams['duration (s)']=duration # this parameter controls the duration of the stimulation.
    stim.stimParams['ABL (dB)']=level_dB #ABL means Average Binaural Level
    stim.stimParams['clickRate (Hz)']=clickRate
    stim.stimParams['Nloop']=nloop
    stim.stimParams['numClicks']=numClicks
    stim.reference=reference
    stim.clickShape=ci_clickshape
    stim.levelRePeak=levelRePeak
    stim.ready()  

The artifact simulation is added by the ArtifactSimulationPlugin.

    aArtifactSimulationPlugin = pipeline.ArtifactSimulationPlugin()
    aArtifactSimulationPlugin.setName("Eyphs ArtifactSimulationPlugin")
    aArtifactSimulationPlugin.setEnabled(enableCISimulation)
    aSpikeSimulationPlugin = pipeline.SpikeSimulationPlugin()

Like all plugins, it can be enabled or disabled by setting setEnabled() to True or False
After adding the new features to example 14 we get some new code:

import ClickTrainLibrary as stimulatorModule
import copy        
def example15():
    sampleRate = 24414
    numChannels = 32
    sweepLen = int(sampleRate/2)
    enableCISimulation = True
    aDatasource = pipeline.Datasource()  
    
    aDatasource.setName("Eyphs PushDataSource")
    sweepSpikeFiltFiltPlugin = pipeline.SweepSpikeFiltFiltPlugin()
    sweepSpikeFiltFiltPlugin.setName("Eyphs SweepSpikeFiltFiltPlugin")
    
    aSpikeSimulationPlugin = pipeline.SpikeSimulationPlugin()   
    aSpikeSimulationPlugin.setName("Eyphs SpikeSimulationPlugin")
    aSpikeSimulationPlugin.loadSpikeTemplates("FilteredSpikeTemplates.npy")
    debugSpikeAmplitudes = [0.0001, 0.0001, 0.0001, 0.0001]
    debugSpikePositions = [0.1, 0.2, 0.3, 0.4]
    aSpikeSimulationPlugin.setDebug(False)
    aSpikeSimulationPlugin.setDebugSpikeAmplitudes(debugSpikeAmplitudes)
    aSpikeSimulationPlugin.setDebugSpikePositions(debugSpikePositions)
    
    aArtifactSimulationPlugin = pipeline.ArtifactSimulationPlugin()
    aArtifactSimulationPlugin.setName("Eyphs ArtifactSimulationPlugin")
    aArtifactSimulationPlugin.setEnabled(enableCISimulation)
    aSpikeSimulationPlugin = pipeline.SpikeSimulationPlugin()
    
    aDisplay = pipeline.MplSweepDataDisplay(plt)
    
    aDatasink = pipeline.Datasink()
    aDatasink.setName("Eyphs DataSink")

    #assemble the pipeline
    aDatasource.setOutput(sweepSpikeFiltFiltPlugin)
    sweepSpikeFiltFiltPlugin.setOutput(aSpikeSimulationPlugin)
    aSpikeSimulationPlugin.setOutput(aArtifactSimulationPlugin)
    aArtifactSimulationPlugin.setOutput(aDisplay)
    aDisplay.setOutput(aDatasink)
 
        #set up the stimulation parameter object    
    #set stimulation parameters     
    level_dB=5  # in dB in relation to 100 uA
    clickRate=50  # 300, 900, 1800
    ears='left' # other options are  'right' and 'both' Must be set to 'left' or 'right' during simulation
    duration = 0.01 # This defines the duration of the stimulation
    numClicks = 1
    nloop = 0
    reference_Simulation=0.100 # set reference to 100 uA
    levelRePeak = True  # normally we set levels relative to RMS If you want to set them relative to abs max instead, set this to True
    clickShape_CI_Simulation = np.array([0,1,0,-1,0]) # use a biphasic pulse
    
    #set up the stimulation parameter object    
    stimulatorModule.soundPlayer=stimulatorModule.pygameSoundHardware()
    stimulatorModule.soundPlayer.sampleRate = 25000
    stim=stimulatorModule.clickTrainObject()
    stim.clickShape= clickShape_CI_Simulation
    stim.stimParams['duration (s)']=duration # this parameter controls the duration of the stimulation.
    stim.stimParams['ABL (dB)']=level_dB #ABL means Average Binaural Level
    stim.stimParams['clickRate (Hz)']=clickRate
    stim.stimParams['Nloop']=nloop
    stim.stimParams['numClicks']=numClicks
    stim.reference=reference_Simulation
    stim.clickShape=clickShape_CI_Simulation
    stim.levelRePeak=levelRePeak
    stim.ears=ears
    stim.ready()    
 
    
    for n in range(100):
        #generate multichannel data and random background noise
        sigTupel=tuple(0.000015*np.random.normal(0,1.0, numChannels*sweepLen))
        data=RZ2ephys.MCsweep(sigTupel,sweepLen,sampleRate,0)
        stimObjectCopy = copy.deepcopy(stim)
        aDataframe = pipeline.Dataframe()
        aDataframe.setStimObject(stimObjectCopy)
        aDataframe.setFrameNumber(n)
        aDataframe.setData(data)
        aDatasource.addDataframe(aDataframe)
        aDatasource.run()

And the output of the program looks like this:

simulated spikes and artifacts
Fig25: The plot shows the simulated spikes and enormously large stimulation artifacts

Where are the spikes? Did we do something wrong? No! The stimulation artifacts are so enormously large that the spikes are simply not visible because the plot automatically scales to the largest amplitude -- the stimulation artifact.
So what can we do? We need to get rid of the artifact. And the simplest method is called blanking which for us simply means deleting the part of the signal that is contaminated with the stimulation artifacts. After that step, we will see the spikes again! The blanking is already available as a plugin. All we need to do is adding it to the pipeline.