V4.2.1a - added standard deviation threshold to ML Algorithm

This commit is contained in:
Graham Jones
2023-08-19 19:54:19 +01:00
parent 70f7a46764
commit bc455a2f7a
6 changed files with 191 additions and 28 deletions

View File

@@ -29,6 +29,9 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
namespace 'uk.org.openseizuredetector' namespace 'uk.org.openseizuredetector'
buildFeatures {
viewBinding true
}
} }
dependencies { dependencies {
@@ -61,6 +64,8 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics' implementation 'com.google.firebase:firebase-analytics'
implementation 'com.firebaseui:firebase-ui-auth:7.2.0' implementation 'com.firebaseui:firebase-ui-auth:7.2.0'
implementation 'com.google.firebase:firebase-firestore' 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 'junit:junit:4.13.2'
testImplementation "androidx.test:core" testImplementation "androidx.test:core"

View File

@@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:versionCode="125" android:versionCode="125"
android:versionName="4.1.11a"> android:versionName="4.2.1a">
<!-- android:allowBackup="false" --> <!-- android:allowBackup="false" -->
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@@ -34,8 +34,12 @@
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".AuthenticateActivity"/> <activity
<!-- @android:style/Theme.Holo.Light" --> android:name=".MlModelManager"
android:exported="false"
android:label="@string/title_activity_ml_model_manager"
android:theme="@style/AppTheme" />
<activity android:name=".AuthenticateActivity" /> <!-- @android:style/Theme.Holo.Light" -->
<activity android:name=".BLEScanActivity" /> <activity android:name=".BLEScanActivity" />
<activity android:name=".ExportDataActivity" /> <!-- android:usesCleartextTraffic="true" --> <activity android:name=".ExportDataActivity" /> <!-- android:usesCleartextTraffic="true" -->
<activity <activity
@@ -70,9 +74,8 @@
<receiver <receiver
android:name=".BootBroadcastReceiver" android:name=".BootBroadcastReceiver"
android:label="BootBroadcastReceiver"
android:exported="true" android:exported="true"
> android:label="BootBroadcastReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>

View File

@@ -1,7 +1,10 @@
package uk.org.openseizuredetector; package uk.org.openseizuredetector;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
import com.android.volley.AuthFailureError; import com.android.volley.AuthFailureError;
import com.android.volley.Request; import com.android.volley.Request;
@@ -31,28 +34,50 @@ public class SdAlgNn {
private Context mContext; private Context mContext;
RequestQueue mQueue; 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) { public SdAlgNn(Context context) {
Log.d(TAG, "SdAlgNn Constructor"); Log.d(TAG, "SdAlgNn Constructor");
mContext = context; 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<Void> initializeTask = TfLite.initialize(mContext); Task<Void> initializeTask = TfLite.initialize(mContext);
initializeTask.addOnSuccessListener(a -> { initializeTask.addOnSuccessListener(a -> {
MappedByteBuffer modelBuffer; MappedByteBuffer modelBuffer;
try { try {
Log.d(TAG, "onSuccessListener - loading model"); Log.d(TAG, "onSuccessListener - loading model");
modelBuffer = FileUtil.loadMappedFile(context, MODEL_PATH); modelBuffer = FileUtil.loadMappedFile(context, MODEL_PATH);
Log.d(TAG, "onSuccessListener - model loaded"); Log.d(TAG, "onSuccessListener - model loaded");
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Error Loading Model File"); Log.e(TAG, "Error Loading Model File");
return; return;
} }
Log.d(TAG, "onSuccessListener - creating interpreter"); Log.d(TAG, "onSuccessListener - creating interpreter");
interpreter = InterpreterApi.create(modelBuffer, interpreter = InterpreterApi.create(modelBuffer,
new InterpreterApi.Options().setRuntime( new InterpreterApi.Options().setRuntime(
InterpreterApi.Options.TfLiteRuntime.FROM_SYSTEM_ONLY)); InterpreterApi.Options.TfLiteRuntime.FROM_SYSTEM_ONLY));
Log.d(TAG, "onSuccessListener - interpreter created ok"); Log.d(TAG, "onSuccessListener - interpreter created ok");
}) })
.addOnFailureListener(e -> { .addOnFailureListener(e -> {
Log.e(TAG, String.format("Cannot initialize interpreter: %s", Log.e(TAG, String.format("Cannot initialize interpreter: %s",
e.getMessage())); e.getMessage()));
@@ -62,25 +87,81 @@ public class SdAlgNn {
} }
public void close() { public void close() {
Log.d(TAG,"close()"); Log.d(TAG, "close()");
if (interpreter != null) { if (interpreter != null) {
interpreter.close(); 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; int i;
float[][][] modelInput = new float[1][125][1]; float[][][] modelInput = new float[1][125][1];
float[][] modelOutput = new float[1][2]; float[][] modelOutput = new float[1][2];
for (int j = 0; j < 125; j++) { 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) { 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); return (0.0f);
} }
interpreter.run(modelInput, modelOutput); interpreter.run(modelInput, modelOutput);
Log.d(TAG,"run - pSeizure="+modelOutput[0][1]); Log.d(TAG, "run - pSeizure=" + modelOutput[0][1]);
return(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);
} }
} }

View File

@@ -2,5 +2,6 @@
<!-- Default screen margins, per the Android Design guidelines. --> <!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. --> <!-- Default screen margins, per the Android Design guidelines. -->
</resources> </resources>

View File

@@ -3,7 +3,7 @@
<string name="app_name">OpenSeizureDetector</string> <string name="app_name">OpenSeizureDetector</string>
<string name="changelog"> <string name="changelog">
"\n "\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.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 \nV4.1.9 - Fixed problem with average heart rate alarm
Fixed issue with phone data source generating continuous alarms for Heart Rate or O2Sat Fixed issue with phone data source generating continuous alarms for Heart Rate or O2Sat
@@ -100,7 +100,7 @@
<string name="AlarmThreshTitle">Alarm Threshold</string> <string name="AlarmThreshTitle">Alarm Threshold</string>
<string name="AlarmThreshSummary">Alarm Threshold (Default = 100)</string> <string name="AlarmThreshSummary">Alarm Threshold (Default = 100)</string>
<string name="AlarmRatioThreshTitle">Alarm Ratio Threshold</string> <string name="AlarmRatioThreshTitle">Alarm Ratio Threshold</string>
<string name="AlarmRatioThreshSummary">Alarm Ratio Threshold (Default = 50). Increase to reduce sensitivity.</string> <string name="AlarmRatioThreshSummary">Alarm Ratio Threshold (Default = 57). Increase to reduce sensitivity.</string>
<string name="AlarmFreqMaxTitle">AlarmFreqMax (Hz)</string> <string name="AlarmFreqMaxTitle">AlarmFreqMax (Hz)</string>
<string name="AlarmFreqMaxSummary">Maximum Frequency of ROI (Hz) (Default = 8 Hz)</string> <string name="AlarmFreqMaxSummary">Maximum Frequency of ROI (Hz) (Default = 8 Hz)</string>
<string name="AlarmFreqMinTitle">AlarmFreqMin (Hz)</string> <string name="AlarmFreqMinTitle">AlarmFreqMin (Hz)</string>
@@ -503,4 +503,52 @@
<string name="error_exporting_data">*** ERROR Exporting Data ***</string> <string name="error_exporting_data">*** ERROR Exporting Data ***</string>
<string name="HrFrozenTitle">Heart Rate measurement Frozen Warning</string> <string name="HrFrozenTitle">Heart Rate measurement Frozen Warning</string>
<string name="HrFrozenSummary">Produce a fault warning if the heart rate measurement freezes and does not change for more than 1 minute.</string> <string name="HrFrozenSummary">Produce a fault warning if the heart rate measurement freezes and does not change for more than 1 minute.</string>
<string name="ml_sd_settings_title">Machine Learning Seizure Detector Settings</string>
<string name="ml_sd_threshold_summary">Movement Threshold (% standard deviation) to activate the Machine Learning Algorithm</string>
<string name="ml_sd_threshold_title">Movement Threshold (%std)</string>
<string name="ml_sd_modelid_title">AI Model ID</string>
<string name="ml_sd_modelid_summary">ID number of machine learning (AI) model to be used - users should not edit this, but use the model Manager page instead.</string>
<string name="title_activity_ml_model_manager">MlModelManager</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="lorem_ipsum">
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.
</string>
</resources> </resources>

View File

@@ -38,6 +38,11 @@
android:title="@string/fall_detect_active_title" /> android:title="@string/fall_detect_active_title" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/SeizureDetectorSettingsTitle"> <PreferenceCategory android:title="@string/SeizureDetectorSettingsTitle">
<CheckBoxPreference
android:defaultValue="true"
android:key="OsdAlarmActive"
android:summary="@string/OsdAlarmEnabledSummary"
android:title="@string/OsdAlarmEnabledTitle" />
<EditTextPreference <EditTextPreference
android:defaultValue="5" android:defaultValue="5"
android:key="WarnTime" android:key="WarnTime"
@@ -72,11 +77,31 @@
<EditTextPreference <EditTextPreference
android:defaultValue="5" android:defaultValue="5"
android:enabled="false"
android:key="SamplePeriod" android:key="SamplePeriod"
android:summary="@string/sample_period_summary" android:summary="@string/sample_period_summary"
android:title="@string/sample_period_title" /> android:title="@string/sample_period_title" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/ml_sd_settings_title">
<CheckBoxPreference
android:defaultValue="true"
android:key="CnnAlarmActive"
android:summary="@string/CnnAlarmEnabledSummary"
android:title="@string/CnnAlarmEnabledTitle" />
<EditTextPreference
android:defaultValue="5"
android:key="CnnAlarmThreshold"
android:summary="@string/ml_sd_threshold_summary"
android:title="@string/ml_sd_threshold_title" />
<EditTextPreference
android:defaultValue="1"
android:key="CnnModelId"
android:enabled="false"
android:summary="@string/ml_sd_modelid_summary"
android:title="@string/ml_sd_modelid_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/HeartRateAlarmSettingsTitle"> <PreferenceCategory android:title="@string/HeartRateAlarmSettingsTitle">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"