diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/viewer/.gitignore b/viewer/.gitignore new file mode 100644 index 0000000..c0e8700 --- /dev/null +++ b/viewer/.gitignore @@ -0,0 +1,82 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ \ No newline at end of file diff --git a/viewer/.idea/encodings.xml b/viewer/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/viewer/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/viewer/.idea/misc.xml b/viewer/.idea/misc.xml new file mode 100644 index 0000000..7bfef59 --- /dev/null +++ b/viewer/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/viewer/.idea/runConfigurations.xml b/viewer/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/viewer/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/viewer/app/.gitignore b/viewer/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/viewer/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/viewer/app/build.gradle b/viewer/app/build.gradle new file mode 100644 index 0000000..abd74e7 --- /dev/null +++ b/viewer/app/build.gradle @@ -0,0 +1,41 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + defaultConfig { + applicationId "io.interactionlab.capimgdemo" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + sourceSets { + main { + jniLibs.srcDirs = ['../../Dependencies/jniLibs'] + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:support-v4:28.0.0' + testImplementation 'junit:junit:4.12' + implementation 'org.tensorflow:tensorflow-android:1.5.0' + implementation files('libs/libftsp.jar') + //implementation project(':openCVLibrary310') + // https://mvnrepository.com/artifact/org.opencv/openCVLibrary + implementation group: 'org.opencv', name: 'openCVLibrary', version: '3.4.0' + +} diff --git a/viewer/app/libs/libftsp.jar b/viewer/app/libs/libftsp.jar new file mode 100644 index 0000000..2246401 Binary files /dev/null and b/viewer/app/libs/libftsp.jar differ diff --git a/viewer/app/proguard-rules.pro b/viewer/app/proguard-rules.pro new file mode 100644 index 0000000..2f55a81 --- /dev/null +++ b/viewer/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\AndroidSDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/viewer/app/src/androidTest/java/io/interactionlab/capimgdemo/ExampleInstrumentedTest.java b/viewer/app/src/androidTest/java/io/interactionlab/capimgdemo/ExampleInstrumentedTest.java new file mode 100644 index 0000000..590e9f6 --- /dev/null +++ b/viewer/app/src/androidTest/java/io/interactionlab/capimgdemo/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package io.interactionlab.capimgdemo; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("io.interactionlab.capimgdemo", appContext.getPackageName()); + } +} diff --git a/viewer/app/src/main/.gitignore b/viewer/app/src/main/.gitignore new file mode 100644 index 0000000..8f576dd --- /dev/null +++ b/viewer/app/src/main/.gitignore @@ -0,0 +1 @@ +/jniLibs \ No newline at end of file diff --git a/viewer/app/src/main/AndroidManifest.xml b/viewer/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..49255e5 --- /dev/null +++ b/viewer/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/viewer/app/src/main/assets/CNN.pb b/viewer/app/src/main/assets/CNN.pb new file mode 100644 index 0000000..6f968d8 Binary files /dev/null and b/viewer/app/src/main/assets/CNN.pb differ diff --git a/viewer/app/src/main/assets/LSTM.pb b/viewer/app/src/main/assets/LSTM.pb new file mode 100644 index 0000000..76d6eab Binary files /dev/null and b/viewer/app/src/main/assets/LSTM.pb differ diff --git a/viewer/app/src/main/ic_launcher-web.png b/viewer/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..ab96d68 Binary files /dev/null and b/viewer/app/src/main/ic_launcher-web.png differ diff --git a/viewer/app/src/main/java/io/interactionlab/capimgdemo/BlobClassifier.java b/viewer/app/src/main/java/io/interactionlab/capimgdemo/BlobClassifier.java new file mode 100644 index 0000000..d5aff4f --- /dev/null +++ b/viewer/app/src/main/java/io/interactionlab/capimgdemo/BlobClassifier.java @@ -0,0 +1,221 @@ +package io.interactionlab.capimgdemo; + +import android.content.Context; + +import org.hcilab.libftsp.capacitivematrix.blobdetection.BlobBoundingBox; +import org.hcilab.libftsp.capacitivematrix.capmatrix.CapacitiveImageTS; +import org.opencv.core.Core; +import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; +import org.opencv.core.CvType; +import org.opencv.core.Point; +import org.opencv.imgproc.Imgproc; +import org.tensorflow.contrib.android.TensorFlowInferenceInterface; + +import io.interactionlab.capimgdemo.demo.ModelDescription; + +import java.util.ArrayList; +import java.util.List; + +import static org.opencv.imgproc.Imgproc.THRESH_BINARY; +import static org.opencv.imgproc.Imgproc.contourArea; +import static org.opencv.imgproc.Imgproc.threshold; + +/** + * Created by Huy on 05/09/2017. + */ + +class BlobClassifier { + private static TensorFlowInferenceInterface inferenceInterface; + private final Context context; + private ModelDescription modelDescription; + + BlobClassifier(Context context) { + // Loading model from assets folder. + this.context = context; + } + + public void setModel(ModelDescription modelDescription) { + this.modelDescription = modelDescription; + inferenceInterface = new TensorFlowInferenceInterface(context.getAssets(), modelDescription.modelPath); + } + + public ClassificationResult classify(float[] pixels) { + // Node Names + String inputName = modelDescription.inputNode; + String outputName = modelDescription.outputNode; + + // Define output nodes + String[] outputNodes = new String[]{outputName}; + float[] outputs = new float[modelDescription.labels.length]; + + // Feed image into the model and fetch the results. + inferenceInterface.feed(inputName, pixels, modelDescription.inputDimensions); + inferenceInterface.run(outputNodes, true); + inferenceInterface.fetch(outputName, outputs); + + ClassificationResult cr = new ClassificationResult(); + + // Convert one-hot encoded result to an int (= detected class) + float maxConf = Float.MIN_VALUE; + int idx = -1; + for (int i = 0; i < outputs.length; i++) { + if (outputs[i] > maxConf) { + maxConf = outputs[i]; + idx = i; + } + } + + float norm = 0.0f; + for (float output : outputs) { + norm += output; + } + maxConf = maxConf / norm; + + cr.index = idx; + cr.label = modelDescription.labels[idx]; + cr.confidence = maxConf; + cr.color = modelDescription.labelColor[idx]; + + return cr; + } + + public float[] imagesToPixels(List images) { + int w = images.get(0)[0].length; + int h = images.get(0).length; + float[] pixels = new float[images.size() * w * h]; + for (int i = 0; i < pixels.length; i++) { + pixels[i] = images.get(i/(w*h))[(i/w)%h][i%w]; + } + return pixels; + } + + public float[] getBlobContentIn27x15(int[][] matrix, BlobBoundingBox bbb) { + // first extract the blob + int y1 = Math.max(bbb.y1 - 1, 0); + int y2 = Math.min(bbb.y2 + 1, 29); + int x1 = Math.max(bbb.x1 - 1, 0); + int x2 = Math.min(bbb.x2 + 1, 17); + + int[][] blob = new int[y2-y1][x2-x1]; + for (int y = 0; y < blob.length; y++) { + for (int x = 0; x < blob[0].length; x++) { + blob[y][x] = matrix[y1+y][x1+x]; + } + } + + // put it into new 27x15 image + float[][] image = new float[27][15]; + for(int y = 0; y < blob.length; y++) { + for(int x = 0; x < blob[0].length; x++) { + image[y][x] = blob[y][x]; + } + } + + float[] result = new float[27*15]; + for(int y = 0; y < 27; y++) { + for(int x = 0; x < 15; x++) { + result[x+15*y] = image[y][x]; + } + } + return result; + } + + public int[][] preprocess(CapacitiveImageTS capImg) { + int[][] matrix = capImg.getMatrix(); + Mat image = int27x15ToPaddedMat(matrix); + return matToInt2D(image); + } + + public List getBlobBoundaries(int[][] matrix) { + Mat image = int29x17ToMat(matrix); + Mat inv_image = new Mat(); + Core.bitwise_not(image, inv_image); + threshold(inv_image, image, 205, 255, THRESH_BINARY); + + ArrayList blobs = new ArrayList<>(); + List contours = new ArrayList<>(); + Imgproc.findContours(image, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); + + if (contours.size() == 0) { + return blobs; + } + + // get max contour + MatOfPoint max_contour = new MatOfPoint(new Point(5, 5)); + for (int i = 0; i < contours.size(); i++) { + if (contourArea(contours.get(i)) > 5 && contourArea(contours.get(i)) < 255) { + if (contourArea(contours.get(i)) > contourArea(max_contour)) { + max_contour = contours.get(i); + } + } + } + + if (contourArea(max_contour)==contourArea(new MatOfPoint(new Point(5, 5)))) { + return blobs; + } + + // get xmin, xmax, ymin, ymax + int x_min = 2147483647; + int x_max = -2147483648; + int y_min = 2147483647; + int y_max = -2147483648; + for (Point p : max_contour.toList()) { + if (p.x < x_min) { + x_min = (int) p.x; + } + + if (p.y < y_min) { + y_min = (int) p.y; + } + + if (p.x > x_max) { + x_max = (int) p.x; + } + + if (p.y > y_max) { + y_max = (int) p.y; + } + } + BlobBoundingBox bbb = new BlobBoundingBox(x_min, y_min, x_max, y_max); + blobs.add(bbb); + return blobs; + } + + private Mat int27x15ToPaddedMat(int[][] matrix) { + Mat image = new Mat(29, 17, CvType.CV_8UC1); + for (int x = 0; x < 29; x++) { + for (int y = 0; y < 17; y++) { + image.put(x, y, 1); + } + } + // fill in matrix + for (int x = 0; x < 27; x++) { + for (int y = 0; y < 15; y++) { + image.put(1+x, 1+y, (double) matrix[x][y]); + } + } + return image; + } + + private Mat int29x17ToMat(int[][] matrix) { + Mat image = new Mat(29, 17, CvType.CV_8UC1); + // fill in matrix + for (int x = 0; x < 29; x++) { + for (int y = 0; y < 17; y++) { + image.put(x, y, (double) matrix[x][y]); + } + } + return image; + } + + private int[][] matToInt2D(Mat mat) { + int[][] matrix = new int[mat.rows()][mat.cols()]; + for (int x = 0; x < mat.rows(); x++) { + for (int y = 0; y < mat.cols(); y++) { + matrix[x][y] = (int) mat.get(x, y)[0]; + } + } + return matrix; + } +} diff --git a/viewer/app/src/main/java/io/interactionlab/capimgdemo/ClassificationResult.java b/viewer/app/src/main/java/io/interactionlab/capimgdemo/ClassificationResult.java new file mode 100644 index 0000000..4ba52ce --- /dev/null +++ b/viewer/app/src/main/java/io/interactionlab/capimgdemo/ClassificationResult.java @@ -0,0 +1,12 @@ +package io.interactionlab.capimgdemo; + +/** + * Created by Huy on 29/06/2018. + */ + +public class ClassificationResult { + int index; + String label; + double confidence; + int color; +} diff --git a/viewer/app/src/main/java/io/interactionlab/capimgdemo/DrawView.java b/viewer/app/src/main/java/io/interactionlab/capimgdemo/DrawView.java new file mode 100644 index 0000000..447d95c --- /dev/null +++ b/viewer/app/src/main/java/io/interactionlab/capimgdemo/DrawView.java @@ -0,0 +1,111 @@ +package io.interactionlab.capimgdemo; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.view.View; + +import org.hcilab.libftsp.capacitivematrix.blobdetection.BlobBoundingBox; +import org.hcilab.libftsp.capacitivematrix.capmatrix.CapacitiveImageTS; + +import java.util.List; + +/** + * Created by Huy on 12/12/2017. + */ + +public class DrawView extends View { + private final Paint paint = new Paint(); + + private CapacitiveImageTS capacitiveImage; + private List bbbl; + private List labels; + private List colors; + + public DrawView(Context context) { + super(context); + } + + + public void updateView(CapacitiveImageTS capacitiveImage) { + this.capacitiveImage = capacitiveImage; + invalidate(); + } + + public void updateView(CapacitiveImageTS capacitiveImage, List bbbl, List labels, List colors) { + this.capacitiveImage = capacitiveImage; + this.bbbl = bbbl; + this.labels = labels; + this.colors = colors; + invalidate(); + } + + private void onDrawCNN(Canvas canvas, int boxWidth, int boxHeight) { + if (bbbl != null) { + // Draw Labels + for (int i = 0; i < bbbl.size(); i++) { + BlobBoundingBox bbb = bbbl.get(i); + paint.setColor(this.colors.get(i)); + paint.setStyle(Paint.Style.STROKE); + Rect r = new Rect(bbb.x1 * boxWidth, bbb.y1 * boxHeight, bbb.x2 * boxWidth, bbb.y2 * boxHeight); + canvas.drawRect(r, paint); + + paint.setTextSize(45); + canvas.drawText(labels.get(i), bbb.x1 * boxWidth - (int) (1.0 * boxWidth), bbb.y1 * boxHeight - (int) (1.0 * boxHeight), paint); + } + } + } + + private void onDrawLSTMCNN(Canvas canvas, int boxWidth, int boxHeight) { + if (!labels.isEmpty()) { + paint.setColor(Color.GREEN); + paint.setStyle(Paint.Style.STROKE); + Rect r = new Rect(boxWidth, 5 * boxHeight, 14 * boxWidth, 10 * boxHeight); + canvas.drawRect(r, paint); + + paint.setTextSize(45); + canvas.drawText(labels.get(0), 2 * boxWidth, 7 * boxHeight, paint); + } + } + + + @Override + public void onDraw(Canvas canvas) { + int boxWidth = 1080 / 15; + int boxHeight = 1920 / 27; + + if (capacitiveImage == null) { + return; + } + + paint.setStyle(Paint.Style.FILL); + int[][] matrix = capacitiveImage.getMatrix(); + + // Draw capacitive matrix + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[i].length; j++) { + int val = matrix[i][j]; + + if (val < 0) + val = 0; + + if (val > 255) + val = 255; + + // Draw rectangle + paint.setColor(new Color().rgb(val, val, val)); + paint.setStyle(Paint.Style.FILL); + Rect r = new Rect(j * boxWidth, (i) * boxHeight, (j + 1) * boxWidth, (i + 1) * boxHeight); + canvas.drawRect(r, paint); + + // Write number + paint.setTextSize(15); + paint.setColor(new Color().rgb(255 - val, 255 - val, 255 - val)); + canvas.drawText(val + "", j * boxWidth + (int) (0.5 * boxWidth), i * boxHeight + (int) (0.5 * boxHeight), paint); + } + } + //onDrawLSTMCNN(canvas, boxWidth, boxHeight); + } +} diff --git a/viewer/app/src/main/java/io/interactionlab/capimgdemo/FullscreenActivity.java b/viewer/app/src/main/java/io/interactionlab/capimgdemo/FullscreenActivity.java new file mode 100644 index 0000000..82e378c --- /dev/null +++ b/viewer/app/src/main/java/io/interactionlab/capimgdemo/FullscreenActivity.java @@ -0,0 +1,289 @@ +package io.interactionlab.capimgdemo; + +import android.annotation.SuppressLint; +import android.content.DialogInterface; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.hcilab.libftsp.LocalDeviceHandler; +import org.hcilab.libftsp.capacitivematrix.blobdetection.BlobBoundingBox; +import org.hcilab.libftsp.capacitivematrix.capmatrix.CapacitiveImageTS; +import org.hcilab.libftsp.listeners.LocalCapImgListener; +import org.opencv.android.OpenCVLoader; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import io.interactionlab.capimgdemo.demo.DemoSettings; +import io.interactionlab.capimgdemo.demo.ModelDescription; + +/** + * An example full-screen activity that shows and hides the system UI (i.e. + * status bar and navigation/system bar) with user interaction. + */ +public class FullscreenActivity extends AppCompatActivity { + + + private static final String TAG = FullscreenActivity.class.getSimpleName(); + + static { + if (!OpenCVLoader.initDebug()) + Log.d("ERROR", "Unable to load OpenCV"); + else + Log.d("SUCCESS", "OpenCV loaded"); + } + /** + * Some older devices needs a small delay between UI widget updates + * and a change of the status and navigation bar. + */ + private static final int UI_ANIMATION_DELAY = 300; + + private final Handler mHideHandler = new Handler(); + + private final Runnable mHidePart2Runnable = new Runnable() { + @SuppressLint("InlinedApi") + @Override + public void run() { + movableWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + }; + private View mControlsView; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // Delayed display of UI elements + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.show(); + } + mControlsView.setVisibility(View.VISIBLE); + } + }; + private final Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hide(); + } + }; + + private RelativeLayout movableWindow; + private DrawView drawView; + private TextView textViewMode; + + private BlobClassifier blobClassifier; + + private final static int WINDOW_SIZE = 50; + private int classification_display_length = 0; + private double cnn_classifications = 0; + private double cnn_classifications_total = 0; + + private void setModel(ModelDescription modelDescription) { + ModelDescription currentModel = modelDescription; + blobClassifier.setModel(currentModel); + textViewMode.setText(Html.fromHtml("Model: " + modelDescription.modelName + "")); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_fullscreen); + + movableWindow = findViewById(R.id.movableScreen); + blobClassifier = new BlobClassifier(this); + + final List images = new ArrayList<>(); + + LocalDeviceHandler localDeviceHandler = new LocalDeviceHandler(); + localDeviceHandler.setLocalCapImgListener(new LocalCapImgListener() { + @Override + public void onLocalCapImg(final CapacitiveImageTS capImg) { // called approximately every 50ms + + if (classification_display_length == 0) { + int[][] large = blobClassifier.preprocess(capImg); + final List blobBoundingBoxes = blobClassifier.getBlobBoundaries(large); + final List labelNames = new ArrayList<>(); + final List colors = new ArrayList<>(); + List flattenedBlobs = new ArrayList<>(); + + // if first blob already detected, add each image up to WINDOW_SIZE + if (!images.isEmpty()) { + images.add(capImg.getMatrix()); + } + + for (BlobBoundingBox bbb : blobBoundingBoxes) { + if (images.isEmpty()) { + images.add(capImg.getMatrix()); + } // first detected blob + flattenedBlobs.add(blobClassifier.getBlobContentIn27x15(large, bbb)); + } + + for (int i = 0; i < flattenedBlobs.size(); i++) { // always classify + ClassificationResult cr = blobClassifier.classify(flattenedBlobs.get(i)); + if (Objects.equals(cr.label, "Finger")) { + cnn_classifications += 1.0; + } + cnn_classifications_total += 1.0; + } + + if (images.size() == WINDOW_SIZE) { // classify after WINDOW_SIZE images + blobClassifier.setModel(DemoSettings.models[1]); // set the model to LSTM + final ClassificationResult cr = blobClassifier.classify(blobClassifier.imagesToPixels(images)); + labelNames.add(cr.label + " (" + ((int) Math.round(cr.confidence * 100)) + "%)"); + final String cnnLabel; + //Log.i("Test","CNN Classifications: "+String.valueOf(cnn_classifications)); + if (cnn_classifications / cnn_classifications_total >= 0.5) { + cnnLabel = "Finger"; + } else { + cnnLabel = "Knuckle"; + } + runOnUiThread(new Runnable() { + @Override + public void run() { + textViewMode.setText(Html.fromHtml(""+cnnLabel+": " + cr.label + "")); + } + }); + cnn_classifications = 0.0; + cnn_classifications_total = 0.0; + colors.add(cr.color); + images.clear(); + blobClassifier.setModel(DemoSettings.models[0]); + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + drawView.updateView(capImg, blobBoundingBoxes, labelNames, colors); + } + }); + + + if (!labelNames.isEmpty()) { + classification_display_length = 20; + } + } else { + if (classification_display_length==1) { + runOnUiThread(new Runnable() { + @Override + public void run() { + textViewMode.setText(""); + } + }); + } + classification_display_length--; + } + } + }); + localDeviceHandler.startHandler(); + + // fill the whole screen. + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); + params.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); + params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); + params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); + + drawView = new DrawView(this); + movableWindow.removeAllViews(); + movableWindow.addView(drawView, params); + + textViewMode = new TextView(this); + textViewMode.setText(Html.fromHtml("x")); + textViewMode.setTextColor(Color.WHITE); + textViewMode.setTextSize(35); + textViewMode.setBackgroundColor(Color.TRANSPARENT); + RelativeLayout.LayoutParams tvmLayout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + tvmLayout.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE); + movableWindow.addView(textViewMode, tvmLayout); + + textViewMode.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String[] modelNames = new String[DemoSettings.models.length]; + for (int i = 0; i < DemoSettings.models.length; i++) { + modelNames[i] = DemoSettings.models[i].modelName; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(FullscreenActivity.this); + builder.setTitle("Select a Model"); + builder.setItems(modelNames, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + setModel(DemoSettings.models[which]); + delayedHide(100); + } + }); + builder.show(); + } + }); + + setModel(DemoSettings.models[0]); + } + + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + // Trigger the initial hide() shortly after the activity has been + // created, to briefly hint to the user that UI controls + // are available. + delayedHide(100); + } + + @Override + protected void onResume() { + super.onResume(); + + // Trigger the initial hide() shortly after the activity has been + // created, to briefly hint to the user that UI controls + // are available. + delayedHide(100); + } + + private void hide() { + // Hide UI first + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + + // Schedule a runnable to remove the status and navigation bar after a delay + mHideHandler.removeCallbacks(mShowPart2Runnable); + mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + } + + @SuppressLint("InlinedApi") + private void show() { +// // Show the system bar + movableWindow.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + + // Schedule a runnable to display UI elements after a delay + mHideHandler.removeCallbacks(mHidePart2Runnable); + mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY); + } + + /** + * Schedules a call to hide() in [delay] milliseconds, canceling any + * previously scheduled calls. + */ + private void delayedHide(int delayMillis) { + mHideHandler.removeCallbacks(mHideRunnable); + mHideHandler.postDelayed(mHideRunnable, delayMillis); + } +} \ No newline at end of file diff --git a/viewer/app/src/main/java/io/interactionlab/capimgdemo/demo/DemoSettings.java b/viewer/app/src/main/java/io/interactionlab/capimgdemo/demo/DemoSettings.java new file mode 100644 index 0000000..a4fc06a --- /dev/null +++ b/viewer/app/src/main/java/io/interactionlab/capimgdemo/demo/DemoSettings.java @@ -0,0 +1,46 @@ +package io.interactionlab.capimgdemo.demo; + +import android.graphics.Color; + +/** + * Created by Huy on 28/06/2018. + */ + +public class DemoSettings { + + private static int[] colorBand(int size) { + int[] band = new int[size]; + for (int i = 0; i < size; i++) { + band[i] = Color.HSVToColor(new float[]{i*360.0f/(float)size, 1.0f, 1.0f}); + } + return band; + } + + public static final ModelDescription[] models = new ModelDescription[]{ + new ModelDescription( + "KnuckleFinger", + "file:///android_asset/CNN.pb", + "conv2d_1_input", + "output_node0", + new long[]{1, 27, 15, 1}, + new String[]{"Knuckle", "Finger"}, + new int[]{Color.GREEN, Color.YELLOW} + ), + new ModelDescription( + "GestureRecognition", + "file:///android_asset/LSTM.pb", + "time_distributed_10_input", + "output_node0", + new long[]{50, 27, 15, 1}, + new String[]{"Tap", "Two Tap", "Swipe left", + "Swipe right", "Swipe up", "Swipe down", + "Swipe up with two", "Swipe down with two", "Circle", + "Arrowhead left", "Arrowhead right", "Checkmark", + "Γ", "L", "Mirrored L", "S", + "Rotate"}, + colorBand(17) + ) + }; +} + + diff --git a/viewer/app/src/main/java/io/interactionlab/capimgdemo/demo/ModelDescription.java b/viewer/app/src/main/java/io/interactionlab/capimgdemo/demo/ModelDescription.java new file mode 100644 index 0000000..bc4b112 --- /dev/null +++ b/viewer/app/src/main/java/io/interactionlab/capimgdemo/demo/ModelDescription.java @@ -0,0 +1,26 @@ +package io.interactionlab.capimgdemo.demo; + +/** + * Created by Huy on 28/06/2018. + */ + +@SuppressWarnings("ALL") +public class ModelDescription { + public final String modelPath; + public final String modelName; + public final String inputNode; + public final String outputNode; + public final long[] inputDimensions; + public final String[] labels; + public final int[] labelColor; + + public ModelDescription(String modelName, String modelPath, String inputNode, String outputNode, long[] inputDimensions, String[] labels, int[] labelColor) { + this.modelName = modelName; + this.modelPath = modelPath; + this.inputNode = inputNode; + this.outputNode = outputNode; + this.inputDimensions = inputDimensions; + this.labels = labels; + this.labelColor = labelColor; + } +} diff --git a/viewer/app/src/main/res/layout/activity_fullscreen.xml b/viewer/app/src/main/res/layout/activity_fullscreen.xml new file mode 100644 index 0000000..2d99cd8 --- /dev/null +++ b/viewer/app/src/main/res/layout/activity_fullscreen.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/viewer/app/src/main/res/mipmap-hdpi/ic_launcher.png b/viewer/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..adf1386 Binary files /dev/null and b/viewer/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/viewer/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/viewer/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..9a078e3 Binary files /dev/null and b/viewer/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/viewer/app/src/main/res/mipmap-mdpi/ic_launcher.png b/viewer/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..1989aca Binary files /dev/null and b/viewer/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/viewer/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/viewer/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..efc028a Binary files /dev/null and b/viewer/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/viewer/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/viewer/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..40fff7d Binary files /dev/null and b/viewer/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/viewer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/viewer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3af2608 Binary files /dev/null and b/viewer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/viewer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/viewer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d679ed4 Binary files /dev/null and b/viewer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/viewer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/viewer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9bec2e6 Binary files /dev/null and b/viewer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/viewer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/viewer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..337b7bb Binary files /dev/null and b/viewer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/viewer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/viewer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..34947cd Binary files /dev/null and b/viewer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/viewer/app/src/main/res/values/attrs.xml b/viewer/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..7ce840e --- /dev/null +++ b/viewer/app/src/main/res/values/attrs.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/viewer/app/src/main/res/values/colors.xml b/viewer/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..227fd33 --- /dev/null +++ b/viewer/app/src/main/res/values/colors.xml @@ -0,0 +1,8 @@ + + + #3F51B5 + #303F9F + #FF4081 + + #66000000 + diff --git a/viewer/app/src/main/res/values/strings.xml b/viewer/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..3bcc682 --- /dev/null +++ b/viewer/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Viewer + diff --git a/viewer/app/src/main/res/values/styles.xml b/viewer/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..dc8a48c --- /dev/null +++ b/viewer/app/src/main/res/values/styles.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/viewer/app/src/main/res/xml/backup_descriptor.xml b/viewer/app/src/main/res/xml/backup_descriptor.xml new file mode 100644 index 0000000..6fd6103 --- /dev/null +++ b/viewer/app/src/main/res/xml/backup_descriptor.xml @@ -0,0 +1,4 @@ + + + + diff --git a/viewer/app/src/test/java/io/interactionlab/capimgdemo/ExampleUnitTest.java b/viewer/app/src/test/java/io/interactionlab/capimgdemo/ExampleUnitTest.java new file mode 100644 index 0000000..0b49a92 --- /dev/null +++ b/viewer/app/src/test/java/io/interactionlab/capimgdemo/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package io.interactionlab.capimgdemo; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/viewer/build.gradle b/viewer/build.gradle new file mode 100644 index 0000000..723d307 --- /dev/null +++ b/viewer/build.gradle @@ -0,0 +1,31 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + maven { + url 'https://maven.google.com/' + name 'Google' + } + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + maven { + url 'https://maven.google.com/' + name 'Google' + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/viewer/gradle.properties b/viewer/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/viewer/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/viewer/gradle/wrapper/gradle-wrapper.jar b/viewer/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/viewer/gradle/wrapper/gradle-wrapper.jar differ diff --git a/viewer/gradle/wrapper/gradle-wrapper.properties b/viewer/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ee0ff82 --- /dev/null +++ b/viewer/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Dec 20 15:44:55 CET 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/viewer/gradlew b/viewer/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/viewer/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/viewer/gradlew.bat b/viewer/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/viewer/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/viewer/settings.gradle b/viewer/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/viewer/settings.gradle @@ -0,0 +1 @@ +include ':app'