From bc455a2f7a3585cb0406b7765142aea119b14ba5 Mon Sep 17 00:00:00 2001 From: Graham Jones Date: Sat, 19 Aug 2023 19:54:19 +0100 Subject: [PATCH] V4.2.1a - added standard deviation threshold to ML Algorithm --- app/build.gradle | 5 + app/src/main/AndroidManifest.xml | 13 +- .../uk/org/openseizuredetector/SdAlgNn.java | 123 +++++++++++++++--- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 52 +++++++- .../main/res/xml/seizure_detector_prefs.xml | 25 ++++ 6 files changed, 191 insertions(+), 28 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f5f75c4..092ad46 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,6 +29,9 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } namespace 'uk.org.openseizuredetector' + buildFeatures { + viewBinding true + } } dependencies { @@ -61,6 +64,8 @@ dependencies { implementation 'com.google.firebase:firebase-analytics' implementation 'com.firebaseui:firebase-ui-auth:7.2.0' implementation 'com.google.firebase:firebase-firestore' + implementation 'androidx.navigation:navigation-fragment:2.5.2' + implementation 'androidx.navigation:navigation-ui:2.5.2' testImplementation 'junit:junit:4.13.2' testImplementation "androidx.test:core" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d5c5bf..676845d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="4.2.1a"> @@ -34,8 +34,12 @@ android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" android:theme="@style/AppTheme"> - - + + + android:label="BootBroadcastReceiver"> diff --git a/app/src/main/java/uk/org/openseizuredetector/SdAlgNn.java b/app/src/main/java/uk/org/openseizuredetector/SdAlgNn.java index a6e4d6e..9b2afa4 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdAlgNn.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdAlgNn.java @@ -1,7 +1,10 @@ package uk.org.openseizuredetector; import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.util.Log; +import android.widget.Toast; import com.android.volley.AuthFailureError; import com.android.volley.Request; @@ -31,28 +34,50 @@ public class SdAlgNn { private Context mContext; RequestQueue mQueue; + private double mSdThresh; // Acceleration Standard Deviation Threshold required to activate analysis (%) + private int mModelId; // ID of ML Model to be used (refers to information in MlModels.json for details). + private int mInputFormat; // ID of input format required for model (populated from MlModels.json). + public SdAlgNn(Context context) { Log.d(TAG, "SdAlgNn Constructor"); mContext = context; + + SharedPreferences SP = PreferenceManager + .getDefaultSharedPreferences(mContext); + try { + String threshStr = SP.getString("CnnAlarmThreshold", "5"); + mSdThresh = Double.parseDouble(threshStr); + Log.v(TAG, "SdAlgNn Constructor mSdThresh = " + mSdThresh); + threshStr = SP.getString("CnnModelId", "1"); + mModelId = Integer.parseInt(threshStr); + Log.v(TAG, "SdAlgNn Constructor mModelId = " + mModelId); + } catch (Exception ex) { + Log.v(TAG, "SdAlgNn Constructor - problem parsing preferences. " + ex.toString()); + Toast toast = Toast.makeText(mContext, "Problem Parsing ML Algorithm Preferences", Toast.LENGTH_SHORT); + toast.show(); + } + + mInputFormat = 1; // FIXME - this needs to be determined from the model ID specified by retrieving a configuration file. + Task initializeTask = TfLite.initialize(mContext); initializeTask.addOnSuccessListener(a -> { - MappedByteBuffer modelBuffer; - try { - Log.d(TAG, "onSuccessListener - loading model"); - modelBuffer = FileUtil.loadMappedFile(context, MODEL_PATH); - Log.d(TAG, "onSuccessListener - model loaded"); - } catch (IOException e) { - Log.e(TAG, "Error Loading Model File"); - return; - } - Log.d(TAG, "onSuccessListener - creating interpreter"); - interpreter = InterpreterApi.create(modelBuffer, - new InterpreterApi.Options().setRuntime( - InterpreterApi.Options.TfLiteRuntime.FROM_SYSTEM_ONLY)); - Log.d(TAG, "onSuccessListener - interpreter created ok"); - }) + MappedByteBuffer modelBuffer; + try { + Log.d(TAG, "onSuccessListener - loading model"); + modelBuffer = FileUtil.loadMappedFile(context, MODEL_PATH); + Log.d(TAG, "onSuccessListener - model loaded"); + } catch (IOException e) { + Log.e(TAG, "Error Loading Model File"); + return; + } + Log.d(TAG, "onSuccessListener - creating interpreter"); + interpreter = InterpreterApi.create(modelBuffer, + new InterpreterApi.Options().setRuntime( + InterpreterApi.Options.TfLiteRuntime.FROM_SYSTEM_ONLY)); + Log.d(TAG, "onSuccessListener - interpreter created ok"); + }) .addOnFailureListener(e -> { Log.e(TAG, String.format("Cannot initialize interpreter: %s", e.getMessage())); @@ -62,25 +87,81 @@ public class SdAlgNn { } public void close() { - Log.d(TAG,"close()"); + Log.d(TAG, "close()"); if (interpreter != null) { interpreter.close(); } } - public float getPseizure(SdData sdData) { + /** + * getPseizureFmt1 - calculate probability of sdData representing seizure-like movement + * using a model with input format #1, which is a simple vector of 125 accelerometer vector + * magnitude readings. + * @param sdData - seizure detector data as input to the model + * @return probability of data representing seizure-like movement. + */ + private float getPseizureFmt1(SdData sdData) { int i; float[][][] modelInput = new float[1][125][1]; float[][] modelOutput = new float[1][2]; for (int j = 0; j < 125; j++) { - modelInput[0][j][0] = (float)sdData.rawData[j]; + modelInput[0][j][0] = (float) sdData.rawData[j]; } if (interpreter == null) { - Log.d(TAG,"getPSeizure() - interpreter is null - returning zero seizure probability"); + Log.d(TAG, "getPSeizure() - interpreter is null - returning zero seizure probability"); return (0.0f); } interpreter.run(modelInput, modelOutput); - Log.d(TAG,"run - pSeizure="+modelOutput[0][1]); - return(modelOutput[0][1]); + Log.d(TAG, "run - pSeizure=" + modelOutput[0][1]); + return (modelOutput[0][1]); + } + + public float getPseizure(SdData sdData) { + int i; + + // First check that we have enough movement to analyse by comparing the acceleration standard deviation to a threshold. + double stdDev; + stdDev = calcRawDataStd(sdData); + if (stdDev < mSdThresh) { + Log.d(TAG, "getPseizure - acceleration stdev below movement threshold: std="+stdDev+", thresh="+mSdThresh); + return (0); + } + + float pSeizure; + switch (mModelId) { + case 1: + pSeizure = getPseizureFmt1(sdData); + break; + default: + Log.e(TAG,"getPSeizure - invalid model ID "+mModelId); + pSeizure = 0; + } + + return(pSeizure); + } + + private double calcRawDataStd(SdData sdData) { + /** + * Calculate the standard deviation in % of the rawData array in the SdData instance provided. + * It assumes that rawdata will contain 125 samples. + * Returns the standard deviation in %. + */ + // FIXME - assumes length of rawdata array is 125 data points + int j; + double sum = 0.0; + for (j = 0; j < 125; j++) { // FIXME - assumed length! + sum += sdData.rawData[j]; + } + double mean = sum / 125; + + double standardDeviation = 0.0; + for (j = 0; j < 125; j++) { // FIXME - assumed length! + standardDeviation += Math.pow(sdData.rawData[j] - mean, 2); + } + standardDeviation = Math.sqrt(standardDeviation / 125); // FIXME - assumed length! + + // Convert standard deviation from milli-g to % + standardDeviation = 100. * standardDeviation / mean; + return (standardDeviation); } } diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 715463a..edf0e6d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,5 +2,6 @@ 16dp 16dp + 16dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 645d309..12dd401 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,7 +3,7 @@ OpenSeizureDetector "\n - \mV4.1.11 - Changed target Android Version to 13 (SDK33) (Play Store policy). + \mV4.2.1 - Changed target Android Version to 13 (SDK33) (Play Store policy). \nV4.1.10 - Added warning if heart rate readings freeze and do not change for more than 1 minute. \nV4.1.9 - Fixed problem with average heart rate alarm Fixed issue with phone data source generating continuous alarms for Heart Rate or O2Sat @@ -100,7 +100,7 @@ Alarm Threshold Alarm Threshold (Default = 100) Alarm Ratio Threshold - Alarm Ratio Threshold (Default = 50). Increase to reduce sensitivity. + Alarm Ratio Threshold (Default = 57). Increase to reduce sensitivity. AlarmFreqMax (Hz) Maximum Frequency of ROI (Hz) (Default = 8 Hz) AlarmFreqMin (Hz) @@ -503,4 +503,52 @@ *** ERROR Exporting Data *** Heart Rate measurement Frozen Warning Produce a fault warning if the heart rate measurement freezes and does not change for more than 1 minute. + Machine Learning Seizure Detector Settings + Movement Threshold (% standard deviation) to activate the Machine Learning Algorithm + Movement Threshold (%std) + AI Model ID + ID number of machine learning (AI) model to be used - users should not edit this, but use the model Manager page instead. + MlModelManager + + First Fragment + Second Fragment + Next + Previous + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris + volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus + dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad + litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend + diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a, + ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n + Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus + egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed + neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada + fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae, + molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor + bibendum, vel congue leo egestas.\n\n + Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit + amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel, + molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer + interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at + lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula, + in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque + est.\n\n + Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh. + Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui + non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In + eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc, + quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra + ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a + placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus + convallis.\n\n + Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et + malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa + gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper, + libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper + sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus + libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus + vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim. + diff --git a/app/src/main/res/xml/seizure_detector_prefs.xml b/app/src/main/res/xml/seizure_detector_prefs.xml index d5bfa93..4b5da03 100644 --- a/app/src/main/res/xml/seizure_detector_prefs.xml +++ b/app/src/main/res/xml/seizure_detector_prefs.xml @@ -38,6 +38,11 @@ android:title="@string/fall_detect_active_title" /> + + + + + + +