From d27a5be819e69469fb40b332335b1c35f8854b62 Mon Sep 17 00:00:00 2001
From: shayo <shay.ohayon@gmail.com>
Date: Wed, 18 Feb 2015 10:24:00 -0500
Subject: [PATCH] Added Standard Deviation Display to Spike Sorter

Useful to estimate the noise levels on channels and to make a somewhat
objective decision on threshold.
Computation is very fast (running statistics). Right click clears up the
stats. Display is on the top left corner.
Currently only works for single channel electrodes (i.e., always
displays the 0th channel statistics).
---
 Source/Processors/SpikeSorter/SpikeSorter.cpp | 32 ++++++++--
 Source/Processors/SpikeSorter/SpikeSorter.h   | 61 ++++++++++++++++++-
 .../SpikeSorter/SpikeSorterCanvas.cpp         | 17 +++---
 3 files changed, 95 insertions(+), 15 deletions(-)

diff --git a/Source/Processors/SpikeSorter/SpikeSorter.cpp b/Source/Processors/SpikeSorter/SpikeSorter.cpp
index 813bdc28c..3fab6eb2a 100644
--- a/Source/Processors/SpikeSorter/SpikeSorter.cpp
+++ b/Source/Processors/SpikeSorter/SpikeSorter.cpp
@@ -221,6 +221,8 @@ Electrode::~Electrode()
 	delete voltageScale;
     delete channels;
 	delete spikeSort;
+	delete runningStats;
+
 }
 
 Electrode::Electrode(int ID, UniqueIDgenerator *uniqueIDgenerator_, PCAcomputingThread *pth, String _name, int _numChannels, int *_channels, float default_threshold, int pre, int post, float samplingRate , int sourceNodeId)
@@ -238,6 +240,7 @@ Electrode::Electrode(int ID, UniqueIDgenerator *uniqueIDgenerator_, PCAcomputing
     isActive = new bool[numChannels];
     channels = new int[numChannels];
 	voltageScale = new double[numChannels];
+	runningStats = new RunningStat[numChannels];
 	depthOffsetMM = 0.0;
 
 	advancerID = -1;
@@ -860,6 +863,25 @@ void SpikeSorter::handleEvent(int eventType, MidiMessage& event, int sampleNum)
 // 	}
 // }
 
+
+float SpikeSorter::getSelectedElectrodeNoise()
+{
+	if (electrodes.size() == 0)
+		return 0.0;
+
+	// TODO, change "0" to active channel to support tetrodes.
+	return electrodes[currentElectrode]->runningStats[0].StandardDeviation();
+}
+
+
+void SpikeSorter::clearRunningStatForSelectedElectrode()
+{
+	if (electrodes.size() == 0)
+		return;
+	// TODO, change "0" to active channel to support tetrodes.
+	electrodes[currentElectrode]->runningStats[0].Clear();
+}
+
 void SpikeSorter::process(AudioSampleBuffer& buffer,
                             MidiBuffer& events)
 {
@@ -893,23 +915,25 @@ void SpikeSorter::process(AudioSampleBuffer& buffer,
         {
 
             sampleIndex++;
-
+			
             // cycle through channels
             for (int chan = 0; chan < electrode->numChannels; chan++)
             {
 
                 // std::cout << "  channel " << chan << std::endl;
-
+				
                 if (*(electrode->isActive+chan))
                 {
 					//float v = getNextSample(currentChannel);
 
                     int currentChannel = electrode->channels[chan];
+					float currentValue = getNextSample(currentChannel);
+					electrode->runningStats[chan].Push(currentValue);
 
 					bool bSpikeDetectedPositive  = electrode->thresholds[chan] > 0 &&
-						(getNextSample(currentChannel) > electrode->thresholds[chan]); // rising edge
+						(currentValue > electrode->thresholds[chan]); // rising edge
 					bool bSpikeDetectedNegative = electrode->thresholds[chan] < 0 &&
-						(getNextSample(currentChannel) < electrode->thresholds[chan]); // falling edge
+						(currentValue < electrode->thresholds[chan]); // falling edge
 
                     if  (bSpikeDetectedPositive || bSpikeDetectedNegative)
                     { 
diff --git a/Source/Processors/SpikeSorter/SpikeSorter.h b/Source/Processors/SpikeSorter/SpikeSorter.h
index bb520b868..8650b05d3 100644
--- a/Source/Processors/SpikeSorter/SpikeSorter.h
+++ b/Source/Processors/SpikeSorter/SpikeSorter.h
@@ -81,6 +81,63 @@ private:
 	int globalUniqueID;
 };
 
+/* snatched from http://www.johndcook.com/blog/standard_deviation/ */
+class RunningStat
+{
+public:
+	RunningStat() : m_n(0) {}
+
+	void Clear()
+	{
+		m_n = 0;
+	}
+
+	void Push(double x)
+	{
+		m_n++;
+
+		// See Knuth TAOCP vol 2, 3rd edition, page 232
+		if (m_n == 1)
+		{
+			m_oldM = m_newM = x;
+			m_oldS = 0.0;
+		}
+		else
+		{
+			m_newM = m_oldM + (x - m_oldM) / m_n;
+			m_newS = m_oldS + (x - m_oldM)*(x - m_newM);
+
+			// set up for next iteration
+			m_oldM = m_newM;
+			m_oldS = m_newS;
+		}
+	}
+
+	int NumDataValues() const
+	{
+		return m_n;
+	}
+
+	double Mean() const
+	{
+		return (m_n > 0) ? m_newM : 0.0;
+	}
+
+	double Variance() const
+	{
+		return ((m_n > 1) ? m_newS / (m_n - 1) : 0.0);
+	}
+
+	double StandardDeviation() const
+	{
+		return sqrt(Variance());
+	}
+
+private:
+	int m_n;
+	double m_oldM, m_newM, m_oldS, m_newS;
+};
+
 class Electrode
 {
 	public:
@@ -106,6 +163,7 @@ class Electrode
 		double *voltageScale;
 		//float PCArange[4];
 
+		RunningStat *runningStats;
 		SpikeHistogramPlot* spikePlot;
 		SpikeSortBoxes* spikeSort;
 		PCAcomputingThread *computingThread;
@@ -181,8 +239,9 @@ public:
     /** Creates the SpikeSorterEditor. */
     AudioProcessorEditor* createEditor();
 
+	float getSelectedElectrodeNoise();
+	void clearRunningStatForSelectedElectrode();
 
-	
 	//void addNetworkEventToQueue(StringTS S);
 
 	void postEventsInQueue(MidiBuffer& events);
diff --git a/Source/Processors/SpikeSorter/SpikeSorterCanvas.cpp b/Source/Processors/SpikeSorter/SpikeSorterCanvas.cpp
index abfc13528..4379d7ab9 100644
--- a/Source/Processors/SpikeSorter/SpikeSorterCanvas.cpp
+++ b/Source/Processors/SpikeSorter/SpikeSorterCanvas.cpp
@@ -1203,7 +1203,7 @@ bool WaveformAxes::checkThreshold(const SpikeObject& s)
 
 void WaveformAxes::clear()
 {
-
+	processor->clearRunningStatForSelectedElectrode();
     spikeBuffer.clear();
     spikeIndex = 0;
     int numSamples=40;
@@ -1668,16 +1668,13 @@ void WaveformAxes::paint(Graphics& g)
 
     if (drawGrid)
         drawWaveformGrid(g);
+	 
+	double noise = processor->getSelectedElectrodeNoise();
+	String d = "STD: " + String(noise, 2) + "uV";
+	g.setFont(Font("Small Text", 13, Font::plain));
+	g.setColour(Colours::white);
 
-    if (channel == 0)
-    {
-        //double depth = processor->getSelectedElectrodeDepth();
-        //String d = "Depth: "+String(depth,4) +" mm";
-        //g.setFont(Font("Small Text", 13, Font::plain));
-        // g.setColour(Colours::white);
-
-        //g.drawText(d,10,10,150,20,Justification::left,false);
-    }
+	g.drawText(d, 10, 10, 150, 20, Justification::left, false);
     // draw the grid lines for the waveforms
 
     // draw the threshold line and labels
-- 
GitLab