diff --git a/mediapipe/examples/android/solutions/posetracking-lindera/src/main/BUILD b/mediapipe/examples/android/solutions/posetracking-lindera/src/main/BUILD index 748ab157d..78854d493 100644 --- a/mediapipe/examples/android/solutions/posetracking-lindera/src/main/BUILD +++ b/mediapipe/examples/android/solutions/posetracking-lindera/src/main/BUILD @@ -36,11 +36,11 @@ android_binary( "//mediapipe/java/com/google/mediapipe/solutioncore:solution_rendering", "//mediapipe/java/com/google/mediapipe/solutioncore:video_input", # "//mediapipe/java/com/google/mediapipe/solutions/posetracking:copperlabs-lindera", - "//mediapipe/java/com/google/mediapipe/solutions/lindera:copperlabs-lindera", - "//mediapipe/java/com/google/mediapipe/solutions/posetracking:copperlabs-pose-api", - "//mediapipe/java/com/google/mediapipe/solutions/posetracking:copperlabs-pose-detection", - "//mediapipe/java/com/google/mediapipe/solutions/posetracking:copperlabs-pose-graph", - "//mediapipe/java/com/google/mediapipe/solutions/posetracking:copperlabs-pose-landmark", + "//mediapipe/java/com/google/mediapipe/solutions/copperlabs/copperlabs-lindera", + "//mediapipe/java/com/google/mediapipe/solutions/copperlabs/copperlabs-pose-api", + "//mediapipe/java/com/google/mediapipe/solutions/copperlabs/copperlabs-pose-api:copperlabs-pose-detection", + "//mediapipe/java/com/google/mediapipe/solutions/copperlabs/copperlabs-pose-api:copperlabs-pose-graph", + "//mediapipe/java/com/google/mediapipe/solutions/copperlabs/copperlabs-pose-api:copperlabs-pose-landmark", "//third_party:androidx_appcompat", "//third_party:androidx_constraint_layout", "//third_party:opencv", diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/.gitignore b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/build.gradle b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/build.gradle new file mode 100644 index 000000000..5eff1c105 --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.google.mediapipe' + compileSdk 32 + + defaultConfig { + applicationId "com.google.mediapipe" + minSdk 24 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + + implementation project(':copperlabs-lindera') + implementation project(':copperlabs-pose-api') + implementation project(':copperlabs-pose-detection') + implementation project(':copperlabs-pose-landmark') + implementation project(':copperlabs-pose-graph') + + implementation fileTree(dir: '../common_libs', include: ['*.jar', '*.aar']) + + implementation 'com.afollestad.material-dialogs:core:0.9.6.0' + // CameraX core library + def camerax_version = "1.0.0-beta10" + implementation "androidx.camera:camera-core:$camerax_version" + implementation "androidx.camera:camera-camera2:$camerax_version" + implementation "androidx.camera:camera-lifecycle:$camerax_version" + + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/proguard-rules.pro b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# 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 \ No newline at end of file diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/androidTest/java/com/google/mediapipe/ExampleInstrumentedTest.java b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/androidTest/java/com/google/mediapipe/ExampleInstrumentedTest.java new file mode 100644 index 000000000..4ad84d885 --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/androidTest/java/com/google/mediapipe/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.google.mediapipe; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.google.mediapipe", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/AndroidManifest.xml b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9dc41437f --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/java/com/google/mediapipe/examples/posetracking_lindera/ComputerVisionPluginImpl.java b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/java/com/google/mediapipe/examples/posetracking_lindera/ComputerVisionPluginImpl.java new file mode 100644 index 000000000..902f72a2d --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/java/com/google/mediapipe/examples/posetracking_lindera/ComputerVisionPluginImpl.java @@ -0,0 +1,140 @@ +package com.google.mediapipe.examples.posetracking_lindera; + +import static java.lang.Math.min; + +import com.google.mediapipe.solutions.lindera.BodyJoints; +import com.google.mediapipe.solutions.lindera.ComputerVisionPlugin; +import com.google.mediapipe.solutions.lindera.XYZPointWithConfidence; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Map; + +public class ComputerVisionPluginImpl implements ComputerVisionPlugin { + + LinkedList bodyJointsEventList = new LinkedList<>(); + static class BodyJointsEvent{ + BodyJoints bodyJoints; + Long timestamp; + + public BodyJointsEvent(long timestamp, BodyJoints bodyJoints) { + this.bodyJoints = bodyJoints; + this.timestamp = timestamp; + } + } + + + + + boolean isLogging = false; + + public void startLogging(){ + isLogging = true; + bodyJointsEventList = new LinkedList<>(); + } + + public JSONObject stopLoggingAndDumpOutput() throws JSONException, IllegalAccessException { + isLogging = false; + // base json string + String json = "{\n" + + " \"identifier\": \"some_name_here.Capture\",\n" + + " \"metaData\": {\n" + + " \"userIdentifier\": \"unknown user\",\n" + + " \"originatingEquipment\": \"Apperture\",\n" + + " \"activityName\": \"exercise name\",\n" + + " \"activityDescription\": \"\",\n" + + " \"tags\": [\n" + + " \n" + + " ]\n" + + " },\n" + + " \"bodyObjects\": [\n" + + " \n" + + " ]}"; + + JSONObject eLog = new JSONObject(json); + JSONArray bodyJointsArr = new JSONArray(); + for (BodyJointsEvent bodyJointsEvent:bodyJointsEventList){ + JSONObject jBodyJointEvent = new JSONObject(); + jBodyJointEvent.put("ts",bodyJointsEvent.timestamp); + BodyJoints bodyJoints = bodyJointsEvent.bodyJoints; + String bodyJointsString = ""; + // iterate over fields of BodyJoints and put them in json format + for (Field field : BodyJoints.class.getDeclaredFields()) { + Class type = field.getType(); + + if (type == XYZPointWithConfidence.class) { + + String name = field.getName(); + // get abbreviation of name for example leftShoulder -> LS + String abbrev = String.valueOf(name.charAt(0)); + for (int i = 1;i cameras = lindera.getAvailableCameras(); + // FRONT or BACK + lindera.setCamera("FRONT"); + lindera.setCameraRotation(CameraRotation.AUTOMATIC); + + lindera.fpsHelper.onFpsUpdate = new Consumer() { + @Override + public void accept(Double fps) { + String text = "FPS: "+String.format("%04.1f" ,fps); + runOnUiThread(()-> { + TextView view = findViewById(R.id.fps_view); + view.setText(text); + }); + } + }; + + } + + + /** + * Sets up the UI components for the live demo with camera input. + */ + private void setupLiveDemoUiComponents() { + + Button startDetectionButton = findViewById(R.id.button_start_detection); + Button toggleLandmarks = findViewById(R.id.button_toggle_landmarks); + Button modelComplexity = findViewById(R.id.button_set_model); + Button startCapture = findViewById(R.id.button_capture_logging); + FrameLayout frameLayout = findViewById(R.id.preview_display_layout); + + startDetectionButton.setOnClickListener( + v -> { +// startCameraButton.setVisibility(View.GONE); + if (!isLinderaInitialized) { + modelLoadAsyncDialogue(()->{ + lindera.initialize(frameLayout, MainActivity.this); + isLinderaInitialized = true; + startDetectionButton.setVisibility(View.GONE); + findViewById(R.id.button_set_model).setVisibility(View.VISIBLE); + findViewById(R.id.button_toggle_landmarks).setVisibility(View.VISIBLE); + findViewById(R.id.button_capture_logging).setVisibility(View.VISIBLE); + + updateLandmarkButtonText(); + updateModelComplexityButtonText(); + }); + + + } + isDetectionStarted = !isDetectionStarted; + + + }); + + toggleLandmarks.setOnClickListener( + v ->{ + this.lindera.setLandmarksVisibility(!this.lindera.getLandmarkVisibility()); + updateLandmarkButtonText(); + } + ); + + startCapture.setOnClickListener(v->{ + + if (isLoggingStarted){ + startCapture.setText("Start Capture"); + isLoggingStarted = false; + try { + JSONObject jsonObject = plugin.stopLoggingAndDumpOutput(); + // save to downloads folder + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DISPLAY_NAME, "data.json"); + values.put(MediaStore.MediaColumns.MIME_TYPE, "application/json"); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS); + + final ContentResolver resolver = getContentResolver(); + Uri uri = null; + final Uri contentUri; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + contentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI; + + uri = resolver.insert(contentUri, values); + final OutputStream stream = resolver.openOutputStream(uri); + stream.write(jsonObject.toString().getBytes(StandardCharsets.UTF_8)); + stream.close(); + Toast.makeText(getApplicationContext(), "data.json saved to Downloads", Toast.LENGTH_LONG).show(); + }else { + Toast.makeText(getApplicationContext(), "Error: API not supported", Toast.LENGTH_LONG).show(); + + } + + } catch (JSONException | IllegalAccessException | IOException e) { + e.printStackTrace(); + Toast.makeText(getApplicationContext(), "Failed to save data", Toast.LENGTH_LONG).show(); + + } + } + else { + ProgressBar pbar = new ProgressBar(this); + frameLayout.addView(pbar); + final Handler handler = new Handler(Looper.getMainLooper()); + startCapture.setEnabled(false); + + handler.postDelayed(new Runnable() { + @Override + public void run() { + frameLayout.removeView(pbar); + startCapture.setEnabled(true); + startCapture.setText("Stop Capture"); + plugin.startLogging(); + } + }, 5000); + isLoggingStarted = true; + } + }); + + modelComplexity.setOnClickListener(v->{ + int modelComplexityVal = lindera.getModelComplexity(); + + new MaterialDialog.Builder(this) + .title("Choose Model Complexity") + .items(Arrays.asList("Lite","Full","Heavy")) + .itemsCallbackSingleChoice(modelComplexityVal, new MaterialDialog.ListCallbackSingleChoice() { + @Override + public boolean onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { + /** + * If you use alwaysCallSingleChoiceCallback(), which is discussed below, + * returning false here won't allow the newly selected radio button to actually be selected. + **/ + if (which != modelComplexityVal){ + modelLoadAsyncDialogue(()-> { + lindera.setModelComplexity(which); + lindera.restartDetection(); + updateModelComplexityButtonText(); + }); + } + return true; + } + }) + .positiveText("choose") + .show(); +// listItemsSingleChoice(R.array.my_items, initialSelection = 1); + + + + }); + + } + void updateLandmarkButtonText(){ + Button toggleLandmarks = findViewById(R.id.button_toggle_landmarks); + + if (this.lindera.getLandmarkVisibility()) { + toggleLandmarks.setText("Show Landmarks (On)"); + }else{ + toggleLandmarks.setText("Show Landmarks (Off)"); + + } + } + + void updateModelComplexityButtonText(){ + String text = "Select Model "; + switch (this.lindera.getModelComplexity()){ + case 0: + text += "(lite)"; + break; + case 1: + text += "(full)"; + break; + case 2: + text += "(heavy)"; + break; + + } + Button setModel = findViewById(R.id.button_set_model); + setModel.setText(text); + + } + + void modelLoadAsyncDialogue(Runnable loader){ + ProgressBar pbar = new ProgressBar(this); + MaterialDialog dialog = new MaterialDialog.Builder(this) + .title("Loading Model") + .customView(pbar, false) + .build(); + dialog.show(); + ExecutorService executor = Executors.newSingleThreadExecutor(); + Handler handler = new Handler(Looper.getMainLooper()); + + executor.execute(new Runnable() { + @Override + public void run() { + //Background work here + handler.post(loader); + dialog.dismiss(); + } + }); + } + + + + + + + + + +} diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..2b068d114 --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/drawable/ic_launcher_background.xml b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..07d5da9cb --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/layout/activity_main.xml b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..975690cb0 --- /dev/null +++ b/mediapipe/java/com/google/mediapipe/solutions/copperlabs/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,85 @@ + + + + + +