diff --git a/app/build.gradle b/app/build.gradle index 43b901f..46e8d09 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,6 +20,9 @@ android { testOptions { unitTests.returnDefaultValues = true + unitTests { + includeAndroidResources = true + } } } @@ -42,6 +45,7 @@ dependencies { implementation 'com.github.wendykierp:JTransforms:3.0' implementation 'com.google.android.gms:play-services-location:10.0.0' //implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3' + testImplementation 'org.robolectric:robolectric:4.3' } diff --git a/app/src/main/java/uk/org/openseizuredetector/SdAnalyser.java b/app/src/main/java/uk/org/openseizuredetector/SdAnalyser.java index 2127d9b..5eaacb5 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdAnalyser.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdAnalyser.java @@ -43,7 +43,7 @@ public class SdAnalyser { private double mWarnTime; private double mAlarmTime; private double mAlarmThresh; - private double mAlarmRatioThresh; + double mAlarmRatioThresh; private double mFreqRes; private int mAlarmCount; private double mFreqCutoff; @@ -79,13 +79,10 @@ public class SdAnalyser { } /** - * doAnalysis() - analyse the data if the accelerometer data array mAccData - * and populate the output data structure mSdData + * getSpecPower(rawData): Calculate the average bin power in the spectrum of rawData + * between 0 and freqCutoff Hz, excluding the DC component */ - void calculateSpectralPowers(double[] rawData) { - // Set the frequency bounds for the analysis in fft output bin numbers. - int nMin = freq2fftBin(mAlarmFreqMin); - int nMax = freq2fftBin(mAlarmFreqMax); + double getSpecPower(double[] rawData) { int nFreqCutoff = freq2fftBin(mFreqCutoff); DoubleFFT_1D fftDo = new DoubleFFT_1D(mNSamp); @@ -104,7 +101,24 @@ public class SdAnalyser { 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. double roiPower = 0; @@ -112,10 +126,49 @@ public class SdAnalyser { roiPower = roiPower + getMagnitude(fft, i); } 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 * @@ -129,67 +182,34 @@ public class SdAnalyser { return mag; } - // Force the data stored in this datasource to update in line with the JSON string encoded data provided. - // Used by webServer to update the GarminDatasource. - // Returns a message string that is passed back to the watch. - public String updateFromJSON(String jsonStr) { + double[] getAccelDataFromJSON(String jsonStr) { String retVal = "undefined"; - Log.v(TAG,"updateFromJSON - "+jsonStr); + double[] rawData = new double[mNSamp]; + Log.v(TAG,"getAccelDataFromJSON - "+jsonStr); try { - JSONObject mainObject = new JSONObject(jsonStr); - //JSONObject dataObject = mainObject.getJSONObject("dataObj"); + JSONObject mainObject = null; + mainObject = new JSONObject(jsonStr); JSONObject dataObject = mainObject; String dataTypeStr = dataObject.getString("dataType"); - Log.v(TAG,"updateFromJSON - dataType="+dataTypeStr); + Log.v(TAG,"getAccelDataFromJSON - dataType="+dataTypeStr); if (dataTypeStr.equals("raw")) { - Log.v(TAG,"updateFromJSON - 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; - } + Log.v(TAG, "getAccelDataFromJSON - processing raw data"); JSONArray accelVals = dataObject.getJSONArray("data"); Log.v(TAG, "Received " + accelVals.length() + " acceleration values"); int 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 { - retVal = "OK"; - } - } 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); + Log.e(TAG,"getAccelDataFromJSON - unrecognised dataType "+dataTypeStr); retVal = "ERROR"; } } 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(); retVal = "ERROR"; } - return(retVal); + return(rawData); } } diff --git a/app/src/test/java/uk/org/openseizuredetector/SdAnalyserTest.java b/app/src/test/java/uk/org/openseizuredetector/SdAnalyserTest.java index 35b483e..a6cfb8c 100644 --- a/app/src/test/java/uk/org/openseizuredetector/SdAnalyserTest.java +++ b/app/src/test/java/uk/org/openseizuredetector/SdAnalyserTest.java @@ -3,14 +3,17 @@ package uk.org.openseizuredetector; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import static org.junit.Assert.*; +@RunWith(RobolectricTestRunner.class) public class SdAnalyserTest { public SdAnalyser sda; - public String alarmJSON = "{ dataType: 'raw', " + - "data: [" + + public String alarmJSON = "{ 'dataType': 'raw', " + + "'data': [" + "1644, 1316, 1144, 1332, 1716, 1716, 1392, 1148, 1276, 1660, " + "1716, 1496, 1196, 1232, 1572, 1684, 1552, 1236, 1228, 1528, " + "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, " + "1260, 1220, 1472, 1692, 1608, 1328, 1192, 1412, 1668, 1656, " + "1356, 1216, 1304, 1636, 1712], " + - "HR:54, " + - "Mute:0 " + + "'HR':54, " + + "'Mute':0 " + "}"; private String okJSON = "{ " + - "dataType: 'raw', " + - "data: [" + + "\"dataType\": \"raw\", " + + "\"data\": [" + "1140, 1188, 1144, 1172, 1228, 1212, 1236, 1236, 1256, 1320, " + "1316, 1280, 1240, 1280, 1324, 1284, 1292, 1268, 1284, 1276, " + "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, " + "1300, 1288, 1320, 1268, 1288, 1280, 1304, 1280, 1280, 1288, " + "1292, 1308, 1268, 1292, 1280], " + - "HR:57, " + - "Mute:0 " + + "\"HR\":57, " + + "\"Mute\":0 " + "}"; @Before @@ -65,7 +68,7 @@ public class SdAnalyserTest { } @Test - public void freq2fftBin() { + public void testFreq2fftBin() { int n; n = sda.freq2fftBin(0.0); assertEquals(0,n); @@ -74,7 +77,7 @@ public class SdAnalyserTest { } @Test - public void getMagnitude() { + public void testGetMagnitude() { double[] fft = {1, 1, 2, 1, 2, 2}; @@ -87,4 +90,54 @@ public class SdAnalyserTest { 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); + } + } \ No newline at end of file