Newer
Older
/*
------------------------------------------------------------------
This file is part of the Open Ephys GUI
Copyright (C) 2016 Open Ephys
------------------------------------------------------------------
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include "SpikeDetector.h"
: GenericProcessor ("Spike Detector")
, overflowBuffer (2, 100)
, dataBuffer (nullptr),
overflowBufferSize (100)
, currentElectrode (-1)
, uniqueID (0)
setProcessorType (PROCESSOR_TYPE_FILTER);
electrodeTypes.add ("single electrode");
electrodeTypes.add ("stereotrode");
electrodeTypes.add ("tetrode");
//// the technically correct form (Greek cardinal prefixes):
// electrodeTypes.add("hentrode");
// electrodeTypes.add("duotrode");
// electrodeTypes.add("triode");
// electrodeTypes.add("tetrode");
// electrodeTypes.add("pentrode");
// electrodeTypes.add("hextrode");
// electrodeTypes.add("heptrode");
// electrodeTypes.add("octrode");
// electrodeTypes.add("enneatrode");
// electrodeTypes.add("decatrode");
// electrodeTypes.add("hendecatrode");
// electrodeTypes.add("dodecatrode");
// electrodeTypes.add("triskaidecatrode");
// electrodeTypes.add("tetrakaidecatrode");
// electrodeTypes.add("pentakaidecatrode");
// electrodeTypes.add("hexadecatrode");
// electrodeTypes.add("heptakaidecatrode");
// electrodeTypes.add("octakaidecatrode");
// electrodeTypes.add("enneakaidecatrode");
// electrodeTypes.add("icosatrode");
for (int i = 0; i < electrodeTypes.size() + 1; ++i)
electrodeCounter.add (0);
SpikeDetector::~SpikeDetector()
{
}
AudioProcessorEditor* SpikeDetector::createEditor()
{
editor = new SpikeDetectorEditor (this, true);
void SpikeDetector::createSpikeChannels()
{
for (int i = 0; i < electrodes.size(); ++i)
{
SimpleElectrode* elec = electrodes[i];
unsigned int nChans = elec->numChannels;
Array<const DataChannel*> chans;
for (int c = 0; c < nChans; c++)
{
chans.add(getDataChannel(elec->channels[c]));
}
SpikeChannel* spk = new SpikeChannel(SpikeChannel::typeFromNumChannels(nChans), this, chans);
spk->setNumSamples(elec->prePeakSamples, elec->postPeakSamples);
spikeChannelArray.add(spk);
}
}
void SpikeDetector::updateSettings()
{
if (getNumInputs() > 0)
{
overflowBuffer.setSize(getNumInputs(), overflowBufferSize);
overflowBuffer.clear();
}
bool SpikeDetector::addElectrode (int nChans, int electrodeID)
{
std::cout << "Adding electrode with " << nChans << " channels." << std::endl;
int firstChan;
if (electrodes.size() == 0)
{
firstChan = 0;
}
else
{
SimpleElectrode* e = electrodes.getLast();
firstChan = *(e->channels + (e->numChannels - 1)) + 1;
}
if (firstChan + nChans > getNumInputs())
{
firstChan = 0; // make sure we don't overflow available channels
}
int currentVal = electrodeCounter[nChans];
electrodeCounter.set (nChans, ++currentVal);
String electrodeName;
// hard-coded for tetrode configuration
if (nChans < 3)
electrodeName = electrodeTypes[nChans - 1];
electrodeName = electrodeTypes[nChans - 2];
String newName = electrodeName.substring (0,1);
electrodeName = electrodeName.substring (1, electrodeName.length());
newName += electrodeName;
newName += " ";
newName += electrodeCounter[nChans];
SimpleElectrode* newElectrode = new SimpleElectrode;
newElectrode->name = newName;
newElectrode->numChannels = nChans;
newElectrode->prePeakSamples = 8;
newElectrode->postPeakSamples = 32;
newElectrode->thresholds.malloc (nChans);
newElectrode->isActive.malloc (nChans);
newElectrode->channels.malloc (nChans);
newElectrode->isMonitored = false;
for (int i = 0; i < nChans; ++i)
*(newElectrode->channels + i) = firstChan+i;
*(newElectrode->thresholds + i) = getDefaultThreshold();
*(newElectrode->isActive + i) = true;
if (electrodeID > 0)
{
newElectrode->electrodeID = electrodeID;
uniqueID = std::max (uniqueID, electrodeID);
}
else
{
newElectrode->electrodeID = ++uniqueID;
}
resetElectrode (newElectrode);
electrodes.add (newElectrode);
currentElectrode = electrodes.size() - 1;
float SpikeDetector::getDefaultThreshold() const
StringArray SpikeDetector::getElectrodeNames() const
for (int i = 0; i < electrodes.size(); ++i)
names.add (electrodes[i]->name);
void SpikeDetector::resetElectrode (SimpleElectrode* e)
{
e->lastBufferIndex = 0;
}
bool SpikeDetector::removeElectrode (int index)
{
// std::cout << "Spike detector removing electrode" << std::endl;
if (index > electrodes.size() || index < 0)
return false;
electrodes.remove (index);
void SpikeDetector::setElectrodeName (int index, String newName)
electrodes[index - 1]->name = newName;
void SpikeDetector::setChannel (int electrodeIndex, int channelNum, int newChannel)
{
std::cout << "Setting electrode " << electrodeIndex << " channel " << channelNum
<< " to " << newChannel << std::endl;
*(electrodes[electrodeIndex]->channels + channelNum) = newChannel;
int SpikeDetector::getNumChannels (int index) const
{
if (index < electrodes.size())
return electrodes[index]->numChannels;
else
return 0;
}
int SpikeDetector::getChannel (int index, int i) const
return *(electrodes[index]->channels + i);
void SpikeDetector::getElectrodes (Array<SimpleElectrode*>& electrodeArray)
electrodeArray.addArray (electrodes);
SimpleElectrode* SpikeDetector::setCurrentElectrodeIndex (int i)
jassert (i >= 0 & i < electrodes.size());
SimpleElectrode* SpikeDetector::getActiveElectrode() const
{
if (electrodes.size() == 0)
return nullptr;
return electrodes[currentElectrode];
}
void SpikeDetector::setChannelActive (int electrodeIndex, int subChannel, bool active)
{
currentElectrode = electrodeIndex;
currentChannelIndex = subChannel;
std::cout << "Setting channel active to " << active << std::endl;
if (active)
setParameter (98, 1);
setParameter (98, 0);
bool SpikeDetector::isChannelActive (int electrodeIndex, int i)
return *(electrodes[electrodeIndex]->isActive + i);
void SpikeDetector::setChannelThreshold (int electrodeNum, int channelNum, float thresh)
{
currentElectrode = electrodeNum;
currentChannelIndex = channelNum;
std::cout << "Setting electrode " << electrodeNum << " channel threshold " << channelNum << " to " << thresh << std::endl;
setParameter (99, thresh);
double SpikeDetector::getChannelThreshold(int electrodeNum, int channelNum) const
return *(electrodes[electrodeNum]->thresholds + channelNum);
void SpikeDetector::setParameter (int parameterIndex, float newValue)
{
//editor->updateParameterButtons(parameterIndex);
if (parameterIndex == 99 && currentElectrode > -1)
{
*(electrodes[currentElectrode]->thresholds + currentChannelIndex) = newValue;
}
else if (parameterIndex == 98 && currentElectrode > -1)
{
if (newValue == 0.0f)
*(electrodes[currentElectrode]->isActive + currentChannelIndex) = false;
*(electrodes[currentElectrode]->isActive + currentChannelIndex) = true;
}
}
bool SpikeDetector::enable()
{
sampleRateForElectrode = (uint16_t) getSampleRate();
useOverflowBuffer.clear();
for (int i = 0; i < electrodes.size(); ++i)
useOverflowBuffer.add (false);
bool SpikeDetector::disable()
{
for (int n = 0; n < electrodes.size(); ++n)
resetElectrode (electrodes[n]);
}
return true;
}
void SpikeDetector::addWaveformToSpikeObject (SpikeEvent::SpikeBuffer& s,
int& peakIndex,
int& electrodeNumber,
int& currentChannel)
int spikeLength = electrodes[electrodeNumber]->prePeakSamples
+ electrodes[electrodeNumber]->postPeakSamples;
const int chan = *(electrodes[electrodeNumber]->channels + currentChannel);
if (isChannelActive (electrodeNumber, currentChannel))
for (int sample = 0; sample < spikeLength; ++sample)
s.set(currentChannel,sample, getNextSample (*(electrodes[electrodeNumber]->channels+currentChannel)));
//std::cout << currentIndex << std::endl;
}
}
else
{
for (int sample = 0; sample < spikeLength; ++sample)
{
// insert a blank spike if the
//std::cout << currentIndex << std::endl;
}
}
sampleIndex -= spikeLength; // reset sample index
}
void SpikeDetector::process (AudioSampleBuffer& buffer)
// cycle through electrodes
SimpleElectrode* electrode;
dataBuffer = &buffer;
//std::cout << dataBuffer.getMagnitude(0,nSamples) << std::endl;
for (int i = 0; i < electrodes.size(); ++i)
{
// std::cout << "ELECTRODE " << i << std::endl;
electrode = electrodes[i];
// refresh buffer index for this electrode
sampleIndex = electrode->lastBufferIndex - 1; // subtract 1 to account for
// increment at start of getNextSample()
const int nSamples = getNumSamples (*electrode->channels);
while (samplesAvailable (nSamples))
for (int chan = 0; chan < electrode->numChannels; ++chan)
{
// std::cout << " channel " << chan << std::endl;
if (*(electrode->isActive + chan))
int currentChannel = *(electrode->channels + chan);
if (-getNextSample (currentChannel) > *(electrode->thresholds + chan)) // trigger spike
{
//std::cout << "Spike detected on electrode " << i << std::endl;
// find the peak
int peakIndex = sampleIndex;
while (-getCurrentSample(currentChannel) < -getNextSample(currentChannel)
&& sampleIndex < peakIndex + electrode->postPeakSamples)
}
peakIndex = sampleIndex;
sampleIndex -= (electrode->prePeakSamples + 1);
const SpikeChannel* spikeChan = getSpikeChannel(i);
SpikeEvent::SpikeBuffer spikeData(spikeChan);
Array<float> thresholds;
for (int channel = 0; channel < electrode->numChannels; ++channel)
{
addWaveformToSpikeObject(spikeData,
peakIndex,
i,
channel);
thresholds.add((int)*(electrode->thresholds + channel));
}
int64 timestamp = getTimestamp(electrode->channels[0]) + peakIndex;
SpikeEventPtr newSpike = SpikeEvent::createSpikeEvent(spikeChan, timestamp, thresholds, spikeData, 0);
addSpike(spikeChan, newSpike, peakIndex);
// advance the sample index
sampleIndex = peakIndex + electrode->postPeakSamples;
// quit spike "for" loop
break;
// end spike trigger
}
// end if channel is active
}
// end cycle through channels on electrode
}
// end cycle through samples
}
electrode->lastBufferIndex = sampleIndex - nSamples; // should be negative
if (nSamples > overflowBufferSize)
{
for (int j = 0; j < electrode->numChannels; ++j)
overflowBuffer.copyFrom (*electrode->channels+j,
0,
buffer,
*electrode->channels + j,
nSamples-overflowBufferSize,
overflowBufferSize);
useOverflowBuffer.set (i, true);
useOverflowBuffer.set (i, false);
// end cycle through electrodes
}
float SpikeDetector::getNextSample (int& chan)
{
const int ind = overflowBufferSize + sampleIndex;
if (ind < overflowBuffer.getNumSamples())
return *overflowBuffer.getWritePointer (chan, ind);
else
return 0;
}
else
{
if (sampleIndex < getNumSamples(chan))
return *dataBuffer->getWritePointer (chan, sampleIndex);
else
return 0;
}
}
float SpikeDetector::getCurrentSample (int& chan)
{
return *overflowBuffer.getWritePointer (chan, overflowBufferSize + sampleIndex - 1);
return *dataBuffer->getWritePointer (chan, sampleIndex - 1);
bool SpikeDetector::samplesAvailable (int nSamples)
{
if (sampleIndex > nSamples - overflowBufferSize/2)
{
return false;
}
else
{
return true;
}
}
void SpikeDetector::saveCustomParametersToXml (XmlElement* parentElement)
for (int i = 0; i < electrodes.size(); ++i)
XmlElement* electrodeNode = parentElement->createNewChildElement ("ELECTRODE");
electrodeNode->setAttribute ("name", electrodes[i]->name);
electrodeNode->setAttribute ("numChannels", electrodes[i]->numChannels);
electrodeNode->setAttribute ("prePeakSamples", electrodes[i]->prePeakSamples);
electrodeNode->setAttribute ("postPeakSamples", electrodes[i]->postPeakSamples);
electrodeNode->setAttribute ("electrodeID", electrodes[i]->electrodeID);
for (int j = 0; j < electrodes[i]->numChannels; ++j)
XmlElement* channelNode = electrodeNode->createNewChildElement ("SUBCHANNEL");
channelNode->setAttribute ("ch", *(electrodes[i]->channels + j));
channelNode->setAttribute ("thresh", *(electrodes[i]->thresholds + j));
channelNode->setAttribute ("isActive", *(electrodes[i]->isActive + j));
void SpikeDetector::loadCustomParametersFromXml()
{
if (parametersAsXml != nullptr) // prevent double-loading
{
// use parametersAsXml to restore state
SpikeDetectorEditor* sde = (SpikeDetectorEditor*) getEditor();
int electrodeIndex = -1;
forEachXmlChildElement (*parametersAsXml, xmlNode)
if (xmlNode->hasTagName ("ELECTRODE"))
++electrodeIndex;
std::cout << "ELECTRODE>>>" << std::endl;
const int channelsPerElectrode = xmlNode->getIntAttribute ("numChannels");
const int electrodeID = xmlNode->getIntAttribute ("electrodeID");
sde->addElectrode (channelsPerElectrode, electrodeID);
setElectrodeName (electrodeIndex + 1, xmlNode->getStringAttribute ("name"));
sde->refreshElectrodeList();
int channelIndex = -1;
forEachXmlChildElement (*xmlNode, channelNode)
if (channelNode->hasTagName ("SUBCHANNEL"))
++channelIndex;
std::cout << "Subchannel " << channelIndex << std::endl;
setChannel (electrodeIndex, channelIndex, channelNode->getIntAttribute ("ch"));
setChannelThreshold (electrodeIndex, channelIndex, channelNode->getDoubleAttribute ("thresh"));
setChannelActive (electrodeIndex, channelIndex, channelNode->getBoolAttribute ("isActive"));