Skip to content

Commit 88280fa

Browse files
committed
Refactor FFT settings variables across the GUI and Add GlobalFFTSettings Class
1 parent 93f39be commit 88280fa

10 files changed

Lines changed: 825 additions & 457 deletions

OpenBCI_GUI/BandPowerEnums.pde

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
2+
public enum BPAutoClean implements IndexingInterface
3+
{
4+
ON (0, "On"),
5+
OFF (1, "Off");
6+
7+
private int index;
8+
private String label;
9+
private static BPAutoClean[] vals = values();
10+
11+
BPAutoClean(int _index, String _label) {
12+
this.index = _index;
13+
this.label = _label;
14+
}
15+
16+
@Override
17+
public String getString() {
18+
return label;
19+
}
20+
21+
@Override
22+
public int getIndex() {
23+
return index;
24+
}
25+
26+
public static List<String> getEnumStringsAsList() {
27+
List<String> enumStrings = new ArrayList<String>();
28+
for (IndexingInterface val : vals) {
29+
enumStrings.add(val.getString());
30+
}
31+
return enumStrings;
32+
}
33+
}
34+
35+
public enum BPAutoCleanThreshold implements IndexingInterface
36+
{
37+
FORTY (0, 40f, "40 uV"),
38+
FIFTY (1, 50f, "50 uV"),
39+
SIXTY (2, 60f, "60 uV"),
40+
SEVENTY (3, 70f, "70 uV"),
41+
EIGHTY (4, 80f, "80 uV"),
42+
NINETY (5, 90f, "90 uV"),
43+
ONE_HUNDRED(6, 100f, "100 uV");
44+
45+
private int index;
46+
private float value;
47+
private String label;
48+
private static BPAutoCleanThreshold[] vals = values();
49+
50+
BPAutoCleanThreshold(int _index, float _value, String _label) {
51+
this.index = _index;
52+
this.value = _value;
53+
this.label = _label;
54+
}
55+
56+
public float getValue() {
57+
return value;
58+
}
59+
60+
@Override
61+
public String getString() {
62+
return label;
63+
}
64+
65+
@Override
66+
public int getIndex() {
67+
return index;
68+
}
69+
70+
public static List<String> getEnumStringsAsList() {
71+
List<String> enumStrings = new ArrayList<String>();
72+
for (IndexingInterface val : vals) {
73+
enumStrings.add(val.getString());
74+
}
75+
return enumStrings;
76+
}
77+
}
78+
79+
public enum BPAutoCleanTimer implements IndexingInterface
80+
{
81+
HALF_SECOND (0, 500, ".5 sec"),
82+
ONE_SECOND (1, 1000, "1 sec"),
83+
THREE_SECONDS (2, 2000, "3 sec"),
84+
FIVE_SECONDS (3, 5000, "5 sec"),
85+
TEN_SECONDS (4, 10000, "10 sec"),
86+
TWENTY_SECONDS (5, 20000, "20 sec"),
87+
THIRTY_SECONDS(6, 30000, "30 sec");
88+
89+
private int index;
90+
private float value;
91+
private String label;
92+
private static BPAutoCleanTimer[] vals = values();
93+
94+
BPAutoCleanTimer(int _index, float _value, String _label) {
95+
this.index = _index;
96+
this.value = _value;
97+
this.label = _label;
98+
}
99+
100+
public float getValue() {
101+
return value;
102+
}
103+
104+
@Override
105+
public String getString() {
106+
return label;
107+
}
108+
109+
@Override
110+
public int getIndex() {
111+
return index;
112+
}
113+
114+
public static List<String> getEnumStringsAsList() {
115+
List<String> enumStrings = new ArrayList<String>();
116+
for (IndexingInterface val : vals) {
117+
enumStrings.add(val.getString());
118+
}
119+
return enumStrings;
120+
}
121+
}

OpenBCI_GUI/DataProcessing.pde

Lines changed: 64 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,24 @@ void processNewData() {
2828
}
2929

3030
//update the data buffers
31-
for (int Ichan=0; Ichan < channelCount; Ichan++) {
31+
for (int channel=0; channel < channelCount; channel++) {
3232
for(int i = 0; i < getCurrentBoardBufferSize(); i++) {
33-
dataProcessingRawBuffer[Ichan][i] = (float)currentData.get(i)[exgChannels[Ichan]];
33+
dataProcessingRawBuffer[channel][i] = (float)currentData.get(i)[exgChannels[channel]];
3434
}
3535

36-
dataProcessingFilteredBuffer[Ichan] = dataProcessingRawBuffer[Ichan].clone();
36+
dataProcessingFilteredBuffer[channel] = dataProcessingRawBuffer[channel].clone();
3737
}
3838

3939
//apply additional processing for the time-domain montage plot (ie, filtering)
4040
dataProcessing.process(dataProcessingFilteredBuffer, fftBuff);
4141

4242
//look to see if the latest data is railed so that we can notify the user on the GUI
43-
for (int Ichan=0; Ichan < globalChannelCount; Ichan++) is_railed[Ichan].update(dataProcessingRawBuffer[Ichan], Ichan);
43+
for (int channel=0; channel < globalChannelCount; channel++) is_railed[channel].update(dataProcessingRawBuffer[channel], channel);
4444

4545
//compute the electrode impedance. Do it in a very simple way [rms to amplitude, then uVolt to Volt, then Volt/Amp to Ohm]
46-
for (int Ichan=0; Ichan < globalChannelCount; Ichan++) {
46+
for (int channel=0; channel < globalChannelCount; channel++) {
4747
// Calculate the impedance
48-
float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[Ichan]*1.0e-6) / BoardCytonConstants.leadOffDrive_amps;
48+
float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[channel]*1.0e-6) / BoardCytonConstants.leadOffDrive_amps;
4949
// Subtract the 2.2kOhm resistor
5050
impedance -= BoardCytonConstants.series_resistor_ohms;
5151
// Verify the impedance is not less than 0
@@ -54,25 +54,24 @@ void processNewData() {
5454
impedance = 0;
5555
}
5656
// Store to the global variable
57-
data_elec_imp_ohm[Ichan] = impedance;
57+
data_elec_imp_ohm[channel] = impedance;
5858
}
5959
}
6060

61-
void initializeFFTObjects(ddf.minim.analysis.FFT[] fftBuff, float[][] dataProcessingRawBuffer, int Nfft, float fs_Hz) {
62-
61+
void initializeFFTObjects(ddf.minim.analysis.FFT[] fftBuff, float[][] dataProcessingRawBuffer, int fftPointCount, float fs_Hz) {
6362
float[] fooData;
64-
for (int Ichan=0; Ichan < globalChannelCount; Ichan++) {
63+
for (int channel=0; channel < globalChannelCount; channel++) {
6564
//make the FFT objects...Following "SoundSpectrum" example that came with the Minim library
66-
fftBuff[Ichan].window(ddf.minim.analysis.FFT.HAMMING);
65+
fftBuff[channel].window(ddf.minim.analysis.FFT.HAMMING);
6766

6867
//do the FFT on the initial data
69-
if (isFFTFiltered == true) {
70-
fooData = dataProcessingFilteredBuffer[Ichan]; //use the filtered data for the FFT
68+
if (globalFFTSettings.getDataIsFiltered()) {
69+
fooData = dataProcessingFilteredBuffer[channel]; //use the filtered data for the FFT
7170
} else {
72-
fooData = dataProcessingRawBuffer[Ichan]; //use the raw data for the FFT
71+
fooData = dataProcessingRawBuffer[channel]; //use the raw data for the FFT
7372
}
74-
fooData = Arrays.copyOfRange(fooData, fooData.length-Nfft, fooData.length);
75-
fftBuff[Ichan].forward(fooData); //compute FFT on this channel of data
73+
fooData = Arrays.copyOfRange(fooData, fooData.length-fftPointCount, fooData.length);
74+
fftBuff[channel].forward(fooData); //compute FFT on this channel of data
7675
}
7776
}
7877

@@ -112,37 +111,37 @@ class DataProcessing {
112111
}
113112

114113
//Process data on a channel-by-channel basis
115-
private synchronized void processChannel(int Ichan, float[][] data_forDisplay_uV, float[] prevFFTdata) {
116-
int Nfft = getNfftSafe();
114+
private synchronized void processChannel(int channel, float[][] data_forDisplay_uV, float[] prevFFTdata) {
115+
int fftPointCount = getNumFFTPoints();
117116
double foo;
118117

119118
// Filter the data in the time domain
120119
// TODO: Use double arrays here and convert to float only to plot data.
121120
// ^^^ This might not feasible or meaningful performance improvement. I looked into it a while ago and it seems we need floats for the FFT library also. -RW 2022)
122121
try {
123-
double[] tempArray = floatToDoubleArray(data_forDisplay_uV[Ichan]);
122+
double[] tempArray = floatToDoubleArray(data_forDisplay_uV[channel]);
124123

125124
//Apply BandStop filter if the filter should be active on this channel
126-
if (filterSettings.values.bandStopFilterActive[Ichan].isActive()) {
125+
if (filterSettings.values.bandStopFilterActive[channel].isActive()) {
127126
DataFilter.perform_bandstop(
128127
tempArray,
129128
currentBoard.getSampleRate(),
130-
filterSettings.values.bandStopStartFreq[Ichan],
131-
filterSettings.values.bandStopStopFreq[Ichan],
132-
filterSettings.values.bandStopFilterOrder[Ichan].getValue(),
133-
filterSettings.values.bandStopFilterType[Ichan].getValue(),
129+
filterSettings.values.bandStopStartFreq[channel],
130+
filterSettings.values.bandStopStopFreq[channel],
131+
filterSettings.values.bandStopFilterOrder[channel].getValue(),
132+
filterSettings.values.bandStopFilterType[channel].getValue(),
134133
1.0);
135134
}
136135

137136
//Apply BandPass filter if the filter should be active on this channel
138-
if (filterSettings.values.bandPassFilterActive[Ichan].isActive()) {
137+
if (filterSettings.values.bandPassFilterActive[channel].isActive()) {
139138
DataFilter.perform_bandpass(
140139
tempArray,
141140
currentBoard.getSampleRate(),
142-
filterSettings.values.bandPassStartFreq[Ichan],
143-
filterSettings.values.bandPassStopFreq[Ichan],
144-
filterSettings.values.bandPassFilterOrder[Ichan].getValue(),
145-
filterSettings.values.bandPassFilterType[Ichan].getValue(),
141+
filterSettings.values.bandPassStartFreq[channel],
142+
filterSettings.values.bandPassStopFreq[channel],
143+
filterSettings.values.bandPassFilterOrder[channel].getValue(),
144+
filterSettings.values.bandPassFilterType[channel].getValue(),
146145
1.0);
147146
}
148147

@@ -190,66 +189,68 @@ class DataProcessing {
190189
break;
191190
}
192191

193-
doubleToFloatArray(tempArray, data_forDisplay_uV[Ichan]);
192+
doubleToFloatArray(tempArray, data_forDisplay_uV[channel]);
194193
} catch (BrainFlowError e) {
195194
e.printStackTrace();
196195
}
197196

198197
//compute the standard deviation of the filtered signal...this is for the head plot
199-
float[] fooData_filt = dataProcessingFilteredBuffer[Ichan]; //use the filtered data
198+
float[] fooData_filt = dataProcessingFilteredBuffer[channel]; //use the filtered data
200199
fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data
201-
data_std_uV[Ichan]=std(fooData_filt); //compute the standard deviation for the whole array "fooData_filt"
200+
data_std_uV[channel] = std(fooData_filt); //compute the standard deviation for the whole array "fooData_filt"
202201

203202
//copy the previous FFT data...enables us to apply some smoothing to the FFT data
204-
for (int I=0; I < fftBuff[Ichan].specSize(); I++) {
205-
prevFFTdata[I] = fftBuff[Ichan].getBand(I); //copy the old spectrum values
203+
for (int I=0; I < fftBuff[channel].specSize(); I++) {
204+
prevFFTdata[I] = fftBuff[channel].getBand(I); //copy the old spectrum values
206205
}
207206

208207
//prepare the data for the new FFT
209208
float[] fooData;
210-
if (isFFTFiltered == true) {
211-
fooData = dataProcessingFilteredBuffer[Ichan]; //use the filtered data for the FFT
209+
if (globalFFTSettings.getDataIsFiltered()) {
210+
fooData = dataProcessingFilteredBuffer[channel]; //use the filtered data for the FFT
212211
} else {
213-
fooData = dataProcessingRawBuffer[Ichan]; //use the raw data for the FFT
212+
fooData = dataProcessingRawBuffer[channel]; //use the raw data for the FFT
214213
}
215-
fooData = Arrays.copyOfRange(fooData, fooData.length-Nfft, fooData.length); //trim to grab just the most recent block of data
214+
fooData = Arrays.copyOfRange(fooData, fooData.length-fftPointCount, fooData.length); //trim to grab just the most recent block of data
216215
float meanData = mean(fooData); //compute the mean
217216
for (int I=0; I < fooData.length; I++) fooData[I] -= meanData; //remove the mean (for a better looking FFT
218217

219218
//compute the FFT
220-
fftBuff[Ichan].forward(fooData); //compute FFT on this channel of data
219+
fftBuff[channel].forward(fooData); //compute FFT on this channel of data
221220

222221
// FFT ref: https://www.mathworks.com/help/matlab/ref/fft.html
223222
// first calculate double-sided FFT amplitude spectrum
224-
for (int I=0; I <= Nfft/2; I++) {
225-
fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) / Nfft));
223+
for (int I=0; I <= fftPointCount/2; I++) {
224+
fftBuff[channel].setBand(I, (float)(fftBuff[channel].getBand(I) / fftPointCount));
226225
}
227226
// then convert into single-sided FFT spectrum: DC & Nyquist (i=0 & i=N/2) remain the same, others multiply by two.
228-
for (int I=1; I < Nfft/2; I++) {
229-
fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) * 2));
227+
for (int I=1; I < fftPointCount/2; I++) {
228+
fftBuff[channel].setBand(I, (float)(fftBuff[channel].getBand(I) * 2));
230229
}
231230

232231
//average the FFT with previous FFT data so that it makes it smoother in time
233232
double min_val = 0.01d;
234-
for (int I=0; I < fftBuff[Ichan].specSize(); I++) { //loop over each fft bin
233+
float smoothingFactor = globalFFTSettings.getSmoothingFactor().getValue();
234+
for (int I=0; I < fftBuff[channel].specSize(); I++) { //loop over each fft bin
235235
if (prevFFTdata[I] < min_val) prevFFTdata[I] = (float)min_val; //make sure we're not too small for the log calls
236-
foo = fftBuff[Ichan].getBand(I);
236+
foo = fftBuff[channel].getBand(I);
237237
if (foo < min_val) foo = min_val; //make sure this value isn't too small
238238

239239
if (true) {
240240
//smooth in dB power space
241-
foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.log(java.lang.Math.pow(foo, 2));
242-
foo += smoothFac[smoothFac_ind] * java.lang.Math.log(java.lang.Math.pow((double)prevFFTdata[I], 2));
241+
foo = (1.0d - smoothingFactor) * java.lang.Math.log(java.lang.Math.pow(foo, 2));
242+
foo += smoothingFactor * java.lang.Math.log(java.lang.Math.pow((double)prevFFTdata[I], 2));
243243
foo = java.lang.Math.sqrt(java.lang.Math.exp(foo)); //average in dB space
244244
} else {
245+
//LEGACY CODE -- NOT USED
245246
//smooth (average) in linear power space
246-
foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.pow(foo, 2);
247-
foo+= smoothFac[smoothFac_ind] * java.lang.Math.pow((double)prevFFTdata[I], 2);
247+
foo = (1.0d - smoothingFactor) * java.lang.Math.pow(foo, 2);
248+
foo+= smoothingFactor * java.lang.Math.pow((double)prevFFTdata[I], 2);
248249
// take sqrt to be back into uV_rtHz
249250
foo = java.lang.Math.sqrt(foo);
250251
}
251-
fftBuff[Ichan].setBand(I, (float)foo); //put the smoothed data back into the fftBuff data holder for use by everyone else
252-
// fftBuff[Ichan].setBand(I, 1.0f); // test
252+
fftBuff[channel].setBand(I, (float)foo); //put the smoothed data back into the fftBuff data holder for use by everyone else
253+
// fftBuff[channel].setBand(I, 1.0f); // test
253254
} //end loop over FFT bins
254255

255256
// calculate single-sided psd by single-sided FFT amplitude spectrum
@@ -260,22 +261,22 @@ class DataProcessing {
260261
for (int i = 0; i < processing_band_low_Hz.length; i++) {
261262
float sum = 0;
262263
// int binNum = 0;
263-
for (int Ibin = 0; Ibin <= Nfft/2; Ibin ++) { // loop over FFT bins
264-
float FFT_freq_Hz = fftBuff[Ichan].indexToFreq(Ibin); // center frequency of this bin
264+
for (int Ibin = 0; Ibin <= fftPointCount/2; Ibin ++) { // loop over FFT bins
265+
float FFT_freq_Hz = fftBuff[channel].indexToFreq(Ibin); // center frequency of this bin
265266
float psdx = 0;
266267
// if the frequency matches a band
267268
if (FFT_freq_Hz >= processing_band_low_Hz[i] && FFT_freq_Hz < processing_band_high_Hz[i]) {
268-
if (Ibin != 0 && Ibin != Nfft/2) {
269-
psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/currentBoard.getSampleRate() / 4;
269+
if (Ibin != 0 && Ibin != fftPointCount/2) {
270+
psdx = fftBuff[channel].getBand(Ibin) * fftBuff[channel].getBand(Ibin) * fftPointCount/currentBoard.getSampleRate() / 4;
270271
}
271272
else {
272-
psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/currentBoard.getSampleRate();
273+
psdx = fftBuff[channel].getBand(Ibin) * fftBuff[channel].getBand(Ibin) * fftPointCount/currentBoard.getSampleRate();
273274
}
274275
sum += psdx;
275276
// binNum ++;
276277
}
277278
}
278-
avgPowerInBins[Ichan][i] = sum; // total power in a band
279+
avgPowerInBins[channel][i] = sum; // total power in a band
279280
// println(i, binNum, sum);
280281
}
281282
}
@@ -284,8 +285,8 @@ class DataProcessing {
284285

285286
float prevFFTdata[] = new float[fftBuff[0].specSize()];
286287

287-
for (int Ichan=0; Ichan < globalChannelCount; Ichan++) {
288-
processChannel(Ichan, data_forDisplay_uV, prevFFTdata);
288+
for (int channel=0; channel < globalChannelCount; channel++) {
289+
processChannel(channel, data_forDisplay_uV, prevFFTdata);
289290
} //end the loop over channels.
290291

291292
for (int i = 0; i < processing_band_low_Hz.length; i++) {
@@ -304,14 +305,14 @@ class DataProcessing {
304305
float[] refData_uV = dataProcessingFilteredBuffer[refChanInd]; //use the filtered data
305306
refData_uV = Arrays.copyOfRange(refData_uV, refData_uV.length-((int)fs_Hz), refData_uV.length); //just grab the most recent second of data
306307
// Compute polarity of each channel
307-
for (int Ichan=0; Ichan < globalChannelCount; Ichan++) {
308-
float[] fooData_filt = dataProcessingFilteredBuffer[Ichan]; //use the filtered data
308+
for (int channel=0; channel < globalChannelCount; channel++) {
309+
float[] fooData_filt = dataProcessingFilteredBuffer[channel]; //use the filtered data
309310
fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data
310311
float dotProd = calcDotProduct(fooData_filt, refData_uV);
311312
if (dotProd >= 0.0f) {
312-
polarity[Ichan]=1.0;
313+
polarity[channel]=1.0;
313314
} else {
314-
polarity[Ichan]=-1.0;
315+
polarity[channel]=-1.0;
315316
}
316317
}
317318

0 commit comments

Comments
 (0)