Produced some unit tests for the seizure detection algorithm
This commit is contained in:
@@ -20,6 +20,9 @@ android {
|
|||||||
|
|
||||||
testOptions {
|
testOptions {
|
||||||
unitTests.returnDefaultValues = true
|
unitTests.returnDefaultValues = true
|
||||||
|
unitTests {
|
||||||
|
includeAndroidResources = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +45,7 @@ dependencies {
|
|||||||
implementation 'com.github.wendykierp:JTransforms:3.0'
|
implementation 'com.github.wendykierp:JTransforms:3.0'
|
||||||
implementation 'com.google.android.gms:play-services-location:10.0.0'
|
implementation 'com.google.android.gms:play-services-location:10.0.0'
|
||||||
//implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3'
|
//implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3'
|
||||||
|
testImplementation 'org.robolectric:robolectric:4.3'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class SdAnalyser {
|
|||||||
private double mWarnTime;
|
private double mWarnTime;
|
||||||
private double mAlarmTime;
|
private double mAlarmTime;
|
||||||
private double mAlarmThresh;
|
private double mAlarmThresh;
|
||||||
private double mAlarmRatioThresh;
|
double mAlarmRatioThresh;
|
||||||
private double mFreqRes;
|
private double mFreqRes;
|
||||||
private int mAlarmCount;
|
private int mAlarmCount;
|
||||||
private double mFreqCutoff;
|
private double mFreqCutoff;
|
||||||
@@ -79,13 +79,10 @@ public class SdAnalyser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* doAnalysis() - analyse the data if the accelerometer data array mAccData
|
* getSpecPower(rawData): Calculate the average bin power in the spectrum of rawData
|
||||||
* and populate the output data structure mSdData
|
* between 0 and freqCutoff Hz, excluding the DC component
|
||||||
*/
|
*/
|
||||||
void calculateSpectralPowers(double[] rawData) {
|
double getSpecPower(double[] rawData) {
|
||||||
// Set the frequency bounds for the analysis in fft output bin numbers.
|
|
||||||
int nMin = freq2fftBin(mAlarmFreqMin);
|
|
||||||
int nMax = freq2fftBin(mAlarmFreqMax);
|
|
||||||
int nFreqCutoff = freq2fftBin(mFreqCutoff);
|
int nFreqCutoff = freq2fftBin(mFreqCutoff);
|
||||||
|
|
||||||
DoubleFFT_1D fftDo = new DoubleFFT_1D(mNSamp);
|
DoubleFFT_1D fftDo = new DoubleFFT_1D(mNSamp);
|
||||||
@@ -104,7 +101,24 @@ public class SdAnalyser {
|
|||||||
fft[2 * i + 1] = 0.;
|
fft[2 * i + 1] = 0.;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
specPower = specPower / (mNSamp / 2);
|
specPower = specPower / mNSamp / 2;
|
||||||
|
return(specPower);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getRoiPower(rawData): Calculate the average bin power in the spectrum of rawData
|
||||||
|
* between alarmFreqMin and alarmFreqMax Hz.
|
||||||
|
*/
|
||||||
|
double getRoiPower(double[] rawData) {
|
||||||
|
// Set the frequency bounds for the analysis in fft output bin numbers.
|
||||||
|
int nMin = freq2fftBin(mAlarmFreqMin);
|
||||||
|
int nMax = freq2fftBin(mAlarmFreqMax);
|
||||||
|
int nFreqCutoff = freq2fftBin(mFreqCutoff);
|
||||||
|
|
||||||
|
DoubleFFT_1D fftDo = new DoubleFFT_1D(mNSamp);
|
||||||
|
double[] fft = new double[mNSamp * 2];
|
||||||
|
System.arraycopy(rawData, 0, fft, 0, mNSamp);
|
||||||
|
fftDo.realForward(fft);
|
||||||
|
|
||||||
// Calculate the Region of Interest power and power ratio.
|
// Calculate the Region of Interest power and power ratio.
|
||||||
double roiPower = 0;
|
double roiPower = 0;
|
||||||
@@ -112,10 +126,49 @@ public class SdAnalyser {
|
|||||||
roiPower = roiPower + getMagnitude(fft, i);
|
roiPower = roiPower + getMagnitude(fft, i);
|
||||||
}
|
}
|
||||||
roiPower = roiPower / (nMax - nMin);
|
roiPower = roiPower / (nMax - nMin);
|
||||||
double roiRatio = 10 * roiPower / specPower;
|
return(roiPower);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getSpectrumRatio(rawData) - return the ratio of the ROI power to the whole spectrum power.
|
||||||
|
* @param rawData
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
double getSpectrumRatio(double rawData[]) {
|
||||||
|
double specPower;
|
||||||
|
double roiPower;
|
||||||
|
double specRatio;
|
||||||
|
|
||||||
|
specPower = getSpecPower(rawData);
|
||||||
|
roiPower = getRoiPower(rawData);
|
||||||
|
|
||||||
|
if (specPower > mAlarmThresh) {
|
||||||
|
specRatio = 10.0 * roiPower / specPower;
|
||||||
|
} else {
|
||||||
|
specRatio = 0.0;
|
||||||
|
}
|
||||||
|
return(specRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getAlarmState(rawData) - determines the alarm state associated with the snapshot of raw
|
||||||
|
* acceleration data rawData[]
|
||||||
|
* @return the alarm state (0=ok, 1 = alarm)
|
||||||
|
*/
|
||||||
|
int getAlarmState(double rawData[]) {
|
||||||
|
int alarmState;
|
||||||
|
|
||||||
|
double alarmRatio = getSpectrumRatio(rawData);
|
||||||
|
|
||||||
|
if (alarmRatio <= mAlarmRatioThresh) {
|
||||||
|
alarmState = 0;
|
||||||
|
} else {
|
||||||
|
alarmState = 1;
|
||||||
|
}
|
||||||
|
return(alarmState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the magnitude of entry i in the fft array fft
|
* Calculate the magnitude of entry i in the fft array fft
|
||||||
*
|
*
|
||||||
@@ -129,67 +182,34 @@ public class SdAnalyser {
|
|||||||
return mag;
|
return mag;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force the data stored in this datasource to update in line with the JSON string encoded data provided.
|
double[] getAccelDataFromJSON(String jsonStr) {
|
||||||
// Used by webServer to update the GarminDatasource.
|
|
||||||
// Returns a message string that is passed back to the watch.
|
|
||||||
public String updateFromJSON(String jsonStr) {
|
|
||||||
String retVal = "undefined";
|
String retVal = "undefined";
|
||||||
Log.v(TAG,"updateFromJSON - "+jsonStr);
|
double[] rawData = new double[mNSamp];
|
||||||
|
Log.v(TAG,"getAccelDataFromJSON - "+jsonStr);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject mainObject = new JSONObject(jsonStr);
|
JSONObject mainObject = null;
|
||||||
//JSONObject dataObject = mainObject.getJSONObject("dataObj");
|
mainObject = new JSONObject(jsonStr);
|
||||||
JSONObject dataObject = mainObject;
|
JSONObject dataObject = mainObject;
|
||||||
String dataTypeStr = dataObject.getString("dataType");
|
String dataTypeStr = dataObject.getString("dataType");
|
||||||
Log.v(TAG,"updateFromJSON - dataType="+dataTypeStr);
|
Log.v(TAG,"getAccelDataFromJSON - dataType="+dataTypeStr);
|
||||||
if (dataTypeStr.equals("raw")) {
|
if (dataTypeStr.equals("raw")) {
|
||||||
Log.v(TAG,"updateFromJSON - processing raw data");
|
Log.v(TAG, "getAccelDataFromJSON - processing raw data");
|
||||||
try {
|
|
||||||
mSdData.mHR = dataObject.getDouble("HR");
|
|
||||||
} catch (JSONException e) {
|
|
||||||
// if we get 'null' HR (For example if the heart rate is not working)
|
|
||||||
mSdData.mHR = -1;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
mMute = dataObject.getInt("Mute");
|
|
||||||
} catch (JSONException e) {
|
|
||||||
// if we get 'null' HR (For example if the heart rate is not working)
|
|
||||||
mMute = 0;
|
|
||||||
}
|
|
||||||
JSONArray accelVals = dataObject.getJSONArray("data");
|
JSONArray accelVals = dataObject.getJSONArray("data");
|
||||||
Log.v(TAG, "Received " + accelVals.length() + " acceleration values");
|
Log.v(TAG, "Received " + accelVals.length() + " acceleration values");
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < accelVals.length(); i++) {
|
for (i = 0; i < accelVals.length(); i++) {
|
||||||
mSdData.rawData[i] = accelVals.getInt(i);
|
rawData[i] = accelVals.getInt(i);
|
||||||
}
|
}
|
||||||
mSdData.mNsamp = accelVals.length();
|
|
||||||
//mNSamp = accelVals.length();
|
|
||||||
mWatchAppRunningCheck = true;
|
|
||||||
doAnalysis();
|
|
||||||
if (mSdData.haveSettings == false) {
|
|
||||||
retVal = "sendSettings";
|
|
||||||
} else {
|
} else {
|
||||||
retVal = "OK";
|
Log.e(TAG,"getAccelDataFromJSON - unrecognised dataType "+dataTypeStr);
|
||||||
}
|
|
||||||
} else if (dataTypeStr.equals("settings")){
|
|
||||||
Log.v(TAG,"updateFromJSON - processing settings");
|
|
||||||
mSamplePeriod = (short)dataObject.getInt("analysisPeriod");
|
|
||||||
mSampleFreq = (short)dataObject.getInt("sampleFreq");
|
|
||||||
mSdData.batteryPc = (short)dataObject.getInt("battery");
|
|
||||||
Log.v(TAG,"updateFromJSON - mSamplePeriod="+mSamplePeriod+" mSampleFreq="+mSampleFreq);
|
|
||||||
mSdData.haveSettings = true;
|
|
||||||
mSdData.mSampleFreq = mSampleFreq;
|
|
||||||
mWatchAppRunningCheck = true;
|
|
||||||
retVal = "OK";
|
|
||||||
} else {
|
|
||||||
Log.e(TAG,"updateFromJSON - unrecognised dataType "+dataTypeStr);
|
|
||||||
retVal = "ERROR";
|
retVal = "ERROR";
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG,"updateFromJSON - Error Parsing JSON String - "+e.toString());
|
Log.e(TAG,"getAccelDataFromJSON - Error Parsing JSON String - "+e.toString());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
retVal = "ERROR";
|
retVal = "ERROR";
|
||||||
}
|
}
|
||||||
return(retVal);
|
return(rawData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ package uk.org.openseizuredetector;
|
|||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class SdAnalyserTest {
|
public class SdAnalyserTest {
|
||||||
public SdAnalyser sda;
|
public SdAnalyser sda;
|
||||||
|
|
||||||
public String alarmJSON = "{ dataType: 'raw', " +
|
public String alarmJSON = "{ 'dataType': 'raw', " +
|
||||||
"data: [" +
|
"'data': [" +
|
||||||
"1644, 1316, 1144, 1332, 1716, 1716, 1392, 1148, 1276, 1660, " +
|
"1644, 1316, 1144, 1332, 1716, 1716, 1392, 1148, 1276, 1660, " +
|
||||||
"1716, 1496, 1196, 1232, 1572, 1684, 1552, 1236, 1228, 1528, " +
|
"1716, 1496, 1196, 1232, 1572, 1684, 1552, 1236, 1228, 1528, " +
|
||||||
"1648, 1572, 1268, 1208, 1492, 1680, 1596, 1272, 1192, 1424, " +
|
"1648, 1572, 1268, 1208, 1492, 1680, 1596, 1272, 1192, 1424, " +
|
||||||
@@ -24,13 +27,13 @@ public class SdAnalyserTest {
|
|||||||
"1188, 1360, 1680, 1724, 1424, 1192, 1224, 1556, 1744, 1588, " +
|
"1188, 1360, 1680, 1724, 1424, 1192, 1224, 1556, 1744, 1588, " +
|
||||||
"1260, 1220, 1472, 1692, 1608, 1328, 1192, 1412, 1668, 1656, " +
|
"1260, 1220, 1472, 1692, 1608, 1328, 1192, 1412, 1668, 1656, " +
|
||||||
"1356, 1216, 1304, 1636, 1712], " +
|
"1356, 1216, 1304, 1636, 1712], " +
|
||||||
"HR:54, " +
|
"'HR':54, " +
|
||||||
"Mute:0 " +
|
"'Mute':0 " +
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
private String okJSON = "{ " +
|
private String okJSON = "{ " +
|
||||||
"dataType: 'raw', " +
|
"\"dataType\": \"raw\", " +
|
||||||
"data: [" +
|
"\"data\": [" +
|
||||||
"1140, 1188, 1144, 1172, 1228, 1212, 1236, 1236, 1256, 1320, " +
|
"1140, 1188, 1144, 1172, 1228, 1212, 1236, 1236, 1256, 1320, " +
|
||||||
"1316, 1280, 1240, 1280, 1324, 1284, 1292, 1268, 1284, 1276, " +
|
"1316, 1280, 1240, 1280, 1324, 1284, 1292, 1268, 1284, 1276, " +
|
||||||
"1296, 1324, 1308, 1288, 1304, 1276, 1304, 1304, 1276, 1296, " +
|
"1296, 1324, 1308, 1288, 1304, 1276, 1304, 1304, 1276, 1296, " +
|
||||||
@@ -44,8 +47,8 @@ public class SdAnalyserTest {
|
|||||||
"1268, 1292, 1292, 1304, 1288, 1284, 1280, 1276, 1288, 1280, " +
|
"1268, 1292, 1292, 1304, 1288, 1284, 1280, 1276, 1288, 1280, " +
|
||||||
"1300, 1288, 1320, 1268, 1288, 1280, 1304, 1280, 1280, 1288, " +
|
"1300, 1288, 1320, 1268, 1288, 1280, 1304, 1280, 1280, 1288, " +
|
||||||
"1292, 1308, 1268, 1292, 1280], " +
|
"1292, 1308, 1268, 1292, 1280], " +
|
||||||
"HR:57, " +
|
"\"HR\":57, " +
|
||||||
"Mute:0 " +
|
"\"Mute\":0 " +
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -65,7 +68,7 @@ public class SdAnalyserTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void freq2fftBin() {
|
public void testFreq2fftBin() {
|
||||||
int n;
|
int n;
|
||||||
n = sda.freq2fftBin(0.0);
|
n = sda.freq2fftBin(0.0);
|
||||||
assertEquals(0,n);
|
assertEquals(0,n);
|
||||||
@@ -74,7 +77,7 @@ public class SdAnalyserTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getMagnitude() {
|
public void testGetMagnitude() {
|
||||||
double[] fft = {1, 1,
|
double[] fft = {1, 1,
|
||||||
2, 1,
|
2, 1,
|
||||||
2, 2};
|
2, 2};
|
||||||
@@ -87,4 +90,54 @@ public class SdAnalyserTest {
|
|||||||
assertEquals(8.0, m, m * 1e-4);
|
assertEquals(8.0, m, m * 1e-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAccelDataFromJson() {
|
||||||
|
double[] retVal;
|
||||||
|
|
||||||
|
retVal = sda.getAccelDataFromJSON(okJSON);
|
||||||
|
assertNotNull(retVal);
|
||||||
|
assertEquals(125,retVal.length);
|
||||||
|
assertEquals(1140,retVal[0],0.001);
|
||||||
|
assertEquals(1280,retVal[124],0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetSpectrumRatio() {
|
||||||
|
double[] okRawVals;
|
||||||
|
double[] alarmRawVals;
|
||||||
|
okRawVals = sda.getAccelDataFromJSON(okJSON);
|
||||||
|
alarmRawVals = sda.getAccelDataFromJSON(alarmJSON);
|
||||||
|
|
||||||
|
double okRatio;
|
||||||
|
double alarmRatio;
|
||||||
|
|
||||||
|
okRatio = sda.getSpectrumRatio(okRawVals);
|
||||||
|
alarmRatio = sda.getSpectrumRatio(alarmRawVals);
|
||||||
|
|
||||||
|
assertTrue("Check Spectrum Ratio for OK data "+okRatio+" is <="+sda.mAlarmRatioThresh,
|
||||||
|
okRatio <= sda.mAlarmRatioThresh);
|
||||||
|
assertTrue("Check Spectrum Ratio for ALARM data "+alarmRatio+" is >"+sda.mAlarmRatioThresh,
|
||||||
|
alarmRatio > sda.mAlarmRatioThresh);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testgetAlarmState() {
|
||||||
|
double[] okRawVals;
|
||||||
|
double[] alarmRawVals;
|
||||||
|
okRawVals = sda.getAccelDataFromJSON(okJSON);
|
||||||
|
alarmRawVals = sda.getAccelDataFromJSON(alarmJSON);
|
||||||
|
|
||||||
|
int okAlarmState;
|
||||||
|
int alarmAlarmState;
|
||||||
|
|
||||||
|
okAlarmState = sda.getAlarmState(okRawVals);
|
||||||
|
alarmAlarmState = sda.getAlarmState(alarmRawVals);
|
||||||
|
|
||||||
|
assertEquals("check OK start detected from raw data",0, okAlarmState);
|
||||||
|
assertEquals("check alarm state detected from raw data",1, alarmAlarmState);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user