diff --git a/Source/Processors/Channel.cpp b/Source/Processors/Channel.cpp index 19bf8e2222b08e06384104f381e00e8442c1a768..210ca9ac3c84c3650019fc0aaaac53c14added1e 100644 --- a/Source/Processors/Channel.cpp +++ b/Source/Processors/Channel.cpp @@ -61,6 +61,11 @@ String Channel::getName() } +void Channel::setName(String name_) +{ + name = name_; +} + void Channel::reset() { createDefaultName(); diff --git a/Source/Processors/Channel.h b/Source/Processors/Channel.h index af30c38c8cd32e3fa0225e9218625422dd049b9a..a32c6cb93161372784abad3f99a69ef582c20fca 100644 --- a/Source/Processors/Channel.h +++ b/Source/Processors/Channel.h @@ -61,6 +61,9 @@ public: /** Returns the name of a given channel. */ String getName(); + /** Sets the name of a given channel. */ + void setName(String); + /** Restores the default settings for a given channel. */ void reset(); diff --git a/Source/Processors/ChannelMappingNode.cpp b/Source/Processors/ChannelMappingNode.cpp index 05e691e6522f18f673b43dd9b453aa680644bd6b..4388f0879c3066987a902ef5e4a39202dba35e64 100644 --- a/Source/Processors/ChannelMappingNode.cpp +++ b/Source/Processors/ChannelMappingNode.cpp @@ -32,6 +32,13 @@ ChannelMappingNode::ChannelMappingNode() { referenceArray.resize(1024); // make room for 1024 channels channelArray.resize(1024); + + for (int i = 0; i < referenceArray.size(); i++) + { + referenceArray.set(i, -1); + channelArray.set(i, i); + } + } ChannelMappingNode::~ChannelMappingNode() @@ -52,7 +59,9 @@ AudioProcessorEditor* ChannelMappingNode::createEditor() void ChannelMappingNode::updateSettings() { - channelBuffer.setSize(getNumInputs(), 10000); + if (getNumInputs() > 0) + channelBuffer.setSize(getNumInputs(), 10000); + } @@ -74,10 +83,10 @@ void ChannelMappingNode::process(AudioSampleBuffer& buffer, int& nSamples) { - // copy everything into the channel buffer - channelBuffer.setDataToReferTo(buffer.getArrayOfChannels(), - buffer.getNumChannels(), - buffer.getNumSamples()); + // use copy constructor to set the data to refer to + channelBuffer = buffer; //.setDataToReferTo(buffer.getArrayOfChannels(), + // buffer.getNumChannels(), + // buffer.getNumSamples()); // copy it back into the buffer according to the channel mapping buffer.clear(); @@ -90,7 +99,7 @@ void ChannelMappingNode::process(AudioSampleBuffer& buffer, channelArray[i], // sourceChannel 0, // sourceStartSample nSamples, // numSamples - 1.0f // gain to apply to source (positive) + 1.0f // gain to apply to source (positive for original signal) ); } diff --git a/Source/Processors/DataThreads/DataThread.h b/Source/Processors/DataThreads/DataThread.h index 03a18aa30679b23f4b10f207d65d0926439a6f0e..72c4aa45cd463291169279c96526c0ca5b90a484 100755 --- a/Source/Processors/DataThreads/DataThread.h +++ b/Source/Processors/DataThreads/DataThread.h @@ -93,6 +93,9 @@ public: return 0; } + /** Changes the names of channels, if the thread needs custom names. */ + virtual void updateChannelNames() { } + SourceNode* sn; int16 eventCode; diff --git a/Source/Processors/DataThreads/RHD2000Thread.cpp b/Source/Processors/DataThreads/RHD2000Thread.cpp index ee53e742b4d7b5737504e6667011f5341da0f655..66cbc03efba3cb614591831ea6863a23f6e1110c 100644 --- a/Source/Processors/DataThreads/RHD2000Thread.cpp +++ b/Source/Processors/DataThreads/RHD2000Thread.cpp @@ -28,7 +28,7 @@ RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn), isTransmitting(fa fastSettleEnabled(false), chipRegisters(30000.0f), dspEnabled(true), boardSampleRate(30000.0f), desiredDspCutoffFreq(0.5f), desiredUpperBandwidth(7500.0f), desiredLowerBandwidth(1.0f), savedSampleRateIndex(16), audioOutputL(-1), audioOutputR(-1), dacOutputShouldChange(false), - acquireAdcChannels(false), + acquireAdcChannels(false), acquireAuxChannels(true), cableLengthPortA(0.914f), cableLengthPortB(0.914f), cableLengthPortC(0.914f), cableLengthPortD(0.914f) // default is 3 feet (0.914 m) { evalBoard = new Rhd2000EvalBoard; @@ -407,6 +407,58 @@ int RHD2000Thread::getNumChannels() return 1; // to prevent crashing with 0 channels } +void RHD2000Thread::updateChannelNames() +{ + + int chNum = -1; + + for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++) + { + + for (int j = 0; j < numChannelsPerDataStream[i]; j++) + { + chNum++; + + sn->channels[chNum]->setName(String(chNum)); + } + } + + if (acquireAuxChannels) + { + for (int i = 0; i < MAX_NUM_DATA_STREAMS; i++) + { + + for (int j = 0; j < 3; j++) + { + + chNum++; + + String chName = "AUX"; + chName += (j+1); + + // this is causing a seg fault for some reason: + // sn->channels[chNum]->setName(chName); + } + } + } + + + if (acquireAdcChannels) + { + for (int j = 0; j < 8; j++) + { + chNum++; + + String chName = "ADC"; + chName += (j+1); + + // sn->channels[chNum]->setName(chName); + } + } + +} + + int RHD2000Thread::getNumEventChannels() { return 16; // 8 inputs, 8 outputs @@ -826,6 +878,7 @@ bool RHD2000Thread::updateBuffer() int streamNumber = -1; int channel = -1; + // do the neural data channels first for (int dataStream = 0; dataStream < MAX_NUM_DATA_STREAMS; dataStream++) { if (numChannelsPerDataStream[dataStream] > 0) @@ -842,48 +895,64 @@ bool RHD2000Thread::updateBuffer() thisSample[channel] = float(value-32768)*0.195f; } - // TEMPORARILY DISABLED -- causing problems - if (samp % 4 == 1) { // every 4th sample should have auxiliary input data - - channel++; - thisSample[channel] = 0.0374 * - float(dataBlock->auxiliaryData[dataStream][1][samp+0]); - auxBuffer[channel]=thisSample[channel]; + } - channel++; - thisSample[channel] = 0.0374 * - float(dataBlock->auxiliaryData[dataStream][1][samp+1]); - auxBuffer[channel]=thisSample[channel]; + } - - channel++; - thisSample[channel] = 0.0374 * - float(dataBlock->auxiliaryData[dataStream][1][samp+2]); - auxBuffer[channel]=thisSample[channel]; + streamNumber = -1; - } else{ // repeat last values from buffer - channel++; - thisSample[channel] = auxBuffer[channel]; - channel++; - thisSample[channel] = auxBuffer[channel]; - channel++; - thisSample[channel] = auxBuffer[channel]; - } + // then do the ADC channels + for (int dataStream = 0; dataStream < MAX_NUM_DATA_STREAMS; dataStream++) + { + if (numChannelsPerDataStream[dataStream] > 0) + { + streamNumber++; + + if (samp % 4 == 1) { // every 4th sample should have auxiliary input data + + channel++; + thisSample[channel] = 0.0374 * + float(dataBlock->auxiliaryData[dataStream][1][samp+0]); + + auxBuffer[channel]=thisSample[channel]; + + channel++; + thisSample[channel] = 0.0374 * + float(dataBlock->auxiliaryData[dataStream][1][samp+1]); + + auxBuffer[channel]=thisSample[channel]; + + + channel++; + thisSample[channel] = 0.0374 * + float(dataBlock->auxiliaryData[dataStream][1][samp+2]); + + auxBuffer[channel]=thisSample[channel]; + + } else{ // repeat last values from buffer - if (acquireAdcChannels) - { - for (int adcChan = 0; adcChan < 8; ++adcChan) { - channel++; - // ADC waveform units = volts - thisSample[channel] = - 0.000050354 * float(dataBlock->boardAdcData[adcChan][samp]); - } + thisSample[channel] = auxBuffer[channel]; + channel++; + thisSample[channel] = auxBuffer[channel]; + channel++; + thisSample[channel] = auxBuffer[channel]; } - - } + } - } + } + + // finally, loop through ADC channels if necessary + if (acquireAdcChannels) + { + for (int adcChan = 0; adcChan < 8; ++adcChan) { + + channel++; + // ADC waveform units = volts + thisSample[channel] = + 0.000050354 * float(dataBlock->boardAdcData[adcChan][samp]); + } + } // std::cout << channel << std::endl; timestamp = dataBlock->timeStamp[samp]; diff --git a/Source/Processors/DataThreads/RHD2000Thread.h b/Source/Processors/DataThreads/RHD2000Thread.h index 71f77d7ceb3c249904a43f8fcdec17f2e4d07cf8..47faa406f3c77257451d7080818bf473bade459b 100644 --- a/Source/Processors/DataThreads/RHD2000Thread.h +++ b/Source/Processors/DataThreads/RHD2000Thread.h @@ -82,6 +82,8 @@ public: bool isAcquisitionActive(); + void updateChannelNames(); + private: ScopedPointer<Rhd2000EvalBoard> evalBoard; @@ -104,6 +106,7 @@ private: bool dacOutputShouldChange; bool acquireAdcChannels; + bool acquireAuxChannels; bool fastSettleEnabled; diff --git a/Source/Processors/Editors/ChannelMappingEditor.cpp b/Source/Processors/Editors/ChannelMappingEditor.cpp index 6a2d946419be5598b784db0bebae227f11327720..4f7faf24396ee598c6d1427456c8e56e1c8f1e5d 100644 --- a/Source/Processors/Editors/ChannelMappingEditor.cpp +++ b/Source/Processors/Editors/ChannelMappingEditor.cpp @@ -34,18 +34,17 @@ ChannelMappingEditor::ChannelMappingEditor(GenericProcessor* parentNode, bool us { desiredWidth = 340; - ElectrodeEditorButton* e1 = new ElectrodeEditorButton("MAPPING",Font("Small Text",14,Font::plain)); - e1->addListener(this); - addAndMakeVisible(e1); - e1->setBounds(15,110,80,10); - e1->setToggleState(true, false); - electrodeEditorButtons.add(e1); - - ElectrodeEditorButton* e2 = new ElectrodeEditorButton("REF",Font("Small Text",14,Font::plain)); - e2->addListener(this); - addAndMakeVisible(e2); - e2->setBounds(100,110,50,10); - electrodeEditorButtons.add(e2); + mappingButton = new ElectrodeEditorButton("MAPPING",Font("Small Text",14,Font::plain)); + mappingButton->addListener(this); + addAndMakeVisible(mappingButton); + mappingButton->setBounds(15,110,80,10); + mappingButton->setToggleState(true, false); + + referenceButton = new ElectrodeEditorButton("REF",Font("Small Text",14,Font::plain)); + referenceButton->addListener(this); + addAndMakeVisible(referenceButton); + referenceButton->setBounds(100,110,50,10); + referenceButton->setToggleState(false, false); channelSelector->setRadioStatus(true); @@ -104,7 +103,7 @@ void ChannelMappingEditor::createElectrodeButtons(int numNeeded) channelArray.add(i+1); - if (column%16 == 0) + if (column % 16 == 0) { column = 0; row++; @@ -119,28 +118,34 @@ void ChannelMappingEditor::createElectrodeButtons(int numNeeded) void ChannelMappingEditor::buttonEvent(Button* button) { - if (button == electrodeEditorButtons[0]) // mapping + if (button == mappingButton) // mapping { + std::cout << "Mapping button clicked." << std::endl; - electrodeEditorButtons[1]->setToggleState(false, false); + referenceButton->setToggleState(false, false); + mappingButton->setToggleState(true, false); for (int i = 0; i < electrodeButtons.size(); i++) { electrodeButtons[i]->setRadioGroupId(999); electrodeButtons[i]->setChannelNum(channelArray[i]); - electrodeButtons[i]->repaint(); + electrodeButtons[i]->repaint(); } } - else if (button == electrodeEditorButtons[1]) // reference + else if (button == referenceButton) // reference { - electrodeEditorButtons[0]->setToggleState(false, false); + + std::cout << "Reference button clicked." << std::endl; + + mappingButton->setToggleState(false, false); + referenceButton->setToggleState(true, false); for (int i = 0; i < electrodeButtons.size(); i++) { electrodeButtons[i]->setRadioGroupId(0); electrodeButtons[i]->setChannelNum(referenceArray[i]); - electrodeButtons[i]->repaint(); + electrodeButtons[i]->repaint(); } @@ -161,6 +166,9 @@ void ChannelMappingEditor::buttonEvent(Button* button) } + std::cout << "Reference button state: " << referenceButton->getToggleState() << std::endl; + std::cout << "Mapping button state: " << mappingButton->getToggleState() << std::endl; + } void ChannelMappingEditor::channelChanged(int chan) @@ -174,7 +182,7 @@ void ChannelMappingEditor::channelChanged(int chan) getProcessor()->setCurrentChannel(i); - if (electrodeEditorButtons[1]->getToggleState()) // reference + if (referenceButton->getToggleState()) // reference { referenceArray.set(i,chan); diff --git a/Source/Processors/Editors/ChannelMappingEditor.h b/Source/Processors/Editors/ChannelMappingEditor.h index 0ddde7119fa9aea18d4446c3db8ae0f5964f6b0a..46b7a499d8f9e453cccf8f63cafbab403e3c538a 100644 --- a/Source/Processors/Editors/ChannelMappingEditor.h +++ b/Source/Processors/Editors/ChannelMappingEditor.h @@ -60,7 +60,8 @@ public: private: OwnedArray<ElectrodeButton> electrodeButtons; - OwnedArray<ElectrodeEditorButton> electrodeEditorButtons; + ScopedPointer<ElectrodeEditorButton> referenceButton; + ScopedPointer<ElectrodeEditorButton> mappingButton; Array<int> channelArray; Array<int> referenceArray; diff --git a/Source/Processors/Editors/SpikeDetectorEditor.cpp b/Source/Processors/Editors/SpikeDetectorEditor.cpp index 20bfdee9d222af90d6e318903483ca9535adf8a0..92ba610036d18777affc392b3acb701230568e03 100755 --- a/Source/Processors/Editors/SpikeDetectorEditor.cpp +++ b/Source/Processors/Editors/SpikeDetectorEditor.cpp @@ -443,6 +443,8 @@ void SpikeDetectorEditor::labelTextChanged(Label* label) electrodeTypes->setText(currentText += "s"); } + getEditorViewport()->makeEditorVisible(this, false, true); + } void SpikeDetectorEditor::comboBoxChanged(ComboBox* comboBox) diff --git a/Source/Processors/Editors/VisualizerEditor.cpp b/Source/Processors/Editors/VisualizerEditor.cpp index 4ae16d93a285fad2ba61b2627773a74e8035eb6d..ff432a7bfd4031996702babf17eb177bf18be598 100755 --- a/Source/Processors/Editors/VisualizerEditor.cpp +++ b/Source/Processors/Editors/VisualizerEditor.cpp @@ -170,6 +170,7 @@ void VisualizerEditor::buttonEvent(Button* button) { canvas = createNewCanvas(); + canvas->update(); if (isPlaying) canvas->beginAnimation(); diff --git a/Source/Processors/ProcessorGraph.cpp b/Source/Processors/ProcessorGraph.cpp index 2c0a3707d99c7c6f97b1566a9214e20ea624bf3d..fa997e55df66f1858981c0896e679b295974b26a 100644 --- a/Source/Processors/ProcessorGraph.cpp +++ b/Source/Processors/ProcessorGraph.cpp @@ -34,6 +34,7 @@ #include "RecordNode.h" #include "ResamplingNode.h" #include "ReferenceNode.h" +#include "ChannelMappingNode.h" #include "AudioResamplingNode.h" #include "SignalGenerator.h" #include "SourceNode.h" @@ -503,6 +504,11 @@ GenericProcessor* ProcessorGraph::createProcessorFromDescription(String& descrip std::cout << "Creating a new digital reference." << std::endl; processor = new ReferenceNode(); } + else if (subProcessorType.equalsIgnoreCase("Channel Map")) + { + std::cout << "Creating a new channel mapping node." << std::endl; + processor = new ChannelMappingNode(); + } sendActionMessage("New filter node created."); diff --git a/Source/Processors/RecordNode.h b/Source/Processors/RecordNode.h index baadf10e0cb4acb6faae76b8355c7f86024c6409..ac44861133170b90cf7806f405a1319af4c039ce 100755 --- a/Source/Processors/RecordNode.h +++ b/Source/Processors/RecordNode.h @@ -102,14 +102,22 @@ public: */ void createNewDirectory(); + File getDataDirectory() {return rootFolder;} + /** Signals when to create a new data directory when recording starts.*/ bool newDirectoryNeeded; + bool isRecording; + + /** Generate a Matlab-compatible datestring */ + String generateDateString(); + + private: /** Keep the RecordNode informed of acquisition and record states. */ - bool isRecording, isProcessing, signalFilesShouldClose; + bool isProcessing, signalFilesShouldClose; /** User-selectable directory for saving data files. Currently defaults to the user's home directory. @@ -165,9 +173,7 @@ private: /** Generates a default directory name, based on the current date and time */ String generateDirectoryName(); - /** Generate a Matlab-compatible datestring */ - String generateDateString(); - + /** Generate filename for a given channel */ void updateFileName(Channel* ch); diff --git a/Source/Processors/SourceNode.cpp b/Source/Processors/SourceNode.cpp index 67fa66afcf3f4cb1adb8115d1525d3a0d7cb5e73..5a8b53ccc8cd2f88e386c5b6ec1b6465b62bbc46 100755 --- a/Source/Processors/SourceNode.cpp +++ b/Source/Processors/SourceNode.cpp @@ -114,6 +114,7 @@ void SourceNode::updateSettings() { inputBuffer = dataThread->getBufferAddress(); + dataThread->updateChannelNames(); std::cout << "Input buffer address is " << inputBuffer << std::endl; } @@ -125,6 +126,7 @@ void SourceNode::updateSettings() eventChannels.add(ch); } + } void SourceNode::actionListenerCallback(const String& msg) diff --git a/Source/Processors/SpikeDetector.cpp b/Source/Processors/SpikeDetector.cpp index 1a5025421829e486c36f58e726e6d71872a5ec39..7ee4348b5d249bb15f5467c8f61004af599ea905 100755 --- a/Source/Processors/SpikeDetector.cpp +++ b/Source/Processors/SpikeDetector.cpp @@ -165,7 +165,7 @@ bool SpikeDetector::addElectrode(int nChans) float SpikeDetector::getDefaultThreshold() { - return 75.0f; + return 50.0f; } StringArray SpikeDetector::getElectrodeNames() @@ -333,6 +333,8 @@ void SpikeDetector::addSpikeEvent(SpikeObject* s, MidiBuffer& eventBuffer, int p // std::cout << "Adding spike event for index " << peakIndex << std::endl; + s->eventType = SPIKE_EVENT_CODE; + int numBytes = packSpike(s, spikeBuffer, 256); eventBuffer.addEvent(spikeBuffer, numBytes, peakIndex); diff --git a/Source/Processors/SpikeDisplayNode.cpp b/Source/Processors/SpikeDisplayNode.cpp index 161aed18f00c95c9393e388042bb8a21f9a4ac9e..ce983edeb85ae85672f1c696b2821bc06da9e97e 100755 --- a/Source/Processors/SpikeDisplayNode.cpp +++ b/Source/Processors/SpikeDisplayNode.cpp @@ -101,6 +101,28 @@ int SpikeDisplayNode::getNumberOfChannelsForElectrode(int elec) return 0; } +String SpikeDisplayNode::getNameForElectrode(int elec) +{ + + int electrodeIndex = -1; + + for (int i = 0; i < eventChannels.size(); i++) + { + if (eventChannels[i]->eventType < 999) + { + electrodeIndex++; + + if (electrodeIndex == elec) + { + std::cout << "Electrode " << elec << " has " << eventChannels[i]->eventType << " channels" << std::endl; + return eventChannels[i]->name; + } + } + } + + return " "; +} + int SpikeDisplayNode::getNumElectrodes() { int nElectrodes = 0; diff --git a/Source/Processors/SpikeDisplayNode.h b/Source/Processors/SpikeDisplayNode.h index 66dcb85b44be709aece4b795320145063c9c961e..9de5055d33e4d141d33bb3d9abc418f807da3c88 100755 --- a/Source/Processors/SpikeDisplayNode.h +++ b/Source/Processors/SpikeDisplayNode.h @@ -71,7 +71,7 @@ public: return eventBuffer; } - + String getNameForElectrode(int i); int getNumberOfChannelsForElectrode(int i); int getNumElectrodes(); diff --git a/Source/Processors/Visualization/LfpDisplayCanvas.cpp b/Source/Processors/Visualization/LfpDisplayCanvas.cpp index 54f91aacb40c5805ad28173d2de4e950f4c31833..9a33de1c99100c0cf85a549c013df4c6ae0bc99e 100755 --- a/Source/Processors/Visualization/LfpDisplayCanvas.cpp +++ b/Source/Processors/Visualization/LfpDisplayCanvas.cpp @@ -183,6 +183,18 @@ void LfpDisplayCanvas::update() lfpDisplay->setNumChannels(nChans); + // update channel names + for (int i = 0; i < processor->getNumInputs(); i++) + { + + String chName = processor->channels[i]->getName(); + + //std::cout << chName << std::endl; + + lfpDisplay->channelInfo[i]->setName(chName); + + } + lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight()); } @@ -807,6 +819,10 @@ LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, LfpDisplay* d, int cha canvas(c), display(d), isSelected(false), chan(channelNumber), channelHeight(40), channelOverlap(300), range(1000.0f) { + + name = String(channelNumber+1); // default is to make the channelNumber the name + + channelHeightFloat = (float) channelHeight; channelFont = Font("Default", channelHeight*0.6, Font::plain); @@ -1002,6 +1018,11 @@ int LfpChannelDisplay::getChannelOverlap() return channelOverlap; } +void LfpChannelDisplay::setName(String name_) +{ + name = name_; +} + // ------------------------------- LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDisplay* display_, int ch) @@ -1013,14 +1034,16 @@ LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDispl void LfpChannelDisplayInfo::paint(Graphics& g) { + + int center = getHeight()/2; g.setColour(lineColour); g.setFont(channelFont); - g.setFont(channelHeightFloat*0.6); + g.setFont(channelHeightFloat*0.3); - g.drawText(String(chan+1), 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); + g.drawText(name, 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); } @@ -1087,4 +1110,4 @@ void eventDisplayInterface::paint(Graphics& g) //g.drawText(String(channelNumber), 8, 2, 200, 15, Justification::left, false); -} \ No newline at end of file +} diff --git a/Source/Processors/Visualization/LfpDisplayCanvas.h b/Source/Processors/Visualization/LfpDisplayCanvas.h index 79f903e24154d02e0149ff69fb2e7c8c24f534b7..bbbf5bfbd429373b37dc771a0aff1c2e75c80d13 100755 --- a/Source/Processors/Visualization/LfpDisplayCanvas.h +++ b/Source/Processors/Visualization/LfpDisplayCanvas.h @@ -187,12 +187,17 @@ public: void setChannelHeight(int r); int getChannelHeight(); + bool setEventDisplayState(int ch, bool state); bool getEventDisplayState(int ch); Array<Colour> channelColours; + Array<LfpChannelDisplay*> channels; + Array<LfpChannelDisplayInfo*> channelInfo; + + private: int numChans; @@ -206,6 +211,9 @@ private: bool eventDisplayEnabled[8]; + Array<Colour> channelColours; + + float range; }; @@ -221,6 +229,8 @@ public: void select(); void deselect(); + void setName(String); + void setColour(Colour c); void setChannelHeight(int); @@ -243,6 +253,8 @@ protected: int chan; + String name; + Font channelFont; Colour lineColour; diff --git a/Source/Processors/Visualization/SpikeDisplayCanvas.cpp b/Source/Processors/Visualization/SpikeDisplayCanvas.cpp index fe16ddafc0a1c36265b1b66d4345aad9fbdd701a..cf904d76971b00497d37da260b8c413dfe337bcd 100755 --- a/Source/Processors/Visualization/SpikeDisplayCanvas.cpp +++ b/Source/Processors/Visualization/SpikeDisplayCanvas.cpp @@ -20,8 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ - + #include "SpikeDisplayCanvas.h" +#include "../RecordNode.h" SpikeDisplayCanvas::SpikeDisplayCanvas(SpikeDisplayNode* n) : processor(n), newSpike(false) @@ -79,7 +80,8 @@ void SpikeDisplayCanvas::update() for (int i = 0; i < nPlots; i++) { - spikeDisplay->addSpikePlot(processor->getNumberOfChannelsForElectrode(i), i); + spikeDisplay->addSpikePlot(processor->getNumberOfChannelsForElectrode(i), i, + processor->getNameForElectrode(i)); } spikeDisplay->resized(); @@ -117,6 +119,11 @@ void SpikeDisplayCanvas::refresh() repaint(); } +RecordNode* SpikeDisplayCanvas::getRecordNode() +{ + return processor->getProcessorGraph()->getRecordNode(); +} + void SpikeDisplayCanvas::processSpikeEvents() { @@ -214,12 +221,12 @@ void SpikeDisplay::removePlots() } -void SpikeDisplay::addSpikePlot(int numChannels, int electrodeNum) +void SpikeDisplay::addSpikePlot(int numChannels, int electrodeNum, String name_) { std::cout << "Adding new spike plot." << std::endl; - SpikePlot* spikePlot = new SpikePlot(canvas, electrodeNum, 1000 + numChannels); + SpikePlot* spikePlot = new SpikePlot(canvas, electrodeNum, 1000 + numChannels, name_); spikePlots.add(spikePlot); addAndMakeVisible(spikePlot); } @@ -348,12 +355,14 @@ void SpikeDisplay::plotSpike(const SpikeObject& spike, int electrodeNum) // ---------------------------------------------------------------- -SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p) : +SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p, String name_) : canvas(sdc), isSelected(false), electrodeNumber(elecNum), plotType(p), - limitsChanged(true) + limitsChanged(true), name(name_), isRecording(false) { + recordNode = sdc->getRecordNode(); + font = Font("Default", 15, Font::plain); switch (p) @@ -407,6 +416,9 @@ SpikePlot::SpikePlot(SpikeDisplayCanvas* sdc, int elecNum, int p) : rangeButtons.add(rangeButton); } + spikeBuffer = new uint8_t[MAX_SPIKE_BUFFER_LEN]; // MAX_SPIKE_BUFFER_LEN defined in SpikeObject.h + + } SpikePlot::~SpikePlot() @@ -422,7 +434,7 @@ void SpikePlot::paint(Graphics& g) g.setFont(font); - g.drawText(String(electrodeNumber+1),10,0,50,20,Justification::left,false); + g.drawText(name,10,0,200,20,Justification::left,false); } @@ -430,13 +442,139 @@ void SpikePlot::processSpikeObject(const SpikeObject& s) { //std::cout<<"ElectrodePlot::processSpikeObject()"<<std::endl; + // first, check if it's above threshold + bool aboveThreshold = false; + for (int i = 0; i < nWaveAx; i++) - wAxes[i]->updateSpikeData(s); + { + aboveThreshold = aboveThreshold | wAxes[i]->checkThreshold(s); + } + + if (aboveThreshold) + { + for (int i = 0; i < nWaveAx; i++) + wAxes[i]->updateSpikeData(s); + + for (int i = 0; i < nProjAx; i++) + pAxes[i]->updateSpikeData(s); + } + + + // then record it! + if (recordNode->isRecording) + { + if (!isRecording) + { + // open file + openFile(); + + isRecording = true; + //std::cout << "Start recording spikes." << std::endl; + + } + + if (aboveThreshold) + { + // write spike to disk + writeSpike(s); + } + + } else { + + if (isRecording) + { + // close file + closeFile(); + + isRecording = false; + //std::cout << "Stop recording spikes." << std::endl; + } + } - for (int i = 0; i < nProjAx; i++) - pAxes[i]->updateSpikeData(s); } +void SpikePlot::openFile() +{ + dataDirectory = recordNode->getDataDirectory(); + + filename = dataDirectory.getFullPathName(); + filename += File::separator; + filename += name.removeCharacters(" "); + filename += ".spikes"; + + std::cout << "OPENING FILE: " << filename << std::endl; + + File fileToUse = File(filename); + + if (!fileToUse.exists()) + { + // open it and write header + file = fopen(filename.toUTF8(), "ab"); + + String header = generateHeader(); + + fwrite(header.toUTF8(), 1, header.getNumBytesAsUTF8(), file); + + } else { + + // append it + file = fopen(filename.toUTF8(), "ab"); + } + + +} + +void SpikePlot::closeFile() +{ + + std::cout << "CLOSING FILE: " << filename << std::endl; + + if (file != NULL) + { + fclose(file); + } + +} + +void SpikePlot::writeSpike(const SpikeObject& s) +{ + + + packSpike(&s, spikeBuffer, 256); + + fwrite(spikeBuffer, 1, 256, file); + + +} + +String SpikePlot::generateHeader() +{ + String header = "header.format = 'OPEN EPHYS DATA FORMAT v0.0'; \n"; + + header += "header.header_bytes = "; + header += String(HEADER_SIZE); + header += ";\n"; + + header += "header.description = 'Spike data...live it up!'; \n"; + + header += "header.date_created = '"; + header += recordNode->generateDateString(); + header += "';\n"; + + header += "header.electrode = '"; + header += name; + header += "';\n"; + + header += "header.channelType = 'Electrode';\n"; + + header = header.paddedRight(' ', HEADER_SIZE); + + //std::cout << header << std::endl; + + return header; +} + + void SpikePlot::select() { isSelected = true; @@ -611,7 +749,7 @@ void SpikePlot::clear() WaveAxes::WaveAxes(int channel) : GenericAxes(channel), drawGrid(true), - bufferSize(10), spikeIndex(0), thresholdLevel(0.5f), range(250.0f), + bufferSize(10), spikeIndex(0), thresholdLevel(0.0f), range(250.0f), isOverThresholdSlider(false), isDraggingThresholdSlider(false) { @@ -723,10 +861,11 @@ void WaveAxes::plotSpike(const SpikeObject& s, Graphics& g) void WaveAxes::drawThresholdSlider(Graphics& g) { - float h = getHeight()*thresholdLevel; + float h = getHeight()*(0.5f - thresholdLevel/range); g.setColour(thresholdColour); g.drawLine(0, h, getWidth(), h); + g.drawText(String(roundFloatToInt(thresholdLevel)),2,h,25,10,Justification::left, false); } @@ -740,12 +879,15 @@ void WaveAxes::drawWaveformGrid(Graphics& g) for (float y = -range/2; y < range/2; y += 25.0f) { - g.drawLine(0,h/2 + y/range*h, w, h/2+ y/range*h); + if (y == 0) + g.drawLine(0,h/2 + y/range*h, w, h/2+ y/range*h,2.0f); + else + g.drawLine(0,h/2 + y/range*h, w, h/2+ y/range*h); } } -void WaveAxes::updateSpikeData(const SpikeObject& s) +bool WaveAxes::updateSpikeData(const SpikeObject& s) { if (!gotFirstSpike) { @@ -754,12 +896,38 @@ void WaveAxes::updateSpikeData(const SpikeObject& s) SpikeObject newSpike = s; - spikeIndex++; - spikeIndex %= bufferSize; + //if (checkThreshold(newSpike)) + //{ + spikeIndex++; + spikeIndex %= bufferSize; + + spikeBuffer.set(spikeIndex, newSpike); + // return true; + + // } else { + // return false; + // } - spikeBuffer.set(spikeIndex, newSpike); +} + +bool WaveAxes::checkThreshold(const SpikeObject& s) +{ + int sampIdx = 40*type; + + for (int i = 0; i < s.nSamples-1; i++) + { + + if (float(s.data[sampIdx]-32768)/float(*s.gain)*1000.0f > thresholdLevel) + { + return true; + } + + sampIdx++; + } + + return false; } @@ -776,6 +944,8 @@ void WaveAxes::clear() spikeBuffer.add(so); } + + repaint(); } void WaveAxes::mouseMove(const MouseEvent& event) @@ -785,7 +955,7 @@ void WaveAxes::mouseMove(const MouseEvent& event) float y = event.y; - float h = getHeight()*thresholdLevel; + float h = getHeight()*(0.5f - thresholdLevel/range); // std::cout << y << " " << h << std::endl; @@ -827,7 +997,19 @@ void WaveAxes::mouseDrag(const MouseEvent& event) { if (isOverThresholdSlider) { - thresholdLevel = float(event.y) / float(getHeight()); + + float thresholdSliderPosition = float(event.y) / float(getHeight()); + + if (thresholdSliderPosition > 0.5f) + thresholdSliderPosition = 0.5f; + else if (thresholdSliderPosition < 0.0f) + thresholdSliderPosition = 0.0f; + + + thresholdLevel = (0.5f - thresholdSliderPosition) * range; + + //std::cout << "Threshold = " << thresholdLevel << std::endl; + repaint(); } } @@ -886,7 +1068,7 @@ void ProjectionAxes::paint(Graphics& g) 0, imageDim-rangeY, rangeX, rangeY); } -void ProjectionAxes::updateSpikeData(const SpikeObject& s) +bool ProjectionAxes::updateSpikeData(const SpikeObject& s) { if (!gotFirstSpike) { @@ -946,6 +1128,8 @@ void ProjectionAxes::clear() { projectionImage.clear(Rectangle<int>(0, 0, projectionImage.getWidth(), projectionImage.getHeight()), Colours::black); + + repaint(); } void ProjectionAxes::n2ProjIdx(int proj, int* p1, int* p2) @@ -1012,7 +1196,7 @@ GenericAxes::~GenericAxes() } -void GenericAxes::updateSpikeData(const SpikeObject& newSpike) +bool GenericAxes::updateSpikeData(const SpikeObject& newSpike) { if (!gotFirstSpike) { diff --git a/Source/Processors/Visualization/SpikeDisplayCanvas.h b/Source/Processors/Visualization/SpikeDisplayCanvas.h index 02ba77143ab868b2840866023d47934bbe1cdad6..ade7bd6c87f6e441a1e798be39209d796dc9488e 100755 --- a/Source/Processors/Visualization/SpikeDisplayCanvas.h +++ b/Source/Processors/Visualization/SpikeDisplayCanvas.h @@ -57,6 +57,7 @@ class GenericAxes; class ProjectionAxes; class WaveAxes; class SpikePlot; +class RecordNode; /** @@ -95,6 +96,8 @@ public: void buttonClicked(Button* button); + RecordNode* getRecordNode(); + private: SpikeDisplayNode* processor; @@ -122,7 +125,7 @@ public: void removePlots(); void clear(); - void addSpikePlot(int numChannels, int electrodeNum); + void addSpikePlot(int numChannels, int electrodeNum, String name); void paint(Graphics& g); @@ -161,12 +164,14 @@ private: Class for drawing the waveforms and projections of incoming spikes. + Also responsible for saving spikes. + */ class SpikePlot : public Component, Button::Listener { public: - SpikePlot(SpikeDisplayCanvas*, int elecNum, int plotType); + SpikePlot(SpikeDisplayCanvas*, int elecNum, int plotType, String name_); virtual ~SpikePlot(); void paint(Graphics& g); @@ -197,6 +202,8 @@ public: private: + bool isRecording; + int plotType; int nWaveAx; @@ -215,9 +222,22 @@ private: void setLimitsOnAxes(); void updateAxesPositions(); + String name; Font font; + // methods for recording: + void openFile(); + void closeFile(); + void writeSpike(const SpikeObject& s); + String generateHeader(); + + RecordNode* recordNode; + FILE* file; + String filename; + File dataDirectory; + uint8_t* spikeBuffer; + }; /** @@ -236,7 +256,7 @@ public: virtual ~GenericAxes(); - virtual void updateSpikeData(const SpikeObject& s); + virtual bool updateSpikeData(const SpikeObject& s); void setXLims(double xmin, double xmax); void getXLims(double* xmin, double* xmax); @@ -280,7 +300,8 @@ public: WaveAxes(int channel); ~WaveAxes() {} - void updateSpikeData(const SpikeObject& s); + bool updateSpikeData(const SpikeObject& s); + bool checkThreshold(const SpikeObject& spike); void paint(Graphics& g); @@ -312,6 +333,8 @@ private: void drawThresholdSlider(Graphics& g); + + Font font; Array<SpikeObject> spikeBuffer; @@ -344,7 +367,7 @@ public: ProjectionAxes(int projectionNum); ~ProjectionAxes() {} - void updateSpikeData(const SpikeObject& s); + bool updateSpikeData(const SpikeObject& s); void paint(Graphics& g); diff --git a/Source/Processors/Visualization/SpikeObject.cpp b/Source/Processors/Visualization/SpikeObject.cpp index c2665d123aca88f6505a8b9432228b22d30d8be2..bb7ce020b4f4ddc6ec1e19f0eb6db375f2c857d3 100755 --- a/Source/Processors/Visualization/SpikeObject.cpp +++ b/Source/Processors/Visualization/SpikeObject.cpp @@ -27,16 +27,13 @@ #include "time.h" // Simple method for serializing a SpikeObject into a string of bytes -int packSpike(SpikeObject* s, uint8_t* buffer, int bufferSize) +int packSpike(const SpikeObject* s, uint8_t* buffer, int bufferSize) { //int reqBytes = 1 + 4 + 2 + 2 + 2 + 2 * s->nChannels * s->nSamples + 2 * s->nChannels * 2; int idx = 0; - s->eventType = SPIKE_EVENT_CODE; - - memcpy(buffer+idx, &(s->eventType), 1); idx += 1; diff --git a/Source/Processors/Visualization/SpikeObject.h b/Source/Processors/Visualization/SpikeObject.h index 1bd73f2ec990f3f2293f7808e7163191f97ff46e..5a71b9e5b9b8a74e553b7a926a15003b63e44fbb 100755 --- a/Source/Processors/Visualization/SpikeObject.h +++ b/Source/Processors/Visualization/SpikeObject.h @@ -68,7 +68,7 @@ struct SpikeObject }; /** Simple method for serializing a SpikeObject into a string of bytes, returns true is the packaged spike buffer is valid */ -int packSpike(SpikeObject* s, uint8_t* buffer, int bufferLength); +int packSpike(const SpikeObject* s, uint8_t* buffer, int bufferLength); /** Simple method for deserializing a string of bytes into a Spike object, returns true is the provided spike buffer is valid */ bool unpackSpike(SpikeObject* s, const uint8_t* buffer, int bufferLength); diff --git a/Source/Processors/Visualization/Visualizer.h b/Source/Processors/Visualization/Visualizer.h index 3825800a64294b488ba5f5d289bc5bf99ad8130d..0e23f3d2cfcb3af037fc5a808728ac1f2b5018b7 100755 --- a/Source/Processors/Visualization/Visualizer.h +++ b/Source/Processors/Visualization/Visualizer.h @@ -94,8 +94,6 @@ public: /** Loads parameters from XML */ virtual void loadVisualizerParameters(XmlElement* xml) { } - - }; diff --git a/Source/UI/ProcessorList.cpp b/Source/UI/ProcessorList.cpp index d5998e9343ea85e7c93a92d3cd3b74f6b1e5e51f..4cf9a433c7d5988ab5c2f0088e535898ae24c752 100755 --- a/Source/UI/ProcessorList.cpp +++ b/Source/UI/ProcessorList.cpp @@ -78,7 +78,8 @@ ProcessorList::ProcessorList() filters->addSubItem(new ProcessorListItem("Spike Detector")); //filters->addSubItem(new ProcessorListItem("Resampler")); filters->addSubItem(new ProcessorListItem("Phase Detector")); - filters->addSubItem(new ProcessorListItem("Digital Ref")); + //filters->addSubItem(new ProcessorListItem("Digital Ref")); + filters->addSubItem(new ProcessorListItem("Channel Map")); ProcessorListItem* sinks = new ProcessorListItem("Sinks"); sinks->addSubItem(new ProcessorListItem("LFP Viewer")); diff --git a/Source/UI/UIComponent.cpp b/Source/UI/UIComponent.cpp index 7d94fb6b2ea432e14860752e4831db6a34401eb8..f9de7eb391bfbc72533158217fd0e90b94fccbdd 100755 --- a/Source/UI/UIComponent.cpp +++ b/Source/UI/UIComponent.cpp @@ -92,7 +92,7 @@ UIComponent::UIComponent(MainWindow* mainWindow_, ProcessorGraph* pgraph, AudioC mainWindow->setMenuBar(this); #endif - // getEditorViewport()->loadState(File("/home/jsiegle/Programming/GUI/Builds/Linux/build/rhythm_config.xml")); + //getEditorViewport()->loadState(File("/home/jsiegle/Programming/GUI/Builds/Linux/build/spike_display_2.xml")); }