diff --git a/CHANGELOG.md b/CHANGELOG.md index 86c8307..6135216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ OpenSeizureDetector Android App - Change Log ============================================ + V4.1.0 - Added experimental support for neural network based seizure detector. V4.0.7 - Improvements to Data Sharing data log manager screen - Removed automatic refresh of shared data events list (Issue #62) - Added option to include warnings in shared data events list (Issue #64) diff --git a/app/build.gradle b/app/build.gradle index 05b0690..8241f89 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,13 +32,20 @@ android { dependencies { implementation 'androidx.multidex:multidex:2.0.1' - implementation files('libs/mpandroidchartlibrary-2-0-7.jar') + //implementation files('libs/mpandroidchartlibrary-2-0-7.jar') + //implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' + // V2.1.3 of MPAndroidChart is the most recent version that compiles without modifying + // how we use ValueFormatter + // FIXME: Update mainactivity so we can use the latest version. + implementation 'com.github.PhilJay:MPAndroidChart:v2.1.3' implementation 'com.getpebble:pebblekit:3.1.0@aar' // Unit testing dependencies implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'com.google.android.material:material:1.4.0' implementation 'com.google.firebase:firebase-auth:19.2.0' implementation 'androidx.test:core:1.4.0' + implementation 'com.google.android.gms:play-services-tflite-java:16.0.0' + implementation 'com.google.android.gms:play-services-tflite-support:16.0.0' testImplementation 'junit:junit:4.13.2' // Set this dependency if you want to use Mockito testImplementation 'org.mockito:mockito-core:4.3.1' @@ -59,6 +66,7 @@ dependencies { implementation 'com.google.firebase:firebase-analytics' implementation 'com.firebaseui:firebase-ui-auth:7.2.0' implementation 'com.google.firebase:firebase-firestore' + } repositories { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 05789f6..bf26521 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="104" + android:versionName="4.1.0a"> diff --git a/app/src/main/assets/best_model_v0.02.tflite b/app/src/main/assets/best_model_v0.02.tflite new file mode 100644 index 0000000..bdf2a23 Binary files /dev/null and b/app/src/main/assets/best_model_v0.02.tflite differ diff --git a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java index 1a55010..d6cd7dd 100644 --- a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java @@ -57,6 +57,7 @@ import com.github.mikephil.charting.components.YAxis; import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; + import com.github.mikephil.charting.utils.ValueFormatter; import com.rohitss.uceh.UCEHandler; diff --git a/app/src/main/java/uk/org/openseizuredetector/SdAlgNn.java b/app/src/main/java/uk/org/openseizuredetector/SdAlgNn.java new file mode 100644 index 0000000..7305b8a --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/SdAlgNn.java @@ -0,0 +1,83 @@ +package uk.org.openseizuredetector; + +import android.content.Context; +import android.util.Log; + +import com.android.volley.AuthFailureError; +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.StringRequest; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tflite.java.TfLite; +//import com.google.android.gms.tflite.java.TfLite; + +import org.json.JSONException; +import org.json.JSONObject; +import org.tensorflow.lite.InterpreterApi; +import org.tensorflow.lite.support.common.FileUtil; + +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.util.HashMap; +import java.util.Map; + +public class SdAlgNn { + private final static String TAG = "SdAlgNn"; + private final static String MODEL_PATH = "best_model_v0.02.tflite"; + private String mUrlBase = "https://osdApi.ddns.net"; + private InterpreterApi interpreter; + private Context mContext; + RequestQueue mQueue; + + + public SdAlgNn(Context context) { + Log.d(TAG, "SdAlgNn Constructor"); + mContext = context; + 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"); + }) + .addOnFailureListener(e -> { + Log.e(TAG, String.format("Cannot initialize interpreter: %s", + e.getMessage())); + }); + // FIXME - Add hardware acceleration - see https://www.tensorflow.org/lite/android/play_services + Log.d(TAG, "constructor finished - Note, NOT using hardware acceleration yet!!!!"); + } + + public void close() { + Log.d(TAG,"close()"); + interpreter.close(); + } + + public int run(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]; + } + interpreter.run(modelInput, modelOutput); + Log.d(TAG,"run - pSeizure="+modelOutput[0][1]); + if (modelOutput[0][1]>0.5) + return 1; + else + return 0; + } +} diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java index 2b9a1ae..12c5be1 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSource.java @@ -91,6 +91,7 @@ public abstract class SdDataSource { private short mFallThreshMax; private short mFallWindow; private int mMute; // !=0 means muted by keypress on watch. + private SdAlgNn mSdAlgNn; // Values for SD_MODE private int SIMPLE_SPEC_FMAX = 10; @@ -110,6 +111,8 @@ public abstract class SdDataSource { mUtil = new OsdUtil(mContext, mHandler); mSdDataReceiver = sdDataReceiver; mSdData = new SdData(); + + mSdAlgNn = new SdAlgNn(mContext); } /** @@ -484,6 +487,7 @@ public abstract class SdDataSource { // Check this data to see if it represents an alarm state. alarmCheck(); + nnCheck(); hrCheck(); o2SatCheck(); fallCheck(); @@ -724,6 +728,13 @@ public abstract class SdDataSource { } } + void nnCheck() { + //Check the current set of data using the neural network model to look for alarms. + Log.d(TAG,"nnCheck"); + int nnResult = mSdAlgNn.run(mSdData); + Log.d(TAG,"nnCheck - nnResult="+nnResult); + } + /** * updatePrefs() - update basic settings from the SharedPreferences * - defined in res/xml/SdDataSourceNetworkPassivePrefs.xml diff --git a/build.gradle b/build.gradle index 7b445bc..c577bdd 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ allprojects { } google() //maven { url 'https://jitpack.io' } + maven { url 'https://jitpack.io' } // for mpandroidchart } tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"