diff --git a/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp b/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp index 87b9778be62e780b8f7c57b9aa3e9b14920e65fa..4e3066c066f2e4d153708b85b0d3c149a63bd28c 100644 --- a/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp +++ b/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp @@ -230,6 +230,19 @@ LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : drawMethodButton->setClickingTogglesState(true); drawMethodButton->setToggleState(false, sendNotification); addAndMakeVisible(drawMethodButton); + + // two sliders for the two histogram components of the supersampled plotting mode + histogramSlider = new Slider; + histogramSlider->setRange (0, 1); + histogramSlider->addListener(this); + addAndMakeVisible (histogramSlider); + + supersampleSlider = new Slider; + supersampleSlider->setRange (0, 1); + supersampleSlider->addListener(this); + addAndMakeVisible (supersampleSlider); + + //button for pausing the display - works by skipping buffer updates. This way scrolling etc still works pauseButton = new UtilityButton("Pause", Font("Small Text", 13, Font::plain)); @@ -262,6 +275,17 @@ LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : lfpDisplay->setEventDisplayState(i,true); } + + // allocate samplesPerPixel, behaves like float samplesPerPixel[nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL] + samplesPerPixel = (float***)malloc(nChans * sizeof(float **)); + for(int i=0;i<nChans;i++) + { + samplesPerPixel[i] = (float**)malloc(MAX_N_SAMP * sizeof(float*)); + for(int j=0;j<MAX_N_SAMP;j++) + { + samplesPerPixel[i][j] = (float*)malloc(MAX_N_SAMP_PER_PIXEL*sizeof(float)); + } + } TopLevelWindow::getTopLevelWindow(0)->addKeyListener(this); } @@ -274,6 +298,19 @@ LfpDisplayCanvas::~LfpDisplayCanvas() deleteAndZero(screenBufferMean); deleteAndZero(screenBufferMax); + // de-allocate 3d-array samplesPerPixel [nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL]; + + for(int i=0;i<nChans;i++) + { + for(int j=0;j<MAX_N_SAMP;j++) + { + free(samplesPerPixel[i][j]); + } + free(samplesPerPixel[i]); + } + free(samplesPerPixel); + + TopLevelWindow::getTopLevelWindow(0)->removeKeyListener(this); } @@ -302,7 +339,10 @@ void LfpDisplayCanvas::resized() eventDisplayInterfaces[i]->setBounds(500+(floor(i/2)*20), getHeight()-40+(i%2)*20, 40, 20); // arrange event channel buttons in two rows eventDisplayInterfaces[i]->repaint(); } - + + histogramSlider->setBounds(1000,getHeight()-50,200,22); + supersampleSlider->setBounds(1000,getHeight()-25,200,22); + int bh = 25/typeButtons.size(); for (int i = 0; i < typeButtons.size(); i++) { @@ -574,6 +614,15 @@ void LfpDisplayCanvas::comboBoxChanged(ComboBox* cb) } +void sliderValueChanged (Slider* slider) +{ + // if (slider == &frequencySlider) + // durationSlider.setValue (1.0 / frequencySlider.getValue(), dontSendNotification); + // else if (slider == &durationSlider) + // frequencySlider.setValue (1.0 / durationSlider.getValue(), dontSendNotification); +} + + int LfpDisplayCanvas::getChannelHeight() { return spreads[spreadSelection->getSelectedId()-1].getIntValue(); @@ -753,9 +802,10 @@ void LfpDisplayCanvas::updateScreenBuffer() float sample_min = 1000000; float sample_max = -1000000; float sample_mean = 0; - int c = 0; + int nextpix = (dbi +(int)ratio) % displayBufferSize; // position to next pixels index + int c = 0; for (int j = dbi; j < nextpix; j++) { float sample_current = displayBuffer->getSample(channel, j); @@ -771,14 +821,30 @@ void LfpDisplayCanvas::updateScreenBuffer() sample_max=sample_current; } c++; - } - - sample_mean = sample_mean/c; - screenBufferMean->addSample(channel, sbi, sample_mean*gain); - screenBufferMin->addSample(channel, sbi, sample_min*gain); - screenBufferMax->addSample(channel, sbi, sample_max*gain); - + + // similarly, for each pixel on the screen, we want a list of all values so we can draw a histogram later + // for simplicity, we'll just do this as 2d array, samplesPerPixel[px][samples] + // with an additional array sampleCountPerPixel[px] that holds the N samples per pixel + if (channel < nChans) // we're looping over one 'extra' channel for events above, so make sure not to loop over that one here + { + c = 0; + for (int j = dbi; j < nextpix & c < MAX_N_SAMP_PER_PIXEL; j++) + { + float sample_current = displayBuffer->getSample(channel, j); + samplesPerPixel[channel][sbi][c]=sample_current; + c++; + } + if (c>0){ + sampleCountPerPixel[sbi]=c-1; // save count of samples for this pixel + }else{ + sampleCountPerPixel[sbi]=0; + } + sample_mean = sample_mean/c; + screenBufferMean->addSample(channel, sbi, sample_mean*gain); + screenBufferMin->addSample(channel, sbi, sample_min*gain); + screenBufferMax->addSample(channel, sbi, sample_max*gain); + } sbi++; } @@ -832,6 +898,16 @@ const float LfpDisplayCanvas::getYCoordMax(int chan, int samp) return *screenBufferMax->getReadPointer(chan, samp); } +const float* LfpDisplayCanvas::getSamplesPerPixel(int chan, int px) +{ + return samplesPerPixel[chan][px]; +} +const int LfpDisplayCanvas::getSampleCountPerPixel(int px) +{ + return sampleCountPerPixel[px]; +} + + bool LfpDisplayCanvas::getInputInvertedState() { @@ -1078,6 +1154,14 @@ int LfpDisplayCanvas::getRangeStep(ChannelType type) return rangeSteps[type]; } +void LfpDisplayCanvas::sliderValueChanged(Slider* slider) +{ + + sliderEvent(slider); +} + +void LfpDisplayCanvas::sliderEvent(Slider* slider) {} + // ------------------------------------------------------------- LfpTimescale::LfpTimescale(LfpDisplayCanvas* c) : canvas(c) @@ -1785,37 +1869,118 @@ void LfpChannelDisplay::paint(Graphics& g) } //std::cout << "e " << canvas->getYCoord(canvas->getNumChannels()-1, i) << std::endl; - g.setColour(lineColour); - - if (drawMethod) // switched between to line drawing and pixel wise drawing + + + // set max-min range for plotting, used in all methods + double a = (canvas->getYCoordMax(chan, i)/range*channelHeightFloat)+getHeight()/2; + double b = (canvas->getYCoordMin(chan, i)/range*channelHeightFloat)+getHeight()/2; + //double m = (canvas->getYCoordMean(chan, i)/range*channelHeightFloat)+getHeight()/2; + if (a<b) { - - // drawLine makes for ok anti-aliased plots, but is pretty slow - g.drawLine(i, - (canvas->getYCoord(chan, i)/range*channelHeightFloat)+getHeight()/2, - i+stepSize, - (canvas->getYCoord(chan, i+stepSize)/range*channelHeightFloat)+getHeight()/2); - - + from = (a); + to = (b); } else { + from = (b); + to = (a); + } + int samplerange=to-from; - // // pixel wise line plot has no anti-aliasing, but runs much faster - double a = (canvas->getYCoordMax(chan, i)/range*channelHeightFloat)+getHeight()/2; - double b = (canvas->getYCoordMin(chan, i)/range*channelHeightFloat)+getHeight()/2; - //double m = (canvas->getYCoordMean(chan, i)/range*channelHeightFloat)+getHeight()/2; - if (a<b) - { - from = (a); - to = (b); - } - else + if (drawMethod) // switched between 'supersampled' drawing and simple pixel wise drawing + { // histogram based supersampling method + + const float *samplesThisPixel = canvas->getSamplesPerPixel(chan, i); + int sampleCountThisPixel = canvas->getSampleCountPerPixel(i); + + if (samplerange>0 & sampleCountThisPixel>1) { - from = (b); - to = (a); + // drawLine makes for ok anti-aliased plots, but is pretty slow + //g.drawLine(i, + // (canvas->getYCoord(chan, i)/range*channelHeightFloat)+getHeight()/2, + // i+stepSize, + // (canvas->getYCoord(chan, i+stepSize)/range*channelHeightFloat)+getHeight()/2); + + + //double a = (samplesThisPixel[sampleCountThisPixel]/range*channelHeightFloat)+getHeight()/2; + //g.setPixel(i,a); + float localHist[samplerange]; // simple histogram + float rangeHist[samplerange]; // paired range histogram, same as plotting at higher res. and subsampling + + for (int k=0; k<samplerange; k++) + { + localHist[k]=0; + rangeHist[k]=0; + } + + /* + for (int k=0; k<=sampleCountThisPixel; k++) // add up simple histogram + { + int cs = (((samplesThisPixel[k]/range*channelHeightFloat)+getHeight()/2)-from); + if (cs<0) {cs=0;}; + if (cs>samplerange) {cs=samplerange;}; + localHist[cs]++; + } + */ + + for (int k=0; k<=sampleCountThisPixel; k++) // add up simple paired-range histogram - for each pair fill intermediate with uniform distr. + { + int cs_this = (((samplesThisPixel[k]/range*channelHeightFloat)+getHeight()/2)-from); + int cs_next = (((samplesThisPixel[k+1]/range*channelHeightFloat)+getHeight()/2)-from); + + if (cs_this<0) {cs_this=0;}; + if (cs_this>samplerange) {cs_this=samplerange;}; + if (cs_next<0) {cs_next=0;}; + if (cs_next>samplerange) {cs_next=samplerange;}; + + int hfrom=0; + int hto=0; + + if (cs_this<cs_next) + { + hfrom = (cs_this); hto = (cs_next); + } + else + { + hfrom = (cs_next); hto = (cs_this); + } + int hrange=hto-hfrom; + float ha=1; + for (int l=hfrom; l<=hto; l++) + { + rangeHist[l]+=ha; + } + } + + + for (int s = 0; s < samplerange; s ++) + { + float a=(rangeHist[s])/(sampleCountThisPixel/2); + if (a>1.0f) {a=1.0f;}; + if (a<0.0f) {a=0.0f;}; + + + //g.setColour( lineColour.interpolatedWith(Colour(255,255,255),0.5f).withAlpha(a) ); // mix in 50% white, alpha from histogram + g.setColour( lineColour.withMultipliedBrightness(2.0f).interpolatedWith(lineColour.withSaturation(0.1f).withMultipliedBrightness(0.4f),1-a) ); + + g.setPixel(i,from+s); + } + + } else { + g.setColour(lineColour); + g.setPixel(i,from); + + } - + + + + } + else + { // simple per-pixel min-max drawing + // pixel wise line plot has no anti-aliasing, but runs much faster + + g.setColour(lineColour); //g.setColour(lineColour.withMultipliedBrightness( 1+(((((float)(to-from)*range)/getHeight())-0.01)*2) )); // make spikes etc slightly brighter @@ -2059,6 +2224,7 @@ void LfpChannelDisplayInfo::resized() } + // Event display Options -------------------------------------------------------------------- EventDisplayInterface::EventDisplayInterface(LfpDisplay* display_, LfpDisplayCanvas* canvas_, int chNum): diff --git a/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.h b/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.h index 0e99bf01dc1058ee1001a2f3a05a262c373e6648..d78670c5ea0942755f1aa53d073f75d7d095cda9 100644 --- a/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.h +++ b/Source/Plugins/LfpDisplayNode/LfpDisplayCanvas.h @@ -46,10 +46,10 @@ class LfpViewport; */ class LfpDisplayCanvas : public Visualizer, + public Slider::Listener, public ComboBox::Listener, public Button::Listener, public KeyListener - { public: LfpDisplayCanvas(LfpDisplayNode* n); @@ -81,7 +81,10 @@ public: const float getXCoord(int chan, int samp); const float getYCoord(int chan, int samp); - + + const float *getSamplesPerPixel(int chan, int px); + const int getSampleCountPerPixel(int px); + const float getYCoordMin(int chan, int samp); const float getYCoordMean(int chan, int samp); const float getYCoordMax(int chan, int samp); @@ -91,7 +94,14 @@ public: void comboBoxChanged(ComboBox* cb); void buttonClicked(Button* button); - + + /** Handles slider events for all editors. */ + void sliderValueChanged(Slider* slider); + + /** Called by sliderValueChanged(). Deals with clicks on custom sliders. Subclasses + of GenericEditor should modify this method only.*/ + void sliderEvent(Slider* slider); + void saveVisualizerParameters(XmlElement* xml); void loadVisualizerParameters(XmlElement* xml); @@ -125,6 +135,7 @@ private: static const int MAX_N_CHAN = 2048; // maximum number of channels static const int MAX_N_SAMP = 5000; // maximum display size in pixels + static const int MAX_N_SAMP_PER_PIXEL = 1000; // maximum samples considered for dreawing each pixel //float waves[MAX_N_CHAN][MAX_N_SAMP*2]; // we need an x and y point for each sample LfpDisplayNode* processor; @@ -151,6 +162,10 @@ private: ScopedPointer<UtilityButton> drawMethodButton; ScopedPointer<UtilityButton> pauseButton; OwnedArray<UtilityButton> typeButtons; + + + ScopedPointer<Slider> histogramSlider; + ScopedPointer<Slider> supersampleSlider; StringArray voltageRanges[CHANNEL_TYPES]; StringArray timebases; @@ -180,6 +195,10 @@ private: int displayBufferSize; int scrollBarThickness; + + //float samplesPerPixel[MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL]; + float*** samplesPerPixel; + int sampleCountPerPixel[MAX_N_SAMP]; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LfpDisplayCanvas);