diff --git a/Plugins/LfpDisplayNode/CMakeLists.txt b/Plugins/LfpDisplayNode/CMakeLists.txt index 4b6ab99ef5..7e0e53a261 100644 --- a/Plugins/LfpDisplayNode/CMakeLists.txt +++ b/Plugins/LfpDisplayNode/CMakeLists.txt @@ -10,9 +10,39 @@ add_sources(${PLUGIN_NAME} LfpDisplayNode.h LfpDisplayEditor.cpp LfpDisplayEditor.h + EventDisplayInterface.cpp + EventDisplayInterface.h + LfpBitmapPlotter.h + LfpBitmapPlotterInfo.h + LfpChannelColourScheme.cpp + LfpChannelColourScheme.h + LfpChannelDisplay.cpp + LfpChannelDisplay.h + LfpChannelDisplayInfo.cpp + LfpChannelDisplayInfo.h + LfpDefaultColourScheme.cpp + LfpDefaultColourScheme.h + LfpDisplay.cpp + LfpDisplay.h LfpDisplayCanvas.cpp LfpDisplayCanvas.h + LfpDisplayOptions.cpp + LfpDisplayOptions.h + LfpGradientColourScheme.cpp + LfpGradientColourScheme.h + LfpMonochromaticColourScheme.cpp + LfpMonochromaticColourScheme.h + LfpTimescale.cpp + LfpTimescale.h + LfpViewport.cpp + LfpViewport.h + PerPixelBitmapPlotter.cpp + PerPixelBitmapPlotter.h + ShowHideOptionsButton.cpp + ShowHideOptionsButton.h + SupersampledBitmapPlotter.cpp + SupersampledBitmapPlotter.h ) #optional: create IDE groups -#plugin_create_filters() \ No newline at end of file +#plugin_create_filters() diff --git a/Plugins/LfpDisplayNode/EventDisplayInterface.cpp b/Plugins/LfpDisplayNode/EventDisplayInterface.cpp new file mode 100644 index 0000000000..4e68d8b245 --- /dev/null +++ b/Plugins/LfpDisplayNode/EventDisplayInterface.cpp @@ -0,0 +1,108 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "EventDisplayInterface.h" +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include "ShowHideOptionsButton.h" +#include "LfpDisplayOptions.h" +#include "LfpTimescale.h" +#include "LfpDisplay.h" +#include "LfpChannelDisplay.h" +#include "LfpChannelDisplayInfo.h" +#include "LfpViewport.h" +#include "LfpBitmapPlotterInfo.h" +#include "LfpBitmapPlotter.h" +#include "PerPixelBitmapPlotter.h" +#include "SupersampledBitmapPlotter.h" +#include "LfpChannelColourScheme.h" + +#include + +using namespace LfpViewer; + +#pragma mark - EventDisplayInterface - +// Event display Options -------------------------------------------------------------------- + +EventDisplayInterface::EventDisplayInterface(LfpDisplay* display_, LfpDisplayCanvas* canvas_, int chNum): + isEnabled(true), display(display_), canvas(canvas_) +{ + + channelNumber = chNum; + + chButton = new UtilityButton(String(channelNumber+1), Font("Small Text", 13, Font::plain)); + chButton->setRadius(5.0f); + chButton->setBounds(4,4,14,14); + chButton->setEnabledState(true); + chButton->setCorners(true, false, true, false); + //chButton.color = display->channelColours[channelNumber*2]; + chButton->addListener(this); + addAndMakeVisible(chButton); + + checkEnabledState(); + +} + +EventDisplayInterface::~EventDisplayInterface() +{ + +} + +void EventDisplayInterface::checkEnabledState() +{ + isEnabled = display->getEventDisplayState(channelNumber); + + //repaint(); +} + +void EventDisplayInterface::buttonClicked(Button* button) +{ + checkEnabledState(); + if (isEnabled) + { + display->setEventDisplayState(channelNumber, false); + } + else + { + display->setEventDisplayState(channelNumber, true); + } + + repaint(); + +} + +void EventDisplayInterface::paint(Graphics& g) +{ + + checkEnabledState(); + + if (isEnabled) + { + g.setColour(display->channelColours[channelNumber*2]); + g.fillRoundedRectangle(2,2,18,18,6.0f); + } + + //g.drawText(String(channelNumber), 8, 2, 200, 15, Justification::left, false); + +} + diff --git a/Plugins/LfpDisplayNode/EventDisplayInterface.h b/Plugins/LfpDisplayNode/EventDisplayInterface.h new file mode 100644 index 0000000000..047b3a9d29 --- /dev/null +++ b/Plugins/LfpDisplayNode/EventDisplayInterface.h @@ -0,0 +1,66 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __EVENTDISPLAYINTERFACE_H__ +#define __EVENTDISPLAYINTERFACE_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +namespace LfpViewer { +#pragma mark - EventDisplayInterface - +//============================================================================== +/** + Interface class for Event Display channels. + */ +class EventDisplayInterface : public Component, + public Button::Listener +{ +public: + EventDisplayInterface(LfpDisplay*, LfpDisplayCanvas*, int chNum); + ~EventDisplayInterface(); + + void paint(Graphics& g); + + void buttonClicked(Button* button); + + void checkEnabledState(); + + bool isEnabled; + +private: + + int channelNumber; + + LfpDisplay* display; + LfpDisplayCanvas* canvas; + + ScopedPointer chButton; + +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpBitmapPlotter.h b/Plugins/LfpDisplayNode/LfpBitmapPlotter.h new file mode 100644 index 0000000000..1db48ffb03 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpBitmapPlotter.h @@ -0,0 +1,55 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __LFPBITMAPPLOTTER_H__ +#define __LFPBITMAPPLOTTER_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +namespace LfpViewer { +#pragma mark - LfpBitmapPlotter - +//============================================================================== +/** + Interface class for different plotting methods. + */ +class LfpBitmapPlotter +{ +public: + LfpBitmapPlotter(LfpDisplay * lfpDisplay) + : display(lfpDisplay) + {} + virtual ~LfpBitmapPlotter() {} + + /** Plots one subsample of data from a single channel to the bitmap provided */ + virtual void plot(Image::BitmapData &bitmapData, LfpBitmapPlotterInfo &plotterInfo) = 0; + +protected: + LfpDisplay * display; +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpBitmapPlotterInfo.h b/Plugins/LfpDisplayNode/LfpBitmapPlotterInfo.h new file mode 100644 index 0000000000..c0030547c6 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpBitmapPlotterInfo.h @@ -0,0 +1,61 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __LFPBITMAPPLOTTERINFO_H__ +#define __LFPBITMAPPLOTTERINFO_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +namespace LfpViewer { +#pragma mark - LfpBitmapPlotterInfo - +//============================================================================== +/** + Information struct for plotting method encapsulation classes. + */ +struct LfpBitmapPlotterInfo +{ + int channelID; + int samp; + int to; + int from; + int x; + int y; + int height; + int width; + float channelHeightFloat; + std::array samplesPerPixel; + int sampleCountPerPixel; + float range; + int samplerange; + float histogramParameterA; + Colour lineColour; + Colour lineColourBright; + Colour lineColourDark; +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpChannelColourScheme.cpp b/Plugins/LfpDisplayNode/LfpChannelColourScheme.cpp new file mode 100644 index 0000000000..5ecb09704a --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpChannelColourScheme.cpp @@ -0,0 +1,57 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "LfpChannelColourScheme.h" +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include "ShowHideOptionsButton.h" +#include "LfpDisplayOptions.h" +#include "LfpTimescale.h" +#include "LfpDisplay.h" +#include "LfpChannelDisplay.h" +#include "LfpChannelDisplayInfo.h" +#include "EventDisplayInterface.h" +#include "LfpViewport.h" +#include "LfpBitmapPlotterInfo.h" +#include "LfpBitmapPlotter.h" +#include "PerPixelBitmapPlotter.h" +#include "SupersampledBitmapPlotter.h" + +#include + +using namespace LfpViewer; + +#pragma mark - LfpChannelColourScheme - + +int LfpChannelColourScheme::colourGrouping = 1; + +void LfpChannelColourScheme::setColourGrouping(int grouping) +{ + colourGrouping = grouping; +} + +int LfpChannelColourScheme::getColourGrouping() +{ + return colourGrouping; +} + diff --git a/Plugins/LfpDisplayNode/LfpChannelColourScheme.h b/Plugins/LfpDisplayNode/LfpChannelColourScheme.h new file mode 100644 index 0000000000..539af16d06 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpChannelColourScheme.h @@ -0,0 +1,71 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __LFPCHANNELCOLOURSCHEME_H__ +#define __LFPCHANNELCOLOURSCHEME_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +namespace LfpViewer { +#pragma mark - LfpChannelColourScheme - +/** + Interface for a color scheme object + */ +class LfpChannelColourScheme : public Component +{ +public: + LfpChannelColourScheme(int numColourChannels_, LfpDisplay* display, LfpDisplayCanvas* canvas) + : lfpDisplay(display) + , canvas(canvas) + , numColourChannels(numColourChannels_) + { } + + virtual ~LfpChannelColourScheme() {} + + void paint(Graphics &g) override {} + void resized() override {} + + virtual const Colour getColourForIndex(int index) const = 0; + + /** Returns true if a color scheme has configurable UI elements that + must be drawn to the options drawer. Subclasses should override this + if they have drawable elements in the options drawer. */ + virtual bool hasConfigurableElements() { return false; } + + void setColourGrouping(int grouping); + int getColourGrouping(); + +protected: + LfpDisplay * lfpDisplay; + LfpDisplayCanvas * canvas; + + int numColourChannels; + static int colourGrouping; +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpChannelDisplay.cpp b/Plugins/LfpDisplayNode/LfpChannelDisplay.cpp new file mode 100644 index 0000000000..af878ee2b0 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpChannelDisplay.cpp @@ -0,0 +1,516 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "LfpChannelDisplay.h" +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include "ShowHideOptionsButton.h" +#include "LfpDisplayOptions.h" +#include "LfpTimescale.h" +#include "LfpDisplay.h" +#include "LfpChannelDisplayInfo.h" +#include "EventDisplayInterface.h" +#include "LfpViewport.h" +#include "LfpBitmapPlotterInfo.h" +#include "LfpBitmapPlotter.h" +#include "PerPixelBitmapPlotter.h" +#include "SupersampledBitmapPlotter.h" +#include "LfpChannelColourScheme.h" + +#include + +using namespace LfpViewer; + +#pragma mark - LfpChannelDisplay - +// ------------------------------------------------------------------ + +LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, LfpDisplay* d, LfpDisplayOptions* o, int channelNumber) + : canvas(c) + , display(d) + , options(o) + , isSelected(false) + , chan(channelNumber) + , name("") + , drawableChan(channelNumber) + , channelOverlap(300) + , channelHeight(30) + , range(250.0f) + , isEnabled(true) + , inputInverted(false) + , canBeInverted(true) + , drawMethod(false) + , isHidden(false) +{ + + name = String(channelNumber+1); // default is to make the channelNumber the name + + channelHeightFloat = (float) channelHeight; + + channelFont = Font("Default", channelHeight*0.6, Font::plain); + + lineColour = Colour(255,255,255); + + type = options->getChannelType(channelNumber); + typeStr = options->getTypeName(type); + +} + +LfpChannelDisplay::~LfpChannelDisplay() +{ + +} + +void LfpChannelDisplay::resized() +{ + // all of this will likely need to be moved into the sharedLfpDisplay image in the lfpDisplay, not here + // now that the complete height is know, the sharedLfpDisplay image that we'll draw the pixel-wise lfp plot to needs to be resized + //lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); +} + +void LfpChannelDisplay::updateType() +{ + type = options->getChannelType(chan); + typeStr = options->getTypeName(type); +} + +void LfpChannelDisplay::setEnabledState(bool state) +{ + + //if (state) + //std::cout << "Setting channel " << name << " to true." << std::endl; + //else + //std::cout << "Setting channel " << name << " to false." << std::endl; + + isEnabled = state; + +} + +void LfpChannelDisplay::setHidden(bool isHidden_) +{ + isHidden = isHidden_; + isEnabled = !isHidden; +} + +void LfpChannelDisplay::pxPaint() +{ + if (!isEnabled) return; // return early if THIS display is not enabled + + Image::BitmapData bdLfpChannelBitmap(display->lfpChannelBitmap, 0,0, display->lfpChannelBitmap.getWidth(), display->lfpChannelBitmap.getHeight()); + + int center = getHeight()/2; + + // max and min of channel in absolute px coords for event displays etc - actual data might be drawn outside of this range + int jfrom_wholechannel= (int) (getY()+center-channelHeight/2)+1 +0 ; + int jto_wholechannel= (int) (getY()+center+channelHeight/2) -0; + + //int jfrom_wholechannel_almost= (int) (getY()+center-channelHeight/3)+1 +0 ; // a bit less tall, for saturation warnings + //int jto_wholechannel_almost= (int) (getY()+center+channelHeight/3) -0; + + // max and min of channel, this is the range where actual data is drawn + int jfrom_wholechannel_clip= (int) (getY()+center-(channelHeight)*canvas->channelOverlapFactor)+1 ; + int jto_wholechannel_clip = (int) (getY()+center+(channelHeight)*canvas->channelOverlapFactor) -0; + + if (jfrom_wholechannel<0) {jfrom_wholechannel=0;}; + if (jto_wholechannel >= display->lfpChannelBitmap.getHeight()) {jto_wholechannel=display->lfpChannelBitmap.getHeight()-1;}; + + // draw most recent drawn sample position + if (canvas->screenBufferIndex[chan]+1 <= display->lfpChannelBitmap.getWidth()) + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k+=2) // draw line + bdLfpChannelBitmap.setPixelColour(canvas->screenBufferIndex[chan]+1,k, Colours::yellow); + + bool clipWarningHi =false; // keep track if something clipped in the display, so we can draw warnings after the data pixels are done + bool clipWarningLo =false; + + bool saturateWarningHi =false; // similar, but for saturating the amplifier, not just the display - make this warning very visible + bool saturateWarningLo =false; + + // pre compute some colors for later so we dont do it once per pixel. + Colour lineColourBright = lineColour.withMultipliedBrightness(2.0f); + //Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f).withMultipliedBrightness(0.3f); + Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f*canvas->histogramParameterB).withMultipliedBrightness(canvas->histogramParameterB); + + int stepSize = 1; + int from = 0; // for vertical line drawing in the LFP data + int to = 0; + + int ifrom = canvas->lastScreenBufferIndex[chan] - 1; // need to start drawing a bit before the actual redraw window for the interpolated line to join correctly + + if (ifrom < 0) + ifrom = 0; + + int ito = canvas->screenBufferIndex[chan] +0; + + if (fullredraw) + { + ifrom = 0; //canvas->leftmargin; + ito = getWidth()-stepSize; + fullredraw = false; + } + + bool drawWithOffsetCorrection = display->getMedianOffsetPlotting(); + + LfpBitmapPlotterInfo plotterInfo; // hold and pass plotting info for each plotting method class + + for (int i = ifrom; i < ito ; i += stepSize) // redraw only changed portion + { + if (i < display->lfpChannelBitmap.getWidth()) + { + //draw zero line + int m = getY()+center; + + if(m > 0 && m < display->lfpChannelBitmap.getHeight()) + { + if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) { // make sure we're not drawing over an existing plot from another channel + bdLfpChannelBitmap.setPixelColour(i,m,Colour(50,50,50)); + } + } + + //draw range markers + if (isSelected) + { + int start = getY()+center -channelHeight/2; + int jump = channelHeight/4; + + for (m = start; m <= start + jump*4; m += jump) + { + if (m > 0 && m < display->lfpChannelBitmap.getHeight()) + { + if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) // make sure we're not drawing over an existing plot from another channel + bdLfpChannelBitmap.setPixelColour(i, m, Colour(80,80,80)); + } + } + } + + // draw event markers + int rawEventState = canvas->getYCoord(canvas->getNumChannels(), i);// get last channel+1 in buffer (represents events) + + for (int ev_ch = 0; ev_ch < 8 ; ev_ch++) // for all event channels + { + if (display->getEventDisplayState(ev_ch)) // check if plotting for this channel is enabled + { + if (rawEventState & (1 << ev_ch)) // events are representet by a bit code, so we have to extract the individual bits with a mask + { +// std::cout << "Drawing event." << std::endl; + Colour currentcolor=display->channelColours[ev_ch*2]; + + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++) // draw line + bdLfpChannelBitmap.setPixelColour(i,k,bdLfpChannelBitmap.getPixelColour(i,k).interpolatedWith(currentcolor,0.3f)); + + } + } + } + + //std::cout << "e " << canvas->getYCoord(canvas->getNumChannels()-1, i) << std::endl; + + // set max-min range for plotting, used in all methods + double a = (canvas->getYCoordMax(chan, i)/range*channelHeightFloat); + double b = (canvas->getYCoordMin(chan, i)/range*channelHeightFloat); + + double mean = (canvas->getMean(chan)/range*channelHeightFloat); + + if (drawWithOffsetCorrection) + { + a -= mean; + b -= mean; + } + + double a_raw = canvas->getYCoordMax(chan, i); + double b_raw = canvas->getYCoordMin(chan, i); + double from_raw=0; double to_raw=0; + + //double m = (canvas->getYCoordMean(chan, i)/range*channelHeightFloat)+getHeight()/2; + if (achannelOverlapFactor; + if (lm>0) + lm=-lm; + + if (from > -lm) {from = -lm; clipWarningHi=true;}; + if (to > -lm) {to = -lm; clipWarningHi=true;}; + if (from < lm) {from = lm; clipWarningLo=true;}; + if (to < lm) {to = lm; clipWarningLo=true;}; + + // test if raw data is clipped for displaying saturation warning + if (from_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;}; + if (to_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;}; + if (from_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;}; + if (to_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;}; + + bool spikeFlag = display->getSpikeRasterPlotting() + && !(saturateWarningHi || saturateWarningLo) + && (from_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold() + || to_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold()); + + from = from + getHeight()/2; // so the plot is centered in the channeldisplay + to = to + getHeight()/2; + + int samplerange = to - from; + + if (drawMethod) // switched between 'supersampled' drawing and simple pixel wise drawing + { // histogram based supersampling method + plotterInfo.channelID = chan; + plotterInfo.samp = i; + plotterInfo.y = getY(); + plotterInfo.from = from; + plotterInfo.height = getHeight(); + plotterInfo.lineColourBright = lineColourBright; + plotterInfo.lineColourDark = lineColourDark; + plotterInfo.range = range; + plotterInfo.channelHeightFloat = channelHeightFloat; + plotterInfo.sampleCountPerPixel = canvas->getSampleCountPerPixel(i); + plotterInfo.samplesPerPixel = canvas->getSamplesPerPixel(chan, i); + plotterInfo.histogramParameterA = canvas->histogramParameterA; + plotterInfo.samplerange = samplerange; + + // TODO: (kelly) complete transition toward plotter class encapsulation +// display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); + + } + else //drawmethod + { // simple per-pixel min-max drawing, has no anti-aliasing, but runs faster + + plotterInfo.channelID = chan; + plotterInfo.y = getY(); + plotterInfo.from = from; + plotterInfo.to = to; + plotterInfo.samp = i; + plotterInfo.lineColour = lineColour; + + // TODO: (kelly) complete transition toward plotter class encapsulation +// display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); // plotterInfo is prepared above + + } + + // Do the actual plotting for the selected plotting method + if (!display->getSpikeRasterPlotting()) + display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); + + // now draw warnings, if needed + if (canvas->drawClipWarning) // draw simple warning if display cuts off data + { + + if(clipWarningHi) { + for (int j=0; j<=3; j++) + { + int clipmarker = jto_wholechannel_clip; + + if(clipmarker>0 && clipmarkerlfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,clipmarker-j,Colour(255,255,255)); + } + } + } + + if(clipWarningLo) { + for (int j=0; j<=3; j++) + { + int clipmarker = jfrom_wholechannel_clip; + + if(clipmarker>0 && clipmarkerlfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,clipmarker+j,Colour(255,255,255)); + } + } + } + + clipWarningHi=false; + clipWarningLo=false; + } + + if (spikeFlag) // draw spikes + { + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line + if(k>0 && klfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,k,lineColour); + } + }; + } + + if (canvas->drawSaturationWarning) // draw bigger warning if actual data gets cuts off + { + + if(saturateWarningHi || saturateWarningLo) { + + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line + Colour thiscolour=Colour(255,0,0); + if (fmod((i+k),50)>25){ + thiscolour=Colour(255,255,255); + } + if(k>0 && klfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,k,thiscolour); + } + }; + } + + saturateWarningHi=false; // we likely just need one of this because for this warning we dont care if its saturating on the positive or negative side + saturateWarningLo=false; + } + } // if i < getWidth() + + } // for i (x pixels) + +} + +void LfpChannelDisplay::paint(Graphics& g) {} + +PopupMenu LfpChannelDisplay::getOptions() +{ + + PopupMenu menu; + menu.addItem(1, "Invert signal", true, inputInverted); + + return menu; +} + +void LfpChannelDisplay::changeParameter(int id) +{ + switch (id) + { + case 1: + setInputInverted(!inputInverted); + default: + break; + } +} + +void LfpChannelDisplay::setRange(float r) +{ + + range = r; + + //std::cout << "Range: " << r << std::endl; +} + +int LfpChannelDisplay::getRange() +{ + return range; +} + +void LfpChannelDisplay::select() +{ + isSelected = true; +} + +void LfpChannelDisplay::deselect() +{ + isSelected = false; +} + +bool LfpChannelDisplay::getSelected() +{ + return isSelected; +} + +void LfpChannelDisplay::setColour(Colour c) +{ + lineColour = c; +} + +void LfpChannelDisplay::setChannelHeight(int c) +{ + channelHeight = c; + + channelHeightFloat = (float) channelHeight; + + if (!inputInverted) + channelHeightFloat = -channelHeightFloat; + + channelOverlap = channelHeight*2; +} + +int LfpChannelDisplay::getChannelHeight() +{ + + return channelHeight; +} + +void LfpChannelDisplay::setChannelOverlap(int overlap) +{ + channelOverlap = overlap; +} + +int LfpChannelDisplay::getChannelOverlap() +{ + return channelOverlap; +} + +int LfpChannelDisplay::getChannelNumber() +{ + return chan; +} + +String LfpChannelDisplay::getName() +{ + return name; +} + +int LfpChannelDisplay::getDrawableChannelNumber() +{ + return drawableChan; +} + +void LfpChannelDisplay::setDrawableChannelNumber(int channelId) +{ + drawableChan = channelId; +} + +void LfpChannelDisplay::setCanBeInverted(bool _canBeInverted) +{ + canBeInverted = _canBeInverted; +} + +void LfpChannelDisplay::setInputInverted(bool isInverted) +{ + if (canBeInverted) + { + inputInverted = isInverted; + setChannelHeight(channelHeight); + } +} + +void LfpChannelDisplay::setDrawMethod(bool isDrawMethod) +{ + + drawMethod = isDrawMethod; + +} + +void LfpChannelDisplay::setName(String name_) +{ + name = name_; +} + +DataChannel::DataChannelTypes LfpChannelDisplay::getType() +{ + return type; +} + diff --git a/Plugins/LfpDisplayNode/LfpChannelDisplay.h b/Plugins/LfpDisplayNode/LfpChannelDisplay.h new file mode 100644 index 0000000000..8fc848e11c --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpChannelDisplay.h @@ -0,0 +1,149 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __LFPCHANNELDISPLAY_H__ +#define __LFPCHANNELDISPLAY_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +namespace LfpViewer { +#pragma mark - LfpChannelDisplay - +//============================================================================== +/** + Displays the information pertaining to a single data channel. + */ +class LfpChannelDisplay : public Component +{ +public: + LfpChannelDisplay(LfpDisplayCanvas*, LfpDisplay*, LfpDisplayOptions*, int channelNumber); + ~LfpChannelDisplay(); + + void resized(); + + void paint(Graphics& g); + + void pxPaint(); // like paint, but just populate lfpChannelBitmap + // needs to avoid a paint(Graphics& g) mechanism here becauswe we need to clear the screen in the lfpDisplay repaint(), + // because otherwise we cant deal with the channel overlap (need to clear a vertical section first, _then_ all channels are dawn, so cant do it per channel) + + void select(); + void deselect(); + + bool getSelected(); + + void setName(String); + + void setColour(Colour c); + + void setChannelHeight(int); + int getChannelHeight(); + + void setChannelOverlap(int); + int getChannelOverlap(); + + /** Return the assigned channel number */ + int getChannelNumber(); + + /** Return the assigned channel name */ + String getName(); + + /** Returns the assigned channel number for this display, relative + to the subset of channels being drawn to the canvas */ + int getDrawableChannelNumber(); + + /** Set the channel number of this channel relative to the subset of + channels being drawn to the canvas */ + void setDrawableChannelNumber(int channelId); + + void setRange(float range); + int getRange(); + + void setInputInverted(bool); + void setCanBeInverted(bool); + + void setDrawMethod(bool); + + PopupMenu getOptions(); + void changeParameter(const int id); + + void setEnabledState(bool); + bool getEnabledState() + { + return isEnabled; + } + + /** Set the isHidden flag, indicates whether this channel display + should render to screen or not */ + void setHidden(bool); + + /** Return a bool flag describing whether this channel display is + hidden from the canvas */ + bool getHidden() { + return isHidden; + } + + DataChannel::DataChannelTypes getType(); + void updateType(); + + bool fullredraw; // used to indicate that a full redraw is required. is set false after each full redraw + +protected: + + LfpDisplayCanvas* canvas; + LfpDisplay* display; + LfpDisplayOptions* options; + + bool isSelected; + bool isHidden; + + int chan; + int drawableChan; + + String name; + + Font channelFont; + + Colour lineColour; + + int channelOverlap; + int channelHeight; + float channelHeightFloat; + + float range; + + bool isEnabled; + bool inputInverted; + bool canBeInverted; + bool drawMethod; + + DataChannel::DataChannelTypes type; + String typeStr; + +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpChannelDisplayInfo.cpp b/Plugins/LfpDisplayNode/LfpChannelDisplayInfo.cpp new file mode 100644 index 0000000000..225cba14fd --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpChannelDisplayInfo.cpp @@ -0,0 +1,329 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "LfpChannelDisplayInfo.h" +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include "ShowHideOptionsButton.h" +#include "LfpDisplayOptions.h" +#include "LfpTimescale.h" +#include "LfpDisplay.h" +#include "LfpChannelDisplay.h" +#include "EventDisplayInterface.h" +#include "LfpViewport.h" +#include "LfpBitmapPlotterInfo.h" +#include "LfpBitmapPlotter.h" +#include "PerPixelBitmapPlotter.h" +#include "SupersampledBitmapPlotter.h" +#include "LfpChannelColourScheme.h" + +#include + +using namespace LfpViewer; + +#pragma mark - LfpChannelDisplayInfo - +// ------------------------------- + +LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDisplay* display_, LfpDisplayOptions* options_, int ch) + : LfpChannelDisplay(canvas_, display_, options_, ch) +{ + + chan = ch; + x = -1.0f; + y = -1.0f; + +// enableButton = new UtilityButton(String(ch+1), Font("Small Text", 13, Font::plain)); + enableButton = new UtilityButton("", Font("Small Text", 13, Font::plain)); + enableButton->setRadius(5.0f); + + enableButton->setEnabledState(true); + enableButton->setCorners(true, true, true, true); + enableButton->addListener(this); + enableButton->setClickingTogglesState(true); + enableButton->setToggleState(true, dontSendNotification); + + isSingleChannel = false; + + addAndMakeVisible(enableButton); + +} + +void LfpChannelDisplayInfo::updateType() +{ + type = options->getChannelType(chan); + typeStr = options->getTypeName(type); + repaint(); +} + +void LfpChannelDisplayInfo::buttonClicked(Button* button) +{ + + bool state = button->getToggleState(); + + display->setEnabledState(state, chan); + + //UtilityButton* b = (UtilityButton*) button; + + // if (state) + // { + // b->setLabel("ON"); + // } else { + // b->setLabel("OFF"); + // } + + //std::cout << "Turn channel " << chan << " to " << button->getToggleState() << std::endl; + +} + +void LfpChannelDisplayInfo::setEnabledState(bool state) +{ + enableButton->setToggleState(state, sendNotification); +} + +void LfpChannelDisplayInfo::setSingleChannelState(bool state) +{ + isSingleChannel = state; +} + +int LfpChannelDisplayInfo::getChannelSampleRate() +{ + return samplerate; +} + +void LfpChannelDisplayInfo::setChannelSampleRate(int samplerate_) +{ + samplerate = samplerate_; +} + +void LfpChannelDisplayInfo::mouseDrag(const MouseEvent &e) +{ + if (e.mods.isLeftButtonDown()) // double check that we initiate only for left click and hold + { + if (e.mods.isCommandDown() && !display->getSingleChannelState()) // CTRL + drag -> change channel spacing + { + + // init state in our track zooming info struct + if (!display->trackZoomInfo.isScrollingY) + { + auto & zoomInfo = display->trackZoomInfo; + + zoomInfo.isScrollingY = true; + zoomInfo.componentStartHeight = getChannelHeight(); + zoomInfo.zoomPivotRatioY = (getY() + e.getMouseDownY())/(float)display->getHeight(); + zoomInfo.zoomPivotRatioX = (getX() + e.getMouseDownX())/(float)display->getWidth(); + zoomInfo.zoomPivotViewportOffset = getPosition() + e.getMouseDownPosition() - canvas->viewport->getViewPosition(); + + zoomInfo.unpauseOnScrollEnd = !display->isPaused; + if (!display->isPaused) display->options->togglePauseButton(true); + } + + int h = display->trackZoomInfo.componentStartHeight; + int hdiff=0; + int dragDeltaY = -0.1 * (e.getScreenPosition().getY() - e.getMouseDownScreenY()); // invert so drag up -> scale up + +// std::cout << dragDeltaY << std::endl; + if (dragDeltaY > 0) + { + hdiff = 2 * dragDeltaY; + } + else + { + if (h > 5) + hdiff = 2 * dragDeltaY; + } + + if (abs(h) > 100) // accelerate scrolling for large ranges + hdiff *= 3; + + int newHeight = h+hdiff; + + // constrain the spread resizing to max and min values; + if (newHeight < display->trackZoomInfo.minZoomHeight) + { + newHeight = display->trackZoomInfo.minZoomHeight; + } + else if (newHeight > display->trackZoomInfo.maxZoomHeight) + { + newHeight = display->trackZoomInfo.maxZoomHeight; + } + + // return early if there is nothing to update + if (newHeight == getChannelHeight()) + { + return; + } + + // set channel heights for all channel +// display->setChannelHeight(newHeight); + for (int i = 0; i < display->getNumChannels(); ++i) + { + display->channels[i]->setChannelHeight(newHeight); + display->channelInfo[i]->setChannelHeight(newHeight); + } + + options->setSpreadSelection(newHeight, false, true); // update combobox + + canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw + + display->setBounds(0,0,display->getWidth()-0, display->getChannelHeight()*display->drawableChannels.size()); // update height so that the scrollbar is correct + + int newViewportY = display->trackZoomInfo.zoomPivotRatioY * display->getHeight() - display->trackZoomInfo.zoomPivotViewportOffset.getY(); + if (newViewportY < 0) newViewportY = 0; // make sure we don't adjust beyond the edge of the actual view + + canvas->viewport->setViewPosition(0, newViewportY); + } + } +} + +void LfpChannelDisplayInfo::mouseUp(const MouseEvent &e) +{ + if (e.mods.isLeftButtonDown() && display->trackZoomInfo.isScrollingY) + { + display->trackZoomInfo.isScrollingY = false; + if (display->trackZoomInfo.unpauseOnScrollEnd) + { + display->isPaused = false; + display->options->togglePauseButton(false); + } + } +} + +void LfpChannelDisplayInfo::paint(Graphics& g) +{ + + int center = getHeight()/2 - (isSingleChannel?(75):(0)); + const bool showChannelNumbers = options->getChannelNameState(); + + // Draw the channel numbers + g.setColour(Colours::grey); + const String channelString = (isChannelNumberHidden() ? ("--") : + showChannelNumbers ? String(getChannelNumber() + 1) : getName()); + bool isCentered = !getEnabledButtonVisibility(); + + g.drawText(channelString, + showChannelNumbers ? 6 : 2, + center-4, + getWidth()/2, + 10, + isCentered ? Justification::centred : Justification::centredLeft, + false); + + g.setColour(lineColour); + g.fillRect(0, 0, 2, getHeight()); + + if (getChannelTypeStringVisibility()) + { + g.setFont(Font("Small Text", 13, Font::plain)); + g.drawText(typeStr,5,center+10,41,10,Justification::centred,false); + } + // g.setFont(channelHeightFloat*0.3); + g.setFont(Font("Small Text", 11, Font::plain)); + + if (isSingleChannel) + { + g.setColour(Colours::darkgrey); + g.drawText("STD:", 5, center+90,41,10,Justification::centred,false); + g.drawText("MEAN:", 5, center+40,41,10,Justification::centred,false); + + if (x > 0) + { + g.drawText("uV:", 5, center+140,41,10,Justification::centred,false); + } + //g.drawText("Y:", 5, center+200,41,10,Justification::centred,false); + + g.setColour(Colours::grey); + g.drawText(String(canvas->getStd(chan)), 5, center+110,41,10,Justification::centred,false); + g.drawText(String(canvas->getMean(chan)), 5, center+60,41,10,Justification::centred,false); + if (x > 0) + { + //g.drawText(String(x), 5, center+150,41,10,Justification::centred,false); + g.drawText(String(y), 5, center+160,41,10,Justification::centred,false); + } + + } + + // g.drawText(name, 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); + +} + +void LfpChannelDisplayInfo::updateXY(float x_, float y_) +{ + x = x_; + y = y_; +} + +void LfpChannelDisplayInfo::resized() +{ + + int center = getHeight()/2 - (isSingleChannel?(75):(0)); + setEnabledButtonVisibility(getHeight() >= 16); + + if (getEnabledButtonVisibility()) + { + enableButton->setBounds(getWidth()/2 - 10, center - 5, 10, 10); + } + + setChannelNumberIsHidden(getHeight() < 16 && (getDrawableChannelNumber() + 1) % 10 != 0); + + setChannelTypeStringVisibility(getHeight() > 34); +} + +void LfpChannelDisplayInfo::setEnabledButtonVisibility(bool shouldBeVisible) +{ + if (shouldBeVisible) + { + addAndMakeVisible(enableButton); + } + else if (enableButton->isVisible()) + { + removeChildComponent(enableButton); + enableButton->setVisible(false); + } + +} + +bool LfpChannelDisplayInfo::getEnabledButtonVisibility() +{ + return enableButton->isVisible(); +} + +void LfpChannelDisplayInfo::setChannelTypeStringVisibility(bool shouldBeVisible) +{ + channelTypeStringIsVisible = shouldBeVisible; +} + +bool LfpChannelDisplayInfo::getChannelTypeStringVisibility() +{ + return channelTypeStringIsVisible || isSingleChannel; +} + +void LfpChannelDisplayInfo::setChannelNumberIsHidden(bool shouldBeHidden) +{ + channelNumberHidden = shouldBeHidden; +} + +bool LfpChannelDisplayInfo::isChannelNumberHidden() +{ + return channelNumberHidden; +} + diff --git a/Plugins/LfpDisplayNode/LfpChannelDisplayInfo.h b/Plugins/LfpDisplayNode/LfpChannelDisplayInfo.h new file mode 100644 index 0000000000..26ed256ae3 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpChannelDisplayInfo.h @@ -0,0 +1,102 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __LFPCHANNELDISPLAYINFO_H__ +#define __LFPCHANNELDISPLAYINFO_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +#include "LfpChannelDisplay.h" +namespace LfpViewer { +#pragma mark - LfpChannelDisplayInfo - +//============================================================================== +/** + Displays meta data pertaining to an associated channel, such as channel number. + + The enableButton displays the channel number and toggles the drawing of the + associated LfpChannelDisplay waveform on or off. + */ +class LfpChannelDisplayInfo : public LfpChannelDisplay, + public Button::Listener +{ + friend class LfpDisplay; +public: + LfpChannelDisplayInfo(LfpDisplayCanvas*, LfpDisplay*, LfpDisplayOptions*, int channelNumber); + + void paint(Graphics& g); + + void buttonClicked(Button* button); + + void resized(); + + void setEnabledState(bool); + void updateType(); + + void updateXY(float, float); + + void setSingleChannelState(bool); + + /** Returns the sample rate associated with this channel */ + int getChannelSampleRate(); + /** Sets the sample rate associated with this channel */ + void setChannelSampleRate(int samplerate); + + int getSubprocessorIdx() { return subProcessorIdx; } + + void setSubprocessorIdx(int subProcessorIdx_) { subProcessorIdx = subProcessorIdx_; } + + /** Updates the parent LfpDisplay that the track vertical zoom should update */ + virtual void mouseDrag(const MouseEvent &event) override; + + /** Disengages the mouse drag to resize track height */ + virtual void mouseUp(const MouseEvent &event) override; + +private: + + bool isSingleChannel; + float x, y; + + int samplerate; + int subProcessorIdx; + + ScopedPointer enableButton; + + bool channelTypeStringIsVisible; + bool channelNumberHidden; + + void setEnabledButtonVisibility(bool shouldBeVisible); + bool getEnabledButtonVisibility(); + + void setChannelTypeStringVisibility(bool shouldBeVisible); + bool getChannelTypeStringVisibility(); + + void setChannelNumberIsHidden(bool shouldBeHidden); + bool isChannelNumberHidden(); +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpDefaultColourScheme.cpp b/Plugins/LfpDisplayNode/LfpDefaultColourScheme.cpp new file mode 100644 index 0000000000..1087dc5ea4 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpDefaultColourScheme.cpp @@ -0,0 +1,89 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "LfpDefaultColourScheme.h" +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include "ShowHideOptionsButton.h" +#include "LfpDisplayOptions.h" +#include "LfpTimescale.h" +#include "LfpDisplay.h" +#include "LfpChannelDisplay.h" +#include "LfpChannelDisplayInfo.h" +#include "EventDisplayInterface.h" +#include "LfpViewport.h" +#include "LfpBitmapPlotterInfo.h" +#include "LfpBitmapPlotter.h" +#include "PerPixelBitmapPlotter.h" +#include "SupersampledBitmapPlotter.h" +#include "LfpChannelColourScheme.h" + +#include + +using namespace LfpViewer; + +#pragma mark - LfpDefaultColourScheme - + +Array LfpDefaultColourScheme::colourList = []() -> Array { + Array colours; + colours.add(Colour(224,185,36)); + colours.add(Colour(214,210,182)); + colours.add(Colour(243,119,33)); + colours.add(Colour(186,157,168)); + colours.add(Colour(237,37,36)); + colours.add(Colour(179,122,79)); + colours.add(Colour(217,46,171)); + colours.add(Colour(217, 139,196)); + colours.add(Colour(101,31,255)); + colours.add(Colour(141,111,181)); + colours.add(Colour(48,117,255)); + colours.add(Colour(184,198,224)); + colours.add(Colour(116,227,156)); + colours.add(Colour(150,158,155)); + colours.add(Colour(82,173,0)); + colours.add(Colour(125,99,32)); + return colours; +}(); + +LfpDefaultColourScheme::LfpDefaultColourScheme(LfpDisplay* display, LfpDisplayCanvas* canvas) + : LfpViewer::LfpChannelColourScheme(LfpDefaultColourScheme::colourList.size(), display, canvas) +{ + setName("Default"); +} + +void LfpDefaultColourScheme::paint(Graphics &g) +{ + +} + +void LfpDefaultColourScheme::resized() +{ + +} + +const Colour LfpDefaultColourScheme::getColourForIndex(int index) const +{ +// return colourList[index % colourList.size()]; + return colourList[(int(index/colourGrouping)) % colourList.size()]; +} + diff --git a/Plugins/LfpDisplayNode/LfpDefaultColourScheme.h b/Plugins/LfpDisplayNode/LfpDefaultColourScheme.h new file mode 100644 index 0000000000..2ba8e60c43 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpDefaultColourScheme.h @@ -0,0 +1,52 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __LFPDEFAULTCOLOURSCHEME_H__ +#define __LFPDEFAULTCOLOURSCHEME_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +#include "LfpChannelColourScheme.h" +namespace LfpViewer { +#pragma mark - LfpDefaultColourScheme - +class LfpDefaultColourScheme : public LfpChannelColourScheme +{ +public: + LfpDefaultColourScheme(LfpDisplay*, LfpDisplayCanvas*); + virtual ~LfpDefaultColourScheme() {} + + void paint(Graphics &g) override; + void resized() override; + + virtual const Colour getColourForIndex(int index) const override; + +private: + static Array colourList; +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpDisplay.cpp b/Plugins/LfpDisplayNode/LfpDisplay.cpp new file mode 100644 index 0000000000..d52f11eb96 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpDisplay.cpp @@ -0,0 +1,943 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "LfpDisplay.h" +#include "LfpDisplayNode.h" +#include "LfpDisplayCanvas.h" +#include "ShowHideOptionsButton.h" +#include "LfpDisplayOptions.h" +#include "LfpTimescale.h" +#include "LfpChannelDisplay.h" +#include "LfpChannelDisplayInfo.h" +#include "EventDisplayInterface.h" +#include "LfpViewport.h" +#include "LfpBitmapPlotterInfo.h" +#include "LfpBitmapPlotter.h" +#include "PerPixelBitmapPlotter.h" +#include "SupersampledBitmapPlotter.h" +#include "LfpChannelColourScheme.h" +#include "LfpDefaultColourScheme.h" +#include "LfpMonochromaticColourScheme.h" +#include "LfpGradientColourScheme.h" + +#include + +using namespace LfpViewer; + +#pragma mark - LfpDisplay - +// --------------------------------------------------------------- + +LfpDisplay::LfpDisplay(LfpDisplayCanvas* c, Viewport* v) + : singleChan(-1) + , canvas(c) + , viewport(v) + , channelsReversed(false) + , displaySkipAmt(0) + , m_SpikeRasterPlottingFlag(false) +{ + perPixelPlotter = new PerPixelBitmapPlotter(this); + supersampledPlotter = new SupersampledBitmapPlotter(this); + +// colorScheme = new LfpDefaultColourScheme(); + colourSchemeList.add(new LfpDefaultColourScheme(this, canvas)); + colourSchemeList.add(new LfpMonochromaticColourScheme(this, canvas)); + colourSchemeList.add(new LfpGradientColourScheme(this, canvas)); + + activeColourScheme = 0; + + plotter = perPixelPlotter; + m_MedianOffsetPlottingFlag = false; + + totalHeight = 0; + colorGrouping=1; + + range[0] = 1000; + range[1] = 500; + range[2] = 500000; + + addMouseListener(this, true); + + // hue cycle + //for (int i = 0; i < 15; i++) + //{ + // channelColours.add(Colour(float(sin((3.14/2)*(float(i)/15))),float(1.0),float(1),float(1.0))); + //} + +// setBufferedToImage(true); // TODO: (kelly) test + + backgroundColour = Colour(0,18,43); + + //hand-built palette + channelColours.add(Colour(224,185,36)); + channelColours.add(Colour(214,210,182)); + channelColours.add(Colour(243,119,33)); + channelColours.add(Colour(186,157,168)); + channelColours.add(Colour(237,37,36)); + channelColours.add(Colour(179,122,79)); + channelColours.add(Colour(217,46,171)); + channelColours.add(Colour(217, 139,196)); + channelColours.add(Colour(101,31,255)); + channelColours.add(Colour(141,111,181)); + channelColours.add(Colour(48,117,255)); + channelColours.add(Colour(184,198,224)); + channelColours.add(Colour(116,227,156)); + channelColours.add(Colour(150,158,155)); + channelColours.add(Colour(82,173,0)); + channelColours.add(Colour(125,99,32)); + + isPaused=false; + +} + +LfpDisplay::~LfpDisplay() +{ +// deleteAllChildren(); +} + +int LfpDisplay::getNumChannels() +{ + return numChans; +} + +int LfpDisplay::getColorGrouping() +{ + return colorGrouping; +} + +void LfpDisplay::setColorGrouping(int i) +{ + colorGrouping=i; + getColourSchemePtr()->setColourGrouping(i); + setColors(); // so that channel colors get re-assigned + +} + +LfpChannelColourScheme * LfpDisplay::getColourSchemePtr() +{ + return colourSchemeList[activeColourScheme]; +} + +void LfpDisplay::setNumChannels(int numChannels) +{ + numChans = numChannels; + +// deleteAllChildren(); + removeAllChildren(); + + channels.clear(); + channelInfo.clear(); + drawableChannels.clear(); + + totalHeight = 0; + cachedDisplayChannelHeight = canvas->getChannelHeight(); + + if (numChans > 0) + { + for (int i = 0; i < numChans; i++) + { + //std::cout << "Adding new display for channel " << i << std::endl; + + LfpChannelDisplay* lfpChan = new LfpChannelDisplay(canvas, this, options, i); + + //lfpChan->setColour(channelColours[i % channelColours.size()]); + lfpChan->setRange(range[options->getChannelType(i)]); + lfpChan->setChannelHeight(canvas->getChannelHeight()); + + addAndMakeVisible(lfpChan); + + channels.add(lfpChan); + + LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, this, options, i); + + //lfpInfo->setColour(channelColours[i % channelColours.size()]); + lfpInfo->setRange(range[options->getChannelType(i)]); + lfpInfo->setChannelHeight(canvas->getChannelHeight()); + lfpInfo->setSubprocessorIdx(canvas->getChannelSubprocessorIdx(i)); + + addAndMakeVisible(lfpInfo); + + channelInfo.add(lfpInfo); + + drawableChannels.add(LfpChannelTrack{ + lfpChan, + lfpInfo + }); + + savedChannelState.add(true); + + totalHeight += lfpChan->getChannelHeight(); + + } + + } + + setColors(); + + std::cout << "TOTAL HEIGHT = " << totalHeight << std::endl; + +} + +void LfpDisplay::setColors() +{ + for (int i = 0; i < drawableChannels.size(); i++) + { + +// channels[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); +// channelInfo[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); + drawableChannels[i].channel->setColour(getColourSchemePtr()->getColourForIndex(i)); + drawableChannels[i].channelInfo->setColour(getColourSchemePtr()->getColourForIndex(i)); + } + +} + +void LfpDisplay::setActiveColourSchemeIdx(int index) +{ + activeColourScheme = index; +} + +int LfpDisplay::getActiveColourSchemeIdx() +{ + return activeColourScheme; +} + +int LfpDisplay::getNumColourSchemes() +{ + return colourSchemeList.size(); +} + +StringArray LfpDisplay::getColourSchemeNameArray() +{ + StringArray nameList; + for (auto scheme : colourSchemeList) + nameList.add(scheme->getName()); + + return nameList; +} + +int LfpDisplay::getTotalHeight() +{ + return totalHeight; +} + +void LfpDisplay::resized() +{ + int totalHeight = 0; + + for (int i = 0; i < drawableChannels.size(); i++) + { + + LfpChannelDisplay* disp = drawableChannels[i].channel; + + if (disp->getHidden()) continue; + + disp->setBounds(canvas->leftmargin, + totalHeight-(disp->getChannelOverlap()*canvas->channelOverlapFactor)/2, + getWidth(), + disp->getChannelHeight()+(disp->getChannelOverlap()*canvas->channelOverlapFactor)); + + disp-> resized(); + + LfpChannelDisplayInfo* info = drawableChannels[i].channelInfo; + + info->setBounds(0, + totalHeight-disp->getChannelHeight() + (disp->getChannelOverlap()*canvas->channelOverlapFactor)/4.0, + canvas->leftmargin + 50, + disp->getChannelHeight()); + + totalHeight += disp->getChannelHeight(); + + } + + canvas->fullredraw = true; //issue full redraw + if (singleChan != -1) + viewport->setViewPosition(Point(0,singleChan*getChannelHeight())); + + lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); + + //inititalize black background + Graphics gLfpChannelBitmap(lfpChannelBitmap); + gLfpChannelBitmap.setColour(Colour(0,0,0)); //background color + gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); + + canvas->fullredraw = true; + + refresh(); + // std::cout << "Total height: " << totalHeight << std::endl; + +} + +void LfpDisplay::paint(Graphics& g) +{ + + g.drawImageAt(lfpChannelBitmap, canvas->leftmargin,0); + +} + +void LfpDisplay::refresh() +{ + // Ensure the lfpChannelBitmap has been initialized + if (lfpChannelBitmap.isNull()) + { + resized(); + } + + // X-bounds of this update + int fillfrom = canvas->lastScreenBufferIndex[0]; + int fillto = (canvas->screenBufferIndex[0]); + + if (fillfrom<0){fillfrom=0;}; + if (fillto>lfpChannelBitmap.getWidth()){fillto=lfpChannelBitmap.getWidth();}; + + int topBorder = viewport->getViewPositionY(); + int bottomBorder = viewport->getViewHeight() + topBorder; + + // clear appropriate section of the bitmap -- + // we need to do this before each channel draws its new section of data into lfpChannelBitmap + Graphics gLfpChannelBitmap(lfpChannelBitmap); + gLfpChannelBitmap.setColour(backgroundColour); //background color + + if (canvas->fullredraw) + { + gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); + } else { + gLfpChannelBitmap.setColour(backgroundColour); //background color + + gLfpChannelBitmap.fillRect(fillfrom,0, (fillto-fillfrom)+1, getHeight()); + }; + + for (int i = 0; i < numChans; i++) +// for (int i = 0; i < drawableChannels.size(); ++i) + { + + int componentTop = channels[i]->getY(); + int componentBottom = channels[i]->getHeight() + componentTop; + + if ((topBorder <= componentBottom && bottomBorder >= componentTop)) // only draw things that are visible + { + if (canvas->fullredraw) + { + channels[i]->fullredraw = true; + + channels[i]->pxPaint(); + channelInfo[i]->repaint(); + + } + else + { + channels[i]->pxPaint(); // draws to lfpChannelBitmap + + // it's not clear why, but apparently because the pxPaint() in a child component of LfpDisplay, we also need to issue repaint() calls for each channel, even though there's nothin to repaint there. Otherwise, the repaint call in LfpDisplay::refresh(), a few lines down, lags behind the update line by ~60 px. This could ahev something to do with teh reopaint message passing in juce. In any case, this seemingly redundant repaint here seems to fix the issue. + + // we redraw from 0 to +2 (px) relative to the real redraw window, the +1 draws the vertical update line + channels[i]->repaint(fillfrom, 0, (fillto-fillfrom)+2, channels[i]->getHeight()); + + } + //std::cout << i << std::endl; + } + + } + + if (fillfrom == 0 && singleChan != -1) + { + channelInfo[singleChan]->repaint(); + } + + if (canvas->fullredraw) + { + repaint(0,topBorder,getWidth(),bottomBorder-topBorder); + }else{ + //repaint(fillfrom, topBorder, (fillto-fillfrom)+1, bottomBorder-topBorder); // doesntb seem to be needed and results in duplicate repaint calls + } + + canvas->fullredraw = false; +} + +void LfpDisplay::setRange(float r, DataChannel::DataChannelTypes type) +{ + range[type] = r; + + if (channels.size() > 0) + { + + for (int i = 0; i < numChans; i++) + { + if (channels[i]->getType() == type) + channels[i]->setRange(range[type]); + } + canvas->fullredraw = true; //issue full redraw + } +} + +int LfpDisplay::getRange() +{ + return getRange(options->getSelectedType()); +} + +int LfpDisplay::getRange(DataChannel::DataChannelTypes type) +{ + for (int i=0; i < numChans; i++) + { + if (channels[i]->getType() == type) + return channels[i]->getRange(); + } + return 0; +} + +void LfpDisplay::setChannelHeight(int r, bool resetSingle) +{ + if (!getSingleChannelState()) cachedDisplayChannelHeight = r; + + for (int i = 0; i < numChans; i++) + { + channels[i]->setChannelHeight(r); + channelInfo[i]->setChannelHeight(r); + } + if (resetSingle && singleChan != -1) + { + //std::cout << "width " << getWidth() << " numchans " << numChans << " height " << getChannelHeight() << std::endl; + setSize(getWidth(),drawableChannels.size()*getChannelHeight()); + viewport->setScrollBarsShown(true,false); + viewport->setViewPosition(Point(0,singleChan*r)); + singleChan = -1; + for (int n = 0; n < numChans; n++) + { + channelInfo[n]->setEnabledState(savedChannelState[n]); + } + } + + resized(); + +} + +void LfpDisplay::setInputInverted(bool isInverted) +{ + + for (int i = 0; i < numChans; i++) + { + channels[i]->setInputInverted(isInverted); + } + + resized(); + +} + +void LfpDisplay::setDrawMethod(bool isDrawMethod) +{ + for (int i = 0; i < numChans; i++) + { + channels[i]->setDrawMethod(isDrawMethod); + } + + if (isDrawMethod) + { + plotter = supersampledPlotter; + } + else + { + plotter = perPixelPlotter; + } + + resized(); + +} + +int LfpDisplay::getChannelHeight() +{ +// return cachedDisplayChannelHeight; + return drawableChannels[0].channel->getChannelHeight(); +// return channels[0]->getChannelHeight(); +} + +void LfpDisplay::cacheNewChannelHeight(int r) +{ + cachedDisplayChannelHeight = r; +} + +bool LfpDisplay::getChannelsReversed() +{ + return channelsReversed; +} + +void LfpDisplay::setChannelsReversed(bool state) +{ + if (state == channelsReversed) return; // bail early, in case bookkeeping error + + channelsReversed = state; + + if (getSingleChannelState()) return; // don't reverse if single channel + + // reverse channels that are currently in drawableChannels + for (int i = 0, j = drawableChannels.size() - 1, len = drawableChannels.size()/2; + i < len; + i++, j--) + { + // remove channel and info components from front and back + // moving toward middle + removeChildComponent(drawableChannels[i].channel); + removeChildComponent(drawableChannels[j].channel); + removeChildComponent(drawableChannels[i].channelInfo); + removeChildComponent(drawableChannels[j].channelInfo); + + // swap front and back, moving towards middle + drawableChannels.swap(i, j); + + // also swap coords + { + const auto channelBoundsA = drawableChannels[i].channel->getBounds(); + const auto channelInfoBoundsA = drawableChannels[i].channelInfo->getBounds(); + + drawableChannels[i].channel->setBounds(drawableChannels[j].channel->getBounds()); + drawableChannels[i].channelInfo->setBounds(drawableChannels[j].channelInfo->getBounds()); + drawableChannels[j].channel->setBounds(channelBoundsA); + drawableChannels[j].channelInfo->setBounds(channelInfoBoundsA); + } + } + + // remove middle component if odd number of channels + if (drawableChannels.size() % 2 != 0) + { + removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channel); + removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channelInfo); + } + + // add the channels and channel info again + for (int i = 0, len = drawableChannels.size(); i < len; i++) + { + + if (!drawableChannels[i].channel->getHidden()) + { + addAndMakeVisible(drawableChannels[i].channel); + addAndMakeVisible(drawableChannels[i].channelInfo); + } + + // flag this to update the waveforms + drawableChannels[i].channel->fullredraw = true; + } + + // necessary to overwrite lfpChannelBitmap's display + refresh(); +} + +int LfpDisplay::getChannelDisplaySkipAmount() +{ + return displaySkipAmt; +} + +void LfpDisplay::setChannelDisplaySkipAmount(int skipAmt) +{ + displaySkipAmt = skipAmt; + + if (!getSingleChannelState()) + rebuildDrawableChannelsList(); + + canvas->redraw(); +} + +bool LfpDisplay::getMedianOffsetPlotting() +{ + return m_MedianOffsetPlottingFlag; +} + +void LfpDisplay::setMedianOffsetPlotting(bool isEnabled) +{ + m_MedianOffsetPlottingFlag = isEnabled; +} + +bool LfpDisplay::getSpikeRasterPlotting() +{ + return m_SpikeRasterPlottingFlag; +} + +void LfpDisplay::setSpikeRasterPlotting(bool isEnabled) +{ + m_SpikeRasterPlottingFlag = isEnabled; +} + +float LfpDisplay::getSpikeRasterThreshold() +{ + return m_SpikeRasterThreshold; +} + +void LfpDisplay::setSpikeRasterThreshold(float thresh) +{ + m_SpikeRasterThreshold = thresh; +} + +void LfpDisplay::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) +{ + + //std::cout << "Mouse wheel " << e.mods.isCommandDown() << " " << wheel.deltaY << std::endl; + //TODO Changing ranges with the wheel is currently broken. With multiple ranges, most + //of the wheel range code needs updating + + if (e.mods.isCommandDown() && singleChan == -1) // CTRL + scroll wheel -> change channel spacing + { + int h = getChannelHeight(); + int hdiff=0; + + // std::cout << wheel.deltaY << std::endl; + + if (wheel.deltaY > 0) + { + hdiff = 2; + } + else + { + if (h > 5) + hdiff = -2; + } + + if (abs(h) > 100) // accelerate scrolling for large ranges + hdiff *= 3; + + int newHeight = h+hdiff; + + // constrain the spread resizing to max and min values; + if (newHeight < trackZoomInfo.minZoomHeight) + { + newHeight = trackZoomInfo.minZoomHeight; + hdiff = 0; + } + else if (newHeight > trackZoomInfo.maxZoomHeight) + { + newHeight = trackZoomInfo.maxZoomHeight; + hdiff = 0; + } + + setChannelHeight(newHeight); + int oldX=viewport->getViewPositionX(); + int oldY=viewport->getViewPositionY(); + + setBounds(0,0,getWidth()-0, getChannelHeight()*drawableChannels.size()); // update height so that the scrollbar is correct + + int mouseY=e.getMouseDownY(); // should be y pos relative to inner viewport (0,0) + int scrollBy = (mouseY/h)*hdiff*2;// compensate for motion of point under current mouse position + viewport->setViewPosition(oldX,oldY+scrollBy); // set back to previous position plus offset + + options->setSpreadSelection(newHeight); // update combobox + + canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw + } + else + { + if (e.mods.isAltDown()) // ALT + scroll wheel -> change channel range (was SHIFT but that clamps wheel.deltaY to 0 on OSX for some reason..) + { + int h = getRange(); + + int step = options->getRangeStep(options->getSelectedType()); + + // std::cout << wheel.deltaY << std::endl; + + if (wheel.deltaY > 0) + { + setRange(h+step,options->getSelectedType()); + } + else + { + if (h > step+1) + setRange(h-step,options->getSelectedType()); + } + + options->setRangeSelection(h); // update combobox + canvas->fullredraw = true; //issue full redraw - scrolling without modifier doesnt require a full redraw + + } + else // just scroll + { + // passes the event up to the viewport so the screen scrolls + if (viewport != nullptr && e.eventComponent == this) // passes only if it's not a listening event + viewport->mouseWheelMove(e.getEventRelativeTo(canvas), wheel); + + } + + } + //refresh(); // doesn't seem to be needed now that channels daraw to bitmap + +} + +void LfpDisplay::toggleSingleChannel(int chan) +{ + if (!getSingleChannelState()) + { + + std::cout << "Single channel on (" << chan << ")" << std::endl; + singleChan = chan; + + int newHeight = viewport->getHeight(); + LfpChannelTrack lfpChannelTrack{drawableChannels[chan].channel, drawableChannels[chan].channelInfo}; + lfpChannelTrack.channelInfo->setEnabledState(true); + lfpChannelTrack.channelInfo->setSingleChannelState(true); + + removeAllChildren(); + + // disable unused channels + for (int i = 0; i < getNumChannels(); i++) + { + if (i != chan) + { + drawableChannels[i].channel->setEnabledState(false); + } + } + + // update drawableChannels, give only the single channel to focus on + drawableChannels.clearQuick(); + drawableChannels.add(lfpChannelTrack); + + addAndMakeVisible(lfpChannelTrack.channel); + addAndMakeVisible(lfpChannelTrack.channelInfo); + + // set channel height and position (so that we allocate the smallest + // necessary image size for drawing) + setChannelHeight(newHeight, false); + + lfpChannelTrack.channel->setTopLeftPosition(canvas->leftmargin, 0); + lfpChannelTrack.channelInfo->setTopLeftPosition(0, 0); + setSize(getWidth(), getChannelHeight()); + + viewport->setViewPosition(0, 0); + + } +// else if (chan == singleChan || chan == -2) + else + { + std::cout << "Single channel off" << std::endl; + for (int n = 0; n < numChans; n++) + { + channelInfo[n]->setSingleChannelState(false); + } + + setChannelHeight(cachedDisplayChannelHeight); + + reactivateChannels(); + rebuildDrawableChannelsList(); + } +} + +void LfpDisplay::reactivateChannels() +{ + + for (int n = 0; n < channels.size(); n++) + setEnabledState(savedChannelState[n], n); + +} + +void LfpDisplay::rebuildDrawableChannelsList() +{ + + if (displaySkipAmt != 0) removeAllChildren(); // start with clean slate + + Array channelsToDraw; + drawableChannels = Array(); + + // iterate over all channels and select drawable ones + for (int i = 0, drawableChannelNum = 0; i < channels.size(); i++) + { +// std::cout << "\tchannel " << i << " has subprocessor index of " << channelInfo[i]->getSubprocessorIdx() << std::endl; + // if channel[i] is not sourced from the correct subprocessor, then hide it and continue + //if (channelInfo[i]->getSubprocessorIdx() != getDisplayedSubprocessor()) + //{ + // channels[i]->setHidden(true); + // channelInfo[i]->setHidden(true); + // continue; + //} + + //std::cout << "Checking for hidden channels" << std::endl; + if (displaySkipAmt == 0 || (i % displaySkipAmt == 0)) // no skips, add all channels + { + channels[i]->setHidden(false); + channelInfo[i]->setHidden(false); + + channelInfo[i]->setDrawableChannelNumber(drawableChannelNum++); + channelInfo[i]->resized(); // to update the conditional drawing of enableButton and channel num + + channelsToDraw.add(LfpDisplay::LfpChannelTrack{ + channels[i], + channelInfo[i] + }); + + addAndMakeVisible(channels[i]); + addAndMakeVisible(channelInfo[i]); + } + else // skip some channels + { +// if (i % (displaySkipAmt) == 0) // add these channels +// { +// channels[i]->setHidden(false); +// channelInfo[i]->setHidden(false); +// +// channelsToDraw.add(LfpDisplay::LfpChannelTrack{ +// channels[i], +// channelInfo[i] +// }); +// +// addAndMakeVisible(channels[i]); +// addAndMakeVisible(channelInfo[i]); +// } +// else // but not these +// { + channels[i]->setHidden(true); + channelInfo[i]->setHidden(true); + + removeChildComponent(channels[i]); + removeChildComponent(channelInfo[i]); +// } + } + } + + // check if channels should be added to drawableChannels in reverse + if (getChannelsReversed()) + { + for (int i = channelsToDraw.size() - 1; i >= 0; --i) + { + drawableChannels.add(channelsToDraw[i]); + } + } + else + { + for (int i = 0; i < channelsToDraw.size(); ++i) + { + drawableChannels.add(channelsToDraw[i]); + } + } + + // this guards against an exception where the editor sets the drawable samplerate + // before the lfpDisplay is fully initialized + if (getHeight() > 0 && getWidth() > 0) + { + canvas->resizeToChannels(); + } + + setColors(); +} + +LfpBitmapPlotter * const LfpDisplay::getPlotterPtr() const +{ + return plotter; +} + +bool LfpDisplay::getSingleChannelState() +{ + //if (singleChan < 0) return false; + //else return true; + return singleChan >= 0; +} + +void LfpDisplay::mouseDown(const MouseEvent& event) +{ + if (drawableChannels.isEmpty()) + { + return; + } + + //int y = event.getMouseDownY(); //relative to each channel pos + MouseEvent canvasevent = event.getEventRelativeTo(viewport); + int y = canvasevent.getMouseDownY() + viewport->getViewPositionY(); // need to account for scrolling + int x = canvasevent.getMouseDownX(); + + int dist = 0; + int mindist = 10000; + int closest = 5; + for (int n = 0; n < drawableChannels.size(); n++) // select closest instead of relying on eventComponent + { + drawableChannels[n].channel->deselect(); + + int cpos = (drawableChannels[n].channel->getY() + (drawableChannels[n].channel->getHeight()/2)); + dist = int(abs(y - cpos)); + +// std::cout << "Mouse down at " << y << " pos is "<< cpos << " n: " << n << " dist " << dist << std::endl; + + if (dist < mindist) + { + mindist = dist-1; + closest = n; + } + } + + drawableChannels[closest].channel->select(); + options->setSelectedType(drawableChannels[closest].channel->getType()); + + if (event.mods.isRightButtonDown()) { // if right click + PopupMenu channelMenu = channels[closest]->getOptions(); + const int result = channelMenu.show(); + drawableChannels[closest].channel->changeParameter(result); + } + else // if left click + { +// if (singleChan != -1) + if (event.getNumberOfClicks() == 2) { + toggleSingleChannel(closest); + } + + if (getSingleChannelState()) + { + + // std::cout << "singleChan = " << singleChan << " " << y << " " << drawableChannels[0].channel->getHeight() << " " << getRange() << std::endl; + //channelInfo[singleChan]->updateXY( + drawableChannels[0].channelInfo->updateXY( + float(x)/getWidth()*canvas->timebase, + (-(float(y)-viewport->getViewPositionY())/viewport->getViewHeight()*float(getRange()))+float(getRange()/2) + ); + } + } + +// canvas->fullredraw = true;//issue full redraw + +// refresh(); + +} + +bool LfpDisplay::setEventDisplayState(int ch, bool state) +{ + eventDisplayEnabled[ch] = state; + return eventDisplayEnabled[ch]; +} + +bool LfpDisplay::getEventDisplayState(int ch) +{ + return eventDisplayEnabled[ch]; +} + +void LfpDisplay::setEnabledState(bool state, int chan, bool updateSaved) +{ + if (chan < numChans) + { + channels[chan]->setEnabledState(state); + channelInfo[chan]->setEnabledState(state); + + if (updateSaved) + savedChannelState.set(chan, state); + + canvas->isChannelEnabled.set(chan, state); + } +} + +bool LfpDisplay::getEnabledState(int chan) +{ + if (chan < numChans) + { + return channels[chan]->getEnabledState(); + } + + return false; +} + diff --git a/Plugins/LfpDisplayNode/LfpDisplay.h b/Plugins/LfpDisplayNode/LfpDisplay.h new file mode 100644 index 0000000000..c19322eb44 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpDisplay.h @@ -0,0 +1,236 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2013 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 . + +*/ +#ifndef __LFPDISPLAY_H__ +#define __LFPDISPLAY_H__ + +#include + +#include +#include + +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" +namespace LfpViewer { +#pragma mark - LfpDisplay - +//============================================================================== +/** + + Holds and draws all of the LfpDisplayChannel and lfpDisplayChannelInfo + instances. + + All of the channels and channelInfos are drawn here to a "master" bitmap + lfpChannelBitmap with height equal to the sum of all channel heights. This + bitmap is drawn by the LfpViewport using Viewport::setViewedComponent. + + */ +class LfpDisplay : public Component +{ +public: + LfpDisplay(LfpDisplayCanvas*, Viewport*); + ~LfpDisplay(); + + Image lfpChannelBitmap; // plot as bitmap instead of separately setting pixels + // this is done purely for the preformance improvement + + void setNumChannels(int numChannels); + int getNumChannels(); + + int getTotalHeight(); + + void paint(Graphics& g); + + void refresh(); + + void resized(); + + void reactivateChannels(); + + void mouseDown(const MouseEvent& event); + void mouseWheelMove(const MouseEvent& event, const MouseWheelDetails& wheel) ; + + void setRange(float range, DataChannel::DataChannelTypes type); + + //Withouth parameters returns selected type + int getRange(); + int getRange(DataChannel::DataChannelTypes type); + + void setChannelHeight(int r, bool resetSingle = true); + int getChannelHeight(); + + LfpChannelColourScheme * getColourSchemePtr(); + + /** Caches a new channel height without updating the channels */ + void cacheNewChannelHeight(int r); + + void setInputInverted(bool); + void setDrawMethod(bool); + + /** Returns a bool indicating if the channels are displayed in reverse order (true) */ + bool getChannelsReversed(); + + /** Reorders the displayed channels, reversed if state == true and normal if false */ + void setChannelsReversed(bool state); + + /** Returns a factor of 2 by which the displayed channels should skip */ + int getChannelDisplaySkipAmount(); + + /** Set the amount of channels to skip (hide) between each that is displayed */ + void setChannelDisplaySkipAmount(int skipAmt); + + void setColors(); + + void setActiveColourSchemeIdx(int index); + int getActiveColourSchemeIdx(); + + int getNumColourSchemes(); + StringArray getColourSchemeNameArray(); + + bool setEventDisplayState(int ch, bool state); + bool getEventDisplayState(int ch); + + int getColorGrouping(); + void setColorGrouping(int i); + + void setEnabledState(bool state, int chan, bool updateSavedChans = true); + bool getEnabledState(int); + + /** Returns true if the median offset is enabled for plotting, else false */ + bool getMedianOffsetPlotting(); + + /** Sets the state for the median offset plotting function */ + void setMedianOffsetPlotting(bool isEnabled); + + /** Returns true if spike raster is enabled for plotting, else false */ + bool getSpikeRasterPlotting(); + + /** Sets the state for the spike raster plotting function */ + void setSpikeRasterPlotting(bool isEnabled); + + /** Returns the value at which the spike raster will detect and draw spikes */ + float getSpikeRasterThreshold(); + + /** Set the threshold value for the spike raster plotting function */ + void setSpikeRasterThreshold(float thresh); + + /** Returns true if a single channel is focused in viewport */ + bool getSingleChannelState(); + + /** Set the viewport's channel focus behavior. + + When a single channel is selected, it fills the entire viewport and + all other channels are hidden. Double clicking a channel's info/event + display toggles this setting. + + @param chan If chan is < 0, no channel will be selected for singular + focus. Giving a value of 0 or greater hides all channels + except for the one at that index in drawableChannels[]. + Note: this parameter is NOT the index in channel[], but + the index of the channel in drawableChannels[]. + */ + void toggleSingleChannel(int chan = -2); + + /** Reconstructs the list of drawableChannels based on ordering and filterning parameters */ + void rebuildDrawableChannelsList(); + + /** Returns a const pointer to the internally managed plotter method class */ + LfpBitmapPlotter * const getPlotterPtr() const; + + Colour backgroundColour; + + Array channelColours; + + OwnedArray channels; // all channels + OwnedArray channelInfo; // all channelInfos + + /** Convenience struct for holding a channel and its info in drawableChannels */ + struct LfpChannelTrack + { + LfpChannelDisplay * channel; + LfpChannelDisplayInfo * channelInfo; + }; + Array drawableChannels; // holds the channels and info that are + // drawable to the screen + + bool eventDisplayEnabled[8]; + bool isPaused; // simple pause function, skips screen buffer updates + + LfpDisplayOptions* options; + + /** Convenience struct to store all variables particular to zooming mechanics */ + struct TrackZoomInfo_Struct + { + const int minZoomHeight = 10; + const int maxZoomHeight = 150; + int currentZoomHeight; // the current zoom height for the drawableChannels (not + // currently in use) + + bool isScrollingX = false; + bool isScrollingY = false; + int componentStartHeight; // a cache for the dimensions of a component during drag events + float timescaleStartScale; // a cache for the timescale size during drag events + float zoomPivotRatioX; // a cache for calculating the anchor point when adjusting viewport + float zoomPivotRatioY; + Point zoomPivotViewportOffset; // similar to above, but pixel-wise offset + bool unpauseOnScrollEnd; + }; + + TrackZoomInfo_Struct trackZoomInfo; // and create an instance here + +private: + + int singleChan; + Array savedChannelState; + + int numChans; + int displaySkipAmt; + int cachedDisplayChannelHeight; // holds a channel height if reset during single channel focus + float drawableSampleRate; + uint32 drawableSubprocessor; + + int totalHeight; + + int colorGrouping; + + bool channelsReversed; + bool m_MedianOffsetPlottingFlag; + bool m_SpikeRasterPlottingFlag; + float m_SpikeRasterThreshold; + + LfpDisplayCanvas* canvas; + Viewport* viewport; + + float range[3]; + + LfpBitmapPlotter * plotter; + + ScopedPointer perPixelPlotter; + ScopedPointer supersampledPlotter; + + // TODO: (kelly) add reference to a color scheme +// LfpChannelColourScheme * colourScheme; + uint8 activeColourScheme; + OwnedArray colourSchemeList; +}; + +}; // namespace +#endif diff --git a/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp b/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp index 5298244e46..1f67716ec1 100644 --- a/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp +++ b/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp @@ -22,14 +22,25 @@ along with this program. If not, see . */ #include "LfpDisplayCanvas.h" +#include "LfpDisplayNode.h" +#include "ShowHideOptionsButton.h" +#include "LfpDisplayOptions.h" +#include "LfpTimescale.h" +#include "LfpDisplay.h" +#include "LfpChannelDisplay.h" +#include "LfpChannelDisplayInfo.h" +#include "EventDisplayInterface.h" +#include "LfpViewport.h" +#include "LfpBitmapPlotter.h" +#include "PerPixelBitmapPlotter.h" +#include "SupersampledBitmapPlotter.h" +#include "LfpChannelColourScheme.h" #include using namespace LfpViewer; - - -#pragma mark - LfpDisplayCanvas - +#pragma mark - LfpDisplayCanvas - LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : timebase(1.0f), displayGain(1.0f), timeOffset(0.0f), @@ -281,8 +292,6 @@ void LfpDisplayCanvas::update() } } - - int LfpDisplayCanvas::getChannelHeight() { //return spreads[spreadSelection->getSelectedId()-1].getIntValue(); @@ -290,13 +299,11 @@ int LfpDisplayCanvas::getChannelHeight() } - void LfpDisplayCanvas::setParameter(int param, float val) { // not used for anything, since LfpDisplayCanvas is not a processor } - void LfpDisplayCanvas::refreshState() { // called when the component's tab becomes visible again @@ -367,7 +374,6 @@ void LfpDisplayCanvas::updateScreenBuffer() //if (channel == 15 || channel == 16) // std::cout << channel << " " << sbi << " " << dbi << " " << nSamples << std::endl; - float ratio = sampleRate * timebase / float(getWidth() - leftmargin - scrollBarThickness); // samples / pixel // this number is crucial: converting from samples to values (in px) for the screen buffer int valuesNeeded = (int) float(nSamples) / ratio; // N pixels needed for this update @@ -419,7 +425,6 @@ void LfpDisplayCanvas::updateScreenBuffer() 1, // numSamples invAlpha*gain); // gain - screenBuffer->addFrom(channel, // destChannel sbi, // destStartSample displayBuffer->getReadPointer(channel, nextPos), // source @@ -636,7 +641,6 @@ void LfpDisplayCanvas::redraw() refresh(); } - void LfpDisplayCanvas::paint(Graphics& g) { @@ -670,7 +674,6 @@ void LfpDisplayCanvas::paint(Graphics& g) g.drawLine(0,getHeight()-60,getWidth(),getHeight()-60,3.0f); - } void LfpDisplayCanvas::refresh() @@ -712,7 +715,6 @@ void LfpDisplayCanvas::saveVisualizerParameters(XmlElement* xml) ed->saveVisualizerParameters(xml); } - void LfpDisplayCanvas::loadVisualizerParameters(XmlElement* xml) { options->loadParameters(xml); @@ -720,3763 +722,3 @@ void LfpDisplayCanvas::loadVisualizerParameters(XmlElement* xml) ed->loadVisualizerParameters(xml); } - - -#pragma mark - ShowHideOptionsButton - -// ============================================================= - - -ShowHideOptionsButton::ShowHideOptionsButton(LfpDisplayOptions* options) : Button("Button") -{ - setClickingTogglesState(true); -} -ShowHideOptionsButton::~ShowHideOptionsButton() -{ - -} - -void ShowHideOptionsButton::paintButton(Graphics& g, bool, bool) -{ - g.setColour(Colours::white); - - Path p; - - float h = getHeight(); - float w = getWidth(); - - if (getToggleState()) - { - p.addTriangle(0.5f*w, 0.2f*h, - 0.2f*w, 0.8f*h, - 0.8f*w, 0.8f*h); - } - else - { - p.addTriangle(0.8f*w, 0.8f*h, - 0.2f*w, 0.5f*h, - 0.8f*w, 0.2f*h); - } - - PathStrokeType pst = PathStrokeType(1.0f, PathStrokeType::curved, PathStrokeType::rounded); - - g.strokePath(p, pst); -} - - - -#pragma mark - LfpDisplayOptions - -// ------------------------------------------------------------- - -LfpDisplayOptions::LfpDisplayOptions(LfpDisplayCanvas* canvas_, LfpTimescale* timescale_, - LfpDisplay* lfpDisplay_, LfpDisplayNode* processor_) - : canvas(canvas_), - lfpDisplay(lfpDisplay_), - timescale(timescale_), - processor(processor_), - selectedChannelType(DataChannel::HEADSTAGE_CHANNEL), - labelFont("Default", 13.0f, Font::plain), - labelColour(100, 100, 100) -{ - // draw the colour scheme options - // TODO: (kelly) this might be better as a modal window - colourSchemeOptionLabel = new Label("colorSchemeOptionLabel", "Color Scheme"); - colourSchemeOptionLabel->setFont(labelFont); - colourSchemeOptionLabel->setColour(Label::textColourId, labelColour); - addAndMakeVisible(colourSchemeOptionLabel); - - StringArray colourSchemeNames = lfpDisplay->getColourSchemeNameArray(); - colourSchemeOptionSelection = new ComboBox("colorSchemeOptionSelection"); - colourSchemeOptionSelection->addItemList(colourSchemeNames, 1); - colourSchemeOptionSelection->setEditableText(false); - colourSchemeOptionSelection->addListener(this); - colourSchemeOptionSelection->setSelectedId(1, dontSendNotification); - addAndMakeVisible(colourSchemeOptionSelection); - - if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) - addAndMakeVisible(lfpDisplay->getColourSchemePtr()); - - //Ranges for neural data - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("25"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("50"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("100"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("250"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("400"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("500"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("750"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("1000"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("2000"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("5000"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("10000"); - voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("15000"); - selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] = 4; - rangeGain[DataChannel::HEADSTAGE_CHANNEL] = 1; //uV - rangeSteps[DataChannel::HEADSTAGE_CHANNEL] = 10; - rangeUnits.add(CharPointer_UTF8("\xC2\xB5V")); - typeNames.add("DATA"); - - UtilityButton* tbut; - tbut = new UtilityButton("DATA",Font("Small Text", 9, Font::plain)); - tbut->setEnabledState(true); - tbut->setCorners(false,false,false,false); - tbut->addListener(this); - tbut->setClickingTogglesState(true); - tbut->setRadioGroupId(100,dontSendNotification); - tbut->setToggleState(true,dontSendNotification); - addAndMakeVisible(tbut); - typeButtons.add(tbut); - - //Ranges for AUX/accelerometer data - voltageRanges[DataChannel::AUX_CHANNEL].add("25"); - voltageRanges[DataChannel::AUX_CHANNEL].add("50"); - voltageRanges[DataChannel::AUX_CHANNEL].add("100"); - voltageRanges[DataChannel::AUX_CHANNEL].add("250"); - voltageRanges[DataChannel::AUX_CHANNEL].add("400"); - voltageRanges[DataChannel::AUX_CHANNEL].add("500"); - voltageRanges[DataChannel::AUX_CHANNEL].add("750"); - voltageRanges[DataChannel::AUX_CHANNEL].add("1000"); - voltageRanges[DataChannel::AUX_CHANNEL].add("2000"); - //voltageRanges[DataChannel::AUX_CHANNEL].add("5000"); - selectedVoltageRange[DataChannel::AUX_CHANNEL] = 9; - rangeGain[DataChannel::AUX_CHANNEL] = 0.001f; //mV - rangeSteps[DataChannel::AUX_CHANNEL] = 10; - rangeUnits.add("mV"); - typeNames.add("AUX"); - - tbut = new UtilityButton("AUX",Font("Small Text", 9, Font::plain)); - tbut->setEnabledState(true); - tbut->setCorners(false,false,false,false); - tbut->addListener(this); - tbut->setClickingTogglesState(true); - tbut->setRadioGroupId(100,dontSendNotification); - tbut->setToggleState(false,dontSendNotification); - addAndMakeVisible(tbut); - typeButtons.add(tbut); - - //Ranges for ADC data - voltageRanges[DataChannel::ADC_CHANNEL].add("0.01"); - voltageRanges[DataChannel::ADC_CHANNEL].add("0.05"); - voltageRanges[DataChannel::ADC_CHANNEL].add("0.1"); - voltageRanges[DataChannel::ADC_CHANNEL].add("0.5"); - voltageRanges[DataChannel::ADC_CHANNEL].add("1.0"); - voltageRanges[DataChannel::ADC_CHANNEL].add("2.0"); - voltageRanges[DataChannel::ADC_CHANNEL].add("5.0"); - voltageRanges[DataChannel::ADC_CHANNEL].add("10.0"); - selectedVoltageRange[DataChannel::ADC_CHANNEL] = 8; - rangeGain[DataChannel::ADC_CHANNEL] = 1; //V - rangeSteps[DataChannel::ADC_CHANNEL] = 0.1; //in V - rangeUnits.add("V"); - typeNames.add("ADC"); - - tbut = new UtilityButton("ADC",Font("Small Text", 9, Font::plain)); - tbut->setEnabledState(true); - tbut->setCorners(false,false,false,false); - tbut->addListener(this); - tbut->setClickingTogglesState(true); - tbut->setRadioGroupId(100,dontSendNotification); - tbut->setToggleState(false,dontSendNotification); - addAndMakeVisible(tbut); - typeButtons.add(tbut); - - selectedVoltageRangeValues[DataChannel::HEADSTAGE_CHANNEL] = voltageRanges[DataChannel::HEADSTAGE_CHANNEL][selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] - 1]; - selectedVoltageRangeValues[DataChannel::AUX_CHANNEL] = voltageRanges[DataChannel::AUX_CHANNEL][selectedVoltageRange[DataChannel::AUX_CHANNEL] - 1]; - selectedVoltageRangeValues[DataChannel::ADC_CHANNEL] = voltageRanges[DataChannel::ADC_CHANNEL][selectedVoltageRange[DataChannel::ADC_CHANNEL] - 1]; - - - - // init channel display skipping options - channelDisplaySkipOptions.add("All"); - channelDisplaySkipOptions.add("2"); - channelDisplaySkipOptions.add("4"); - channelDisplaySkipOptions.add("8"); - channelDisplaySkipOptions.add("16"); - channelDisplaySkipOptions.add("32"); - channelDisplaySkipOptions.add("64"); - selectedChannelDisplaySkip = 1; - selectedChannelDisplaySkipValue = channelDisplaySkipOptions[selectedChannelDisplaySkip - 1]; - - channelDisplaySkipSelection = new ComboBox("Channel Skip"); - channelDisplaySkipSelection->addItemList(channelDisplaySkipOptions, 1); - channelDisplaySkipSelection->setSelectedId(selectedChannelDisplaySkip, sendNotification); - channelDisplaySkipSelection->setEditableText(false); - channelDisplaySkipSelection->addListener(this); - addAndMakeVisible(channelDisplaySkipSelection); - - channelDisplaySkipLabel = new Label("Channel Display Skip", "Ch. Skip"); - channelDisplaySkipLabel->setFont(labelFont); - channelDisplaySkipLabel->setColour(Label::textColourId, labelColour); - addAndMakeVisible(channelDisplaySkipLabel); - - - - // init spike raster options - spikeRasterSelectionOptions = {"Off", "-50", "-100", "-150", "-200", "-300", "-400", "-500"}; - selectedSpikeRasterThreshold = 1; - selectedSpikeRasterThresholdValue = spikeRasterSelectionOptions[selectedSpikeRasterThreshold - 1]; - - spikeRasterSelection = new ComboBox("spikeRasterSelection"); - spikeRasterSelection->addItemList(spikeRasterSelectionOptions, 1); - spikeRasterSelection->setSelectedId(selectedSpikeRasterThreshold, dontSendNotification); - spikeRasterSelection->setEditableText(true); - spikeRasterSelection->addListener(this); - addAndMakeVisible(spikeRasterSelection); - - spikeRasterLabel = new Label("spikeRasterLabel", "Spike Raster Thresh."); - spikeRasterLabel->setFont(labelFont); - spikeRasterLabel->setColour(Label::textColourId, labelColour); - addAndMakeVisible(spikeRasterLabel); - - - - // init median offset plotting - medianOffsetPlottingLabel = new Label("Median Offset Correction", "Median Offset Correction"); - medianOffsetPlottingLabel->setFont(labelFont); - medianOffsetPlottingLabel->setColour(Label::textColourId, labelColour); - addAndMakeVisible(medianOffsetPlottingLabel); - - medianOffsetPlottingButton = new UtilityButton("0", labelFont); - medianOffsetPlottingButton->setRadius(5.0f); - medianOffsetPlottingButton->setEnabledState(true); - medianOffsetPlottingButton->setCorners(true, true, true, true); - medianOffsetPlottingButton->addListener(this); - medianOffsetPlottingButton->setClickingTogglesState(true); - medianOffsetPlottingButton->setToggleState(false, sendNotification); - addAndMakeVisible(medianOffsetPlottingButton); - - //init channel name toggle - showChannelNumberLabel = new Label("showcChannelLabel", "Show channel number instead of name"); - showChannelNumberLabel->setFont(labelFont); - showChannelNumberLabel->setColour(Label::textColourId, labelColour); - addAndMakeVisible(showChannelNumberLabel); - - showChannelNumberButton = new UtilityButton("0", labelFont); - showChannelNumberButton->setRadius(5.0f); - showChannelNumberButton->setEnabledState(true); - showChannelNumberButton->setCorners(true, true, true, true); - showChannelNumberButton->addListener(this); - showChannelNumberButton->setClickingTogglesState(true); - showChannelNumberButton->setToggleState(false, sendNotification); - addAndMakeVisible(showChannelNumberButton); - - - // init show/hide options button - showHideOptionsButton = new ShowHideOptionsButton(this); - showHideOptionsButton->addListener(this); - addAndMakeVisible(showHideOptionsButton); - - // init timebases options - timebases.add("0.25"); - timebases.add("0.5"); - timebases.add("1.0"); - timebases.add("2.0"); - timebases.add("3.0"); - timebases.add("4.0"); - timebases.add("5.0"); - timebases.add("10.0"); - timebases.add("20.0"); - selectedTimebase = 4; - selectedTimebaseValue = timebases[selectedTimebase-1]; - - spreads.add("10"); - spreads.add("20"); - spreads.add("30"); - spreads.add("40"); - spreads.add("50"); - spreads.add("60"); - spreads.add("70"); - spreads.add("80"); - spreads.add("90"); - spreads.add("100"); - selectedSpread = 4; - selectedSpreadValue = spreads[selectedSpread-1]; - - - overlaps.add("0.5"); - overlaps.add("0.75"); - overlaps.add("1"); - overlaps.add("2"); - overlaps.add("3"); - overlaps.add("4"); - overlaps.add("5"); - selectedOverlap = 4; - selectedOverlapValue = overlaps[selectedOverlap-1]; - - saturationThresholds.add("0.5"); - saturationThresholds.add("100"); - saturationThresholds.add("1000"); - saturationThresholds.add("5000"); - saturationThresholds.add("6389"); - - selectedSaturation = 5; - selectedSaturationValue = saturationThresholds[selectedSaturation-1]; - - - colorGroupings.add("1"); - colorGroupings.add("2"); - colorGroupings.add("4"); - colorGroupings.add("8"); - colorGroupings.add("16"); - - - rangeSelection = new ComboBox("Voltage range"); - rangeSelection->addItemList(voltageRanges[DataChannel::HEADSTAGE_CHANNEL], 1); - rangeSelection->setSelectedId(selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL], sendNotification); - rangeSelection->setEditableText(true); - rangeSelection->addListener(this); - addAndMakeVisible(rangeSelection); - - - timebaseSelection = new ComboBox("Timebase"); - timebaseSelection->addItemList(timebases, 1); - timebaseSelection->setSelectedId(selectedTimebase, sendNotification); - timebaseSelection->setEditableText(true); - timebaseSelection->addListener(this); - addAndMakeVisible(timebaseSelection); - - - spreadSelection = new ComboBox("Spread"); - spreadSelection->addItemList(spreads, 1); - spreadSelection->setSelectedId(selectedSpread,sendNotification); - spreadSelection->addListener(this); - spreadSelection->setEditableText(true); - addAndMakeVisible(spreadSelection); - - overlapSelection = new ComboBox("Overlap"); - overlapSelection->addItemList(overlaps, 1); - overlapSelection->setSelectedId(selectedOverlap,sendNotification); - overlapSelection->addListener(this); - overlapSelection->setEditableText(true); - addAndMakeVisible(overlapSelection); - - saturationWarningSelection = new ComboBox("Sat.Warn"); - saturationWarningSelection->addItemList(saturationThresholds, 1); - saturationWarningSelection->setSelectedId(selectedSaturation,sendNotification); - saturationWarningSelection->addListener(this); - saturationWarningSelection->setEditableText(true); - addAndMakeVisible(saturationWarningSelection); - - - colorGroupingSelection = new ComboBox("Color Grouping"); - colorGroupingSelection->addItemList(colorGroupings, 1); - colorGroupingSelection->setSelectedId(1,sendNotification); - colorGroupingSelection->addListener(this); - addAndMakeVisible(colorGroupingSelection); - - invertInputButton = new UtilityButton("Invert", Font("Small Text", 13, Font::plain)); - invertInputButton->setRadius(5.0f); - invertInputButton->setEnabledState(true); - invertInputButton->setCorners(true, true, true, true); - invertInputButton->addListener(this); - invertInputButton->setClickingTogglesState(true); - invertInputButton->setToggleState(false, sendNotification); - addAndMakeVisible(invertInputButton); - - // toggle button to reverse the order of channels - reverseChannelsDisplayButton = new UtilityButton("0", labelFont); - reverseChannelsDisplayButton->setRadius(5.0f); - reverseChannelsDisplayButton->setEnabledState(true); - reverseChannelsDisplayButton->setCorners(true, true, true, true); - reverseChannelsDisplayButton->addListener(this); - reverseChannelsDisplayButton->setClickingTogglesState(true); - reverseChannelsDisplayButton->setToggleState(lfpDisplay->getChannelsReversed(), sendNotification); - addAndMakeVisible(reverseChannelsDisplayButton); - - reverseChannelsDisplayLabel = new Label("Rev. Channels", "Rev. Channels"); - reverseChannelsDisplayLabel->setFont(labelFont); - reverseChannelsDisplayLabel->setColour(Label::textColourId, labelColour); - addAndMakeVisible(reverseChannelsDisplayLabel); - - - //button for controlling drawing algorithm - old line-style or new per-pixel style - drawMethodButton = new UtilityButton("DrawMethod", Font("Small Text", 13, Font::plain)); - drawMethodButton->setRadius(5.0f); - drawMethodButton->setEnabledState(true); - drawMethodButton->setCorners(true, true, true, true); - drawMethodButton->addListener(this); - drawMethodButton->setClickingTogglesState(true); - drawMethodButton->setToggleState(false, sendNotification); - addAndMakeVisible(drawMethodButton); - - // two sliders for the two histogram components of the supersampled plotting mode - // todo: rename these - brightnessSliderA = new Slider(); - brightnessSliderA->setRange (0, 1); - brightnessSliderA->setTextBoxStyle(Slider::NoTextBox, false, 50,30); - brightnessSliderA->addListener(this); - addAndMakeVisible (brightnessSliderA); - - brightnessSliderB = new Slider; - brightnessSliderB->setRange (0, 1); - brightnessSliderB->setTextBoxStyle(Slider::NoTextBox, false, 50,30); - brightnessSliderB->addListener(this); - addAndMakeVisible (brightnessSliderB); - - sliderALabel = new Label("Brightness","Brightness"); - sliderALabel->setFont(Font("Small Text", 13, Font::plain)); - sliderALabel->setColour(Label::textColourId,Colour(150,150,150)); - addAndMakeVisible(sliderALabel); - - sliderBLabel = new Label("Min. brightness","Min. brightness"); - sliderBLabel->setFont(Font("Small Text", 13, Font::plain)); - sliderBLabel->setColour(Label::textColourId,Colour(150,150,150)); - addAndMakeVisible(sliderBLabel); - - - //ScopedPointer drawClipWarningButton; // optinally draw (subtle) warning if data is clipped in display - drawClipWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain)); - drawClipWarningButton->setRadius(5.0f); - drawClipWarningButton->setEnabledState(true); - drawClipWarningButton->setCorners(true, true, true, true); - drawClipWarningButton->addListener(this); - drawClipWarningButton->setClickingTogglesState(true); - drawClipWarningButton->setToggleState(false, sendNotification); - addAndMakeVisible(drawClipWarningButton); - - - //ScopedPointer drawSaturateWarningButton; // optionally raise hell if the actual data is saturating - drawSaturateWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain)); - drawSaturateWarningButton->setRadius(5.0f); - drawSaturateWarningButton->setEnabledState(true); - drawSaturateWarningButton->setCorners(true, true, true, true); - drawSaturateWarningButton->addListener(this); - drawSaturateWarningButton->setClickingTogglesState(true); - drawSaturateWarningButton->setToggleState(false, sendNotification); - addAndMakeVisible(drawSaturateWarningButton); - - - //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)); - pauseButton->setRadius(5.0f); - pauseButton->setEnabledState(true); - pauseButton->setCorners(true, true, true, true); - pauseButton->addListener(this); - pauseButton->setClickingTogglesState(true); - pauseButton->setToggleState(false, sendNotification); - addAndMakeVisible(pauseButton); - - // add event display-specific controls (currently just an enable/disable button) - for (int i = 0; i < 8; i++) - { - - EventDisplayInterface* eventOptions = new EventDisplayInterface(lfpDisplay, canvas, i); - eventDisplayInterfaces.add(eventOptions); - addAndMakeVisible(eventOptions); - eventOptions->setBounds(700+(floor(i/2)*20), getHeight()-20-(i%2)*20, 40, 20); - - lfpDisplay->setEventDisplayState(i,true); - - } - - lfpDisplay->setRange(voltageRanges[DataChannel::HEADSTAGE_CHANNEL][selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::HEADSTAGE_CHANNEL] - , DataChannel::HEADSTAGE_CHANNEL); - lfpDisplay->setRange(voltageRanges[DataChannel::ADC_CHANNEL][selectedVoltageRange[DataChannel::ADC_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::ADC_CHANNEL] - , DataChannel::ADC_CHANNEL); - lfpDisplay->setRange(voltageRanges[DataChannel::AUX_CHANNEL][selectedVoltageRange[DataChannel::AUX_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::AUX_CHANNEL] - , DataChannel::AUX_CHANNEL); - -} - -LfpDisplayOptions::~LfpDisplayOptions() -{ - -} - -void LfpDisplayOptions::resized() -{ - rangeSelection->setBounds(5,getHeight()-30,80,25); - timebaseSelection->setBounds(175,getHeight()-30,60,25); - - spreadSelection->setBounds(5,getHeight()-90,60,25); - - overlapSelection->setBounds(100,getHeight()-90,60,25); - - drawClipWarningButton->setBounds(175,getHeight()-89,20,20); - drawSaturateWarningButton->setBounds(325, getHeight()-89, 20, 20); - - colorGroupingSelection->setBounds(400,getHeight()-90,60,25); - - invertInputButton->setBounds(35,getHeight()-190,100,22); - drawMethodButton->setBounds(35,getHeight()-160,100,22); - - pauseButton->setBounds(450,getHeight()-50,50,44); - - - // Reverse Channels Display - reverseChannelsDisplayButton->setBounds(pauseButton->getRight() + 5, - getHeight() - 50, - 20, - 20); - reverseChannelsDisplayLabel->setBounds(reverseChannelsDisplayButton->getRight(), - reverseChannelsDisplayButton->getY(), - 120, - 22); - - // Channel Display Skip Selector - channelDisplaySkipSelection->setBounds(reverseChannelsDisplayButton->getX(), - reverseChannelsDisplayButton->getBottom(), - 60, - 25); - channelDisplaySkipLabel->setBounds(channelDisplaySkipSelection->getRight(), - channelDisplaySkipSelection->getY() + 2, - 100, - 22); - - // Median Offset Plotting Button - medianOffsetPlottingButton->setBounds(reverseChannelsDisplayLabel->getRight() + 5, - reverseChannelsDisplayButton->getY(), - 20, - 20); - medianOffsetPlottingLabel->setBounds(medianOffsetPlottingButton->getRight(), - medianOffsetPlottingButton->getY(), - 150, - 22); - - //Channel name toggle - showChannelNumberButton->setBounds(medianOffsetPlottingLabel->getRight() + 5, - medianOffsetPlottingLabel->getY(), - 20, - 20); - showChannelNumberLabel->setBounds(showChannelNumberButton->getRight(), - showChannelNumberButton->getY(), - 200, - 22); - - // Spike raster plotting button - spikeRasterSelection->setBounds(medianOffsetPlottingButton->getX(), - medianOffsetPlottingButton->getBottom(), - 60, - 25); - spikeRasterLabel->setBounds(spikeRasterSelection->getRight(), - spikeRasterSelection->getY(), - 120, - 22); - - - // Saturation Warning Selection - saturationWarningSelection->setBounds(250, getHeight()-90, 60, 25); - - - for (int i = 0; i < 8; i++) - { - eventDisplayInterfaces[i]->setBounds(300+(floor(i/2)*20), getHeight()-40+(i%2)*20, 40, 20); // arrange event channel buttons in two rows - eventDisplayInterfaces[i]->repaint(); - } - - brightnessSliderA->setBounds(170,getHeight()-190,100,22); - sliderALabel->setBounds(270, getHeight()-190, 180, 22); - brightnessSliderA->setValue(0.9); //set default value - - brightnessSliderB->setBounds(170,getHeight()-160,100,22); - sliderBLabel->setBounds(270, getHeight()-160, 180, 22); - brightnessSliderB->setValue(0.1); //set default value - - showHideOptionsButton->setBounds (getWidth() - 28, getHeight() - 28, 20, 20); - - int bh = 25/typeButtons.size(); - for (int i = 0; i < typeButtons.size(); i++) - { - typeButtons[i]->setBounds(95,getHeight()-30+i*bh,50,bh); - } - - - colourSchemeOptionLabel->setBounds(medianOffsetPlottingButton->getX(), - getHeight()-190, - 100, - 22); - colourSchemeOptionSelection->setBounds(colourSchemeOptionLabel->getRight(), - colourSchemeOptionLabel->getY(), - 80, - 25); - - // set the size of the active colour scheme's options, if it has configurable options - if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) - { - lfpDisplay->getColourSchemePtr()->setBounds(colourSchemeOptionLabel->getX(), - colourSchemeOptionLabel->getBottom(), - 200, - 110); - } -} - -void LfpDisplayOptions::paint(Graphics& g) -{ - int row1 = 55; - int row2 = 110; - - g.fillAll(Colours::black); - g.setFont(Font("Default", 16, Font::plain)); - - g.setColour(Colour(100,100,100)); - - g.drawText("Range("+ rangeUnits[selectedChannelType] +")",5,getHeight()-row1,300,20,Justification::left, false); - g.drawText("Timebase(s)",160,getHeight()-row1,300,20,Justification::left, false); - g.drawText("Size(px)",5,getHeight()-row2,300,20,Justification::left, false); - g.drawText("Clip",100,getHeight()-row2,300,20,Justification::left, false); - g.drawText("Warn",168,getHeight()-row2,300,20,Justification::left, false); - - g.drawText("Sat. Warning",225,getHeight()-row2,300,20,Justification::left, false); - - g.drawText("Color grouping",365,getHeight()-row2,300,20,Justification::left, false); - - g.drawText("Event disp.",300,getHeight()-row1,300,20,Justification::left, false); - - if(canvas->drawClipWarning) - { - g.setColour(Colours::white); - g.fillRoundedRectangle(173,getHeight()-90-1,24,24,6.0f); - } - - if(canvas->drawSaturationWarning) - { - g.setColour(Colours::red); - g.fillRoundedRectangle(323,getHeight()-90-1,24,24,6.0f); - } - -} - -int LfpDisplayOptions::getChannelHeight() -{ - return (int)spreadSelection->getText().getIntValue(); -} - - -bool LfpDisplayOptions::getDrawMethodState() -{ - - return drawMethodButton->getToggleState(); -} - -bool LfpDisplayOptions::getInputInvertedState() -{ - return invertInputButton->getToggleState(); -} - -bool LfpDisplayOptions::getChannelNameState() -{ - return showChannelNumberButton->getToggleState(); -} - -bool LfpDisplayOptions::getDisplaySpikeRasterizerState() -{ -// return spikeRasterButton->getToggleState(); - return false; -} - -void LfpDisplayOptions::setDisplaySpikeRasterizerState(bool isEnabled) -{ -// spikeRasterButton->setToggleState(isEnabled, dontSendNotification); - -// if (isEnabled) medianOffsetPlottingButton->setToggleState(true, sendNotification); -} - -void LfpDisplayOptions::setRangeSelection(float range, bool canvasMustUpdate) -{ - if (canvasMustUpdate) - { - rangeSelection->setText(String(range/rangeGain[selectedChannelType]), sendNotification); - } - else - { - rangeSelection->setText(String(range/rangeGain[selectedChannelType]),dontSendNotification); - - selectedVoltageRange[selectedChannelType] = rangeSelection->getSelectedId(); - selectedVoltageRangeValues[selectedChannelType] = rangeSelection->getText(); - - canvas->repaint(); - canvas->refresh(); - } - -} - -void LfpDisplayOptions::setSpreadSelection(int spread, bool canvasMustUpdate, bool deferDisplayRefresh) -{ - - if (canvasMustUpdate) - { - spreadSelection->setText(String(spread),sendNotification); - } - else - { - spreadSelection->setText(String(spread),dontSendNotification); - selectedSpread = spreadSelection->getSelectedId(); - selectedSpreadValue = spreadSelection->getText(); - - if (!deferDisplayRefresh) - { - canvas->repaint(); - canvas->refresh(); - } - } -} - -void LfpDisplayOptions::togglePauseButton(bool sendUpdate) -{ - pauseButton->setToggleState(!pauseButton->getToggleState(), sendUpdate ? sendNotification : dontSendNotification); -} - -void LfpDisplayOptions::buttonClicked(Button* b) -{ - if (b == invertInputButton) - { - lfpDisplay->setInputInverted(b->getToggleState()); - return; - } - if (b == reverseChannelsDisplayButton) - { - lfpDisplay->setChannelsReversed(b->getToggleState()); - return; - } - if (b == medianOffsetPlottingButton) - { - if (lfpDisplay->getSpikeRasterPlotting()) - { - medianOffsetPlottingButton->setToggleState(true, dontSendNotification); - } - else - { - lfpDisplay->setMedianOffsetPlotting(b->getToggleState()); - } - return; - } - if (b == drawMethodButton) - { - lfpDisplay->setDrawMethod(b->getToggleState()); // this should be done the same way as drawClipWarning - or the other way around. - - return; - } - if (b == drawClipWarningButton) - { - canvas->drawClipWarning = b->getToggleState(); - canvas->redraw(); - return; - } - if (b == drawSaturateWarningButton) - { - canvas->drawSaturationWarning = b->getToggleState(); - canvas->redraw(); - return; - } - - if (b == pauseButton) - { - lfpDisplay->isPaused = b->getToggleState(); - return; - } - - if (b == showHideOptionsButton) - { - canvas->toggleOptionsDrawer(b->getToggleState()); - } - - if (b == showChannelNumberButton) - { - int numChannels = lfpDisplay->channelInfo.size(); - for (int i = 0; i < numChannels; ++i) - { - lfpDisplay->channelInfo[i]->repaint(); - } - return; - } - - int idx = typeButtons.indexOf((UtilityButton*)b); - - if ((idx >= 0) && (b->getToggleState())) - { - for (int i = 0; i < lfpDisplay->getNumChannels(); i++) - { - if (lfpDisplay->channels[i]->getSelected()) - { - lfpDisplay->channels[i]->deselect(); - lfpDisplay->channels[i]->repaint(); - } - } - - setSelectedType((DataChannel::DataChannelTypes) idx, false); - } - -} - -void LfpDisplayOptions::setTimebaseAndSelectionText(float timebase) -{ - canvas->timebase = timebase; - - if (canvas->timebase) // if timebase != 0 - { - if (canvas->timebase < timebases[0].getFloatValue()) - { - timebaseSelection->setSelectedId(1, dontSendNotification); - canvas->timebase = timebases[0].getFloatValue(); - } - else if (canvas->timebase > timebases[timebases.size()-1].getFloatValue()) - { - timebaseSelection->setSelectedId(timebases.size(), dontSendNotification); - canvas->timebase = timebases[timebases.size()-1].getFloatValue(); - } - else{ - timebaseSelection->setText(String(canvas->timebase, 1), dontSendNotification); - } - } - else - { - if (selectedSpread == 0) - { - timebaseSelection->setText(selectedTimebaseValue, dontSendNotification); - canvas->timebase = selectedTimebaseValue.getFloatValue(); - } - else - { - timebaseSelection->setSelectedId(selectedTimebase,dontSendNotification); - canvas->timebase = timebases[selectedTimebase-1].getFloatValue(); - } - - } -} - - -void LfpDisplayOptions::comboBoxChanged(ComboBox* cb) -{ - if (canvas->getNumChannels() == 0) return; - - if (cb == channelDisplaySkipSelection) - { - const int skipAmt = pow(2, cb->getSelectedId() - 1); - lfpDisplay->setChannelDisplaySkipAmount(skipAmt); - } - else if (cb == spikeRasterSelection) - { - // if custom value - if (cb->getSelectedId() == 0) - { - auto val = fabsf(cb->getText().getFloatValue()); - - if (val == 0) // if value is zero, just disable plotting and set text to "Off" - { - cb->setSelectedItemIndex(0, dontSendNotification); - lfpDisplay->setSpikeRasterPlotting(false); - return; - } - - if (val > 500) - { - val = 500; - } - - val *= -1; - - spikeRasterSelection->setText(String(val), dontSendNotification); - lfpDisplay->setSpikeRasterThreshold(val); - medianOffsetPlottingButton->setToggleState(true, dontSendNotification); - lfpDisplay->setMedianOffsetPlotting(true); - lfpDisplay->setSpikeRasterPlotting(true); - } - else if (cb->getSelectedItemIndex() == 0) // if "Off" - { - lfpDisplay->setSpikeRasterPlotting(false); - return; - } - else - { - auto val = cb->getText().getFloatValue(); - - lfpDisplay->setSpikeRasterThreshold(val); - medianOffsetPlottingButton->setToggleState(true, dontSendNotification); - lfpDisplay->setMedianOffsetPlotting(true); - lfpDisplay->setSpikeRasterPlotting(true); - } - } - else if (cb == colourSchemeOptionSelection) - { - // hide the old colour scheme config options if they are displayed - if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) - removeChildComponent(lfpDisplay->getColourSchemePtr()); - - // change the active colour scheme ptr - lfpDisplay->setActiveColourSchemeIdx(cb->getSelectedId()-1); - - // show the new colour scheme's config options if has any - - if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) - { - lfpDisplay->getColourSchemePtr()->setBounds(colourSchemeOptionLabel->getX(), - colourSchemeOptionLabel->getBottom(), - 200, - 110); - addAndMakeVisible(lfpDisplay->getColourSchemePtr()); - } - - // update the lfpDisplay's colors and redraw - lfpDisplay->setColors(); - canvas->redraw(); - } - else if (cb == timebaseSelection) - { - if (cb->getSelectedId()) - { - canvas->timebase = timebases[cb->getSelectedId()-1].getFloatValue(); - } - else - { - setTimebaseAndSelectionText(cb->getText().getFloatValue()); - } - } - else if (cb == rangeSelection) - { - if (cb->getSelectedId()) - { - lfpDisplay->setRange(voltageRanges[selectedChannelType][cb->getSelectedId()-1].getFloatValue()*rangeGain[selectedChannelType] - ,selectedChannelType); - } - else - { - float vRange = cb->getText().getFloatValue(); - if (vRange) - { - if (vRange < voltageRanges[selectedChannelType][0].getFloatValue()) - { - cb->setSelectedId(1,dontSendNotification); - vRange = voltageRanges[selectedChannelType][0].getFloatValue(); - } - else if (vRange > voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue()) - { - // cb->setSelectedId(voltageRanges[selectedChannelType].size(),dontSendNotification); - // vRange = voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue(); - } - else - { - if (rangeGain[selectedChannelType] > 1) - cb->setText(String(vRange,1),dontSendNotification); - else - cb->setText(String(vRange),dontSendNotification); - } - lfpDisplay->setRange(vRange*rangeGain[selectedChannelType],selectedChannelType); - } - else - { - if (selectedVoltageRange[selectedChannelType]) - cb->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification); - else - cb->setSelectedId(selectedVoltageRange[selectedChannelType],dontSendNotification); - } - } - selectedVoltageRange[selectedChannelType] = cb->getSelectedId(); - selectedVoltageRangeValues[selectedChannelType] = cb->getText(); - //std::cout << "Setting range to " << voltageRanges[cb->getSelectedId()-1].getFloatValue() << std::endl; - canvas->redraw(); - } - else if (cb == spreadSelection) - { - - if (cb->getSelectedId()) - { - if (lfpDisplay->getSingleChannelState()) - { - lfpDisplay->cacheNewChannelHeight(spreads[cb->getSelectedId()-1].getIntValue()); - } - else - { - lfpDisplay->setChannelHeight(spreads[cb->getSelectedId()-1].getIntValue()); - resized(); - } - } - else - { - int spread = cb->getText().getIntValue(); - if (spread) - { - if (spread < spreads[0].getFloatValue()) - { - cb->setSelectedId(1,dontSendNotification); - spread = spreads[0].getFloatValue(); - } - else if (spread > spreads[spreads.size()-1].getFloatValue()) - { - cb->setSelectedId(spreads.size(),dontSendNotification); - spread = spreads[spreads.size()-1].getFloatValue(); - } - else - { - cb->setText(String(spread),dontSendNotification); - } - - // if single channel focus is on, cache the value - if (lfpDisplay->getSingleChannelState()) - { - lfpDisplay->cacheNewChannelHeight(spread); - } - else - { - lfpDisplay->setChannelHeight(spread); - canvas->resized(); - } - } - else - { - if (selectedSpread == 0) - cb->setText(selectedSpreadValue,dontSendNotification); - else - cb->setSelectedId(selectedSpread,dontSendNotification); - } - } - selectedSpread = cb->getSelectedId(); - selectedSpreadValue = cb->getText(); - - if (!lfpDisplay->getSingleChannelState()) canvas->redraw(); - //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl; - } - else if (cb == saturationWarningSelection) - { - if (cb->getSelectedId()) - { - selectedSaturationValueFloat = (saturationThresholds[cb->getSelectedId()-1].getFloatValue()); - } - else - { - selectedSaturationValueFloat = cb->getText().getFloatValue(); - if (selectedSaturationValueFloat) - { - std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl; - if (selectedSaturationValueFloat < 0) - { - cb->setSelectedId(1,dontSendNotification); - selectedSaturationValueFloat = saturationThresholds[0].getFloatValue(); - } - else - { - // cb->setText(String(selectedSaturationValueFloat),dontSendNotification); - } - } - else - { - // cb->setSelectedId(1,dontSendNotification); - //selectedSaturationValueFloat = saturationThresholds[0].getFloatValue(); - - } - } - canvas->redraw(); - - std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl; - } - else if (cb == overlapSelection) - { - if (cb->getSelectedId()) - { - canvas->channelOverlapFactor = (overlaps[cb->getSelectedId()-1].getFloatValue()); - canvas->resized(); - } - else - { - float overlap = cb->getText().getFloatValue(); - if (overlap) - { - if (overlap < overlaps[0].getFloatValue()) - { - cb->setSelectedId(1,dontSendNotification); - overlap = overlaps[0].getFloatValue(); - } - else if (overlap > overlaps[overlaps.size()-1].getFloatValue()) - { - cb->setSelectedId(overlaps.size(),dontSendNotification); - overlap = overlaps[overlaps.size()-1].getFloatValue(); - } - else - { - cb->setText(String(overlap),dontSendNotification); - } - canvas->channelOverlapFactor= overlap; - canvas->resized(); - } - else - { - if (selectedSpread == 0) - cb->setText(selectedSpreadValue,dontSendNotification); - else - cb->setSelectedId(selectedSpread,dontSendNotification); - } - } - selectedSpread = cb->getSelectedId(); - selectedSpreadValue = cb->getText(); - lfpDisplay->setChannelHeight( lfpDisplay->getChannelHeight()); - canvas->redraw(); - //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl; - } - - else if (cb == colorGroupingSelection) - { - // set color grouping here - lfpDisplay->setColorGrouping(colorGroupings[cb->getSelectedId()-1].getIntValue());// so that channel colors get re-assigned - canvas->redraw(); - } - - timescale->setTimebase(canvas->timebase); -} - - -void LfpDisplayOptions::sliderValueChanged(Slider* sl) -{ - if (sl == brightnessSliderA) - canvas->histogramParameterA = sl->getValue(); - - if (sl == brightnessSliderB) - canvas->histogramParameterB = sl->getValue(); - - - canvas->fullredraw=true; - //repaint(); - canvas->refresh(); - -} - -void LfpDisplayOptions::sliderEvent(Slider* sl) {} - -DataChannel::DataChannelTypes LfpDisplayOptions::getChannelType(int n) -{ - if (n < processor->getNumInputs()) - return processor->getDataChannel(n)->getChannelType(); - else - return DataChannel::HEADSTAGE_CHANNEL; -} - -DataChannel::DataChannelTypes LfpDisplayOptions::getSelectedType() -{ - return selectedChannelType; -} - -void LfpDisplayOptions::setSelectedType(DataChannel::DataChannelTypes type, bool toggleButton) -{ - if (selectedChannelType == type) - return; //Nothing to do here - selectedChannelType = type; - rangeSelection->clear(dontSendNotification); - rangeSelection->addItemList(voltageRanges[type],1); - - int id = selectedVoltageRange[type]; - if (id) - rangeSelection->setSelectedId(id,sendNotification); - else - rangeSelection->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification); - - repaint(5,getHeight()-55,300,100); - - if (toggleButton) - typeButtons[type]->setToggleState(true,dontSendNotification); -} - -String LfpDisplayOptions::getTypeName(DataChannel::DataChannelTypes type) -{ - return typeNames[type]; -} - -int LfpDisplayOptions::getRangeStep(DataChannel::DataChannelTypes type) -{ - return rangeSteps[type]; -} - - - -void LfpDisplayOptions::saveParameters(XmlElement* xml) -{ - // TODO: (kelly) add savers for: - // - channel reverse - // - channel zoom slider - // - channel display skip - std::cout << "Saving lfp display params" << std::endl; - - XmlElement* xmlNode = xml->createNewChildElement("LFPDISPLAY"); - - lfpDisplay->reactivateChannels(); - - xmlNode->setAttribute("Range",selectedVoltageRangeValues[0]+","+selectedVoltageRangeValues[1]+ - ","+selectedVoltageRangeValues[2]); - xmlNode->setAttribute("Timebase",timebaseSelection->getText()); - xmlNode->setAttribute("Spread",spreadSelection->getText()); - xmlNode->setAttribute("colorGrouping",colorGroupingSelection->getSelectedId()); - xmlNode->setAttribute("isInverted",invertInputButton->getToggleState()); - xmlNode->setAttribute("drawMethod",drawMethodButton->getToggleState()); - - int eventButtonState = 0; - - for (int i = 0; i < 8; i++) - { - if (lfpDisplay->eventDisplayEnabled[i]) - { - eventButtonState += (1 << i); - } - } - - lfpDisplay->reactivateChannels(); - - xmlNode->setAttribute("EventButtonState", eventButtonState); - - String channelDisplayState = ""; - - for (int i = 0; i < canvas->nChans; i++) - { - if (lfpDisplay->getEnabledState(i)) - { - channelDisplayState += "1"; - } - else - { - channelDisplayState += "0"; - } - //std::cout << channelDisplayState; - } - - //std::cout << std::endl; - - - xmlNode->setAttribute("ChannelDisplayState", channelDisplayState); - - xmlNode->setAttribute("ScrollX",canvas->viewport->getViewPositionX()); - xmlNode->setAttribute("ScrollY",canvas->viewport->getViewPositionY()); -} - - -void LfpDisplayOptions::loadParameters(XmlElement* xml) -{ - // TODO: (kelly) add loaders for: - // - channel reverse - // - channel zoom slider - // - channel display skip - forEachXmlChildElement(*xml, xmlNode) - { - if (xmlNode->hasTagName("LFPDISPLAY")) - { - StringArray ranges; - ranges.addTokens(xmlNode->getStringAttribute("Range"),",",String::empty); - selectedVoltageRangeValues[0] = ranges[0]; - selectedVoltageRangeValues[1] = ranges[1]; - selectedVoltageRangeValues[2] = ranges[2]; - selectedVoltageRange[0] = voltageRanges[0].indexOf(ranges[0])+1; - selectedVoltageRange[1] = voltageRanges[1].indexOf(ranges[1])+1; - selectedVoltageRange[2] = voltageRanges[2].indexOf(ranges[2])+1; - rangeSelection->setText(ranges[0]); - - timebaseSelection->setText(xmlNode->getStringAttribute("Timebase")); - spreadSelection->setText(xmlNode->getStringAttribute("Spread")); - if (xmlNode->hasAttribute("colorGrouping")) - { - colorGroupingSelection->setSelectedId(xmlNode->getIntAttribute("colorGrouping")); - } - else - { - colorGroupingSelection->setSelectedId(1); - } - - invertInputButton->setToggleState(xmlNode->getBoolAttribute("isInverted", true), sendNotification); - - drawMethodButton->setToggleState(xmlNode->getBoolAttribute("drawMethod", true), sendNotification); - - canvas->viewport->setViewPosition(xmlNode->getIntAttribute("ScrollX"), - xmlNode->getIntAttribute("ScrollY")); - - int eventButtonState = xmlNode->getIntAttribute("EventButtonState"); - - for (int i = 0; i < 8; i++) - { - lfpDisplay->eventDisplayEnabled[i] = (eventButtonState >> i) & 1; - - eventDisplayInterfaces[i]->checkEnabledState(); - } - - String channelDisplayState = xmlNode->getStringAttribute("ChannelDisplayState"); - - for (int i = 0; i < channelDisplayState.length(); i++) - { - - if (channelDisplayState.substring(i,i+1).equalsIgnoreCase("1")) - { - //std::cout << "LfpDisplayCanvas enabling channel " << i << std::endl; - //lfpDisplay->enableChannel(true, i); - canvas->isChannelEnabled.set(i,true); //lfpDisplay->enableChannel(true, i); - } - else - { - //lfpDisplay->enableChannel(false, i); - canvas->isChannelEnabled.set(i,false); - } - - - } - } - } - -} - - -#pragma mark - LfpTimescale - -// ------------------------------------------------------------- - -LfpTimescale::LfpTimescale(LfpDisplayCanvas* c, LfpDisplay* lfpDisplay) - : canvas(c) - , lfpDisplay(lfpDisplay) -{ - - font = Font("Default", 16, Font::plain); -} - -LfpTimescale::~LfpTimescale() -{ - -} - -void LfpTimescale::paint(Graphics& g) -{ - - g.setFont(font); - - g.setColour(Colour(100,100,100)); - - const String timeScaleUnitLabel = (timebase >= 2)?("s:"):("ms:"); - g.drawText(timeScaleUnitLabel,5,0,100,getHeight(),Justification::left, false); - - const int steps = labels.size() + 1; - for (int i = 0; i < steps; i++) - { - - // TODO: (kelly) added an extra spatial dimension to the timeline ticks, may be overkill - if (i == 0) - { - g.drawLine(1, - 0, - 1, - getHeight(), - 3.0f); - } - if (i != 0 && i % 4 == 0) - { - g.drawLine(getWidth()/steps*i, - 0, - getWidth()/steps*i, - getHeight(), - 3.0f); - } - else if (i != 0 && i % 2 == 0) - { - g.drawLine(getWidth()/steps*i, - getHeight(), - getWidth()/steps*i, - getHeight() / 2, - 3.0f); - } - else - { - g.drawLine(getWidth()/steps*i, - getHeight(), - getWidth()/steps*i, - 3 * getHeight()/4, - 2.0f); - } - - - if (i != 0 && i % 2 == 0) - g.drawText(labels[i-1],getWidth()/steps*i+3,0,100,getHeight(),Justification::left, false); - - } -} - -void LfpTimescale::mouseUp(const MouseEvent &e) -{ - if (e.mods.isLeftButtonDown()) - { - lfpDisplay->trackZoomInfo.isScrollingX = false; - } -} - -void LfpTimescale::resized() -{ - setTimebase(timebase); -} - -void LfpTimescale::mouseDrag(const juce::MouseEvent &e) -{ - if (e.mods.isLeftButtonDown()) // double check that we initiate only for left click and hold - { - if (e.mods.isCommandDown()) // CTRL + drag -> change channel spacing - { - // init state in our track zooming info struct - if (!lfpDisplay->trackZoomInfo.isScrollingX) - { - lfpDisplay->trackZoomInfo.isScrollingX = true; - lfpDisplay->trackZoomInfo.timescaleStartScale = timebase; - } - - float timescale = lfpDisplay->trackZoomInfo.timescaleStartScale; - float dTimescale=0; - int dragDeltaX = (e.getScreenPosition().getX() - e.getMouseDownScreenX()); // invert so drag up -> scale up - -// std::cout << dragDeltaX << std::endl; - if (dragDeltaX > 0) - { - dTimescale = 0.01 * dragDeltaX; - } - else - { - // TODO: (kelly) change this to scale appropriately for -dragDeltaX - if (timescale > 0.25) - dTimescale = 0.01 * dragDeltaX; - } - - if (timescale >= 1) // accelerate scrolling for large ranges - dTimescale *= 4; - - if (timescale >= 5) - dTimescale *= 4; - - if (timescale >= 10) - dTimescale *= 4; - - // round dTimescale to the nearest 0.005 sec - dTimescale = ((dTimescale + (0.005/2)) / 0.005) * 0.005; - - float newTimescale = timescale+dTimescale; - - if (newTimescale < 0.25) newTimescale = 0.250; - if (newTimescale > 20) newTimescale = 20; - - // don't bother updating if the new timebase is the same as the old (if clipped, for example) - if (timescale != newTimescale) - { - lfpDisplay->options->setTimebaseAndSelectionText(newTimescale); - setTimebase(canvas->timebase); - } - } - } -} - -void LfpTimescale::setTimebase(float t) -{ - timebase = t; - - labels.clear(); - - const int minWidth = 60; - labelIncrement = 0.025f; - - - while (getWidth() != 0 && // setTimebase can be called before LfpTimescale has width - getWidth() / (timebase / labelIncrement) < minWidth) // so, if width is 0 then don't iterate for scale factor - { -// std::cout << getWidth() / (timebase / labelIncrement) << " is smaller than minimum width, calculating new step size" << std::endl; - if (labelIncrement < 0.2) - labelIncrement *= 2; - else - labelIncrement += 0.2f; - } - - for (float i = labelIncrement; i < timebase; i += labelIncrement) - { - String labelString = String(i * ((timebase >= 2)?(1):(1000.0f))); - labels.add(labelString.substring(0,6)); - } - - repaint(); - -} - - - -#pragma mark - LfpDisplay - -// --------------------------------------------------------------- - -LfpDisplay::LfpDisplay(LfpDisplayCanvas* c, Viewport* v) - : singleChan(-1) - , canvas(c) - , viewport(v) - , channelsReversed(false) - , displaySkipAmt(0) - , m_SpikeRasterPlottingFlag(false) -{ - perPixelPlotter = new PerPixelBitmapPlotter(this); - supersampledPlotter = new SupersampledBitmapPlotter(this); - -// colorScheme = new LfpDefaultColourScheme(); - colourSchemeList.add(new LfpDefaultColourScheme(this, canvas)); - colourSchemeList.add(new LfpMonochromaticColourScheme(this, canvas)); - colourSchemeList.add(new LfpGradientColourScheme(this, canvas)); - - activeColourScheme = 0; - - - plotter = perPixelPlotter; - m_MedianOffsetPlottingFlag = false; - - totalHeight = 0; - colorGrouping=1; - - range[0] = 1000; - range[1] = 500; - range[2] = 500000; - - addMouseListener(this, true); - - // hue cycle - //for (int i = 0; i < 15; i++) - //{ - // channelColours.add(Colour(float(sin((3.14/2)*(float(i)/15))),float(1.0),float(1),float(1.0))); - //} - -// setBufferedToImage(true); // TODO: (kelly) test - - backgroundColour = Colour(0,18,43); - - //hand-built palette - channelColours.add(Colour(224,185,36)); - channelColours.add(Colour(214,210,182)); - channelColours.add(Colour(243,119,33)); - channelColours.add(Colour(186,157,168)); - channelColours.add(Colour(237,37,36)); - channelColours.add(Colour(179,122,79)); - channelColours.add(Colour(217,46,171)); - channelColours.add(Colour(217, 139,196)); - channelColours.add(Colour(101,31,255)); - channelColours.add(Colour(141,111,181)); - channelColours.add(Colour(48,117,255)); - channelColours.add(Colour(184,198,224)); - channelColours.add(Colour(116,227,156)); - channelColours.add(Colour(150,158,155)); - channelColours.add(Colour(82,173,0)); - channelColours.add(Colour(125,99,32)); - - isPaused=false; - -} - -LfpDisplay::~LfpDisplay() -{ -// deleteAllChildren(); -} - - - -int LfpDisplay::getNumChannels() -{ - return numChans; -} - - - -int LfpDisplay::getColorGrouping() -{ - return colorGrouping; -} - -void LfpDisplay::setColorGrouping(int i) -{ - colorGrouping=i; - getColourSchemePtr()->setColourGrouping(i); - setColors(); // so that channel colors get re-assigned - -} - -LfpChannelColourScheme * LfpDisplay::getColourSchemePtr() -{ - return colourSchemeList[activeColourScheme]; -} - -void LfpDisplay::setNumChannels(int numChannels) -{ - numChans = numChannels; - - -// deleteAllChildren(); - removeAllChildren(); - - channels.clear(); - channelInfo.clear(); - drawableChannels.clear(); - - totalHeight = 0; - cachedDisplayChannelHeight = canvas->getChannelHeight(); - - if (numChans > 0) - { - for (int i = 0; i < numChans; i++) - { - //std::cout << "Adding new display for channel " << i << std::endl; - - LfpChannelDisplay* lfpChan = new LfpChannelDisplay(canvas, this, options, i); - - //lfpChan->setColour(channelColours[i % channelColours.size()]); - lfpChan->setRange(range[options->getChannelType(i)]); - lfpChan->setChannelHeight(canvas->getChannelHeight()); - - addAndMakeVisible(lfpChan); - - channels.add(lfpChan); - - LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, this, options, i); - - //lfpInfo->setColour(channelColours[i % channelColours.size()]); - lfpInfo->setRange(range[options->getChannelType(i)]); - lfpInfo->setChannelHeight(canvas->getChannelHeight()); - lfpInfo->setSubprocessorIdx(canvas->getChannelSubprocessorIdx(i)); - - addAndMakeVisible(lfpInfo); - - channelInfo.add(lfpInfo); - - drawableChannels.add(LfpChannelTrack{ - lfpChan, - lfpInfo - }); - - savedChannelState.add(true); - - totalHeight += lfpChan->getChannelHeight(); - - } - - } - - setColors(); - - - std::cout << "TOTAL HEIGHT = " << totalHeight << std::endl; - -} - -void LfpDisplay::setColors() -{ - for (int i = 0; i < drawableChannels.size(); i++) - { - -// channels[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); -// channelInfo[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); - drawableChannels[i].channel->setColour(getColourSchemePtr()->getColourForIndex(i)); - drawableChannels[i].channelInfo->setColour(getColourSchemePtr()->getColourForIndex(i)); - } - -} - -void LfpDisplay::setActiveColourSchemeIdx(int index) -{ - activeColourScheme = index; -} - -int LfpDisplay::getActiveColourSchemeIdx() -{ - return activeColourScheme; -} - -int LfpDisplay::getNumColourSchemes() -{ - return colourSchemeList.size(); -} - -StringArray LfpDisplay::getColourSchemeNameArray() -{ - StringArray nameList; - for (auto scheme : colourSchemeList) - nameList.add(scheme->getName()); - - return nameList; -} - -int LfpDisplay::getTotalHeight() -{ - return totalHeight; -} - -void LfpDisplay::resized() -{ - int totalHeight = 0; - - for (int i = 0; i < drawableChannels.size(); i++) - { - - LfpChannelDisplay* disp = drawableChannels[i].channel; - - if (disp->getHidden()) continue; - - disp->setBounds(canvas->leftmargin, - totalHeight-(disp->getChannelOverlap()*canvas->channelOverlapFactor)/2, - getWidth(), - disp->getChannelHeight()+(disp->getChannelOverlap()*canvas->channelOverlapFactor)); - - disp-> resized(); - - LfpChannelDisplayInfo* info = drawableChannels[i].channelInfo; - - info->setBounds(0, - totalHeight-disp->getChannelHeight() + (disp->getChannelOverlap()*canvas->channelOverlapFactor)/4.0, - canvas->leftmargin + 50, - disp->getChannelHeight()); - - totalHeight += disp->getChannelHeight(); - - } - - canvas->fullredraw = true; //issue full redraw - if (singleChan != -1) - viewport->setViewPosition(Point(0,singleChan*getChannelHeight())); - - - - lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); - - //inititalize black background - Graphics gLfpChannelBitmap(lfpChannelBitmap); - gLfpChannelBitmap.setColour(Colour(0,0,0)); //background color - gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); - - - canvas->fullredraw = true; - - refresh(); - // std::cout << "Total height: " << totalHeight << std::endl; - -} - -void LfpDisplay::paint(Graphics& g) -{ - - g.drawImageAt(lfpChannelBitmap, canvas->leftmargin,0); - -} - - -void LfpDisplay::refresh() -{ - // Ensure the lfpChannelBitmap has been initialized - if (lfpChannelBitmap.isNull()) - { - resized(); - } - - // X-bounds of this update - int fillfrom = canvas->lastScreenBufferIndex[0]; - int fillto = (canvas->screenBufferIndex[0]); - - if (fillfrom<0){fillfrom=0;}; - if (fillto>lfpChannelBitmap.getWidth()){fillto=lfpChannelBitmap.getWidth();}; - - int topBorder = viewport->getViewPositionY(); - int bottomBorder = viewport->getViewHeight() + topBorder; - - // clear appropriate section of the bitmap -- - // we need to do this before each channel draws its new section of data into lfpChannelBitmap - Graphics gLfpChannelBitmap(lfpChannelBitmap); - gLfpChannelBitmap.setColour(backgroundColour); //background color - - if (canvas->fullredraw) - { - gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); - } else { - gLfpChannelBitmap.setColour(backgroundColour); //background color - - gLfpChannelBitmap.fillRect(fillfrom,0, (fillto-fillfrom)+1, getHeight()); - }; - - - for (int i = 0; i < numChans; i++) -// for (int i = 0; i < drawableChannels.size(); ++i) - { - - int componentTop = channels[i]->getY(); - int componentBottom = channels[i]->getHeight() + componentTop; - - if ((topBorder <= componentBottom && bottomBorder >= componentTop)) // only draw things that are visible - { - if (canvas->fullredraw) - { - channels[i]->fullredraw = true; - - channels[i]->pxPaint(); - channelInfo[i]->repaint(); - - } - else - { - channels[i]->pxPaint(); // draws to lfpChannelBitmap - - // it's not clear why, but apparently because the pxPaint() in a child component of LfpDisplay, we also need to issue repaint() calls for each channel, even though there's nothin to repaint there. Otherwise, the repaint call in LfpDisplay::refresh(), a few lines down, lags behind the update line by ~60 px. This could ahev something to do with teh reopaint message passing in juce. In any case, this seemingly redundant repaint here seems to fix the issue. - - // we redraw from 0 to +2 (px) relative to the real redraw window, the +1 draws the vertical update line - channels[i]->repaint(fillfrom, 0, (fillto-fillfrom)+2, channels[i]->getHeight()); - - - } - //std::cout << i << std::endl; - } - - } - - if (fillfrom == 0 && singleChan != -1) - { - channelInfo[singleChan]->repaint(); - } - - - if (canvas->fullredraw) - { - repaint(0,topBorder,getWidth(),bottomBorder-topBorder); - }else{ - //repaint(fillfrom, topBorder, (fillto-fillfrom)+1, bottomBorder-topBorder); // doesntb seem to be needed and results in duplicate repaint calls - } - - canvas->fullredraw = false; -} - - - -void LfpDisplay::setRange(float r, DataChannel::DataChannelTypes type) -{ - range[type] = r; - - if (channels.size() > 0) - { - - for (int i = 0; i < numChans; i++) - { - if (channels[i]->getType() == type) - channels[i]->setRange(range[type]); - } - canvas->fullredraw = true; //issue full redraw - } -} - -int LfpDisplay::getRange() -{ - return getRange(options->getSelectedType()); -} - -int LfpDisplay::getRange(DataChannel::DataChannelTypes type) -{ - for (int i=0; i < numChans; i++) - { - if (channels[i]->getType() == type) - return channels[i]->getRange(); - } - return 0; -} - - -void LfpDisplay::setChannelHeight(int r, bool resetSingle) -{ - if (!getSingleChannelState()) cachedDisplayChannelHeight = r; - - for (int i = 0; i < numChans; i++) - { - channels[i]->setChannelHeight(r); - channelInfo[i]->setChannelHeight(r); - } - if (resetSingle && singleChan != -1) - { - //std::cout << "width " << getWidth() << " numchans " << numChans << " height " << getChannelHeight() << std::endl; - setSize(getWidth(),drawableChannels.size()*getChannelHeight()); - viewport->setScrollBarsShown(true,false); - viewport->setViewPosition(Point(0,singleChan*r)); - singleChan = -1; - for (int n = 0; n < numChans; n++) - { - channelInfo[n]->setEnabledState(savedChannelState[n]); - } - } - - resized(); - -} - -void LfpDisplay::setInputInverted(bool isInverted) -{ - - for (int i = 0; i < numChans; i++) - { - channels[i]->setInputInverted(isInverted); - } - - resized(); - -} - -void LfpDisplay::setDrawMethod(bool isDrawMethod) -{ - for (int i = 0; i < numChans; i++) - { - channels[i]->setDrawMethod(isDrawMethod); - } - - if (isDrawMethod) - { - plotter = supersampledPlotter; - } - else - { - plotter = perPixelPlotter; - } - - resized(); - -} - - -int LfpDisplay::getChannelHeight() -{ -// return cachedDisplayChannelHeight; - return drawableChannels[0].channel->getChannelHeight(); -// return channels[0]->getChannelHeight(); -} - -void LfpDisplay::cacheNewChannelHeight(int r) -{ - cachedDisplayChannelHeight = r; -} - -bool LfpDisplay::getChannelsReversed() -{ - return channelsReversed; -} - -void LfpDisplay::setChannelsReversed(bool state) -{ - if (state == channelsReversed) return; // bail early, in case bookkeeping error - - channelsReversed = state; - - if (getSingleChannelState()) return; // don't reverse if single channel - - // reverse channels that are currently in drawableChannels - for (int i = 0, j = drawableChannels.size() - 1, len = drawableChannels.size()/2; - i < len; - i++, j--) - { - // remove channel and info components from front and back - // moving toward middle - removeChildComponent(drawableChannels[i].channel); - removeChildComponent(drawableChannels[j].channel); - removeChildComponent(drawableChannels[i].channelInfo); - removeChildComponent(drawableChannels[j].channelInfo); - - // swap front and back, moving towards middle - drawableChannels.swap(i, j); - - // also swap coords - { - const auto channelBoundsA = drawableChannels[i].channel->getBounds(); - const auto channelInfoBoundsA = drawableChannels[i].channelInfo->getBounds(); - - drawableChannels[i].channel->setBounds(drawableChannels[j].channel->getBounds()); - drawableChannels[i].channelInfo->setBounds(drawableChannels[j].channelInfo->getBounds()); - drawableChannels[j].channel->setBounds(channelBoundsA); - drawableChannels[j].channelInfo->setBounds(channelInfoBoundsA); - } - } - - - // remove middle component if odd number of channels - if (drawableChannels.size() % 2 != 0) - { - removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channel); - removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channelInfo); - } - - // add the channels and channel info again - for (int i = 0, len = drawableChannels.size(); i < len; i++) - { - - if (!drawableChannels[i].channel->getHidden()) - { - addAndMakeVisible(drawableChannels[i].channel); - addAndMakeVisible(drawableChannels[i].channelInfo); - } - - // flag this to update the waveforms - drawableChannels[i].channel->fullredraw = true; - } - - // necessary to overwrite lfpChannelBitmap's display - refresh(); -} - -int LfpDisplay::getChannelDisplaySkipAmount() -{ - return displaySkipAmt; -} - -void LfpDisplay::setChannelDisplaySkipAmount(int skipAmt) -{ - displaySkipAmt = skipAmt; - - if (!getSingleChannelState()) - rebuildDrawableChannelsList(); - - canvas->redraw(); -} - -bool LfpDisplay::getMedianOffsetPlotting() -{ - return m_MedianOffsetPlottingFlag; -} - -void LfpDisplay::setMedianOffsetPlotting(bool isEnabled) -{ - m_MedianOffsetPlottingFlag = isEnabled; -} - -bool LfpDisplay::getSpikeRasterPlotting() -{ - return m_SpikeRasterPlottingFlag; -} - -void LfpDisplay::setSpikeRasterPlotting(bool isEnabled) -{ - m_SpikeRasterPlottingFlag = isEnabled; -} - -float LfpDisplay::getSpikeRasterThreshold() -{ - return m_SpikeRasterThreshold; -} - -void LfpDisplay::setSpikeRasterThreshold(float thresh) -{ - m_SpikeRasterThreshold = thresh; -} - -void LfpDisplay::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) -{ - - //std::cout << "Mouse wheel " << e.mods.isCommandDown() << " " << wheel.deltaY << std::endl; - //TODO Changing ranges with the wheel is currently broken. With multiple ranges, most - //of the wheel range code needs updating - - - if (e.mods.isCommandDown() && singleChan == -1) // CTRL + scroll wheel -> change channel spacing - { - int h = getChannelHeight(); - int hdiff=0; - - // std::cout << wheel.deltaY << std::endl; - - if (wheel.deltaY > 0) - { - hdiff = 2; - } - else - { - if (h > 5) - hdiff = -2; - } - - if (abs(h) > 100) // accelerate scrolling for large ranges - hdiff *= 3; - - int newHeight = h+hdiff; - - // constrain the spread resizing to max and min values; - if (newHeight < trackZoomInfo.minZoomHeight) - { - newHeight = trackZoomInfo.minZoomHeight; - hdiff = 0; - } - else if (newHeight > trackZoomInfo.maxZoomHeight) - { - newHeight = trackZoomInfo.maxZoomHeight; - hdiff = 0; - } - - setChannelHeight(newHeight); - int oldX=viewport->getViewPositionX(); - int oldY=viewport->getViewPositionY(); - - setBounds(0,0,getWidth()-0, getChannelHeight()*drawableChannels.size()); // update height so that the scrollbar is correct - - int mouseY=e.getMouseDownY(); // should be y pos relative to inner viewport (0,0) - int scrollBy = (mouseY/h)*hdiff*2;// compensate for motion of point under current mouse position - viewport->setViewPosition(oldX,oldY+scrollBy); // set back to previous position plus offset - - options->setSpreadSelection(newHeight); // update combobox - - canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw - } - else - { - if (e.mods.isAltDown()) // ALT + scroll wheel -> change channel range (was SHIFT but that clamps wheel.deltaY to 0 on OSX for some reason..) - { - int h = getRange(); - - - int step = options->getRangeStep(options->getSelectedType()); - - // std::cout << wheel.deltaY << std::endl; - - if (wheel.deltaY > 0) - { - setRange(h+step,options->getSelectedType()); - } - else - { - if (h > step+1) - setRange(h-step,options->getSelectedType()); - } - - options->setRangeSelection(h); // update combobox - canvas->fullredraw = true; //issue full redraw - scrolling without modifier doesnt require a full redraw - - } - else // just scroll - { - // passes the event up to the viewport so the screen scrolls - if (viewport != nullptr && e.eventComponent == this) // passes only if it's not a listening event - viewport->mouseWheelMove(e.getEventRelativeTo(canvas), wheel); - - } - - - } - //refresh(); // doesn't seem to be needed now that channels daraw to bitmap - -} - -void LfpDisplay::toggleSingleChannel(int chan) -{ - if (!getSingleChannelState()) - { - - std::cout << "Single channel on (" << chan << ")" << std::endl; - singleChan = chan; - - int newHeight = viewport->getHeight(); - LfpChannelTrack lfpChannelTrack{drawableChannels[chan].channel, drawableChannels[chan].channelInfo}; - lfpChannelTrack.channelInfo->setEnabledState(true); - lfpChannelTrack.channelInfo->setSingleChannelState(true); - - removeAllChildren(); - - // disable unused channels - for (int i = 0; i < getNumChannels(); i++) - { - if (i != chan) - { - drawableChannels[i].channel->setEnabledState(false); - } - } - - // update drawableChannels, give only the single channel to focus on - drawableChannels.clearQuick(); - drawableChannels.add(lfpChannelTrack); - - addAndMakeVisible(lfpChannelTrack.channel); - addAndMakeVisible(lfpChannelTrack.channelInfo); - - // set channel height and position (so that we allocate the smallest - // necessary image size for drawing) - setChannelHeight(newHeight, false); - - lfpChannelTrack.channel->setTopLeftPosition(canvas->leftmargin, 0); - lfpChannelTrack.channelInfo->setTopLeftPosition(0, 0); - setSize(getWidth(), getChannelHeight()); - - viewport->setViewPosition(0, 0); - - } -// else if (chan == singleChan || chan == -2) - else - { - std::cout << "Single channel off" << std::endl; - for (int n = 0; n < numChans; n++) - { - channelInfo[n]->setSingleChannelState(false); - } - - setChannelHeight(cachedDisplayChannelHeight); - - reactivateChannels(); - rebuildDrawableChannelsList(); - } -} - -void LfpDisplay::reactivateChannels() -{ - - for (int n = 0; n < channels.size(); n++) - setEnabledState(savedChannelState[n], n); - -} - -void LfpDisplay::rebuildDrawableChannelsList() -{ - - if (displaySkipAmt != 0) removeAllChildren(); // start with clean slate - - Array channelsToDraw; - drawableChannels = Array(); - - // iterate over all channels and select drawable ones - for (int i = 0, drawableChannelNum = 0; i < channels.size(); i++) - { -// std::cout << "\tchannel " << i << " has subprocessor index of " << channelInfo[i]->getSubprocessorIdx() << std::endl; - // if channel[i] is not sourced from the correct subprocessor, then hide it and continue - //if (channelInfo[i]->getSubprocessorIdx() != getDisplayedSubprocessor()) - //{ - // channels[i]->setHidden(true); - // channelInfo[i]->setHidden(true); - // continue; - //} - - //std::cout << "Checking for hidden channels" << std::endl; - if (displaySkipAmt == 0 || (i % displaySkipAmt == 0)) // no skips, add all channels - { - channels[i]->setHidden(false); - channelInfo[i]->setHidden(false); - - channelInfo[i]->setDrawableChannelNumber(drawableChannelNum++); - channelInfo[i]->resized(); // to update the conditional drawing of enableButton and channel num - - channelsToDraw.add(LfpDisplay::LfpChannelTrack{ - channels[i], - channelInfo[i] - }); - - addAndMakeVisible(channels[i]); - addAndMakeVisible(channelInfo[i]); - } - else // skip some channels - { -// if (i % (displaySkipAmt) == 0) // add these channels -// { -// channels[i]->setHidden(false); -// channelInfo[i]->setHidden(false); -// -// channelsToDraw.add(LfpDisplay::LfpChannelTrack{ -// channels[i], -// channelInfo[i] -// }); -// -// addAndMakeVisible(channels[i]); -// addAndMakeVisible(channelInfo[i]); -// } -// else // but not these -// { - channels[i]->setHidden(true); - channelInfo[i]->setHidden(true); - - removeChildComponent(channels[i]); - removeChildComponent(channelInfo[i]); -// } - } - } - - // check if channels should be added to drawableChannels in reverse - if (getChannelsReversed()) - { - for (int i = channelsToDraw.size() - 1; i >= 0; --i) - { - drawableChannels.add(channelsToDraw[i]); - } - } - else - { - for (int i = 0; i < channelsToDraw.size(); ++i) - { - drawableChannels.add(channelsToDraw[i]); - } - } - - // this guards against an exception where the editor sets the drawable samplerate - // before the lfpDisplay is fully initialized - if (getHeight() > 0 && getWidth() > 0) - { - canvas->resizeToChannels(); - } - - setColors(); -} - -LfpBitmapPlotter * const LfpDisplay::getPlotterPtr() const -{ - return plotter; -} - -bool LfpDisplay::getSingleChannelState() -{ - //if (singleChan < 0) return false; - //else return true; - return singleChan >= 0; -} - - -void LfpDisplay::mouseDown(const MouseEvent& event) -{ - if (drawableChannels.isEmpty()) - { - return; - } - - //int y = event.getMouseDownY(); //relative to each channel pos - MouseEvent canvasevent = event.getEventRelativeTo(viewport); - int y = canvasevent.getMouseDownY() + viewport->getViewPositionY(); // need to account for scrolling - int x = canvasevent.getMouseDownX(); - - int dist = 0; - int mindist = 10000; - int closest = 5; - for (int n = 0; n < drawableChannels.size(); n++) // select closest instead of relying on eventComponent - { - drawableChannels[n].channel->deselect(); - - int cpos = (drawableChannels[n].channel->getY() + (drawableChannels[n].channel->getHeight()/2)); - dist = int(abs(y - cpos)); - -// std::cout << "Mouse down at " << y << " pos is "<< cpos << " n: " << n << " dist " << dist << std::endl; - - if (dist < mindist) - { - mindist = dist-1; - closest = n; - } - } - - drawableChannels[closest].channel->select(); - options->setSelectedType(drawableChannels[closest].channel->getType()); - - if (event.mods.isRightButtonDown()) { // if right click - PopupMenu channelMenu = channels[closest]->getOptions(); - const int result = channelMenu.show(); - drawableChannels[closest].channel->changeParameter(result); - } - else // if left click - { -// if (singleChan != -1) - if (event.getNumberOfClicks() == 2) { - toggleSingleChannel(closest); - } - - if (getSingleChannelState()) - { - - // std::cout << "singleChan = " << singleChan << " " << y << " " << drawableChannels[0].channel->getHeight() << " " << getRange() << std::endl; - //channelInfo[singleChan]->updateXY( - drawableChannels[0].channelInfo->updateXY( - float(x)/getWidth()*canvas->timebase, - (-(float(y)-viewport->getViewPositionY())/viewport->getViewHeight()*float(getRange()))+float(getRange()/2) - ); - } - } - -// canvas->fullredraw = true;//issue full redraw - -// refresh(); - -} - - -bool LfpDisplay::setEventDisplayState(int ch, bool state) -{ - eventDisplayEnabled[ch] = state; - return eventDisplayEnabled[ch]; -} - - -bool LfpDisplay::getEventDisplayState(int ch) -{ - return eventDisplayEnabled[ch]; -} - - -void LfpDisplay::setEnabledState(bool state, int chan, bool updateSaved) -{ - if (chan < numChans) - { - channels[chan]->setEnabledState(state); - channelInfo[chan]->setEnabledState(state); - - if (updateSaved) - savedChannelState.set(chan, state); - - canvas->isChannelEnabled.set(chan, state); - } -} - -bool LfpDisplay::getEnabledState(int chan) -{ - if (chan < numChans) - { - return channels[chan]->getEnabledState(); - } - - return false; -} - - - -#pragma mark - LfpChannelDisplay - -// ------------------------------------------------------------------ - -LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, LfpDisplay* d, LfpDisplayOptions* o, int channelNumber) - : canvas(c) - , display(d) - , options(o) - , isSelected(false) - , chan(channelNumber) - , name("") - , drawableChan(channelNumber) - , channelOverlap(300) - , channelHeight(30) - , range(250.0f) - , isEnabled(true) - , inputInverted(false) - , canBeInverted(true) - , drawMethod(false) - , isHidden(false) -{ - - - name = String(channelNumber+1); // default is to make the channelNumber the name - - - channelHeightFloat = (float) channelHeight; - - channelFont = Font("Default", channelHeight*0.6, Font::plain); - - lineColour = Colour(255,255,255); - - type = options->getChannelType(channelNumber); - typeStr = options->getTypeName(type); - -} - -LfpChannelDisplay::~LfpChannelDisplay() -{ - -} - -void LfpChannelDisplay::resized() -{ - // all of this will likely need to be moved into the sharedLfpDisplay image in the lfpDisplay, not here - // now that the complete height is know, the sharedLfpDisplay image that we'll draw the pixel-wise lfp plot to needs to be resized - //lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); -} - - -void LfpChannelDisplay::updateType() -{ - type = options->getChannelType(chan); - typeStr = options->getTypeName(type); -} - -void LfpChannelDisplay::setEnabledState(bool state) -{ - - //if (state) - //std::cout << "Setting channel " << name << " to true." << std::endl; - //else - //std::cout << "Setting channel " << name << " to false." << std::endl; - - isEnabled = state; - -} - -void LfpChannelDisplay::setHidden(bool isHidden_) -{ - isHidden = isHidden_; - isEnabled = !isHidden; -} - -void LfpChannelDisplay::pxPaint() -{ - if (!isEnabled) return; // return early if THIS display is not enabled - - Image::BitmapData bdLfpChannelBitmap(display->lfpChannelBitmap, 0,0, display->lfpChannelBitmap.getWidth(), display->lfpChannelBitmap.getHeight()); - - int center = getHeight()/2; - - // max and min of channel in absolute px coords for event displays etc - actual data might be drawn outside of this range - int jfrom_wholechannel= (int) (getY()+center-channelHeight/2)+1 +0 ; - int jto_wholechannel= (int) (getY()+center+channelHeight/2) -0; - - //int jfrom_wholechannel_almost= (int) (getY()+center-channelHeight/3)+1 +0 ; // a bit less tall, for saturation warnings - //int jto_wholechannel_almost= (int) (getY()+center+channelHeight/3) -0; - - // max and min of channel, this is the range where actual data is drawn - int jfrom_wholechannel_clip= (int) (getY()+center-(channelHeight)*canvas->channelOverlapFactor)+1 ; - int jto_wholechannel_clip = (int) (getY()+center+(channelHeight)*canvas->channelOverlapFactor) -0; - - if (jfrom_wholechannel<0) {jfrom_wholechannel=0;}; - if (jto_wholechannel >= display->lfpChannelBitmap.getHeight()) {jto_wholechannel=display->lfpChannelBitmap.getHeight()-1;}; - - // draw most recent drawn sample position - if (canvas->screenBufferIndex[chan]+1 <= display->lfpChannelBitmap.getWidth()) - for (int k=jfrom_wholechannel; k<=jto_wholechannel; k+=2) // draw line - bdLfpChannelBitmap.setPixelColour(canvas->screenBufferIndex[chan]+1,k, Colours::yellow); - - - bool clipWarningHi =false; // keep track if something clipped in the display, so we can draw warnings after the data pixels are done - bool clipWarningLo =false; - - bool saturateWarningHi =false; // similar, but for saturating the amplifier, not just the display - make this warning very visible - bool saturateWarningLo =false; - - // pre compute some colors for later so we dont do it once per pixel. - Colour lineColourBright = lineColour.withMultipliedBrightness(2.0f); - //Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f).withMultipliedBrightness(0.3f); - Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f*canvas->histogramParameterB).withMultipliedBrightness(canvas->histogramParameterB); - - - int stepSize = 1; - int from = 0; // for vertical line drawing in the LFP data - int to = 0; - - int ifrom = canvas->lastScreenBufferIndex[chan] - 1; // need to start drawing a bit before the actual redraw window for the interpolated line to join correctly - - if (ifrom < 0) - ifrom = 0; - - int ito = canvas->screenBufferIndex[chan] +0; - - if (fullredraw) - { - ifrom = 0; //canvas->leftmargin; - ito = getWidth()-stepSize; - fullredraw = false; - } - - bool drawWithOffsetCorrection = display->getMedianOffsetPlotting(); - - LfpBitmapPlotterInfo plotterInfo; // hold and pass plotting info for each plotting method class - - - for (int i = ifrom; i < ito ; i += stepSize) // redraw only changed portion - { - if (i < display->lfpChannelBitmap.getWidth()) - { - //draw zero line - int m = getY()+center; - - if(m > 0 && m < display->lfpChannelBitmap.getHeight()) - { - if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) { // make sure we're not drawing over an existing plot from another channel - bdLfpChannelBitmap.setPixelColour(i,m,Colour(50,50,50)); - } - } - - //draw range markers - if (isSelected) - { - int start = getY()+center -channelHeight/2; - int jump = channelHeight/4; - - for (m = start; m <= start + jump*4; m += jump) - { - if (m > 0 && m < display->lfpChannelBitmap.getHeight()) - { - if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) // make sure we're not drawing over an existing plot from another channel - bdLfpChannelBitmap.setPixelColour(i, m, Colour(80,80,80)); - } - } - } - - // draw event markers - int rawEventState = canvas->getYCoord(canvas->getNumChannels(), i);// get last channel+1 in buffer (represents events) - - for (int ev_ch = 0; ev_ch < 8 ; ev_ch++) // for all event channels - { - if (display->getEventDisplayState(ev_ch)) // check if plotting for this channel is enabled - { - if (rawEventState & (1 << ev_ch)) // events are representet by a bit code, so we have to extract the individual bits with a mask - { -// std::cout << "Drawing event." << std::endl; - Colour currentcolor=display->channelColours[ev_ch*2]; - - for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++) // draw line - bdLfpChannelBitmap.setPixelColour(i,k,bdLfpChannelBitmap.getPixelColour(i,k).interpolatedWith(currentcolor,0.3f)); - - } - } - } - - //std::cout << "e " << canvas->getYCoord(canvas->getNumChannels()-1, i) << std::endl; - - - // set max-min range for plotting, used in all methods - double a = (canvas->getYCoordMax(chan, i)/range*channelHeightFloat); - double b = (canvas->getYCoordMin(chan, i)/range*channelHeightFloat); - - double mean = (canvas->getMean(chan)/range*channelHeightFloat); - - if (drawWithOffsetCorrection) - { - a -= mean; - b -= mean; - } - - double a_raw = canvas->getYCoordMax(chan, i); - double b_raw = canvas->getYCoordMin(chan, i); - double from_raw=0; double to_raw=0; - - //double m = (canvas->getYCoordMean(chan, i)/range*channelHeightFloat)+getHeight()/2; - if (achannelOverlapFactor; - if (lm>0) - lm=-lm; - - if (from > -lm) {from = -lm; clipWarningHi=true;}; - if (to > -lm) {to = -lm; clipWarningHi=true;}; - if (from < lm) {from = lm; clipWarningLo=true;}; - if (to < lm) {to = lm; clipWarningLo=true;}; - - - // test if raw data is clipped for displaying saturation warning - if (from_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;}; - if (to_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;}; - if (from_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;}; - if (to_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;}; - - bool spikeFlag = display->getSpikeRasterPlotting() - && !(saturateWarningHi || saturateWarningLo) - && (from_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold() - || to_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold()); - - from = from + getHeight()/2; // so the plot is centered in the channeldisplay - to = to + getHeight()/2; - - int samplerange = to - from; - - if (drawMethod) // switched between 'supersampled' drawing and simple pixel wise drawing - { // histogram based supersampling method - plotterInfo.channelID = chan; - plotterInfo.samp = i; - plotterInfo.y = getY(); - plotterInfo.from = from; - plotterInfo.height = getHeight(); - plotterInfo.lineColourBright = lineColourBright; - plotterInfo.lineColourDark = lineColourDark; - plotterInfo.range = range; - plotterInfo.channelHeightFloat = channelHeightFloat; - plotterInfo.sampleCountPerPixel = canvas->getSampleCountPerPixel(i); - plotterInfo.samplesPerPixel = canvas->getSamplesPerPixel(chan, i); - plotterInfo.histogramParameterA = canvas->histogramParameterA; - plotterInfo.samplerange = samplerange; - - // TODO: (kelly) complete transition toward plotter class encapsulation -// display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); - - } - else //drawmethod - { // simple per-pixel min-max drawing, has no anti-aliasing, but runs faster - - plotterInfo.channelID = chan; - plotterInfo.y = getY(); - plotterInfo.from = from; - plotterInfo.to = to; - plotterInfo.samp = i; - plotterInfo.lineColour = lineColour; - - // TODO: (kelly) complete transition toward plotter class encapsulation -// display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); // plotterInfo is prepared above - - } - - // Do the actual plotting for the selected plotting method - if (!display->getSpikeRasterPlotting()) - display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); - - - - - // now draw warnings, if needed - if (canvas->drawClipWarning) // draw simple warning if display cuts off data - { - - if(clipWarningHi) { - for (int j=0; j<=3; j++) - { - int clipmarker = jto_wholechannel_clip; - - if(clipmarker>0 && clipmarkerlfpChannelBitmap.getHeight()){ - bdLfpChannelBitmap.setPixelColour(i,clipmarker-j,Colour(255,255,255)); - } - } - } - - if(clipWarningLo) { - for (int j=0; j<=3; j++) - { - int clipmarker = jfrom_wholechannel_clip; - - if(clipmarker>0 && clipmarkerlfpChannelBitmap.getHeight()){ - bdLfpChannelBitmap.setPixelColour(i,clipmarker+j,Colour(255,255,255)); - } - } - } - - clipWarningHi=false; - clipWarningLo=false; - } - - if (spikeFlag) // draw spikes - { - for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line - if(k>0 && klfpChannelBitmap.getHeight()){ - bdLfpChannelBitmap.setPixelColour(i,k,lineColour); - } - }; - } - - - if (canvas->drawSaturationWarning) // draw bigger warning if actual data gets cuts off - { - - if(saturateWarningHi || saturateWarningLo) { - - - for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line - Colour thiscolour=Colour(255,0,0); - if (fmod((i+k),50)>25){ - thiscolour=Colour(255,255,255); - } - if(k>0 && klfpChannelBitmap.getHeight()){ - bdLfpChannelBitmap.setPixelColour(i,k,thiscolour); - } - }; - } - - saturateWarningHi=false; // we likely just need one of this because for this warning we dont care if its saturating on the positive or negative side - saturateWarningLo=false; - } - } // if i < getWidth() - - } // for i (x pixels) - -} - -void LfpChannelDisplay::paint(Graphics& g) {} - - - -PopupMenu LfpChannelDisplay::getOptions() -{ - - PopupMenu menu; - menu.addItem(1, "Invert signal", true, inputInverted); - - return menu; -} - -void LfpChannelDisplay::changeParameter(int id) -{ - switch (id) - { - case 1: - setInputInverted(!inputInverted); - default: - break; - } -} - -void LfpChannelDisplay::setRange(float r) -{ - - range = r; - - //std::cout << "Range: " << r << std::endl; -} - -int LfpChannelDisplay::getRange() -{ - return range; -} - - -void LfpChannelDisplay::select() -{ - isSelected = true; -} - -void LfpChannelDisplay::deselect() -{ - isSelected = false; -} - -bool LfpChannelDisplay::getSelected() -{ - return isSelected; -} - -void LfpChannelDisplay::setColour(Colour c) -{ - lineColour = c; -} - - -void LfpChannelDisplay::setChannelHeight(int c) -{ - channelHeight = c; - - channelHeightFloat = (float) channelHeight; - - if (!inputInverted) - channelHeightFloat = -channelHeightFloat; - - channelOverlap = channelHeight*2; -} - -int LfpChannelDisplay::getChannelHeight() -{ - - return channelHeight; -} - -void LfpChannelDisplay::setChannelOverlap(int overlap) -{ - channelOverlap = overlap; -} - - -int LfpChannelDisplay::getChannelOverlap() -{ - return channelOverlap; -} - -int LfpChannelDisplay::getChannelNumber() -{ - return chan; -} - -String LfpChannelDisplay::getName() -{ - return name; -} - -int LfpChannelDisplay::getDrawableChannelNumber() -{ - return drawableChan; -} - -void LfpChannelDisplay::setDrawableChannelNumber(int channelId) -{ - drawableChan = channelId; -} - -void LfpChannelDisplay::setCanBeInverted(bool _canBeInverted) -{ - canBeInverted = _canBeInverted; -} - -void LfpChannelDisplay::setInputInverted(bool isInverted) -{ - if (canBeInverted) - { - inputInverted = isInverted; - setChannelHeight(channelHeight); - } -} - -void LfpChannelDisplay::setDrawMethod(bool isDrawMethod) -{ - - drawMethod = isDrawMethod; - -} - - -void LfpChannelDisplay::setName(String name_) -{ - name = name_; -} - -DataChannel::DataChannelTypes LfpChannelDisplay::getType() -{ - return type; -} - - - -#pragma mark - LfpChannelDisplayInfo - -// ------------------------------- - -LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDisplay* display_, LfpDisplayOptions* options_, int ch) - : LfpChannelDisplay(canvas_, display_, options_, ch) -{ - - chan = ch; - x = -1.0f; - y = -1.0f; - -// enableButton = new UtilityButton(String(ch+1), Font("Small Text", 13, Font::plain)); - enableButton = new UtilityButton("", Font("Small Text", 13, Font::plain)); - enableButton->setRadius(5.0f); - - enableButton->setEnabledState(true); - enableButton->setCorners(true, true, true, true); - enableButton->addListener(this); - enableButton->setClickingTogglesState(true); - enableButton->setToggleState(true, dontSendNotification); - - isSingleChannel = false; - - addAndMakeVisible(enableButton); - -} - -void LfpChannelDisplayInfo::updateType() -{ - type = options->getChannelType(chan); - typeStr = options->getTypeName(type); - repaint(); -} - -void LfpChannelDisplayInfo::buttonClicked(Button* button) -{ - - bool state = button->getToggleState(); - - display->setEnabledState(state, chan); - - //UtilityButton* b = (UtilityButton*) button; - - // if (state) - // { - // b->setLabel("ON"); - // } else { - // b->setLabel("OFF"); - // } - - //std::cout << "Turn channel " << chan << " to " << button->getToggleState() << std::endl; - -} - -void LfpChannelDisplayInfo::setEnabledState(bool state) -{ - enableButton->setToggleState(state, sendNotification); -} - -void LfpChannelDisplayInfo::setSingleChannelState(bool state) -{ - isSingleChannel = state; -} - -int LfpChannelDisplayInfo::getChannelSampleRate() -{ - return samplerate; -} - -void LfpChannelDisplayInfo::setChannelSampleRate(int samplerate_) -{ - samplerate = samplerate_; -} - -void LfpChannelDisplayInfo::mouseDrag(const MouseEvent &e) -{ - if (e.mods.isLeftButtonDown()) // double check that we initiate only for left click and hold - { - if (e.mods.isCommandDown() && !display->getSingleChannelState()) // CTRL + drag -> change channel spacing - { - - // init state in our track zooming info struct - if (!display->trackZoomInfo.isScrollingY) - { - auto & zoomInfo = display->trackZoomInfo; - - zoomInfo.isScrollingY = true; - zoomInfo.componentStartHeight = getChannelHeight(); - zoomInfo.zoomPivotRatioY = (getY() + e.getMouseDownY())/(float)display->getHeight(); - zoomInfo.zoomPivotRatioX = (getX() + e.getMouseDownX())/(float)display->getWidth(); - zoomInfo.zoomPivotViewportOffset = getPosition() + e.getMouseDownPosition() - canvas->viewport->getViewPosition(); - - zoomInfo.unpauseOnScrollEnd = !display->isPaused; - if (!display->isPaused) display->options->togglePauseButton(true); - } - - int h = display->trackZoomInfo.componentStartHeight; - int hdiff=0; - int dragDeltaY = -0.1 * (e.getScreenPosition().getY() - e.getMouseDownScreenY()); // invert so drag up -> scale up - -// std::cout << dragDeltaY << std::endl; - if (dragDeltaY > 0) - { - hdiff = 2 * dragDeltaY; - } - else - { - if (h > 5) - hdiff = 2 * dragDeltaY; - } - - if (abs(h) > 100) // accelerate scrolling for large ranges - hdiff *= 3; - - int newHeight = h+hdiff; - - // constrain the spread resizing to max and min values; - if (newHeight < display->trackZoomInfo.minZoomHeight) - { - newHeight = display->trackZoomInfo.minZoomHeight; - } - else if (newHeight > display->trackZoomInfo.maxZoomHeight) - { - newHeight = display->trackZoomInfo.maxZoomHeight; - } - - // return early if there is nothing to update - if (newHeight == getChannelHeight()) - { - return; - } - - // set channel heights for all channel -// display->setChannelHeight(newHeight); - for (int i = 0; i < display->getNumChannels(); ++i) - { - display->channels[i]->setChannelHeight(newHeight); - display->channelInfo[i]->setChannelHeight(newHeight); - } - - options->setSpreadSelection(newHeight, false, true); // update combobox - - canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw - - display->setBounds(0,0,display->getWidth()-0, display->getChannelHeight()*display->drawableChannels.size()); // update height so that the scrollbar is correct - - int newViewportY = display->trackZoomInfo.zoomPivotRatioY * display->getHeight() - display->trackZoomInfo.zoomPivotViewportOffset.getY(); - if (newViewportY < 0) newViewportY = 0; // make sure we don't adjust beyond the edge of the actual view - - canvas->viewport->setViewPosition(0, newViewportY); - } - } -} - -void LfpChannelDisplayInfo::mouseUp(const MouseEvent &e) -{ - if (e.mods.isLeftButtonDown() && display->trackZoomInfo.isScrollingY) - { - display->trackZoomInfo.isScrollingY = false; - if (display->trackZoomInfo.unpauseOnScrollEnd) - { - display->isPaused = false; - display->options->togglePauseButton(false); - } - } -} - -void LfpChannelDisplayInfo::paint(Graphics& g) -{ - - int center = getHeight()/2 - (isSingleChannel?(75):(0)); - const bool showChannelNumbers = options->getChannelNameState(); - - // Draw the channel numbers - g.setColour(Colours::grey); - const String channelString = (isChannelNumberHidden() ? ("--") : - showChannelNumbers ? String(getChannelNumber() + 1) : getName()); - bool isCentered = !getEnabledButtonVisibility(); - - g.drawText(channelString, - showChannelNumbers ? 6 : 2, - center-4, - getWidth()/2, - 10, - isCentered ? Justification::centred : Justification::centredLeft, - false); - - g.setColour(lineColour); - g.fillRect(0, 0, 2, getHeight()); - - if (getChannelTypeStringVisibility()) - { - g.setFont(Font("Small Text", 13, Font::plain)); - g.drawText(typeStr,5,center+10,41,10,Justification::centred,false); - } - // g.setFont(channelHeightFloat*0.3); - g.setFont(Font("Small Text", 11, Font::plain)); - - if (isSingleChannel) - { - g.setColour(Colours::darkgrey); - g.drawText("STD:", 5, center+90,41,10,Justification::centred,false); - g.drawText("MEAN:", 5, center+40,41,10,Justification::centred,false); - - if (x > 0) - { - g.drawText("uV:", 5, center+140,41,10,Justification::centred,false); - } - //g.drawText("Y:", 5, center+200,41,10,Justification::centred,false); - - g.setColour(Colours::grey); - g.drawText(String(canvas->getStd(chan)), 5, center+110,41,10,Justification::centred,false); - g.drawText(String(canvas->getMean(chan)), 5, center+60,41,10,Justification::centred,false); - if (x > 0) - { - //g.drawText(String(x), 5, center+150,41,10,Justification::centred,false); - g.drawText(String(y), 5, center+160,41,10,Justification::centred,false); - } - - } - - // g.drawText(name, 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); - -} - -void LfpChannelDisplayInfo::updateXY(float x_, float y_) -{ - x = x_; - y = y_; -} - -void LfpChannelDisplayInfo::resized() -{ - - int center = getHeight()/2 - (isSingleChannel?(75):(0)); - setEnabledButtonVisibility(getHeight() >= 16); - - if (getEnabledButtonVisibility()) - { - enableButton->setBounds(getWidth()/2 - 10, center - 5, 10, 10); - } - - setChannelNumberIsHidden(getHeight() < 16 && (getDrawableChannelNumber() + 1) % 10 != 0); - - setChannelTypeStringVisibility(getHeight() > 34); -} - -void LfpChannelDisplayInfo::setEnabledButtonVisibility(bool shouldBeVisible) -{ - if (shouldBeVisible) - { - addAndMakeVisible(enableButton); - } - else if (enableButton->isVisible()) - { - removeChildComponent(enableButton); - enableButton->setVisible(false); - } - -} - -bool LfpChannelDisplayInfo::getEnabledButtonVisibility() -{ - return enableButton->isVisible(); -} - -void LfpChannelDisplayInfo::setChannelTypeStringVisibility(bool shouldBeVisible) -{ - channelTypeStringIsVisible = shouldBeVisible; -} - -bool LfpChannelDisplayInfo::getChannelTypeStringVisibility() -{ - return channelTypeStringIsVisible || isSingleChannel; -} - -void LfpChannelDisplayInfo::setChannelNumberIsHidden(bool shouldBeHidden) -{ - channelNumberHidden = shouldBeHidden; -} - -bool LfpChannelDisplayInfo::isChannelNumberHidden() -{ - return channelNumberHidden; -} - - - - -#pragma mark - EventDisplayInterface - -// Event display Options -------------------------------------------------------------------- - -EventDisplayInterface::EventDisplayInterface(LfpDisplay* display_, LfpDisplayCanvas* canvas_, int chNum): - isEnabled(true), display(display_), canvas(canvas_) -{ - - channelNumber = chNum; - - chButton = new UtilityButton(String(channelNumber+1), Font("Small Text", 13, Font::plain)); - chButton->setRadius(5.0f); - chButton->setBounds(4,4,14,14); - chButton->setEnabledState(true); - chButton->setCorners(true, false, true, false); - //chButton.color = display->channelColours[channelNumber*2]; - chButton->addListener(this); - addAndMakeVisible(chButton); - - - checkEnabledState(); - -} - -EventDisplayInterface::~EventDisplayInterface() -{ - -} - -void EventDisplayInterface::checkEnabledState() -{ - isEnabled = display->getEventDisplayState(channelNumber); - - //repaint(); -} - -void EventDisplayInterface::buttonClicked(Button* button) -{ - checkEnabledState(); - if (isEnabled) - { - display->setEventDisplayState(channelNumber, false); - } - else - { - display->setEventDisplayState(channelNumber, true); - } - - repaint(); - -} - - -void EventDisplayInterface::paint(Graphics& g) -{ - - checkEnabledState(); - - if (isEnabled) - { - g.setColour(display->channelColours[channelNumber*2]); - g.fillRoundedRectangle(2,2,18,18,6.0f); - } - - - //g.drawText(String(channelNumber), 8, 2, 200, 15, Justification::left, false); - -} - - - -#pragma mark - LfpViewport - -// Lfp Viewport ------------------------------------------- - -LfpViewport::LfpViewport(LfpDisplayCanvas *canvas) - : Viewport() -{ - this->canvas = canvas; -} - -void LfpViewport::visibleAreaChanged(const Rectangle& newVisibleArea) -{ - canvas->fullredraw = true; - canvas->refresh(); -} - - - -#pragma mark - PerPixelBitmapPlotter - - -PerPixelBitmapPlotter::PerPixelBitmapPlotter(LfpDisplay * lfpDisplay) - : LfpBitmapPlotter(lfpDisplay) -{ } - -void PerPixelBitmapPlotter::plot(Image::BitmapData &bitmapData, LfpBitmapPlotterInfo &pInfo) -{ - int jfrom = pInfo.from + pInfo.y; - int jto = pInfo.to + pInfo.y; - - //if (yofs<0) {yofs=0;}; - - if (pInfo.samp < 0) {pInfo.samp = 0;}; - if (pInfo.samp >= display->lfpChannelBitmap.getWidth()) {pInfo.samp = display->lfpChannelBitmap.getWidth()-1;}; // this shouldnt happen, there must be some bug above - to replicate, run at max refresh rate where draws overlap the right margin by a lot - - if (jfrom<0) {jfrom=0;}; - if (jto >= display->lfpChannelBitmap.getHeight()) {jto=display->lfpChannelBitmap.getHeight()-1;}; - - - for (int j = jfrom; j <= jto; j += 1) - { - - //uint8* const pu8Pixel = bdSharedLfpDisplay.getPixelPointer( (int)(i),(int)(j)); - //*(pu8Pixel) = 200; - //*(pu8Pixel+1) = 200; - //*(pu8Pixel+2) = 200; - - bitmapData.setPixelColour(pInfo.samp,j,pInfo.lineColour); - - } -} - - - -#pragma mark - LfpSupersampledBitmapPlotter - - -SupersampledBitmapPlotter::SupersampledBitmapPlotter(LfpDisplay * lfpDisplay) - : LfpBitmapPlotter(lfpDisplay) -{ } - -void SupersampledBitmapPlotter::plot(Image::BitmapData &bdLfpChannelBitmap, LfpBitmapPlotterInfo &pInfo) -{ - std::array samplesThisPixel = pInfo.samplesPerPixel; -// int sampleCountThisPixel = lfpDisplay->canvas->getSampleCountPerPixel(pInfo.samp); - int sampleCountThisPixel = pInfo.sampleCountPerPixel; - - if (pInfo.samplerange>0 && sampleCountThisPixel>0) - { - - //float localHist[samplerange]; // simple histogram - Array rangeHist; // [samplerange]; // paired range histogram, same as plotting at higher res. and subsampling - - for (int k = 0; k <= pInfo.samplerange; k++) - rangeHist.add(0); - - for (int k = 0; k <= sampleCountThisPixel; k++) // add up paired-range histogram per pixel - for each pair fill intermediate with uniform distr. - { - int cs_this = (((samplesThisPixel[k]/pInfo.range*pInfo.channelHeightFloat)+pInfo.height/2)-pInfo.from); // sample values -> pixel coordinates relative to from - int cs_next = (((samplesThisPixel[k+1]/pInfo.range*pInfo.channelHeightFloat)+pInfo.height/2)-pInfo.from); - - - if (cs_this<0) {cs_this=0;}; //here we could clip the diaplay to the max/min, or ignore out of bound values, not sure which one is better - if (cs_this>pInfo.samplerange) {cs_this=pInfo.samplerange;}; - if (cs_next<0) {cs_next=0;}; - if (cs_next>pInfo.samplerange) {cs_next=pInfo.samplerange;}; - - int hfrom=0; - int hto=0; - - if (cs_this1.0f) {a=1.0f;}; - if (a<0.0f) {a=0.0f;}; - - - //Colour gradedColor = lineColour.withMultipliedBrightness(2.0f).interpolatedWith(lineColour.withMultipliedSaturation(0.6f).withMultipliedBrightness(0.3f),1-a) ; - Colour gradedColor = pInfo.lineColourBright.interpolatedWith(pInfo.lineColourDark,1-a); - //Colour gradedColor = Colour(0,255,0); - - int ploty = pInfo.from + s + pInfo.y; - if(ploty>0 && ploty < display->lfpChannelBitmap.getHeight()) { - bdLfpChannelBitmap.setPixelColour(pInfo.samp, pInfo.from + s + pInfo.y, gradedColor); - } - } - - } else { - - int ploty = pInfo.from + pInfo.y; - if(ploty>0 && ploty < display->lfpChannelBitmap.getHeight()) { - bdLfpChannelBitmap.setPixelColour(pInfo.samp, ploty, pInfo.lineColour); - } - } -} - - - -#pragma mark - LfpChannelColourScheme - - -int LfpChannelColourScheme::colourGrouping = 1; - -void LfpChannelColourScheme::setColourGrouping(int grouping) -{ - colourGrouping = grouping; -} - -int LfpChannelColourScheme::getColourGrouping() -{ - return colourGrouping; -} - - - -#pragma mark - LfpDefaultColourScheme - - -Array LfpDefaultColourScheme::colourList = []() -> Array { - Array colours; - colours.add(Colour(224,185,36)); - colours.add(Colour(214,210,182)); - colours.add(Colour(243,119,33)); - colours.add(Colour(186,157,168)); - colours.add(Colour(237,37,36)); - colours.add(Colour(179,122,79)); - colours.add(Colour(217,46,171)); - colours.add(Colour(217, 139,196)); - colours.add(Colour(101,31,255)); - colours.add(Colour(141,111,181)); - colours.add(Colour(48,117,255)); - colours.add(Colour(184,198,224)); - colours.add(Colour(116,227,156)); - colours.add(Colour(150,158,155)); - colours.add(Colour(82,173,0)); - colours.add(Colour(125,99,32)); - return colours; -}(); - -LfpDefaultColourScheme::LfpDefaultColourScheme(LfpDisplay* display, LfpDisplayCanvas* canvas) - : LfpViewer::LfpChannelColourScheme(LfpDefaultColourScheme::colourList.size(), display, canvas) -{ - setName("Default"); -} - -void LfpDefaultColourScheme::paint(Graphics &g) -{ - -} - -void LfpDefaultColourScheme::resized() -{ - -} - -const Colour LfpDefaultColourScheme::getColourForIndex(int index) const -{ -// return colourList[index % colourList.size()]; - return colourList[(int(index/colourGrouping)) % colourList.size()]; -} - - - -#pragma mark - LfpMonochromaticColorScheme - -LfpMonochromaticColourScheme::LfpMonochromaticColourScheme(LfpDisplay* display, LfpDisplayCanvas* canvas) - : LfpChannelColourScheme (8, display, canvas) - , isBlackAndWhite (false) - , colourPattern (DOWN_UP) -{ - setName("Monochromatic"); - - numChannelsLabel = new Label("numChannelsLabel", "Num Color Steps"); - numChannelsLabel->setFont(Font("Default", 13.0f, Font::plain)); - numChannelsLabel->setColour(Label::textColourId, Colour(100, 100, 100)); - addAndMakeVisible(numChannelsLabel); - - StringArray numChannelsSelectionOptions = {"4", "8", "16"}; - numChannelsSelection = new ComboBox("numChannelsSelection"); - numChannelsSelection->addItemList(numChannelsSelectionOptions, 1); - numChannelsSelection->setEditableText(true); - numChannelsSelection->addListener(this); - numChannelsSelection->setSelectedId(2, dontSendNotification); - addAndMakeVisible(numChannelsSelection); - - - baseHueLabel = new Label("baseHue", "Hue"); - baseHueLabel->setFont(Font("Default", 13.0f, Font::plain)); - baseHueLabel->setColour(Label::textColourId, Colour(100, 100, 100)); - addAndMakeVisible(baseHueLabel); - - baseHueSlider = new Slider; - baseHueSlider->setRange(0, 1); - baseHueSlider->setValue(0); - baseHueSlider->setTextBoxStyle(Slider::NoTextBox, false, 0, 0); - baseHueSlider->addListener(this); - addAndMakeVisible(baseHueSlider); - - baseHueSlider->addMouseListener(this, true); - - - colourPatternLabel = new Label("colourPatternLabel", "Pattern"); - colourPatternLabel->setFont(Font("Default", 13.0f, Font::plain)); - colourPatternLabel->setColour(Label::textColourId, Colour(100, 100, 100)); - addAndMakeVisible(colourPatternLabel); - - StringArray colourPatternSelectionOptions = {"Down", "Up", "Down-Up", "Up-Down"}; - colourPatternSelection = new ComboBox("colourPatternSelection"); - colourPatternSelection->addItemList(colourPatternSelectionOptions, 1); - colourPatternSelection->setEditableText(false); - colourPatternSelection->addListener(this); - colourPatternSelection->setSelectedId(colourPattern + 1, dontSendNotification); - addAndMakeVisible(colourPatternSelection); - - baseHue = Colour::fromHSV(0, 1, 1, 1); - swatchHue = baseHue; - - calculateColourSeriesFromBaseHue(); -} - -void LfpMonochromaticColourScheme::paint(Graphics &g) -{ - g.setColour(swatchHue); - g.fillRect(colourSwatchRect); -} - -void LfpMonochromaticColourScheme::resized() -{ - numChannelsLabel->setBounds(0, 5, 120, 25); - numChannelsSelection->setBounds(numChannelsLabel->getRight(), - numChannelsLabel->getY(), - 60, - 25); - - baseHueLabel->setBounds(0, numChannelsLabel->getBottom(), 35, 25); - baseHueSlider->setBounds(baseHueLabel->getRight(), - baseHueLabel->getY(), - numChannelsSelection->getRight() - baseHueLabel->getRight() - 20, - 25); - - colourSwatchRect.setBounds(baseHueSlider->getRight() + 5, baseHueSlider->getY() + 5, 15, baseHueSlider->getHeight() - 10); - - colourPatternLabel->setBounds(0, baseHueLabel->getBottom(), 80, 25); - colourPatternSelection->setBounds(colourPatternLabel->getRight(), - colourPatternLabel->getY(), - numChannelsSelection->getRight() - colourPatternLabel->getRight(), - 25); - -} - -void LfpMonochromaticColourScheme::sliderValueChanged(Slider *sl) -{ - swatchHue = Colour::fromHSV(sl->getValue(), 1, 1, 1); - repaint(colourSwatchRect); -} - -void LfpMonochromaticColourScheme::comboBoxChanged(ComboBox *cb) -{ - if (cb == numChannelsSelection) - { - int numChannelsColourSpread = 0; - if (cb->getSelectedId()) - { - numChannelsColourSpread = cb->getText().getIntValue(); - } - else - { - numChannelsColourSpread = cb->getText().getIntValue(); - if (numChannelsColourSpread < 1) numChannelsColourSpread = 1; - else if (numChannelsColourSpread > 16) numChannelsColourSpread = 16; - - cb->setText(String(numChannelsColourSpread), dontSendNotification); - } - - setNumColourSeriesSteps(numChannelsColourSpread); - } - else if (cb == colourPatternSelection) - { - setColourPattern((ColourPattern)(cb->getSelectedId() - 1)); - } - calculateColourSeriesFromBaseHue(); - lfpDisplay->setColors(); -// canvas->fullredraw = true; - canvas->redraw(); -} - -void LfpMonochromaticColourScheme::mouseUp(const MouseEvent &e) -{ - if (swatchHue.getARGB() != baseHue.getARGB()) - { - baseHue = swatchHue; - calculateColourSeriesFromBaseHue(); - lfpDisplay->setColors(); - canvas->redraw(); - } -} - -void LfpMonochromaticColourScheme::setBaseHue(Colour base) -{ - baseHue = base; - calculateColourSeriesFromBaseHue(); -} - -const Colour LfpMonochromaticColourScheme::getBaseHue() const -{ - return baseHue; -} - -void LfpMonochromaticColourScheme::setNumColourSeriesSteps(int numSteps) -{ - numColourChannels = numSteps; -} - -int LfpMonochromaticColourScheme::getNumColourSeriesSteps() -{ - return numColourChannels; -} - -const Colour LfpMonochromaticColourScheme::getColourForIndex(int index) const -{ - int colourIdx = (int(index/colourGrouping) % numColourChannels); - - // adjust for oscillating patterns - if (colourPattern == DOWN_UP || colourPattern == UP_DOWN) - { - int mid = numColourChannels / 2; - if (colourIdx > mid) - { - if (numColourChannels % 2 == 0) - colourIdx = numColourChannels - colourIdx; - else - colourIdx = (numColourChannels - colourIdx) * 2 - 1; - } - else if (numColourChannels % 2 != 0) - { - colourIdx *= 2; - } - } - - // invert if the pattern is UP or UP_DOWN - if (colourPattern == UP) - colourIdx = (numColourChannels - 1) - colourIdx; - else if (colourPattern == UP_DOWN) - colourIdx = (colourList.size() - 1) - colourIdx; - - return colourList[colourIdx]; -} - -void LfpMonochromaticColourScheme::calculateColourSeriesFromBaseHue() -{ - colourList.clear(); - - int coloursToCalculate = numColourChannels; - - if (numColourChannels % 2 == 0 && (colourPattern == DOWN_UP || colourPattern == UP_DOWN)) - { - coloursToCalculate = coloursToCalculate / 2 + 1; - } - - for (int i = 0; i < coloursToCalculate; ++i) - { - float saturation = 1 - (i / float(coloursToCalculate + 1)); - colourList.add(baseHue.withMultipliedSaturation(saturation)); - } -} - - - -#pragma mark - LfpGradientColourScheme - -LfpGradientColourScheme::LfpGradientColourScheme(LfpDisplay * display, LfpDisplayCanvas * canvas) - : LfpMonochromaticColourScheme(display, canvas) -{ - setName("Gradient"); - - baseHueLabel->setName("baseHueA"); - baseHueLabel->setText("Hue A", dontSendNotification); - - baseHueLabelB = new Label("baseHueB", "Hue B"); - baseHueLabelB->setFont(Font("Default", 13.0f, Font::plain)); - baseHueLabelB->setColour(Label::textColourId, Colour(100, 100, 100)); - addAndMakeVisible(baseHueLabelB); - - baseHueSliderB = new Slider; - baseHueSliderB->setRange(0, 1); - baseHueSliderB->setValue(0.5); - baseHueSliderB->setTextBoxStyle(Slider::NoTextBox, false, 0, 0); - baseHueSliderB->addListener(this); - addAndMakeVisible(baseHueSliderB); - - baseHueSliderB->addMouseListener(this, true); - - baseHueB = Colour::fromHSV(0.5, 1.0, 1.0, 1.0); - swatchHueB = baseHueB; - - calculateColourSeriesFromBaseHue(); -} - -void LfpGradientColourScheme::paint(Graphics &g) -{ - g.setColour(swatchHue); - g.fillRect(colourSwatchRect); - - g.setColour(swatchHueB); - g.fillRect(colourSwatchRectB); -} - -void LfpGradientColourScheme::resized() -{ - numChannelsLabel->setBounds(0, 5, 120, 25); - numChannelsSelection->setBounds(numChannelsLabel->getRight(), - numChannelsLabel->getY(), - 60, - 25); - - baseHueLabel->setBounds(0, numChannelsLabel->getBottom(), 35, 25); - baseHueSlider->setBounds(baseHueLabel->getRight(), - baseHueLabel->getY(), - numChannelsSelection->getRight() - baseHueLabel->getRight() - 20, - 25); - - colourSwatchRect.setBounds(baseHueSlider->getRight() + 5, baseHueSlider->getY() + 5, 15, baseHueSlider->getHeight() - 10); - - baseHueLabelB->setBounds(0, baseHueLabel->getBottom(), 35, 25); - baseHueSliderB->setBounds(baseHueLabelB->getRight(), - baseHueLabelB->getY(), - numChannelsSelection->getRight() - baseHueLabelB->getRight() - 20, - 25); - - colourSwatchRectB.setBounds(baseHueSliderB->getRight() + 5, baseHueSliderB->getY() + 5, 15, baseHueSliderB->getHeight() - 10); - - colourPatternLabel->setBounds(0, baseHueLabelB->getBottom(), 80, 25); - colourPatternSelection->setBounds(colourPatternLabel->getRight(), - colourPatternLabel->getY(), - numChannelsSelection->getRight() - colourPatternLabel->getRight(), - 25); -} - -void LfpGradientColourScheme::sliderValueChanged(Slider *sl) -{ - if (sl == baseHueSlider) - { - swatchHue = Colour::fromHSV(sl->getValue(), 1, 1, 1); - repaint(colourSwatchRect); - } - else - { - swatchHueB = Colour::fromHSV(sl->getValue(), 1, 1, 1); - repaint(colourSwatchRectB); - } -} - -void LfpGradientColourScheme::mouseUp(const MouseEvent &e) -{ - if (e.originalComponent == baseHueSlider) - { - if (swatchHue.getARGB() != baseHue.getARGB()) - { - baseHue = swatchHue; - calculateColourSeriesFromBaseHue(); - lfpDisplay->setColors(); - canvas->redraw(); - } - } - else - { - if (swatchHueB.getARGB() != baseHueB.getARGB()) - { - baseHueB = swatchHueB; - calculateColourSeriesFromBaseHue(); - lfpDisplay->setColors(); - canvas->redraw(); - } - } -} - -void LfpGradientColourScheme::calculateColourSeriesFromBaseHue() -{ - colourList.clear(); - - int coloursToCalculate = numColourChannels; - - if (numColourChannels % 2 == 0 && (colourPattern == DOWN_UP || colourPattern == UP_DOWN)) - { - coloursToCalculate = coloursToCalculate / 2 + 1; - } - - for (int i = 0; i < coloursToCalculate; ++i) - { - float hue = (baseHueB.getHue() - baseHue.getHue()) * i / float(coloursToCalculate - 1); - colourList.add(baseHue.withRotatedHue(hue)); - } -} diff --git a/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp.orig b/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp.orig new file mode 100644 index 0000000000..5298244e46 --- /dev/null +++ b/Plugins/LfpDisplayNode/LfpDisplayCanvas.cpp.orig @@ -0,0 +1,4482 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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 . + +*/ + +#include "LfpDisplayCanvas.h" + +#include + +using namespace LfpViewer; + + + +#pragma mark - LfpDisplayCanvas - + +LfpDisplayCanvas::LfpDisplayCanvas(LfpDisplayNode* processor_) : + timebase(1.0f), displayGain(1.0f), timeOffset(0.0f), + processor(processor_) +{ + + nChans = processor->getNumSubprocessorChannels(); + + displayBuffer = processor->getDisplayBufferAddress(); + displayBufferSize = displayBuffer->getNumSamples(); + + screenBuffer = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBuffer->clear(); + + screenBufferMin = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBufferMin->clear(); + screenBufferMean = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBufferMean->clear(); + screenBufferMax = new AudioSampleBuffer(MAX_N_CHAN, MAX_N_SAMP); + screenBufferMax->clear(); + + viewport = new LfpViewport(this); + lfpDisplay = new LfpDisplay(this, viewport); + timescale = new LfpTimescale(this, lfpDisplay); + options = new LfpDisplayOptions(this, timescale, lfpDisplay, processor); + + lfpDisplay->options = options; + + timescale->setTimebase(timebase); + + viewport->setViewedComponent(lfpDisplay, false); + viewport->setScrollBarsShown(true, false); + + scrollBarThickness = viewport->getScrollBarThickness(); + + isChannelEnabled.insertMultiple(0,true,10000); // max 10k channels + + //viewport->getVerticalScrollBar()->addListener(this->scrollBarMoved(viewport->getVerticalScrollBar(), 1.0)); + + addAndMakeVisible(viewport); + addAndMakeVisible(timescale); + addAndMakeVisible(options); + + lfpDisplay->setNumChannels(nChans); + + resizeSamplesPerPixelBuffer(nChans); + + TopLevelWindow::getTopLevelWindow(0)->addKeyListener(this); + + optionsDrawerIsOpen = false; + +} + +LfpDisplayCanvas::~LfpDisplayCanvas() +{ + + // de-allocate 3d-array samplesPerPixel [nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL]; + + //for(int i=0;iremoveKeyListener(this); +} + +void LfpDisplayCanvas::resizeSamplesPerPixelBuffer(int numCh) +{ + // allocate samplesPerPixel, behaves like float samplesPerPixel[nChans][MAX_N_SAMP][MAX_N_SAMP_PER_PIXEL] + //samplesPerPixel = (float***)malloc(nChans * sizeof(float **)); + + // 3D array: dimensions channels x samples x samples per pixel + samplesPerPixel.clear(); + samplesPerPixel.resize(numCh); + + //for(int i = 0; i < numCh; i++) + //{ + //std::vector< std::vector> v1; + // samplesPerPixel[i].resize(MAX_N_SAMP); + //samplesPerPixel.push_back(v1); + //samplesPerPixel[i] = (float**)malloc(MAX_N_SAMP * sizeof(float*)); + + // for(int j = 0; j < MAX_N_SAMP; j++) + // { + //std::vector v2; + //v2.resize(MAX_N_SAMP_PER_PIXEL); + // samplesPerPixel[i][j].resize(MAX_N_SAMP_PER_PIXEL); + // //samplesPerPixel[i][j] = (float*)malloc(MAX_N_SAMP_PER_PIXEL*sizeof(float)); + // } + //} +} + +void LfpDisplayCanvas::toggleOptionsDrawer(bool isOpen) +{ + optionsDrawerIsOpen = isOpen; + auto viewportPosition = viewport->getViewPositionY(); // remember viewport position + resized(); + viewport->setViewPosition(0, viewportPosition); // return viewport position +} + +void LfpDisplayCanvas::resized() +{ + + timescale->setBounds(leftmargin,0,getWidth()-scrollBarThickness-leftmargin,30); + viewport->setBounds(0,30,getWidth(),getHeight()-90); + + if (nChans > 0) + { + if (lfpDisplay->getSingleChannelState()) + lfpDisplay->setChannelHeight(viewport->getHeight(),false); + + lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, lfpDisplay->getChannelHeight()*lfpDisplay->drawableChannels.size()); + } + else + { + lfpDisplay->setBounds(0, 0, getWidth(), getHeight()); + } + + if (optionsDrawerIsOpen) + options->setBounds(0, getHeight()-200, getWidth(), 200); + else + options->setBounds(0, getHeight()-55, getWidth(), 55); + +} + +void LfpDisplayCanvas::resizeToChannels(bool respectViewportPosition) +{ + lfpDisplay->setBounds(0,0,getWidth()-scrollBarThickness, lfpDisplay->getChannelHeight()*lfpDisplay->drawableChannels.size()); + + // if param is flagged, move the viewport scroll back to same relative position before + // resize took place + if (!respectViewportPosition) return; + + // get viewport scroll position as ratio against lfpDisplay's dims + // so that we can set scrollbar back to rough position before resize + // (else viewport scrolls back to top after resize) + const double yPositionRatio = viewport->getViewPositionY() / (double)lfpDisplay->getHeight(); + const double xPositionRatio = viewport->getViewPositionX() / (double)lfpDisplay->getWidth(); + + viewport->setViewPosition(lfpDisplay->getWidth() * xPositionRatio, + lfpDisplay->getHeight() * yPositionRatio); +} + +void LfpDisplayCanvas::beginAnimation() +{ + + if (true) + { + + displayBufferSize = displayBuffer->getNumSamples(); + + for (int i = 0; i < screenBufferIndex.size(); i++) + { + screenBufferIndex.set(i, 0); + } + + startCallbacks(); + } +} + +void LfpDisplayCanvas::endAnimation() +{ + if (true) + { + + stopCallbacks(); + } +} + +void LfpDisplayCanvas::update() +{ + + displayBufferSize = displayBuffer->getNumSamples(); + + nChans = jmax(processor->getNumSubprocessorChannels(), 0); + + resizeSamplesPerPixelBuffer(nChans); + + sampleRate = 30000; // default + + for (auto* arr : { &screenBufferIndex, &lastScreenBufferIndex, &displayBufferIndex }) + { + arr->clearQuick(); + arr->insertMultiple(0, 0, nChans + 1); // extra channel for events + } + + options->setEnabled(nChans != 0); + // must manually ensure that overlapSelection propagates up to canvas + channelOverlapFactor = options->selectedOverlapValue.getFloatValue(); + + int firstChannelInSubprocessor = 0; + for (int i = 0, nInputs = processor->getNumInputs(); i < nInputs; i++) + { + + if (processor->getDataSubprocId(i) == drawableSubprocessor) + { + sampleRate = processor->getDataChannel(i)->getSampleRate(); + firstChannelInSubprocessor = i; + break; + } + + } + + if (nChans != lfpDisplay->getNumChannels()) + { + refreshScreenBuffer(); + + if (nChans > 0) + lfpDisplay->setNumChannels(nChans); + + // update channel names + //std::cout << "Updating channel names" << std::endl; + for (int i = 0; i < nChans; i++) + { + String chName = processor->getDataChannel(firstChannelInSubprocessor + i)->getName(); + lfpDisplay->channelInfo[i]->setName(chName); + lfpDisplay->setEnabledState(isChannelEnabled[i], i); + } + + if (nChans == 0) lfpDisplay->setBounds(0, 0, getWidth(), getHeight()); + else { + lfpDisplay->rebuildDrawableChannelsList(); + lfpDisplay->setBounds(0, 0, getWidth()-scrollBarThickness*2, lfpDisplay->getTotalHeight()); + } + + resized(); + } + else + { + for (int i = 0; i < nChans; i++) + { + String chName = processor->getDataChannel(firstChannelInSubprocessor + i)->getName(); + lfpDisplay->channelInfo[i]->setName(chName); + lfpDisplay->channels[i]->updateType(); + lfpDisplay->channelInfo[i]->updateType(); + } + + if (nChans > 0) + { + lfpDisplay->rebuildDrawableChannelsList(); + } + } +} + + + +int LfpDisplayCanvas::getChannelHeight() +{ + //return spreads[spreadSelection->getSelectedId()-1].getIntValue(); + return options->getChannelHeight(); + +} + + +void LfpDisplayCanvas::setParameter(int param, float val) +{ + // not used for anything, since LfpDisplayCanvas is not a processor +} + + +void LfpDisplayCanvas::refreshState() +{ + // called when the component's tab becomes visible again + + if (true) + { + for (int i = 0; i <= displayBufferIndex.size(); i++) // include event channel + { + + displayBufferIndex.set(i, processor->getDisplayBufferIndex(i)); + screenBufferIndex.set(i, 0); + } + } + +} + +void LfpDisplayCanvas::refreshScreenBuffer() +{ + if (true) + { + for (int i = 0; i < screenBufferIndex.size(); i++) + screenBufferIndex.set(i, 0); + + screenBuffer->clear(); + screenBufferMin->clear(); + screenBufferMean->clear(); + screenBufferMax->clear(); + } + +} + +void LfpDisplayCanvas::updateScreenBuffer() +{ + if (true) + { + // copy new samples from the displayBuffer into the screenBuffer + int maxSamples = lfpDisplay->getWidth() - leftmargin; + + ScopedLock displayLock(*processor->getMutex()); + + for (int channel = 0; channel <= nChans; channel++) // pull one extra channel for event display + { + + //if (channel == 0) + // std::cout << sampleRate[channel] << std::endl; + + if (screenBufferIndex[channel] >= maxSamples) // wrap around if we reached right edge before + screenBufferIndex.set(channel, 0); + + // hold these values locally for each channel - is this a good idea? + int sbi = screenBufferIndex[channel]; + int dbi = displayBufferIndex[channel]; + + + + lastScreenBufferIndex.set(channel, sbi); + + int index = processor->getDisplayBufferIndex(channel); + + int nSamples = index - dbi; // N new samples (not pixels) to be added to displayBufferIndex + + if (nSamples < 0) // buffer has reset to 0 -- xxx 2do bug: this shouldnt happen because it makes the range/histogram display not work properly/look off for one pixel + { + nSamples = (displayBufferSize - dbi) + index + 1; + // std::cout << "nsamples 0 " ; + } + + //if (channel == 15 || channel == 16) + // std::cout << channel << " " << sbi << " " << dbi << " " << nSamples << std::endl; + + + float ratio = sampleRate * timebase / float(getWidth() - leftmargin - scrollBarThickness); // samples / pixel + // this number is crucial: converting from samples to values (in px) for the screen buffer + int valuesNeeded = (int) float(nSamples) / ratio; // N pixels needed for this update + + if (sbi + valuesNeeded > maxSamples) // crop number of samples to fit canvas width + { + valuesNeeded = maxSamples - sbi; + } + float subSampleOffset = 0.0; + + dbi %= displayBufferSize; // make sure we're not overshooting + int nextPos = (dbi + 1) % displayBufferSize; // position next to displayBufferIndex in display buffer to copy from + + // if (channel == 0) + // std::cout << "Channel " + // << channel << " : " + // << sbi << " : " + // << index << " : " + // << dbi << " : " + // << valuesNeeded << " : " + // << ratio + // << std::endl; + + if (valuesNeeded > 0 && valuesNeeded < 1000000) + { + for (int i = 0; i < valuesNeeded; i++) // also fill one extra sample for line drawing interpolation to match across draws + { + //If paused don't update screen buffers, but update all indexes as needed + if (!lfpDisplay->isPaused) + { + float gain = 1.0; + float alpha = (float)subSampleOffset; + float invAlpha = 1.0f - alpha; + + screenBuffer->clear(channel, sbi, 1); + screenBufferMean->clear(channel, sbi, 1); + screenBufferMin->clear(channel, sbi, 1); + screenBufferMax->clear(channel, sbi, 1); + + dbi %= displayBufferSize; // just to be sure + + // update continuous data channels + if (channel != nChans) + { + // interpolate between two samples with invAlpha and alpha + screenBuffer->addFrom(channel, // destChannel + sbi, // destStartSample + displayBuffer->getReadPointer(channel, dbi), // source + 1, // numSamples + invAlpha*gain); // gain + + + screenBuffer->addFrom(channel, // destChannel + sbi, // destStartSample + displayBuffer->getReadPointer(channel, nextPos), // source + 1, // numSamples + alpha*gain); // gain + } + + + // same thing again, but this time add the min,mean, and max of all samples in current pixel + float sample_min = 10000000; + float sample_max = -10000000; + float sample_mean = 0; + + int nextpix = (dbi + (int)ratio + 1) % (displayBufferSize + 1); // position to next pixels index + + if (nextpix <= dbi) { // at the end of the displaybuffer, this can occur and it causes the display to miss one pixel woth of sample - this circumvents that + // std::cout << "np " ; + nextpix = dbi; + } + + for (int j = dbi; j < nextpix; j++) + { + + float sample_current = displayBuffer->getSample(channel, j); + sample_mean = sample_mean + sample_current; + + if (sample_min > sample_current) + { + sample_min = sample_current; + } + + if (sample_max < sample_current) + { + sample_max = sample_current; + } + + } + + // update event channel + if (channel == nChans) + { + //std::cout << sample_max << std::endl; + screenBuffer->setSample(channel, sbi, sample_max); + //if (screenBuffer->getSample(channel, sbi - 1) != sample_max) + // std::cout << "Sample changed" << std::endl; + //screenBuffer->setSample(channel, sbi, sample_max); + } + + // 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 + { + int 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++; + } + + subSampleOffset += ratio; + + while (subSampleOffset >= 1.0) + { + if (++dbi > displayBufferSize) + dbi = 0; + + nextPos = (dbi + 1) % displayBufferSize; + subSampleOffset -= 1.0; + } + + } + + // update values after we're done + screenBufferIndex.set(channel, sbi); + displayBufferIndex.set(channel, dbi); + } + + } + } + +} + +const float LfpDisplayCanvas::getXCoord(int chan, int samp) +{ + return samp; +} + +int LfpDisplayCanvas::getNumChannels() +{ + return nChans; +} + +int LfpDisplayCanvas::getNumChannelsVisible() +{ + return lfpDisplay->drawableChannels.size(); +} + +int LfpDisplayCanvas::getChannelSubprocessorIdx(int channel) +{ + return processor->getDataChannel(channel)->getSubProcessorIdx(); +} + +const float LfpDisplayCanvas::getYCoord(int chan, int samp) +{ + return *screenBuffer->getReadPointer(chan, samp); +} + +const float LfpDisplayCanvas::getYCoordMean(int chan, int samp) +{ + return *screenBufferMean->getReadPointer(chan, samp); +} +const float LfpDisplayCanvas::getYCoordMin(int chan, int samp) +{ + return *screenBufferMin->getReadPointer(chan, samp); +} +const float LfpDisplayCanvas::getYCoordMax(int chan, int samp) +{ + return *screenBufferMax->getReadPointer(chan, samp); +} + +std::array LfpDisplayCanvas::getSamplesPerPixel(int chan, int px) +{ + return samplesPerPixel[chan][px]; +} +const int LfpDisplayCanvas::getSampleCountPerPixel(int px) +{ + return sampleCountPerPixel[px]; +} + +float LfpDisplayCanvas::getMean(int chan) +{ + float total = 0.0f; + float numPts = 0; + + float sample = 0.0f; + for (int samp = 0; samp < (lfpDisplay->getWidth() - leftmargin); samp += 10) + { + sample = *screenBuffer->getReadPointer(chan, samp); + total += sample; + numPts++; + } + + //std::cout << sample << std::endl; + + return total / numPts; +} + +float LfpDisplayCanvas::getStd(int chan) +{ + float std = 0.0f; + + float mean = getMean(chan); + float numPts = 1; + + for (int samp = 0; samp < (lfpDisplay->getWidth() - leftmargin); samp += 10) + { + std += pow((*screenBuffer->getReadPointer(chan, samp) - mean),2); + numPts++; + } + + return sqrt(std / numPts); + +} + +bool LfpDisplayCanvas::getInputInvertedState() +{ + return options->getInputInvertedState(); //invertInputButton->getToggleState(); +} + +bool LfpDisplayCanvas::getDisplaySpikeRasterizerState() +{ + return options->getDisplaySpikeRasterizerState(); +} + +bool LfpDisplayCanvas::getDrawMethodState() +{ + + return options->getDrawMethodState(); //drawMethodButton->getToggleState(); +} + +void LfpDisplayCanvas::setDrawableSampleRate(float samplerate) +{ +// std::cout << "setting the drawable sample rate in the canvas" << std::endl; + displayedSampleRate = samplerate; +} + +void LfpDisplayCanvas::setDrawableSubprocessor(uint32 sp) +{ + drawableSubprocessor = sp; + displayBuffer = processor->getDisplayBufferAddress(); + update(); +} + +void LfpDisplayCanvas::redraw() +{ + fullredraw=true; + repaint(); + refresh(); +} + + +void LfpDisplayCanvas::paint(Graphics& g) +{ + + //std::cout << "Painting" << std::endl; + + //g.setColour(Colour(0,0,0)); // for high-precision per-pixel density display, make background black for better visibility + g.setColour(lfpDisplay->backgroundColour); //background color + g.fillRect(0, 0, getWidth(), getHeight()); + + g.setGradientFill(ColourGradient(Colour(50,50,50),0,0, + Colour(25,25,25),0,30, + false)); + + g.fillRect(0, 0, getWidth()-scrollBarThickness, 30); + + g.setColour(Colours::black); + + g.drawLine(0,30,getWidth()-scrollBarThickness,30); + + g.setColour(Colour(25,25,60)); // timing grid color + + int w = getWidth()-scrollBarThickness-leftmargin; + + for (int i = 0; i < 10; i++) + { + if (i == 5 || i == 0) + g.drawLine(w/10*i+leftmargin,timescale->getHeight(),w/10*i+leftmargin,getHeight()-60-timescale->getHeight(),3.0f); + else + g.drawLine(w/10*i+leftmargin,timescale->getHeight(),w/10*i+leftmargin,getHeight()-60-timescale->getHeight(),1.0f); + } + + g.drawLine(0,getHeight()-60,getWidth(),getHeight()-60,3.0f); + + +} + +void LfpDisplayCanvas::refresh() +{ + if (true) + { + updateScreenBuffer(); + + lfpDisplay->refresh(); // redraws only the new part of the screen buffer + } +} + +bool LfpDisplayCanvas::keyPressed(const KeyPress& key) +{ + if (key.getKeyCode() == key.spaceKey) + { + options->togglePauseButton(); + + return true; + } + + return false; +} + +bool LfpDisplayCanvas::keyPressed(const KeyPress& key, Component* orig) +{ + if (getTopLevelComponent() == orig && isVisible()) + { + return keyPressed(key); + } + return false; +} + +void LfpDisplayCanvas::saveVisualizerParameters(XmlElement* xml) +{ + + options->saveParameters(xml); + LfpDisplayEditor* ed = (LfpDisplayEditor*) processor->getEditor(); + ed->saveVisualizerParameters(xml); +} + + +void LfpDisplayCanvas::loadVisualizerParameters(XmlElement* xml) +{ + options->loadParameters(xml); + LfpDisplayEditor* ed = (LfpDisplayEditor*) processor->getEditor(); + ed->loadVisualizerParameters(xml); +} + + + +#pragma mark - ShowHideOptionsButton - +// ============================================================= + + +ShowHideOptionsButton::ShowHideOptionsButton(LfpDisplayOptions* options) : Button("Button") +{ + setClickingTogglesState(true); +} +ShowHideOptionsButton::~ShowHideOptionsButton() +{ + +} + +void ShowHideOptionsButton::paintButton(Graphics& g, bool, bool) +{ + g.setColour(Colours::white); + + Path p; + + float h = getHeight(); + float w = getWidth(); + + if (getToggleState()) + { + p.addTriangle(0.5f*w, 0.2f*h, + 0.2f*w, 0.8f*h, + 0.8f*w, 0.8f*h); + } + else + { + p.addTriangle(0.8f*w, 0.8f*h, + 0.2f*w, 0.5f*h, + 0.8f*w, 0.2f*h); + } + + PathStrokeType pst = PathStrokeType(1.0f, PathStrokeType::curved, PathStrokeType::rounded); + + g.strokePath(p, pst); +} + + + +#pragma mark - LfpDisplayOptions - +// ------------------------------------------------------------- + +LfpDisplayOptions::LfpDisplayOptions(LfpDisplayCanvas* canvas_, LfpTimescale* timescale_, + LfpDisplay* lfpDisplay_, LfpDisplayNode* processor_) + : canvas(canvas_), + lfpDisplay(lfpDisplay_), + timescale(timescale_), + processor(processor_), + selectedChannelType(DataChannel::HEADSTAGE_CHANNEL), + labelFont("Default", 13.0f, Font::plain), + labelColour(100, 100, 100) +{ + // draw the colour scheme options + // TODO: (kelly) this might be better as a modal window + colourSchemeOptionLabel = new Label("colorSchemeOptionLabel", "Color Scheme"); + colourSchemeOptionLabel->setFont(labelFont); + colourSchemeOptionLabel->setColour(Label::textColourId, labelColour); + addAndMakeVisible(colourSchemeOptionLabel); + + StringArray colourSchemeNames = lfpDisplay->getColourSchemeNameArray(); + colourSchemeOptionSelection = new ComboBox("colorSchemeOptionSelection"); + colourSchemeOptionSelection->addItemList(colourSchemeNames, 1); + colourSchemeOptionSelection->setEditableText(false); + colourSchemeOptionSelection->addListener(this); + colourSchemeOptionSelection->setSelectedId(1, dontSendNotification); + addAndMakeVisible(colourSchemeOptionSelection); + + if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) + addAndMakeVisible(lfpDisplay->getColourSchemePtr()); + + //Ranges for neural data + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("25"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("50"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("100"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("250"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("400"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("500"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("750"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("1000"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("2000"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("5000"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("10000"); + voltageRanges[DataChannel::HEADSTAGE_CHANNEL].add("15000"); + selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] = 4; + rangeGain[DataChannel::HEADSTAGE_CHANNEL] = 1; //uV + rangeSteps[DataChannel::HEADSTAGE_CHANNEL] = 10; + rangeUnits.add(CharPointer_UTF8("\xC2\xB5V")); + typeNames.add("DATA"); + + UtilityButton* tbut; + tbut = new UtilityButton("DATA",Font("Small Text", 9, Font::plain)); + tbut->setEnabledState(true); + tbut->setCorners(false,false,false,false); + tbut->addListener(this); + tbut->setClickingTogglesState(true); + tbut->setRadioGroupId(100,dontSendNotification); + tbut->setToggleState(true,dontSendNotification); + addAndMakeVisible(tbut); + typeButtons.add(tbut); + + //Ranges for AUX/accelerometer data + voltageRanges[DataChannel::AUX_CHANNEL].add("25"); + voltageRanges[DataChannel::AUX_CHANNEL].add("50"); + voltageRanges[DataChannel::AUX_CHANNEL].add("100"); + voltageRanges[DataChannel::AUX_CHANNEL].add("250"); + voltageRanges[DataChannel::AUX_CHANNEL].add("400"); + voltageRanges[DataChannel::AUX_CHANNEL].add("500"); + voltageRanges[DataChannel::AUX_CHANNEL].add("750"); + voltageRanges[DataChannel::AUX_CHANNEL].add("1000"); + voltageRanges[DataChannel::AUX_CHANNEL].add("2000"); + //voltageRanges[DataChannel::AUX_CHANNEL].add("5000"); + selectedVoltageRange[DataChannel::AUX_CHANNEL] = 9; + rangeGain[DataChannel::AUX_CHANNEL] = 0.001f; //mV + rangeSteps[DataChannel::AUX_CHANNEL] = 10; + rangeUnits.add("mV"); + typeNames.add("AUX"); + + tbut = new UtilityButton("AUX",Font("Small Text", 9, Font::plain)); + tbut->setEnabledState(true); + tbut->setCorners(false,false,false,false); + tbut->addListener(this); + tbut->setClickingTogglesState(true); + tbut->setRadioGroupId(100,dontSendNotification); + tbut->setToggleState(false,dontSendNotification); + addAndMakeVisible(tbut); + typeButtons.add(tbut); + + //Ranges for ADC data + voltageRanges[DataChannel::ADC_CHANNEL].add("0.01"); + voltageRanges[DataChannel::ADC_CHANNEL].add("0.05"); + voltageRanges[DataChannel::ADC_CHANNEL].add("0.1"); + voltageRanges[DataChannel::ADC_CHANNEL].add("0.5"); + voltageRanges[DataChannel::ADC_CHANNEL].add("1.0"); + voltageRanges[DataChannel::ADC_CHANNEL].add("2.0"); + voltageRanges[DataChannel::ADC_CHANNEL].add("5.0"); + voltageRanges[DataChannel::ADC_CHANNEL].add("10.0"); + selectedVoltageRange[DataChannel::ADC_CHANNEL] = 8; + rangeGain[DataChannel::ADC_CHANNEL] = 1; //V + rangeSteps[DataChannel::ADC_CHANNEL] = 0.1; //in V + rangeUnits.add("V"); + typeNames.add("ADC"); + + tbut = new UtilityButton("ADC",Font("Small Text", 9, Font::plain)); + tbut->setEnabledState(true); + tbut->setCorners(false,false,false,false); + tbut->addListener(this); + tbut->setClickingTogglesState(true); + tbut->setRadioGroupId(100,dontSendNotification); + tbut->setToggleState(false,dontSendNotification); + addAndMakeVisible(tbut); + typeButtons.add(tbut); + + selectedVoltageRangeValues[DataChannel::HEADSTAGE_CHANNEL] = voltageRanges[DataChannel::HEADSTAGE_CHANNEL][selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] - 1]; + selectedVoltageRangeValues[DataChannel::AUX_CHANNEL] = voltageRanges[DataChannel::AUX_CHANNEL][selectedVoltageRange[DataChannel::AUX_CHANNEL] - 1]; + selectedVoltageRangeValues[DataChannel::ADC_CHANNEL] = voltageRanges[DataChannel::ADC_CHANNEL][selectedVoltageRange[DataChannel::ADC_CHANNEL] - 1]; + + + + // init channel display skipping options + channelDisplaySkipOptions.add("All"); + channelDisplaySkipOptions.add("2"); + channelDisplaySkipOptions.add("4"); + channelDisplaySkipOptions.add("8"); + channelDisplaySkipOptions.add("16"); + channelDisplaySkipOptions.add("32"); + channelDisplaySkipOptions.add("64"); + selectedChannelDisplaySkip = 1; + selectedChannelDisplaySkipValue = channelDisplaySkipOptions[selectedChannelDisplaySkip - 1]; + + channelDisplaySkipSelection = new ComboBox("Channel Skip"); + channelDisplaySkipSelection->addItemList(channelDisplaySkipOptions, 1); + channelDisplaySkipSelection->setSelectedId(selectedChannelDisplaySkip, sendNotification); + channelDisplaySkipSelection->setEditableText(false); + channelDisplaySkipSelection->addListener(this); + addAndMakeVisible(channelDisplaySkipSelection); + + channelDisplaySkipLabel = new Label("Channel Display Skip", "Ch. Skip"); + channelDisplaySkipLabel->setFont(labelFont); + channelDisplaySkipLabel->setColour(Label::textColourId, labelColour); + addAndMakeVisible(channelDisplaySkipLabel); + + + + // init spike raster options + spikeRasterSelectionOptions = {"Off", "-50", "-100", "-150", "-200", "-300", "-400", "-500"}; + selectedSpikeRasterThreshold = 1; + selectedSpikeRasterThresholdValue = spikeRasterSelectionOptions[selectedSpikeRasterThreshold - 1]; + + spikeRasterSelection = new ComboBox("spikeRasterSelection"); + spikeRasterSelection->addItemList(spikeRasterSelectionOptions, 1); + spikeRasterSelection->setSelectedId(selectedSpikeRasterThreshold, dontSendNotification); + spikeRasterSelection->setEditableText(true); + spikeRasterSelection->addListener(this); + addAndMakeVisible(spikeRasterSelection); + + spikeRasterLabel = new Label("spikeRasterLabel", "Spike Raster Thresh."); + spikeRasterLabel->setFont(labelFont); + spikeRasterLabel->setColour(Label::textColourId, labelColour); + addAndMakeVisible(spikeRasterLabel); + + + + // init median offset plotting + medianOffsetPlottingLabel = new Label("Median Offset Correction", "Median Offset Correction"); + medianOffsetPlottingLabel->setFont(labelFont); + medianOffsetPlottingLabel->setColour(Label::textColourId, labelColour); + addAndMakeVisible(medianOffsetPlottingLabel); + + medianOffsetPlottingButton = new UtilityButton("0", labelFont); + medianOffsetPlottingButton->setRadius(5.0f); + medianOffsetPlottingButton->setEnabledState(true); + medianOffsetPlottingButton->setCorners(true, true, true, true); + medianOffsetPlottingButton->addListener(this); + medianOffsetPlottingButton->setClickingTogglesState(true); + medianOffsetPlottingButton->setToggleState(false, sendNotification); + addAndMakeVisible(medianOffsetPlottingButton); + + //init channel name toggle + showChannelNumberLabel = new Label("showcChannelLabel", "Show channel number instead of name"); + showChannelNumberLabel->setFont(labelFont); + showChannelNumberLabel->setColour(Label::textColourId, labelColour); + addAndMakeVisible(showChannelNumberLabel); + + showChannelNumberButton = new UtilityButton("0", labelFont); + showChannelNumberButton->setRadius(5.0f); + showChannelNumberButton->setEnabledState(true); + showChannelNumberButton->setCorners(true, true, true, true); + showChannelNumberButton->addListener(this); + showChannelNumberButton->setClickingTogglesState(true); + showChannelNumberButton->setToggleState(false, sendNotification); + addAndMakeVisible(showChannelNumberButton); + + + // init show/hide options button + showHideOptionsButton = new ShowHideOptionsButton(this); + showHideOptionsButton->addListener(this); + addAndMakeVisible(showHideOptionsButton); + + // init timebases options + timebases.add("0.25"); + timebases.add("0.5"); + timebases.add("1.0"); + timebases.add("2.0"); + timebases.add("3.0"); + timebases.add("4.0"); + timebases.add("5.0"); + timebases.add("10.0"); + timebases.add("20.0"); + selectedTimebase = 4; + selectedTimebaseValue = timebases[selectedTimebase-1]; + + spreads.add("10"); + spreads.add("20"); + spreads.add("30"); + spreads.add("40"); + spreads.add("50"); + spreads.add("60"); + spreads.add("70"); + spreads.add("80"); + spreads.add("90"); + spreads.add("100"); + selectedSpread = 4; + selectedSpreadValue = spreads[selectedSpread-1]; + + + overlaps.add("0.5"); + overlaps.add("0.75"); + overlaps.add("1"); + overlaps.add("2"); + overlaps.add("3"); + overlaps.add("4"); + overlaps.add("5"); + selectedOverlap = 4; + selectedOverlapValue = overlaps[selectedOverlap-1]; + + saturationThresholds.add("0.5"); + saturationThresholds.add("100"); + saturationThresholds.add("1000"); + saturationThresholds.add("5000"); + saturationThresholds.add("6389"); + + selectedSaturation = 5; + selectedSaturationValue = saturationThresholds[selectedSaturation-1]; + + + colorGroupings.add("1"); + colorGroupings.add("2"); + colorGroupings.add("4"); + colorGroupings.add("8"); + colorGroupings.add("16"); + + + rangeSelection = new ComboBox("Voltage range"); + rangeSelection->addItemList(voltageRanges[DataChannel::HEADSTAGE_CHANNEL], 1); + rangeSelection->setSelectedId(selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL], sendNotification); + rangeSelection->setEditableText(true); + rangeSelection->addListener(this); + addAndMakeVisible(rangeSelection); + + + timebaseSelection = new ComboBox("Timebase"); + timebaseSelection->addItemList(timebases, 1); + timebaseSelection->setSelectedId(selectedTimebase, sendNotification); + timebaseSelection->setEditableText(true); + timebaseSelection->addListener(this); + addAndMakeVisible(timebaseSelection); + + + spreadSelection = new ComboBox("Spread"); + spreadSelection->addItemList(spreads, 1); + spreadSelection->setSelectedId(selectedSpread,sendNotification); + spreadSelection->addListener(this); + spreadSelection->setEditableText(true); + addAndMakeVisible(spreadSelection); + + overlapSelection = new ComboBox("Overlap"); + overlapSelection->addItemList(overlaps, 1); + overlapSelection->setSelectedId(selectedOverlap,sendNotification); + overlapSelection->addListener(this); + overlapSelection->setEditableText(true); + addAndMakeVisible(overlapSelection); + + saturationWarningSelection = new ComboBox("Sat.Warn"); + saturationWarningSelection->addItemList(saturationThresholds, 1); + saturationWarningSelection->setSelectedId(selectedSaturation,sendNotification); + saturationWarningSelection->addListener(this); + saturationWarningSelection->setEditableText(true); + addAndMakeVisible(saturationWarningSelection); + + + colorGroupingSelection = new ComboBox("Color Grouping"); + colorGroupingSelection->addItemList(colorGroupings, 1); + colorGroupingSelection->setSelectedId(1,sendNotification); + colorGroupingSelection->addListener(this); + addAndMakeVisible(colorGroupingSelection); + + invertInputButton = new UtilityButton("Invert", Font("Small Text", 13, Font::plain)); + invertInputButton->setRadius(5.0f); + invertInputButton->setEnabledState(true); + invertInputButton->setCorners(true, true, true, true); + invertInputButton->addListener(this); + invertInputButton->setClickingTogglesState(true); + invertInputButton->setToggleState(false, sendNotification); + addAndMakeVisible(invertInputButton); + + // toggle button to reverse the order of channels + reverseChannelsDisplayButton = new UtilityButton("0", labelFont); + reverseChannelsDisplayButton->setRadius(5.0f); + reverseChannelsDisplayButton->setEnabledState(true); + reverseChannelsDisplayButton->setCorners(true, true, true, true); + reverseChannelsDisplayButton->addListener(this); + reverseChannelsDisplayButton->setClickingTogglesState(true); + reverseChannelsDisplayButton->setToggleState(lfpDisplay->getChannelsReversed(), sendNotification); + addAndMakeVisible(reverseChannelsDisplayButton); + + reverseChannelsDisplayLabel = new Label("Rev. Channels", "Rev. Channels"); + reverseChannelsDisplayLabel->setFont(labelFont); + reverseChannelsDisplayLabel->setColour(Label::textColourId, labelColour); + addAndMakeVisible(reverseChannelsDisplayLabel); + + + //button for controlling drawing algorithm - old line-style or new per-pixel style + drawMethodButton = new UtilityButton("DrawMethod", Font("Small Text", 13, Font::plain)); + drawMethodButton->setRadius(5.0f); + drawMethodButton->setEnabledState(true); + drawMethodButton->setCorners(true, true, true, true); + drawMethodButton->addListener(this); + drawMethodButton->setClickingTogglesState(true); + drawMethodButton->setToggleState(false, sendNotification); + addAndMakeVisible(drawMethodButton); + + // two sliders for the two histogram components of the supersampled plotting mode + // todo: rename these + brightnessSliderA = new Slider(); + brightnessSliderA->setRange (0, 1); + brightnessSliderA->setTextBoxStyle(Slider::NoTextBox, false, 50,30); + brightnessSliderA->addListener(this); + addAndMakeVisible (brightnessSliderA); + + brightnessSliderB = new Slider; + brightnessSliderB->setRange (0, 1); + brightnessSliderB->setTextBoxStyle(Slider::NoTextBox, false, 50,30); + brightnessSliderB->addListener(this); + addAndMakeVisible (brightnessSliderB); + + sliderALabel = new Label("Brightness","Brightness"); + sliderALabel->setFont(Font("Small Text", 13, Font::plain)); + sliderALabel->setColour(Label::textColourId,Colour(150,150,150)); + addAndMakeVisible(sliderALabel); + + sliderBLabel = new Label("Min. brightness","Min. brightness"); + sliderBLabel->setFont(Font("Small Text", 13, Font::plain)); + sliderBLabel->setColour(Label::textColourId,Colour(150,150,150)); + addAndMakeVisible(sliderBLabel); + + + //ScopedPointer drawClipWarningButton; // optinally draw (subtle) warning if data is clipped in display + drawClipWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain)); + drawClipWarningButton->setRadius(5.0f); + drawClipWarningButton->setEnabledState(true); + drawClipWarningButton->setCorners(true, true, true, true); + drawClipWarningButton->addListener(this); + drawClipWarningButton->setClickingTogglesState(true); + drawClipWarningButton->setToggleState(false, sendNotification); + addAndMakeVisible(drawClipWarningButton); + + + //ScopedPointer drawSaturateWarningButton; // optionally raise hell if the actual data is saturating + drawSaturateWarningButton = new UtilityButton("0", Font("Small Text", 13, Font::plain)); + drawSaturateWarningButton->setRadius(5.0f); + drawSaturateWarningButton->setEnabledState(true); + drawSaturateWarningButton->setCorners(true, true, true, true); + drawSaturateWarningButton->addListener(this); + drawSaturateWarningButton->setClickingTogglesState(true); + drawSaturateWarningButton->setToggleState(false, sendNotification); + addAndMakeVisible(drawSaturateWarningButton); + + + //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)); + pauseButton->setRadius(5.0f); + pauseButton->setEnabledState(true); + pauseButton->setCorners(true, true, true, true); + pauseButton->addListener(this); + pauseButton->setClickingTogglesState(true); + pauseButton->setToggleState(false, sendNotification); + addAndMakeVisible(pauseButton); + + // add event display-specific controls (currently just an enable/disable button) + for (int i = 0; i < 8; i++) + { + + EventDisplayInterface* eventOptions = new EventDisplayInterface(lfpDisplay, canvas, i); + eventDisplayInterfaces.add(eventOptions); + addAndMakeVisible(eventOptions); + eventOptions->setBounds(700+(floor(i/2)*20), getHeight()-20-(i%2)*20, 40, 20); + + lfpDisplay->setEventDisplayState(i,true); + + } + + lfpDisplay->setRange(voltageRanges[DataChannel::HEADSTAGE_CHANNEL][selectedVoltageRange[DataChannel::HEADSTAGE_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::HEADSTAGE_CHANNEL] + , DataChannel::HEADSTAGE_CHANNEL); + lfpDisplay->setRange(voltageRanges[DataChannel::ADC_CHANNEL][selectedVoltageRange[DataChannel::ADC_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::ADC_CHANNEL] + , DataChannel::ADC_CHANNEL); + lfpDisplay->setRange(voltageRanges[DataChannel::AUX_CHANNEL][selectedVoltageRange[DataChannel::AUX_CHANNEL] - 1].getFloatValue()*rangeGain[DataChannel::AUX_CHANNEL] + , DataChannel::AUX_CHANNEL); + +} + +LfpDisplayOptions::~LfpDisplayOptions() +{ + +} + +void LfpDisplayOptions::resized() +{ + rangeSelection->setBounds(5,getHeight()-30,80,25); + timebaseSelection->setBounds(175,getHeight()-30,60,25); + + spreadSelection->setBounds(5,getHeight()-90,60,25); + + overlapSelection->setBounds(100,getHeight()-90,60,25); + + drawClipWarningButton->setBounds(175,getHeight()-89,20,20); + drawSaturateWarningButton->setBounds(325, getHeight()-89, 20, 20); + + colorGroupingSelection->setBounds(400,getHeight()-90,60,25); + + invertInputButton->setBounds(35,getHeight()-190,100,22); + drawMethodButton->setBounds(35,getHeight()-160,100,22); + + pauseButton->setBounds(450,getHeight()-50,50,44); + + + // Reverse Channels Display + reverseChannelsDisplayButton->setBounds(pauseButton->getRight() + 5, + getHeight() - 50, + 20, + 20); + reverseChannelsDisplayLabel->setBounds(reverseChannelsDisplayButton->getRight(), + reverseChannelsDisplayButton->getY(), + 120, + 22); + + // Channel Display Skip Selector + channelDisplaySkipSelection->setBounds(reverseChannelsDisplayButton->getX(), + reverseChannelsDisplayButton->getBottom(), + 60, + 25); + channelDisplaySkipLabel->setBounds(channelDisplaySkipSelection->getRight(), + channelDisplaySkipSelection->getY() + 2, + 100, + 22); + + // Median Offset Plotting Button + medianOffsetPlottingButton->setBounds(reverseChannelsDisplayLabel->getRight() + 5, + reverseChannelsDisplayButton->getY(), + 20, + 20); + medianOffsetPlottingLabel->setBounds(medianOffsetPlottingButton->getRight(), + medianOffsetPlottingButton->getY(), + 150, + 22); + + //Channel name toggle + showChannelNumberButton->setBounds(medianOffsetPlottingLabel->getRight() + 5, + medianOffsetPlottingLabel->getY(), + 20, + 20); + showChannelNumberLabel->setBounds(showChannelNumberButton->getRight(), + showChannelNumberButton->getY(), + 200, + 22); + + // Spike raster plotting button + spikeRasterSelection->setBounds(medianOffsetPlottingButton->getX(), + medianOffsetPlottingButton->getBottom(), + 60, + 25); + spikeRasterLabel->setBounds(spikeRasterSelection->getRight(), + spikeRasterSelection->getY(), + 120, + 22); + + + // Saturation Warning Selection + saturationWarningSelection->setBounds(250, getHeight()-90, 60, 25); + + + for (int i = 0; i < 8; i++) + { + eventDisplayInterfaces[i]->setBounds(300+(floor(i/2)*20), getHeight()-40+(i%2)*20, 40, 20); // arrange event channel buttons in two rows + eventDisplayInterfaces[i]->repaint(); + } + + brightnessSliderA->setBounds(170,getHeight()-190,100,22); + sliderALabel->setBounds(270, getHeight()-190, 180, 22); + brightnessSliderA->setValue(0.9); //set default value + + brightnessSliderB->setBounds(170,getHeight()-160,100,22); + sliderBLabel->setBounds(270, getHeight()-160, 180, 22); + brightnessSliderB->setValue(0.1); //set default value + + showHideOptionsButton->setBounds (getWidth() - 28, getHeight() - 28, 20, 20); + + int bh = 25/typeButtons.size(); + for (int i = 0; i < typeButtons.size(); i++) + { + typeButtons[i]->setBounds(95,getHeight()-30+i*bh,50,bh); + } + + + colourSchemeOptionLabel->setBounds(medianOffsetPlottingButton->getX(), + getHeight()-190, + 100, + 22); + colourSchemeOptionSelection->setBounds(colourSchemeOptionLabel->getRight(), + colourSchemeOptionLabel->getY(), + 80, + 25); + + // set the size of the active colour scheme's options, if it has configurable options + if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) + { + lfpDisplay->getColourSchemePtr()->setBounds(colourSchemeOptionLabel->getX(), + colourSchemeOptionLabel->getBottom(), + 200, + 110); + } +} + +void LfpDisplayOptions::paint(Graphics& g) +{ + int row1 = 55; + int row2 = 110; + + g.fillAll(Colours::black); + g.setFont(Font("Default", 16, Font::plain)); + + g.setColour(Colour(100,100,100)); + + g.drawText("Range("+ rangeUnits[selectedChannelType] +")",5,getHeight()-row1,300,20,Justification::left, false); + g.drawText("Timebase(s)",160,getHeight()-row1,300,20,Justification::left, false); + g.drawText("Size(px)",5,getHeight()-row2,300,20,Justification::left, false); + g.drawText("Clip",100,getHeight()-row2,300,20,Justification::left, false); + g.drawText("Warn",168,getHeight()-row2,300,20,Justification::left, false); + + g.drawText("Sat. Warning",225,getHeight()-row2,300,20,Justification::left, false); + + g.drawText("Color grouping",365,getHeight()-row2,300,20,Justification::left, false); + + g.drawText("Event disp.",300,getHeight()-row1,300,20,Justification::left, false); + + if(canvas->drawClipWarning) + { + g.setColour(Colours::white); + g.fillRoundedRectangle(173,getHeight()-90-1,24,24,6.0f); + } + + if(canvas->drawSaturationWarning) + { + g.setColour(Colours::red); + g.fillRoundedRectangle(323,getHeight()-90-1,24,24,6.0f); + } + +} + +int LfpDisplayOptions::getChannelHeight() +{ + return (int)spreadSelection->getText().getIntValue(); +} + + +bool LfpDisplayOptions::getDrawMethodState() +{ + + return drawMethodButton->getToggleState(); +} + +bool LfpDisplayOptions::getInputInvertedState() +{ + return invertInputButton->getToggleState(); +} + +bool LfpDisplayOptions::getChannelNameState() +{ + return showChannelNumberButton->getToggleState(); +} + +bool LfpDisplayOptions::getDisplaySpikeRasterizerState() +{ +// return spikeRasterButton->getToggleState(); + return false; +} + +void LfpDisplayOptions::setDisplaySpikeRasterizerState(bool isEnabled) +{ +// spikeRasterButton->setToggleState(isEnabled, dontSendNotification); + +// if (isEnabled) medianOffsetPlottingButton->setToggleState(true, sendNotification); +} + +void LfpDisplayOptions::setRangeSelection(float range, bool canvasMustUpdate) +{ + if (canvasMustUpdate) + { + rangeSelection->setText(String(range/rangeGain[selectedChannelType]), sendNotification); + } + else + { + rangeSelection->setText(String(range/rangeGain[selectedChannelType]),dontSendNotification); + + selectedVoltageRange[selectedChannelType] = rangeSelection->getSelectedId(); + selectedVoltageRangeValues[selectedChannelType] = rangeSelection->getText(); + + canvas->repaint(); + canvas->refresh(); + } + +} + +void LfpDisplayOptions::setSpreadSelection(int spread, bool canvasMustUpdate, bool deferDisplayRefresh) +{ + + if (canvasMustUpdate) + { + spreadSelection->setText(String(spread),sendNotification); + } + else + { + spreadSelection->setText(String(spread),dontSendNotification); + selectedSpread = spreadSelection->getSelectedId(); + selectedSpreadValue = spreadSelection->getText(); + + if (!deferDisplayRefresh) + { + canvas->repaint(); + canvas->refresh(); + } + } +} + +void LfpDisplayOptions::togglePauseButton(bool sendUpdate) +{ + pauseButton->setToggleState(!pauseButton->getToggleState(), sendUpdate ? sendNotification : dontSendNotification); +} + +void LfpDisplayOptions::buttonClicked(Button* b) +{ + if (b == invertInputButton) + { + lfpDisplay->setInputInverted(b->getToggleState()); + return; + } + if (b == reverseChannelsDisplayButton) + { + lfpDisplay->setChannelsReversed(b->getToggleState()); + return; + } + if (b == medianOffsetPlottingButton) + { + if (lfpDisplay->getSpikeRasterPlotting()) + { + medianOffsetPlottingButton->setToggleState(true, dontSendNotification); + } + else + { + lfpDisplay->setMedianOffsetPlotting(b->getToggleState()); + } + return; + } + if (b == drawMethodButton) + { + lfpDisplay->setDrawMethod(b->getToggleState()); // this should be done the same way as drawClipWarning - or the other way around. + + return; + } + if (b == drawClipWarningButton) + { + canvas->drawClipWarning = b->getToggleState(); + canvas->redraw(); + return; + } + if (b == drawSaturateWarningButton) + { + canvas->drawSaturationWarning = b->getToggleState(); + canvas->redraw(); + return; + } + + if (b == pauseButton) + { + lfpDisplay->isPaused = b->getToggleState(); + return; + } + + if (b == showHideOptionsButton) + { + canvas->toggleOptionsDrawer(b->getToggleState()); + } + + if (b == showChannelNumberButton) + { + int numChannels = lfpDisplay->channelInfo.size(); + for (int i = 0; i < numChannels; ++i) + { + lfpDisplay->channelInfo[i]->repaint(); + } + return; + } + + int idx = typeButtons.indexOf((UtilityButton*)b); + + if ((idx >= 0) && (b->getToggleState())) + { + for (int i = 0; i < lfpDisplay->getNumChannels(); i++) + { + if (lfpDisplay->channels[i]->getSelected()) + { + lfpDisplay->channels[i]->deselect(); + lfpDisplay->channels[i]->repaint(); + } + } + + setSelectedType((DataChannel::DataChannelTypes) idx, false); + } + +} + +void LfpDisplayOptions::setTimebaseAndSelectionText(float timebase) +{ + canvas->timebase = timebase; + + if (canvas->timebase) // if timebase != 0 + { + if (canvas->timebase < timebases[0].getFloatValue()) + { + timebaseSelection->setSelectedId(1, dontSendNotification); + canvas->timebase = timebases[0].getFloatValue(); + } + else if (canvas->timebase > timebases[timebases.size()-1].getFloatValue()) + { + timebaseSelection->setSelectedId(timebases.size(), dontSendNotification); + canvas->timebase = timebases[timebases.size()-1].getFloatValue(); + } + else{ + timebaseSelection->setText(String(canvas->timebase, 1), dontSendNotification); + } + } + else + { + if (selectedSpread == 0) + { + timebaseSelection->setText(selectedTimebaseValue, dontSendNotification); + canvas->timebase = selectedTimebaseValue.getFloatValue(); + } + else + { + timebaseSelection->setSelectedId(selectedTimebase,dontSendNotification); + canvas->timebase = timebases[selectedTimebase-1].getFloatValue(); + } + + } +} + + +void LfpDisplayOptions::comboBoxChanged(ComboBox* cb) +{ + if (canvas->getNumChannels() == 0) return; + + if (cb == channelDisplaySkipSelection) + { + const int skipAmt = pow(2, cb->getSelectedId() - 1); + lfpDisplay->setChannelDisplaySkipAmount(skipAmt); + } + else if (cb == spikeRasterSelection) + { + // if custom value + if (cb->getSelectedId() == 0) + { + auto val = fabsf(cb->getText().getFloatValue()); + + if (val == 0) // if value is zero, just disable plotting and set text to "Off" + { + cb->setSelectedItemIndex(0, dontSendNotification); + lfpDisplay->setSpikeRasterPlotting(false); + return; + } + + if (val > 500) + { + val = 500; + } + + val *= -1; + + spikeRasterSelection->setText(String(val), dontSendNotification); + lfpDisplay->setSpikeRasterThreshold(val); + medianOffsetPlottingButton->setToggleState(true, dontSendNotification); + lfpDisplay->setMedianOffsetPlotting(true); + lfpDisplay->setSpikeRasterPlotting(true); + } + else if (cb->getSelectedItemIndex() == 0) // if "Off" + { + lfpDisplay->setSpikeRasterPlotting(false); + return; + } + else + { + auto val = cb->getText().getFloatValue(); + + lfpDisplay->setSpikeRasterThreshold(val); + medianOffsetPlottingButton->setToggleState(true, dontSendNotification); + lfpDisplay->setMedianOffsetPlotting(true); + lfpDisplay->setSpikeRasterPlotting(true); + } + } + else if (cb == colourSchemeOptionSelection) + { + // hide the old colour scheme config options if they are displayed + if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) + removeChildComponent(lfpDisplay->getColourSchemePtr()); + + // change the active colour scheme ptr + lfpDisplay->setActiveColourSchemeIdx(cb->getSelectedId()-1); + + // show the new colour scheme's config options if has any + + if (lfpDisplay->getColourSchemePtr()->hasConfigurableElements()) + { + lfpDisplay->getColourSchemePtr()->setBounds(colourSchemeOptionLabel->getX(), + colourSchemeOptionLabel->getBottom(), + 200, + 110); + addAndMakeVisible(lfpDisplay->getColourSchemePtr()); + } + + // update the lfpDisplay's colors and redraw + lfpDisplay->setColors(); + canvas->redraw(); + } + else if (cb == timebaseSelection) + { + if (cb->getSelectedId()) + { + canvas->timebase = timebases[cb->getSelectedId()-1].getFloatValue(); + } + else + { + setTimebaseAndSelectionText(cb->getText().getFloatValue()); + } + } + else if (cb == rangeSelection) + { + if (cb->getSelectedId()) + { + lfpDisplay->setRange(voltageRanges[selectedChannelType][cb->getSelectedId()-1].getFloatValue()*rangeGain[selectedChannelType] + ,selectedChannelType); + } + else + { + float vRange = cb->getText().getFloatValue(); + if (vRange) + { + if (vRange < voltageRanges[selectedChannelType][0].getFloatValue()) + { + cb->setSelectedId(1,dontSendNotification); + vRange = voltageRanges[selectedChannelType][0].getFloatValue(); + } + else if (vRange > voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue()) + { + // cb->setSelectedId(voltageRanges[selectedChannelType].size(),dontSendNotification); + // vRange = voltageRanges[selectedChannelType][voltageRanges[selectedChannelType].size()-1].getFloatValue(); + } + else + { + if (rangeGain[selectedChannelType] > 1) + cb->setText(String(vRange,1),dontSendNotification); + else + cb->setText(String(vRange),dontSendNotification); + } + lfpDisplay->setRange(vRange*rangeGain[selectedChannelType],selectedChannelType); + } + else + { + if (selectedVoltageRange[selectedChannelType]) + cb->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification); + else + cb->setSelectedId(selectedVoltageRange[selectedChannelType],dontSendNotification); + } + } + selectedVoltageRange[selectedChannelType] = cb->getSelectedId(); + selectedVoltageRangeValues[selectedChannelType] = cb->getText(); + //std::cout << "Setting range to " << voltageRanges[cb->getSelectedId()-1].getFloatValue() << std::endl; + canvas->redraw(); + } + else if (cb == spreadSelection) + { + + if (cb->getSelectedId()) + { + if (lfpDisplay->getSingleChannelState()) + { + lfpDisplay->cacheNewChannelHeight(spreads[cb->getSelectedId()-1].getIntValue()); + } + else + { + lfpDisplay->setChannelHeight(spreads[cb->getSelectedId()-1].getIntValue()); + resized(); + } + } + else + { + int spread = cb->getText().getIntValue(); + if (spread) + { + if (spread < spreads[0].getFloatValue()) + { + cb->setSelectedId(1,dontSendNotification); + spread = spreads[0].getFloatValue(); + } + else if (spread > spreads[spreads.size()-1].getFloatValue()) + { + cb->setSelectedId(spreads.size(),dontSendNotification); + spread = spreads[spreads.size()-1].getFloatValue(); + } + else + { + cb->setText(String(spread),dontSendNotification); + } + + // if single channel focus is on, cache the value + if (lfpDisplay->getSingleChannelState()) + { + lfpDisplay->cacheNewChannelHeight(spread); + } + else + { + lfpDisplay->setChannelHeight(spread); + canvas->resized(); + } + } + else + { + if (selectedSpread == 0) + cb->setText(selectedSpreadValue,dontSendNotification); + else + cb->setSelectedId(selectedSpread,dontSendNotification); + } + } + selectedSpread = cb->getSelectedId(); + selectedSpreadValue = cb->getText(); + + if (!lfpDisplay->getSingleChannelState()) canvas->redraw(); + //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl; + } + else if (cb == saturationWarningSelection) + { + if (cb->getSelectedId()) + { + selectedSaturationValueFloat = (saturationThresholds[cb->getSelectedId()-1].getFloatValue()); + } + else + { + selectedSaturationValueFloat = cb->getText().getFloatValue(); + if (selectedSaturationValueFloat) + { + std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl; + if (selectedSaturationValueFloat < 0) + { + cb->setSelectedId(1,dontSendNotification); + selectedSaturationValueFloat = saturationThresholds[0].getFloatValue(); + } + else + { + // cb->setText(String(selectedSaturationValueFloat),dontSendNotification); + } + } + else + { + // cb->setSelectedId(1,dontSendNotification); + //selectedSaturationValueFloat = saturationThresholds[0].getFloatValue(); + + } + } + canvas->redraw(); + + std::cout << "Setting saturation warning to to " << selectedSaturationValueFloat << std::endl; + } + else if (cb == overlapSelection) + { + if (cb->getSelectedId()) + { + canvas->channelOverlapFactor = (overlaps[cb->getSelectedId()-1].getFloatValue()); + canvas->resized(); + } + else + { + float overlap = cb->getText().getFloatValue(); + if (overlap) + { + if (overlap < overlaps[0].getFloatValue()) + { + cb->setSelectedId(1,dontSendNotification); + overlap = overlaps[0].getFloatValue(); + } + else if (overlap > overlaps[overlaps.size()-1].getFloatValue()) + { + cb->setSelectedId(overlaps.size(),dontSendNotification); + overlap = overlaps[overlaps.size()-1].getFloatValue(); + } + else + { + cb->setText(String(overlap),dontSendNotification); + } + canvas->channelOverlapFactor= overlap; + canvas->resized(); + } + else + { + if (selectedSpread == 0) + cb->setText(selectedSpreadValue,dontSendNotification); + else + cb->setSelectedId(selectedSpread,dontSendNotification); + } + } + selectedSpread = cb->getSelectedId(); + selectedSpreadValue = cb->getText(); + lfpDisplay->setChannelHeight( lfpDisplay->getChannelHeight()); + canvas->redraw(); + //std::cout << "Setting spread to " << spreads[cb->getSelectedId()-1].getFloatValue() << std::endl; + } + + else if (cb == colorGroupingSelection) + { + // set color grouping here + lfpDisplay->setColorGrouping(colorGroupings[cb->getSelectedId()-1].getIntValue());// so that channel colors get re-assigned + canvas->redraw(); + } + + timescale->setTimebase(canvas->timebase); +} + + +void LfpDisplayOptions::sliderValueChanged(Slider* sl) +{ + if (sl == brightnessSliderA) + canvas->histogramParameterA = sl->getValue(); + + if (sl == brightnessSliderB) + canvas->histogramParameterB = sl->getValue(); + + + canvas->fullredraw=true; + //repaint(); + canvas->refresh(); + +} + +void LfpDisplayOptions::sliderEvent(Slider* sl) {} + +DataChannel::DataChannelTypes LfpDisplayOptions::getChannelType(int n) +{ + if (n < processor->getNumInputs()) + return processor->getDataChannel(n)->getChannelType(); + else + return DataChannel::HEADSTAGE_CHANNEL; +} + +DataChannel::DataChannelTypes LfpDisplayOptions::getSelectedType() +{ + return selectedChannelType; +} + +void LfpDisplayOptions::setSelectedType(DataChannel::DataChannelTypes type, bool toggleButton) +{ + if (selectedChannelType == type) + return; //Nothing to do here + selectedChannelType = type; + rangeSelection->clear(dontSendNotification); + rangeSelection->addItemList(voltageRanges[type],1); + + int id = selectedVoltageRange[type]; + if (id) + rangeSelection->setSelectedId(id,sendNotification); + else + rangeSelection->setText(selectedVoltageRangeValues[selectedChannelType],dontSendNotification); + + repaint(5,getHeight()-55,300,100); + + if (toggleButton) + typeButtons[type]->setToggleState(true,dontSendNotification); +} + +String LfpDisplayOptions::getTypeName(DataChannel::DataChannelTypes type) +{ + return typeNames[type]; +} + +int LfpDisplayOptions::getRangeStep(DataChannel::DataChannelTypes type) +{ + return rangeSteps[type]; +} + + + +void LfpDisplayOptions::saveParameters(XmlElement* xml) +{ + // TODO: (kelly) add savers for: + // - channel reverse + // - channel zoom slider + // - channel display skip + std::cout << "Saving lfp display params" << std::endl; + + XmlElement* xmlNode = xml->createNewChildElement("LFPDISPLAY"); + + lfpDisplay->reactivateChannels(); + + xmlNode->setAttribute("Range",selectedVoltageRangeValues[0]+","+selectedVoltageRangeValues[1]+ + ","+selectedVoltageRangeValues[2]); + xmlNode->setAttribute("Timebase",timebaseSelection->getText()); + xmlNode->setAttribute("Spread",spreadSelection->getText()); + xmlNode->setAttribute("colorGrouping",colorGroupingSelection->getSelectedId()); + xmlNode->setAttribute("isInverted",invertInputButton->getToggleState()); + xmlNode->setAttribute("drawMethod",drawMethodButton->getToggleState()); + + int eventButtonState = 0; + + for (int i = 0; i < 8; i++) + { + if (lfpDisplay->eventDisplayEnabled[i]) + { + eventButtonState += (1 << i); + } + } + + lfpDisplay->reactivateChannels(); + + xmlNode->setAttribute("EventButtonState", eventButtonState); + + String channelDisplayState = ""; + + for (int i = 0; i < canvas->nChans; i++) + { + if (lfpDisplay->getEnabledState(i)) + { + channelDisplayState += "1"; + } + else + { + channelDisplayState += "0"; + } + //std::cout << channelDisplayState; + } + + //std::cout << std::endl; + + + xmlNode->setAttribute("ChannelDisplayState", channelDisplayState); + + xmlNode->setAttribute("ScrollX",canvas->viewport->getViewPositionX()); + xmlNode->setAttribute("ScrollY",canvas->viewport->getViewPositionY()); +} + + +void LfpDisplayOptions::loadParameters(XmlElement* xml) +{ + // TODO: (kelly) add loaders for: + // - channel reverse + // - channel zoom slider + // - channel display skip + forEachXmlChildElement(*xml, xmlNode) + { + if (xmlNode->hasTagName("LFPDISPLAY")) + { + StringArray ranges; + ranges.addTokens(xmlNode->getStringAttribute("Range"),",",String::empty); + selectedVoltageRangeValues[0] = ranges[0]; + selectedVoltageRangeValues[1] = ranges[1]; + selectedVoltageRangeValues[2] = ranges[2]; + selectedVoltageRange[0] = voltageRanges[0].indexOf(ranges[0])+1; + selectedVoltageRange[1] = voltageRanges[1].indexOf(ranges[1])+1; + selectedVoltageRange[2] = voltageRanges[2].indexOf(ranges[2])+1; + rangeSelection->setText(ranges[0]); + + timebaseSelection->setText(xmlNode->getStringAttribute("Timebase")); + spreadSelection->setText(xmlNode->getStringAttribute("Spread")); + if (xmlNode->hasAttribute("colorGrouping")) + { + colorGroupingSelection->setSelectedId(xmlNode->getIntAttribute("colorGrouping")); + } + else + { + colorGroupingSelection->setSelectedId(1); + } + + invertInputButton->setToggleState(xmlNode->getBoolAttribute("isInverted", true), sendNotification); + + drawMethodButton->setToggleState(xmlNode->getBoolAttribute("drawMethod", true), sendNotification); + + canvas->viewport->setViewPosition(xmlNode->getIntAttribute("ScrollX"), + xmlNode->getIntAttribute("ScrollY")); + + int eventButtonState = xmlNode->getIntAttribute("EventButtonState"); + + for (int i = 0; i < 8; i++) + { + lfpDisplay->eventDisplayEnabled[i] = (eventButtonState >> i) & 1; + + eventDisplayInterfaces[i]->checkEnabledState(); + } + + String channelDisplayState = xmlNode->getStringAttribute("ChannelDisplayState"); + + for (int i = 0; i < channelDisplayState.length(); i++) + { + + if (channelDisplayState.substring(i,i+1).equalsIgnoreCase("1")) + { + //std::cout << "LfpDisplayCanvas enabling channel " << i << std::endl; + //lfpDisplay->enableChannel(true, i); + canvas->isChannelEnabled.set(i,true); //lfpDisplay->enableChannel(true, i); + } + else + { + //lfpDisplay->enableChannel(false, i); + canvas->isChannelEnabled.set(i,false); + } + + + } + } + } + +} + + +#pragma mark - LfpTimescale - +// ------------------------------------------------------------- + +LfpTimescale::LfpTimescale(LfpDisplayCanvas* c, LfpDisplay* lfpDisplay) + : canvas(c) + , lfpDisplay(lfpDisplay) +{ + + font = Font("Default", 16, Font::plain); +} + +LfpTimescale::~LfpTimescale() +{ + +} + +void LfpTimescale::paint(Graphics& g) +{ + + g.setFont(font); + + g.setColour(Colour(100,100,100)); + + const String timeScaleUnitLabel = (timebase >= 2)?("s:"):("ms:"); + g.drawText(timeScaleUnitLabel,5,0,100,getHeight(),Justification::left, false); + + const int steps = labels.size() + 1; + for (int i = 0; i < steps; i++) + { + + // TODO: (kelly) added an extra spatial dimension to the timeline ticks, may be overkill + if (i == 0) + { + g.drawLine(1, + 0, + 1, + getHeight(), + 3.0f); + } + if (i != 0 && i % 4 == 0) + { + g.drawLine(getWidth()/steps*i, + 0, + getWidth()/steps*i, + getHeight(), + 3.0f); + } + else if (i != 0 && i % 2 == 0) + { + g.drawLine(getWidth()/steps*i, + getHeight(), + getWidth()/steps*i, + getHeight() / 2, + 3.0f); + } + else + { + g.drawLine(getWidth()/steps*i, + getHeight(), + getWidth()/steps*i, + 3 * getHeight()/4, + 2.0f); + } + + + if (i != 0 && i % 2 == 0) + g.drawText(labels[i-1],getWidth()/steps*i+3,0,100,getHeight(),Justification::left, false); + + } +} + +void LfpTimescale::mouseUp(const MouseEvent &e) +{ + if (e.mods.isLeftButtonDown()) + { + lfpDisplay->trackZoomInfo.isScrollingX = false; + } +} + +void LfpTimescale::resized() +{ + setTimebase(timebase); +} + +void LfpTimescale::mouseDrag(const juce::MouseEvent &e) +{ + if (e.mods.isLeftButtonDown()) // double check that we initiate only for left click and hold + { + if (e.mods.isCommandDown()) // CTRL + drag -> change channel spacing + { + // init state in our track zooming info struct + if (!lfpDisplay->trackZoomInfo.isScrollingX) + { + lfpDisplay->trackZoomInfo.isScrollingX = true; + lfpDisplay->trackZoomInfo.timescaleStartScale = timebase; + } + + float timescale = lfpDisplay->trackZoomInfo.timescaleStartScale; + float dTimescale=0; + int dragDeltaX = (e.getScreenPosition().getX() - e.getMouseDownScreenX()); // invert so drag up -> scale up + +// std::cout << dragDeltaX << std::endl; + if (dragDeltaX > 0) + { + dTimescale = 0.01 * dragDeltaX; + } + else + { + // TODO: (kelly) change this to scale appropriately for -dragDeltaX + if (timescale > 0.25) + dTimescale = 0.01 * dragDeltaX; + } + + if (timescale >= 1) // accelerate scrolling for large ranges + dTimescale *= 4; + + if (timescale >= 5) + dTimescale *= 4; + + if (timescale >= 10) + dTimescale *= 4; + + // round dTimescale to the nearest 0.005 sec + dTimescale = ((dTimescale + (0.005/2)) / 0.005) * 0.005; + + float newTimescale = timescale+dTimescale; + + if (newTimescale < 0.25) newTimescale = 0.250; + if (newTimescale > 20) newTimescale = 20; + + // don't bother updating if the new timebase is the same as the old (if clipped, for example) + if (timescale != newTimescale) + { + lfpDisplay->options->setTimebaseAndSelectionText(newTimescale); + setTimebase(canvas->timebase); + } + } + } +} + +void LfpTimescale::setTimebase(float t) +{ + timebase = t; + + labels.clear(); + + const int minWidth = 60; + labelIncrement = 0.025f; + + + while (getWidth() != 0 && // setTimebase can be called before LfpTimescale has width + getWidth() / (timebase / labelIncrement) < minWidth) // so, if width is 0 then don't iterate for scale factor + { +// std::cout << getWidth() / (timebase / labelIncrement) << " is smaller than minimum width, calculating new step size" << std::endl; + if (labelIncrement < 0.2) + labelIncrement *= 2; + else + labelIncrement += 0.2f; + } + + for (float i = labelIncrement; i < timebase; i += labelIncrement) + { + String labelString = String(i * ((timebase >= 2)?(1):(1000.0f))); + labels.add(labelString.substring(0,6)); + } + + repaint(); + +} + + + +#pragma mark - LfpDisplay - +// --------------------------------------------------------------- + +LfpDisplay::LfpDisplay(LfpDisplayCanvas* c, Viewport* v) + : singleChan(-1) + , canvas(c) + , viewport(v) + , channelsReversed(false) + , displaySkipAmt(0) + , m_SpikeRasterPlottingFlag(false) +{ + perPixelPlotter = new PerPixelBitmapPlotter(this); + supersampledPlotter = new SupersampledBitmapPlotter(this); + +// colorScheme = new LfpDefaultColourScheme(); + colourSchemeList.add(new LfpDefaultColourScheme(this, canvas)); + colourSchemeList.add(new LfpMonochromaticColourScheme(this, canvas)); + colourSchemeList.add(new LfpGradientColourScheme(this, canvas)); + + activeColourScheme = 0; + + + plotter = perPixelPlotter; + m_MedianOffsetPlottingFlag = false; + + totalHeight = 0; + colorGrouping=1; + + range[0] = 1000; + range[1] = 500; + range[2] = 500000; + + addMouseListener(this, true); + + // hue cycle + //for (int i = 0; i < 15; i++) + //{ + // channelColours.add(Colour(float(sin((3.14/2)*(float(i)/15))),float(1.0),float(1),float(1.0))); + //} + +// setBufferedToImage(true); // TODO: (kelly) test + + backgroundColour = Colour(0,18,43); + + //hand-built palette + channelColours.add(Colour(224,185,36)); + channelColours.add(Colour(214,210,182)); + channelColours.add(Colour(243,119,33)); + channelColours.add(Colour(186,157,168)); + channelColours.add(Colour(237,37,36)); + channelColours.add(Colour(179,122,79)); + channelColours.add(Colour(217,46,171)); + channelColours.add(Colour(217, 139,196)); + channelColours.add(Colour(101,31,255)); + channelColours.add(Colour(141,111,181)); + channelColours.add(Colour(48,117,255)); + channelColours.add(Colour(184,198,224)); + channelColours.add(Colour(116,227,156)); + channelColours.add(Colour(150,158,155)); + channelColours.add(Colour(82,173,0)); + channelColours.add(Colour(125,99,32)); + + isPaused=false; + +} + +LfpDisplay::~LfpDisplay() +{ +// deleteAllChildren(); +} + + + +int LfpDisplay::getNumChannels() +{ + return numChans; +} + + + +int LfpDisplay::getColorGrouping() +{ + return colorGrouping; +} + +void LfpDisplay::setColorGrouping(int i) +{ + colorGrouping=i; + getColourSchemePtr()->setColourGrouping(i); + setColors(); // so that channel colors get re-assigned + +} + +LfpChannelColourScheme * LfpDisplay::getColourSchemePtr() +{ + return colourSchemeList[activeColourScheme]; +} + +void LfpDisplay::setNumChannels(int numChannels) +{ + numChans = numChannels; + + +// deleteAllChildren(); + removeAllChildren(); + + channels.clear(); + channelInfo.clear(); + drawableChannels.clear(); + + totalHeight = 0; + cachedDisplayChannelHeight = canvas->getChannelHeight(); + + if (numChans > 0) + { + for (int i = 0; i < numChans; i++) + { + //std::cout << "Adding new display for channel " << i << std::endl; + + LfpChannelDisplay* lfpChan = new LfpChannelDisplay(canvas, this, options, i); + + //lfpChan->setColour(channelColours[i % channelColours.size()]); + lfpChan->setRange(range[options->getChannelType(i)]); + lfpChan->setChannelHeight(canvas->getChannelHeight()); + + addAndMakeVisible(lfpChan); + + channels.add(lfpChan); + + LfpChannelDisplayInfo* lfpInfo = new LfpChannelDisplayInfo(canvas, this, options, i); + + //lfpInfo->setColour(channelColours[i % channelColours.size()]); + lfpInfo->setRange(range[options->getChannelType(i)]); + lfpInfo->setChannelHeight(canvas->getChannelHeight()); + lfpInfo->setSubprocessorIdx(canvas->getChannelSubprocessorIdx(i)); + + addAndMakeVisible(lfpInfo); + + channelInfo.add(lfpInfo); + + drawableChannels.add(LfpChannelTrack{ + lfpChan, + lfpInfo + }); + + savedChannelState.add(true); + + totalHeight += lfpChan->getChannelHeight(); + + } + + } + + setColors(); + + + std::cout << "TOTAL HEIGHT = " << totalHeight << std::endl; + +} + +void LfpDisplay::setColors() +{ + for (int i = 0; i < drawableChannels.size(); i++) + { + +// channels[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); +// channelInfo[i]->setColour(channelColours[(int(i/colorGrouping)+1) % channelColours.size()]); + drawableChannels[i].channel->setColour(getColourSchemePtr()->getColourForIndex(i)); + drawableChannels[i].channelInfo->setColour(getColourSchemePtr()->getColourForIndex(i)); + } + +} + +void LfpDisplay::setActiveColourSchemeIdx(int index) +{ + activeColourScheme = index; +} + +int LfpDisplay::getActiveColourSchemeIdx() +{ + return activeColourScheme; +} + +int LfpDisplay::getNumColourSchemes() +{ + return colourSchemeList.size(); +} + +StringArray LfpDisplay::getColourSchemeNameArray() +{ + StringArray nameList; + for (auto scheme : colourSchemeList) + nameList.add(scheme->getName()); + + return nameList; +} + +int LfpDisplay::getTotalHeight() +{ + return totalHeight; +} + +void LfpDisplay::resized() +{ + int totalHeight = 0; + + for (int i = 0; i < drawableChannels.size(); i++) + { + + LfpChannelDisplay* disp = drawableChannels[i].channel; + + if (disp->getHidden()) continue; + + disp->setBounds(canvas->leftmargin, + totalHeight-(disp->getChannelOverlap()*canvas->channelOverlapFactor)/2, + getWidth(), + disp->getChannelHeight()+(disp->getChannelOverlap()*canvas->channelOverlapFactor)); + + disp-> resized(); + + LfpChannelDisplayInfo* info = drawableChannels[i].channelInfo; + + info->setBounds(0, + totalHeight-disp->getChannelHeight() + (disp->getChannelOverlap()*canvas->channelOverlapFactor)/4.0, + canvas->leftmargin + 50, + disp->getChannelHeight()); + + totalHeight += disp->getChannelHeight(); + + } + + canvas->fullredraw = true; //issue full redraw + if (singleChan != -1) + viewport->setViewPosition(Point(0,singleChan*getChannelHeight())); + + + + lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); + + //inititalize black background + Graphics gLfpChannelBitmap(lfpChannelBitmap); + gLfpChannelBitmap.setColour(Colour(0,0,0)); //background color + gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); + + + canvas->fullredraw = true; + + refresh(); + // std::cout << "Total height: " << totalHeight << std::endl; + +} + +void LfpDisplay::paint(Graphics& g) +{ + + g.drawImageAt(lfpChannelBitmap, canvas->leftmargin,0); + +} + + +void LfpDisplay::refresh() +{ + // Ensure the lfpChannelBitmap has been initialized + if (lfpChannelBitmap.isNull()) + { + resized(); + } + + // X-bounds of this update + int fillfrom = canvas->lastScreenBufferIndex[0]; + int fillto = (canvas->screenBufferIndex[0]); + + if (fillfrom<0){fillfrom=0;}; + if (fillto>lfpChannelBitmap.getWidth()){fillto=lfpChannelBitmap.getWidth();}; + + int topBorder = viewport->getViewPositionY(); + int bottomBorder = viewport->getViewHeight() + topBorder; + + // clear appropriate section of the bitmap -- + // we need to do this before each channel draws its new section of data into lfpChannelBitmap + Graphics gLfpChannelBitmap(lfpChannelBitmap); + gLfpChannelBitmap.setColour(backgroundColour); //background color + + if (canvas->fullredraw) + { + gLfpChannelBitmap.fillRect(0,0, getWidth(), getHeight()); + } else { + gLfpChannelBitmap.setColour(backgroundColour); //background color + + gLfpChannelBitmap.fillRect(fillfrom,0, (fillto-fillfrom)+1, getHeight()); + }; + + + for (int i = 0; i < numChans; i++) +// for (int i = 0; i < drawableChannels.size(); ++i) + { + + int componentTop = channels[i]->getY(); + int componentBottom = channels[i]->getHeight() + componentTop; + + if ((topBorder <= componentBottom && bottomBorder >= componentTop)) // only draw things that are visible + { + if (canvas->fullredraw) + { + channels[i]->fullredraw = true; + + channels[i]->pxPaint(); + channelInfo[i]->repaint(); + + } + else + { + channels[i]->pxPaint(); // draws to lfpChannelBitmap + + // it's not clear why, but apparently because the pxPaint() in a child component of LfpDisplay, we also need to issue repaint() calls for each channel, even though there's nothin to repaint there. Otherwise, the repaint call in LfpDisplay::refresh(), a few lines down, lags behind the update line by ~60 px. This could ahev something to do with teh reopaint message passing in juce. In any case, this seemingly redundant repaint here seems to fix the issue. + + // we redraw from 0 to +2 (px) relative to the real redraw window, the +1 draws the vertical update line + channels[i]->repaint(fillfrom, 0, (fillto-fillfrom)+2, channels[i]->getHeight()); + + + } + //std::cout << i << std::endl; + } + + } + + if (fillfrom == 0 && singleChan != -1) + { + channelInfo[singleChan]->repaint(); + } + + + if (canvas->fullredraw) + { + repaint(0,topBorder,getWidth(),bottomBorder-topBorder); + }else{ + //repaint(fillfrom, topBorder, (fillto-fillfrom)+1, bottomBorder-topBorder); // doesntb seem to be needed and results in duplicate repaint calls + } + + canvas->fullredraw = false; +} + + + +void LfpDisplay::setRange(float r, DataChannel::DataChannelTypes type) +{ + range[type] = r; + + if (channels.size() > 0) + { + + for (int i = 0; i < numChans; i++) + { + if (channels[i]->getType() == type) + channels[i]->setRange(range[type]); + } + canvas->fullredraw = true; //issue full redraw + } +} + +int LfpDisplay::getRange() +{ + return getRange(options->getSelectedType()); +} + +int LfpDisplay::getRange(DataChannel::DataChannelTypes type) +{ + for (int i=0; i < numChans; i++) + { + if (channels[i]->getType() == type) + return channels[i]->getRange(); + } + return 0; +} + + +void LfpDisplay::setChannelHeight(int r, bool resetSingle) +{ + if (!getSingleChannelState()) cachedDisplayChannelHeight = r; + + for (int i = 0; i < numChans; i++) + { + channels[i]->setChannelHeight(r); + channelInfo[i]->setChannelHeight(r); + } + if (resetSingle && singleChan != -1) + { + //std::cout << "width " << getWidth() << " numchans " << numChans << " height " << getChannelHeight() << std::endl; + setSize(getWidth(),drawableChannels.size()*getChannelHeight()); + viewport->setScrollBarsShown(true,false); + viewport->setViewPosition(Point(0,singleChan*r)); + singleChan = -1; + for (int n = 0; n < numChans; n++) + { + channelInfo[n]->setEnabledState(savedChannelState[n]); + } + } + + resized(); + +} + +void LfpDisplay::setInputInverted(bool isInverted) +{ + + for (int i = 0; i < numChans; i++) + { + channels[i]->setInputInverted(isInverted); + } + + resized(); + +} + +void LfpDisplay::setDrawMethod(bool isDrawMethod) +{ + for (int i = 0; i < numChans; i++) + { + channels[i]->setDrawMethod(isDrawMethod); + } + + if (isDrawMethod) + { + plotter = supersampledPlotter; + } + else + { + plotter = perPixelPlotter; + } + + resized(); + +} + + +int LfpDisplay::getChannelHeight() +{ +// return cachedDisplayChannelHeight; + return drawableChannels[0].channel->getChannelHeight(); +// return channels[0]->getChannelHeight(); +} + +void LfpDisplay::cacheNewChannelHeight(int r) +{ + cachedDisplayChannelHeight = r; +} + +bool LfpDisplay::getChannelsReversed() +{ + return channelsReversed; +} + +void LfpDisplay::setChannelsReversed(bool state) +{ + if (state == channelsReversed) return; // bail early, in case bookkeeping error + + channelsReversed = state; + + if (getSingleChannelState()) return; // don't reverse if single channel + + // reverse channels that are currently in drawableChannels + for (int i = 0, j = drawableChannels.size() - 1, len = drawableChannels.size()/2; + i < len; + i++, j--) + { + // remove channel and info components from front and back + // moving toward middle + removeChildComponent(drawableChannels[i].channel); + removeChildComponent(drawableChannels[j].channel); + removeChildComponent(drawableChannels[i].channelInfo); + removeChildComponent(drawableChannels[j].channelInfo); + + // swap front and back, moving towards middle + drawableChannels.swap(i, j); + + // also swap coords + { + const auto channelBoundsA = drawableChannels[i].channel->getBounds(); + const auto channelInfoBoundsA = drawableChannels[i].channelInfo->getBounds(); + + drawableChannels[i].channel->setBounds(drawableChannels[j].channel->getBounds()); + drawableChannels[i].channelInfo->setBounds(drawableChannels[j].channelInfo->getBounds()); + drawableChannels[j].channel->setBounds(channelBoundsA); + drawableChannels[j].channelInfo->setBounds(channelInfoBoundsA); + } + } + + + // remove middle component if odd number of channels + if (drawableChannels.size() % 2 != 0) + { + removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channel); + removeChildComponent(drawableChannels[drawableChannels.size()/2+1].channelInfo); + } + + // add the channels and channel info again + for (int i = 0, len = drawableChannels.size(); i < len; i++) + { + + if (!drawableChannels[i].channel->getHidden()) + { + addAndMakeVisible(drawableChannels[i].channel); + addAndMakeVisible(drawableChannels[i].channelInfo); + } + + // flag this to update the waveforms + drawableChannels[i].channel->fullredraw = true; + } + + // necessary to overwrite lfpChannelBitmap's display + refresh(); +} + +int LfpDisplay::getChannelDisplaySkipAmount() +{ + return displaySkipAmt; +} + +void LfpDisplay::setChannelDisplaySkipAmount(int skipAmt) +{ + displaySkipAmt = skipAmt; + + if (!getSingleChannelState()) + rebuildDrawableChannelsList(); + + canvas->redraw(); +} + +bool LfpDisplay::getMedianOffsetPlotting() +{ + return m_MedianOffsetPlottingFlag; +} + +void LfpDisplay::setMedianOffsetPlotting(bool isEnabled) +{ + m_MedianOffsetPlottingFlag = isEnabled; +} + +bool LfpDisplay::getSpikeRasterPlotting() +{ + return m_SpikeRasterPlottingFlag; +} + +void LfpDisplay::setSpikeRasterPlotting(bool isEnabled) +{ + m_SpikeRasterPlottingFlag = isEnabled; +} + +float LfpDisplay::getSpikeRasterThreshold() +{ + return m_SpikeRasterThreshold; +} + +void LfpDisplay::setSpikeRasterThreshold(float thresh) +{ + m_SpikeRasterThreshold = thresh; +} + +void LfpDisplay::mouseWheelMove(const MouseEvent& e, const MouseWheelDetails& wheel) +{ + + //std::cout << "Mouse wheel " << e.mods.isCommandDown() << " " << wheel.deltaY << std::endl; + //TODO Changing ranges with the wheel is currently broken. With multiple ranges, most + //of the wheel range code needs updating + + + if (e.mods.isCommandDown() && singleChan == -1) // CTRL + scroll wheel -> change channel spacing + { + int h = getChannelHeight(); + int hdiff=0; + + // std::cout << wheel.deltaY << std::endl; + + if (wheel.deltaY > 0) + { + hdiff = 2; + } + else + { + if (h > 5) + hdiff = -2; + } + + if (abs(h) > 100) // accelerate scrolling for large ranges + hdiff *= 3; + + int newHeight = h+hdiff; + + // constrain the spread resizing to max and min values; + if (newHeight < trackZoomInfo.minZoomHeight) + { + newHeight = trackZoomInfo.minZoomHeight; + hdiff = 0; + } + else if (newHeight > trackZoomInfo.maxZoomHeight) + { + newHeight = trackZoomInfo.maxZoomHeight; + hdiff = 0; + } + + setChannelHeight(newHeight); + int oldX=viewport->getViewPositionX(); + int oldY=viewport->getViewPositionY(); + + setBounds(0,0,getWidth()-0, getChannelHeight()*drawableChannels.size()); // update height so that the scrollbar is correct + + int mouseY=e.getMouseDownY(); // should be y pos relative to inner viewport (0,0) + int scrollBy = (mouseY/h)*hdiff*2;// compensate for motion of point under current mouse position + viewport->setViewPosition(oldX,oldY+scrollBy); // set back to previous position plus offset + + options->setSpreadSelection(newHeight); // update combobox + + canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw + } + else + { + if (e.mods.isAltDown()) // ALT + scroll wheel -> change channel range (was SHIFT but that clamps wheel.deltaY to 0 on OSX for some reason..) + { + int h = getRange(); + + + int step = options->getRangeStep(options->getSelectedType()); + + // std::cout << wheel.deltaY << std::endl; + + if (wheel.deltaY > 0) + { + setRange(h+step,options->getSelectedType()); + } + else + { + if (h > step+1) + setRange(h-step,options->getSelectedType()); + } + + options->setRangeSelection(h); // update combobox + canvas->fullredraw = true; //issue full redraw - scrolling without modifier doesnt require a full redraw + + } + else // just scroll + { + // passes the event up to the viewport so the screen scrolls + if (viewport != nullptr && e.eventComponent == this) // passes only if it's not a listening event + viewport->mouseWheelMove(e.getEventRelativeTo(canvas), wheel); + + } + + + } + //refresh(); // doesn't seem to be needed now that channels daraw to bitmap + +} + +void LfpDisplay::toggleSingleChannel(int chan) +{ + if (!getSingleChannelState()) + { + + std::cout << "Single channel on (" << chan << ")" << std::endl; + singleChan = chan; + + int newHeight = viewport->getHeight(); + LfpChannelTrack lfpChannelTrack{drawableChannels[chan].channel, drawableChannels[chan].channelInfo}; + lfpChannelTrack.channelInfo->setEnabledState(true); + lfpChannelTrack.channelInfo->setSingleChannelState(true); + + removeAllChildren(); + + // disable unused channels + for (int i = 0; i < getNumChannels(); i++) + { + if (i != chan) + { + drawableChannels[i].channel->setEnabledState(false); + } + } + + // update drawableChannels, give only the single channel to focus on + drawableChannels.clearQuick(); + drawableChannels.add(lfpChannelTrack); + + addAndMakeVisible(lfpChannelTrack.channel); + addAndMakeVisible(lfpChannelTrack.channelInfo); + + // set channel height and position (so that we allocate the smallest + // necessary image size for drawing) + setChannelHeight(newHeight, false); + + lfpChannelTrack.channel->setTopLeftPosition(canvas->leftmargin, 0); + lfpChannelTrack.channelInfo->setTopLeftPosition(0, 0); + setSize(getWidth(), getChannelHeight()); + + viewport->setViewPosition(0, 0); + + } +// else if (chan == singleChan || chan == -2) + else + { + std::cout << "Single channel off" << std::endl; + for (int n = 0; n < numChans; n++) + { + channelInfo[n]->setSingleChannelState(false); + } + + setChannelHeight(cachedDisplayChannelHeight); + + reactivateChannels(); + rebuildDrawableChannelsList(); + } +} + +void LfpDisplay::reactivateChannels() +{ + + for (int n = 0; n < channels.size(); n++) + setEnabledState(savedChannelState[n], n); + +} + +void LfpDisplay::rebuildDrawableChannelsList() +{ + + if (displaySkipAmt != 0) removeAllChildren(); // start with clean slate + + Array channelsToDraw; + drawableChannels = Array(); + + // iterate over all channels and select drawable ones + for (int i = 0, drawableChannelNum = 0; i < channels.size(); i++) + { +// std::cout << "\tchannel " << i << " has subprocessor index of " << channelInfo[i]->getSubprocessorIdx() << std::endl; + // if channel[i] is not sourced from the correct subprocessor, then hide it and continue + //if (channelInfo[i]->getSubprocessorIdx() != getDisplayedSubprocessor()) + //{ + // channels[i]->setHidden(true); + // channelInfo[i]->setHidden(true); + // continue; + //} + + //std::cout << "Checking for hidden channels" << std::endl; + if (displaySkipAmt == 0 || (i % displaySkipAmt == 0)) // no skips, add all channels + { + channels[i]->setHidden(false); + channelInfo[i]->setHidden(false); + + channelInfo[i]->setDrawableChannelNumber(drawableChannelNum++); + channelInfo[i]->resized(); // to update the conditional drawing of enableButton and channel num + + channelsToDraw.add(LfpDisplay::LfpChannelTrack{ + channels[i], + channelInfo[i] + }); + + addAndMakeVisible(channels[i]); + addAndMakeVisible(channelInfo[i]); + } + else // skip some channels + { +// if (i % (displaySkipAmt) == 0) // add these channels +// { +// channels[i]->setHidden(false); +// channelInfo[i]->setHidden(false); +// +// channelsToDraw.add(LfpDisplay::LfpChannelTrack{ +// channels[i], +// channelInfo[i] +// }); +// +// addAndMakeVisible(channels[i]); +// addAndMakeVisible(channelInfo[i]); +// } +// else // but not these +// { + channels[i]->setHidden(true); + channelInfo[i]->setHidden(true); + + removeChildComponent(channels[i]); + removeChildComponent(channelInfo[i]); +// } + } + } + + // check if channels should be added to drawableChannels in reverse + if (getChannelsReversed()) + { + for (int i = channelsToDraw.size() - 1; i >= 0; --i) + { + drawableChannels.add(channelsToDraw[i]); + } + } + else + { + for (int i = 0; i < channelsToDraw.size(); ++i) + { + drawableChannels.add(channelsToDraw[i]); + } + } + + // this guards against an exception where the editor sets the drawable samplerate + // before the lfpDisplay is fully initialized + if (getHeight() > 0 && getWidth() > 0) + { + canvas->resizeToChannels(); + } + + setColors(); +} + +LfpBitmapPlotter * const LfpDisplay::getPlotterPtr() const +{ + return plotter; +} + +bool LfpDisplay::getSingleChannelState() +{ + //if (singleChan < 0) return false; + //else return true; + return singleChan >= 0; +} + + +void LfpDisplay::mouseDown(const MouseEvent& event) +{ + if (drawableChannels.isEmpty()) + { + return; + } + + //int y = event.getMouseDownY(); //relative to each channel pos + MouseEvent canvasevent = event.getEventRelativeTo(viewport); + int y = canvasevent.getMouseDownY() + viewport->getViewPositionY(); // need to account for scrolling + int x = canvasevent.getMouseDownX(); + + int dist = 0; + int mindist = 10000; + int closest = 5; + for (int n = 0; n < drawableChannels.size(); n++) // select closest instead of relying on eventComponent + { + drawableChannels[n].channel->deselect(); + + int cpos = (drawableChannels[n].channel->getY() + (drawableChannels[n].channel->getHeight()/2)); + dist = int(abs(y - cpos)); + +// std::cout << "Mouse down at " << y << " pos is "<< cpos << " n: " << n << " dist " << dist << std::endl; + + if (dist < mindist) + { + mindist = dist-1; + closest = n; + } + } + + drawableChannels[closest].channel->select(); + options->setSelectedType(drawableChannels[closest].channel->getType()); + + if (event.mods.isRightButtonDown()) { // if right click + PopupMenu channelMenu = channels[closest]->getOptions(); + const int result = channelMenu.show(); + drawableChannels[closest].channel->changeParameter(result); + } + else // if left click + { +// if (singleChan != -1) + if (event.getNumberOfClicks() == 2) { + toggleSingleChannel(closest); + } + + if (getSingleChannelState()) + { + + // std::cout << "singleChan = " << singleChan << " " << y << " " << drawableChannels[0].channel->getHeight() << " " << getRange() << std::endl; + //channelInfo[singleChan]->updateXY( + drawableChannels[0].channelInfo->updateXY( + float(x)/getWidth()*canvas->timebase, + (-(float(y)-viewport->getViewPositionY())/viewport->getViewHeight()*float(getRange()))+float(getRange()/2) + ); + } + } + +// canvas->fullredraw = true;//issue full redraw + +// refresh(); + +} + + +bool LfpDisplay::setEventDisplayState(int ch, bool state) +{ + eventDisplayEnabled[ch] = state; + return eventDisplayEnabled[ch]; +} + + +bool LfpDisplay::getEventDisplayState(int ch) +{ + return eventDisplayEnabled[ch]; +} + + +void LfpDisplay::setEnabledState(bool state, int chan, bool updateSaved) +{ + if (chan < numChans) + { + channels[chan]->setEnabledState(state); + channelInfo[chan]->setEnabledState(state); + + if (updateSaved) + savedChannelState.set(chan, state); + + canvas->isChannelEnabled.set(chan, state); + } +} + +bool LfpDisplay::getEnabledState(int chan) +{ + if (chan < numChans) + { + return channels[chan]->getEnabledState(); + } + + return false; +} + + + +#pragma mark - LfpChannelDisplay - +// ------------------------------------------------------------------ + +LfpChannelDisplay::LfpChannelDisplay(LfpDisplayCanvas* c, LfpDisplay* d, LfpDisplayOptions* o, int channelNumber) + : canvas(c) + , display(d) + , options(o) + , isSelected(false) + , chan(channelNumber) + , name("") + , drawableChan(channelNumber) + , channelOverlap(300) + , channelHeight(30) + , range(250.0f) + , isEnabled(true) + , inputInverted(false) + , canBeInverted(true) + , drawMethod(false) + , isHidden(false) +{ + + + name = String(channelNumber+1); // default is to make the channelNumber the name + + + channelHeightFloat = (float) channelHeight; + + channelFont = Font("Default", channelHeight*0.6, Font::plain); + + lineColour = Colour(255,255,255); + + type = options->getChannelType(channelNumber); + typeStr = options->getTypeName(type); + +} + +LfpChannelDisplay::~LfpChannelDisplay() +{ + +} + +void LfpChannelDisplay::resized() +{ + // all of this will likely need to be moved into the sharedLfpDisplay image in the lfpDisplay, not here + // now that the complete height is know, the sharedLfpDisplay image that we'll draw the pixel-wise lfp plot to needs to be resized + //lfpChannelBitmap = Image(Image::ARGB, getWidth(), getHeight(), false); +} + + +void LfpChannelDisplay::updateType() +{ + type = options->getChannelType(chan); + typeStr = options->getTypeName(type); +} + +void LfpChannelDisplay::setEnabledState(bool state) +{ + + //if (state) + //std::cout << "Setting channel " << name << " to true." << std::endl; + //else + //std::cout << "Setting channel " << name << " to false." << std::endl; + + isEnabled = state; + +} + +void LfpChannelDisplay::setHidden(bool isHidden_) +{ + isHidden = isHidden_; + isEnabled = !isHidden; +} + +void LfpChannelDisplay::pxPaint() +{ + if (!isEnabled) return; // return early if THIS display is not enabled + + Image::BitmapData bdLfpChannelBitmap(display->lfpChannelBitmap, 0,0, display->lfpChannelBitmap.getWidth(), display->lfpChannelBitmap.getHeight()); + + int center = getHeight()/2; + + // max and min of channel in absolute px coords for event displays etc - actual data might be drawn outside of this range + int jfrom_wholechannel= (int) (getY()+center-channelHeight/2)+1 +0 ; + int jto_wholechannel= (int) (getY()+center+channelHeight/2) -0; + + //int jfrom_wholechannel_almost= (int) (getY()+center-channelHeight/3)+1 +0 ; // a bit less tall, for saturation warnings + //int jto_wholechannel_almost= (int) (getY()+center+channelHeight/3) -0; + + // max and min of channel, this is the range where actual data is drawn + int jfrom_wholechannel_clip= (int) (getY()+center-(channelHeight)*canvas->channelOverlapFactor)+1 ; + int jto_wholechannel_clip = (int) (getY()+center+(channelHeight)*canvas->channelOverlapFactor) -0; + + if (jfrom_wholechannel<0) {jfrom_wholechannel=0;}; + if (jto_wholechannel >= display->lfpChannelBitmap.getHeight()) {jto_wholechannel=display->lfpChannelBitmap.getHeight()-1;}; + + // draw most recent drawn sample position + if (canvas->screenBufferIndex[chan]+1 <= display->lfpChannelBitmap.getWidth()) + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k+=2) // draw line + bdLfpChannelBitmap.setPixelColour(canvas->screenBufferIndex[chan]+1,k, Colours::yellow); + + + bool clipWarningHi =false; // keep track if something clipped in the display, so we can draw warnings after the data pixels are done + bool clipWarningLo =false; + + bool saturateWarningHi =false; // similar, but for saturating the amplifier, not just the display - make this warning very visible + bool saturateWarningLo =false; + + // pre compute some colors for later so we dont do it once per pixel. + Colour lineColourBright = lineColour.withMultipliedBrightness(2.0f); + //Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f).withMultipliedBrightness(0.3f); + Colour lineColourDark = lineColour.withMultipliedSaturation(0.5f*canvas->histogramParameterB).withMultipliedBrightness(canvas->histogramParameterB); + + + int stepSize = 1; + int from = 0; // for vertical line drawing in the LFP data + int to = 0; + + int ifrom = canvas->lastScreenBufferIndex[chan] - 1; // need to start drawing a bit before the actual redraw window for the interpolated line to join correctly + + if (ifrom < 0) + ifrom = 0; + + int ito = canvas->screenBufferIndex[chan] +0; + + if (fullredraw) + { + ifrom = 0; //canvas->leftmargin; + ito = getWidth()-stepSize; + fullredraw = false; + } + + bool drawWithOffsetCorrection = display->getMedianOffsetPlotting(); + + LfpBitmapPlotterInfo plotterInfo; // hold and pass plotting info for each plotting method class + + + for (int i = ifrom; i < ito ; i += stepSize) // redraw only changed portion + { + if (i < display->lfpChannelBitmap.getWidth()) + { + //draw zero line + int m = getY()+center; + + if(m > 0 && m < display->lfpChannelBitmap.getHeight()) + { + if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) { // make sure we're not drawing over an existing plot from another channel + bdLfpChannelBitmap.setPixelColour(i,m,Colour(50,50,50)); + } + } + + //draw range markers + if (isSelected) + { + int start = getY()+center -channelHeight/2; + int jump = channelHeight/4; + + for (m = start; m <= start + jump*4; m += jump) + { + if (m > 0 && m < display->lfpChannelBitmap.getHeight()) + { + if ( bdLfpChannelBitmap.getPixelColour(i,m) == display->backgroundColour ) // make sure we're not drawing over an existing plot from another channel + bdLfpChannelBitmap.setPixelColour(i, m, Colour(80,80,80)); + } + } + } + + // draw event markers + int rawEventState = canvas->getYCoord(canvas->getNumChannels(), i);// get last channel+1 in buffer (represents events) + + for (int ev_ch = 0; ev_ch < 8 ; ev_ch++) // for all event channels + { + if (display->getEventDisplayState(ev_ch)) // check if plotting for this channel is enabled + { + if (rawEventState & (1 << ev_ch)) // events are representet by a bit code, so we have to extract the individual bits with a mask + { +// std::cout << "Drawing event." << std::endl; + Colour currentcolor=display->channelColours[ev_ch*2]; + + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++) // draw line + bdLfpChannelBitmap.setPixelColour(i,k,bdLfpChannelBitmap.getPixelColour(i,k).interpolatedWith(currentcolor,0.3f)); + + } + } + } + + //std::cout << "e " << canvas->getYCoord(canvas->getNumChannels()-1, i) << std::endl; + + + // set max-min range for plotting, used in all methods + double a = (canvas->getYCoordMax(chan, i)/range*channelHeightFloat); + double b = (canvas->getYCoordMin(chan, i)/range*channelHeightFloat); + + double mean = (canvas->getMean(chan)/range*channelHeightFloat); + + if (drawWithOffsetCorrection) + { + a -= mean; + b -= mean; + } + + double a_raw = canvas->getYCoordMax(chan, i); + double b_raw = canvas->getYCoordMin(chan, i); + double from_raw=0; double to_raw=0; + + //double m = (canvas->getYCoordMean(chan, i)/range*channelHeightFloat)+getHeight()/2; + if (achannelOverlapFactor; + if (lm>0) + lm=-lm; + + if (from > -lm) {from = -lm; clipWarningHi=true;}; + if (to > -lm) {to = -lm; clipWarningHi=true;}; + if (from < lm) {from = lm; clipWarningLo=true;}; + if (to < lm) {to = lm; clipWarningLo=true;}; + + + // test if raw data is clipped for displaying saturation warning + if (from_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;}; + if (to_raw > options->selectedSaturationValueFloat) { saturateWarningHi=true;}; + if (from_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;}; + if (to_raw < -options->selectedSaturationValueFloat) { saturateWarningLo=true;}; + + bool spikeFlag = display->getSpikeRasterPlotting() + && !(saturateWarningHi || saturateWarningLo) + && (from_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold() + || to_raw - canvas->getYCoordMean(chan, i) < display->getSpikeRasterThreshold()); + + from = from + getHeight()/2; // so the plot is centered in the channeldisplay + to = to + getHeight()/2; + + int samplerange = to - from; + + if (drawMethod) // switched between 'supersampled' drawing and simple pixel wise drawing + { // histogram based supersampling method + plotterInfo.channelID = chan; + plotterInfo.samp = i; + plotterInfo.y = getY(); + plotterInfo.from = from; + plotterInfo.height = getHeight(); + plotterInfo.lineColourBright = lineColourBright; + plotterInfo.lineColourDark = lineColourDark; + plotterInfo.range = range; + plotterInfo.channelHeightFloat = channelHeightFloat; + plotterInfo.sampleCountPerPixel = canvas->getSampleCountPerPixel(i); + plotterInfo.samplesPerPixel = canvas->getSamplesPerPixel(chan, i); + plotterInfo.histogramParameterA = canvas->histogramParameterA; + plotterInfo.samplerange = samplerange; + + // TODO: (kelly) complete transition toward plotter class encapsulation +// display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); + + } + else //drawmethod + { // simple per-pixel min-max drawing, has no anti-aliasing, but runs faster + + plotterInfo.channelID = chan; + plotterInfo.y = getY(); + plotterInfo.from = from; + plotterInfo.to = to; + plotterInfo.samp = i; + plotterInfo.lineColour = lineColour; + + // TODO: (kelly) complete transition toward plotter class encapsulation +// display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); // plotterInfo is prepared above + + } + + // Do the actual plotting for the selected plotting method + if (!display->getSpikeRasterPlotting()) + display->getPlotterPtr()->plot(bdLfpChannelBitmap, plotterInfo); + + + + + // now draw warnings, if needed + if (canvas->drawClipWarning) // draw simple warning if display cuts off data + { + + if(clipWarningHi) { + for (int j=0; j<=3; j++) + { + int clipmarker = jto_wholechannel_clip; + + if(clipmarker>0 && clipmarkerlfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,clipmarker-j,Colour(255,255,255)); + } + } + } + + if(clipWarningLo) { + for (int j=0; j<=3; j++) + { + int clipmarker = jfrom_wholechannel_clip; + + if(clipmarker>0 && clipmarkerlfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,clipmarker+j,Colour(255,255,255)); + } + } + } + + clipWarningHi=false; + clipWarningLo=false; + } + + if (spikeFlag) // draw spikes + { + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line + if(k>0 && klfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,k,lineColour); + } + }; + } + + + if (canvas->drawSaturationWarning) // draw bigger warning if actual data gets cuts off + { + + if(saturateWarningHi || saturateWarningLo) { + + + for (int k=jfrom_wholechannel; k<=jto_wholechannel; k++){ // draw line + Colour thiscolour=Colour(255,0,0); + if (fmod((i+k),50)>25){ + thiscolour=Colour(255,255,255); + } + if(k>0 && klfpChannelBitmap.getHeight()){ + bdLfpChannelBitmap.setPixelColour(i,k,thiscolour); + } + }; + } + + saturateWarningHi=false; // we likely just need one of this because for this warning we dont care if its saturating on the positive or negative side + saturateWarningLo=false; + } + } // if i < getWidth() + + } // for i (x pixels) + +} + +void LfpChannelDisplay::paint(Graphics& g) {} + + + +PopupMenu LfpChannelDisplay::getOptions() +{ + + PopupMenu menu; + menu.addItem(1, "Invert signal", true, inputInverted); + + return menu; +} + +void LfpChannelDisplay::changeParameter(int id) +{ + switch (id) + { + case 1: + setInputInverted(!inputInverted); + default: + break; + } +} + +void LfpChannelDisplay::setRange(float r) +{ + + range = r; + + //std::cout << "Range: " << r << std::endl; +} + +int LfpChannelDisplay::getRange() +{ + return range; +} + + +void LfpChannelDisplay::select() +{ + isSelected = true; +} + +void LfpChannelDisplay::deselect() +{ + isSelected = false; +} + +bool LfpChannelDisplay::getSelected() +{ + return isSelected; +} + +void LfpChannelDisplay::setColour(Colour c) +{ + lineColour = c; +} + + +void LfpChannelDisplay::setChannelHeight(int c) +{ + channelHeight = c; + + channelHeightFloat = (float) channelHeight; + + if (!inputInverted) + channelHeightFloat = -channelHeightFloat; + + channelOverlap = channelHeight*2; +} + +int LfpChannelDisplay::getChannelHeight() +{ + + return channelHeight; +} + +void LfpChannelDisplay::setChannelOverlap(int overlap) +{ + channelOverlap = overlap; +} + + +int LfpChannelDisplay::getChannelOverlap() +{ + return channelOverlap; +} + +int LfpChannelDisplay::getChannelNumber() +{ + return chan; +} + +String LfpChannelDisplay::getName() +{ + return name; +} + +int LfpChannelDisplay::getDrawableChannelNumber() +{ + return drawableChan; +} + +void LfpChannelDisplay::setDrawableChannelNumber(int channelId) +{ + drawableChan = channelId; +} + +void LfpChannelDisplay::setCanBeInverted(bool _canBeInverted) +{ + canBeInverted = _canBeInverted; +} + +void LfpChannelDisplay::setInputInverted(bool isInverted) +{ + if (canBeInverted) + { + inputInverted = isInverted; + setChannelHeight(channelHeight); + } +} + +void LfpChannelDisplay::setDrawMethod(bool isDrawMethod) +{ + + drawMethod = isDrawMethod; + +} + + +void LfpChannelDisplay::setName(String name_) +{ + name = name_; +} + +DataChannel::DataChannelTypes LfpChannelDisplay::getType() +{ + return type; +} + + + +#pragma mark - LfpChannelDisplayInfo - +// ------------------------------- + +LfpChannelDisplayInfo::LfpChannelDisplayInfo(LfpDisplayCanvas* canvas_, LfpDisplay* display_, LfpDisplayOptions* options_, int ch) + : LfpChannelDisplay(canvas_, display_, options_, ch) +{ + + chan = ch; + x = -1.0f; + y = -1.0f; + +// enableButton = new UtilityButton(String(ch+1), Font("Small Text", 13, Font::plain)); + enableButton = new UtilityButton("", Font("Small Text", 13, Font::plain)); + enableButton->setRadius(5.0f); + + enableButton->setEnabledState(true); + enableButton->setCorners(true, true, true, true); + enableButton->addListener(this); + enableButton->setClickingTogglesState(true); + enableButton->setToggleState(true, dontSendNotification); + + isSingleChannel = false; + + addAndMakeVisible(enableButton); + +} + +void LfpChannelDisplayInfo::updateType() +{ + type = options->getChannelType(chan); + typeStr = options->getTypeName(type); + repaint(); +} + +void LfpChannelDisplayInfo::buttonClicked(Button* button) +{ + + bool state = button->getToggleState(); + + display->setEnabledState(state, chan); + + //UtilityButton* b = (UtilityButton*) button; + + // if (state) + // { + // b->setLabel("ON"); + // } else { + // b->setLabel("OFF"); + // } + + //std::cout << "Turn channel " << chan << " to " << button->getToggleState() << std::endl; + +} + +void LfpChannelDisplayInfo::setEnabledState(bool state) +{ + enableButton->setToggleState(state, sendNotification); +} + +void LfpChannelDisplayInfo::setSingleChannelState(bool state) +{ + isSingleChannel = state; +} + +int LfpChannelDisplayInfo::getChannelSampleRate() +{ + return samplerate; +} + +void LfpChannelDisplayInfo::setChannelSampleRate(int samplerate_) +{ + samplerate = samplerate_; +} + +void LfpChannelDisplayInfo::mouseDrag(const MouseEvent &e) +{ + if (e.mods.isLeftButtonDown()) // double check that we initiate only for left click and hold + { + if (e.mods.isCommandDown() && !display->getSingleChannelState()) // CTRL + drag -> change channel spacing + { + + // init state in our track zooming info struct + if (!display->trackZoomInfo.isScrollingY) + { + auto & zoomInfo = display->trackZoomInfo; + + zoomInfo.isScrollingY = true; + zoomInfo.componentStartHeight = getChannelHeight(); + zoomInfo.zoomPivotRatioY = (getY() + e.getMouseDownY())/(float)display->getHeight(); + zoomInfo.zoomPivotRatioX = (getX() + e.getMouseDownX())/(float)display->getWidth(); + zoomInfo.zoomPivotViewportOffset = getPosition() + e.getMouseDownPosition() - canvas->viewport->getViewPosition(); + + zoomInfo.unpauseOnScrollEnd = !display->isPaused; + if (!display->isPaused) display->options->togglePauseButton(true); + } + + int h = display->trackZoomInfo.componentStartHeight; + int hdiff=0; + int dragDeltaY = -0.1 * (e.getScreenPosition().getY() - e.getMouseDownScreenY()); // invert so drag up -> scale up + +// std::cout << dragDeltaY << std::endl; + if (dragDeltaY > 0) + { + hdiff = 2 * dragDeltaY; + } + else + { + if (h > 5) + hdiff = 2 * dragDeltaY; + } + + if (abs(h) > 100) // accelerate scrolling for large ranges + hdiff *= 3; + + int newHeight = h+hdiff; + + // constrain the spread resizing to max and min values; + if (newHeight < display->trackZoomInfo.minZoomHeight) + { + newHeight = display->trackZoomInfo.minZoomHeight; + } + else if (newHeight > display->trackZoomInfo.maxZoomHeight) + { + newHeight = display->trackZoomInfo.maxZoomHeight; + } + + // return early if there is nothing to update + if (newHeight == getChannelHeight()) + { + return; + } + + // set channel heights for all channel +// display->setChannelHeight(newHeight); + for (int i = 0; i < display->getNumChannels(); ++i) + { + display->channels[i]->setChannelHeight(newHeight); + display->channelInfo[i]->setChannelHeight(newHeight); + } + + options->setSpreadSelection(newHeight, false, true); // update combobox + + canvas->fullredraw = true;//issue full redraw - scrolling without modifier doesnt require a full redraw + + display->setBounds(0,0,display->getWidth()-0, display->getChannelHeight()*display->drawableChannels.size()); // update height so that the scrollbar is correct + + int newViewportY = display->trackZoomInfo.zoomPivotRatioY * display->getHeight() - display->trackZoomInfo.zoomPivotViewportOffset.getY(); + if (newViewportY < 0) newViewportY = 0; // make sure we don't adjust beyond the edge of the actual view + + canvas->viewport->setViewPosition(0, newViewportY); + } + } +} + +void LfpChannelDisplayInfo::mouseUp(const MouseEvent &e) +{ + if (e.mods.isLeftButtonDown() && display->trackZoomInfo.isScrollingY) + { + display->trackZoomInfo.isScrollingY = false; + if (display->trackZoomInfo.unpauseOnScrollEnd) + { + display->isPaused = false; + display->options->togglePauseButton(false); + } + } +} + +void LfpChannelDisplayInfo::paint(Graphics& g) +{ + + int center = getHeight()/2 - (isSingleChannel?(75):(0)); + const bool showChannelNumbers = options->getChannelNameState(); + + // Draw the channel numbers + g.setColour(Colours::grey); + const String channelString = (isChannelNumberHidden() ? ("--") : + showChannelNumbers ? String(getChannelNumber() + 1) : getName()); + bool isCentered = !getEnabledButtonVisibility(); + + g.drawText(channelString, + showChannelNumbers ? 6 : 2, + center-4, + getWidth()/2, + 10, + isCentered ? Justification::centred : Justification::centredLeft, + false); + + g.setColour(lineColour); + g.fillRect(0, 0, 2, getHeight()); + + if (getChannelTypeStringVisibility()) + { + g.setFont(Font("Small Text", 13, Font::plain)); + g.drawText(typeStr,5,center+10,41,10,Justification::centred,false); + } + // g.setFont(channelHeightFloat*0.3); + g.setFont(Font("Small Text", 11, Font::plain)); + + if (isSingleChannel) + { + g.setColour(Colours::darkgrey); + g.drawText("STD:", 5, center+90,41,10,Justification::centred,false); + g.drawText("MEAN:", 5, center+40,41,10,Justification::centred,false); + + if (x > 0) + { + g.drawText("uV:", 5, center+140,41,10,Justification::centred,false); + } + //g.drawText("Y:", 5, center+200,41,10,Justification::centred,false); + + g.setColour(Colours::grey); + g.drawText(String(canvas->getStd(chan)), 5, center+110,41,10,Justification::centred,false); + g.drawText(String(canvas->getMean(chan)), 5, center+60,41,10,Justification::centred,false); + if (x > 0) + { + //g.drawText(String(x), 5, center+150,41,10,Justification::centred,false); + g.drawText(String(y), 5, center+160,41,10,Justification::centred,false); + } + + } + + // g.drawText(name, 10, center-channelHeight/2, 200, channelHeight, Justification::left, false); + +} + +void LfpChannelDisplayInfo::updateXY(float x_, float y_) +{ + x = x_; + y = y_; +} + +void LfpChannelDisplayInfo::resized() +{ + + int center = getHeight()/2 - (isSingleChannel?(75):(0)); + setEnabledButtonVisibility(getHeight() >= 16); + + if (getEnabledButtonVisibility()) + { + enableButton->setBounds(getWidth()/2 - 10, center - 5, 10, 10); + } + + setChannelNumberIsHidden(getHeight() < 16 && (getDrawableChannelNumber() + 1) % 10 != 0); + + setChannelTypeStringVisibility(getHeight() > 34); +} + +void LfpChannelDisplayInfo::setEnabledButtonVisibility(bool shouldBeVisible) +{ + if (shouldBeVisible) + { + addAndMakeVisible(enableButton); + } + else if (enableButton->isVisible()) + { + removeChildComponent(enableButton); + enableButton->setVisible(false); + } + +} + +bool LfpChannelDisplayInfo::getEnabledButtonVisibility() +{ + return enableButton->isVisible(); +} + +void LfpChannelDisplayInfo::setChannelTypeStringVisibility(bool shouldBeVisible) +{ + channelTypeStringIsVisible = shouldBeVisible; +} + +bool LfpChannelDisplayInfo::getChannelTypeStringVisibility() +{ + return channelTypeStringIsVisible || isSingleChannel; +} + +void LfpChannelDisplayInfo::setChannelNumberIsHidden(bool shouldBeHidden) +{ + channelNumberHidden = shouldBeHidden; +} + +bool LfpChannelDisplayInfo::isChannelNumberHidden() +{ + return channelNumberHidden; +} + + + + +#pragma mark - EventDisplayInterface - +// Event display Options -------------------------------------------------------------------- + +EventDisplayInterface::EventDisplayInterface(LfpDisplay* display_, LfpDisplayCanvas* canvas_, int chNum): + isEnabled(true), display(display_), canvas(canvas_) +{ + + channelNumber = chNum; + + chButton = new UtilityButton(String(channelNumber+1), Font("Small Text", 13, Font::plain)); + chButton->setRadius(5.0f); + chButton->setBounds(4,4,14,14); + chButton->setEnabledState(true); + chButton->setCorners(true, false, true, false); + //chButton.color = display->channelColours[channelNumber*2]; + chButton->addListener(this); + addAndMakeVisible(chButton); + + + checkEnabledState(); + +} + +EventDisplayInterface::~EventDisplayInterface() +{ + +} + +void EventDisplayInterface::checkEnabledState() +{ + isEnabled = display->getEventDisplayState(channelNumber); + + //repaint(); +} + +void EventDisplayInterface::buttonClicked(Button* button) +{ + checkEnabledState(); + if (isEnabled) + { + display->setEventDisplayState(channelNumber, false); + } + else + { + display->setEventDisplayState(channelNumber, true); + } + + repaint(); + +} + + +void EventDisplayInterface::paint(Graphics& g) +{ + + checkEnabledState(); + + if (isEnabled) + { + g.setColour(display->channelColours[channelNumber*2]); + g.fillRoundedRectangle(2,2,18,18,6.0f); + } + + + //g.drawText(String(channelNumber), 8, 2, 200, 15, Justification::left, false); + +} + + + +#pragma mark - LfpViewport - +// Lfp Viewport ------------------------------------------- + +LfpViewport::LfpViewport(LfpDisplayCanvas *canvas) + : Viewport() +{ + this->canvas = canvas; +} + +void LfpViewport::visibleAreaChanged(const Rectangle& newVisibleArea) +{ + canvas->fullredraw = true; + canvas->refresh(); +} + + + +#pragma mark - PerPixelBitmapPlotter - + +PerPixelBitmapPlotter::PerPixelBitmapPlotter(LfpDisplay * lfpDisplay) + : LfpBitmapPlotter(lfpDisplay) +{ } + +void PerPixelBitmapPlotter::plot(Image::BitmapData &bitmapData, LfpBitmapPlotterInfo &pInfo) +{ + int jfrom = pInfo.from + pInfo.y; + int jto = pInfo.to + pInfo.y; + + //if (yofs<0) {yofs=0;}; + + if (pInfo.samp < 0) {pInfo.samp = 0;}; + if (pInfo.samp >= display->lfpChannelBitmap.getWidth()) {pInfo.samp = display->lfpChannelBitmap.getWidth()-1;}; // this shouldnt happen, there must be some bug above - to replicate, run at max refresh rate where draws overlap the right margin by a lot + + if (jfrom<0) {jfrom=0;}; + if (jto >= display->lfpChannelBitmap.getHeight()) {jto=display->lfpChannelBitmap.getHeight()-1;}; + + + for (int j = jfrom; j <= jto; j += 1) + { + + //uint8* const pu8Pixel = bdSharedLfpDisplay.getPixelPointer( (int)(i),(int)(j)); + //*(pu8Pixel) = 200; + //*(pu8Pixel+1) = 200; + //*(pu8Pixel+2) = 200; + + bitmapData.setPixelColour(pInfo.samp,j,pInfo.lineColour); + + } +} + + + +#pragma mark - LfpSupersampledBitmapPlotter - + +SupersampledBitmapPlotter::SupersampledBitmapPlotter(LfpDisplay * lfpDisplay) + : LfpBitmapPlotter(lfpDisplay) +{ } + +void SupersampledBitmapPlotter::plot(Image::BitmapData &bdLfpChannelBitmap, LfpBitmapPlotterInfo &pInfo) +{ + std::array samplesThisPixel = pInfo.samplesPerPixel; +// int sampleCountThisPixel = lfpDisplay->canvas->getSampleCountPerPixel(pInfo.samp); + int sampleCountThisPixel = pInfo.sampleCountPerPixel; + + if (pInfo.samplerange>0 && sampleCountThisPixel>0) + { + + //float localHist[samplerange]; // simple histogram + Array rangeHist; // [samplerange]; // paired range histogram, same as plotting at higher res. and subsampling + + for (int k = 0; k <= pInfo.samplerange; k++) + rangeHist.add(0); + + for (int k = 0; k <= sampleCountThisPixel; k++) // add up paired-range histogram per pixel - for each pair fill intermediate with uniform distr. + { + int cs_this = (((samplesThisPixel[k]/pInfo.range*pInfo.channelHeightFloat)+pInfo.height/2)-pInfo.from); // sample values -> pixel coordinates relative to from + int cs_next = (((samplesThisPixel[k+1]/pInfo.range*pInfo.channelHeightFloat)+pInfo.height/2)-pInfo.from); + + + if (cs_this<0) {cs_this=0;}; //here we could clip the diaplay to the max/min, or ignore out of bound values, not sure which one is better + if (cs_this>pInfo.samplerange) {cs_this=pInfo.samplerange;}; + if (cs_next<0) {cs_next=0;}; + if (cs_next>pInfo.samplerange) {cs_next=pInfo.samplerange;}; + + int hfrom=0; + int hto=0; + + if (cs_this1.0f) {a=1.0f;}; + if (a<0.0f) {a=0.0f;}; + + + //Colour gradedColor = lineColour.withMultipliedBrightness(2.0f).interpolatedWith(lineColour.withMultipliedSaturation(0.6f).withMultipliedBrightness(0.3f),1-a) ; + Colour gradedColor = pInfo.lineColourBright.interpolatedWith(pInfo.lineColourDark,1-a); + //Colour gradedColor = Colour(0,255,0); + + int ploty = pInfo.from + s + pInfo.y; + if(ploty>0 && ploty < display->lfpChannelBitmap.getHeight()) { + bdLfpChannelBitmap.setPixelColour(pInfo.samp, pInfo.from + s + pInfo.y, gradedColor); + } + } + + } else { + + int ploty = pInfo.from + pInfo.y; + if(ploty>0 && ploty < display->lfpChannelBitmap.getHeight()) { + bdLfpChannelBitmap.setPixelColour(pInfo.samp, ploty, pInfo.lineColour); + } + } +} + + + +#pragma mark - LfpChannelColourScheme - + +int LfpChannelColourScheme::colourGrouping = 1; + +void LfpChannelColourScheme::setColourGrouping(int grouping) +{ + colourGrouping = grouping; +} + +int LfpChannelColourScheme::getColourGrouping() +{ + return colourGrouping; +} + + + +#pragma mark - LfpDefaultColourScheme - + +Array LfpDefaultColourScheme::colourList = []() -> Array { + Array colours; + colours.add(Colour(224,185,36)); + colours.add(Colour(214,210,182)); + colours.add(Colour(243,119,33)); + colours.add(Colour(186,157,168)); + colours.add(Colour(237,37,36)); + colours.add(Colour(179,122,79)); + colours.add(Colour(217,46,171)); + colours.add(Colour(217, 139,196)); + colours.add(Colour(101,31,255)); + colours.add(Colour(141,111,181)); + colours.add(Colour(48,117,255)); + colours.add(Colour(184,198,224)); + colours.add(Colour(116,227,156)); + colours.add(Colour(150,158,155)); + colours.add(Colour(82,173,0)); + colours.add(Colour(125,99,32)); + return colours; +}(); + +LfpDefaultColourScheme::LfpDefaultColourScheme(LfpDisplay* display, LfpDisplayCanvas* canvas) + : LfpViewer::LfpChannelColourScheme(LfpDefaultColourScheme::colourList.size(), display, canvas) +{ + setName("Default"); +} + +void LfpDefaultColourScheme::paint(Graphics &g) +{ + +} + +void LfpDefaultColourScheme::resized() +{ + +} + +const Colour LfpDefaultColourScheme::getColourForIndex(int index) const +{ +// return colourList[index % colourList.size()]; + return colourList[(int(index/colourGrouping)) % colourList.size()]; +} + + + +#pragma mark - LfpMonochromaticColorScheme + +LfpMonochromaticColourScheme::LfpMonochromaticColourScheme(LfpDisplay* display, LfpDisplayCanvas* canvas) + : LfpChannelColourScheme (8, display, canvas) + , isBlackAndWhite (false) + , colourPattern (DOWN_UP) +{ + setName("Monochromatic"); + + numChannelsLabel = new Label("numChannelsLabel", "Num Color Steps"); + numChannelsLabel->setFont(Font("Default", 13.0f, Font::plain)); + numChannelsLabel->setColour(Label::textColourId, Colour(100, 100, 100)); + addAndMakeVisible(numChannelsLabel); + + StringArray numChannelsSelectionOptions = {"4", "8", "16"}; + numChannelsSelection = new ComboBox("numChannelsSelection"); + numChannelsSelection->addItemList(numChannelsSelectionOptions, 1); + numChannelsSelection->setEditableText(true); + numChannelsSelection->addListener(this); + numChannelsSelection->setSelectedId(2, dontSendNotification); + addAndMakeVisible(numChannelsSelection); + + + baseHueLabel = new Label("baseHue", "Hue"); + baseHueLabel->setFont(Font("Default", 13.0f, Font::plain)); + baseHueLabel->setColour(Label::textColourId, Colour(100, 100, 100)); + addAndMakeVisible(baseHueLabel); + + baseHueSlider = new Slider; + baseHueSlider->setRange(0, 1); + baseHueSlider->setValue(0); + baseHueSlider->setTextBoxStyle(Slider::NoTextBox, false, 0, 0); + baseHueSlider->addListener(this); + addAndMakeVisible(baseHueSlider); + + baseHueSlider->addMouseListener(this, true); + + + colourPatternLabel = new Label("colourPatternLabel", "Pattern"); + colourPatternLabel->setFont(Font("Default", 13.0f, Font::plain)); + colourPatternLabel->setColour(Label::textColourId, Colour(100, 100, 100)); + addAndMakeVisible(colourPatternLabel); + + StringArray colourPatternSelectionOptions = {"Down", "Up", "Down-Up", "Up-Down"}; + colourPatternSelection = new ComboBox("colourPatternSelection"); + colourPatternSelection->addItemList(colourPatternSelectionOptions, 1); + colourPatternSelection->setEditableText(false); + colourPatternSelection->addListener(this); + colourPatternSelection->setSelectedId(colourPattern + 1, dontSendNotification); + addAndMakeVisible(colourPatternSelection); + + baseHue = Colour::fromHSV(0, 1, 1, 1); + swatchHue = baseHue; + + calculateColourSeriesFromBaseHue(); +} + +void LfpMonochromaticColourScheme::paint(Graphics &g) +{ + g.setColour(swatchHue); + g.fillRect(colourSwatchRect); +} + +void LfpMonochromaticColourScheme::resized() +{ + numChannelsLabel->setBounds(0, 5, 120, 25); + numChannelsSelection->setBounds(numChannelsLabel->getRight(), + numChannelsLabel->getY(), + 60, + 25); + + baseHueLabel->setBounds(0, numChannelsLabel->getBottom(), 35, 25); + baseHueSlider->setBounds(baseHueLabel->getRight(), + baseHueLabel->getY(), + numChannelsSelection->getRight() - baseHueLabel->getRight() - 20, + 25); + + colourSwatchRect.setBounds(baseHueSlider->getRight() + 5, baseHueSlider->getY() + 5, 15, baseHueSlider->getHeight() - 10); + + colourPatternLabel->setBounds(0, baseHueLabel->getBottom(), 80, 25); + colourPatternSelection->setBounds(colourPatternLabel->getRight(), + colourPatternLabel->getY(), + numChannelsSelection->getRight() - colourPatternLabel->getRight(), + 25); + +} + +void LfpMonochromaticColourScheme::sliderValueChanged(Slider *sl) +{ + swatchHue = Colour::fromHSV(sl->getValue(), 1, 1, 1); + repaint(colourSwatchRect); +} + +void LfpMonochromaticColourScheme::comboBoxChanged(ComboBox *cb) +{ + if (cb == numChannelsSelection) + { + int numChannelsColourSpread = 0; + if (cb->getSelectedId()) + { + numChannelsColourSpread = cb->getText().getIntValue(); + } + else + { + numChannelsColourSpread = cb->getText().getIntValue(); + if (numChannelsColourSpread < 1) numChannelsColourSpread = 1; + else if (numChannelsColourSpread > 16) numChannelsColourSpread = 16; + + cb->setText(String(numChannelsColourSpread), dontSendNotification); + } + + setNumColourSeriesSteps(numChannelsColourSpread); + } + else if (cb == colourPatternSelection) + { + setColourPattern((ColourPattern)(cb->getSelectedId() - 1)); + } + calculateColourSeriesFromBaseHue(); + lfpDisplay->setColors(); +// canvas->fullredraw = true; + canvas->redraw(); +} + +void LfpMonochromaticColourScheme::mouseUp(const MouseEvent &e) +{ + if (swatchHue.getARGB() != baseHue.getARGB()) + { + baseHue = swatchHue; + calculateColourSeriesFromBaseHue(); + lfpDisplay->setColors(); + canvas->redraw(); + } +} + +void LfpMonochromaticColourScheme::setBaseHue(Colour base) +{ + baseHue = base; + calculateColourSeriesFromBaseHue(); +} + +const Colour LfpMonochromaticColourScheme::getBaseHue() const +{ + return baseHue; +} + +void LfpMonochromaticColourScheme::setNumColourSeriesSteps(int numSteps) +{ + numColourChannels = numSteps; +} + +int LfpMonochromaticColourScheme::getNumColourSeriesSteps() +{ + return numColourChannels; +} + +const Colour LfpMonochromaticColourScheme::getColourForIndex(int index) const +{ + int colourIdx = (int(index/colourGrouping) % numColourChannels); + + // adjust for oscillating patterns + if (colourPattern == DOWN_UP || colourPattern == UP_DOWN) + { + int mid = numColourChannels / 2; + if (colourIdx > mid) + { + if (numColourChannels % 2 == 0) + colourIdx = numColourChannels - colourIdx; + else + colourIdx = (numColourChannels - colourIdx) * 2 - 1; + } + else if (numColourChannels % 2 != 0) + { + colourIdx *= 2; + } + } + + // invert if the pattern is UP or UP_DOWN + if (colourPattern == UP) + colourIdx = (numColourChannels - 1) - colourIdx; + else if (colourPattern == UP_DOWN) + colourIdx = (colourList.size() - 1) - colourIdx; + + return colourList[colourIdx]; +} + +void LfpMonochromaticColourScheme::calculateColourSeriesFromBaseHue() +{ + colourList.clear(); + + int coloursToCalculate = numColourChannels; + + if (numColourChannels % 2 == 0 && (colourPattern == DOWN_UP || colourPattern == UP_DOWN)) + { + coloursToCalculate = coloursToCalculate / 2 + 1; + } + + for (int i = 0; i < coloursToCalculate; ++i) + { + float saturation = 1 - (i / float(coloursToCalculate + 1)); + colourList.add(baseHue.withMultipliedSaturation(saturation)); + } +} + + + +#pragma mark - LfpGradientColourScheme + +LfpGradientColourScheme::LfpGradientColourScheme(LfpDisplay * display, LfpDisplayCanvas * canvas) + : LfpMonochromaticColourScheme(display, canvas) +{ + setName("Gradient"); + + baseHueLabel->setName("baseHueA"); + baseHueLabel->setText("Hue A", dontSendNotification); + + baseHueLabelB = new Label("baseHueB", "Hue B"); + baseHueLabelB->setFont(Font("Default", 13.0f, Font::plain)); + baseHueLabelB->setColour(Label::textColourId, Colour(100, 100, 100)); + addAndMakeVisible(baseHueLabelB); + + baseHueSliderB = new Slider; + baseHueSliderB->setRange(0, 1); + baseHueSliderB->setValue(0.5); + baseHueSliderB->setTextBoxStyle(Slider::NoTextBox, false, 0, 0); + baseHueSliderB->addListener(this); + addAndMakeVisible(baseHueSliderB); + + baseHueSliderB->addMouseListener(this, true); + + baseHueB = Colour::fromHSV(0.5, 1.0, 1.0, 1.0); + swatchHueB = baseHueB; + + calculateColourSeriesFromBaseHue(); +} + +void LfpGradientColourScheme::paint(Graphics &g) +{ + g.setColour(swatchHue); + g.fillRect(colourSwatchRect); + + g.setColour(swatchHueB); + g.fillRect(colourSwatchRectB); +} + +void LfpGradientColourScheme::resized() +{ + numChannelsLabel->setBounds(0, 5, 120, 25); + numChannelsSelection->setBounds(numChannelsLabel->getRight(), + numChannelsLabel->getY(), + 60, + 25); + + baseHueLabel->setBounds(0, numChannelsLabel->getBottom(), 35, 25); + baseHueSlider->setBounds(baseHueLabel->getRight(), + baseHueLabel->getY(), + numChannelsSelection->getRight() - baseHueLabel->getRight() - 20, + 25); + + colourSwatchRect.setBounds(baseHueSlider->getRight() + 5, baseHueSlider->getY() + 5, 15, baseHueSlider->getHeight() - 10); + + baseHueLabelB->setBounds(0, baseHueLabel->getBottom(), 35, 25); + baseHueSliderB->setBounds(baseHueLabelB->getRight(), + baseHueLabelB->getY(), + numChannelsSelection->getRight() - baseHueLabelB->getRight() - 20, + 25); + + colourSwatchRectB.setBounds(baseHueSliderB->getRight() + 5, baseHueSliderB->getY() + 5, 15, baseHueSliderB->getHeight() - 10); + + colourPatternLabel->setBounds(0, baseHueLabelB->getBottom(), 80, 25); + colourPatternSelection->setBounds(colourPatternLabel->getRight(), + colourPatternLabel->getY(), + numChannelsSelection->getRight() - colourPatternLabel->getRight(), + 25); +} + +void LfpGradientColourScheme::sliderValueChanged(Slider *sl) +{ + if (sl == baseHueSlider) + { + swatchHue = Colour::fromHSV(sl->getValue(), 1, 1, 1); + repaint(colourSwatchRect); + } + else + { + swatchHueB = Colour::fromHSV(sl->getValue(), 1, 1, 1); + repaint(colourSwatchRectB); + } +} + +void LfpGradientColourScheme::mouseUp(const MouseEvent &e) +{ + if (e.originalComponent == baseHueSlider) + { + if (swatchHue.getARGB() != baseHue.getARGB()) + { + baseHue = swatchHue; + calculateColourSeriesFromBaseHue(); + lfpDisplay->setColors(); + canvas->redraw(); + } + } + else + { + if (swatchHueB.getARGB() != baseHueB.getARGB()) + { + baseHueB = swatchHueB; + calculateColourSeriesFromBaseHue(); + lfpDisplay->setColors(); + canvas->redraw(); + } + } +} + +void LfpGradientColourScheme::calculateColourSeriesFromBaseHue() +{ + colourList.clear(); + + int coloursToCalculate = numColourChannels; + + if (numColourChannels % 2 == 0 && (colourPattern == DOWN_UP || colourPattern == UP_DOWN)) + { + coloursToCalculate = coloursToCalculate / 2 + 1; + } + + for (int i = 0; i < coloursToCalculate; ++i) + { + float hue = (baseHueB.getHue() - baseHue.getHue()) * i / float(coloursToCalculate - 1); + colourList.add(baseHue.withRotatedHue(hue)); + } +} diff --git a/Plugins/LfpDisplayNode/LfpDisplayCanvas.h b/Plugins/LfpDisplayNode/LfpDisplayCanvas.h index e4cfd2d0d7..2323752a75 100644 --- a/Plugins/LfpDisplayNode/LfpDisplayCanvas.h +++ b/Plugins/LfpDisplayNode/LfpDisplayCanvas.h @@ -20,39 +20,18 @@ along with this program. If not, see . */ -#ifndef __LFPDISPLAYCANVAS_H_Alpha__ -#define __LFPDISPLAYCANVAS_H_Alpha__ +#ifndef __LFPDISPLAYCANVAS_H__ +#define __LFPDISPLAYCANVAS_H__ #include -#include "LfpDisplayNode.h" #include #include -#define CHANNEL_TYPES 3 -#define MAX_N_CHAN 2048 -#define MAX_N_SAMP 5000 -#define MAX_N_SAMP_PER_PIXEL 100 - +#include "LfpDisplayClasses.h" +#include "LfpDisplayNode.h" namespace LfpViewer { - -class LfpDisplayNode; - -class LfpTimescale; -class LfpDisplay; -class LfpChannelDisplay; -class LfpChannelDisplayInfo; -class EventDisplayInterface; -class LfpViewport; -class LfpDisplayOptions; -class LfpBitmapPlotter; -class PerPixelBitmapPlotter; -class SupersampledBitmapPlotter; -class LfpChannelColourScheme; - - - -#pragma mark - LfpDisplayCanvas - +#pragma mark - LfpDisplayCanvas - //============================================================================== /** @@ -218,925 +197,5 @@ class LfpDisplayCanvas : public Visualizer, }; - - -#pragma mark - ShowHideOptionsButton - -//============================================================================== -/** - - Toggles view options drawer for LfpDisplayCanvas. - - */ -class ShowHideOptionsButton : public Button -{ -public: - ShowHideOptionsButton(LfpDisplayOptions*); - virtual ~ShowHideOptionsButton(); - void paintButton(Graphics& g, bool, bool); - LfpDisplayOptions* options; -}; - - - -#pragma mark - LfpDisplayOptions - -//============================================================================== -/** - - Holds the LfpDisplay UI controls - - */ -class LfpDisplayOptions : public Component, - public Slider::Listener, - public ComboBox::Listener, - public Button::Listener -{ -public: - LfpDisplayOptions(LfpDisplayCanvas*, LfpTimescale*, LfpDisplay*, LfpDisplayNode*); - ~LfpDisplayOptions(); - - void paint(Graphics& g); - void resized(); - - void setRangeSelection(float range, bool canvasMustUpdate = false); // set range selection combo box to correct value if it has been changed by scolling etc. - void setSpreadSelection(int spread, bool canvasMustUpdate = false, bool deferDisplayRefresh = false); // set spread selection combo box to correct value if it has been changed by scolling etc. - - void comboBoxChanged(ComboBox* cb); - void buttonClicked(Button* button); - - /** Changes the timebase value used by LfpTimescale and LfpDisplayCanvas. */ - void setTimebaseAndSelectionText(float timebase); - - /** Handles slider events for all editors. */ - void sliderValueChanged(Slider* sl); - - /** Called by sliderValueChanged(). Deals with clicks on custom sliders. Subclasses - of GenericEditor should modify this method only.*/ - void sliderEvent(Slider* sl); - - int getChannelHeight(); - bool getDrawMethodState(); - bool getInputInvertedState(); - bool getChannelNameState(); - - /** Return a bool describing whether the spike raster functionality is enabled */ - bool getDisplaySpikeRasterizerState(); - - /** Sets the state of the spike raster functionality on/off */ - void setDisplaySpikeRasterizerState(bool isEnabled); - - //void setRangeSelection(float range, bool canvasMustUpdate); - void setSpreadSelection(); - - void togglePauseButton(bool sendUpdate = true); - - void saveParameters(XmlElement* xml); - void loadParameters(XmlElement* xml); - - DataChannel::DataChannelTypes getChannelType(int n); - DataChannel::DataChannelTypes getSelectedType(); - String getTypeName(DataChannel::DataChannelTypes type); - int getRangeStep(DataChannel::DataChannelTypes type); - - void setSelectedType(DataChannel::DataChannelTypes type, bool toggleButton = true); - - int selectedSpread; - String selectedSpreadValue; - - int selectedTimebase; - String selectedTimebaseValue; - - int selectedOverlap; - String selectedOverlapValue; - - int selectedChannelDisplaySkip; - String selectedChannelDisplaySkipValue; - - int selectedSpikeRasterThreshold; - String selectedSpikeRasterThresholdValue; - - // this enum is a candidate option for refactoring, not used yet - enum ChannelDisplaySkipValue { - None = 0, - One, - Two, - Four, - Eight, - Sixteen, - ThirtyTwo - } enum_selectedChannelDisplaySkipValue = None; - - int selectedSaturation; // for saturation warning - String selectedSaturationValue; - float selectedSaturationValueFloat; // TODO: this is way ugly - we should refactor all these parameters soon and get them into a nicer format- probably when we do the general plugin parameter overhaul. - -private: - - LfpDisplayCanvas* canvas; - LfpDisplay* lfpDisplay; - LfpTimescale* timescale; - LfpDisplayNode* processor; - - Font labelFont; - Colour labelColour; - - ScopedPointer timebaseSelection; - ScopedPointer rangeSelection; - ScopedPointer spreadSelection; - - ScopedPointer overlapSelection; - ScopedPointer drawClipWarningButton; // optinally draw (subtle) warning if data is clipped in display - - ScopedPointer saturationWarningSelection; - ScopedPointer drawSaturateWarningButton; // optionally raise hell if the actual data is saturating - - ScopedPointer colorGroupingSelection; - ScopedPointer invertInputButton; - ScopedPointer drawMethodButton; - ScopedPointer pauseButton; - OwnedArray typeButtons; - - // label and button for spike raster functionality - ScopedPointer