First Push
|
Depois Largura: | Altura: | Tamanho: 266 KiB |
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package=""
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<uses-sdk android:minSdkVersion="10" />
|
||||
<application android:label=""
|
||||
android:icon="@drawable/icon"
|
||||
android:debuggable="true">
|
||||
<activity android:name="">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,196 @@
|
||||
|
||||
////////////////////
|
||||
//
|
||||
// This class creates and manages a button for use on the screen to trigger actions.
|
||||
//
|
||||
// Created: Chip Audette, Oct 2013.
|
||||
// Modified: Conor Russomanno, Oct 2014
|
||||
//
|
||||
// Based on Processing's "Button" example code
|
||||
//
|
||||
////////////////////
|
||||
|
||||
class Button {
|
||||
|
||||
int but_x, but_y, but_dx, but_dy; // Position of square button
|
||||
//int rectSize = 90; // Diameter of rect
|
||||
|
||||
color currentColor;
|
||||
color color_hover = color(127, 134, 143);//color(252, 221, 198);
|
||||
color color_pressed = color(150,170,200); //bgColor;
|
||||
color color_highlight = color(102);
|
||||
color color_notPressed = color(255); //color(227,118,37);
|
||||
color buttonStrokeColor = bgColor;
|
||||
color textColorActive = color(255);
|
||||
color textColorNotActive = bgColor;
|
||||
color rectHighlight;
|
||||
boolean drawHand = false;
|
||||
//boolean isMouseHere = false;
|
||||
boolean buttonHasStroke = true;
|
||||
boolean isActive = false;
|
||||
boolean isDropdownButton = false;
|
||||
boolean wasPressed = false;
|
||||
public String but_txt;
|
||||
PFont buttonFont = f2;
|
||||
|
||||
public Button(int x, int y, int w, int h, String txt, int fontSize) {
|
||||
setup(x, y, w, h, txt);
|
||||
//println(PFont.list()); //see which fonts are available
|
||||
//font = createFont("SansSerif.plain",fontSize);
|
||||
//font = createFont("Lucida Sans Regular",fontSize);
|
||||
// font = createFont("Arial",fontSize);
|
||||
//font = loadFont("SansSerif.plain.vlw");
|
||||
}
|
||||
|
||||
public void setup(int x, int y, int w, int h, String txt) {
|
||||
but_x = x;
|
||||
but_y = y;
|
||||
but_dx = w;
|
||||
but_dy = h;
|
||||
setString(txt);
|
||||
}
|
||||
|
||||
public void setString(String txt) {
|
||||
but_txt = txt;
|
||||
//println("Button: setString: string = " + txt);
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return isActive;
|
||||
}
|
||||
|
||||
public void setIsActive(boolean val) {
|
||||
isActive = val;
|
||||
}
|
||||
|
||||
public void makeDropdownButton(boolean val) {
|
||||
isDropdownButton = val;
|
||||
}
|
||||
|
||||
public boolean isMouseHere() {
|
||||
if ( overRect(but_x, but_y, but_dx, but_dy) ) {
|
||||
cursor(HAND);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
color getColor() {
|
||||
if (isActive) {
|
||||
currentColor = color_pressed;
|
||||
} else if (isMouseHere()) {
|
||||
currentColor = color_hover;
|
||||
} else {
|
||||
currentColor = color_notPressed;
|
||||
}
|
||||
return currentColor;
|
||||
}
|
||||
|
||||
public void setCurrentColor(color _color){
|
||||
currentColor = _color;
|
||||
}
|
||||
|
||||
public void setColorPressed(color _color) {
|
||||
color_pressed = _color;
|
||||
}
|
||||
public void setColorNotPressed(color _color) {
|
||||
color_notPressed = _color;
|
||||
}
|
||||
|
||||
public void setStrokeColor(color _color) {
|
||||
buttonStrokeColor = _color;
|
||||
}
|
||||
|
||||
public void hasStroke(boolean _trueORfalse) {
|
||||
buttonHasStroke = _trueORfalse;
|
||||
}
|
||||
|
||||
boolean overRect(int x, int y, int width, int height) {
|
||||
if (mouseX >= x && mouseX <= x+width &&
|
||||
mouseY >= y && mouseY <= y+height) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(int _x, int _y) {
|
||||
but_x = _x;
|
||||
but_y = _y;
|
||||
draw();
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
//draw the button
|
||||
fill(getColor());
|
||||
if (buttonHasStroke) {
|
||||
stroke(buttonStrokeColor); //button border
|
||||
} else {
|
||||
noStroke();
|
||||
}
|
||||
// noStroke();
|
||||
rect(but_x, but_y, but_dx, but_dy);
|
||||
|
||||
//draw the text
|
||||
if (isActive) {
|
||||
fill(textColorActive);
|
||||
} else {
|
||||
fill(textColorNotActive);
|
||||
}
|
||||
stroke(255);
|
||||
textFont(buttonFont); //load f2 ... from control panel
|
||||
textSize(12);
|
||||
textAlign(CENTER, CENTER);
|
||||
textLeading(round(0.9*(textAscent()+textDescent())));
|
||||
// int x1 = but_x+but_dx/2;
|
||||
// int y1 = but_y+but_dy/2;
|
||||
int x1, y1;
|
||||
//no auto wrap
|
||||
x1 = but_x+but_dx/2;
|
||||
y1 = but_y+but_dy/2;
|
||||
text(but_txt, x1, y1);
|
||||
|
||||
//draw open/close arrow if it's a dropdown button
|
||||
if (isDropdownButton) {
|
||||
pushStyle();
|
||||
fill(255);
|
||||
noStroke();
|
||||
// smooth();
|
||||
// stroke(255);
|
||||
// strokeWeight(1);
|
||||
if (isActive) {
|
||||
float point1x = but_x + (but_dx - ((3f*but_dy)/4f));
|
||||
float point1y = but_y + but_dy/3f;
|
||||
float point2x = but_x + (but_dx-(but_dy/4f));
|
||||
float point2y = but_y + but_dy/3f;
|
||||
float point3x = but_x + (but_dx - (but_dy/2f));
|
||||
float point3y = but_y + (2f*but_dy)/3f;
|
||||
triangle(point1x, point1y, point2x, point2y, point3x, point3y); //downward triangle, indicating open
|
||||
} else {
|
||||
float point1x = but_x + (but_dx - ((3f*but_dy)/4f));
|
||||
float point1y = but_y + (2f*but_dy)/3f;
|
||||
float point2x = but_x + (but_dx-(but_dy/4f));
|
||||
float point2y = but_y + (2f*but_dy)/3f;
|
||||
float point3x = but_x + (but_dx - (but_dy/2f));
|
||||
float point3y = but_y + but_dy/3f;
|
||||
triangle(point1x, point1y, point2x, point2y, point3x, point3y); //upward triangle, indicating closed
|
||||
}
|
||||
popStyle();
|
||||
}
|
||||
|
||||
if (true) {
|
||||
if (!isMouseHere() && drawHand) {
|
||||
cursor(ARROW);
|
||||
drawHand = false;
|
||||
verbosePrint("don't draw hand");
|
||||
}
|
||||
//if cursor is over button change cursor icon to hand!
|
||||
if (isMouseHere() && !drawHand) {
|
||||
cursor(HAND);
|
||||
drawHand = true;
|
||||
verbosePrint("draw hand");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,583 @@
|
||||
|
||||
|
||||
//these arrays of channel values need to be global so that they don't reset on screen resize, when GUI reinitializes (there's definitely a more efficient way to do this...)
|
||||
int numSettingsPerChannel = 6; //each channel has 6 different settings
|
||||
char[][] channelSettingValues = new char [nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button
|
||||
char[][] impedanceCheckValues = new char [nchan][2];
|
||||
|
||||
public void updateChannelArrays(int _nchan) {
|
||||
channelSettingValues = new char [_nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button
|
||||
impedanceCheckValues = new char [_nchan][2];
|
||||
}
|
||||
|
||||
// color[] channelColors = new color[16];
|
||||
color[] channelColors = {
|
||||
color(129, 129, 129),
|
||||
color(124, 75, 141),
|
||||
color(54, 87, 158),
|
||||
color(49, 113, 89),
|
||||
color(221, 178, 13),
|
||||
color(253, 94, 52),
|
||||
color(224, 56, 45),
|
||||
color(162, 82, 49)
|
||||
};
|
||||
|
||||
class ChannelController {
|
||||
|
||||
public float x1, y1, w1, h1, x2, y2, w2, h2; //all 1 values refer to the left panel that is always visible ... al 2 values refer to the right panel that is only visible when showFullController = true
|
||||
public int montage_w, montage_h;
|
||||
public int rowHeight;
|
||||
public int buttonSpacing;
|
||||
boolean showFullController = false;
|
||||
boolean[] drawImpedanceValues = new boolean [nchan];
|
||||
|
||||
int spacer1 = 3;
|
||||
int spacer2 = 5; //space between buttons
|
||||
|
||||
// [Number of Channels] x 6 array of buttons for channel settings
|
||||
Button[][] channelSettingButtons = new Button [nchan][numSettingsPerChannel]; // [channel#][Button#]
|
||||
// char[][] channelSettingValues = new char [nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button
|
||||
|
||||
//buttons just to the left of
|
||||
Button[][] impedanceCheckButtons = new Button [nchan][2];
|
||||
// char [][] impedanceCheckValues = new char [nchan][2];
|
||||
|
||||
// Array for storing SRB2 history settings of channels prior to shutting off .. so you can return to previous state when reactivating channel
|
||||
char[] previousSRB2 = new char [nchan];
|
||||
// Array for storing SRB2 history settings of channels prior to shutting off .. so you can return to previous state when reactivating channel
|
||||
char[] previousBIAS = new char [nchan];
|
||||
|
||||
//maximum different values for the different settings (Power Down, Gain, Input Type, BIAS, SRB2, SRB1) of
|
||||
//refer to page 44 of ADS1299 Datasheet: http://www.ti.com/lit/ds/symlink/ads1299.pdf
|
||||
char[] maxValuesPerSetting = {
|
||||
'1', // Power Down :: (0)ON, (1)OFF
|
||||
'6', // Gain :: (0) x1, (1) x2, (2) x4, (3) x6, (4) x8, (5) x12, (6) x24 ... default
|
||||
'7', // Channel Input :: (0)Normal Electrode Input, (1)Input Shorted, (2)Used in conjunction with BIAS_MEAS, (3)MVDD for supply measurement, (4)Temperature Sensor, (5)Test Signal, (6)BIAS_DRP ... positive electrode is driver, (7)BIAS_DRN ... negative electrode is driver
|
||||
'1', // BIAS :: (0) Yes, (1) No
|
||||
'1', // SRB2 :: (0) Open, (1) Closed
|
||||
'1'
|
||||
}; // SRB1 :: (0) Yes, (1) No ... this setting affects all channels ... either all on or all off
|
||||
|
||||
//variables used for channel write timing in writeChannelSettings()
|
||||
//long timeOfLastChannelWrite = 0;
|
||||
int channelToWrite = -1;
|
||||
//int channelWriteCounter = 0;
|
||||
//boolean isWritingChannel = false;
|
||||
|
||||
//variables use for imp write timing with writeImpedanceSettings()
|
||||
//long timeOfLastImpWrite = 0;
|
||||
int impChannelToWrite = -1;
|
||||
//int impWriteCounter = 0;
|
||||
//boolean isWritingImp = false;
|
||||
|
||||
boolean rewriteChannelWhenDoneWriting = false;
|
||||
int channelToWriteWhenDoneWriting = 0;
|
||||
|
||||
boolean rewriteImpedanceWhenDoneWriting = false;
|
||||
int impChannelToWriteWhenDoneWriting = 0;
|
||||
char final_pORn = '0';
|
||||
char final_onORoff = '0';
|
||||
|
||||
ChannelController(float _xPos, float _yPos, float _width, float _height, float _montage_w, float _montage_h) {
|
||||
//positioning values for left panel (that is always visible)
|
||||
x1 = _xPos;
|
||||
y1 = _yPos;
|
||||
w1 = _width;
|
||||
h1 = _height;
|
||||
|
||||
//positioning values for right panel that is only visible when showFullController = true (behind the graph)
|
||||
x2 = x1 + w1;
|
||||
// x2 = gui.showMontageButton.but_x;
|
||||
y2 = y1;
|
||||
w2 = _montage_w;
|
||||
h2 = h1;
|
||||
|
||||
createChannelSettingButtons();
|
||||
|
||||
// set on/off buttons to default channel colors
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
channelSettingButtons[i][0].setColorNotPressed(channelColors[i%8]);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadDefaultChannelSettings() {
|
||||
verbosePrint("ChannelController: loading default channel settings to GUI's channel controller...");
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
verbosePrint("chan: " + i + " ");
|
||||
for (int j = 0; j < numSettingsPerChannel; j++) { //channel setting values
|
||||
channelSettingValues[i][j] = char(openBCI.get_defaultChannelSettings().toCharArray()[j]); //parse defaultChannelSettings string created in the OpenBCI_ADS1299 class
|
||||
if (j == numSettingsPerChannel - 1) {
|
||||
println(char(openBCI.get_defaultChannelSettings().toCharArray()[j]));
|
||||
} else {
|
||||
print(char(openBCI.get_defaultChannelSettings().toCharArray()[j]) + ",");
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < 2; k++) { //impedance setting values
|
||||
impedanceCheckValues[i][k] = '0';
|
||||
}
|
||||
}
|
||||
verbosePrint("made it!");
|
||||
update(); //update 1 time to refresh button values based on new loaded settings
|
||||
}
|
||||
|
||||
public void update() {
|
||||
|
||||
//make false to check again below
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
drawImpedanceValues[i] = false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < nchan; i++) { //for every channel
|
||||
//update buttons based on channelSettingValues[i][j]
|
||||
for (int j = 0; j < numSettingsPerChannel; j++) {
|
||||
switch(j) { //what setting are we looking at
|
||||
case 0: //on/off ??
|
||||
if (channelSettingValues[i][j] == '0') channelSettingButtons[i][0].setColorNotPressed(channelColors[i%8]);// power down == false, set color to vibrant
|
||||
if (channelSettingValues[i][j] == '1') channelSettingButtons[i][0].setColorNotPressed(color(75)); // channelSettingButtons[i][0].setString("B"); // power down == true, set color to dark gray, indicating power down
|
||||
break;
|
||||
case 1: //GAIN ??
|
||||
if (channelSettingValues[i][j] == '0') channelSettingButtons[i][1].setString("x1");
|
||||
if (channelSettingValues[i][j] == '1') channelSettingButtons[i][1].setString("x2");
|
||||
if (channelSettingValues[i][j] == '2') channelSettingButtons[i][1].setString("x4");
|
||||
if (channelSettingValues[i][j] == '3') channelSettingButtons[i][1].setString("x6");
|
||||
if (channelSettingValues[i][j] == '4') channelSettingButtons[i][1].setString("x8");
|
||||
if (channelSettingValues[i][j] == '5') channelSettingButtons[i][1].setString("x12");
|
||||
if (channelSettingValues[i][j] == '6') channelSettingButtons[i][1].setString("x24");
|
||||
break;
|
||||
case 2: //input type ??
|
||||
if (channelSettingValues[i][j] == '0') channelSettingButtons[i][2].setString("Normal");
|
||||
if (channelSettingValues[i][j] == '1') channelSettingButtons[i][2].setString("Shorted");
|
||||
if (channelSettingValues[i][j] == '2') channelSettingButtons[i][2].setString("BIAS_MEAS");
|
||||
if (channelSettingValues[i][j] == '3') channelSettingButtons[i][2].setString("MVDD");
|
||||
if (channelSettingValues[i][j] == '4') channelSettingButtons[i][2].setString("Temp.");
|
||||
if (channelSettingValues[i][j] == '5') channelSettingButtons[i][2].setString("Test");
|
||||
if (channelSettingValues[i][j] == '6') channelSettingButtons[i][2].setString("BIAS_DRP");
|
||||
if (channelSettingValues[i][j] == '7') channelSettingButtons[i][2].setString("BIAS_DRN");
|
||||
break;
|
||||
case 3: //BIAS ??
|
||||
if (channelSettingValues[i][j] == '0') channelSettingButtons[i][3].setString("Don't Include");
|
||||
if (channelSettingValues[i][j] == '1') channelSettingButtons[i][3].setString("Include");
|
||||
break;
|
||||
case 4: // SRB2 ??
|
||||
if (channelSettingValues[i][j] == '0') channelSettingButtons[i][4].setString("Off");
|
||||
if (channelSettingValues[i][j] == '1') channelSettingButtons[i][4].setString("On");
|
||||
break;
|
||||
case 5: // SRB1 ??
|
||||
if (channelSettingValues[i][j] == '0') channelSettingButtons[i][5].setString("No");
|
||||
if (channelSettingValues[i][j] == '1') channelSettingButtons[i][5].setString("Yes");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < 2; k++) {
|
||||
switch(k) {
|
||||
case 0: // P Imp Buttons
|
||||
if (impedanceCheckValues[i][k] == '0') {
|
||||
impedanceCheckButtons[i][0].setColorNotPressed(color(75));
|
||||
impedanceCheckButtons[i][0].setString("");
|
||||
}
|
||||
if (impedanceCheckValues[i][k] == '1') {
|
||||
impedanceCheckButtons[i][0].setColorNotPressed(isSelected_color);
|
||||
impedanceCheckButtons[i][0].setString("");
|
||||
if (showFullController) {
|
||||
drawImpedanceValues[i] = false;
|
||||
} else {
|
||||
drawImpedanceValues[i] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: // N Imp Buttons
|
||||
if (impedanceCheckValues[i][k] == '0') {
|
||||
impedanceCheckButtons[i][1].setColorNotPressed(color(75));
|
||||
impedanceCheckButtons[i][1].setString("");
|
||||
}
|
||||
if (impedanceCheckValues[i][k] == '1') {
|
||||
impedanceCheckButtons[i][1].setColorNotPressed(isSelected_color);
|
||||
impedanceCheckButtons[i][1].setString("");
|
||||
if (showFullController) {
|
||||
drawImpedanceValues[i] = false;
|
||||
} else {
|
||||
drawImpedanceValues[i] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//then reset to 1
|
||||
|
||||
//
|
||||
if (openBCI.get_isWritingChannel()) {
|
||||
openBCI.writeChannelSettings(channelToWrite,channelSettingValues);
|
||||
}
|
||||
|
||||
if (rewriteChannelWhenDoneWriting == true && openBCI.get_isWritingChannel() == false) {
|
||||
initChannelWrite(channelToWriteWhenDoneWriting);
|
||||
rewriteChannelWhenDoneWriting = false;
|
||||
}
|
||||
|
||||
if (openBCI.get_isWritingImp()) {
|
||||
openBCI.writeImpedanceSettings(impChannelToWrite,impedanceCheckValues);
|
||||
}
|
||||
|
||||
if (rewriteImpedanceWhenDoneWriting == true && openBCI.get_isWritingImp() == false) {
|
||||
initImpWrite(impChannelToWriteWhenDoneWriting, final_pORn, final_onORoff);
|
||||
rewriteImpedanceWhenDoneWriting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
|
||||
pushStyle();
|
||||
noStroke();
|
||||
|
||||
//draw phantom rectangle to cover up random crap from Graph2D... we are replacing this stuff with the Montage Controller
|
||||
fill(bgColor);
|
||||
rect(x1 - 2, y1-(height*0.01f), w1, h1+(height*0.02f));
|
||||
|
||||
//draw light green rect behind pane title
|
||||
fill(216, 233, 171);
|
||||
rect(x2-2, y2-25, w2+1, 25);
|
||||
|
||||
//BG of montage controller (for debugging mainly)
|
||||
// fill(255,255,255,123);
|
||||
// rect(x1, y1 - 1, w1, h1);
|
||||
|
||||
//draw background pane of impedance buttons
|
||||
fill(221);
|
||||
rect(x1 + w1/3 + 1, y1, 2*(w1/3) - 3, h1 - 2);
|
||||
|
||||
//draw slightly darker line guides/separators for impedance buttons
|
||||
stroke(175);
|
||||
strokeWeight(2);
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
line(x1 + w1/3 + 2, y1 + (((h1-1)/(nchan+1))*(i+1)), x2 - 3, y1 + (((h1-1)/(nchan+1))*(i+1)));
|
||||
}
|
||||
line(x1 + 2*(w1/3) - 1, y1 + 1, x1 + 2*(w1/3) - 1, y1 + (h1-1) - 1);
|
||||
strokeWeight(0);
|
||||
|
||||
//channel buttons
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
channelSettingButtons[i][0].draw(); //draw on/off channel buttons
|
||||
//draw impedance buttons
|
||||
for (int j = 0; j < 2; j++) {
|
||||
impedanceCheckButtons[i][j].draw();
|
||||
}
|
||||
}
|
||||
|
||||
//label impedance button columns
|
||||
fill(bgColor);
|
||||
text("P", x1 + 1*(w1/2), y1 + 12);
|
||||
text("N", x1 + 5*(w1/6) - 2, y1 + 12);
|
||||
|
||||
if (showFullController) {
|
||||
//background
|
||||
noStroke();
|
||||
fill(0, 0, 0, 100);
|
||||
rect(x1 + w1, y1, w2, h2);
|
||||
|
||||
// [numChan] x 5 ... all channel setting buttons (other than on/off)
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
for (int j = 1; j < 6; j++) {
|
||||
// println("drawing button " + i + "," + j);
|
||||
// println("Button: " + channelSettingButtons[i][j]);
|
||||
channelSettingButtons[i][j].draw();
|
||||
}
|
||||
}
|
||||
|
||||
//draw column headers for channel settings behind EEG graph
|
||||
fill(bgColor);
|
||||
text("PGA Gain", x2 + (w2/10)*1, y1 - 12);
|
||||
text("Input Type", x2 + (w2/10)*3, y1 - 12);
|
||||
text(" Bias ", x2 + (w2/10)*5, y1 - 12);
|
||||
text("SRB2", x2 + (w2/10)*7, y1 - 12);
|
||||
text("SRB1", x2 + (w2/10)*9, y1 - 12);
|
||||
|
||||
//if mode is not from OpenBCI, draw a dark overlay to indicate that you cannot edit these settings
|
||||
if (eegDataSource != DATASOURCE_NORMAL && eegDataSource != DATASOURCE_NORMAL_W_AUX) {
|
||||
fill(0, 0, 0, 200);
|
||||
noStroke();
|
||||
rect(x2-2, y2, w2+1, h2);
|
||||
fill(255);
|
||||
textSize(24);
|
||||
text("DATA SOURCE (LIVE) only", x2 + (w2/2), y2 + (h2/2));
|
||||
}
|
||||
}
|
||||
|
||||
if ((eegDataSource != DATASOURCE_NORMAL) && (eegDataSource != DATASOURCE_NORMAL_W_AUX)) {
|
||||
fill(0, 0, 0, 200);
|
||||
rect(x1 + w1/3 + 1, y1, 2*(w1/3) - 3, h1 - 2);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
if (drawImpedanceValues[i] == true) {
|
||||
gui.impValuesMontage[i].draw(); //impedance values on montage plot
|
||||
}
|
||||
}
|
||||
|
||||
popStyle();
|
||||
}
|
||||
|
||||
public void mousePressed() {
|
||||
//if fullChannelController and one of the buttons (other than ON/OFF) is clicked
|
||||
|
||||
//if dataSource is coming from OpenBCI, allow user to interact with channel controller
|
||||
if ( (eegDataSource == DATASOURCE_NORMAL) || (eegDataSource == DATASOURCE_NORMAL_W_AUX) ) {
|
||||
if (showFullController) {
|
||||
for (int i = 0; i < nchan; i++) { //When [i][j] button is clicked
|
||||
for (int j = 1; j < numSettingsPerChannel; j++) {
|
||||
if (channelSettingButtons[i][j].isMouseHere()) {
|
||||
//increment [i][j] channelSettingValue by, until it reaches max values per setting [j],
|
||||
channelSettingButtons[i][j].wasPressed = true;
|
||||
channelSettingButtons[i][j].isActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//on/off button and Imp buttons can always be clicked/released
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
if (channelSettingButtons[i][0].isMouseHere()) {
|
||||
channelSettingButtons[i][0].wasPressed = true;
|
||||
channelSettingButtons[i][0].isActive = true;
|
||||
}
|
||||
|
||||
//only allow editing of impedance if dataSource == from OpenBCI
|
||||
if ((eegDataSource == DATASOURCE_NORMAL) || (eegDataSource == DATASOURCE_NORMAL_W_AUX)) {
|
||||
if (impedanceCheckButtons[i][0].isMouseHere()) {
|
||||
impedanceCheckButtons[i][0].wasPressed = true;
|
||||
impedanceCheckButtons[i][0].isActive = true;
|
||||
}
|
||||
if (impedanceCheckButtons[i][1].isMouseHere()) {
|
||||
impedanceCheckButtons[i][1].wasPressed = true;
|
||||
impedanceCheckButtons[i][1].isActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseReleased() {
|
||||
//if fullChannelController and one of the buttons (other than ON/OFF) is released
|
||||
if (showFullController) {
|
||||
for (int i = 0; i < nchan; i++) { //When [i][j] button is clicked
|
||||
for (int j = 1; j < numSettingsPerChannel; j++) {
|
||||
if (channelSettingButtons[i][j].isMouseHere() && channelSettingButtons[i][j].wasPressed == true) {
|
||||
if (channelSettingValues[i][j] < maxValuesPerSetting[j]) {
|
||||
channelSettingValues[i][j]++; //increment [i][j] channelSettingValue by, until it reaches max values per setting [j],
|
||||
} else {
|
||||
channelSettingValues[i][j] = '0';
|
||||
}
|
||||
// if you're not currently writing a channel and not waiting to rewrite after you've finished mashing the button
|
||||
if (!openBCI.get_isWritingChannel() && rewriteChannelWhenDoneWriting == false) {
|
||||
initChannelWrite(i);//write new ADS1299 channel row values to OpenBCI
|
||||
} else { //else wait until a the current write has finished and then write again ... this is to not overwrite the wrong values while writing a channel
|
||||
verbosePrint("CONGRATULATIONS, YOU'RE MASHING BUTTONS!");
|
||||
rewriteChannelWhenDoneWriting = true;
|
||||
channelToWriteWhenDoneWriting = i;
|
||||
}
|
||||
}
|
||||
|
||||
// if(!channelSettingButtons[i][j].isMouseHere()){
|
||||
channelSettingButtons[i][j].isActive = false;
|
||||
channelSettingButtons[i][j].wasPressed = false;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
//ON/OFF button can always be clicked/released
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
//was on/off clicked?
|
||||
if (channelSettingButtons[i][0].isMouseHere() && channelSettingButtons[i][0].wasPressed == true) {
|
||||
if (channelSettingValues[i][0] < maxValuesPerSetting[0]) {
|
||||
channelSettingValues[i][0] = '1'; //increment [i][j] channelSettingValue by, until it reaches max values per setting [j],
|
||||
// channelSettingButtons[i][0].setColorNotPressed(color(25,25,25));
|
||||
// powerDownChannel(i);
|
||||
deactivateChannel(i);
|
||||
} else {
|
||||
channelSettingValues[i][0] = '0';
|
||||
// channelSettingButtons[i][0].setColorNotPressed(color(255));
|
||||
// powerUpChannel(i);
|
||||
activateChannel(i);
|
||||
}
|
||||
// writeChannelSettings(i);//write new ADS1299 channel row values to OpenBCI
|
||||
}
|
||||
|
||||
//was P imp check button clicked?
|
||||
if (impedanceCheckButtons[i][0].isMouseHere() && impedanceCheckButtons[i][0].wasPressed == true) {
|
||||
if (impedanceCheckValues[i][0] < '1') {
|
||||
// impedanceCheckValues[i][0] = '1'; //increment [i][j] channelSettingValue by, until it reaches max values per setting [j],
|
||||
// channelSettingButtons[i][0].setColorNotPressed(color(25,25,25));
|
||||
// writeImpedanceSettings(i);
|
||||
initImpWrite(i, 'p', '1');
|
||||
//initImpWrite
|
||||
verbosePrint("a");
|
||||
} else {
|
||||
// impedanceCheckValues[i][0] = '0';
|
||||
// channelSettingButtons[i][0].setColorNotPressed(color(255));
|
||||
// writeImpedanceSettings(i);
|
||||
initImpWrite(i, 'p', '0');
|
||||
verbosePrint("b");
|
||||
}
|
||||
// writeChannelSettings(i);//write new ADS1299 channel row values to OpenBCI
|
||||
}
|
||||
|
||||
//was N imp check button clicked?
|
||||
if (impedanceCheckButtons[i][1].isMouseHere() && impedanceCheckButtons[i][1].wasPressed == true) {
|
||||
if (impedanceCheckValues[i][1] < '1') {
|
||||
initImpWrite(i, 'n', '1');
|
||||
//initImpWrite
|
||||
verbosePrint("c");
|
||||
} else {
|
||||
initImpWrite(i, 'n', '0');
|
||||
verbosePrint("d");
|
||||
}
|
||||
// writeChannelSettings(i);//write new ADS1299 channel row values to OpenBCI
|
||||
}
|
||||
|
||||
channelSettingButtons[i][0].isActive = false;
|
||||
channelSettingButtons[i][0].wasPressed = false;
|
||||
impedanceCheckButtons[i][0].isActive = false;
|
||||
impedanceCheckButtons[i][0].wasPressed = false;
|
||||
impedanceCheckButtons[i][1].isActive = false;
|
||||
impedanceCheckButtons[i][1].wasPressed = false;
|
||||
}
|
||||
|
||||
update(); //update once to refresh button values
|
||||
}
|
||||
|
||||
public void fillValuesBasedOnDefault(byte _defaultValues) {
|
||||
//interpret incoming HEX value (from OpenBCI) and pass into all default channelSettingValues
|
||||
//dencode byte from OpenBCI and break it apart into the channelSettingValues[][] array
|
||||
}
|
||||
|
||||
public void powerDownChannel(int _numChannel) {
|
||||
verbosePrint("Powering down channel " + str(int(_numChannel) + int(1)));
|
||||
//save SRB2 and BIAS settings in 2D history array (to turn back on when channel is reactivated)
|
||||
previousBIAS[_numChannel] = channelSettingValues[_numChannel][3];
|
||||
previousSRB2[_numChannel] = channelSettingValues[_numChannel][4];
|
||||
channelSettingValues[_numChannel][3] = '0'; //make sure to disconnect from BIAS
|
||||
channelSettingValues[_numChannel][4] = '0'; //make sure to disconnect from SRB2
|
||||
|
||||
// initChannelWrite(_numChannel);//writeChannelSettings
|
||||
channelSettingValues[_numChannel][0] = '1'; //update powerUp/powerDown value of 2D array
|
||||
//if(_numChannel < 8){
|
||||
verbosePrint("Command: " + command_deactivate_channel[_numChannel]);
|
||||
//openBCI.serial_openBCI.write(command_deactivate_channel[_numChannel]);
|
||||
openBCI.deactivateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels
|
||||
//}else{ //if a daisy channel
|
||||
// verbosePrint("Command: " + command_deactivate_channel_daisy[_numChannel - 8]);
|
||||
// openBCI.serial_openBCI.write(command_deactivate_channel_daisy[_numChannel - 8]);
|
||||
//}
|
||||
}
|
||||
|
||||
public void powerUpChannel(int _numChannel) {
|
||||
verbosePrint("Powering up channel " + str(int(_numChannel) + int(1)));
|
||||
//replace SRB2 and BIAS settings with values from 2D history array
|
||||
channelSettingValues[_numChannel][3] = previousBIAS[_numChannel];
|
||||
channelSettingValues[_numChannel][4] = previousSRB2[_numChannel];
|
||||
|
||||
// initChannelWrite(_numChannel);//writeChannelSettings
|
||||
channelSettingValues[_numChannel][0] = '0'; //update powerUp/powerDown value of 2D array
|
||||
//if(_numChannel < 8){
|
||||
verbosePrint("Command: " + command_activate_channel[_numChannel]);
|
||||
//openBCI.serial_openBCI.write(command_activate_channel[_numChannel]);
|
||||
openBCI.activateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels//assumes numChannel counts from zero (not one)...handles regular and daisy channels
|
||||
//} else{ //if a daisy channel
|
||||
// verbosePrint("Command: " + command_activate_channel_daisy[_numChannel - 8]);
|
||||
// openBCI.serial_openBCI.write(command_activate_channel_daisy[_numChannel - 8]);
|
||||
//}
|
||||
}
|
||||
|
||||
public void initChannelWrite(int _numChannel) {
|
||||
//after clicking any button, write the new settings for that channel to OpenBCI
|
||||
if (!openBCI.get_isWritingImp()) { //make sure you aren't currently writing imp settings for a channel
|
||||
verbosePrint("Writing channel settings for channel " + str(_numChannel+1) + " to OpenBCI!");
|
||||
openBCI.initChannelWrite(_numChannel);
|
||||
channelToWrite = _numChannel;
|
||||
}
|
||||
}
|
||||
|
||||
public void initImpWrite(int _numChannel, char pORn, char onORoff) {
|
||||
//after clicking any button, write the new settings for that channel to OpenBCI
|
||||
if (!openBCI.get_isWritingChannel()) { //make sure you aren't currently writing imp settings for a channel
|
||||
// if you're not currently writing a channel and not waiting to rewrite after you've finished mashing the button
|
||||
if (!openBCI.get_isWritingImp() && rewriteImpedanceWhenDoneWriting == false) {
|
||||
verbosePrint("Writing impedance check settings (" + pORn + "," + onORoff + ") for channel " + str(_numChannel+1) + " to OpenBCI!");
|
||||
if (pORn == 'p') {
|
||||
impedanceCheckValues[_numChannel][0] = onORoff;
|
||||
}
|
||||
if (pORn == 'n') {
|
||||
impedanceCheckValues[_numChannel][1] = onORoff;
|
||||
}
|
||||
openBCI.initImpWrite(_numChannel);
|
||||
impChannelToWrite = _numChannel;
|
||||
} else { //else wait until a the current write has finished and then write again ... this is to not overwrite the wrong values while writing a channel
|
||||
verbosePrint("CONGRATULATIONS, YOU'RE MASHING BUTTONS!");
|
||||
rewriteImpedanceWhenDoneWriting = true;
|
||||
impChannelToWriteWhenDoneWriting = _numChannel;
|
||||
|
||||
if (pORn == 'p') {
|
||||
final_pORn = 'p';
|
||||
}
|
||||
if (pORn == 'n') {
|
||||
final_pORn = 'n';
|
||||
}
|
||||
final_onORoff = onORoff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void createChannelSettingButtons() {
|
||||
//the size and space of these buttons are dependendant on the size of the screen and full ChannelController
|
||||
|
||||
verbosePrint("ChannelController: createChannelSettingButtons: creating channel setting buttons...");
|
||||
int buttonW = 0;
|
||||
int buttonX = 0;
|
||||
int buttonH = 0;
|
||||
int buttonY = 0; //variables to be used for button creation below
|
||||
String buttonString = "";
|
||||
Button tempButton;
|
||||
|
||||
//create all activate/deactivate buttons (left-most button in widget left of EEG graph). These buttons are always visible
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
buttonW = int((w1 - (spacer1 *4)) / 3);
|
||||
buttonX = int(x1 + (spacer1));
|
||||
// buttonH = int((h1 / (nchan + 1)) - (spacer1/2));
|
||||
buttonH = buttonW;
|
||||
buttonY = int(y1 + ((h1/(nchan+1))*(i+1)) - (buttonH/2));
|
||||
buttonString = str(i+1);
|
||||
tempButton = new Button (buttonX, buttonY, buttonW, buttonH, buttonString, 14);
|
||||
channelSettingButtons[i][0] = tempButton;
|
||||
}
|
||||
//create all (P)ositive impedance check butttons ... these are the buttons just to the right of activate/deactivate buttons ... These are also always visible
|
||||
//create all (N)egative impedance check butttons ... these are the buttons just to the right of activate/deactivate buttons ... These are also always visible
|
||||
|
||||
int downSizer = 6;
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
for (int j = 1; j < 3; j++) {
|
||||
buttonW = int(((w1 - (spacer1 *4)) / 3) - downSizer);
|
||||
buttonX = int((x1 + j*(buttonW+6) + (j+1)*(spacer1)) + (downSizer/2) + 1);
|
||||
// buttonH = int((h2 / (nchan + 1)) - (spacer2/2));
|
||||
buttonY = int((y1 + (((h1-1)/(nchan+1))*(i+1)) - (buttonH/2)) + (downSizer/2) + 1);
|
||||
buttonString = "";
|
||||
tempButton = new Button (buttonX, buttonY, buttonW, buttonW, buttonString, 14);
|
||||
impedanceCheckButtons[i][j-1] = tempButton;
|
||||
}
|
||||
}
|
||||
|
||||
//create all other channel setting buttons... these are only visible when the user toggles to "showFullController = true"
|
||||
for (int i = 0; i < nchan; i++) {
|
||||
for (int j = 1; j < 6; j++) {
|
||||
buttonW = int((w2 - (spacer2*6)) / 5);
|
||||
buttonX = int((x2 + (spacer2 * (j))) + ((j-1) * buttonW));
|
||||
// buttonH = int((h2 / (nchan + 1)) - (spacer2/2));
|
||||
buttonY = int(y2 + (((h2-1)/(nchan+1))*(i+1)) - (buttonH/2));
|
||||
buttonString = "N/A";
|
||||
tempButton = new Button (buttonX, buttonY, buttonW, buttonH, buttonString, 14);
|
||||
channelSettingButtons[i][j] = tempButton;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,792 @@
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// System Control Panel
|
||||
// - Select serial port from dropdown
|
||||
// - Select default configuration (EEG, EKG, EMG)
|
||||
// - Select Electrode Count (8 vs 16)
|
||||
// - Select data mode (synthetic, playback file, real-time)
|
||||
// - Record data? (y/n)
|
||||
// - select output location
|
||||
// - link to help guide
|
||||
// - buttons to start/stop/reset application
|
||||
//
|
||||
// Written by: Conor Russomanno (Oct. 2014)
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import controlP5.*;
|
||||
|
||||
ControlP5 cp5; //program-wide instance of ControlP5
|
||||
CallbackListener cb = new CallbackListener() { //used by ControlP5 to clear text field on double-click
|
||||
public void controlEvent(CallbackEvent theEvent) {
|
||||
println("CallbackListener: controlEvent: clearing");
|
||||
cp5.get(Textfield.class, "fileName").clear();
|
||||
}
|
||||
};
|
||||
|
||||
MenuList sourceList;
|
||||
|
||||
//Global buttons and elements for the control panel (changed within the classes below)
|
||||
MenuList serialList;
|
||||
String[] serialPorts = new String[Serial.list().length];
|
||||
|
||||
MenuList sdTimes;
|
||||
|
||||
color boxColor = color(200);
|
||||
color boxStrokeColor = color(138, 146, 153);
|
||||
color isSelected_color = color(184, 220, 105);
|
||||
|
||||
// Button openClosePort;
|
||||
// boolean portButtonPressed;
|
||||
|
||||
Button refreshPort;
|
||||
boolean refreshButtonPressed = false;
|
||||
|
||||
Button initSystemButton;
|
||||
boolean initButtonPressed = false; //default false
|
||||
|
||||
Button autoFileName;
|
||||
boolean fileButtonPressed = false;
|
||||
|
||||
Button chanButton8;
|
||||
boolean chanButton8Pressed = false;
|
||||
|
||||
Button chanButton16;
|
||||
boolean chanButton16Pressed = false;
|
||||
|
||||
Button selectPlaybackFile;
|
||||
boolean selectPlaybackFilePressed = false;
|
||||
|
||||
Button selectSDFile;
|
||||
boolean selectSDFilePressed = false;
|
||||
|
||||
|
||||
class ControlPanel {
|
||||
|
||||
public int x, y, w, h;
|
||||
public boolean isOpen;
|
||||
|
||||
boolean showSourceBox, showSerialBox, showFileBox, showChannelBox, showInitBox;
|
||||
PlotFontInfo fontInfo;
|
||||
|
||||
//various control panel elements that are unique to specific datasources
|
||||
DataSourceBox dataSourceBox;
|
||||
SerialBox serialBox;
|
||||
DataLogBox dataLogBox;
|
||||
ChannelCountBox channelCountBox;
|
||||
InitBox initBox;
|
||||
|
||||
PlaybackFileBox playbackFileBox;
|
||||
SDConverterBox sdConverterBox;
|
||||
|
||||
SDBox sdBox;
|
||||
|
||||
boolean drawStopInstructions;
|
||||
|
||||
int globalPadding; //design feature: passed through to all box classes as the global spacing .. in pixels .. for all elements/subelements
|
||||
int globalBorder;
|
||||
|
||||
boolean convertingSD = false;
|
||||
|
||||
ControlPanel(OpenBCI_GUI mainClass) {
|
||||
|
||||
x = 2;
|
||||
y = 2 + controlPanelCollapser.but_dy;
|
||||
w = controlPanelCollapser.but_dx;
|
||||
h = height - int(helpWidget.h);
|
||||
|
||||
isOpen = true;
|
||||
|
||||
fontInfo = new PlotFontInfo();
|
||||
|
||||
// f1 = createFont("Raleway-SemiBold.otf", 16);
|
||||
// f2 = createFont("Raleway-Regular.otf", 15);
|
||||
// f3 = createFont("Raleway-SemiBold.otf", 15);
|
||||
|
||||
globalPadding = 10; //controls the padding of all elements on the control panel
|
||||
globalBorder = 0; //controls the border of all elements in the control panel ... using processing's stroke() instead
|
||||
|
||||
cp5 = new ControlP5(mainClass);
|
||||
|
||||
//boxes active when eegDataSource = Normal (OpenBCI)
|
||||
dataSourceBox = new DataSourceBox(x, y, w, h, globalPadding);
|
||||
serialBox = new SerialBox(x + w, dataSourceBox.y, w, h, globalPadding);
|
||||
dataLogBox = new DataLogBox(x + w, (serialBox.y + serialBox.h), w, h, globalPadding);
|
||||
channelCountBox = new ChannelCountBox(x + w, (dataLogBox.y + dataLogBox.h), w, h, globalPadding);
|
||||
sdBox = new SDBox(x + w, (channelCountBox.y + channelCountBox.h), w, h, globalPadding);
|
||||
|
||||
//boxes active when eegDataSource = Playback
|
||||
playbackFileBox = new PlaybackFileBox(x + w, dataSourceBox.y, w, h, globalPadding);
|
||||
sdConverterBox = new SDConverterBox(x + w, (playbackFileBox.y + playbackFileBox.h), w, h, globalPadding);
|
||||
|
||||
initBox = new InitBox(x, (dataSourceBox.y + dataSourceBox.h), w, h, globalPadding);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
//toggle view of cp5 / serial list selection table
|
||||
if (isOpen) { // if control panel is open
|
||||
if (!cp5.isVisible()) { //and cp5 is not visible
|
||||
cp5.show(); // shot it
|
||||
}
|
||||
} else { //the opposite of above
|
||||
if (cp5.isVisible()) {
|
||||
cp5.hide();
|
||||
}
|
||||
}
|
||||
|
||||
//update all boxes if they need to be
|
||||
dataSourceBox.update();
|
||||
serialBox.update();
|
||||
dataLogBox.update();
|
||||
channelCountBox.update();
|
||||
sdBox.update();
|
||||
initBox.update();
|
||||
|
||||
serialList.updateMenu();
|
||||
|
||||
//SD File Conversion
|
||||
while (convertingSD == true) {
|
||||
convertSDFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
|
||||
pushStyle();
|
||||
noStroke();
|
||||
|
||||
//dark overlay of rest of interface to indicate it's not clickable
|
||||
fill(0, 0, 0, 185);
|
||||
rect(0, 0, width, height);
|
||||
|
||||
pushStyle();
|
||||
fill(255);
|
||||
noStroke();
|
||||
rect(0, 0, width, 32);
|
||||
popStyle();
|
||||
|
||||
// //background pane of control panel
|
||||
// fill(35,35,35);
|
||||
// rect(0,0,w,h);
|
||||
|
||||
popStyle();
|
||||
|
||||
initBox.draw();
|
||||
|
||||
if (systemMode == 10) {
|
||||
drawStopInstructions = true;
|
||||
}
|
||||
|
||||
if (systemMode != 10) { // only draw control panel boxes if system running is false
|
||||
dataSourceBox.draw();
|
||||
drawStopInstructions = false;
|
||||
cp5.setVisible(true);//make sure controlP5 elements are visible
|
||||
if (eegDataSource == 0) { //when data source is from OpenBCI
|
||||
serialBox.draw();
|
||||
dataLogBox.draw();
|
||||
channelCountBox.draw();
|
||||
sdBox.draw();
|
||||
cp5.get(Textfield.class, "fileName").setVisible(true); //make sure the data file field is visible
|
||||
cp5.get(MenuList.class, "serialList").setVisible(true); //make sure the serialList menulist is visible
|
||||
cp5.get(MenuList.class, "sdTimes").setVisible(true); //make sure the SD time record options menulist is visible
|
||||
//make sure serial list is visible
|
||||
//set other CP5 controllers invisible
|
||||
} else if (eegDataSource == 1) { //when data source is from playback file
|
||||
playbackFileBox.draw();
|
||||
sdConverterBox.draw();
|
||||
//set other CP5 controllers invisible
|
||||
cp5.get(Textfield.class, "fileName").setVisible(false); //make sure the data file field is visible
|
||||
cp5.get(MenuList.class, "serialList").setVisible(false);
|
||||
cp5.get(MenuList.class, "sdTimes").setVisible(false);
|
||||
} else if (eegDataSource == 2) {
|
||||
//make sure serial list is visible
|
||||
//set other CP5 controllers invisible
|
||||
cp5.get(Textfield.class, "fileName").setVisible(false); //make sure the data file field is visible
|
||||
cp5.get(MenuList.class, "serialList").setVisible(false);
|
||||
cp5.get(MenuList.class, "sdTimes").setVisible(false);
|
||||
} else {
|
||||
//set other CP5 controllers invisible
|
||||
cp5.get(Textfield.class, "fileName").setVisible(false); //make sure the data file field is visible
|
||||
cp5.get(MenuList.class, "serialList").setVisible(false);
|
||||
cp5.get(MenuList.class, "sdTimes").setVisible(false);
|
||||
}
|
||||
} else {
|
||||
cp5.setVisible(false); // if isRunning is true, hide all controlP5 elements
|
||||
}
|
||||
|
||||
//draw the box that tells you to stop the system in order to edit control settings
|
||||
if (drawStopInstructions) {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
strokeWeight(1);
|
||||
stroke(boxStrokeColor);
|
||||
rect(x, y, w, dataSourceBox.h); //draw background of box
|
||||
String stopInstructions = "Press the \"STOP SYSTEM\" button to edit system settings.";
|
||||
textAlign(CENTER, TOP);
|
||||
textFont(f2);
|
||||
fill(bgColor);
|
||||
text(stopInstructions, x + globalPadding*2, y + globalPadding*4, w - globalPadding*4, dataSourceBox.h - globalPadding*4);
|
||||
popStyle();
|
||||
}
|
||||
}
|
||||
|
||||
//mouse pressed in control panel
|
||||
public void CPmousePressed() {
|
||||
verbosePrint("CPmousePressed");
|
||||
|
||||
if (initSystemButton.isMouseHere()) {
|
||||
initSystemButton.setIsActive(true);
|
||||
initButtonPressed = true;
|
||||
}
|
||||
|
||||
//only able to click buttons of control panel when system is not running
|
||||
if (systemMode != 10) {
|
||||
//active buttons during DATASOURCE_NORMAL
|
||||
if (eegDataSource == 0) {
|
||||
if (refreshPort.isMouseHere()) {
|
||||
refreshPort.setIsActive(true);
|
||||
refreshButtonPressed = true;
|
||||
}
|
||||
|
||||
if (autoFileName.isMouseHere()) {
|
||||
autoFileName.setIsActive(true);
|
||||
fileButtonPressed = true;
|
||||
}
|
||||
|
||||
if (chanButton8.isMouseHere()) {
|
||||
chanButton8.setIsActive(true);
|
||||
chanButton8Pressed = true;
|
||||
chanButton8.color_notPressed = isSelected_color;
|
||||
chanButton16.color_notPressed = autoFileName.color_notPressed; //default color of button
|
||||
}
|
||||
|
||||
if (chanButton16.isMouseHere()) {
|
||||
chanButton16.setIsActive(true);
|
||||
chanButton16Pressed = true;
|
||||
chanButton8.color_notPressed = autoFileName.color_notPressed; //default color of button
|
||||
chanButton16.color_notPressed = isSelected_color;
|
||||
}
|
||||
}
|
||||
|
||||
//active buttons during DATASOURCE_PLAYBACKFILE
|
||||
if (eegDataSource == 1) {
|
||||
if (selectPlaybackFile.isMouseHere()) {
|
||||
selectPlaybackFile.setIsActive(true);
|
||||
selectPlaybackFilePressed = true;
|
||||
}
|
||||
|
||||
if (selectSDFile.isMouseHere()) {
|
||||
selectSDFile.setIsActive(true);
|
||||
selectSDFilePressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// output("Text File Name: " + cp5.get(Textfield.class,"fileName").getText());
|
||||
}
|
||||
|
||||
//mouse released in control panel
|
||||
public void CPmouseReleased() {
|
||||
verbosePrint("CPMouseReleased: CPmouseReleased start...");
|
||||
if (initSystemButton.isMouseHere() && initButtonPressed) {
|
||||
|
||||
//if system is not active ... initate system and flip button state
|
||||
if (initSystemButton.but_txt == "START SYSTEM") {
|
||||
|
||||
if ((eegDataSource == DATASOURCE_NORMAL || eegDataSource == DATASOURCE_NORMAL_W_AUX) && openBCI_portName == "N/A") { //if data source == normal && if no serial port selected OR no SD setting selected
|
||||
output("No Serial/COM port selected. Please select your Serial/COM port and retry system initiation.");
|
||||
initButtonPressed = false;
|
||||
initSystemButton.setIsActive(false);
|
||||
return;
|
||||
} else if (eegDataSource == DATASOURCE_PLAYBACKFILE && playbackData_fname == "N/A") { //if data source == playback && playback file == 'N/A'
|
||||
output("No playback file selected. Please select a playback file and retry system initiation."); // tell user that they need to select a file before the system can be started
|
||||
initButtonPressed = false;
|
||||
initSystemButton.setIsActive(false);
|
||||
return;
|
||||
} else if (eegDataSource == -1) {//if no data source selected
|
||||
output("No DATA SOURCE selected. Please select a DATA SOURCE and retry system initiation.");//tell user they must select a data source before initiating system
|
||||
initButtonPressed = false;
|
||||
initSystemButton.setIsActive(false);
|
||||
return;
|
||||
} else { //otherwise, initiate system!
|
||||
verbosePrint("ControlPanel: CPmouseReleased: init");
|
||||
initSystemButton.setString("STOP SYSTEM");
|
||||
//global steps to START SYSTEM
|
||||
// prepare the serial port
|
||||
verbosePrint("ControlPanel — port is open: " + openBCI.isSerialPortOpen());
|
||||
if (openBCI.isSerialPortOpen() == true) {
|
||||
openBCI.closeSerialPort();
|
||||
}
|
||||
fileName = cp5.get(Textfield.class, "fileName").getText(); // store the current text field value of "File Name" to be passed along to dataFiles
|
||||
initSystem();
|
||||
}
|
||||
}
|
||||
|
||||
//if system is already active ... stop system and flip button state back
|
||||
else {
|
||||
output("SYSTEM STOPPED");
|
||||
initSystemButton.setString("START SYSTEM");
|
||||
haltSystem();
|
||||
}
|
||||
//cursor(ARROW); //this this back to ARROW
|
||||
}
|
||||
|
||||
//open or close serial port if serial port button is pressed (left button in serial widget)
|
||||
if (refreshPort.isMouseHere() && refreshButtonPressed) {
|
||||
output("Serial/COM List Refreshed");
|
||||
serialPorts = new String[Serial.list().length];
|
||||
serialPorts = Serial.list();
|
||||
serialList.items.clear();
|
||||
for (int i = 0; i < serialPorts.length; i++) {
|
||||
String tempPort = serialPorts[(serialPorts.length-1) - i]; //list backwards... because usually our port is at the bottom
|
||||
serialList.addItem(makeItem(tempPort));
|
||||
}
|
||||
serialList.updateMenu();
|
||||
}
|
||||
|
||||
//open or close serial port if serial port button is pressed (left button in serial widget)
|
||||
if (autoFileName.isMouseHere() && fileButtonPressed) {
|
||||
output("Autogenerated \"File Name\" based on current date/time");
|
||||
cp5.get(Textfield.class, "fileName").setText(getDateString());
|
||||
}
|
||||
|
||||
if (chanButton8.isMouseHere() && chanButton8Pressed) {
|
||||
nchan = 8;
|
||||
fftBuff = new FFT[nchan]; //from the minim library
|
||||
yLittleBuff_uV = new float[nchan][nPointsPerUpdate];
|
||||
output("channel count set to " + str(nchan));
|
||||
updateChannelArrays(nchan); //make sure to reinitialize the channel arrays with the right number of channels
|
||||
}
|
||||
|
||||
if (chanButton16.isMouseHere() && chanButton16Pressed) {
|
||||
nchan = 16;
|
||||
fftBuff = new FFT[nchan]; //reinitialize the FFT buffer
|
||||
yLittleBuff_uV = new float[nchan][nPointsPerUpdate];
|
||||
output("channel count set to " + str(nchan));
|
||||
updateChannelArrays(nchan); //make sure to reinitialize the channel arrays with the right number of channels
|
||||
}
|
||||
|
||||
if (selectPlaybackFile.isMouseHere() && selectPlaybackFilePressed) {
|
||||
output("select a file for playback");
|
||||
selectInput("Select a pre-recorded file for playback:", "playbackSelected");
|
||||
}
|
||||
|
||||
if (selectSDFile.isMouseHere() && selectSDFilePressed) {
|
||||
output("select an SD file to convert to a playback file");
|
||||
createPlaybackFileFromSD();
|
||||
selectInput("Select an SD file to convert for playback:", "sdFileSelected");
|
||||
}
|
||||
|
||||
//reset all buttons to false
|
||||
refreshPort.setIsActive(false);
|
||||
refreshButtonPressed = false;
|
||||
initSystemButton.setIsActive(false);
|
||||
initButtonPressed = false;
|
||||
autoFileName.setIsActive(false);
|
||||
fileButtonPressed = false;
|
||||
chanButton8.setIsActive(false);
|
||||
chanButton8Pressed = false;
|
||||
chanButton16.setIsActive(false);
|
||||
chanButton16Pressed = false;
|
||||
selectPlaybackFile.setIsActive(false);
|
||||
selectPlaybackFilePressed = false;
|
||||
selectSDFile.setIsActive(false);
|
||||
selectSDFilePressed = false;
|
||||
}
|
||||
};
|
||||
|
||||
public void controlEvent(ControlEvent theEvent) {
|
||||
|
||||
if (theEvent.isFrom("sourceList")) {
|
||||
Map bob = ((MenuList)theEvent.getController()).getItem(int(theEvent.getValue()));
|
||||
String str = (String)bob.get("headline");
|
||||
str = str.substring(0, str.length()-5);
|
||||
//output("Data Source = " + str);
|
||||
int newDataSource = int(theEvent.getValue());
|
||||
eegDataSource = newDataSource; // reset global eegDataSource to the selected value from the list
|
||||
output("The new data source is " + str);
|
||||
}
|
||||
|
||||
if (theEvent.isFrom("serialList")) {
|
||||
Map bob = ((MenuList)theEvent.getController()).getItem(int(theEvent.getValue()));
|
||||
openBCI_portName = (String)bob.get("headline");
|
||||
output("OpenBCI Port Name = " + openBCI_portName);
|
||||
}
|
||||
|
||||
if (theEvent.isFrom("sdTimes")) {
|
||||
Map bob = ((MenuList)theEvent.getController()).getItem(int(theEvent.getValue()));
|
||||
sdSettingString = (String)bob.get("headline");
|
||||
sdSetting = int(theEvent.getValue());
|
||||
if (sdSetting != 0) {
|
||||
output("OpenBCI microSD Setting = " + sdSettingString + " recording time");
|
||||
} else {
|
||||
output("OpenBCI microSD Setting = " + sdSettingString);
|
||||
}
|
||||
verbosePrint("SD setting = " + sdSetting);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================//
|
||||
// BELOW ARE THE CLASSES FOR THE VARIOUS //
|
||||
// CONTROL PANEL BOXes (control widgets) //
|
||||
//==============================================================================//
|
||||
|
||||
class DataSourceBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
|
||||
CheckBox sourceCheckBox;
|
||||
|
||||
DataSourceBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 115;
|
||||
padding = _padding;
|
||||
|
||||
sourceList = new MenuList(cp5, "sourceList", w - padding*2, 72, f2);
|
||||
// sourceList.itemHeight = 28;
|
||||
// sourceList.padding = 9;
|
||||
sourceList.setPosition(x + padding, y + padding*2 + 13);
|
||||
sourceList.addItem(makeItem("LIVE (from OpenBCI) >"));
|
||||
sourceList.addItem(makeItem("PLAYBACK (from file) >"));
|
||||
sourceList.addItem(makeItem("SYNTHETIC (algorithmic) >"));
|
||||
sourceList.scrollerLength = 10;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
fill(bgColor);
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("DATA SOURCE", x + padding, y + padding);
|
||||
popStyle();
|
||||
//draw contents of Data Source Box at top of control panel
|
||||
//Title
|
||||
//checkboxes of system states
|
||||
}
|
||||
};
|
||||
|
||||
class SerialBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
//connect/disconnect button
|
||||
//Refresh list button
|
||||
//String port status;
|
||||
|
||||
SerialBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 147;
|
||||
padding = _padding;
|
||||
|
||||
// openClosePort = new Button (padding + border, y + padding*3 + 13 + 150, (w-padding*3)/2, 24, "OPEN PORT", fontInfo.buttonLabel_size);
|
||||
refreshPort = new Button (x + padding, y + padding*3 + 13 + 71, w - padding*2, 24, "REFRESH LIST", fontInfo.buttonLabel_size);
|
||||
|
||||
serialList = new MenuList(cp5, "serialList", w - padding*2, 72, f2);
|
||||
serialList.setPosition(x + padding, y + padding*2 + 13);
|
||||
serialPorts = Serial.list();
|
||||
for (int i = 0; i < serialPorts.length; i++) {
|
||||
String tempPort = serialPorts[(serialPorts.length-1) - i]; //list backwards... because usually our port is at the bottom
|
||||
serialList.addItem(makeItem(tempPort));
|
||||
}
|
||||
}
|
||||
|
||||
public void update() {
|
||||
// serialList.updateMenu();
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
fill(bgColor);
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("SERIAL/COM PORT", x + padding, y + padding);
|
||||
popStyle();
|
||||
|
||||
// openClosePort.draw();
|
||||
refreshPort.draw();
|
||||
}
|
||||
|
||||
public void refreshSerialList() {
|
||||
}
|
||||
};
|
||||
|
||||
class DataLogBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
String fileName;
|
||||
//text field for inputing text
|
||||
//create/open/closefile button
|
||||
String fileStatus;
|
||||
boolean isFileOpen; //true if file has been activated and is ready to write to
|
||||
//String port status;
|
||||
|
||||
DataLogBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 101;
|
||||
padding = _padding;
|
||||
//instantiate button
|
||||
//figure out default file name (from Chip's code)
|
||||
isFileOpen = false; //set to true on button push
|
||||
fileStatus = "NO FILE CREATED";
|
||||
|
||||
//button to autogenerate file name based on time/date
|
||||
autoFileName = new Button (x + padding, y + 66, w-(padding*2), 24, "AUTOGENERATE FILE NAME", fontInfo.buttonLabel_size);
|
||||
|
||||
cp5.addTextfield("fileName")
|
||||
.setPosition(x + 90, y + 32)
|
||||
.setCaptionLabel("")
|
||||
.setSize(157, 26)
|
||||
.setFont(f2)
|
||||
.setFocus(false)
|
||||
.setColor(color(26, 26, 26))
|
||||
.setColorBackground(color(255, 255, 255)) // text field bg color
|
||||
.setColorValueLabel(color(0, 0, 0)) // text color
|
||||
.setColorForeground(isSelected_color) // border color when not selected
|
||||
.setColorActive(isSelected_color) // border color when selected
|
||||
.setColorCursor(color(26, 26, 26))
|
||||
.setText(getDateString())
|
||||
.align(5, 10, 20, 40)
|
||||
.onDoublePress(cb)
|
||||
.setAutoClear(true);
|
||||
|
||||
//clear text field on double click
|
||||
}
|
||||
|
||||
public void update() {
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
fill(bgColor);
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("DATA LOG FILE", x + padding, y + padding);
|
||||
textFont(f3);
|
||||
text("File Name", x + padding, y + padding*2 + 18);
|
||||
popStyle();
|
||||
autoFileName.draw();
|
||||
}
|
||||
};
|
||||
|
||||
class ChannelCountBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
|
||||
boolean isSystemInitialized;
|
||||
// button for init/halt system
|
||||
|
||||
ChannelCountBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 73;
|
||||
padding = _padding;
|
||||
|
||||
chanButton8 = new Button (x + padding, y + padding*2 + 18, (w-padding*3)/2, 24, "8 CHANNELS", fontInfo.buttonLabel_size);
|
||||
if (nchan == 8) chanButton8.color_notPressed = isSelected_color; //make it appear like this one is already selected
|
||||
chanButton16 = new Button (x + padding*2 + (w-padding*3)/2, y + padding*2 + 18, (w-padding*3)/2, 24, "16 CHANNELS", fontInfo.buttonLabel_size);
|
||||
if (nchan == 16) chanButton16.color_notPressed = isSelected_color; //make it appear like this one is already selected
|
||||
}
|
||||
|
||||
public void update() {
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
fill(bgColor);
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("CHANNEL COUNT", x + padding, y + padding);
|
||||
fill(bgColor); //set color to green
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("(" + str(nchan) + ")", x + padding + 142, y + padding); // print the channel count in green next to the box title
|
||||
popStyle();
|
||||
|
||||
chanButton8.draw();
|
||||
chanButton16.draw();
|
||||
}
|
||||
};
|
||||
|
||||
class PlaybackFileBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
|
||||
PlaybackFileBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 67;
|
||||
padding = _padding;
|
||||
|
||||
selectPlaybackFile = new Button (x + padding, y + padding*2 + 13, w - padding*2, 24, "SELECT PLAYBACK FILE", fontInfo.buttonLabel_size);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
fill(bgColor);
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("PLAYBACK FILE", x + padding, y + padding);
|
||||
popStyle();
|
||||
|
||||
selectPlaybackFile.draw();
|
||||
// chanButton16.draw();
|
||||
}
|
||||
};
|
||||
|
||||
class SDBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
|
||||
SDBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 150;
|
||||
padding = _padding;
|
||||
|
||||
sdTimes = new MenuList(cp5, "sdTimes", w - padding*2, 108, f2);
|
||||
sdTimes.setPosition(x + padding, y + padding*2 + 13);
|
||||
serialPorts = Serial.list();
|
||||
|
||||
//add items for the various SD times
|
||||
sdTimes.addItem(makeItem("Do not write to SD..."));
|
||||
sdTimes.addItem(makeItem("5 minute maximum"));
|
||||
sdTimes.addItem(makeItem("15 minute maximum"));
|
||||
sdTimes.addItem(makeItem("30 minute maximum"));
|
||||
sdTimes.addItem(makeItem("1 hour maximum"));
|
||||
sdTimes.addItem(makeItem("2 hours maximum"));
|
||||
sdTimes.addItem(makeItem("4 hour maximum"));
|
||||
sdTimes.addItem(makeItem("12 hour maximum"));
|
||||
sdTimes.addItem(makeItem("24 hour maximum"));
|
||||
|
||||
sdTimes.activeItem = sdSetting; //added to indicate default choice (sdSetting is in OpenBCI_GUI)
|
||||
}
|
||||
|
||||
public void update() {
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
fill(bgColor);
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("WRITE TO SD (Y/N)?", x + padding, y + padding);
|
||||
popStyle();
|
||||
|
||||
//the drawing of the sdTimes is handled earlier in ControlPanel.draw()
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
class SDConverterBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
|
||||
SDConverterBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 67;
|
||||
padding = _padding;
|
||||
|
||||
selectSDFile = new Button (x + padding, y + padding*2 + 13, w - padding*2, 24, "SELECT SD FILE", fontInfo.buttonLabel_size);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
fill(bgColor);
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
text("CONVERT SD FOR PLAYBACK", x + padding, y + padding);
|
||||
popStyle();
|
||||
|
||||
selectSDFile.draw();
|
||||
}
|
||||
};
|
||||
|
||||
class InitBox {
|
||||
int x, y, w, h, padding; //size and position
|
||||
|
||||
boolean initButtonPressed; //default false
|
||||
|
||||
boolean isSystemInitialized;
|
||||
// button for init/halt system
|
||||
|
||||
InitBox(int _x, int _y, int _w, int _h, int _padding) {
|
||||
x = _x;
|
||||
y = _y;
|
||||
w = _w;
|
||||
h = 50;
|
||||
padding = _padding;
|
||||
|
||||
//init button
|
||||
initSystemButton = new Button (padding, y + padding, w-padding*2, h - padding*2, "START SYSTEM", fontInfo.buttonLabel_size);
|
||||
//initSystemButton.color_notPressed = color(boolor);
|
||||
//initSystemButton.buttonStrokeColor = color(boxColor);
|
||||
initButtonPressed = false;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
|
||||
pushStyle();
|
||||
fill(boxColor);
|
||||
stroke(boxStrokeColor);
|
||||
strokeWeight(1);
|
||||
rect(x, y, w, h);
|
||||
popStyle();
|
||||
initSystemButton.draw();
|
||||
}
|
||||
};
|
||||
|
||||
void playbackSelected(File selection) {
|
||||
if (selection == null) {
|
||||
println("ControlPanel: playbackSelected: Window was closed or the user hit cancel.");
|
||||
} else {
|
||||
println("ControlPanel: playbackSelected: User selected " + selection.getAbsolutePath());
|
||||
output("You have selected \"" + selection.getAbsolutePath() + "\" for playback.");
|
||||
playbackData_fname = selection.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
//import ddf.minim.analysis.*; //for FFT
|
||||
|
||||
boolean drawEMG = false; //if true... toggles on EEG_Processing_User.draw and toggles off the headplot in Gui_Manager
|
||||
|
||||
class EEG_Processing_User {
|
||||
private float fs_Hz; //sample rate
|
||||
private int nchan;
|
||||
|
||||
boolean switchesActive = false;
|
||||
|
||||
//Left Eye Variables
|
||||
boolean isTriggered_L = false;
|
||||
float upperThreshold_L = 25; //default uV upper threshold value ... this will automatically change over time
|
||||
float lowerThreshold_L = 0; //default uV lower threshold value ... this will automatically change over time
|
||||
int averagePeriod_L = 125; //number of data packets to average over (250 = 1 sec)
|
||||
int thresholdPeriod_L = 1250; //number of packets
|
||||
int ourChan_L = 1 - 1; //channel being monitored ... "3 - 1" means channel 3 (with a 0 index)
|
||||
float myAverage_L = 0.0; //this will change over time ... used for calculations below
|
||||
float acceptableLimitUV_L = 200; //uV values above this limit are excluded, as a result of them almost certainly being noise...
|
||||
int uncounted_L = 0;
|
||||
//prez related
|
||||
boolean switchTripped_L = false;
|
||||
int switchCounter_L = 0;
|
||||
float timeOfLastTrip_L = 0;
|
||||
float tripThreshold_L = 0.75;
|
||||
float untripThreshold_L = 0.6;
|
||||
|
||||
//Right Eye Variables
|
||||
boolean isTriggered_R = false;
|
||||
float upperThreshold_R= 25; //default uV upper threshold value ... this will automatically change over time
|
||||
float lowerThreshold_R = 0; //default uV lower threshold value ... this will automatically change over time
|
||||
int averagePeriod_R = 125; //number of data packets to average over (250 = 1 sec)
|
||||
int thresholdPeriod_R = 1250; //number of packets
|
||||
int ourChan_R = 2 - 1; //channel being monitored ... "3 - 1" means channel 3 (with a 0 index)
|
||||
float myAverage_R = 0.0; //this will change over time ... used for calculations below
|
||||
float acceptableLimitUV_R = 200; //uV values above this limit are excluded, as a result of them almost certainly being noise...
|
||||
int uncounted_R = 0;
|
||||
//prez related
|
||||
boolean switchTripped_R = false;
|
||||
int switchCounter_R = 0;
|
||||
float timeOfLastTrip_R = 0;
|
||||
float tripThreshold_R = 0.75;
|
||||
float untripThreshold_R = 0.60;
|
||||
|
||||
//add your own variables here
|
||||
boolean isTriggered = false; //boolean to keep track of when the trigger condition is met
|
||||
float upperThreshold = 25; //default uV upper threshold value ... this will automatically change over time
|
||||
float lowerThreshold = 0; //default uV lower threshold value ... this will automatically change over time
|
||||
int averagePeriod = 125; //number of data packets to average over (250 = 1 sec)
|
||||
int thresholdPeriod = 1250; //number of packets
|
||||
int ourChan = 4 - 1; //channel being monitored ... "3 - 1" means channel 3 (with a 0 index)
|
||||
float myAverage = 0.0; //this will change over time ... used for calculations below
|
||||
float acceptableLimitUV = 150; //uV values above this limit are excluded, as a result of them almost certainly being noise...
|
||||
boolean switchTripped = false;
|
||||
int switchCounter = 0;
|
||||
float timeOfLastTrip = 0;
|
||||
float tripThreshold = 0.50;
|
||||
float untripThreshold = 0.30;
|
||||
|
||||
//if writing to a serial port
|
||||
int output = 0; //value between 0-255 that is the relative position of the current uV average between the rolling lower and upper uV thresholds
|
||||
float output_normalized = 0; //converted to between 0-1
|
||||
float output_adjusted = 0; //adjusted depending on range that is expected on the other end, ie 0-255?
|
||||
|
||||
//if writing to a serial port
|
||||
int output_L = 0; //value between 0-255 that is the relative position of the current uV average between the rolling lower and upper uV thresholds
|
||||
float output_normalized_L = 0; //converted to between 0-1
|
||||
float output_adjusted_L = 0; //adjusted depending on range that is expected on the other end, ie 0-255?
|
||||
|
||||
//if writing to a serial port
|
||||
int output_R = 0; //value between 0-255 that is the relative position of the current uV average between the rolling lower and upper uV thresholds
|
||||
float output_normalized_R = 0; //converted to between 0-1
|
||||
float output_adjusted_R = 0; //adjusted depending on range that is expected on the other end, ie 0-255?
|
||||
|
||||
//class constructor
|
||||
EEG_Processing_User(int NCHAN, float sample_rate_Hz) {
|
||||
nchan = NCHAN;
|
||||
fs_Hz = sample_rate_Hz;
|
||||
}
|
||||
|
||||
//add some functions here...if you'd like
|
||||
|
||||
//here is the processing routine called by the OpenBCI main program...update this with whatever you'd like to do
|
||||
public void process(float[][] data_newest_uV, //holds raw EEG data that is new since the last call
|
||||
float[][] data_long_uV, //holds a longer piece of buffered EEG data, of same length as will be plotted on the screen
|
||||
float[][] data_forDisplay_uV, //this data has been filtered and is ready for plotting on the screen
|
||||
FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data
|
||||
|
||||
//for example, you could loop over each EEG channel to do some sort of time-domain processing
|
||||
//using the sample values that have already been filtered, as will be plotted on the display
|
||||
float EEG_value_uV;
|
||||
|
||||
//chan 3
|
||||
myAverage = 0.0;
|
||||
for (int i = data_forDisplay_uV[ourChan].length - averagePeriod; i < data_forDisplay_uV[ourChan].length; i++) {
|
||||
if (abs(data_forDisplay_uV[ourChan][i]) <= acceptableLimitUV) { //prevent BIG spikes from effecting the average
|
||||
myAverage += abs(data_forDisplay_uV[ourChan][i]); //add value to average ... we will soon divide by # of packets
|
||||
}
|
||||
}
|
||||
myAverage = myAverage / float(averagePeriod); //finishing the average
|
||||
|
||||
//Left Eye -- Chan 1
|
||||
myAverage_L = 0.0;
|
||||
for (int i = data_forDisplay_uV[ourChan_L].length - averagePeriod_L; i < data_forDisplay_uV[ourChan_L].length; i++) {
|
||||
if (abs(data_forDisplay_uV[ourChan_L][i]) <= acceptableLimitUV_L) { //prevent BIG spikes from effecting the average
|
||||
myAverage_L += abs(data_forDisplay_uV[ourChan_L][i]); //add value to average ... we will soon divide by # of packets
|
||||
} else {
|
||||
myAverage_L += acceptableLimitUV_L; //if it's greater than the limit, just add the limit
|
||||
}
|
||||
}
|
||||
myAverage_L = myAverage_L / float(averagePeriod_L); //finishing the average
|
||||
uncounted_L = 0;
|
||||
//println("myAverage_L = " + myAverage_L);
|
||||
|
||||
//Right Eye -- Chan 2
|
||||
myAverage_R = 0.0;
|
||||
for (int i = data_forDisplay_uV[ourChan_R].length - averagePeriod_R; i < data_forDisplay_uV[ourChan_R].length; i++) {
|
||||
if (abs(data_forDisplay_uV[ourChan_R][i]) <= acceptableLimitUV_R) { //prevent BIG spikes from effecting the average
|
||||
myAverage_R += abs(data_forDisplay_uV[ourChan_R][i]); //add value to average ... we will soon divide by # of packets
|
||||
} else {
|
||||
myAverage_R += acceptableLimitUV_R;
|
||||
}
|
||||
}
|
||||
|
||||
myAverage_R = myAverage_R / float(averagePeriod_R); //finishing the average
|
||||
uncounted_R = 0;
|
||||
//println("uncounted_R" + uncounted_R);
|
||||
//println("averagePeriod_R = " + averagePeriod_R);
|
||||
//println("myAverage_R = " + myAverage_R);
|
||||
//println("------------------");
|
||||
|
||||
//--------------------- some conditionals -- CHAN 3 -------------------------
|
||||
|
||||
if (myAverage >= upperThreshold && myAverage <= acceptableLimitUV) { //
|
||||
upperThreshold = myAverage;
|
||||
}
|
||||
|
||||
if (myAverage <= lowerThreshold) {
|
||||
lowerThreshold = myAverage;
|
||||
}
|
||||
|
||||
if (upperThreshold >= myAverage) {
|
||||
upperThreshold -= (upperThreshold - 25)/(frameRate * 5); //have upper threshold creep downwards to keep range tight
|
||||
}
|
||||
|
||||
if (lowerThreshold <= myAverage) {
|
||||
lowerThreshold += (25 - lowerThreshold)/(frameRate * 5); //have lower threshold creep upwards to keep range tight
|
||||
}
|
||||
|
||||
output = (int)map(myAverage, lowerThreshold, upperThreshold, 0, 255);
|
||||
output_normalized = map(myAverage, lowerThreshold, upperThreshold, 0, 1);
|
||||
output_adjusted = ((-0.1/(output_normalized*255.0)) + 255.0);
|
||||
|
||||
//trip the output to a value between 0-255
|
||||
if (output < 0) output = 0;
|
||||
if (output > 255) output = 255;
|
||||
|
||||
//attempt to write to serial_output. If this serial port does not exist, do nothing.
|
||||
try {
|
||||
//println("inMoov_output: | " + output + " |");
|
||||
serial_output.write(output);
|
||||
}
|
||||
catch(RuntimeException e) {
|
||||
if (isVerbose) println("serial not present");
|
||||
}
|
||||
|
||||
//------------------ LEFT EYE & RIGHT EYE ------------------------- //
|
||||
|
||||
//LEFT
|
||||
if (myAverage_L >= upperThreshold_L && myAverage_L <= acceptableLimitUV_L) { //
|
||||
upperThreshold_L = myAverage_L;
|
||||
}
|
||||
if (myAverage_L <= lowerThreshold_L) {
|
||||
lowerThreshold_L = myAverage_L;
|
||||
}
|
||||
if (upperThreshold_L >= (myAverage_L + 35)) {
|
||||
//upperThreshold_L -= (upperThreshold_L)/(frameRate * 5); //have upper threshold creep downwards to keep range tight
|
||||
upperThreshold_L *= .97;
|
||||
}
|
||||
if (lowerThreshold_L <= myAverage_L) {
|
||||
lowerThreshold_L += (10 - lowerThreshold_L)/(frameRate * 5); //have lower threshold creep upwards to keep range tight
|
||||
}
|
||||
//output_L = (int)map(myAverage_L, lowerThreshold_L, upperThreshold_L, 0, 255);
|
||||
output_normalized_L = map(myAverage_L, lowerThreshold_L, upperThreshold_L, 0, 1);
|
||||
//output_adjusted_L = ((-0.1/(output_normalized_L*255.0)) + 255.0);
|
||||
|
||||
//RIGHT
|
||||
if (myAverage_R >= upperThreshold_R && myAverage_R <= acceptableLimitUV_R) { //
|
||||
upperThreshold_R = myAverage_R;
|
||||
}
|
||||
if (myAverage_R <= lowerThreshold_R) {
|
||||
lowerThreshold_R = myAverage_R;
|
||||
}
|
||||
if (upperThreshold_R >= myAverage_R + 35) {
|
||||
//upperThreshold_R -= (upperThreshold_R - 25)/(frameRate * 5); //have upper threshold creep downwards to keep range tight
|
||||
upperThreshold_R *= .97;
|
||||
}
|
||||
if (lowerThreshold_R <= myAverage_R) {
|
||||
lowerThreshold_R += (10 - lowerThreshold_R)/(frameRate * 5); //have lower threshold creep upwards to keep range tight
|
||||
}
|
||||
//output_L = (int)map(myAverage_L, lowerThreshold_L, upperThreshold_L, 0, 255);
|
||||
output_normalized_R = map(myAverage_R, lowerThreshold_R, upperThreshold_R, 0, 1);
|
||||
//output_adjusted_L = ((-0.1/(output_normalized_L*255.0)) + 255.0);
|
||||
|
||||
//======================= TRIPPING SWITCHES ==========================//
|
||||
if (switchesActive) {
|
||||
// =========================== RIGHT ================================ //
|
||||
if (output_normalized_L >= tripThreshold_L && switchTripped_L == false && (millis() - timeOfLastTrip_L) >= 2000 && switchTripped_R == false) {
|
||||
println("switchTripped_L = true");
|
||||
switchTripped_L = true;
|
||||
timeOfLastTrip_L = millis();
|
||||
switchCounter_L = 1;
|
||||
}
|
||||
|
||||
if (output_normalized_R >= tripThreshold_R && switchTripped_R == false && (millis() - timeOfLastTrip_R) >= 2000 && switchTripped_L == false) {
|
||||
println("switchTripped_R = true");
|
||||
switchTripped_R = true;
|
||||
timeOfLastTrip_R = millis();
|
||||
switchCounter_R = 1;
|
||||
}
|
||||
if ((millis() - timeOfLastTrip_R) >= 750 && (millis() - timeOfLastTrip_R) <= 1250) {
|
||||
println("sweet zone R");
|
||||
if (switchTripped_L) {
|
||||
switchTripped_L = false;
|
||||
myPresentation.slideBack();
|
||||
}
|
||||
}
|
||||
//=========================== LEFT ================================
|
||||
if ((millis() - timeOfLastTrip_L) >= 750 && (millis() - timeOfLastTrip_L) <= 1250) {
|
||||
println("sweet zone L");
|
||||
if (switchTripped_R) {
|
||||
switchTripped_R = false;
|
||||
myPresentation.slideForward();
|
||||
}
|
||||
}
|
||||
if (millis() - timeOfLastTrip_L >= 250) {
|
||||
switchTripped_L = false;
|
||||
switchCounter_L = 0;
|
||||
}
|
||||
if (millis() - timeOfLastTrip_R >= 250) {
|
||||
switchTripped_R = false;
|
||||
switchCounter_R = 0;
|
||||
}
|
||||
//============================= JAW ===================================
|
||||
if (output_normalized >= tripThreshold && switchTripped == false && millis() - timeOfLastTrip >= 750) {
|
||||
switchTripped = true;
|
||||
switchCounter++;
|
||||
timeOfLastTrip = millis();
|
||||
}
|
||||
if (switchTripped == true && output_normalized <= untripThreshold) {
|
||||
switchTripped = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (millis() - timeOfLastTrip >= 1250) {
|
||||
if (switchCounter == 1) {
|
||||
//do nothing
|
||||
println("Reset Switch...");
|
||||
} else if (switchCounter == 2) {
|
||||
//do nothing
|
||||
//lock slides
|
||||
myPresentation.lockSlides = !myPresentation.lockSlides;
|
||||
println("Lock Slides");
|
||||
} else if (switchCounter == 3) {
|
||||
//next slide
|
||||
drawPresentation = !drawPresentation;
|
||||
println("Next Slide!");
|
||||
} else if (switchCounter == 4) {
|
||||
//previous slide
|
||||
println("Previous Slide!!!");
|
||||
myPresentation.slideBack();
|
||||
} else if (switchCounter == 5) {
|
||||
//previous slide
|
||||
drawPresentation = !drawPresentation;
|
||||
println("Turning on presentation!!!");
|
||||
} else if (switchCounter == 6) {
|
||||
//previous slide
|
||||
// robotHand = !robotHand;
|
||||
println("Turn Robot Hand ON/OFF!!!");
|
||||
}
|
||||
switchCounter = 0; //reset switch counter
|
||||
}
|
||||
|
||||
////--RIGHT
|
||||
//if (output_normalized_R >= tripThreshold_R && switchTripped_R == false && millis() - timeOfLastTrip_R >= 750) {
|
||||
// switchTripped_R = true;
|
||||
// switchCounter_R++;
|
||||
// timeOfLastTrip_R = millis();
|
||||
//}
|
||||
//if (switchTripped_R == true && output_normalized_R <= untripThreshold_R) {
|
||||
// switchTripped_R = false;
|
||||
//}
|
||||
//if (millis() - timeOfLastTrip_R >= 1250) {
|
||||
// if (switchCounter_R == 1) {
|
||||
// if (output_normalized_L >= tripThreshold_L && switchTripped_L == false && millis() - timeOfLastTrip_L >= 750) {
|
||||
// switchTripped_L = true;
|
||||
// switchCounter_L++;
|
||||
// timeOfLastTrip_L = millis();
|
||||
// myPresentation.slideBack();
|
||||
// }
|
||||
// println("Reset Switch...");
|
||||
// } else if (switchCounter_R == 2) {
|
||||
// //do nothing
|
||||
// println("Reset Switch...");
|
||||
// } else if (switchCounter_R == 3) {
|
||||
// //next slide
|
||||
// myPresentation.slideForward();
|
||||
// println("Next Slide!");
|
||||
// } else if (switchCounter_R == 4) {
|
||||
// //previous slide
|
||||
// println("Previous Slide!!!");
|
||||
// myPresentation.slideBack();
|
||||
// } else if (switchCounter_R == 5) {
|
||||
// //previous slide
|
||||
// drawPresentation = !drawPresentation;
|
||||
// println("Turning on presentation!!!");
|
||||
// } else if (switchCounter_R == 6) {
|
||||
// //previous slide
|
||||
// // robotHand = !robotHand;
|
||||
// println("Turn Robot Hand ON/OFF!!!");
|
||||
// }
|
||||
// switchCounter_R = 0; //reset switch counter
|
||||
//}
|
||||
//==================================================================
|
||||
|
||||
//if switchCounter_L was triggered between 800 & 1200 ms ago
|
||||
//and switchCounter_R is triggered
|
||||
//go forward 1 slide
|
||||
|
||||
|
||||
//if switchCounter_R was triggered between 800 & 1200 ms ago
|
||||
//and switchCounter_L is triggered
|
||||
//go back 1 slide
|
||||
|
||||
//OR, you could loop over each EEG channel and do some sort of frequency-domain processing from the FFT data
|
||||
float FFT_freq_Hz, FFT_value_uV;
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
//loop over each new sample
|
||||
for (int Ibin=0; Ibin < fftBuff[Ichan].specSize(); Ibin++) {
|
||||
FFT_freq_Hz = fftData[Ichan].indexToFreq(Ibin);
|
||||
FFT_value_uV = fftData[Ichan].getBand(Ibin);
|
||||
|
||||
//add your processing here...
|
||||
|
||||
//println("EEG_Processing_User: Ichan = " + Ichan + ", Freq = " + FFT_freq_Hz + "Hz, FFT Value = " + FFT_value_uV + "uV/bin");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
if (drawEMG) {
|
||||
pushStyle();
|
||||
|
||||
//circle for outer threshold
|
||||
noFill();
|
||||
stroke(0, 255, 0);
|
||||
strokeWeight(2);
|
||||
float scaleFactor = 1.0;
|
||||
float scaleFactorJaw = 1.5;
|
||||
|
||||
//LEFT -- draw visualizer
|
||||
pushMatrix();
|
||||
translate((-width)/8.0, 0);
|
||||
ellipse(3*(width/4), height/4, scaleFactor * upperThreshold_L, scaleFactor * upperThreshold_L);
|
||||
//circle for inner threshold
|
||||
stroke(0, 255, 255);
|
||||
ellipse(3*(width/4), height/4, scaleFactor * lowerThreshold_L, scaleFactor * lowerThreshold_L);
|
||||
//realtime
|
||||
fill(255, 0, 0, 125);
|
||||
noStroke();
|
||||
ellipse(3*(width/4), height/4, scaleFactor * myAverage_L, scaleFactor * myAverage_L);
|
||||
//draw background bar for mapped uV value indication
|
||||
fill(0, 255, 255, 125);
|
||||
rect(13*(width/16), height/8, (width/64), (height/4));
|
||||
//draw real time bar of actually mapped value
|
||||
fill(0, 255, 255);
|
||||
rect(13*(width/16), 3*(height/8), (width/64), map(output_normalized_L, 0, 1, 0, (-1) * (height/4)));
|
||||
popMatrix();
|
||||
|
||||
noFill();
|
||||
stroke(0, 255, 0);
|
||||
strokeWeight(2);
|
||||
|
||||
//RIGHT -- draw visualizer
|
||||
pushMatrix();
|
||||
translate(width/8, 0);
|
||||
ellipse(3*(width/4), height/4, scaleFactor * upperThreshold_R, scaleFactor * upperThreshold_R);
|
||||
//circle for inner threshold
|
||||
stroke(0, 255, 255);
|
||||
ellipse(3*(width/4), height/4, scaleFactor * lowerThreshold_R, scaleFactor * lowerThreshold_R);
|
||||
//realtime
|
||||
fill(255, 0, 0, 125);
|
||||
noStroke();
|
||||
ellipse(3*(width/4), height/4, scaleFactor * myAverage_R, scaleFactor * myAverage_R);
|
||||
//draw background bar for mapped uV value indication
|
||||
fill(0, 255, 255, 125);
|
||||
rect(13*(width/16), height/8, (width/64), (height/4));
|
||||
//draw real time bar of actually mapped value
|
||||
fill(0, 255, 255);
|
||||
rect(13*(width/16), 3*(height/8), (width/64), map(output_normalized_R, 0, 1, 0, (-1) * (height/4)));
|
||||
popMatrix();
|
||||
|
||||
//circle for outer threshold
|
||||
noFill();
|
||||
stroke(0, 255, 0);
|
||||
strokeWeight(2);
|
||||
ellipse(3*(width/4), height/4, scaleFactorJaw * upperThreshold, scaleFactorJaw * upperThreshold);
|
||||
//circle for inner threshold
|
||||
stroke(0, 255, 255);
|
||||
ellipse(3*(width/4), height/4, scaleFactorJaw * lowerThreshold, scaleFactorJaw * lowerThreshold);
|
||||
//realtime
|
||||
fill(255, 0, 0, 125);
|
||||
noStroke();
|
||||
ellipse(3*(width/4), height/4, scaleFactorJaw * myAverage, scaleFactorJaw * myAverage);
|
||||
//draw background bar for mapped uV value indication
|
||||
fill(0, 255, 255, 125);
|
||||
rect(13*(width/16), height/8, (width/64), (height/4));
|
||||
//draw real time bar of actually mapped value
|
||||
fill(0, 255, 255);
|
||||
rect(13*(width/16), 3*(height/8), (width/64), map(output_normalized, 0, 1, 0, (-1) * (height/4)));
|
||||
|
||||
popStyle();
|
||||
}
|
||||
drawTriggerFeedback();
|
||||
} //end of draw
|
||||
|
||||
public void drawTriggerFeedback() {
|
||||
//Is the board streaming data?
|
||||
//if so ... draw left eye trigger feedback
|
||||
if (isRunning) {
|
||||
//LEFT
|
||||
if (eegProcessing_user.switchCounter_L == 1) {
|
||||
//draw red circle
|
||||
fill(255, 0, 0);
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_L == 2) {
|
||||
//draw green circle
|
||||
fill(0, 255, 0);
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_L == 3) {
|
||||
//draw blue circle
|
||||
fill(0, 0, 255);
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_L == 4) {
|
||||
//draw blue circle
|
||||
fill(0, 255, 255);
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_L == 5) {
|
||||
//draw blue circle
|
||||
fill(255, 255, 0);
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_L == 6) {
|
||||
//draw blue circle
|
||||
fill(255, 0, 255);
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
}
|
||||
|
||||
//RIGHT
|
||||
if (eegProcessing_user.switchCounter_R == 1) {
|
||||
//draw red circle
|
||||
fill(255, 0, 0);
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_R == 2) {
|
||||
//draw green circle
|
||||
fill(0, 255, 0);
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_R == 3) {
|
||||
//draw blue circle
|
||||
fill(0, 0, 255);
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_R == 4) {
|
||||
//draw blue circle
|
||||
fill(0, 255, 255);
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_R == 5) {
|
||||
//draw blue circle
|
||||
fill(255, 255, 0);
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
} else if (eegProcessing_user.switchCounter_R == 6) {
|
||||
//draw blue circle
|
||||
fill(255, 0, 255);
|
||||
ellipse(width/2+40, height - 40, 20, 20);
|
||||
noFill();
|
||||
ellipse(width/2-40, height - 40, 20, 20);
|
||||
}
|
||||
|
||||
if (switchCounter == 1) {
|
||||
//draw red circle
|
||||
fill(255, 0, 0);
|
||||
ellipse(width/2, height - 40, 20, 20);
|
||||
} else if (switchCounter == 2) {
|
||||
//draw green circle
|
||||
fill(0, 255, 0);
|
||||
ellipse(width/2, height - 40, 20, 20);
|
||||
} else if (switchCounter == 3) {
|
||||
//draw blue circle
|
||||
fill(0, 0, 255);
|
||||
ellipse(width/2, height - 40, 20, 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EEG_Processing {
|
||||
private float fs_Hz; //sample rate
|
||||
private int nchan;
|
||||
final int N_FILT_CONFIGS = 5;
|
||||
FilterConstants[] filtCoeff_bp = new FilterConstants[N_FILT_CONFIGS];
|
||||
final int N_NOTCH_CONFIGS = 3;
|
||||
FilterConstants[] filtCoeff_notch = new FilterConstants[N_NOTCH_CONFIGS];
|
||||
private int currentFilt_ind = 0;
|
||||
private int currentNotch_ind = 0; // set to 0 to default to 60Hz, set to 1 to default to 50Hz
|
||||
float data_std_uV[];
|
||||
float polarity[];
|
||||
|
||||
|
||||
EEG_Processing(int NCHAN, float sample_rate_Hz) {
|
||||
nchan = NCHAN;
|
||||
fs_Hz = sample_rate_Hz;
|
||||
data_std_uV = new float[nchan];
|
||||
polarity = new float[nchan];
|
||||
|
||||
|
||||
//check to make sure the sample rate is acceptable and then define the filters
|
||||
if (abs(fs_Hz-250.0f) < 1.0) {
|
||||
defineFilters();
|
||||
} else {
|
||||
println("EEG_Processing: *** ERROR *** Filters can currently only work at 250 Hz");
|
||||
defineFilters(); //define the filters anyway just so that the code doesn't bomb
|
||||
}
|
||||
}
|
||||
|
||||
public float getSampleRateHz() {
|
||||
return fs_Hz;
|
||||
};
|
||||
|
||||
//define filters...assumes sample rate of 250 Hz !!!!!
|
||||
private void defineFilters() {
|
||||
int n_filt;
|
||||
double[] b, a, b2, a2;
|
||||
String filt_txt, filt_txt2;
|
||||
String short_txt, short_txt2;
|
||||
|
||||
//loop over all of the pre-defined filter types
|
||||
n_filt = filtCoeff_notch.length;
|
||||
for (int Ifilt=0; Ifilt < n_filt; Ifilt++) {
|
||||
switch (Ifilt) {
|
||||
case 0:
|
||||
//60 Hz notch filter, assumed fs = 250 Hz. 2nd Order Butterworth: b, a = signal.butter(2,[59.0 61.0]/(fs_Hz / 2.0), 'bandstop')
|
||||
b2 = new double[] { 9.650809863447347e-001, -2.424683201757643e-001, 1.945391494128786e+000, -2.424683201757643e-001, 9.650809863447347e-001 };
|
||||
a2 = new double[] { 1.000000000000000e+000, -2.467782611297853e-001, 1.944171784691352e+000, -2.381583792217435e-001, 9.313816821269039e-001 };
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 60Hz", "60Hz");
|
||||
break;
|
||||
case 1:
|
||||
//50 Hz notch filter, assumed fs = 250 Hz. 2nd Order Butterworth: b, a = signal.butter(2,[49.0 51.0]/(fs_Hz / 2.0), 'bandstop')
|
||||
b2 = new double[] { 0.96508099, -1.19328255, 2.29902305, -1.19328255, 0.96508099 };
|
||||
a2 = new double[] { 1.0, -1.21449348, 2.29780334, -1.17207163, 0.93138168 };
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 50Hz", "50Hz");
|
||||
break;
|
||||
case 2:
|
||||
//no notch filter
|
||||
b2 = new double[] { 1.0 };
|
||||
a2 = new double[] { 1.0 };
|
||||
filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "No Notch", "None");
|
||||
break;
|
||||
}
|
||||
} // end loop over notch filters
|
||||
|
||||
n_filt = filtCoeff_bp.length;
|
||||
for (int Ifilt=0; Ifilt<n_filt; Ifilt++) {
|
||||
//define bandpass filter
|
||||
switch (Ifilt) {
|
||||
case 0:
|
||||
//butter(2,[1 50]/(250/2)); %bandpass filter
|
||||
b = new double[] {
|
||||
2.001387256580675e-001, 0.0f, -4.002774513161350e-001, 0.0f, 2.001387256580675e-001
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -2.355934631131582e+000, 1.941257088655214e+000, -7.847063755334187e-001, 1.999076052968340e-001
|
||||
};
|
||||
filt_txt = "Bandpass 1-50Hz";
|
||||
short_txt = "1-50 Hz";
|
||||
break;
|
||||
case 1:
|
||||
//butter(2,[7 13]/(250/2));
|
||||
b = new double[] {
|
||||
5.129268366104263e-003, 0.0f, -1.025853673220853e-002, 0.0f, 5.129268366104263e-003
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -3.678895469764040e+000, 5.179700413522124e+000, -3.305801890016702e+000, 8.079495914209149e-001
|
||||
};
|
||||
filt_txt = "Bandpass 7-13Hz";
|
||||
short_txt = "7-13 Hz";
|
||||
break;
|
||||
case 2:
|
||||
//[b,a]=butter(2,[15 50]/(250/2)); %matlab command
|
||||
b = new double[] {
|
||||
1.173510367246093e-001, 0.0f, -2.347020734492186e-001, 0.0f, 1.173510367246093e-001
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -2.137430180172061e+000, 2.038578008108517e+000, -1.070144399200925e+000, 2.946365275879138e-001
|
||||
};
|
||||
filt_txt = "Bandpass 15-50Hz";
|
||||
short_txt = "15-50 Hz";
|
||||
break;
|
||||
case 3:
|
||||
//[b,a]=butter(2,[5 50]/(250/2)); %matlab command
|
||||
b = new double[] {
|
||||
1.750876436721012e-001, 0.0f, -3.501752873442023e-001, 0.0f, 1.750876436721012e-001
|
||||
};
|
||||
a = new double[] {
|
||||
1.0f, -2.299055356038497e+000, 1.967497759984450e+000, -8.748055564494800e-001, 2.196539839136946e-001
|
||||
};
|
||||
filt_txt = "Bandpass 5-50Hz";
|
||||
short_txt = "5-50 Hz";
|
||||
break;
|
||||
default:
|
||||
//no filtering
|
||||
b = new double[] {
|
||||
1.0
|
||||
};
|
||||
a = new double[] {
|
||||
1.0
|
||||
};
|
||||
filt_txt = "No BP Filter";
|
||||
short_txt = "No Filter";
|
||||
} //end switch block
|
||||
|
||||
//create the bandpass filter
|
||||
filtCoeff_bp[Ifilt] = new FilterConstants(b, a, filt_txt, short_txt);
|
||||
} //end loop over band pass filters
|
||||
} //end defineFilters method
|
||||
|
||||
public String getFilterDescription() {
|
||||
return filtCoeff_bp[currentFilt_ind].name + ", " + filtCoeff_notch[currentNotch_ind].name;
|
||||
}
|
||||
public String getShortFilterDescription() {
|
||||
return filtCoeff_bp[currentFilt_ind].short_name;
|
||||
}
|
||||
public String getShortNotchDescription() {
|
||||
return filtCoeff_notch[currentNotch_ind].short_name;
|
||||
}
|
||||
|
||||
public void incrementFilterConfiguration() {
|
||||
//increment the index
|
||||
currentFilt_ind++;
|
||||
if (currentFilt_ind >= N_FILT_CONFIGS) currentFilt_ind = 0;
|
||||
}
|
||||
public void incrementNotchConfiguration() {
|
||||
//increment the index
|
||||
currentNotch_ind++;
|
||||
if (currentNotch_ind >= N_NOTCH_CONFIGS) currentNotch_ind = 0;
|
||||
}
|
||||
|
||||
public void process(float[][] data_newest_uV, //holds raw EEG data that is new since the last call
|
||||
float[][] data_long_uV, //holds a longer piece of buffered EEG data, of same length as will be plotted on the screen
|
||||
float[][] data_forDisplay_uV, //put data here that should be plotted on the screen
|
||||
FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data
|
||||
|
||||
//loop over each EEG channel
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
|
||||
//filter the data in the time domain
|
||||
filterIIR(filtCoeff_notch[currentNotch_ind].b, filtCoeff_notch[currentNotch_ind].a, data_forDisplay_uV[Ichan]); //notch
|
||||
filterIIR(filtCoeff_bp[currentFilt_ind].b, filtCoeff_bp[currentFilt_ind].a, data_forDisplay_uV[Ichan]); //bandpass
|
||||
|
||||
//compute the standard deviation of the filtered signal...this is for the head plot
|
||||
float[] fooData_filt = dataBuffY_filtY_uV[Ichan]; //use the filtered data
|
||||
fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data
|
||||
data_std_uV[Ichan]=std(fooData_filt); //compute the standard deviation for the whole array "fooData_filt"
|
||||
} //close loop over channels
|
||||
|
||||
//find strongest channel
|
||||
int refChanInd = findMax(data_std_uV);
|
||||
//println("EEG_Processing: strongest chan (one referenced) = " + (refChanInd+1));
|
||||
float[] refData_uV = dataBuffY_filtY_uV[refChanInd]; //use the filtered data
|
||||
refData_uV = Arrays.copyOfRange(refData_uV, refData_uV.length-((int)fs_Hz), refData_uV.length); //just grab the most recent second of data
|
||||
|
||||
|
||||
//compute polarity of each channel
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
float[] fooData_filt = dataBuffY_filtY_uV[Ichan]; //use the filtered data
|
||||
fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data
|
||||
float dotProd = calcDotProduct(fooData_filt, refData_uV);
|
||||
if (dotProd >= 0.0f) {
|
||||
polarity[Ichan]=1.0;
|
||||
} else {
|
||||
polarity[Ichan]=-1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,928 @@
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
//
|
||||
// This class creates and manages all of the graphical user interface (GUI) elements
|
||||
// for the primary display. This is the display with the head, with the FFT frequency
|
||||
// traces, and with the montage of time-domain traces. It also holds all of the buttons.
|
||||
//
|
||||
// Chip Audette, Oct 2013 - May 2014
|
||||
//
|
||||
// Requires the plotting library from gwoptics. Built on gwoptics 0.5.0
|
||||
// http://www.gwoptics.org/processing/gwoptics_p5lib/
|
||||
//
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
|
||||
//import processing.core.PApplet;
|
||||
import org.gwoptics.graphics.*;
|
||||
import org.gwoptics.graphics.graph2D.*;
|
||||
import org.gwoptics.graphics.graph2D.Graph2D;
|
||||
import org.gwoptics.graphics.graph2D.LabelPos;
|
||||
import org.gwoptics.graphics.graph2D.traces.Blank2DTrace;
|
||||
import org.gwoptics.graphics.graph2D.backgrounds.*;
|
||||
import ddf.minim.analysis.*; //for FFT
|
||||
import java.util.*; //for Array.copyOfRange()
|
||||
|
||||
class Gui_Manager {
|
||||
ScatterTrace montageTrace;
|
||||
ScatterTrace_FFT fftTrace;
|
||||
Graph2D gMontage, gFFT, gSpectrogram;
|
||||
GridBackground gbMontage, gbFFT;
|
||||
Button stopButton;
|
||||
PlotFontInfo fontInfo;
|
||||
HeadPlot headPlot1;
|
||||
Button[] chanButtons;
|
||||
// Button guiPageButton;
|
||||
//boolean showImpedanceButtons;
|
||||
Button[] impedanceButtonsP;
|
||||
Button[] impedanceButtonsN;
|
||||
Button biasButton;
|
||||
Button intensityFactorButton;
|
||||
Button loglinPlotButton;
|
||||
Button filtBPButton;
|
||||
Button filtNotchButton;
|
||||
Button fftNButton;
|
||||
Button smoothingButton;
|
||||
Button maxDisplayFreqButton;
|
||||
Button showPolarityButton;
|
||||
|
||||
//these two buttons toggle between EEG graph state (they are mutually exclusive states)
|
||||
Button showMontageButton; // to show uV time graph as opposed to channel controller
|
||||
Button showChannelControllerButton; //to drawChannelController on top of gMontage
|
||||
// boolean isChannelControllerVisible;
|
||||
|
||||
TextBox titleMontage, titleFFT,titleSpectrogram;
|
||||
TextBox[] chanValuesMontage;
|
||||
TextBox[] impValuesMontage;
|
||||
boolean showMontageValues;
|
||||
public int guiPage;
|
||||
boolean vertScaleAsLog = true;
|
||||
Spectrogram spectrogram;
|
||||
boolean showSpectrogram;
|
||||
int whichChannelForSpectrogram;
|
||||
|
||||
//define some color variables
|
||||
int bgColorGraphs = 255;
|
||||
int gridColor = 200;
|
||||
int borderColor = 50;
|
||||
int axisColor = 50;
|
||||
int fontColor = 255;
|
||||
|
||||
// MontageController mc;
|
||||
ChannelController cc;
|
||||
|
||||
private float fftYOffset[];
|
||||
private float default_vertScale_uV=200.0; //this defines the Y-scale on the montage plots...this is the vertical space between traces
|
||||
private float[] vertScaleFactor = {1.0f, 2.0f, 5.0f, 50.0f, 0.25f, 0.5f};
|
||||
private int vertScaleFactor_ind = 0;
|
||||
float vertScale_uV=default_vertScale_uV;
|
||||
float vertScaleMin_uV_whenLog = 0.1f;
|
||||
float montage_yoffsets[];
|
||||
private float[] maxDisplayFreq_Hz = {20.0f, 40.0f, 60.0f, 120.0f};
|
||||
private int maxDisplayFreq_ind = 2;
|
||||
|
||||
public final static int GUI_PAGE_CHANNEL_ONOFF = 0;
|
||||
public final static int GUI_PAGE_IMPEDANCE_CHECK = 1;
|
||||
public final static int GUI_PAGE_HEADPLOT_SETUP = 2;
|
||||
public final static int N_GUI_PAGES = 3;
|
||||
|
||||
public final static String stopButton_pressToStop_txt = "Stop Data Stream";
|
||||
public final static String stopButton_pressToStart_txt = "Start Data Stream";
|
||||
|
||||
Gui_Manager(PApplet parent,int win_x, int win_y,int nchan,float displayTime_sec, float default_yScale_uV,
|
||||
String filterDescription, float smooth_fac) {
|
||||
// Gui_Manager(PApplet parent,int win_x, int win_y,int nchan,float displayTime_sec, float yScale_uV, float fs_Hz,
|
||||
// String montageFilterText, String detectName) {
|
||||
showSpectrogram = false;
|
||||
whichChannelForSpectrogram = 0; //assume
|
||||
|
||||
//define some layout parameters
|
||||
float axes_x, axes_y;
|
||||
float spacer_bottom = 30/float(win_y); //want this to be a fixed 30 pixels
|
||||
float spacer_top = float(controlPanelCollapser.but_dy)/float(win_y);
|
||||
float gutter_topbot = 0.03f;
|
||||
float gutter_left = 0.08f; //edge around the GUI
|
||||
float gutter_right = 0.015f; //edge around the GUI
|
||||
float height_UI_tray = 0.1f + spacer_bottom; //0.1f;//0.10f; //empty space along bottom for UI elements
|
||||
float left_right_split = 0.45f; //notional dividing line between left and right plots, measured from left
|
||||
float available_top2bot = 1.0f - 2*gutter_topbot - height_UI_tray;
|
||||
float up_down_split = 0.5f; //notional dividing line between top and bottom plots, measured from top
|
||||
float gutter_between_buttons = 0.005f; //space between buttons
|
||||
float title_gutter = 0.02f;
|
||||
float headPlot_fromTop = 0.12f;
|
||||
fontInfo = new PlotFontInfo();
|
||||
|
||||
//montage control panel variables
|
||||
// float x_cc = float(win_x)*(left_right_split+gutter_right - 0.01f);
|
||||
float x_cc = 5;
|
||||
// float y_cc = float(win_y)*(gutter_topbot+title_gutter+spacer_top);
|
||||
float y_cc = float(win_y)*(height_UI_tray);
|
||||
float w_cc = float(win_x)*(0.09f-gutter_right); //width of montage controls (on left of montage)
|
||||
float h_cc = float(win_y)*(available_top2bot-title_gutter-spacer_top); //height of montage controls (on left of montage)
|
||||
|
||||
//setup the montage plot...the right side
|
||||
default_vertScale_uV = default_yScale_uV; //here is the vertical scaling of the traces
|
||||
// float[] axisMontage_relPos = {
|
||||
// left_right_split+gutter_left,
|
||||
// gutter_topbot+title_gutter+spacer_top,
|
||||
// (1.0f-left_right_split)-gutter_left-gutter_right,
|
||||
// available_top2bot-title_gutter-spacer_top
|
||||
// }; //from left, from top, width, height
|
||||
|
||||
float[] axisMontage_relPos = {
|
||||
gutter_left,
|
||||
height_UI_tray,
|
||||
(1.0f-left_right_split)-gutter_left-gutter_right,
|
||||
available_top2bot-title_gutter-spacer_top
|
||||
}; //from left, from top, width, height
|
||||
axes_x = float(win_x)*axisMontage_relPos[2]; //width of the axis in pixels
|
||||
axes_y = float(win_y)*axisMontage_relPos[3]; //height of the axis in pixels
|
||||
gMontage = new Graph2D(parent, int(axes_x), int(axes_y), false); //last argument is whether the axes cross at zero
|
||||
setupMontagePlot(gMontage, win_x, win_y, axisMontage_relPos,displayTime_sec,fontInfo,filterDescription);
|
||||
|
||||
verbosePrint("Gui_Manager: Buttons: " + int(float(win_x)*axisMontage_relPos[0]) + ", " + (int(float(win_y)*axisMontage_relPos[1])-40));
|
||||
|
||||
showMontageButton = new Button (int(float(win_x)*axisMontage_relPos[0]) - 1, int(float(win_y)*axisMontage_relPos[1])-45, 125, 21, "EEG DATA", 14);
|
||||
showMontageButton.makeDropdownButton(true);
|
||||
showMontageButton.setColorPressed(color(184,220,105));
|
||||
showMontageButton.setColorNotPressed(color(255));
|
||||
showMontageButton.hasStroke(false);
|
||||
showMontageButton.setIsActive(true);
|
||||
showMontageButton.buttonFont = f1;
|
||||
showMontageButton.textColorActive = bgColor;
|
||||
|
||||
|
||||
showChannelControllerButton = new Button (int(float(win_x)*axisMontage_relPos[0])+127, int(float(win_y)*axisMontage_relPos[1])-45, 125, 21, "CHAN SET", 14);
|
||||
showChannelControllerButton.makeDropdownButton(true);
|
||||
showChannelControllerButton.setColorPressed(color(184,220,105));
|
||||
showChannelControllerButton.setColorNotPressed(color(255));
|
||||
showChannelControllerButton.hasStroke(false);
|
||||
showChannelControllerButton.setIsActive(false);
|
||||
showChannelControllerButton.textColorActive = bgColor;
|
||||
|
||||
//setup montage controller
|
||||
cc = new ChannelController(x_cc, y_cc, w_cc, h_cc, axes_x, axes_y);
|
||||
|
||||
|
||||
//setup the FFT plot...bottom on left side
|
||||
//float height_subplot = 0.5f*(available_top2bot-2*gutter_topbot);
|
||||
// float[] axisFFT_relPos = {
|
||||
// gutter_left,
|
||||
// gutter_topbot+ up_down_split*available_top2bot + gutter_topbot+title_gutter + spacer_top,
|
||||
// left_right_split-gutter_left-gutter_right,
|
||||
// available_top2bot*(1.0f-up_down_split) - gutter_topbot-title_gutter - spacer_top
|
||||
// }; //from left, from top, width, height
|
||||
float[] axisFFT_relPos = {
|
||||
gutter_left + left_right_split + 0.1f,
|
||||
up_down_split*available_top2bot + height_UI_tray + gutter_topbot,
|
||||
left_right_split-gutter_left-gutter_right,
|
||||
available_top2bot*(1.0f-up_down_split) - gutter_topbot-title_gutter - spacer_top
|
||||
}; //from left, from top, width, height
|
||||
axes_x = int(float(win_x)*axisFFT_relPos[2]); //width of the axis in pixels
|
||||
axes_y = int(float(win_y)*axisFFT_relPos[3]); //height of the axis in pixels
|
||||
gFFT = new Graph2D(parent, int(axes_x), int(axes_y), false); //last argument is whether the axes cross at zero
|
||||
setupFFTPlot(gFFT, win_x, win_y, axisFFT_relPos,fontInfo);
|
||||
|
||||
//setup the spectrogram plot
|
||||
// float[] axisSpectrogram_relPos = axisMontage_relPos;
|
||||
// axes_x = int(float(win_x)*axisSpectrogram_relPos[2]);
|
||||
// axes_y = int(float(win_y)*axisSpectrogram_relPos[3]);
|
||||
// gSpectrogram = new Graph2D(parent, axes_x, axes_y, false); //last argument is wheter the axes cross at zero
|
||||
// setupSpectrogram(gSpectrogram, win_x, win_y, axisMontage_relPos,displayTime_sec,fontInfo);
|
||||
// int Nspec = 256;
|
||||
// int Nstep = 32;
|
||||
// spectrogram = new Spectrogram(Nspec,openBCI.fs_Hz,Nstep,displayTime_sec);
|
||||
// spectrogram.clim[0] = java.lang.Math.log(gFFT.getYAxis().getMinValue()); //set the minium value for the color scale on the spectrogram
|
||||
// spectrogram.clim[1] = java.lang.Math.log(gFFT.getYAxis().getMaxValue()/10.0); //set the maximum value for the color scale on the spectrogram
|
||||
// updateMaxDisplayFreq();
|
||||
|
||||
//setup the head plot...top on the left side
|
||||
float[] axisHead_relPos = axisFFT_relPos.clone();
|
||||
// axisHead_relPos[1] = gutter_topbot + spacer_top; //set y position to be at top of left side
|
||||
axisHead_relPos[1] = headPlot_fromTop; //set y position to be at top of right side
|
||||
axisHead_relPos[3] = available_top2bot*up_down_split - gutter_topbot;
|
||||
headPlot1 = new HeadPlot(axisHead_relPos[0],axisHead_relPos[1],axisHead_relPos[2],axisHead_relPos[3],win_x,win_y,nchan);
|
||||
setSmoothFac(smooth_fac);
|
||||
|
||||
//setup the buttons
|
||||
int w,h,x,y;
|
||||
h = 26; //button height, was 25
|
||||
y = 2; //button y position, measured top
|
||||
|
||||
// //// Is this block used anymore? Chip 2014-11-23
|
||||
//setup the gui page button
|
||||
w = 80; //button width
|
||||
x = (int)((3*gutter_between_buttons + left_right_split) * win_x);
|
||||
// x = int(float(win_x)*0.3f);
|
||||
// guiPageButton = new Button(x,y,w,h,"Page\n" + (guiPage+1) + " of " + N_GUI_PAGES,fontInfo.buttonLabel_size);
|
||||
// //// End Ques by Chip 2014-11-12
|
||||
|
||||
//setup the channel on/off buttons...only plot 8 buttons, even if there are more channels
|
||||
//because as of 4/3/2014, you can only turn on/off the higher channels (the ones above chan 8)
|
||||
//by also turning off the corresponding lower channel. So, deactiving channel 9 must also
|
||||
//deactivate channel 1, therefore, we might as well use just the 1 button.
|
||||
// int xoffset = x + w + (int)(2*gutter_between_buttons*win_x);
|
||||
// int xoffset = (int)(float(win_x)*gutter_left);
|
||||
int xoffset = (int)(float(win_x)*0.5f);
|
||||
|
||||
w = 80; //button width
|
||||
int w_orig = w;
|
||||
//if (nchan > 10) w -= (nchan-8)*2; //make the buttons skinnier
|
||||
int nChanBut = min(nchan,8);
|
||||
chanButtons = new Button[nChanBut];
|
||||
String txt;
|
||||
for (int Ibut = 0; Ibut < nChanBut; Ibut++) {
|
||||
x = calcButtonXLocation(Ibut, win_x, w, xoffset,gutter_between_buttons);
|
||||
txt = "Chan\n" + Integer.toString(Ibut+1);
|
||||
if (nchan > 8+Ibut) txt = txt + "+" + Integer.toString(Ibut+1+8);
|
||||
chanButtons[Ibut] = new Button(x,y,w,h,txt,fontInfo.buttonLabel_size);
|
||||
}
|
||||
|
||||
//setup the impedance measurement (lead-off) control buttons
|
||||
//showImpedanceButtons = false; //by default, do not show the buttons
|
||||
int vertspace_pix = max(1,int(gutter_between_buttons*win_x/4));
|
||||
int w1 = w_orig; //use same width as for buttons above
|
||||
int h1 = h/2-vertspace_pix; //use buttons with half the height
|
||||
impedanceButtonsP = new Button[nchan];
|
||||
for (int Ibut = 0; Ibut < nchan; Ibut++) {
|
||||
x = calcButtonXLocation(Ibut, win_x, w1, xoffset, gutter_between_buttons);
|
||||
impedanceButtonsP[Ibut] = new Button(x,y,w1,h1,"Imp P" + (Ibut+1),fontInfo.buttonLabel_size);
|
||||
}
|
||||
impedanceButtonsN = new Button[nchan];
|
||||
for (int Ibut = 0; Ibut < nchan; Ibut++) {
|
||||
x = calcButtonXLocation(Ibut, win_x, w1, xoffset, gutter_between_buttons);
|
||||
impedanceButtonsN[Ibut] = new Button(x,y+h-h1,w1,h1,"Imp N" + (Ibut+1),fontInfo.buttonLabel_size);
|
||||
}
|
||||
h1 = h;
|
||||
x = calcButtonXLocation(nchan, win_x, w1, xoffset, gutter_between_buttons);
|
||||
biasButton = new Button(x,y,w1,h1,"Bias\n" + "Auto",fontInfo.buttonLabel_size);
|
||||
|
||||
|
||||
//setup the buttons to control the processing and frequency displays
|
||||
int Ibut=0;
|
||||
w = 70;
|
||||
h = 26;
|
||||
y = 2;
|
||||
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
maxDisplayFreqButton = new Button(x,y,w,h,"Max Freq\n" + round(maxDisplayFreq_Hz[maxDisplayFreq_ind]) + " Hz",fontInfo.buttonLabel_size);
|
||||
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
showPolarityButton = new Button(x,y,w,h,"Polarity\n" + headPlot1.getUsePolarityTrueFalse(),fontInfo.buttonLabel_size);
|
||||
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
smoothingButton = new Button(x,y,w,h,"Smooth\n" + headPlot1.smooth_fac,fontInfo.buttonLabel_size);
|
||||
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
loglinPlotButton = new Button(x,y,w,h,"Vert Scale\n" + get_vertScaleAsLogText(),fontInfo.buttonLabel_size);
|
||||
|
||||
//x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
//fftNButton = new Button(x,y,w,h,"FFT N\n" + Nfft,fontInfo.buttonLabel_size);
|
||||
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
intensityFactorButton = new Button(x,y,w,h,"Vert Scale\n" + round(vertScale_uV) + "uV",fontInfo.buttonLabel_size);
|
||||
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
filtNotchButton = new Button(x,y,w,h,"Notch\n" + eegProcessing.getShortNotchDescription(),fontInfo.buttonLabel_size);
|
||||
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
filtBPButton = new Button(x,y,w,h,"BP Filt\n" + eegProcessing.getShortFilterDescription(),fontInfo.buttonLabel_size);
|
||||
|
||||
set_vertScaleAsLog(true);
|
||||
|
||||
//setup start/stop button
|
||||
// x = win_x - int(gutter_right*float(win_x)) - w;
|
||||
//x = width/2 - w;
|
||||
x = calcButtonXLocation(Ibut++, win_x, w, xoffset,gutter_between_buttons);
|
||||
int w_wide = 120; //button width, wider
|
||||
x = x + w - w_wide-((int)(gutter_between_buttons*win_x)); //adjust the x position for the wider button, plus double the gutter
|
||||
stopButton = new Button(x,y,w_wide,h,stopButton_pressToStart_txt,fontInfo.buttonLabel_size);
|
||||
|
||||
|
||||
//set the initial display page for the GUI
|
||||
setGUIpage(GUI_PAGE_HEADPLOT_SETUP);
|
||||
}
|
||||
private int calcButtonXLocation(int Ibut,int win_x,int w, int xoffset, float gutter_between_buttons) {
|
||||
// return xoffset + (Ibut * (w + (int)(gutter_between_buttons*win_x)));
|
||||
return width - ((Ibut+1) * (w + 2)) - 1;
|
||||
}
|
||||
|
||||
public void setDefaultVertScale(float val_uV) {
|
||||
default_vertScale_uV = val_uV;
|
||||
updateVertScale();
|
||||
}
|
||||
public void setVertScaleFactor_ind(int ind) {
|
||||
vertScaleFactor_ind = max(0,ind);
|
||||
if (ind >= vertScaleFactor.length) vertScaleFactor_ind = 0;
|
||||
updateVertScale();
|
||||
}
|
||||
public void incrementVertScaleFactor() {
|
||||
setVertScaleFactor_ind(vertScaleFactor_ind+1); //wrap-around is handled inside the function
|
||||
}
|
||||
public void updateVertScale() {
|
||||
vertScale_uV = default_vertScale_uV*vertScaleFactor[vertScaleFactor_ind];
|
||||
//println("Gui_Manager: updateVertScale: vertScale_uV = " + vertScale_uV);
|
||||
|
||||
//update how the plots are scaled
|
||||
if (montageTrace != null) montageTrace.setYScale_uV(vertScale_uV); //the Y-axis on the montage plot is fixed...the data is simply scaled prior to plotting
|
||||
if (gFFT != null) gFFT.setYAxisMax(vertScale_uV);
|
||||
headPlot1.setMaxIntensity_uV(vertScale_uV);
|
||||
intensityFactorButton.setString("Vert Scale\n" + round(vertScale_uV) + "uV");
|
||||
|
||||
//update the Yticks on the FFT plot
|
||||
if (gFFT != null) {
|
||||
if (vertScaleAsLog) {
|
||||
gFFT.setYAxisTickSpacing(1);
|
||||
} else {
|
||||
gFFT.setYAxisTickSpacing(pow(10.0,floor(log10(vertScale_uV/4))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public String get_vertScaleAsLogText() {
|
||||
if (vertScaleAsLog) {
|
||||
return "Log";
|
||||
} else {
|
||||
return "Linear";
|
||||
}
|
||||
}
|
||||
public void set_vertScaleAsLog(boolean state) {
|
||||
vertScaleAsLog = state;
|
||||
|
||||
//change the FFT Plot
|
||||
if (gFFT != null) {
|
||||
if (vertScaleAsLog) {
|
||||
gFFT.setYAxisMin(vertScaleMin_uV_whenLog);
|
||||
Axis2D ay=gFFT.getYAxis();
|
||||
ay.setLogarithmicAxis(true);
|
||||
updateVertScale(); //force a re-do of the Yticks
|
||||
} else {
|
||||
Axis2D ay=gFFT.getYAxis();
|
||||
ay.setLogarithmicAxis(false);
|
||||
gFFT.setYAxisMin(0.0f);
|
||||
updateVertScale(); //force a re-do of the Yticks
|
||||
}
|
||||
}
|
||||
|
||||
//change the head plot
|
||||
headPlot1.set_plotColorAsLog(vertScaleAsLog);
|
||||
|
||||
//change the button
|
||||
if (loglinPlotButton != null) {
|
||||
loglinPlotButton.setString("Vert Scale\n" + get_vertScaleAsLogText());
|
||||
}
|
||||
}
|
||||
|
||||
public void setSmoothFac(float fac) {
|
||||
headPlot1.smooth_fac = fac;
|
||||
}
|
||||
|
||||
public void setMaxDisplayFreq_ind(int ind) {
|
||||
maxDisplayFreq_ind = max(0,ind);
|
||||
if (ind >= maxDisplayFreq_Hz.length) maxDisplayFreq_ind = 0;
|
||||
updateMaxDisplayFreq();
|
||||
}
|
||||
public void incrementMaxDisplayFreq() {
|
||||
setMaxDisplayFreq_ind(maxDisplayFreq_ind+1); //wrap-around is handled inside the function
|
||||
}
|
||||
public void updateMaxDisplayFreq() {
|
||||
//set the frequency limit of the display
|
||||
float foo_Hz = maxDisplayFreq_Hz[maxDisplayFreq_ind];
|
||||
gFFT.setXAxisMax(foo_Hz);
|
||||
if (fftTrace != null) fftTrace.set_plotXlim(0.0f,foo_Hz);
|
||||
//gSpectrogram.setYAxisMax(foo_Hz);
|
||||
|
||||
//set the ticks
|
||||
if (foo_Hz < 38.0f) {
|
||||
foo_Hz = 5.0f;
|
||||
} else if (foo_Hz < 78.0f) {
|
||||
foo_Hz = 10.0f;
|
||||
} else if (foo_Hz < 168.0f) {
|
||||
foo_Hz = 20.0f;
|
||||
} else {
|
||||
foo_Hz = (float)floor(foo_Hz / 50.0) * 50.0f;
|
||||
}
|
||||
gFFT.setXAxisTickSpacing(foo_Hz);
|
||||
//gSpectrogram.setYAxisTickSpacing(foo_Hz);
|
||||
|
||||
if (maxDisplayFreqButton != null) maxDisplayFreqButton.setString("Max Freq\n" + round(maxDisplayFreq_Hz[maxDisplayFreq_ind]) + " Hz");
|
||||
}
|
||||
|
||||
|
||||
public void setDoNotPlotOutsideXlim(boolean state) {
|
||||
if (state) {
|
||||
//println("GUI_Manager: setDoNotPlotAboveXlim: " + gFFT.getXAxis().getMaxValue());
|
||||
fftTrace.set_plotXlim(gFFT.getXAxis().getMinValue(),gFFT.getXAxis().getMaxValue());
|
||||
montageTrace.set_plotXlim(gMontage.getXAxis().getMinValue(),gMontage.getXAxis().getMaxValue());
|
||||
} else {
|
||||
fftTrace.set_plotXlim(Float.NaN,Float.NaN);
|
||||
}
|
||||
}
|
||||
public void setDecimateFactor(int fac) {
|
||||
montageTrace.setDecimateFactor(fac);
|
||||
}
|
||||
|
||||
public void setupMontagePlot(Graph2D g, int win_x, int win_y, float[] axis_relPos,float displayTime_sec, PlotFontInfo fontInfo,String filterDescription) {
|
||||
|
||||
g.setAxisColour(axisColor, axisColor, axisColor);
|
||||
g.setFontColour(fontColor, fontColor, fontColor);
|
||||
|
||||
int x1,y1;
|
||||
x1 = int(axis_relPos[0]*float(win_x));
|
||||
g.position.x = x1;
|
||||
y1 = int(axis_relPos[1]*float(win_y));
|
||||
g.position.y = y1;
|
||||
//g.position.y = 0;
|
||||
|
||||
g.setYAxisMin(-nchan-1.0f);
|
||||
g.setYAxisMax(0.0f);
|
||||
g.setYAxisTickSpacing(1f);
|
||||
g.setYAxisMinorTicks(0);
|
||||
g.setYAxisLabelAccuracy(0);
|
||||
g.setYAxisLabel("EEG Channel");
|
||||
g.setYAxisLabelFont(fontInfo.fontName,fontInfo.axisLabel_size, true);
|
||||
g.setYAxisTickFont(fontInfo.fontName,fontInfo.tickLabel_size, false);
|
||||
|
||||
g.setXAxisMin(-displayTime_sec);
|
||||
g.setXAxisMax(0f);
|
||||
g.setXAxisTickSpacing(1f);
|
||||
g.setXAxisMinorTicks(1);
|
||||
g.setXAxisLabelAccuracy(0);
|
||||
g.setXAxisLabel("Time (sec)");
|
||||
g.setXAxisLabelFont(fontInfo.fontName,fontInfo.axisLabel_size, false);
|
||||
g.setXAxisTickFont(fontInfo.fontName,fontInfo.tickLabel_size, false);
|
||||
|
||||
// switching on Grid, with different colours for X and Y lines
|
||||
gbMontage = new GridBackground(new GWColour(bgColorGraphs));
|
||||
gbMontage.setGridColour(gridColor, gridColor, gridColor, gridColor, gridColor, gridColor);
|
||||
g.setBackground(gbMontage);
|
||||
|
||||
g.setBorderColour(borderColor,borderColor,borderColor);
|
||||
|
||||
// add title
|
||||
titleMontage = new TextBox("EEG Data (" + filterDescription + ")",0,0);
|
||||
int x2 = x1 + int(round(0.5*axis_relPos[2]*float(win_x)));
|
||||
int y2 = y1 - 2; //deflect two pixels upward
|
||||
titleMontage.x = x2;
|
||||
titleMontage.y = y2;
|
||||
titleMontage.textColor = color(bgColor);
|
||||
titleMontage.setFontSize(14);
|
||||
titleMontage.alignH = CENTER;
|
||||
|
||||
//add channel data values and impedance values
|
||||
int x3, y3;
|
||||
//float w = int(round(axis_relPos[2]*win_x));
|
||||
TextBox fooBox = new TextBox("",0,0);
|
||||
chanValuesMontage = new TextBox[nchan];
|
||||
impValuesMontage = new TextBox[nchan];
|
||||
Axis2D xAxis = g.getXAxis();
|
||||
Axis2D yAxis = g.getYAxis();
|
||||
int h = int(round(axis_relPos[3]*win_y));
|
||||
for (int i=0; i<nchan; i++) {
|
||||
y3 = y1 + h - yAxis.valueToPosition((float)(-(i+1))); //set to be on the centerline of the trace
|
||||
for (int j=0; j<2; j++) { //loop over the different text box types
|
||||
switch (j) {
|
||||
case 0:
|
||||
//voltage value text
|
||||
x3 = x1 + xAxis.valueToPosition(xAxis.getMaxValue()) - 2; //set to right edge of plot. nudge 2 pixels to the left
|
||||
fooBox = new TextBox("0.00 uVrms",x3,y3);
|
||||
break;
|
||||
case 1:
|
||||
//impedance value text
|
||||
x3 = x1 + xAxis.valueToPosition(xAxis.getMinValue()) + 2; //set to left edge of plot. nudge 2 pixels to the right
|
||||
fooBox = new TextBox("0.00 kOhm",x3,y3);
|
||||
break;
|
||||
}
|
||||
fooBox.textColor = color(0,0,0);
|
||||
fooBox.drawBackground = true;
|
||||
fooBox.backgroundColor = color(255,255,255, 125);
|
||||
noStroke();
|
||||
switch (j) {
|
||||
case 0:
|
||||
//voltage value text
|
||||
fooBox.alignH = RIGHT;
|
||||
chanValuesMontage[i] = fooBox;
|
||||
break;
|
||||
case 1:
|
||||
//impedance value text
|
||||
fooBox.alignH = LEFT;
|
||||
impValuesMontage[i] = fooBox;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
showMontageValues = true; // default to having them NOT displayed
|
||||
}
|
||||
|
||||
public void setupFFTPlot(Graph2D g, int win_x, int win_y, float[] axis_relPos,PlotFontInfo fontInfo) {
|
||||
|
||||
g.setAxisColour(axisColor, axisColor, axisColor);
|
||||
g.setFontColour(fontColor, fontColor, fontColor);
|
||||
|
||||
int x1,y1;
|
||||
x1 = int(axis_relPos[0]*float(win_x));
|
||||
g.position.x = x1;
|
||||
y1 = int(axis_relPos[1]*float(win_y));
|
||||
g.position.y = y1;
|
||||
//g.position.y = 0;
|
||||
|
||||
//setup the y axis
|
||||
g.setYAxisMin(vertScaleMin_uV_whenLog);
|
||||
g.setYAxisMax(vertScale_uV);
|
||||
g.setYAxisTickSpacing(1);
|
||||
g.setYAxisMinorTicks(0);
|
||||
g.setYAxisLabelAccuracy(0);
|
||||
//g.setYAxisLabel("EEG Amplitude (uV/sqrt(Hz))"); // Some people prefer this...but you'll have to change the normalization in OpenBCI_GUI\processNewData()
|
||||
g.setYAxisLabel("EEG Amplitude (uV per bin)"); // CHIP 2014-10-24...currently, this matches the normalization in OpenBCI_GUI\processNewData()
|
||||
g.setYAxisLabelFont(fontInfo.fontName,fontInfo.axisLabel_size, false);
|
||||
g.setYAxisTickFont(fontInfo.fontName,fontInfo.tickLabel_size, false);
|
||||
|
||||
//get the Y-axis and make it log
|
||||
Axis2D ay=g.getYAxis();
|
||||
ay.setLogarithmicAxis(true);
|
||||
|
||||
//setup the x axis
|
||||
g.setXAxisMin(0f);
|
||||
g.setXAxisMax(maxDisplayFreq_Hz[maxDisplayFreq_ind]);
|
||||
g.setXAxisTickSpacing(10f);
|
||||
g.setXAxisMinorTicks(2);
|
||||
g.setXAxisLabelAccuracy(0);
|
||||
g.setXAxisLabel("Frequency (Hz)");
|
||||
g.setXAxisLabelFont(fontInfo.fontName,fontInfo.axisLabel_size, false);
|
||||
g.setXAxisTickFont(fontInfo.fontName,fontInfo.tickLabel_size, false);
|
||||
|
||||
|
||||
// switching on Grid, with differetn colours for X and Y lines
|
||||
gbFFT = new GridBackground(new GWColour(bgColorGraphs));
|
||||
gbFFT.setGridColour(gridColor, gridColor, gridColor, gridColor, gridColor, gridColor);
|
||||
g.setBackground(gbFFT);
|
||||
|
||||
g.setBorderColour(borderColor,borderColor,borderColor);
|
||||
|
||||
// add title
|
||||
titleFFT = new TextBox("FFT Plot",0,0);
|
||||
int x2 = x1 + int(round(0.5*axis_relPos[2]*float(win_x)));
|
||||
int y2 = y1 - 2; //deflect two pixels upward
|
||||
titleFFT.x = x2;
|
||||
titleFFT.y = y2;
|
||||
titleFFT.textColor = color(255,255,255);
|
||||
titleFFT.setFontSize(16);
|
||||
titleFFT.alignH = CENTER;
|
||||
}
|
||||
|
||||
public void setupSpectrogram(Graph2D g, int win_x, int win_y, float[] axis_relPos,float displayTime_sec, PlotFontInfo fontInfo) {
|
||||
//start by setting up as if it were the montage plot
|
||||
//setupMontagePlot(g, win_x, win_y, axis_relPos,displayTime_sec,fontInfo,title);
|
||||
|
||||
g.setAxisColour(220, 220, 220);
|
||||
g.setFontColour(255, 255, 255);
|
||||
|
||||
int x1 = int(axis_relPos[0]*float(win_x));
|
||||
g.position.x = x1;
|
||||
int y1 = int(axis_relPos[1]*float(win_y));
|
||||
g.position.y = y1;
|
||||
|
||||
//setup the x axis
|
||||
g.setXAxisMin(-displayTime_sec);
|
||||
g.setXAxisMax(0f);
|
||||
g.setXAxisTickSpacing(1f);
|
||||
g.setXAxisMinorTicks(1);
|
||||
g.setXAxisLabelAccuracy(0);
|
||||
g.setXAxisLabel("Time (sec)");
|
||||
g.setXAxisLabelFont(fontInfo.fontName,fontInfo.axisLabel_size, false);
|
||||
g.setXAxisTickFont(fontInfo.fontName,fontInfo.tickLabel_size, false);
|
||||
|
||||
//setup the y axis...frequency
|
||||
g.setYAxisMin(0.0f-0.5f);
|
||||
g.setYAxisMax(maxDisplayFreq_Hz[maxDisplayFreq_ind]);
|
||||
g.setYAxisTickSpacing(10.0f);
|
||||
g.setYAxisMinorTicks(2);
|
||||
g.setYAxisLabelAccuracy(0);
|
||||
g.setYAxisLabel("Frequency (Hz)");
|
||||
g.setYAxisLabelFont(fontInfo.fontName,fontInfo.axisLabel_size, false);
|
||||
g.setYAxisTickFont(fontInfo.fontName,fontInfo.tickLabel_size, false);
|
||||
|
||||
|
||||
//make title
|
||||
titleSpectrogram = new TextBox(makeSpectrogramTitle(),0,0);
|
||||
int x2 = x1 + int(round(0.5*axis_relPos[2]*float(win_x)));
|
||||
int y2 = y1 - 2; //deflect two pixels upward
|
||||
titleSpectrogram.x = x2;
|
||||
titleSpectrogram.y = y2;
|
||||
titleSpectrogram.textColor = color(255,255,255);
|
||||
titleSpectrogram.setFontSize(16);
|
||||
titleSpectrogram.alignH = CENTER;
|
||||
}
|
||||
|
||||
public void initializeMontageTraces(float[] dataBuffX, float [][] dataBuffY) {
|
||||
|
||||
//create the trace object, add it to the plotting object, and set the data and scale factor
|
||||
//montageTrace = new ScatterTrace(); //I can't have this here because it dies. It must be in setup()
|
||||
gMontage.addTrace(montageTrace);
|
||||
montageTrace.setXYData_byRef(dataBuffX, dataBuffY);
|
||||
montageTrace.setYScaleFac(1f / vertScale_uV);
|
||||
//montageTrace.setYScaleFac(1.0f); //for OpenBCI_GUI_Simpler
|
||||
|
||||
//set the y-offsets for each trace in the fft plot.
|
||||
//have each trace bumped down by -1.0.
|
||||
for (int Ichan=0; Ichan < nchan; Ichan++) {
|
||||
montage_yoffsets[Ichan]=(float)(-(Ichan+1));
|
||||
}
|
||||
montageTrace.setYOffset_byRef(montage_yoffsets);
|
||||
}
|
||||
|
||||
|
||||
public void initializeFFTTraces(ScatterTrace_FFT fftTrace,FFT[] fftBuff,float[] fftYOffset,Graph2D gFFT) {
|
||||
for (int Ichan = 0; Ichan < fftYOffset.length; Ichan++) {
|
||||
//set the Y-offste for the individual traces in the plots
|
||||
fftYOffset[Ichan]= 0f; //set so that there is no additional offset
|
||||
}
|
||||
|
||||
//make the trace for the FFT and add it to the FFT Plot axis
|
||||
//fftTrace = new ScatterTrace_FFT(fftBuff); //can't put this here...must be in setup()
|
||||
fftTrace.setYOffset(fftYOffset);
|
||||
gFFT.addTrace(fftTrace);
|
||||
}
|
||||
|
||||
|
||||
public void initDataTraces(float[] dataBuffX,float[][] dataBuffY,FFT[] fftBuff,float[] dataBuffY_std, DataStatus[] is_railed, float[] dataBuffY_polarity) {
|
||||
//initialize the time-domain montage-plot traces
|
||||
montageTrace = new ScatterTrace();
|
||||
montage_yoffsets = new float[nchan];
|
||||
initializeMontageTraces(dataBuffX,dataBuffY);
|
||||
montageTrace.set_isRailed(is_railed);
|
||||
|
||||
//initialize the FFT traces
|
||||
fftTrace = new ScatterTrace_FFT(fftBuff); //can't put this here...must be in setup()
|
||||
fftYOffset = new float[nchan];
|
||||
initializeFFTTraces(fftTrace,fftBuff,fftYOffset,gFFT);
|
||||
|
||||
//link the data to the head plot
|
||||
headPlot1.setIntensityData_byRef(dataBuffY_std,is_railed);
|
||||
headPlot1.setPolarityData_byRef(dataBuffY_polarity);
|
||||
}
|
||||
|
||||
public void setShowSpectrogram(boolean show) {
|
||||
showSpectrogram = show;
|
||||
}
|
||||
|
||||
public void tellGUIWhichChannelForSpectrogram(int Ichan) { // Ichan starts at zero
|
||||
if (Ichan != whichChannelForSpectrogram) {
|
||||
whichChannelForSpectrogram = Ichan;
|
||||
titleSpectrogram.string = makeSpectrogramTitle();
|
||||
}
|
||||
}
|
||||
public String makeSpectrogramTitle() {
|
||||
return ("Spectrogram, Channel " + (whichChannelForSpectrogram+1) + " (As Received)");
|
||||
}
|
||||
|
||||
|
||||
public void setGUIpage(int page) {
|
||||
if ((page >= 0) && (page < N_GUI_PAGES)) {
|
||||
guiPage = page;
|
||||
} else {
|
||||
guiPage = 0;
|
||||
}
|
||||
//update the text on the button
|
||||
// guiPageButton.setString("Page\n" + (guiPage+1) + " of " + N_GUI_PAGES);
|
||||
}
|
||||
|
||||
public void incrementGUIpage() {
|
||||
setGUIpage( (guiPage+1) % N_GUI_PAGES );
|
||||
}
|
||||
|
||||
public boolean isMouseOnGraph2D(Graph2D g, int mouse_x, int mouse_y) {
|
||||
GraphDataPoint dataPoint = new GraphDataPoint();
|
||||
getGraph2DdataPoint(g,mouse_x,mouse_y,dataPoint);
|
||||
if ( (dataPoint.x >= g.getXAxis().getMinValue()) &
|
||||
(dataPoint.x <= g.getXAxis().getMaxValue()) &
|
||||
(dataPoint.y >= g.getYAxis().getMinValue()) &
|
||||
(dataPoint.y <= g.getYAxis().getMaxValue()) ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMouseOnMontage(int mouse_x, int mouse_y) {
|
||||
return isMouseOnGraph2D(gMontage,mouse_x,mouse_y);
|
||||
}
|
||||
public boolean isMouseOnFFT(int mouse_x, int mouse_y) {
|
||||
return isMouseOnGraph2D(gFFT,mouse_x,mouse_y);
|
||||
}
|
||||
|
||||
public void getGraph2DdataPoint(Graph2D g, int mouse_x,int mouse_y, GraphDataPoint dataPoint) {
|
||||
int rel_x = mouse_x - int(g.position.x);
|
||||
int rel_y = g.getYAxis().getLength() - (mouse_y - int(g.position.y));
|
||||
dataPoint.x = g.getXAxis().positionToValue(rel_x);
|
||||
dataPoint.y = g.getYAxis().positionToValue(rel_y);
|
||||
}
|
||||
public void getMontageDataPoint(int mouse_x, int mouse_y, GraphDataPoint dataPoint) {
|
||||
getGraph2DdataPoint(gMontage,mouse_x,mouse_y,dataPoint);
|
||||
dataPoint.x_units = "sec";
|
||||
dataPoint.y_units = "uV";
|
||||
}
|
||||
public void getFFTdataPoint(int mouse_x,int mouse_y,GraphDataPoint dataPoint) {
|
||||
getGraph2DdataPoint(gFFT, mouse_x,mouse_y,dataPoint);
|
||||
dataPoint.x_units = "Hz";
|
||||
dataPoint.y_units = "uV/sqrt(Hz)";
|
||||
}
|
||||
|
||||
// public boolean isMouseOnHeadPlot(int mouse_x, int mouse_y) {
|
||||
// return headPlot1.isPixelInsideHead(mouse_x,mouse_y) {
|
||||
// }
|
||||
|
||||
public void update(float[] data_std_uV,float[] data_elec_imp_ohm) {
|
||||
//assume new data has already arrived via the pre-existing references to dataBuffX and dataBuffY and FftBuff
|
||||
montageTrace.generate(); //graph doesn't update without this
|
||||
fftTrace.generate(); //graph doesn't update without this
|
||||
headPlot1.update();
|
||||
cc.update();
|
||||
|
||||
//update the text strings
|
||||
String fmt; float val;
|
||||
for (int Ichan=0; Ichan < data_std_uV.length; Ichan++) {
|
||||
//update the voltage values
|
||||
val = data_std_uV[Ichan];
|
||||
chanValuesMontage[Ichan].string = String.format(getFmt(val),val) + " uVrms";
|
||||
if (montageTrace.is_railed != null) {
|
||||
if (montageTrace.is_railed[Ichan].is_railed == true) {
|
||||
chanValuesMontage[Ichan].string = "RAILED";
|
||||
} else if (montageTrace.is_railed[Ichan].is_railed_warn == true) {
|
||||
chanValuesMontage[Ichan].string = "NEAR RAILED";
|
||||
}
|
||||
}
|
||||
|
||||
//update the impedance values
|
||||
val = data_elec_imp_ohm[Ichan]/1000;
|
||||
impValuesMontage[Ichan].string = String.format(getFmt(val),val) + " kOhm";
|
||||
if (montageTrace.is_railed != null) {
|
||||
if (montageTrace.is_railed[Ichan].is_railed == true) {
|
||||
impValuesMontage[Ichan].string = "RAILED";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getFmt(float val) {
|
||||
String fmt;
|
||||
if (val > 100.0f) {
|
||||
fmt = "%.0f";
|
||||
} else if (val > 10.0f) {
|
||||
fmt = "%.1f";
|
||||
} else {
|
||||
fmt = "%.2f";
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
if(!drawEMG){
|
||||
headPlot1.draw();
|
||||
}
|
||||
|
||||
//draw montage or spectrogram
|
||||
if (showSpectrogram == false) {
|
||||
|
||||
//show time-domain montage, only if full channel controller is not visible, to save some processing
|
||||
gMontage.draw();
|
||||
|
||||
//add annotations
|
||||
if (showMontageValues) {
|
||||
for (int Ichan = 0; Ichan < chanValuesMontage.length; Ichan++) {
|
||||
chanValuesMontage[Ichan].draw();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//show the spectrogram
|
||||
gSpectrogram.draw(); //draw the spectrogram axes
|
||||
titleSpectrogram.draw(); //draw the spectrogram title
|
||||
|
||||
//draw the spectrogram image
|
||||
PVector pos = gSpectrogram.position;
|
||||
Axis2D ax = gSpectrogram.getXAxis();
|
||||
int x = ax.valueToPosition(ax.getMinValue())+(int)pos.x;
|
||||
int w = ax.valueToPosition(ax.getMaxValue());
|
||||
ax = gSpectrogram.getYAxis();
|
||||
int y = (int) pos.y - ax.valueToPosition(ax.getMinValue()); //position needs top-left. The MAX value is at the top-left for this plot.
|
||||
int h = ax.valueToPosition(ax.getMaxValue());
|
||||
//println("gui_Manager.draw(): x,y,w,h = " + x + " " + y + " " + w + " " + h);
|
||||
float max_freq_Hz = gSpectrogram.getYAxis().getMaxValue()-0.5f;
|
||||
spectrogram.draw(x,y,w,h,max_freq_Hz);
|
||||
}
|
||||
|
||||
//draw the regular FFT spectrum display
|
||||
gFFT.draw();
|
||||
titleFFT.draw();//println("completed FFT draw...");
|
||||
|
||||
//draw the UI buttons and other elements
|
||||
stopButton.draw();
|
||||
|
||||
//commented out because pages 1-2 are being moved to the left of the EEG montage
|
||||
// guiPageButton.draw();
|
||||
|
||||
switch (guiPage) { //the rest of the elements depend upon what GUI page we're on
|
||||
//note: GUI_PAGE_CHANNEL_ON_OFF is the default at the end
|
||||
case GUI_PAGE_IMPEDANCE_CHECK:
|
||||
//show impedance buttons and text
|
||||
for (int Ichan = 0; Ichan < chanButtons.length; Ichan++) {
|
||||
impedanceButtonsP[Ichan].draw(); //P-channel buttons
|
||||
impedanceButtonsN[Ichan].draw(); //N-channel buttons
|
||||
}
|
||||
for (int Ichan = 0; Ichan < impValuesMontage.length; Ichan++) {
|
||||
impValuesMontage[Ichan].draw(); //impedance values on montage plot
|
||||
}
|
||||
biasButton.draw();
|
||||
break;
|
||||
case GUI_PAGE_HEADPLOT_SETUP:
|
||||
intensityFactorButton.draw();
|
||||
loglinPlotButton.draw();
|
||||
filtBPButton.draw();
|
||||
filtNotchButton.draw();
|
||||
//fftNButton.draw();
|
||||
smoothingButton.draw();
|
||||
showPolarityButton.draw();
|
||||
maxDisplayFreqButton.draw();
|
||||
break;
|
||||
default: //assume GUI_PAGE_CHANNEL_ONOFF:
|
||||
//show channel buttons
|
||||
for (int Ichan = 0; Ichan < chanButtons.length; Ichan++) { chanButtons[Ichan].draw(); }
|
||||
//detectButton.draw();
|
||||
//spectrogramButton.draw();
|
||||
}
|
||||
|
||||
if (showMontageValues) {
|
||||
for (int Ichan = 0; Ichan < chanValuesMontage.length; Ichan++) {
|
||||
chanValuesMontage[Ichan].draw();
|
||||
}
|
||||
}
|
||||
|
||||
// if(controlPanelCollapser.isActive){
|
||||
// controlPanel.draw();
|
||||
// }
|
||||
// controlPanelCollapser.draw();
|
||||
|
||||
cc.draw();
|
||||
if(cc.showFullController == false){
|
||||
titleMontage.draw();
|
||||
}
|
||||
showMontageButton.draw();
|
||||
showChannelControllerButton.draw();
|
||||
|
||||
}
|
||||
|
||||
public void mousePressed(){
|
||||
verbosePrint("Gui_Manager: mousePressed: mouse pressed.");
|
||||
//if showMontage button pressed
|
||||
if(showMontageButton.isMouseHere()){
|
||||
//turn off visibility of channel full controller
|
||||
cc.showFullController = false;
|
||||
showMontageButton.setIsActive(true);
|
||||
showMontageButton.buttonFont = f1;
|
||||
showChannelControllerButton.setIsActive(false);
|
||||
showChannelControllerButton.buttonFont = f2;
|
||||
}
|
||||
//if showChannelController is pressed
|
||||
if(showChannelControllerButton.isMouseHere()){
|
||||
cc.showFullController = true;
|
||||
showMontageButton.setIsActive(false);
|
||||
showMontageButton.buttonFont = f2;
|
||||
showChannelControllerButton.setIsActive(true);
|
||||
showChannelControllerButton.buttonFont = f1;
|
||||
}
|
||||
|
||||
//if cursor inside channel controller
|
||||
// if(mouseX >= cc.x1 && mouseX <= (cc.x2 - cc.w2) && mouseY >= cc.y1 && mouseY <= (cc.y1 + cc.h1) ){
|
||||
verbosePrint("Gui_Manager: mousePressed: Channel Controller mouse pressed...");
|
||||
cc.mousePressed();
|
||||
// }
|
||||
|
||||
|
||||
//turn off visibility of graph
|
||||
// turn on drawing and interactivity of channel controller
|
||||
|
||||
//however, the on/off & impedance values must show to the right at all times ... so it should change a boolean in ChannelController
|
||||
|
||||
}
|
||||
|
||||
public void mouseReleased(){
|
||||
//verbosePrint("Gui_Manager: mouseReleased()");
|
||||
|
||||
// if(mouseX >= cc.x1 && mouseX <= (cc.x2 - cc.w2) && mouseY >= cc.y1 && mouseY <= (cc.y1 + cc.h1) ){
|
||||
verbosePrint("Gui_Manager: mouseReleased(): Channel Controller mouse released...");
|
||||
cc.mouseReleased();
|
||||
|
||||
|
||||
stopButton.setIsActive(false);
|
||||
// guiPageButton.setIsActive(false);
|
||||
intensityFactorButton.setIsActive(false);
|
||||
loglinPlotButton.setIsActive(false);
|
||||
filtBPButton.setIsActive(false);
|
||||
filtNotchButton.setIsActive(false);
|
||||
smoothingButton.setIsActive(false);
|
||||
showPolarityButton.setIsActive(false);
|
||||
maxDisplayFreqButton.setIsActive(false);
|
||||
biasButton.setIsActive(false);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
//this class is used to create the help widget that provides system feedback in response to interactivity
|
||||
//it is intended to serve as a pseudo-console, allowing us to print useful information to the interface as opposed to an IDE console
|
||||
|
||||
class HelpWidget {
|
||||
|
||||
public float x, y, w, h;
|
||||
// ArrayList<String> prevOutputs; //growing list of all previous system interactivity
|
||||
|
||||
String currentOutput = "..."; //current text shown in help widget, based on most recent command
|
||||
|
||||
int padding = 5;
|
||||
|
||||
HelpWidget(float _xPos, float _yPos, float _width, float _height) {
|
||||
x = _xPos;
|
||||
y = _yPos;
|
||||
w = _width;
|
||||
h = _height;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
|
||||
pushStyle();
|
||||
noStroke();
|
||||
|
||||
// draw background of widget
|
||||
fill(255);
|
||||
rect(x, height-h, width, h);
|
||||
|
||||
//draw bg of text field of widget
|
||||
strokeWeight(1);
|
||||
stroke(color(0, 5, 11));
|
||||
fill(color(0, 5, 11));
|
||||
rect(x + padding, height-h + padding, width - padding*5 - 128, h - padding *2);
|
||||
|
||||
textSize(14);
|
||||
fill(255);
|
||||
textAlign(LEFT, TOP);
|
||||
text(currentOutput, padding*2, height - h + padding + 4);
|
||||
|
||||
//draw OpenBCI LOGO
|
||||
image(logo, width - (128+padding*2), height - 26, 128, 22);
|
||||
|
||||
popStyle();
|
||||
}
|
||||
|
||||
public void output(String _output) {
|
||||
currentOutput = _output;
|
||||
// prevOutputs.add(_output);
|
||||
}
|
||||
};
|
||||
|
||||
public void output(String _output) {
|
||||
helpWidget.output(_output);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
|
||||
//===================== MENU LIST CLASS ===========================//
|
||||
//==================EXTENSION OF CONTROLP5=========================//
|
||||
//==============USED FOR SOURCEBOX & SERIALBOX=====================//
|
||||
//
|
||||
// Created: Conor Russomanno Oct. 2014
|
||||
// Based on ControlP5 Processing Library example, written by Andreas Schlegel
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
//makeItem function used by MenuList class below
|
||||
Map<String, Object> makeItem(String theHeadline) {
|
||||
Map m = new HashMap<String, Object>();
|
||||
m.put("headline", theHeadline);
|
||||
return m;
|
||||
}
|
||||
|
||||
public class MenuList extends Controller {
|
||||
|
||||
float pos, npos;
|
||||
int itemHeight = 24;
|
||||
int scrollerLength = 40;
|
||||
int scrollerWidth = 15;
|
||||
List< Map<String, Object>> items = new ArrayList< Map<String, Object>>();
|
||||
PGraphics menu;
|
||||
boolean updateMenu;
|
||||
boolean drawHand;
|
||||
int hoverItem = -1;
|
||||
int activeItem = -1;
|
||||
PFont menuFont = f2;
|
||||
int padding = 7;
|
||||
|
||||
|
||||
MenuList(ControlP5 c, String theName, int theWidth, int theHeight, PFont theFont) {
|
||||
|
||||
super( c, theName, 0, 0, theWidth, theHeight );
|
||||
c.register( this );
|
||||
menu = createGraphics(getWidth(),getHeight());
|
||||
|
||||
menuFont = theFont;
|
||||
|
||||
setView(new ControllerView<MenuList>() {
|
||||
|
||||
public void display(PGraphics pg, MenuList t) {
|
||||
if (updateMenu) {
|
||||
updateMenu();
|
||||
}
|
||||
if (inside()) {
|
||||
if(!drawHand){
|
||||
cursor(HAND);
|
||||
drawHand = true;
|
||||
}
|
||||
menu.beginDraw();
|
||||
int len = -(itemHeight * items.size()) + getHeight();
|
||||
int ty;
|
||||
if(len != 0){
|
||||
ty = int(map(pos, len, 0, getHeight() - scrollerLength - 2, 2 ) );
|
||||
} else {
|
||||
ty = 0;
|
||||
}
|
||||
menu.fill(bgColor, 100);
|
||||
if(ty > 0){
|
||||
menu.rect(getWidth()-scrollerWidth-2, ty, scrollerWidth, scrollerLength );
|
||||
}
|
||||
menu.endDraw();
|
||||
}
|
||||
else {
|
||||
if(drawHand){
|
||||
drawHand = false;
|
||||
cursor(ARROW);
|
||||
}
|
||||
}
|
||||
pg.image(menu, 0, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
updateMenu();
|
||||
}
|
||||
|
||||
/* only update the image buffer when necessary - to save some resources */
|
||||
void updateMenu() {
|
||||
int len = -(itemHeight * items.size()) + getHeight();
|
||||
npos = constrain(npos, len, 0);
|
||||
pos += (npos - pos) * 0.1;
|
||||
// pos += (npos - pos) * 0.1;
|
||||
menu.beginDraw();
|
||||
menu.noStroke();
|
||||
menu.background(255, 64);
|
||||
menu.textFont(cp5.getFont().getFont());
|
||||
menu.pushMatrix();
|
||||
menu.translate( 0, pos );
|
||||
menu.pushMatrix();
|
||||
|
||||
int i0;
|
||||
if((itemHeight * items.size()) != 0){
|
||||
i0 = PApplet.max( 0, int(map(-pos, 0, itemHeight * items.size(), 0, items.size())));
|
||||
} else{
|
||||
i0 = 0;
|
||||
}
|
||||
int range = ceil((float(getHeight())/float(itemHeight))+1);
|
||||
int i1 = PApplet.min( items.size(), i0 + range );
|
||||
|
||||
menu.translate(0, i0*itemHeight);
|
||||
|
||||
for (int i=i0; i<i1; i++) {
|
||||
Map m = items.get(i);
|
||||
menu.fill(255, 100);
|
||||
if (i == hoverItem) {
|
||||
menu.fill(127, 134, 143);
|
||||
}
|
||||
if (i == activeItem) {
|
||||
menu.stroke(184, 220, 105, 255);
|
||||
menu.strokeWeight(1);
|
||||
menu.fill(184, 220, 105, 255);
|
||||
menu.rect(0, 0, getWidth()-1, itemHeight-1 );
|
||||
menu.noStroke();
|
||||
} else {
|
||||
menu.rect(0, 0, getWidth(), itemHeight-1 );
|
||||
}
|
||||
menu.fill(bgColor);
|
||||
menu.textFont(menuFont);
|
||||
menu.text(m.get("headline").toString(), 8, itemHeight - padding); // 5/17
|
||||
menu.translate( 0, itemHeight );
|
||||
}
|
||||
menu.popMatrix();
|
||||
menu.popMatrix();
|
||||
menu.endDraw();
|
||||
updateMenu = abs(npos-pos)>0.01 ? true:false;
|
||||
}
|
||||
|
||||
/* when detecting a click, check if the click happend to the far right, if yes, scroll to that position,
|
||||
* otherwise do whatever this item of the list is supposed to do.
|
||||
*/
|
||||
public void onClick() {
|
||||
if (getPointer().x()>getWidth()-scrollerWidth) {
|
||||
npos= -map(getPointer().y(), 0, getHeight(), 0, items.size()*itemHeight);
|
||||
updateMenu = true;
|
||||
} else {
|
||||
int len = itemHeight * items.size();
|
||||
int index = int( map( getPointer().y() - pos, 0, len, 0, items.size() ) ) ;
|
||||
setValue(index);
|
||||
activeItem = index;
|
||||
}
|
||||
updateMenu = true;
|
||||
}
|
||||
|
||||
public void onMove() {
|
||||
if (getPointer().x()>getWidth() || getPointer().x()<0 || getPointer().y()<0 || getPointer().y()>getHeight() ) {
|
||||
hoverItem = -1;
|
||||
} else {
|
||||
int len = itemHeight * items.size();
|
||||
int index = int( map( getPointer().y() - pos, 0, len, 0, items.size() ) ) ;
|
||||
hoverItem = index;
|
||||
}
|
||||
updateMenu = true;
|
||||
}
|
||||
|
||||
public void onDrag() {
|
||||
if (getPointer().x() > (getWidth()-scrollerWidth)) {
|
||||
npos= -map(getPointer().y(), 0, getHeight(), 0, items.size()*itemHeight);
|
||||
updateMenu = true;
|
||||
} else {
|
||||
npos += getPointer().dy() * 2;
|
||||
updateMenu = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void onScroll(int n) {
|
||||
npos += ( n * 4 );
|
||||
updateMenu = true;
|
||||
}
|
||||
|
||||
void addItem(Map<String, Object> m) {
|
||||
items.add(m);
|
||||
updateMenu = true;
|
||||
}
|
||||
|
||||
void removeItem(Map<String, Object> m) {
|
||||
items.remove(m);
|
||||
updateMenu = true;
|
||||
}
|
||||
|
||||
Map<String, Object> getItem(int theIndex) {
|
||||
return items.get(theIndex);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,382 @@
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This file contains all key commands for interactivity with GUI & OpenBCI
|
||||
// Created by Chip Audette, Joel Murphy, & Conor Russomanno
|
||||
// - Extracted from OpenBCI_GUI because it was getting too klunky
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//interpret a keypress...the key pressed comes in as "key"
|
||||
void keyPressed() {
|
||||
//note that the Processing variable "key" is the keypress as an ASCII character
|
||||
//note that the Processing variable "keyCode" is the keypress as a JAVA keycode. This differs from ASCII
|
||||
//println("OpenBCI_GUI: keyPressed: key = " + key + ", int(key) = " + int(key) + ", keyCode = " + keyCode);
|
||||
|
||||
if(!controlPanel.isOpen){ //don't parse the key if the control panel is open
|
||||
if ((int(key) >=32) && (int(key) <= 126)) { //32 through 126 represent all the usual printable ASCII characters
|
||||
parseKey(key);
|
||||
} else {
|
||||
parseKeycode(keyCode);
|
||||
}
|
||||
}
|
||||
|
||||
if(key==27){
|
||||
key=0; //disable 'esc' quitting program
|
||||
}
|
||||
}
|
||||
|
||||
void parseKey(char val) {
|
||||
int Ichan; boolean activate; int code_P_N_Both;
|
||||
|
||||
//assumes that val is a usual printable ASCII character (ASCII 32 through 126)
|
||||
switch (val) {
|
||||
case '.':
|
||||
drawEMG = !drawEMG;
|
||||
break;
|
||||
|
||||
case '1':
|
||||
deactivateChannel(1-1);
|
||||
break;
|
||||
case '2':
|
||||
deactivateChannel(2-1);
|
||||
break;
|
||||
case '3':
|
||||
deactivateChannel(3-1);
|
||||
break;
|
||||
case '4':
|
||||
deactivateChannel(4-1);
|
||||
break;
|
||||
case '5':
|
||||
deactivateChannel(5-1);
|
||||
break;
|
||||
case '6':
|
||||
deactivateChannel(6-1);
|
||||
break;
|
||||
case '7':
|
||||
deactivateChannel(7-1);
|
||||
break;
|
||||
case '8':
|
||||
deactivateChannel(8-1);
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(9-1);
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(10-1);
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(11-1);
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(12-1);
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(13-1);
|
||||
}
|
||||
break;
|
||||
case 'y':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(14-1);
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(15-1);
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
if(nchan == 16){
|
||||
deactivateChannel(16-1);
|
||||
}
|
||||
break;
|
||||
|
||||
//activate channels 1-8
|
||||
case '!':
|
||||
activateChannel(1-1);
|
||||
break;
|
||||
case '@':
|
||||
activateChannel(2-1);
|
||||
break;
|
||||
case '#':
|
||||
activateChannel(3-1);
|
||||
break;
|
||||
case '$':
|
||||
activateChannel(4-1);
|
||||
break;
|
||||
case '%':
|
||||
activateChannel(5-1);
|
||||
break;
|
||||
case '^':
|
||||
activateChannel(6-1);
|
||||
break;
|
||||
case '&':
|
||||
activateChannel(7-1);
|
||||
break;
|
||||
case '*':
|
||||
activateChannel(8-1);
|
||||
break;
|
||||
|
||||
//activate channels 9-16 (DAISY MODE ONLY)
|
||||
case 'Q':
|
||||
if(nchan == 16){
|
||||
activateChannel(9-1);
|
||||
}
|
||||
break;
|
||||
case 'W':
|
||||
if(nchan == 16){
|
||||
activateChannel(10-1);
|
||||
}
|
||||
break;
|
||||
case 'E':
|
||||
if(nchan == 16){
|
||||
activateChannel(11-1);
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
if(nchan == 16){
|
||||
activateChannel(12-1);
|
||||
}
|
||||
break;
|
||||
case 'T':
|
||||
if(nchan == 16){
|
||||
activateChannel(13-1);
|
||||
}
|
||||
break;
|
||||
case 'Y':
|
||||
if(nchan == 16){
|
||||
activateChannel(14-1);
|
||||
}
|
||||
break;
|
||||
case 'U':
|
||||
if(nchan == 16){
|
||||
activateChannel(15-1);
|
||||
}
|
||||
break;
|
||||
case 'I':
|
||||
if(nchan == 16){
|
||||
activateChannel(16-1);
|
||||
}
|
||||
break;
|
||||
|
||||
//other controls
|
||||
case 's':
|
||||
println("case s...");
|
||||
stopRunning();
|
||||
// stopButtonWasPressed();
|
||||
break;
|
||||
case 'b':
|
||||
println("case b...");
|
||||
startRunning();
|
||||
// stopButtonWasPressed();
|
||||
break;
|
||||
case 'n':
|
||||
println("openBCI: " + openBCI);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
printRegisters();
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
verbosePrint("Updating GUI's channel settings to default...");
|
||||
gui.cc.loadDefaultChannelSettings();
|
||||
//openBCI.serial_openBCI.write('d');
|
||||
openBCI.configureAllChannelsToDefault();
|
||||
break;
|
||||
|
||||
// //change the state of the impedance measurements...activate the N-channels
|
||||
// case 'A':
|
||||
// Ichan = 1; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'S':
|
||||
// Ichan = 2; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'D':
|
||||
// Ichan = 3; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'F':
|
||||
// Ichan = 4; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'G':
|
||||
// Ichan = 5; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'H':
|
||||
// Ichan = 6; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'J':
|
||||
// Ichan = 7; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'K':
|
||||
// Ichan = 8; activate = true; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
|
||||
// //change the state of the impedance measurements...deactivate the N-channels
|
||||
// case 'Z':
|
||||
// Ichan = 1; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'X':
|
||||
// Ichan = 2; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'C':
|
||||
// Ichan = 3; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'V':
|
||||
// Ichan = 4; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'B':
|
||||
// Ichan = 5; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'N':
|
||||
// Ichan = 6; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case 'M':
|
||||
// Ichan = 7; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
// case '<':
|
||||
// Ichan = 8; activate = false; code_P_N_Both = 1; setChannelImpedanceState(Ichan-1,activate,code_P_N_Both);
|
||||
// break;
|
||||
|
||||
|
||||
case 'm':
|
||||
String picfname = "OpenBCI-" + getDateString() + ".jpg";
|
||||
println("OpenBCI_GUI: 'm' was pressed...taking screenshot:" + picfname);
|
||||
saveFrame("./SavedData/" + picfname); // take a shot of that!
|
||||
break;
|
||||
|
||||
default:
|
||||
println("OpenBCI_GUI: '" + key + "' Pressed...sending to OpenBCI...");
|
||||
// if (openBCI.serial_openBCI != null) openBCI.serial_openBCI.write(key);//send the value as ascii with a newline character
|
||||
//if (openBCI.serial_openBCI != null) openBCI.serial_openBCI.write(key);//send the value as ascii with a newline character
|
||||
openBCI.sendChar(key);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void parseKeycode(int val) {
|
||||
//assumes that val is Java keyCode
|
||||
switch (val) {
|
||||
case 8:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received BACKSPACE keypress. Ignoring...");
|
||||
break;
|
||||
case 9:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received TAB keypress. Ignoring...");
|
||||
//gui.showImpedanceButtons = !gui.showImpedanceButtons;
|
||||
// gui.incrementGUIpage(); //deprecated with new channel controller
|
||||
break;
|
||||
case 10:
|
||||
println("Entering Presentation Mode");
|
||||
drawPresentation = !drawPresentation;
|
||||
break;
|
||||
case 16:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received SHIFT keypress. Ignoring...");
|
||||
break;
|
||||
case 17:
|
||||
//println("OpenBCI_GUI: parseKeycode(" + val + "): received CTRL keypress. Ignoring...");
|
||||
break;
|
||||
case 18:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received ALT keypress. Ignoring...");
|
||||
break;
|
||||
case 20:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received CAPS LOCK keypress. Ignoring...");
|
||||
break;
|
||||
case 27:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received ESC keypress. Stopping OpenBCI...");
|
||||
//stopRunning();
|
||||
break;
|
||||
case 33:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received PAGE UP keypress. Ignoring...");
|
||||
break;
|
||||
case 34:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received PAGE DOWN keypress. Ignoring...");
|
||||
break;
|
||||
case 35:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received END keypress. Ignoring...");
|
||||
break;
|
||||
case 36:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received HOME keypress. Ignoring...");
|
||||
break;
|
||||
case 37:
|
||||
println("Slide Back!");
|
||||
if (millis() - myPresentation.timeOfLastSlideChange >= 250) {
|
||||
if(myPresentation.currentSlide >= 0){
|
||||
myPresentation.slideBack();
|
||||
myPresentation.timeOfLastSlideChange = millis();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 38:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received UP ARROW keypress. Ignoring...");
|
||||
eegProcessing_user.switchesActive = true;
|
||||
break;
|
||||
case 39:
|
||||
println("Forward!");
|
||||
if (millis() - myPresentation.timeOfLastSlideChange >= 250) {
|
||||
if(myPresentation.currentSlide < myPresentation.presentationSlides.length - 1){
|
||||
myPresentation.slideForward();
|
||||
myPresentation.timeOfLastSlideChange = millis();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 40:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received DOWN ARROW keypress. Ignoring...");
|
||||
eegProcessing_user.switchesActive = false;
|
||||
break;
|
||||
case 112:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F1 keypress. Ignoring...");
|
||||
break;
|
||||
case 113:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F2 keypress. Ignoring...");
|
||||
break;
|
||||
case 114:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F3 keypress. Ignoring...");
|
||||
break;
|
||||
case 115:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F4 keypress. Ignoring...");
|
||||
break;
|
||||
case 116:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F5 keypress. Ignoring...");
|
||||
break;
|
||||
case 117:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F6 keypress. Ignoring...");
|
||||
break;
|
||||
case 118:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F7 keypress. Ignoring...");
|
||||
break;
|
||||
case 119:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F8 keypress. Ignoring...");
|
||||
break;
|
||||
case 120:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F9 keypress. Ignoring...");
|
||||
break;
|
||||
case 121:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F10 keypress. Ignoring...");
|
||||
break;
|
||||
case 122:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F11 keypress. Ignoring...");
|
||||
break;
|
||||
case 123:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received F12 keypress. Ignoring...");
|
||||
break;
|
||||
case 127:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received DELETE keypress. Ignoring...");
|
||||
break;
|
||||
case 155:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): received INSERT keypress. Ignoring...");
|
||||
break;
|
||||
default:
|
||||
println("OpenBCI_GUI: parseKeycode(" + val + "): value is not known. Ignoring...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Playground Class
|
||||
// Created: 11/22/14 by Conor Russomanno
|
||||
// An extra interface pane for additional GUI features
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class Playground {
|
||||
|
||||
//button for opening and closing
|
||||
float x, y, w, h;
|
||||
color boxBG;
|
||||
color strokeColor;
|
||||
float topMargin, bottomMargin;
|
||||
|
||||
boolean isOpen;
|
||||
boolean collapsing;
|
||||
|
||||
Button collapser;
|
||||
|
||||
Playground(int _topMargin) {
|
||||
|
||||
topMargin = _topMargin;
|
||||
bottomMargin = helpWidget.h;
|
||||
|
||||
isOpen = false;
|
||||
collapsing = true;
|
||||
|
||||
boxBG = color(255);
|
||||
strokeColor = color(138, 146, 153);
|
||||
collapser = new Button(0, 0, 20, 60, "<", 14);
|
||||
|
||||
x = width;
|
||||
y = topMargin;
|
||||
w = 0;
|
||||
h = height - (topMargin+bottomMargin);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
// verbosePrint("uh huh");
|
||||
if (collapsing) {
|
||||
collapse();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
|
||||
if (x > width) {
|
||||
x = width;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
// verbosePrint("yeaaa");
|
||||
pushStyle();
|
||||
fill(boxBG);
|
||||
stroke(strokeColor);
|
||||
rect(width - w, topMargin, w, height - (topMargin + bottomMargin));
|
||||
textFont(f1);
|
||||
textAlign(LEFT, TOP);
|
||||
fill(bgColor);
|
||||
text("Developer Playground", x + 10, y + 10);
|
||||
fill(255, 0, 0);
|
||||
collapser.draw(int(x - collapser.but_dx), int(topMargin + (h-collapser.but_dy)/2));
|
||||
popStyle();
|
||||
}
|
||||
|
||||
boolean isMouseHere() {
|
||||
if (mouseX >= x && mouseX <= width && mouseY >= y && mouseY <= height - bottomMargin) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isMouseInButton() {
|
||||
verbosePrint("Playground: isMouseInButton: attempting");
|
||||
if (mouseX >= collapser.but_x && mouseX <= collapser.but_x+collapser.but_dx && mouseY >= collapser.but_y && mouseY <= collapser.but_y + collapser.but_dy) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleWindow() {
|
||||
if (isOpen) {//if open
|
||||
verbosePrint("close");
|
||||
collapsing = true;//collapsing = true;
|
||||
isOpen = false;
|
||||
collapser.but_txt = "<";
|
||||
} else {//if closed
|
||||
verbosePrint("open");
|
||||
collapsing = false;//expanding = true;
|
||||
isOpen = true;
|
||||
collapser.but_txt = ">";
|
||||
}
|
||||
}
|
||||
|
||||
public void mousePressed() {
|
||||
verbosePrint("Playground >> mousePressed()");
|
||||
}
|
||||
|
||||
public void mouseReleased() {
|
||||
verbosePrint("Playground >> mouseReleased()");
|
||||
}
|
||||
|
||||
public void expand() {
|
||||
if (w <= width/3) {
|
||||
w = w + 50;
|
||||
x = width - w;
|
||||
}
|
||||
}
|
||||
|
||||
public void collapse() {
|
||||
if (w >= 0) {
|
||||
w = w - 50;
|
||||
x = width - w;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Created: 2/19/16
|
||||
// by Conor Russomanno for BodyHacking Con DIY Cyborgia Presentation
|
||||
// This code is used to organize a neuro-powered presentation... refer to triggers in the EEG_Processing_User class of the EEG_Processing.pde file
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
boolean drawPresentation = false;
|
||||
|
||||
class Presentation {
|
||||
//presentation images
|
||||
PImage presentationSlides[] = new PImage[16];
|
||||
float timeOfLastSlideChange = 0;
|
||||
int currentSlide = 1;
|
||||
int slideCount = 0;
|
||||
boolean lockSlides = false;
|
||||
|
||||
Presentation (){
|
||||
//loading presentation images
|
||||
println("attempting to load images for presentation...");
|
||||
presentationSlides[0] = loadImage("prez-images/DemocratizationOfNeurotech.001.jpg");
|
||||
presentationSlides[1] = loadImage("prez-images/DemocratizationOfNeurotech.001.jpg");
|
||||
presentationSlides[2] = loadImage("prez-images/DemocratizationOfNeurotech.002.jpg");
|
||||
presentationSlides[3] = loadImage("prez-images/DemocratizationOfNeurotech.003.jpg");
|
||||
presentationSlides[4] = loadImage("prez-images/DemocratizationOfNeurotech.004.jpg");
|
||||
presentationSlides[5] = loadImage("prez-images/DemocratizationOfNeurotech.005.jpg");
|
||||
presentationSlides[6] = loadImage("prez-images/DemocratizationOfNeurotech.006.jpg");
|
||||
presentationSlides[7] = loadImage("prez-images/DemocratizationOfNeurotech.007.jpg");
|
||||
presentationSlides[8] = loadImage("prez-images/DemocratizationOfNeurotech.008.jpg");
|
||||
presentationSlides[9] = loadImage("prez-images/DemocratizationOfNeurotech.009.jpg");
|
||||
presentationSlides[10] = loadImage("prez-images/DemocratizationOfNeurotech.010.jpg");
|
||||
presentationSlides[11] = loadImage("prez-images/DemocratizationOfNeurotech.011.jpg");
|
||||
presentationSlides[12] = loadImage("prez-images/DemocratizationOfNeurotech.012.jpg");
|
||||
presentationSlides[13] = loadImage("prez-images/DemocratizationOfNeurotech.013.jpg");
|
||||
presentationSlides[14] = loadImage("prez-images/DemocratizationOfNeurotech.014.jpg");
|
||||
presentationSlides[15] = loadImage("prez-images/DemocratizationOfNeurotech.015.jpg");
|
||||
//presentationSlides[16] = loadImage("prez-images/DemocratizationOfNeurotech.016.jpg");
|
||||
//presentationSlides[17] = loadImage("prez-images/DemocratizationOfNeurotech.017.jpg");
|
||||
//presentationSlides[18] = loadImage("prez-images/DemocratizationOfNeurotech.018.jpg");
|
||||
//presentationSlides[19] = loadImage("prez-images/DemocratizationOfNeurotech.019.jpg");
|
||||
//presentationSlides[20] = loadImage("prez-images/DemocratizationOfNeurotech.020.jpg");
|
||||
//presentationSlides[21] = loadImage("prez-images/DemocratizationOfNeurotech.021.jpg");
|
||||
//presentationSlides[22] = loadImage("prez-images/DemocratizationOfNeurotech.022.jpg");
|
||||
//presentationSlides[23] = loadImage("prez-images/DemocratizationOfNeurotech.023.jpg");
|
||||
//presentationSlides[24] = loadImage("prez-images/DemocratizationOfNeurotech.024.jpg");
|
||||
//presentationSlides[25] = loadImage("prez-images/DemocratizationOfNeurotech.025.jpg");
|
||||
//presentationSlides[26] = loadImage("prez-images/DemocratizationOfNeurotech.026.jpg");
|
||||
//presentationSlides[27] = loadImage("prez-images/DemocratizationOfNeurotech.027.jpg");
|
||||
//presentationSlides[28] = loadImage("prez-images/DemocratizationOfNeurotech.028.jpg");
|
||||
//presentationSlides[29] = loadImage("prez-images/DemocratizationOfNeurotech.029.jpg");
|
||||
//presentationSlides[30] = loadImage("prez-images/DemocratizationOfNeurotech.030.jpg");
|
||||
//presentationSlides[31] = loadImage("prez-images/DemocratizationOfNeurotech.031.jpg");
|
||||
//presentationSlides[32] = loadImage("prez-images/DemocratizationOfNeurotech.032.jpg");
|
||||
//presentationSlides[33] = loadImage("prez-images/DemocratizationOfNeurotech.033.jpg");
|
||||
//presentationSlides[34] = loadImage("prez-images/DemocratizationOfNeurotech.034.jpg");
|
||||
//presentationSlides[35] = loadImage("prez-images/DemocratizationOfNeurotech.035.jpg");
|
||||
//presentationSlides[36] = loadImage("prez-images/DemocratizationOfNeurotech.036.jpg");
|
||||
//presentationSlides[37] = loadImage("prez-images/DemocratizationOfNeurotech.037.jpg");
|
||||
//presentationSlides[38] = loadImage("prez-images/DemocratizationOfNeurotech.038.jpg");
|
||||
//presentationSlides[39] = loadImage("prez-images/DemocratizationOfNeurotech.039.jpg");
|
||||
//presentationSlides[40] = loadImage("prez-images/DemocratizationOfNeurotech.040.jpg");
|
||||
//presentationSlides[41] = loadImage("prez-images/DemocratizationOfNeurotech.041.jpg");
|
||||
//presentationSlides[42] = loadImage("prez-images/DemocratizationOfNeurotech.042.jpg");
|
||||
//presentationSlides[43] = loadImage("prez-images/DemocratizationOfNeurotech.043.jpg");
|
||||
//presentationSlides[44] = loadImage("prez-images/DemocratizationOfNeurotech.044.jpg");
|
||||
//presentationSlides[45] = loadImage("prez-images/DemocratizationOfNeurotech.045.jpg");
|
||||
//presentationSlides[46] = loadImage("prez-images/DemocratizationOfNeurotech.046.jpg");
|
||||
//presentationSlides[47] = loadImage("prez-images/DemocratizationOfNeurotech.047.jpg");
|
||||
//presentationSlides[48] = loadImage("prez-images/DemocratizationOfNeurotech.048.jpg");
|
||||
slideCount = 28;
|
||||
println("DONE loading images!");
|
||||
|
||||
}
|
||||
|
||||
public void slideForward() {
|
||||
if(currentSlide < slideCount - 1 && drawPresentation && !lockSlides){
|
||||
println("Slide Forward!");
|
||||
currentSlide++;
|
||||
}
|
||||
}
|
||||
|
||||
public void slideBack() {
|
||||
if(currentSlide > 0 && drawPresentation && !lockSlides){
|
||||
println("Slide Back!");
|
||||
currentSlide--;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
// ----- Drawing Presentation -------
|
||||
if (drawPresentation == true) {
|
||||
image(presentationSlides[currentSlide], 0, 0, width, height);
|
||||
}
|
||||
|
||||
if(lockSlides){
|
||||
//draw red rectangle to indicate that slides are locked
|
||||
pushStyle();
|
||||
fill(255,0,0);
|
||||
rect(width - 50, 25, 25, 25);
|
||||
popStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
//////////////////////////////////
|
||||
//
|
||||
// This file contains code used to convert HEX files (stored by OpenBCI on the local SD) into
|
||||
// text files that can be used for PLAYBACK mode.
|
||||
// Created: Conor Russomanno - 10/22/14 (based on code written by Joel Murphy summer 2014)
|
||||
//
|
||||
//////////////////////////////////
|
||||
|
||||
//variables for SD file conversion
|
||||
BufferedReader dataReader;
|
||||
String dataLine;
|
||||
PrintWriter dataWriter;
|
||||
String convertedLine;
|
||||
String thisLine;
|
||||
String h;
|
||||
float[] intData = new float[20];
|
||||
String logFileName;
|
||||
long thisTime;
|
||||
long thatTime;
|
||||
|
||||
public void convertSDFile() {
|
||||
println("");
|
||||
try {
|
||||
dataLine = dataReader.readLine();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
dataLine = null;
|
||||
}
|
||||
|
||||
if (dataLine == null) {
|
||||
// Stop reading because of an error or file is empty
|
||||
thisTime = millis() - thatTime;
|
||||
controlPanel.convertingSD = false;
|
||||
println("nothing left in file");
|
||||
println("SD file conversion took "+thisTime+" mS");
|
||||
dataWriter.flush();
|
||||
dataWriter.close();
|
||||
} else {
|
||||
// println(dataLine);
|
||||
String[] hexNums = splitTokens(dataLine, ",");
|
||||
|
||||
if (hexNums[0].charAt(0) == '%') {
|
||||
// println(dataLine);
|
||||
dataWriter.println(dataLine);
|
||||
println(dataLine);
|
||||
} else {
|
||||
for (int i=0; i<hexNums.length; i++) {
|
||||
h = hexNums[i];
|
||||
if (i > 0) {
|
||||
if (h.charAt(0) > '7') { // if the number is negative
|
||||
h = "FF" + hexNums[i]; // keep it negative
|
||||
} else { // if the number is positive
|
||||
h = "00" + hexNums[i]; // keep it positive
|
||||
}
|
||||
if (i > 8) { // accelerometer data needs another byte
|
||||
if (h.charAt(0) == 'F') {
|
||||
h = "FF" + h;
|
||||
} else {
|
||||
h = "00" + h;
|
||||
}
|
||||
}
|
||||
}
|
||||
// println(h); // use for debugging
|
||||
if (h.length()%2 == 0) { // make sure this is a real number
|
||||
intData[i] = unhex(h);
|
||||
} else {
|
||||
intData[i] = 0;
|
||||
}
|
||||
|
||||
//if not first column(sample #) or columns 9-11 (accelerometer), convert to uV
|
||||
if (i>=1 && i<=8) {
|
||||
intData[i] *= openBCI.get_scale_fac_uVolts_per_count();
|
||||
}
|
||||
|
||||
//print the current channel value
|
||||
dataWriter.print(intData[i]);
|
||||
if (i < hexNums.length-1) {
|
||||
//print "," separator
|
||||
dataWriter.print(",");
|
||||
}
|
||||
}
|
||||
//println();
|
||||
dataWriter.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void createPlaybackFileFromSD() {
|
||||
logFileName = "data/EEG_Data/SDconverted-"+getDateString()+".txt";
|
||||
dataWriter = createWriter(logFileName);
|
||||
dataWriter.println("%OBCI Data Log - " + getDateString());
|
||||
}
|
||||
|
||||
void sdFileSelected(File selection) {
|
||||
if (selection == null) {
|
||||
println("Window was closed or the user hit cancel.");
|
||||
} else {
|
||||
println("User selected " + selection.getAbsolutePath());
|
||||
dataReader = createReader(selection.getAbsolutePath()); // ("positions.txt");
|
||||
controlPanel.convertingSD = true;
|
||||
println("Timing SD file conversion...");
|
||||
thatTime = millis();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Should be about 23 minutes long....
|
||||
5 minutes of baseline (reading google news on my cell phone, my default activity)
|
||||
15 minutes of breathing meditation (there were two interval bells at 5 minute intervals tha tmay be visible on eeg)
|
||||
then, another ~ 3 min of baseline google news reading.
|
||||
@@ -0,0 +1,4 @@
|
||||
Should be about 9 minutes long....
|
||||
2 minutes of baseline (reading google news on my cell phone, my default activity)
|
||||
5 minutes of relaxation, letting thoughts wander, pondering last minute christmas presents
|
||||
then, 2 min of reading chip's blog post on eye blinks.
|
||||
@@ -0,0 +1,414 @@
|
||||
|
||||
//////////////////
|
||||
//
|
||||
// The ScatterTrace class is used to draw and manage the traces on each
|
||||
// X-Y line plot created using gwoptics graphing library
|
||||
//
|
||||
// Created: Chip Audette, May 2014
|
||||
//
|
||||
// Based on examples in gwoptics graphic library v0.5.0
|
||||
// http://www.gwoptics.org/processing/gwoptics_p5lib/
|
||||
//
|
||||
// Note that this class does NOT store any of the data used for the
|
||||
// plot. Instead, you point it to the data that lives in your
|
||||
// own program. In Java-speak, I believe that this is called
|
||||
// "aliasing"...in this class, I have made an "alias" to your data.
|
||||
// Some people consider this dangerous. Because Processing is slow,
|
||||
// this was one technique for making it faster. By making an alias
|
||||
// to your data, you don't need to pass me the data for every update
|
||||
// and I don't need to make a copy of it. Instead, once you update
|
||||
// your data array, the alias in this class is already pointing to
|
||||
// the right place. Cool, huh?
|
||||
//
|
||||
////////////////
|
||||
|
||||
//import processing.core.PApplet;
|
||||
import org.gwoptics.graphics.*;
|
||||
import org.gwoptics.graphics.graph2D.*;
|
||||
import org.gwoptics.graphics.graph2D.Graph2D;
|
||||
import org.gwoptics.graphics.graph2D.LabelPos;
|
||||
import org.gwoptics.graphics.graph2D.traces.Blank2DTrace;
|
||||
import org.gwoptics.graphics.graph2D.backgrounds.*;
|
||||
import java.awt.Color;
|
||||
|
||||
class ScatterTrace extends Blank2DTrace {
|
||||
private float[] dataX;
|
||||
private float[][] dataY;
|
||||
private float plotYScale = 1f; //multiplied to data prior to plotting
|
||||
private float plotYOffset[]; //added to data prior to plotting, after applying plotYScale
|
||||
private int decimate_factor = 1; // set to 1 to plot all points, 2 to plot every other point, 3 for every third point
|
||||
private DataStatus[] is_railed;
|
||||
PFont font = createFont("Arial", 16);
|
||||
float[] plotXlim;
|
||||
|
||||
public ScatterTrace() {
|
||||
//font = createFont("Arial",10);
|
||||
plotXlim = new float[] {
|
||||
Float.NaN, Float.NaN
|
||||
};
|
||||
}
|
||||
|
||||
/* set the plot's X and Y data by overwriting the existing data */
|
||||
public void setXYData_byRef(float[] x, float[][] y) {
|
||||
//dataX = x.clone(); //makes a copy
|
||||
dataX = x; //just copies the reference!
|
||||
setYData_byRef(y);
|
||||
}
|
||||
|
||||
public void setYData_byRef(float[][] y) {
|
||||
//dataY = y.clone(); //makes a copy
|
||||
dataY = y;//just copies the reference!
|
||||
}
|
||||
|
||||
public void setYOffset_byRef(float[] yoff) {
|
||||
plotYOffset = yoff; //just copies the reference!
|
||||
}
|
||||
|
||||
public void setYScale_uV(float yscale_uV) {
|
||||
setYScaleFac(1.0f/yscale_uV);
|
||||
}
|
||||
|
||||
public void setYScaleFac(float yscale) {
|
||||
plotYScale = yscale;
|
||||
}
|
||||
|
||||
public void set_plotXlim(float val_low, float val_high) {
|
||||
if (val_high < val_low) {
|
||||
float foo = val_low;
|
||||
val_low = val_high;
|
||||
val_high = foo;
|
||||
}
|
||||
plotXlim[0]=val_low;
|
||||
plotXlim[1]=val_high;
|
||||
}
|
||||
public void set_isRailed(DataStatus[] is_rail) {
|
||||
is_railed = is_rail;
|
||||
}
|
||||
|
||||
//here is the fucntion that gets called with every call to the GUI's own draw() fucntion
|
||||
public void TraceDraw(Blank2DTrace.PlotRenderer pr) {
|
||||
float x_val;
|
||||
|
||||
if (dataX.length > 0) {
|
||||
pr.canvas.pushStyle(); //save whatever was the previous style
|
||||
//pr.canvas.stroke(255, 0, 0); //set the new line's color
|
||||
//pr.canvas.strokeWeight(1); //set the new line's linewidth
|
||||
|
||||
//draw all the individual segments
|
||||
for (int Ichan = 0; Ichan < dataY.length; Ichan++) {
|
||||
|
||||
//if colorMode == 1 ...
|
||||
switch (Ichan % 8) {
|
||||
case 0:
|
||||
pr.canvas.stroke(129, 129, 129); //set the new line's color;
|
||||
break;
|
||||
case 1:
|
||||
pr.canvas.stroke(124, 75, 141); //set the new line's color;
|
||||
break;
|
||||
case 2:
|
||||
pr.canvas.stroke(54, 87, 158); //set the new line's color;
|
||||
break;
|
||||
case 3:
|
||||
pr.canvas.stroke(49, 113, 89); //set the new line's color;
|
||||
break;
|
||||
case 4:
|
||||
pr.canvas.stroke(221, 178, 13); //set the new line's color;
|
||||
break;
|
||||
case 5:
|
||||
pr.canvas.stroke(253, 94, 52); //set the new line's color;
|
||||
break;
|
||||
case 6:
|
||||
pr.canvas.stroke(224, 56, 45); //set the new line's color;
|
||||
break;
|
||||
case 7:
|
||||
pr.canvas.stroke(162, 82, 49); //set the new line's color;
|
||||
break;
|
||||
}
|
||||
|
||||
//if colorMode == 2 ... for future dev work ... want to be able to edit colors of EEG montage traces
|
||||
|
||||
// color _RGB = Color.HSBtoRGB(float((255/OpenBCI_Nchannels)*Ichan), 100.0f, 100.0f);
|
||||
// println("_RGB: " + _RGB);
|
||||
// pr.canvas.stroke(_RGB);
|
||||
|
||||
// pr.canvas.stroke((int((255/OpenBCI_Nchannels)*Ichan)), 125-(int(((255/OpenBCI_Nchannels)*Ichan)/2)), 255-(int((255/OpenBCI_Nchannels)*Ichan)));
|
||||
// pr.canvas.stroke((int((255/nchan)*Ichan)), 125-(int(((255/nchan)*Ichan)/2)), 255-(int((255/nchan)*Ichan)));
|
||||
|
||||
float new_x = pr.valToX(dataX[0]); //first point, convert from data coordinates to pixel coordinates
|
||||
float new_y = pr.valToY(dataY[Ichan][0]*plotYScale+plotYOffset[Ichan]); //first point, convert from data coordinates to pixel coordinate
|
||||
float prev_x, prev_y;
|
||||
for (int i=1; i < dataY[Ichan].length; i+= decimate_factor) {
|
||||
prev_x = new_x;
|
||||
prev_y = new_y;
|
||||
x_val = dataX[i];
|
||||
if ( (Float.isNaN(plotXlim[0])) || ((x_val >= plotXlim[0]) && (x_val <= plotXlim[1])) ) {
|
||||
new_x = pr.valToX(x_val);
|
||||
new_y = pr.valToY(dataY[Ichan][i]*plotYScale+plotYOffset[Ichan]);
|
||||
pr.canvas.line(prev_x, prev_y, new_x, new_y);
|
||||
//if (i==1) println("ScatterTrace: first point: new_x, new_y = " + new_x + ", " + new_y);
|
||||
} else {
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
//add annotation for is_railed...doesn't work right
|
||||
// if (is_railed != null) {
|
||||
// if (Ichan < is_railed.length) {
|
||||
// if (is_railed[Ichan]) {
|
||||
// new_x = pr.valToX(-2.0); //near time zero
|
||||
// new_y = pr.valToY(0.0+plotYOffset[Ichan]);
|
||||
// println("ScatterTrace: text: new_x, new_y = " + new_x + ", " + new_y);
|
||||
// fill(50,50,50);
|
||||
// textFont(font);
|
||||
// textAlign(RIGHT, BOTTOM);
|
||||
// pr.canvas.text("RAILED",new_x,new_y,100);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
pr.canvas.popStyle(); //restore whatever was the previous style
|
||||
}
|
||||
}
|
||||
|
||||
public void setDecimateFactor(int val) {
|
||||
decimate_factor = max(1, val);
|
||||
//println("ScatterTrace: setDecimateFactor to " + decimate_factor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class ScatterTrace_FFT extends Blank2DTrace {
|
||||
private FFT[] fftData;
|
||||
private float plotYOffset[];
|
||||
private float[] plotXlim = new float[] {
|
||||
Float.NaN, Float.NaN
|
||||
};
|
||||
private float[] goodBand_Hz = {
|
||||
-1.0f, -1.0f
|
||||
};
|
||||
private float[] badBand_Hz = {
|
||||
-1.0f, -1.0f
|
||||
};
|
||||
private boolean showFFTFilteringData = false;
|
||||
private DetectionData_FreqDomain[] detectionData;
|
||||
private Oscil wave;
|
||||
|
||||
public ScatterTrace_FFT() {
|
||||
}
|
||||
|
||||
public ScatterTrace_FFT(FFT foo_fft[]) {
|
||||
setFFT_byRef(foo_fft);
|
||||
// if (foo_fft.length != plotYOffset.length) {
|
||||
// plotYOffset = new float[foo_fft.length];
|
||||
// }
|
||||
}
|
||||
|
||||
public void setFFT_byRef(FFT foo_fft[]) {
|
||||
fftData = foo_fft;//just copies the reference!
|
||||
}
|
||||
|
||||
public void setYOffset(float yoff[]) {
|
||||
plotYOffset = yoff;
|
||||
}
|
||||
public void set_plotXlim(float val_low, float val_high) {
|
||||
if (val_high < val_low) {
|
||||
float foo = val_low;
|
||||
val_low = val_high;
|
||||
val_high = foo;
|
||||
}
|
||||
plotXlim[0]=val_low;
|
||||
plotXlim[1]=val_high;
|
||||
}
|
||||
|
||||
public void setGoodBand(float band_Hz[]) {
|
||||
for (int i=0; i<2; i++) {
|
||||
goodBand_Hz[i]=band_Hz[i];
|
||||
};
|
||||
}
|
||||
public void setBadBand(float band_Hz[]) {
|
||||
for (int i=0; i<2; i++) {
|
||||
badBand_Hz[i]=band_Hz[i];
|
||||
};
|
||||
}
|
||||
public void showFFTFilteringData(boolean show) {
|
||||
showFFTFilteringData = show;
|
||||
}
|
||||
public void setDetectionData_freqDomain(DetectionData_FreqDomain[] data) {
|
||||
detectionData = data.clone();
|
||||
}
|
||||
public void setAudioOscillator(Oscil wave_given) {
|
||||
wave = wave_given;
|
||||
}
|
||||
|
||||
public void TraceDraw(Blank2DTrace.PlotRenderer pr) {
|
||||
float x_val, spec_value;
|
||||
|
||||
//save whatever was the previous style
|
||||
pr.canvas.pushStyle();
|
||||
|
||||
// //add FFT processing bands
|
||||
// float[] fooBand_Hz;
|
||||
// for (int i=0; i<2; i++) {
|
||||
// if (i==0) {
|
||||
// fooBand_Hz = goodBand_Hz;
|
||||
// pr.canvas.stroke(100,255,100);
|
||||
// } else {
|
||||
// fooBand_Hz = badBand_Hz;
|
||||
// pr.canvas.stroke(255,100,100);
|
||||
// }
|
||||
// pr.canvas.strokeWeight(13);
|
||||
// float x1 = pr.valToX(fooBand_Hz[0]);
|
||||
// float x2 = pr.valToX(fooBand_Hz[1]);
|
||||
// if (!showFFTFilteringData) {
|
||||
// x1 = -1.0f; x2=-1.0f; //draw offscreen when not active
|
||||
// }
|
||||
// float y1 = pr.valToY(0.13f);
|
||||
// float y2 = pr.valToY(0.13f);
|
||||
// pr.canvas.line(x1,y1,x2,y2);
|
||||
// }
|
||||
|
||||
if (fftData != null) {
|
||||
pr.canvas.pushStyle(); //save whatever was the previous style
|
||||
|
||||
//draw all the individual segments
|
||||
for (int Ichan=0; Ichan < fftData.length; Ichan++) {
|
||||
//if colorMode == 1 ...
|
||||
switch (Ichan % 8) {
|
||||
case 0:
|
||||
pr.canvas.stroke(129, 129, 129); //set the new line's color;
|
||||
break;
|
||||
case 1:
|
||||
pr.canvas.stroke(124, 75, 141); //set the new line's color;
|
||||
break;
|
||||
case 2:
|
||||
pr.canvas.stroke(54, 87, 158); //set the new line's color;
|
||||
break;
|
||||
case 3:
|
||||
pr.canvas.stroke(49, 113, 89); //set the new line's color;
|
||||
break;
|
||||
case 4:
|
||||
pr.canvas.stroke(221, 178, 13); //set the new line's color;
|
||||
break;
|
||||
case 5:
|
||||
pr.canvas.stroke(253, 94, 52); //set the new line's color;
|
||||
break;
|
||||
case 6:
|
||||
pr.canvas.stroke(224, 56, 45); //set the new line's color;
|
||||
break;
|
||||
case 7:
|
||||
pr.canvas.stroke(162, 82, 49); //set the new line's color;
|
||||
break;
|
||||
}
|
||||
|
||||
// //if colorMode == 2...
|
||||
// // pr.canvas.stroke((int((255/OpenBCI_Nchannels)*Ichan)), 125-(int(((255/OpenBCI_Nchannels)*Ichan)/2)), 255-(int((255/OpenBCI_Nchannels)*Ichan)));
|
||||
// pr.canvas.stroke((int((255/nchan)*Ichan)), 125-(int(((255/nchan)*Ichan)/2)), 255-(int((255/nchan)*Ichan)));
|
||||
|
||||
|
||||
float new_x = pr.valToX(fftData[Ichan].indexToFreq(0)); //first point, convert from data coordinates to pixel coordinates
|
||||
float new_y = pr.valToY(fftData[Ichan].getBand(0)+plotYOffset[Ichan]); //first point, convert from data coordinates to pixel coordinate
|
||||
float prev_x, prev_y;
|
||||
for (int i=1; i < fftData[Ichan].specSize (); i++) {
|
||||
prev_x = new_x;
|
||||
prev_y = new_y;
|
||||
x_val = fftData[Ichan].indexToFreq(i);
|
||||
//only plot those points that are within the frequency limits of the plot
|
||||
if ( (Float.isNaN(plotXlim[0])) || ((x_val >= plotXlim[0]) && (x_val <= plotXlim[1])) ) {
|
||||
new_x = pr.valToX(x_val);
|
||||
//spec_value = fftData[Ichan].getBand(i)/fftData[Ichan].specSize(); //uV_per_bin...this normalization is now done elsewhere
|
||||
spec_value = fftData[Ichan].getBand(i);
|
||||
new_y = pr.valToY(spec_value+plotYOffset[Ichan]);
|
||||
pr.canvas.line(prev_x, prev_y, new_x, new_y);
|
||||
} else {
|
||||
//do nothing
|
||||
} // end if Float.isNan
|
||||
} //end of loop over spec size
|
||||
|
||||
// //add detection-related graphics
|
||||
// if (showFFTFilteringData) {
|
||||
// //add ellipse showing peak
|
||||
// float new_x2 = pr.valToX(detectionData[Ichan].inband_freq_Hz);
|
||||
// float new_y2 = pr.valToY(detectionData[Ichan].inband_uV);
|
||||
// int diam = 8;
|
||||
// pr.canvas.strokeWeight(1); //set the new line's linewidth
|
||||
// if (detectionData[Ichan].isDetected) { //if there is a detection, make more prominent
|
||||
// diam = 8;
|
||||
// pr.canvas.strokeWeight(4); //set the new line's linewidth
|
||||
// }
|
||||
// ellipseMode(CENTER);
|
||||
// pr.canvas.ellipse(new_x2,new_y2,diam,diam);
|
||||
//
|
||||
// //add horizontal lines indicating the detction threshold and guard level (use a dashed line)
|
||||
// for (int Iband=0;Iband<2;Iband++) {
|
||||
// float x1, x2,y;
|
||||
// if (Iband==1) {
|
||||
// x1 = pr.valToX(badBand_Hz[0]);
|
||||
// x2 = pr.valToX(badBand_Hz[1]);
|
||||
// y = pr.valToY(detectionData[Ichan].guard_uV);
|
||||
// } else {
|
||||
// x1 = pr.valToX(goodBand_Hz[0]);
|
||||
// x2 = pr.valToX(goodBand_Hz[1]);
|
||||
// y = pr.valToY(detectionData[Ichan].thresh_uV);
|
||||
// }
|
||||
//
|
||||
// pr.canvas.strokeWeight(1.5);
|
||||
// float dx = 8; //how big is the dash+space
|
||||
// float nudge = 2;
|
||||
// float foo_x=min(x1+dx,x2); //start here
|
||||
// while (foo_x < x2) {
|
||||
// pr.canvas.line(foo_x-dx+nudge,y,foo_x-(5*dx)/8+nudge,y);
|
||||
// foo_x += dx;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
} // end loop over channels
|
||||
|
||||
// //update the audio
|
||||
// if (showFFTFilteringData & (wave != null)) {
|
||||
// //find if any channels have detected, and which is the strongest SNR
|
||||
// float maxExcessSNR = -100.0f;
|
||||
// for (int Ichan=0; Ichan < detectionData.length; Ichan++) {
|
||||
// if (detectionData[Ichan].isDetected) {
|
||||
// //how much above the threshold are we
|
||||
// maxExcessSNR = max(maxExcessSNR,(detectionData[Ichan].inband_uV)/(detectionData[Ichan].thresh_uV));
|
||||
// }
|
||||
// }
|
||||
// float audioFreq_Hz = calcDesiredAudioFrequency(maxExcessSNR);
|
||||
// if (audioFreq_Hz > 0) {
|
||||
// wave.amplitude.setLastValue(0.8); //turn on
|
||||
// wave.frequency.setLastValue(audioFreq_Hz); //set the desired frequency
|
||||
// println("ScatterTrace: excessSNR = " + maxExcessSNR + ", freq = " + audioFreq_Hz + " Hz");
|
||||
// } else {
|
||||
// //turn off
|
||||
// wave.amplitude.setLastValue(0.0);
|
||||
// }
|
||||
// } else {
|
||||
// //ensure that the audio is off
|
||||
// wave.amplitude.setLastValue(0);
|
||||
// }
|
||||
|
||||
|
||||
pr.canvas.popStyle(); //restore whatever was the previous style
|
||||
}
|
||||
}
|
||||
|
||||
float calcDesiredAudioFrequency(float excessSNR) {
|
||||
//set some constants
|
||||
final float excessSNRRange[] = {
|
||||
1.0f, 3.0f
|
||||
}; //not dB, just linear units
|
||||
final float freqRange_Hz[] = {
|
||||
200.0f, 600.0f
|
||||
};
|
||||
|
||||
//compute the desired snr
|
||||
float outputFreq_Hz = -1.0f;
|
||||
if (excessSNR >= excessSNRRange[0]) {
|
||||
excessSNR = constrain(excessSNR, excessSNRRange[0], excessSNRRange[1]);
|
||||
outputFreq_Hz = map(excessSNR, excessSNRRange[0], excessSNRRange[1], freqRange_Hz[0], freqRange_Hz[1]);
|
||||
}
|
||||
return outputFreq_Hz;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
///////////////////////////////////////////////
|
||||
//
|
||||
// Created: Chip Audette, Oct 2013
|
||||
// Modified: through May 2014
|
||||
//
|
||||
// No warranty. Use at your own risk. Use for whatever you'd like.
|
||||
//
|
||||
///////////////////////////////////////////////
|
||||
|
||||
|
||||
//import ddf.minim.analysis.*; //for FFT
|
||||
|
||||
class Spectrogram {
|
||||
public int Nfft;
|
||||
//public float dataSlices[][]; //holds the data in [Nslices][Nfft] manner
|
||||
//public float dT_perSlice_sec; //time interval between slices
|
||||
public float fs_Hz; //sample rate
|
||||
public PImage img;
|
||||
public double clim[] = {0.0d, 1.0d};
|
||||
private FFT localFftData;
|
||||
private float[] localDataBuff;
|
||||
private int localDataBuffCounter;
|
||||
public int fft_stepsize;
|
||||
public int Nslices;
|
||||
|
||||
|
||||
Spectrogram(int N, float fs, int fft_step, float tmax_sec) {
|
||||
println("Spectrogram: N, fs, fft_step, tmax_sec = " + N + " " + fs + " " + fft_step + " " + tmax_sec);
|
||||
Nfft=N;
|
||||
fs_Hz = fs;
|
||||
//dT_perSlice_sec = ((float)Nfft) / fs;
|
||||
fft_stepsize = constrain(fft_step,1,Nfft);
|
||||
// clim[0] = java.lang.Math.log(0.01f);
|
||||
// clim[1] = java.lang.Math.log(200.0f);
|
||||
|
||||
//create zero data for the local time-domain buffer
|
||||
localDataBuff = new float[Nfft];
|
||||
for (int I=0; I < Nfft; I++) {
|
||||
localDataBuff[I] = 0.0f; //initialize
|
||||
}
|
||||
localDataBuffCounter = Nfft-fft_stepsize;
|
||||
|
||||
//initialize FFT
|
||||
localFftData = new FFT(Nfft, fs_Hz);
|
||||
localFftData.window(FFT.HAMMING);
|
||||
|
||||
//create the image
|
||||
int tmax_samps = (int)(tmax_sec * fs_Hz + 0.5f); //the 0.5 is to round, not truncate
|
||||
Nslices = (int)(((float)(tmax_samps-Nfft))/((float)fft_stepsize+0.5)) + 1;
|
||||
img = createImage(Nslices,localFftData.specSize(),RGB);
|
||||
println("Spectrogram: image is " + Nslices + " x " + localFftData.specSize());
|
||||
img.loadPixels(); //this is apparently necessary to allocate the space for the pixels
|
||||
int count=0;
|
||||
for (int J=0; J < localFftData.specSize(); J++) {
|
||||
for (int I=0; I<Nslices;I++) {
|
||||
img.pixels[count]=getColor(java.lang.Math.log(0.0001f));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
img.updatePixels();
|
||||
}
|
||||
|
||||
public void addDataPoint(float dataPoint) {
|
||||
|
||||
//add point
|
||||
localDataBuff[localDataBuffCounter] = dataPoint;
|
||||
//println("Spectrogram.addDataPoint(): counter = " + localDataBuffCounter + ", data = " + localDataBuff[localDataBuffCounter]);
|
||||
|
||||
//increment counter for next time
|
||||
localDataBuffCounter++;
|
||||
|
||||
//are we full?
|
||||
if (localDataBuffCounter >= Nfft) {
|
||||
//println("Spectrogra.addDataPoint(): processing the FFT block");
|
||||
|
||||
//compute the new FFT and update the overall image
|
||||
addDataBlock(localDataBuff);
|
||||
|
||||
//shift the data buffer to make space for the next points
|
||||
//println("addDataPoint: Nfft, fft_stepsize + " + Nfft + " " + fft_stepsize);
|
||||
for (int I=0; I < (Nfft-fft_stepsize); I++) {
|
||||
localDataBuff[I]=localDataBuff[(int)(I+fft_stepsize)];
|
||||
//println("addDataPoint: Shifting " + I + " from " + (I+fft_stepsize) + ", val = " + (localDataBuff[I]));
|
||||
}
|
||||
|
||||
//point the counter to the new location to start accumulating data
|
||||
localDataBuffCounter = Nfft-fft_stepsize;
|
||||
}
|
||||
}
|
||||
|
||||
public void addDataBlock(float[] dataHere) {
|
||||
float foo;
|
||||
|
||||
//do the FFT on the data block
|
||||
float[] localCopy = new float[dataHere.length];
|
||||
localCopy = Arrays.copyOfRange(dataHere,0, dataHere.length);
|
||||
float meanVal = mean(localCopy);
|
||||
for (int I=0; I<localCopy.length;I++) localCopy[I] -= meanVal; //remove mean before doing FFT
|
||||
localFftData.forward(localCopy);
|
||||
|
||||
//convert fft data to uV_per_sqrtHz
|
||||
//final float mean_winpow_sqr = 0.3966; //account for power lost when windowing...mean(hamming(N).^2) = 0.3966
|
||||
final float mean_winpow = 1.0f/sqrt(2.0f); //account for power lost when windowing...mean(hamming(N).^2) = 0.3966
|
||||
final float scale_rawPSDtoPSDPerHz = ((float)localFftData.specSize())*fs_Hz*mean_winpow; //normalize the amplitude by the number of bins to get the correct scaling to uV/sqrt(Hz)???
|
||||
for (int I=0; I < localFftData.specSize(); I++) { //loop over each FFT bin
|
||||
foo = sqrt(pow(localFftData.getBand(I),2)/scale_rawPSDtoPSDPerHz);
|
||||
//if ((I > 5) & (I < 15)) println("Spectrogram: uV/rtHz = " + I + " " + foo);
|
||||
localFftData.setBand(I,foo);
|
||||
}
|
||||
|
||||
//update image...shift all previous pixels to the left
|
||||
int pixel_ind=0;
|
||||
int nPixelsWide = Nslices;
|
||||
for (int J=0; J < localFftData.specSize(); J++) {
|
||||
for (int I=0; I < (nPixelsWide-1); I++) {
|
||||
pixel_ind = J*nPixelsWide + I;
|
||||
img.pixels[pixel_ind] = img.pixels[pixel_ind+1];
|
||||
}
|
||||
}
|
||||
|
||||
//update image...set the color based on the latest data
|
||||
for (int J=0; J < localFftData.specSize(); J++) {
|
||||
pixel_ind = (localFftData.specSize()-J-1)*nPixelsWide + (nPixelsWide-1); //build from bottom-left
|
||||
foo = localFftData.getBand(J); foo=max(foo,0.001f);
|
||||
img.pixels[pixel_ind] = getColor(java.lang.Math.log(foo));
|
||||
}
|
||||
|
||||
//we're finished with the pixels, so update the image
|
||||
//println("addNewData: updating the pixels");
|
||||
img.updatePixels();
|
||||
}
|
||||
|
||||
//model after matlab's "jet" color scheme
|
||||
private color getColor(double given_val) {
|
||||
float r,b,g;
|
||||
float val = (float)((given_val - clim[0])/(clim[1]-clim[0]));
|
||||
val = min(1.0f,max(0.0f,val)); //span [0.0 1.0]
|
||||
|
||||
//compute color
|
||||
float[] bounds = {1.0f/8.0f, 3.0f/8.0f, 5.0f/8.0f, 7.0f/8.0f};
|
||||
if (val < bounds[0]) {
|
||||
r = 0.0f;
|
||||
g = 0.0f;
|
||||
b = map(val,0.0f,bounds[0],0.5f,1.0f);
|
||||
} else if (val < bounds[1]) {
|
||||
r = 0.0f;
|
||||
g = map(val,bounds[0],bounds[1],0.0f,1.0f);
|
||||
b = 1.0f;
|
||||
} else if (val < bounds[2]) {
|
||||
r = map(val,bounds[1],bounds[2],0.0f,1.0f);
|
||||
g = 1.0f;
|
||||
b = map(val,bounds[1],bounds[2],1.0f,0.0f);
|
||||
} else if (val < bounds[3]) {
|
||||
r = 1.0f;
|
||||
g = map(val,bounds[2],bounds[3],1.0f,0.0f);
|
||||
b = 0.0f;
|
||||
} else {
|
||||
r = map(val,bounds[3],1.0f,1.0f,0.5f);
|
||||
g = 0.0f;
|
||||
b = 0.0f;
|
||||
}
|
||||
return color((int)(r*255.f),(int)(g*255.f),(int)(b*255.f));
|
||||
}
|
||||
|
||||
public void draw(int x, int y, int w, int h,float max_freq_Hz) {
|
||||
//float max_freq_Hz = freq_lim_Hz[1];
|
||||
int max_ind = 0;
|
||||
while ((localFftData.indexToFreq(max_ind) <= max_freq_Hz) & (max_ind < localFftData.specSize()-1)) max_ind++;
|
||||
//println("Spectrogram.draw(): max_ind = " + max_ind);
|
||||
//PImage foo = (PImage)(img.get(0,localFftData.specSize()-1-max_ind,Nslices,localFftData.specSize()-1)).clone();
|
||||
//println("spectrogram.draw() max freq = " + localFftData.indexToFreq(max_ind));
|
||||
int img_x = 0;
|
||||
int img_y = localFftData.specSize()-1-max_ind;
|
||||
int img_w = Nslices - img_x + 1;
|
||||
int img_h = localFftData.specSize()-1 - img_y + 1;
|
||||
image(img.get(img_x,img_y,img_w,img_h),x,y,w,h); //plot a subset
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
X,Y
|
||||
-0.30,-0.275
|
||||
-0.225,-0.05
|
||||
-0.225,0.175
|
||||
-0.275,0.335
|
||||
0.0,-0.4
|
||||
0.0,-0.15
|
||||
0.0,0.025
|
||||
0.0,0.25
|
||||
0.3,-0.275
|
||||
0.225,-0.025
|
||||
0.225,0.175
|
||||
0.275,0.335
|
||||
+0.1,0
|
||||
+0.1,0
|
||||
+0.1,0
|
||||
+0.1,0
|
||||
0.0,0.025
|
||||
@@ -0,0 +1,18 @@
|
||||
X,Y
|
||||
-0.125,-0.416
|
||||
0.125,-0.416
|
||||
-0.2,0.0
|
||||
0.2,0.0
|
||||
-0.3425,0.27
|
||||
0.3425,0.27
|
||||
-0.125,0.416
|
||||
0.125,0.416
|
||||
-0.3425,-0.27
|
||||
0.3425,-0.27
|
||||
-0.18,-0.15
|
||||
0.18,-0.15
|
||||
-0.416,0.0
|
||||
0.416,0.0
|
||||
-0.18,0.15
|
||||
0.18,0.15
|
||||
0.0,0.0
|
||||
@@ -0,0 +1,18 @@
|
||||
X,Y
|
||||
-0.125,-0.416
|
||||
0.125,-0.416
|
||||
-0.2,0.0
|
||||
0.2,0.0
|
||||
-0.3425,0.27
|
||||
0.3425,0.27
|
||||
-0.125,0.416
|
||||
0.125,0.416
|
||||
0.0,0.0
|
||||
0.0,0.0
|
||||
0.0,0.0
|
||||
0.0,0.0
|
||||
0.0,0.0
|
||||
0.0,0.0
|
||||
0.0,0.0
|
||||
0.0,0.0
|
||||
0.0,-0.275
|
||||
|
Depois Largura: | Altura: | Tamanho: 4.0 KiB |
|
Depois Largura: | Altura: | Tamanho: 5.4 KiB |
|
Depois Largura: | Altura: | Tamanho: 328 KiB |
|
Depois Largura: | Altura: | Tamanho: 341 KiB |
|
Depois Largura: | Altura: | Tamanho: 509 KiB |
|
Depois Largura: | Altura: | Tamanho: 459 KiB |
|
Depois Largura: | Altura: | Tamanho: 288 KiB |
|
Depois Largura: | Altura: | Tamanho: 555 KiB |
|
Depois Largura: | Altura: | Tamanho: 424 KiB |
|
Depois Largura: | Altura: | Tamanho: 310 KiB |
|
Depois Largura: | Altura: | Tamanho: 539 KiB |
|
Depois Largura: | Altura: | Tamanho: 374 KiB |
|
Depois Largura: | Altura: | Tamanho: 412 KiB |
|
Depois Largura: | Altura: | Tamanho: 475 KiB |
|
Depois Largura: | Altura: | Tamanho: 463 KiB |
|
Depois Largura: | Altura: | Tamanho: 613 KiB |
|
Depois Largura: | Altura: | Tamanho: 218 KiB |
@@ -0,0 +1,180 @@
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// Class: OutputFile_rawtxt
|
||||
// Purpose: handle file creation and writing for the text log file
|
||||
// Created: Chip Audette May 2, 2014
|
||||
//
|
||||
//write data to a text file
|
||||
public class OutputFile_rawtxt {
|
||||
PrintWriter output;
|
||||
String fname;
|
||||
private int rowsWritten;
|
||||
|
||||
OutputFile_rawtxt(float fs_Hz) {
|
||||
|
||||
//build up the file name
|
||||
fname = "SavedData"+System.getProperty("file.separator")+"OpenBCI-RAW-";
|
||||
|
||||
//add year month day to the file name
|
||||
fname = fname + year() + "-";
|
||||
if (month() < 10) fname=fname+"0";
|
||||
fname = fname + month() + "-";
|
||||
if (day() < 10) fname = fname + "0";
|
||||
fname = fname + day();
|
||||
|
||||
//add hour minute sec to the file name
|
||||
fname = fname + "_";
|
||||
if (hour() < 10) fname = fname + "0";
|
||||
fname = fname + hour() + "-";
|
||||
if (minute() < 10) fname = fname + "0";
|
||||
fname = fname + minute() + "-";
|
||||
if (second() < 10) fname = fname + "0";
|
||||
fname = fname + second();
|
||||
|
||||
//add the extension
|
||||
fname = fname + ".txt";
|
||||
|
||||
//open the file
|
||||
output = createWriter(fname);
|
||||
|
||||
//add the header
|
||||
writeHeader(fs_Hz);
|
||||
|
||||
//init the counter
|
||||
rowsWritten = 0;
|
||||
}
|
||||
|
||||
//variation on constructor to have custom name
|
||||
OutputFile_rawtxt(float fs_Hz, String _fileName) {
|
||||
fname = "SavedData"+System.getProperty("file.separator")+"OpenBCI-RAW-";
|
||||
fname += _fileName;
|
||||
fname += ".txt";
|
||||
output = createWriter(fname); //open the file
|
||||
writeHeader(fs_Hz); //add the header
|
||||
rowsWritten = 0; //init the counter
|
||||
}
|
||||
|
||||
public void writeHeader(float fs_Hz) {
|
||||
output.println("%OpenBCI Raw EEG Data");
|
||||
output.println("%");
|
||||
output.println("%Sample Rate = " + fs_Hz + " Hz");
|
||||
output.println("%First Column = SampleIndex");
|
||||
output.println("%Other Columns = EEG data in microvolts followed by Accel Data (in G) interleaved with Aux Data");
|
||||
output.flush();
|
||||
}
|
||||
|
||||
|
||||
// public void writeRawData_dataPacket(DataPacket_ADS1299 data, float scale_to_uV, float scale_for_aux) {
|
||||
// writeRawData_dataPacket(data, scale_to_uV, data.values.length);
|
||||
// }
|
||||
public void writeRawData_dataPacket(DataPacket_ADS1299 data, float scale_to_uV, float scale_for_aux) {
|
||||
if (output != null) {
|
||||
output.print(Integer.toString(data.sampleIndex));
|
||||
writeValues(data.values,scale_to_uV);
|
||||
writeValues(data.auxValues,scale_for_aux);
|
||||
output.println(); rowsWritten++;
|
||||
//output.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeValues(int[] values, float scale_fac) {
|
||||
int nVal = values.length;
|
||||
for (int Ival = 0; Ival < nVal; Ival++) {
|
||||
output.print(", ");
|
||||
output.print(String.format("%.2f", scale_fac * float(values[Ival])));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void closeFile() {
|
||||
output.flush();
|
||||
output.close();
|
||||
}
|
||||
|
||||
public int getRowsWritten() {
|
||||
return rowsWritten;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Class: Table_CSV
|
||||
// Purpose: Extend the Table class to handle data files with comment lines
|
||||
// Created: Chip Audette May 2, 2014
|
||||
//
|
||||
// Usage: Only invoke this object when you want to read in a data
|
||||
// file in CSV format. Read it in at the time of creation via
|
||||
//
|
||||
// String fname = "myfile.csv";
|
||||
// TableCSV myTable = new TableCSV(fname);
|
||||
//
|
||||
//import java.io.*;
|
||||
//import processing.core.PApplet;
|
||||
class Table_CSV extends Table {
|
||||
Table_CSV(String fname) throws IOException {
|
||||
init();
|
||||
readCSV(PApplet.createReader(createInput(fname)));
|
||||
}
|
||||
|
||||
//this function is nearly completely copied from parseBasic from Table.java
|
||||
void readCSV(BufferedReader reader) throws IOException {
|
||||
boolean header=false; //added by Chip, May 2, 2014;
|
||||
boolean tsv = false; //added by Chip, May 2, 2014;
|
||||
|
||||
String line = null;
|
||||
int row = 0;
|
||||
if (rowCount == 0) {
|
||||
setRowCount(10);
|
||||
}
|
||||
//int prev = 0; //-1;
|
||||
try {
|
||||
while ( (line = reader.readLine ()) != null) {
|
||||
//added by Chip, May 2, 2014 to ignore lines that are comments
|
||||
if (line.charAt(0) == '%') {
|
||||
//println("Table_CSV: readCSV: ignoring commented line...");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row == getRowCount()) {
|
||||
setRowCount(row << 1);
|
||||
}
|
||||
if (row == 0 && header) {
|
||||
setColumnTitles(tsv ? PApplet.split(line, '\t') : splitLineCSV(line));
|
||||
header = false;
|
||||
}
|
||||
else {
|
||||
setRow(row, tsv ? PApplet.split(line, '\t') : splitLineCSV(line));
|
||||
row++;
|
||||
}
|
||||
|
||||
// this is problematic unless we're going to calculate rowCount first
|
||||
if (row % 10000 == 0) {
|
||||
/*
|
||||
if (row < rowCount) {
|
||||
int pct = (100 * row) / rowCount;
|
||||
if (pct != prev) { // also prevents "0%" from showing up
|
||||
System.out.println(pct + "%");
|
||||
prev = pct;
|
||||
}
|
||||
}
|
||||
*/
|
||||
try {
|
||||
// Sleep this thread so that the GC can catch up
|
||||
Thread.sleep(10);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Error reading table on line " + row, e);
|
||||
}
|
||||
// shorten or lengthen based on what's left
|
||||
if (row != getRowCount()) {
|
||||
setRowCount(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
|
||||
//////////////////////////////////////
|
||||
//
|
||||
// This file contains classes that are helfpul in some way.
|
||||
//
|
||||
// Created: Chip Audette, Oct 2013 - Dec 2014
|
||||
//
|
||||
/////////////////////////////////////
|
||||
|
||||
class DataPacket_ADS1299 {
|
||||
int sampleIndex;
|
||||
int[] values;
|
||||
int[] auxValues;
|
||||
|
||||
//constructor, give it "nValues", which should match the number of values in the
|
||||
//data payload in each data packet from the Arduino. This is likely to be at least
|
||||
//the number of EEG channels in the OpenBCI system (ie, 8 channels if a single OpenBCI
|
||||
//board) plus whatever auxiliary data the Arduino is sending.
|
||||
DataPacket_ADS1299(int nValues, int nAuxValues) {
|
||||
values = new int[nValues];
|
||||
auxValues = new int[nAuxValues];
|
||||
}
|
||||
int printToConsole() {
|
||||
print("printToConsole: DataPacket = ");
|
||||
print(sampleIndex);
|
||||
for (int i=0; i < values.length; i++) {
|
||||
print(", " + values[i]);
|
||||
}
|
||||
for (int i=0; i < auxValues.length; i++) {
|
||||
print(", " + auxValues[i]);
|
||||
}
|
||||
println();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int copyTo(DataPacket_ADS1299 target) { return copyTo(target, 0, 0); }
|
||||
int copyTo(DataPacket_ADS1299 target, int target_startInd_values, int target_startInd_aux) {
|
||||
target.sampleIndex = sampleIndex;
|
||||
return copyValuesAndAuxTo(target, target_startInd_values, target_startInd_aux);
|
||||
}
|
||||
int copyValuesAndAuxTo(DataPacket_ADS1299 target, int target_startInd_values, int target_startInd_aux) {
|
||||
int nvalues = values.length;
|
||||
for (int i=0; i < nvalues; i++) {
|
||||
target.values[target_startInd_values + i] = values[i];
|
||||
}
|
||||
nvalues = auxValues.length;
|
||||
for (int i=0; i < nvalues; i++) {
|
||||
target.auxValues[target_startInd_aux + i] = auxValues[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class DataStatus {
|
||||
public boolean is_railed;
|
||||
private int threshold_railed;
|
||||
public boolean is_railed_warn;
|
||||
private int threshold_railed_warn;
|
||||
|
||||
DataStatus(int thresh_railed, int thresh_railed_warn) {
|
||||
is_railed = false;
|
||||
threshold_railed = thresh_railed;
|
||||
is_railed_warn = false;
|
||||
threshold_railed_warn = thresh_railed_warn;
|
||||
}
|
||||
public void update(int data_value) {
|
||||
is_railed = false;
|
||||
if (abs(data_value) >= threshold_railed) is_railed = true;
|
||||
is_railed_warn = false;
|
||||
if (abs(data_value) >= threshold_railed_warn) is_railed_warn = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public class FilterConstants {
|
||||
public double[] a;
|
||||
public double[] b;
|
||||
public String name;
|
||||
public String short_name;
|
||||
FilterConstants(double[] b_given, double[] a_given, String name_given, String short_name_given) {
|
||||
b = new double[b_given.length];a = new double[b_given.length];
|
||||
for (int i=0; i<b.length;i++) { b[i] = b_given[i];}
|
||||
for (int i=0; i<a.length;i++) { a[i] = a_given[i];}
|
||||
name = name_given;
|
||||
short_name = short_name_given;
|
||||
}
|
||||
}
|
||||
|
||||
public class DetectionData_FreqDomain {
|
||||
public float inband_uV = 0.0f;
|
||||
public float inband_freq_Hz = 0.0f;
|
||||
public float guard_uV = 0.0f;
|
||||
public float thresh_uV = 0.0f;
|
||||
public boolean isDetected = false;
|
||||
|
||||
DetectionData_FreqDomain() {
|
||||
}
|
||||
};
|
||||
|
||||
public class GraphDataPoint {
|
||||
public double x;
|
||||
public double y;
|
||||
public String x_units;
|
||||
public String y_units;
|
||||
};
|
||||
|
||||
class PlotFontInfo {
|
||||
String fontName = "fonts/Raleway-Regular.otf";
|
||||
int axisLabel_size = 16;
|
||||
int tickLabel_size = 14;
|
||||
int buttonLabel_size = 12;
|
||||
};
|
||||
|
||||
public class TextBox {
|
||||
public int x, y;
|
||||
public color textColor;
|
||||
public color backgroundColor;
|
||||
private PFont font;
|
||||
private int fontSize;
|
||||
public String string;
|
||||
public boolean drawBackground;
|
||||
public int backgroundEdge_pixels;
|
||||
public int alignH,alignV;
|
||||
|
||||
// textBox(String s,int x1,int y1) {
|
||||
// textBox(s,x1,y1,0);
|
||||
// }
|
||||
TextBox(String s, int x1, int y1) {
|
||||
string = s; x = x1; y = y1;
|
||||
backgroundColor = color(255,255,255);
|
||||
textColor = color(0,0,0);
|
||||
fontSize = 12;
|
||||
font = createFont("Arial",fontSize);
|
||||
backgroundEdge_pixels = 1;
|
||||
drawBackground = false;
|
||||
alignH = LEFT;
|
||||
alignV = BOTTOM;
|
||||
}
|
||||
public void setFontSize(int size) {
|
||||
fontSize = size;
|
||||
font = createFont("fonts/Raleway-SemiBold.otf",fontSize);
|
||||
}
|
||||
public void draw() {
|
||||
//define text
|
||||
noStroke();
|
||||
textFont(font);
|
||||
|
||||
//draw the box behind the text
|
||||
if (drawBackground == true) {
|
||||
int w = int(round(textWidth(string)));
|
||||
int xbox = x - backgroundEdge_pixels;
|
||||
switch (alignH) {
|
||||
case LEFT:
|
||||
xbox = x - backgroundEdge_pixels;
|
||||
break;
|
||||
case RIGHT:
|
||||
xbox = x - w - backgroundEdge_pixels;
|
||||
break;
|
||||
case CENTER:
|
||||
xbox = x - int(round(w/2.0)) - backgroundEdge_pixels;
|
||||
break;
|
||||
}
|
||||
w = w + 2*backgroundEdge_pixels;
|
||||
int h = int(textAscent())+2*backgroundEdge_pixels;
|
||||
int ybox = y - int(round(textAscent())) - backgroundEdge_pixels -2;
|
||||
fill(backgroundColor);
|
||||
rect(xbox,ybox,w,h);
|
||||
}
|
||||
//draw the text itself
|
||||
fill(textColor);
|
||||
textAlign(alignH,alignV);
|
||||
text(string,x,y);
|
||||
strokeWeight(1);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
|
||||
//compute the standard deviation
|
||||
float std(float[] data) {
|
||||
//calc mean
|
||||
float ave = mean(data);
|
||||
|
||||
//calc sum of squares relative to mean
|
||||
float val = 0;
|
||||
for (int i=0; i < data.length; i++) {
|
||||
val += pow(data[i]-ave,2);
|
||||
}
|
||||
|
||||
// divide by n to make it the average
|
||||
val /= data.length;
|
||||
|
||||
//take square-root and return the standard
|
||||
return (float)Math.sqrt(val);
|
||||
}
|
||||
|
||||
|
||||
float mean(float[] data) {
|
||||
return mean(data,data.length);
|
||||
}
|
||||
|
||||
int medianDestructive(int[] data) {
|
||||
sort(data);
|
||||
int midPoint = data.length / 2;
|
||||
return data[midPoint];
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
//
|
||||
// Some functions to implement some math and some filtering. These functions
|
||||
// probably already exist in Java somewhere, but it was easier for me to just
|
||||
// recreate them myself as I needed them.
|
||||
//
|
||||
// Created: Chip Audette, Oct 2013
|
||||
//
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
int findMax(float[] data) {
|
||||
float maxVal = data[0];
|
||||
int maxInd = 0;
|
||||
for (int I=1; I<data.length; I++) {
|
||||
if (data[I] > maxVal) {
|
||||
maxVal = data[I];
|
||||
maxInd = I;
|
||||
}
|
||||
}
|
||||
return maxInd;
|
||||
}
|
||||
|
||||
float mean(float[] data, int Nback) {
|
||||
return sum(data,Nback)/Nback;
|
||||
}
|
||||
|
||||
float sum(float[] data) {
|
||||
return sum(data, data.length);
|
||||
}
|
||||
|
||||
float sum(float[] data, int Nback) {
|
||||
float sum = 0;
|
||||
if (Nback > 0) {
|
||||
for (int i=(data.length)-Nback; i < data.length; i++) {
|
||||
sum += data[i];
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
float calcDotProduct(float[] data1, float[] data2) {
|
||||
int len = min(data1.length, data2.length);
|
||||
float val=0.0;
|
||||
for (int I=0;I<len;I++) {
|
||||
val+=data1[I]*data2[I];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
float log10(float val) {
|
||||
return (float)Math.log10(val);
|
||||
}
|
||||
|
||||
float filterWEA_1stOrderIIR(float[] filty, float learn_fac, float filt_state) {
|
||||
float prev = filt_state;
|
||||
for (int i=0; i < filty.length; i++) {
|
||||
filty[i] = prev*(1-learn_fac) + filty[i]*learn_fac;
|
||||
prev = filty[i]; //save for next time
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
void filterIIR(double[] filt_b, double[] filt_a, float[] data) {
|
||||
int Nback = filt_b.length;
|
||||
double[] prev_y = new double[Nback];
|
||||
double[] prev_x = new double[Nback];
|
||||
|
||||
//step through data points
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
//shift the previous outputs
|
||||
for (int j = Nback-1; j > 0; j--) {
|
||||
prev_y[j] = prev_y[j-1];
|
||||
prev_x[j] = prev_x[j-1];
|
||||
}
|
||||
|
||||
//add in the new point
|
||||
prev_x[0] = data[i];
|
||||
|
||||
//compute the new data point
|
||||
double out = 0;
|
||||
for (int j = 0; j < Nback; j++) {
|
||||
out += filt_b[j]*prev_x[j];
|
||||
if (j > 0) {
|
||||
out -= filt_a[j]*prev_y[j];
|
||||
}
|
||||
}
|
||||
|
||||
//save output value
|
||||
prev_y[0] = out;
|
||||
data[i] = (float)out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void removeMean(float[] filty, int Nback) {
|
||||
float meanVal = mean(filty,Nback);
|
||||
for (int i=0; i < filty.length; i++) {
|
||||
filty[i] -= meanVal;
|
||||
}
|
||||
}
|
||||
|
||||
void rereferenceTheMontage(float[][] data) {
|
||||
int n_chan = data.length;
|
||||
int n_points = data[0].length;
|
||||
float sum, mean;
|
||||
|
||||
//loop over all data points
|
||||
for (int Ipoint=0;Ipoint<n_points;Ipoint++) {
|
||||
//compute mean signal right now
|
||||
sum=0.0;
|
||||
for (int Ichan=0;Ichan<n_chan;Ichan++) sum += data[Ichan][Ipoint];
|
||||
mean = sum / n_chan;
|
||||
|
||||
//remove the mean signal from all channels
|
||||
for (int Ichan=0;Ichan<n_chan;Ichan++) data[Ichan][Ipoint] -= mean;
|
||||
}
|
||||
}
|
||||
|
||||
class RunningMean {
|
||||
private float[] values;
|
||||
private int cur_ind = 0;
|
||||
RunningMean(int N) {
|
||||
values = new float[N];
|
||||
cur_ind = 0;
|
||||
}
|
||||
public void addValue(float val) {
|
||||
values[cur_ind] = val;
|
||||
cur_ind = (cur_ind + 1) % values.length;
|
||||
}
|
||||
public float calcMean() {
|
||||
return mean(values);
|
||||
}
|
||||
};
|
||||
|
||||