diff --git a/deploy/lite/android/demo/.gitignore b/deploy/lite/android/demo/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2b75303ac58f551de0a327638a60b909c6d33ece --- /dev/null +++ b/deploy/lite/android/demo/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/deploy/lite/android/demo/app/.gitignore b/deploy/lite/android/demo/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/deploy/lite/android/demo/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/deploy/lite/android/demo/app/build.gradle b/deploy/lite/android/demo/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..f743f1d23905566772c4e572e9700df5ad779ca0 --- /dev/null +++ b/deploy/lite/android/demo/app/build.gradle @@ -0,0 +1,119 @@ +import java.security.MessageDigest + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.baidu.paddlex.lite.demo" + minSdkVersion 15 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(include: ['*.aar'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.android.support:design:28.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} + + +def paddlexAndroidSdk = 'https://bj.bcebos.com/paddlex/deploy/lite/paddlex_lite_11cbd50e.tar.gz' + +task downloadAndExtractPaddleXAndroidSdk(type: DefaultTask) { + doFirst { + println "Downloading and extracting PaddleX Android SDK"} + doLast { + // Prepare cache folder for sdk + if (!file("cache").exists()) { + mkdir "cache" + } + // Generate cache name for sdk + MessageDigest messageDigest = MessageDigest.getInstance('MD5') + messageDigest.update(paddlexAndroidSdk.bytes) + String cacheName = new BigInteger(1, messageDigest.digest()).toString(32) + // Download sdk + if (!file("cache/${cacheName}.tar.gz").exists()) { + ant.get(src: paddlexAndroidSdk, dest: file("cache/${cacheName}.tar.gz")) + } + // Unpack sdk + copy { + from tarTree("cache/${cacheName}.tar.gz") + into "cache/${cacheName}" + } + // Copy sdk + if (!file("libs/paddlex.aar").exists()) { + copy { + from "cache/${cacheName}/paddlex.aar" + into "libs" + } + } + } +} + +preBuild.dependsOn downloadAndExtractPaddleXAndroidSdk + +def paddleXLiteModel = 'https://bj.bcebos.com/paddlex/deploy/lite/mobilenetv2_imagenet_lite2.6.1.tar.gz' +task downloadAndExtractPaddleXLiteModel(type: DefaultTask) { + doFirst { + println "Downloading and extracting PaddleX Android SDK"} + + doLast { + // Prepare cache folder for model + if (!file("cache").exists()) { + mkdir "cache" + } + // Generate cache name for model + MessageDigest messageDigest = MessageDigest.getInstance('MD5') + messageDigest.update(paddleXLiteModel.bytes) + String cacheName = new BigInteger(1, messageDigest.digest()).toString(32) + // Download sdk + if (!file("cache/${cacheName}.tar.gz").exists()) { + ant.get(src: paddleXLiteModel, dest: file("cache/${cacheName}.tar.gz")) + } + + // Unpack model + copy { + from tarTree("cache/${cacheName}.tar.gz") + into "cache/${cacheName}" + } + + // Copy model.nb + if (!file("src/main/assets/model/model.nb").exists()) { + copy { + from "cache/${cacheName}/model.nb" + into "src/main/assets/model/" + } + } + // Copy config file model.yml + if (!file("src/main/assets/config/model.yml").exists()) { + copy { + from "cache/${cacheName}/model.yml" + into "src/main/assets/config/" + } + } + // Copy config file model.yml + if (!file("src/main/assets/images/test.jpg").exists()) { + copy { + from "cache/${cacheName}/test.jpg" + into "src/main/assets/images/" + } + } + } + +} + +preBuild.dependsOn downloadAndExtractPaddleXLiteModel diff --git a/deploy/lite/android/demo/app/proguard-rules.pro b/deploy/lite/android/demo/app/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f1b424510da51fd82143bc74a0a801ae5a1e2fcd --- /dev/null +++ b/deploy/lite/android/demo/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 diff --git a/deploy/lite/android/demo/app/src/androidTest/java/com/baidu/paddlex/lite/demo/ExampleInstrumentedTest.java b/deploy/lite/android/demo/app/src/androidTest/java/com/baidu/paddlex/lite/demo/ExampleInstrumentedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4b58dec6f5dd8bfa083ec951d659dd0690f67221 --- /dev/null +++ b/deploy/lite/android/demo/app/src/androidTest/java/com/baidu/paddlex/lite/demo/ExampleInstrumentedTest.java @@ -0,0 +1,32 @@ +package com.baidu.paddlex.lite.demo; + +import android.content.Context; +import android.content.res.AssetManager; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import com.baidu.paddlex.config.ConfigParser; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertEquals; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws IOException { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + AssetManager ass = appContext.getAssets(); + assertEquals("com.baidu.paddlex.lite.demo", appContext.getPackageName()); + } +} diff --git a/deploy/lite/android/demo/app/src/main/AndroidManifest.xml b/deploy/lite/android/demo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..940c9692fcf6fdfe6b07e8f4641fe7e9a9e5ff5f --- /dev/null +++ b/deploy/lite/android/demo/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/AppCompatPreferenceActivity.java b/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/AppCompatPreferenceActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..c6f4eff8e736278c71ef2c34783dd3e1b3659495 --- /dev/null +++ b/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/AppCompatPreferenceActivity.java @@ -0,0 +1,126 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.baidu.paddlex.lite.demo; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + *

+ * This technique can be used with an {@link android.app.Activity} class, not just + * {@link android.preference.PreferenceActivity}. + */ + +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/MainActivity.java b/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/MainActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..62e47214fc80a40fbfa173967f61e490eab92e47 --- /dev/null +++ b/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/MainActivity.java @@ -0,0 +1,466 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.baidu.paddlex.lite.demo; + +import android.Manifest; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.text.method.ScrollingMovementMethod; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import com.baidu.paddlex.Predictor; +import com.baidu.paddlex.Utils; +import com.baidu.paddlex.config.ConfigParser; +import com.baidu.paddlex.postprocess.ClsResult; +import com.baidu.paddlex.postprocess.DetResult; +import com.baidu.paddlex.postprocess.SegResult; +import com.baidu.paddlex.visual.Visualize; +import org.opencv.core.Mat; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class MainActivity extends AppCompatActivity { + public static final int OPEN_GALLERY_REQUEST_CODE = 0; + public static final int TAKE_PHOTO_REQUEST_CODE = 1; + public static final int REQUEST_LOAD_MODEL = 0; + public static final int REQUEST_RUN_MODEL = 1; + public static final int RESPONSE_LOAD_MODEL_SUCCESSED = 0; + public static final int RESPONSE_LOAD_MODEL_FAILED = 1; + public static final int RESPONSE_RUN_MODEL_SUCCESSED = 2; + public static final int RESPONSE_RUN_MODEL_FAILED = 3; + private static final String TAG = MainActivity.class.getSimpleName(); + protected ProgressDialog pbLoadModel = null; + protected ProgressDialog pbRunModel = null; + + protected Handler receiver = null; // receive messages from worker thread + protected Handler sender = null; // send command to worker thread + protected HandlerThread worker = null; // worker thread to load&run model + + protected TextView tvInputSetting; + protected ImageView ivInputImage; + protected TextView tvOutputResult; + protected TextView tvInferenceTime; + private Button predictButton; + protected String testImagePathFromAsset; + protected String testYamlPathFromAsset; + protected String testModelPathFromAsset; + + // Predictor + protected Predictor predictor = new Predictor(); + // model config + protected ConfigParser configParser = new ConfigParser(); + // Visualize + protected Visualize visualize = new Visualize(); + // Predict Mat of Opencv + protected Mat predictMat; + + + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + receiver = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case RESPONSE_LOAD_MODEL_SUCCESSED: + pbLoadModel.dismiss(); + Toast.makeText(MainActivity.this, "Load model successfully!", Toast.LENGTH_SHORT).show(); + break; + case RESPONSE_LOAD_MODEL_FAILED: + pbLoadModel.dismiss(); + Toast.makeText(MainActivity.this, "Load model failed!", Toast.LENGTH_SHORT).show(); + break; + case RESPONSE_RUN_MODEL_SUCCESSED: + pbRunModel.dismiss(); + onRunModelSuccessed(); + break; + case RESPONSE_RUN_MODEL_FAILED: + pbRunModel.dismiss(); + Toast.makeText(MainActivity.this, "Run model failed!", Toast.LENGTH_SHORT).show(); + onRunModelFailed(); + break; + default: + break; + } + } + }; + worker = new HandlerThread("Predictor Worker"); + worker.start(); + sender = new Handler(worker.getLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case REQUEST_LOAD_MODEL: + // load model and reload test image + if (onLoadModel()) { + receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_SUCCESSED); + } else { + receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_FAILED); + } + break; + case REQUEST_RUN_MODEL: + // run model if model is loaded + if (onRunModel()) { + receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_SUCCESSED); + } else { + receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_FAILED); + } + break; + default: + break; + } + } + }; + + tvInputSetting = findViewById(R.id.tv_input_setting); + ivInputImage = findViewById(R.id.iv_input_image); + predictButton = findViewById(R.id.iv_predict_button); + tvInferenceTime = findViewById(R.id.tv_inference_time); + tvOutputResult = findViewById(R.id.tv_output_result); + tvInputSetting.setMovementMethod(ScrollingMovementMethod.getInstance()); + tvOutputResult.setMovementMethod(ScrollingMovementMethod.getInstance()); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + String image_path = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), + getString(R.string.IMAGE_PATH_DEFAULT)); + Utils.initialOpencv(); + loadTestImageFromAsset(image_path); + predictButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(predictor.isLoaded()){ + onLoadModelSuccessed(); + } + } + }); + + } + + public boolean onLoadModel() { + return predictor.init(configParser); + } + + public boolean onRunModel() { + return predictor.isLoaded() && predictor.predict(); + } + + public void onRunModelFailed() { + } + + public void loadModel() { + pbLoadModel = ProgressDialog.show(this, "", "Loading model...", false, false); + sender.sendEmptyMessage(REQUEST_LOAD_MODEL); + } + + public void runModel() { + pbRunModel = ProgressDialog.show(this, "", "Running model...", false, false); + sender.sendEmptyMessage(REQUEST_RUN_MODEL); + } + + public void onLoadModelSuccessed() { + if (predictMat != null && predictor.isLoaded()) { + int w = predictMat.width(); + int h = predictMat.height(); + int c = predictMat.channels(); + predictor.setInputMat(predictMat); + runModel(); + } + } + + public void onRunModelSuccessed() { + // obtain results and update UI + tvInferenceTime.setText("Inference time: " + predictor.getInferenceTime() + " ms"); + + if (configParser.getModelType().equalsIgnoreCase("segmenter")) { + SegResult segResult = predictor.getSegResult(); + Mat maskMat = visualize.draw(segResult, predictMat.clone(), predictor.getImageBlob(), 1); + Imgproc.cvtColor(maskMat, maskMat, Imgproc.COLOR_BGRA2RGBA); + Bitmap outputImage = Bitmap.createBitmap(maskMat.width(), maskMat.height(), Bitmap.Config.ARGB_8888); + org.opencv.android.Utils.matToBitmap(maskMat, outputImage); + if (outputImage != null) { + ivInputImage.setImageBitmap(outputImage); + } + } else if (configParser.getModelType().equalsIgnoreCase("detector")) { + DetResult detResult = predictor.getDetResult(); + Mat roiMat = visualize.draw(detResult, predictMat.clone()); + Imgproc.cvtColor(roiMat, roiMat, Imgproc.COLOR_BGR2RGB); + Bitmap outputImage = Bitmap.createBitmap(roiMat.width(),roiMat.height(), Bitmap.Config.ARGB_8888); + org.opencv.android.Utils.matToBitmap(roiMat,outputImage); + if (outputImage != null) { + ivInputImage.setImageBitmap(outputImage); + } + } else if (configParser.getModelType().equalsIgnoreCase("classifier")) { + ClsResult clsResult = predictor.getClsResult(); + if (configParser.getLabeList().size() > 0) { + String outputResult = "Top1: " + clsResult.getCategory() + " - " + String.format("%.3f", clsResult.getScore()); + tvOutputResult.setText(outputResult); + tvOutputResult.scrollTo(0, 0); + } + } + } + + public void onMatChanged(Mat mat) { + this.predictMat = mat.clone(); + } + + public void onImageChanged(Bitmap image) { + ivInputImage.setImageBitmap(image); + tvOutputResult.setText(""); + tvInferenceTime.setText("Inference time: -- ms"); + } + + public void onSettingsClicked() { + startActivity(new Intent(MainActivity.this, SettingsActivity.class)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_action_options, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + break; + case R.id.open_gallery: + if (requestAllPermissions()) { + openGallery(); + } + break; + case R.id.take_photo: + if (requestAllPermissions()) { + takePhoto(); + } + break; + case R.id.settings: + if (requestAllPermissions()) { + // make sure we have SDCard r&w permissions to load model from SDCard + onSettingsClicked(); + } + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK && data != null) { + switch (requestCode) { + case OPEN_GALLERY_REQUEST_CODE: + try { + ContentResolver resolver = getContentResolver(); + Uri uri = data.getData(); + Bitmap image = MediaStore.Images.Media.getBitmap(resolver, uri); + String[] proj = {MediaStore.Images.Media.DATA}; + Cursor cursor = managedQuery(uri, proj, null, null, null); + cursor.moveToFirst(); + int columnIndex = cursor.getColumnIndex(proj[0]); + String imgDecodableString = cursor.getString(columnIndex); + File file = new File(imgDecodableString); + Mat mat = Imgcodecs.imread(file.getAbsolutePath(),Imgcodecs.IMREAD_COLOR); + onImageChanged(image); + onMatChanged(mat); + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + break; + case TAKE_PHOTO_REQUEST_CODE: + Bitmap image = (Bitmap) data.getParcelableExtra("data"); + Mat mat = new Mat(); + org.opencv.android.Utils.bitmapToMat(image, mat); + Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2BGR); + onImageChanged(image); + onMatChanged(mat); + break; + default: + break; + } + } + } + + private boolean requestAllPermissions() { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, + Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.CAMERA}, + 0); + return false; + } + return true; + } + + private void openGallery() { + Intent intent = new Intent(Intent.ACTION_PICK, null); + intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); + startActivityForResult(intent, OPEN_GALLERY_REQUEST_CODE); + } + + private void takePhoto() { + Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (takePhotoIntent.resolveActivity(getPackageManager()) != null) { + startActivityForResult(takePhotoIntent, TAKE_PHOTO_REQUEST_CODE); + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + boolean isLoaded = predictor.isLoaded(); + menu.findItem(R.id.open_gallery).setEnabled(isLoaded); + menu.findItem(R.id.take_photo).setEnabled(isLoaded); + return super.onPrepareOptionsMenu(menu); + } + + @Override + protected void onResume() { + Log.i(TAG, "begin onResume"); + super.onResume(); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + boolean settingsChanged = false; + boolean testImageChanged = false; + String modelPath = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + settingsChanged |= !modelPath.equalsIgnoreCase(testModelPathFromAsset); + String yamlPath = sharedPreferences.getString(getString(R.string.YAML_PATH_KEY), + getString(R.string.YAML_PATH_DEFAULT)); + settingsChanged |= !yamlPath.equalsIgnoreCase(testYamlPathFromAsset); + int cpuThreadNum = Integer.parseInt(sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), + getString(R.string.CPU_THREAD_NUM_DEFAULT))); + settingsChanged |= cpuThreadNum != configParser.getCpuThreadNum(); + String cpuPowerMode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), + getString(R.string.CPU_POWER_MODE_DEFAULT)); + settingsChanged |= !cpuPowerMode.equalsIgnoreCase(configParser.getCpuPowerMode()); + String imagePath = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), + getString(R.string.IMAGE_PATH_DEFAULT)); + testImageChanged |= !imagePath.equalsIgnoreCase(testImagePathFromAsset); + + testYamlPathFromAsset = yamlPath; + testModelPathFromAsset = modelPath; + if (settingsChanged) { + try { + String realModelPath = modelPath; + if (!modelPath.substring(0, 1).equals("/")) { + String modelFileName = Utils.getFileNameFromString(modelPath); + realModelPath = this.getCacheDir() + File.separator + modelFileName; + Utils.copyFileFromAssets(this, modelPath, realModelPath); + } + String realYamlPath = yamlPath; + if (!yamlPath.substring(0, 1).equals("/")) { + String yamlFileName = Utils.getFileNameFromString(yamlPath); + realYamlPath = this.getCacheDir() + File.separator + yamlFileName; + Utils.copyFileFromAssets(this, yamlPath, realYamlPath); + } + configParser.init(realModelPath, realYamlPath, cpuThreadNum, cpuPowerMode); + visualize.init(configParser.getNumClasses()); + } catch (IOException e) { + e.printStackTrace(); + Toast.makeText(MainActivity.this, "Load config failed!", Toast.LENGTH_SHORT).show(); + } + // update UI + tvInputSetting.setText("Model: " + configParser.getModel()+ "\n" + "CPU" + + " Thread Num: " + Integer.toString(configParser.getCpuThreadNum()) + "\n" + "CPU Power Mode: " + configParser.getCpuPowerMode()); + tvInputSetting.scrollTo(0, 0); + // reload model if configure has been changed + loadModel(); + } + + if (testImageChanged){ + loadTestImageFromAsset(imagePath); + } + } + + public void loadTestImageFromAsset(String imagePath){ + if (imagePath.isEmpty()) { + return; + } + // read test image file from custom file_paths if the first character of mode file_paths is '/', otherwise read test + // image file from assets + testImagePathFromAsset = imagePath; + if (!imagePath.substring(0, 1).equals("/")) { + InputStream imageStream = null; + try { + imageStream = getAssets().open(imagePath); + } catch (IOException e) { + e.printStackTrace(); + } + onImageChanged(BitmapFactory.decodeStream(imageStream)); + String realPath; + String imageFileName = Utils.getFileNameFromString(imagePath); + realPath = this.getCacheDir() + File.separator + imageFileName; + Utils.copyFileFromAssets(this, imagePath, realPath); + onMatChanged(Imgcodecs.imread(realPath, Imgcodecs.IMREAD_COLOR)); + } else { + if (!new File(imagePath).exists()) { + return; + } + onMatChanged(Imgcodecs.imread(imagePath, Imgcodecs.IMREAD_COLOR)); + onImageChanged( BitmapFactory.decodeFile(imagePath)); + } + } + + @Override + protected void onDestroy() { + if (predictor != null) { + predictor.releaseModel(); + } + worker.quit(); + super.onDestroy(); + } +} \ No newline at end of file diff --git a/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/SettingsActivity.java b/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/SettingsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..271343ff5a626ba5d8a224dfe832738ae4ede123 --- /dev/null +++ b/deploy/lite/android/demo/app/src/main/java/com/baidu/paddlex/lite/demo/SettingsActivity.java @@ -0,0 +1,158 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.baidu.paddlex.lite.demo; + +import com.baidu.paddlex.Utils; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.support.v7.app.ActionBar; + +import java.util.ArrayList; +import java.util.List; + +public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { + ListPreference lpChoosePreInstalledModel = null; + CheckBoxPreference cbEnableCustomSettings = null; + EditTextPreference etModelPath = null; + EditTextPreference etYamlPath = null; + EditTextPreference etImagePath = null; + ListPreference lpCPUThreadNum = null; + ListPreference lpCPUPowerMode = null; + + List preInstalledModelPaths = null; + List preInstalledYamlPaths = null; + List preInstalledImagePaths = null; + List preInstalledCPUThreadNums = null; + List preInstalledCPUPowerModes = null; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + ActionBar supportActionBar = getSupportActionBar(); + if (supportActionBar != null) { + supportActionBar.setDisplayHomeAsUpEnabled(true); + } + + // initialized pre-installed models + preInstalledModelPaths = new ArrayList(); + preInstalledYamlPaths = new ArrayList(); + preInstalledImagePaths = new ArrayList(); + preInstalledCPUThreadNums = new ArrayList(); + preInstalledCPUPowerModes = new ArrayList(); + preInstalledModelPaths.add(getString(R.string.MODEL_PATH_DEFAULT)); + preInstalledYamlPaths.add(getString(R.string.YAML_PATH_DEFAULT)); + preInstalledImagePaths.add(getString(R.string.IMAGE_PATH_DEFAULT)); + preInstalledCPUThreadNums.add(getString(R.string.CPU_THREAD_NUM_DEFAULT)); + preInstalledCPUPowerModes.add(getString(R.string.CPU_POWER_MODE_DEFAULT)); + // initialize UI components + lpChoosePreInstalledModel = + (ListPreference) findPreference(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY)); + String[] preInstalledModelNames = new String[preInstalledModelPaths.size()]; + for (int i = 0; i < preInstalledModelPaths.size(); i++) { + preInstalledModelNames[i] = + preInstalledModelPaths.get(i).substring(preInstalledModelPaths.get(i).lastIndexOf("/") + 1); + } + lpChoosePreInstalledModel.setEntries(preInstalledModelNames); + lpChoosePreInstalledModel.setEntryValues(preInstalledModelPaths.toArray(new String[preInstalledModelPaths.size()])); + cbEnableCustomSettings = + (CheckBoxPreference) findPreference(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY)); + etModelPath = (EditTextPreference) findPreference(getString(R.string.MODEL_PATH_KEY)); + etModelPath.setTitle("Model Path (SDCard: " + Utils.getSDCardDirectory() + ")"); + etYamlPath = (EditTextPreference) findPreference(getString(R.string.YAML_PATH_KEY)); + etImagePath = (EditTextPreference) findPreference(getString(R.string.IMAGE_PATH_KEY)); + lpCPUThreadNum = + (ListPreference) findPreference(getString(R.string.CPU_THREAD_NUM_KEY)); + lpCPUPowerMode = + (ListPreference) findPreference(getString(R.string.CPU_POWER_MODE_KEY)); + } + + private void reloadPreferenceAndUpdateUI() { + SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); + boolean enableCustomSettings = + sharedPreferences.getBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); + String modelPath = sharedPreferences.getString(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + int modelIdx = lpChoosePreInstalledModel.findIndexOfValue(modelPath); + if (modelIdx >= 0 && modelIdx < preInstalledModelPaths.size()) { + if (!enableCustomSettings) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(getString(R.string.MODEL_PATH_KEY), preInstalledModelPaths.get(modelIdx)); + editor.putString(getString(R.string.YAML_PATH_KEY), preInstalledYamlPaths.get(modelIdx)); + editor.putString(getString(R.string.IMAGE_PATH_KEY), preInstalledImagePaths.get(modelIdx)); + editor.putString(getString(R.string.CPU_THREAD_NUM_KEY), preInstalledCPUThreadNums.get(modelIdx)); + editor.putString(getString(R.string.CPU_POWER_MODE_KEY), preInstalledCPUPowerModes.get(modelIdx)); + editor.commit(); + } + lpChoosePreInstalledModel.setSummary(modelPath); + } + + cbEnableCustomSettings.setChecked(enableCustomSettings); + etModelPath.setEnabled(enableCustomSettings); + etYamlPath.setEnabled(enableCustomSettings); + etImagePath.setEnabled(enableCustomSettings); + lpCPUThreadNum.setEnabled(enableCustomSettings); + lpCPUPowerMode.setEnabled(enableCustomSettings); + modelPath = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + String YamlPath = sharedPreferences.getString(getString(R.string.YAML_PATH_KEY), + getString(R.string.YAML_PATH_DEFAULT)); + String imagePath = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), + getString(R.string.IMAGE_PATH_DEFAULT)); + String cpuThreadNum = sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), + getString(R.string.CPU_THREAD_NUM_DEFAULT)); + String cpuPowerMode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), + getString(R.string.CPU_POWER_MODE_DEFAULT)); + + etModelPath.setSummary(modelPath); + etModelPath.setText(modelPath); + etYamlPath.setSummary(YamlPath); + etYamlPath.setText(YamlPath); + etImagePath.setSummary(imagePath); + etImagePath.setText(imagePath); + lpCPUThreadNum.setValue(cpuThreadNum); + lpCPUThreadNum.setSummary(cpuThreadNum); + lpCPUPowerMode.setValue(cpuPowerMode); + lpCPUPowerMode.setSummary(cpuPowerMode); + + } + + @Override + protected void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + reloadPreferenceAndUpdateUI(); + } + + @Override + protected void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY))) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); + editor.commit(); + } + reloadPreferenceAndUpdateUI(); + } +} diff --git a/deploy/lite/android/demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/deploy/lite/android/demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..1f6bb290603d7caa16c5fb6f61bbfdc750622f5c --- /dev/null +++ b/deploy/lite/android/demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/deploy/lite/android/demo/app/src/main/res/drawable/face.jpg b/deploy/lite/android/demo/app/src/main/res/drawable/face.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8963ae3db05894cd4bf3ea17957297363db73171 Binary files /dev/null and b/deploy/lite/android/demo/app/src/main/res/drawable/face.jpg differ diff --git a/deploy/lite/android/demo/app/src/main/res/drawable/ic_launcher_background.xml b/deploy/lite/android/demo/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d025f9bf6b67c63044a36a9ff44fbc69e5c5822 --- /dev/null +++ b/deploy/lite/android/demo/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/lite/android/demo/app/src/main/res/layout/activity_main.xml b/deploy/lite/android/demo/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..97c79f86dbedee3b71ef4b787b05352f70a428fd --- /dev/null +++ b/deploy/lite/android/demo/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + +