From 72ad170ec9e6bed6cbec73db72368a206143c13d Mon Sep 17 00:00:00 2001 From: Josh Siegle <joshs@alleninstitute.org> Date: Sun, 29 May 2016 16:02:58 +0300 Subject: [PATCH] Basic implementation of RFMapper --- .../Plugins/PhaseDetector/PhaseDetector.cpp | 2 +- Source/Plugins/RFMapper/RFMapper.cpp | 184 ++++++++++---- Source/Plugins/RFMapper/RFMapper.h | 37 +++ Source/Plugins/RFMapper/RFMapperEditor.cpp | 235 ++++++++++++++++-- Source/Plugins/RFMapper/RFMapperEditor.h | 44 +++- Source/Plugins/SpikeRaster/SpikeRaster.h | 2 + 6 files changed, 434 insertions(+), 70 deletions(-) diff --git a/Source/Plugins/PhaseDetector/PhaseDetector.cpp b/Source/Plugins/PhaseDetector/PhaseDetector.cpp index 40681ebe2..db7e754f9 100644 --- a/Source/Plugins/PhaseDetector/PhaseDetector.cpp +++ b/Source/Plugins/PhaseDetector/PhaseDetector.cpp @@ -30,7 +30,7 @@ PhaseDetector::PhaseDetector() risingPos(false), risingNeg(false), fallingPos(false), fallingNeg(false) { - + } PhaseDetector::~PhaseDetector() diff --git a/Source/Plugins/RFMapper/RFMapper.cpp b/Source/Plugins/RFMapper/RFMapper.cpp index bb2d8ea32..da35b3e7e 100644 --- a/Source/Plugins/RFMapper/RFMapper.cpp +++ b/Source/Plugins/RFMapper/RFMapper.cpp @@ -30,12 +30,14 @@ #include "RFMapperEditor.h" RFMapper::RFMapper() - : GenericProcessor("RF Mapper") //, threshold(200.0), state(true) + : GenericProcessor("RF Mapper"), redrawRequested(false), displayBufferSize(100) { //Without a custom editor, generic parameter controls can be added //parameters.add(Parameter("thresh", 0.0, 500.0, 200.0, 0)); + electrodes.clear(); + } RFMapper::~RFMapper() @@ -56,8 +58,41 @@ AudioProcessorEditor* RFMapper::createEditor() return editor; } +void RFMapper::updateSettings() +{ + //std::cout << "Setting num inputs on SpikeDisplayNode to " << getNumInputs() << std::endl; + + electrodes.clear(); + + for (int i = 0; i < eventChannels.size(); i++) + { + ChannelType type = eventChannels[i]->getType(); + + if (type == ELECTRODE_CHANNEL) + { + + Electrode elec; + elec.numChannels = static_cast<SpikeChannel*>(eventChannels[i]->extraData.get())->numChannels; + + elec.name = eventChannels[i]->getName(); + elec.currentSpikeIndex = 0; + elec.mostRecentSpikes.ensureStorageAllocated(displayBufferSize); + + for (int j = 0; j < elec.numChannels; j++) + { + elec.displayThresholds.add(0); + elec.detectorThresholds.add(0); + } + + electrodes.add(elec); + + } + } + +} + -void RFMapper::setParameter(int parameterIndex, float newValue) +void RFMapper::setParameter(int param, float newValue) { //Parameter& p = parameters.getReference(parameterIndex); @@ -66,63 +101,108 @@ void RFMapper::setParameter(int parameterIndex, float newValue) //threshold = newValue; //std::cout << float(p[0]) << std::endl; - editor->updateParameterButtons(parameterIndex); + if (param == 2) // redraw + { + redrawRequested = true; + + } +} + +bool RFMapper::enable() +{ + std::cout << "RFMapper::enable()" << std::endl; + RFMapperEditor* editor = (RFMapperEditor*) getEditor(); + + editor->enable(); + return true; + +} + +bool RFMapper::disable() +{ + std::cout << "RFMapper disabled!" << std::endl; + RFMapperEditor* editor = (RFMapperEditor*) getEditor(); + editor->disable(); + return true; +} + +int RFMapper::getNumElectrodes() +{ + return electrodes.size(); } -void RFMapper::process(AudioSampleBuffer& buffer, - MidiBuffer& events) +void RFMapper::process(AudioSampleBuffer& buffer, MidiBuffer& events) { - /** - Generic structure for processing buffer data - */ - int nChannels = buffer.getNumChannels(); - for (int chan = 0; chan < nChannels; chan++) - { - int nSamples = getNumSamples(chan); - /** - Do something here. - - To obtain a read-only pointer to the n sample of a channel: - float* samplePtr = buffer.getReadPointer(chan,n); - - To obtain a read-write pointer to the n sample of a channel: - float* samplePtr = buffer.getWritePointer(chan,n); - - All the samples in a channel are consecutive, so this example is valid: - float* samplePtr = buffer.getWritePointer(chan,0); - for (i=0; i < nSamples; i++) - { - *(samplePtr+i) = (*samplePtr+i)+offset; - } - - See also documentation and examples for buffer.copyFrom and buffer.addFrom to operate on entire channels at once. - - To add a TTL event generated on the n-th sample: - addEvents(events, TTL, n); - - - */ - } - - /** Simple example that creates an event when the first channel goes under a negative threshold - - for (int i = 0; i < getNumSamples(channels[0]->sourceNodeId); i++) + + checkForEvents(events); // automatically calls 'handleEvent + + if (redrawRequested) { - if ((*buffer.getReadPointer(0, i) < -threshold) && !state) + //std::cout << "redraw" << std::endl; + for (int i = 0; i < getNumElectrodes(); i++) { - - // generate midi event - addEvent(events, TTL, i); - - state = true; - - } else if ((*buffer.getReadPointer(0, i) > -threshold + bufferZone) && state) + + Electrode& e = electrodes.getReference(i); + + // transfer buffered spikes to spike plot + for (int j = 0; j < e.currentSpikeIndex; j++) + { + //std::cout << "Transferring spikes." << std::endl; + e.rfMap->processSpikeObject(e.mostRecentSpikes[j]); + e.currentSpikeIndex = 0; + } + + } + + redrawRequested = false; + } + +} + +void RFMapper::addRFMapForElectrode(RFMap* rf, int i) +{ + Electrode& e = electrodes.getReference(i); + e.rfMap = rf; +} + + +void RFMapper::handleEvent(int eventType, MidiMessage& event, int samplePosition) +{ + + //std::cout << "Received event of type " << eventType << std::endl; + + if (eventType == SPIKE) + { + + const uint8_t* dataptr = event.getRawData(); + int bufferSize = event.getRawDataSize(); + + if (bufferSize > 0) { - state = false; + + SpikeObject newSpike; + + bool isValid = unpackSpike(&newSpike, dataptr, bufferSize); + + if (isValid) + { + int electrodeNum = newSpike.source; + + Electrode& e = electrodes.getReference(electrodeNum); + // std::cout << electrodeNum << std::endl; + + // add to buffer + if (e.currentSpikeIndex < displayBufferSize) + { + // std::cout << "Adding spike " << e.currentSpikeIndex + 1 << std::endl; + e.mostRecentSpikes.set(e.currentSpikeIndex, newSpike); + e.currentSpikeIndex++; + } + + } + } - - } - */ + } } diff --git a/Source/Plugins/RFMapper/RFMapper.h b/Source/Plugins/RFMapper/RFMapper.h index 8ab1629f7..35419ac01 100644 --- a/Source/Plugins/RFMapper/RFMapper.h +++ b/Source/Plugins/RFMapper/RFMapper.h @@ -29,6 +29,9 @@ #endif #include <ProcessorHeaders.h> +#include <SpikeLib.h> + +class RFMap; /** @@ -67,6 +70,10 @@ public: return true; } + void updateSettings(); + + int getNumElectrodes(); + /** If the processor has a custom editor, this method must be defined to instantiate it. */ AudioProcessorEditor* createEditor(); @@ -85,11 +92,15 @@ public: */ void process(AudioSampleBuffer& buffer, MidiBuffer& events); + void handleEvent(int, MidiMessage&, int); + /** The method that standard controls on the editor will call. It is recommended that any variables used by the "process" function are modified only through this method while data acquisition is active. */ void setParameter(int parameterIndex, float newValue); + void addRFMapForElectrode(RFMap* rf, int i); + /** Optional method called every time the signal chain is refreshed or changed in any way. Allows the processor to handle variations in the channel configuration or any other parameter @@ -98,6 +109,9 @@ public: structure shouldn't be manipulated outside of this method. */ //void updateSettings(); + bool enable(); + bool disable(); + private: @@ -108,6 +122,29 @@ private: // float threshold; // bool state; + struct Electrode + { + String name; + + int numChannels; + + Array<float> displayThresholds; + Array<float> detectorThresholds; + + Array<SpikeObject> mostRecentSpikes; + int currentSpikeIndex; + + int recordIndex; + + RFMap* rfMap; + + }; + + Array<Electrode> electrodes; + + int displayBufferSize; + bool redrawRequested; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RFMapper); }; diff --git a/Source/Plugins/RFMapper/RFMapperEditor.cpp b/Source/Plugins/RFMapper/RFMapperEditor.cpp index adabc34ba..f1c2663c6 100644 --- a/Source/Plugins/RFMapper/RFMapperEditor.cpp +++ b/Source/Plugins/RFMapper/RFMapperEditor.cpp @@ -31,6 +31,18 @@ RFMapperEditor::RFMapperEditor(GenericProcessor* parentNode, bool useDefaultPara tabText = "Mapper"; desiredWidth = 180; + + electrodeSelector = new ComboBox(); + electrodeSelector->setBounds(35,40,100,20); + electrodeSelector->addListener(this); + + addAndMakeVisible(electrodeSelector); + + unitSelector = new ComboBox(); + unitSelector->setBounds(35,70,100,20); + unitSelector->addListener(this); + + addAndMakeVisible(unitSelector); } @@ -43,31 +55,68 @@ Visualizer* RFMapperEditor::createNewCanvas() { RFMapper* processor = (RFMapper*) getProcessor(); - return new RFMapperCanvas(processor); + canvas = new RFMapperCanvas(processor); + return canvas; } -void RFMapperEditor::buttonCallback(Button* button) +void RFMapperEditor::comboBoxChanged(ComboBox* c) { + + RFMapperCanvas* rfmc = (RFMapperCanvas*) canvas.get(); + + if (c == electrodeSelector) + { + rfmc->setCurrentElectrode(c->getSelectedId()-1); + } else if (c == unitSelector) + { + rfmc->setCurrentUnit(c->getSelectedId()-1); + } - int gId = button->getRadioGroupId(); +} + +void RFMapperEditor::updateSettings() +{ + RFMapper* processor = (RFMapper*) getProcessor(); + + int numElectrodes = processor->getNumElectrodes(); - if (gId > 0) + electrodeSelector->clear(); + unitSelector->clear(); + + if (numElectrodes > 0) { - if (canvas != 0) + + for (int i = 1; i <= numElectrodes; i++) { - canvas->setParameter(gId-1, button->getName().getFloatValue()); + String itemName = "Electrode "; + itemName += String(i); + std::cout << i << " " << itemName << std::endl; + electrodeSelector->addItem(itemName, i); } + + electrodeSelector->setSelectedId(1, dontSendNotification); + + unitSelector->addItem("MUA", 1); + for (int i = 1; i <= 5; i++) + { + String itemName = "Unit "; + itemName += String(i); + unitSelector->addItem(itemName, i + 1); + } + + unitSelector->setSelectedId(1, dontSendNotification); } - -} + updateVisualizer(); +} -RFMapperCanvas::RFMapperCanvas(RFMapper* sr) : processor(sr) +RFMapperCanvas::RFMapperCanvas(RFMapper* sr) : processor(sr), currentMap(0) { - + + rfMaps.clear(); } @@ -77,12 +126,14 @@ RFMapperCanvas::~RFMapperCanvas(){ void RFMapperCanvas::beginAnimation() { - + startCallbacks(); + std::cout << "RF Mapper beginning animation." << std::endl; + } void RFMapperCanvas::endAnimation() { - + stopCallbacks(); } void RFMapperCanvas::refreshState() @@ -91,23 +142,179 @@ void RFMapperCanvas::refreshState() void RFMapperCanvas::update() { - + int nMaps = processor->getNumElectrodes(); + + clearRfMaps(); + + for (int i = 0; i < nMaps; i++) + { + RFMap* rf = addRFMap(i); + processor->addRFMapForElectrode(rf, i); + } + + addAndMakeVisible(rfMaps[currentMap]); + + resized(); + repaint(); + } void RFMapperCanvas::setParameter(int, float) {} void RFMapperCanvas::paint(Graphics& g) { - g.fillAll(Colours::orange); + g.fillAll(Colours::grey); } void RFMapperCanvas::refresh() { + processor->setParameter(2, 0.0f); // request redraw + repaint(); + + //::cout << "Refresh" << std::endl; } void RFMapperCanvas::resized() { + if (rfMaps.size() > 0) + rfMaps[currentMap]->setBounds(0, 0, getWidth(), getHeight()); + + repaint(); +} + +void RFMapperCanvas::clearRfMaps() +{ + rfMaps.clear(); + +} + +RFMap* RFMapperCanvas::addRFMap(int electrodeNum) +{ + + std::cout << "Adding new rf map." << std::endl; + + RFMap* rfMap = new RFMap(this, electrodeNum); + rfMaps.add(rfMap); + + return rfMap; +} + +void RFMapperCanvas::setCurrentElectrode(int electrodeNum) +{ + currentMap = electrodeNum; + + update(); +} + +void RFMapperCanvas::setCurrentUnit(int unitNum) +{ + rfMaps[currentMap]->setCurrentUnit(unitNum); +} + +RFMap::RFMap(RFMapperCanvas*, int elecNum) +{ + map = AudioSampleBuffer(50,30); + + random = Random(); + + unitId = 0; + + reset(); +} + +RFMap::~RFMap() +{ + +} + +void RFMap::reset() +{ + float numXPixels = map.getNumChannels(); + float numYPixels = map.getNumSamples(); + + for (int n = 0; n < numXPixels; n++) + { + for (int m = 0; m < numYPixels; m++) + { + map.setSample(n,m, 0.); + } + } + + repaint(); +} + +void RFMap::paint(Graphics& g) +{ + float numXPixels = map.getNumChannels(); + float numYPixels = map.getNumSamples(); + + float xHeight = getWidth()/numXPixels; + float yHeight = getHeight()/numYPixels; + + for (int n = 0; n < numXPixels; n++) + { + for (int m = 0; m < numYPixels; m++) + { + float colourIndex = map.getSample(n,m); + + if (colourIndex == 0.) + { + g.setColour(Colours::grey); + } else if (colourIndex == 1.) + { + g.setColour(Colours::white); + } else { + g.setColour(Colours::black); + } + + g.fillRect(n*xHeight, m*yHeight, xHeight, yHeight); + } + } +} +void RFMap::resized() +{ + +} + +void RFMap::setCurrentUnit(int unit) +{ + unitId = unit; } + +void RFMap::processSpikeObject(const SpikeObject& s) +{ + + //std::cout << "Got spike." << std::endl; + + if (s.sortedId == unitId) + { + + float numXPixels = map.getNumChannels(); + float numYPixels = map.getNumSamples(); + + for (int n = 0; n < numXPixels; n++) + { + for (int m = 0; m < numYPixels; m++) + { + float colourIndex = random.nextFloat(); + + if (colourIndex < 0.33) + { + map.setSample(n,m,-1.); + } else if (colourIndex > 0.33 && colourIndex < 0.66) + { + map.setSample(n,m, 0.); + } else { + map.setSample(n,m,1.); + } + + } + } + + } + + +} diff --git a/Source/Plugins/RFMapper/RFMapperEditor.h b/Source/Plugins/RFMapper/RFMapperEditor.h index 6954321d2..c26755c31 100644 --- a/Source/Plugins/RFMapper/RFMapperEditor.h +++ b/Source/Plugins/RFMapper/RFMapperEditor.h @@ -26,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include <VisualizerEditorHeaders.h> #include <VisualizerWindowHeaders.h> +#include <SpikeLib.h> #include "RFMapper.h" @@ -39,18 +40,22 @@ class Visualizer; */ -class RFMapperEditor : public VisualizerEditor +class RFMapperEditor : public VisualizerEditor, public ComboBox::Listener { public: RFMapperEditor(GenericProcessor*, bool useDefaultParameterEditors); ~RFMapperEditor(); - void buttonCallback(Button* button); + void updateSettings(); + + void comboBoxChanged(ComboBox* c); Visualizer* createNewCanvas(); private: - + + ScopedPointer<ComboBox> electrodeSelector; + ScopedPointer<ComboBox> unitSelector; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RFMapperEditor); }; @@ -72,6 +77,9 @@ public: void setParameter(int, int, int, float) {} void paint(Graphics& g); + + void setCurrentUnit(int); + void setCurrentElectrode(int); void refresh(); @@ -80,6 +88,36 @@ public: private: RFMapper* processor; + OwnedArray<RFMap> rfMaps; + + void clearRfMaps(); + RFMap* addRFMap(int); + + int currentMap; + +}; + +class RFMap : public Component +{ +public: + RFMap(RFMapperCanvas*, int elecNum); + virtual ~RFMap(); + + AudioSampleBuffer map; + + void paint(Graphics& g); + void resized(); + void reset(); + + void processSpikeObject(const SpikeObject& s); + + Random random; + + void setCurrentUnit(int); + int unitId; + + + }; diff --git a/Source/Plugins/SpikeRaster/SpikeRaster.h b/Source/Plugins/SpikeRaster/SpikeRaster.h index 98839df1b..33176b1df 100644 --- a/Source/Plugins/SpikeRaster/SpikeRaster.h +++ b/Source/Plugins/SpikeRaster/SpikeRaster.h @@ -85,6 +85,8 @@ public: */ void process(AudioSampleBuffer& buffer, MidiBuffer& events); + void handleEvent(int, MidiMessage&, int); + /** The method that standard controls on the editor will call. It is recommended that any variables used by the "process" function are modified only through this method while data acquisition is active. */ -- GitLab