From dd6c2713b279fd41ca68788a7686fb245bf9c8a5 Mon Sep 17 00:00:00 2001 From: Luis Miguel Massih Pereira Date: Sun, 23 May 2021 01:00:32 -0300 Subject: [PATCH 1/4] updated eexample to flutter 2 and new lib versions --- example/lib/main.dart | 46 ++++++++++++++++++----------------- example/pubspec.yaml | 6 ++--- example/test/widget_test.dart | 2 +- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 726cde9..8a626d0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -32,23 +32,24 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - File _image; - List _recognitions; + File? _image; + List? _recognitions; String _model = mobile; - double _imageHeight; - double _imageWidth; + double? _imageHeight; + double? _imageWidth; bool _busy = false; Future predictImagePicker() async { - var image = await ImagePicker.pickImage(source: ImageSource.gallery); + var image = await ImagePicker().getImage(source: ImageSource.gallery); if (image == null) return; setState(() { _busy = true; }); - predictImage(image); + var file = File(image.path); + predictImage(file); } - Future predictImage(File image) async { + Future predictImage(File? image) async { if (image == null) return; switch (_model) { @@ -100,7 +101,7 @@ class _MyAppState extends State { Future loadModel() async { Tflite.close(); try { - String res; + String? res; switch (_model) { case yolo: res = await Tflite.loadModel( @@ -183,7 +184,7 @@ class _MyAppState extends State { imageStd: 127.5, ); setState(() { - _recognitions = recognitions; + _recognitions = recognitions!; }); int endTime = new DateTime.now().millisecondsSinceEpoch; print("Inference took ${endTime - startTime}ms"); @@ -200,7 +201,7 @@ class _MyAppState extends State { threshold: 0.05, ); setState(() { - _recognitions = recognitions; + _recognitions = recognitions!; }); int endTime = new DateTime.now().millisecondsSinceEpoch; print("Inference took ${endTime - startTime}ms"); @@ -226,7 +227,7 @@ class _MyAppState extends State { // numResultsPerClass: 1, // ); setState(() { - _recognitions = recognitions; + _recognitions = recognitions!; }); int endTime = new DateTime.now().millisecondsSinceEpoch; print("Inference took ${endTime - startTime}ms"); @@ -246,7 +247,7 @@ class _MyAppState extends State { // numResultsPerClass: 1, // ); setState(() { - _recognitions = recognitions; + _recognitions = recognitions!; }); int endTime = new DateTime.now().millisecondsSinceEpoch; print("Inference took ${endTime - startTime}ms"); @@ -261,7 +262,7 @@ class _MyAppState extends State { ); setState(() { - _recognitions = recognitions; + _recognitions = recognitions!; }); int endTime = new DateTime.now().millisecondsSinceEpoch; print("Inference took ${endTime - startTime}"); @@ -277,7 +278,7 @@ class _MyAppState extends State { print(recognitions); setState(() { - _recognitions = recognitions; + _recognitions = recognitions!; }); int endTime = new DateTime.now().millisecondsSinceEpoch; print("Inference took ${endTime - startTime}ms"); @@ -304,9 +305,9 @@ class _MyAppState extends State { if (_imageHeight == null || _imageWidth == null) return []; double factorX = screen.width; - double factorY = _imageHeight / _imageWidth * screen.width; + double factorY = _imageHeight! / _imageWidth! * screen.width; Color blue = Color.fromRGBO(37, 213, 253, 1.0); - return _recognitions.map((re) { + return _recognitions!.map((re) { return Positioned( left: re["rect"]["x"] * factorX, top: re["rect"]["y"] * factorY, @@ -338,10 +339,10 @@ class _MyAppState extends State { if (_imageHeight == null || _imageWidth == null) return []; double factorX = screen.width; - double factorY = _imageHeight / _imageWidth * screen.width; + double factorY = _imageHeight! / _imageWidth! * screen.width; var lists = []; - _recognitions.forEach((re) { + _recognitions!.forEach((re) { var color = Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0) .withOpacity(1.0); var list = re["keypoints"].values.map((k) { @@ -382,16 +383,17 @@ class _MyAppState extends State { decoration: BoxDecoration( image: DecorationImage( alignment: Alignment.topCenter, - image: MemoryImage(_recognitions), + image: MemoryImage(_recognitions!.first), fit: BoxFit.fill)), - child: Opacity(opacity: 0.3, child: Image.file(_image))), + child: Opacity(opacity: 0.3, child: Image.file(_image!))), )); } else { stackChildren.add(Positioned( top: 0.0, left: 0.0, width: size.width, - child: _image == null ? Text('No image selected.') : Image.file(_image), + child: + _image == null ? Text('No image selected.') : Image.file(_image!), )); } @@ -399,7 +401,7 @@ class _MyAppState extends State { stackChildren.add(Center( child: Column( children: _recognitions != null - ? _recognitions.map((res) { + ? _recognitions!.map((res) { return Text( "${res["index"]} - ${res["label"]}: ${res["confidence"].toStringAsFixed(3)}", style: TextStyle( diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ceb496c..a9eab93 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -10,7 +10,7 @@ description: Demonstrates how to use the tflite plugin. version: 1.0.0+1 environment: - sdk: ">=2.0.0-dev.68.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: @@ -24,9 +24,9 @@ dev_dependencies: flutter_test: sdk: flutter - image_picker: ^0.6.7 + image_picker: ^0.7.5+2 - image: ^2.1.4 + image: ^3.0.2 tflite: path: ../ diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 00a807f..a921c2e 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -18,7 +18,7 @@ void main() { expect( find.byWidgetPredicate( (Widget widget) => - widget is Text && widget.data.startsWith('Running on:'), + widget is Text && widget.data!.startsWith('Running on:'), ), findsOneWidget); }); From 011f35771e9bdc900a366b4ca4d1976047292062 Mon Sep 17 00:00:00 2001 From: Puspharaj Date: Thu, 4 Nov 2021 09:08:29 +0530 Subject: [PATCH 2/4] Add files via upload --- pubspec.yaml | 134 +++++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2cda2a4..eb52b2c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,64 +1,70 @@ -name: tflite -description: A Flutter plugin for accessing TensorFlow Lite. Supports both iOS and Android. -version: 1.1.2 -homepage: https://github.com/shaqian/flutter_tflite - -environment: - sdk: '>=2.12.0 <3.0.0' - flutter: ">=1.10.0" - -dependencies: - flutter: - sdk: flutter - - meta: ^1.3.0 - -dev_dependencies: - flutter_test: - sdk: flutter - test: ^1.16.5 - -# For information on the generic Dart part of this file, see the -# following page: https://www.dartlang.org/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - plugin: - platforms: - android: - package: sq.flutter.tflite - pluginClass: TflitePlugin - ios: - pluginClass: TflitePlugin - - - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.io/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.io/assets-and-images/#resolution-aware. - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.io/custom-fonts/#from-packages +name: tflite +description: A Flutter plugin for accessing TensorFlow Lite. Supports both iOS and Android. +version: 1.1.3 +homepage: https://github.com/shaqian/flutter_tflite + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: sq.flutter.tflite + pluginClass: TflitePlugin + ios: + pluginClass: TflitePlugin + web: + pluginClass: TfliteWeb + fileName: tflite_web.dart + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages From 7c6e58552d2a92a699ca181e593a137148a3bba6 Mon Sep 17 00:00:00 2001 From: puspharaj Date: Thu, 4 Nov 2021 09:33:33 +0530 Subject: [PATCH 3/4] Android V2 embedding chages done --- .history/analysis_options_20211031112442.yaml | 43 + .history/analysis_options_20211104093118.yaml | 43 + .history/pubspec_20211104092600.yaml | 70 + .history/pubspec_20211104092801.yaml | 70 + .history/pubspec_20211104092836.yaml | 70 + analysis_options.yaml | 43 + android/.classpath | 6 - android/.idea/.gitignore | 3 + android/.idea/.name | 1 + android/.idea/compiler.xml | 6 + android/.idea/gradle.xml | 19 + android/.idea/jarRepositories.xml | 25 + android/.idea/misc.xml | 3 + android/.idea/modules.xml | 8 + android/.project | 21 +- .../org.eclipse.buildship.core.prefs | 13 +- android/build.gradle | 26 +- android/gradle.properties | 1 - .../gradle/wrapper/gradle-wrapper.properties | 5 + .../java/sq/flutter/tflite/TflitePlugin.java | 2752 +++++++++-------- example/.gitignore | 53 +- example/.metadata | 6 +- example/README.md | 37 +- example/analysis_options.yaml | 29 + example/android/.gitignore | 19 +- example/android/.project | 15 +- .../org.eclipse.buildship.core.prefs | 11 + example/android/app/.classpath | 6 - example/android/app/.project | 23 - .../org.eclipse.buildship.core.prefs | 2 - example/android/app/build.gradle | 24 +- .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 38 +- .../flutter/tflite_example/MainActivity.java | 6 + .../flutter/tfliteexample/MainActivity.java | 13 - .../res/drawable-v21/launch_background.xml | 12 + .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 12 +- .../app/src/profile/AndroidManifest.xml | 7 + example/android/build.gradle | 8 +- example/android/gradle.properties | 1 - .../gradle/wrapper/gradle-wrapper.properties | 4 +- example/android/settings.gradle | 18 +- example/android/settings_aar.gradle | 1 - example/ios/.gitignore | 66 +- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Flutter/Debug.xcconfig | 1 - example/ios/Flutter/Release.xcconfig | 1 - example/ios/Podfile | 38 - example/ios/Podfile.lock | 35 - example/ios/Runner.xcodeproj/project.pbxproj | 169 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 12 +- .../contents.xcworkspacedata | 3 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 + example/ios/Runner/AppDelegate.m | 4 +- .../Icon-App-1024x1024@1x.png | Bin 11112 -> 10932 bytes example/ios/Runner/Info.plist | 8 +- example/lib/main.dart | 100 +- example/pubspec.yaml | 65 +- example/test/widget_test.dart | 21 +- example/tflite_example_android.iml | 27 - example/web/favicon.png | Bin 0 -> 917 bytes example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes example/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes example/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes example/web/index.html | 101 + example/web/manifest.json | 35 + example/yolo.jpg | Bin 71470 -> 0 bytes lib/tflite.dart | 131 +- lib/tflite_web.dart | 44 + pubspec.yaml | 2 +- 74 files changed, 2527 insertions(+), 1960 deletions(-) create mode 100644 .history/analysis_options_20211031112442.yaml create mode 100644 .history/analysis_options_20211104093118.yaml create mode 100644 .history/pubspec_20211104092600.yaml create mode 100644 .history/pubspec_20211104092801.yaml create mode 100644 .history/pubspec_20211104092836.yaml create mode 100644 analysis_options.yaml delete mode 100644 android/.classpath create mode 100644 android/.idea/.gitignore create mode 100644 android/.idea/.name create mode 100644 android/.idea/compiler.xml create mode 100644 android/.idea/gradle.xml create mode 100644 android/.idea/jarRepositories.xml create mode 100644 android/.idea/modules.xml create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/analysis_options.yaml delete mode 100644 example/android/app/.classpath delete mode 100644 example/android/app/.project delete mode 100644 example/android/app/.settings/org.eclipse.buildship.core.prefs create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/java/sq/flutter/tflite_example/MainActivity.java delete mode 100644 example/android/app/src/main/java/sq/flutter/tfliteexample/MainActivity.java create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml delete mode 100644 example/android/settings_aar.gradle delete mode 100644 example/ios/Podfile delete mode 100644 example/ios/Podfile.lock create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 example/tflite_example_android.iml create mode 100644 example/web/favicon.png create mode 100644 example/web/icons/Icon-192.png create mode 100644 example/web/icons/Icon-512.png create mode 100644 example/web/icons/Icon-maskable-192.png create mode 100644 example/web/icons/Icon-maskable-512.png create mode 100644 example/web/index.html create mode 100644 example/web/manifest.json delete mode 100644 example/yolo.jpg create mode 100644 lib/tflite_web.dart diff --git a/.history/analysis_options_20211031112442.yaml b/.history/analysis_options_20211031112442.yaml new file mode 100644 index 0000000..c3323b8 --- /dev/null +++ b/.history/analysis_options_20211031112442.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + todo: ignore + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + - always_use_package_imports + - always_declare_return_types + - cancel_subscriptions + - close_sinks + - comment_references + - one_member_abstracts + - only_throw_errors + - package_api_docs + - prefer_final_in_for_each + - prefer_single_quotes + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/.history/analysis_options_20211104093118.yaml b/.history/analysis_options_20211104093118.yaml new file mode 100644 index 0000000..013b99c --- /dev/null +++ b/.history/analysis_options_20211104093118.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + todo: ignore + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + #- always_use_package_imports + - always_declare_return_types + - cancel_subscriptions + - close_sinks + - comment_references + - one_member_abstracts + - only_throw_errors + - package_api_docs + - prefer_final_in_for_each + - prefer_single_quotes + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/.history/pubspec_20211104092600.yaml b/.history/pubspec_20211104092600.yaml new file mode 100644 index 0000000..538653d --- /dev/null +++ b/.history/pubspec_20211104092600.yaml @@ -0,0 +1,70 @@ +name: tflite +description: A Flutter plugin for accessing TensorFlow Lite. Supports both iOS and Android. +version: 1.1.3 +homepage: https://github.com/shaqian/flutter_tflite + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: sq.flutter.tflite + pluginClass: TflitePlugin + ios: + pluginClass: TflitePlugin + web: + pluginClass: TfliteWeb + fileName: tflite_web.dart + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/.history/pubspec_20211104092801.yaml b/.history/pubspec_20211104092801.yaml new file mode 100644 index 0000000..538653d --- /dev/null +++ b/.history/pubspec_20211104092801.yaml @@ -0,0 +1,70 @@ +name: tflite +description: A Flutter plugin for accessing TensorFlow Lite. Supports both iOS and Android. +version: 1.1.3 +homepage: https://github.com/shaqian/flutter_tflite + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: sq.flutter.tflite + pluginClass: TflitePlugin + ios: + pluginClass: TflitePlugin + web: + pluginClass: TfliteWeb + fileName: tflite_web.dart + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/.history/pubspec_20211104092836.yaml b/.history/pubspec_20211104092836.yaml new file mode 100644 index 0000000..b801677 --- /dev/null +++ b/.history/pubspec_20211104092836.yaml @@ -0,0 +1,70 @@ +name: tflite +description: A Flutter plugin for accessing TensorFlow Lite. Supports both iOS and Android, Web under development. +version: 1.1.3 +homepage: https://github.com/shaqian/flutter_tflite + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: sq.flutter.tflite + pluginClass: TflitePlugin + ios: + pluginClass: TflitePlugin + web: + pluginClass: TfliteWeb + fileName: tflite_web.dart + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..013b99c --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,43 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + todo: ignore + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + #- always_use_package_imports + - always_declare_return_types + - cancel_subscriptions + - close_sinks + - comment_references + - one_member_abstracts + - only_throw_errors + - package_api_docs + - prefer_final_in_for_each + - prefer_single_quotes + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.classpath b/android/.classpath deleted file mode 100644 index eb19361..0000000 --- a/android/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/android/.idea/.gitignore b/android/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/android/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/android/.idea/.name b/android/.idea/.name new file mode 100644 index 0000000..f62d0c7 --- /dev/null +++ b/android/.idea/.name @@ -0,0 +1 @@ +tflite \ No newline at end of file diff --git a/android/.idea/compiler.xml b/android/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/android/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml new file mode 100644 index 0000000..b220fcc --- /dev/null +++ b/android/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/android/.idea/jarRepositories.xml b/android/.idea/jarRepositories.xml new file mode 100644 index 0000000..d2ce72d --- /dev/null +++ b/android/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index 9eeb5ee..860da66 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,5 +1,8 @@ + + + diff --git a/android/.idea/modules.xml b/android/.idea/modules.xml new file mode 100644 index 0000000..6e1e501 --- /dev/null +++ b/android/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/android/.project b/android/.project index 5fb2462..e28190d 100644 --- a/android/.project +++ b/android/.project @@ -1,15 +1,10 @@ - tflite - Project tflite created by Buildship. + android + Project android created by Buildship. - - org.eclipse.jdt.core.javabuilder - - - org.eclipse.buildship.core.gradleprojectbuilder @@ -17,7 +12,17 @@ - org.eclipse.jdt.core.javanature org.eclipse.buildship.core.gradleprojectnature + + + 1635544201826 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs index 6aa97a9..6606b3d 100644 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,13 @@ -connection.project.dir=../example/android +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.0-rc-1)) +connection.project.dir= eclipse.preferences.version=1 +gradle.user.home= +java.home=C\:/Program Files/Eclipse Foundation/jdk-17.0.0.35-hotspot +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/android/build.gradle b/android/build.gradle index 8002459..fc6bdcb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,39 +1,39 @@ group 'sq.flutter.tflite' -version '1.0-SNAPSHOT' +version '1.0' buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.1.0' } } rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } apply plugin: 'com.android.library' android { - compileSdkVersion 28 + compileSdkVersion 31 - defaultConfig { - minSdkVersion 19 - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' - } - lintOptions { - disable 'InvalidPackage' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } + defaultConfig { + minSdkVersion 17 + } dependencies { - compile 'org.tensorflow:tensorflow-lite:+' - compile 'org.tensorflow:tensorflow-lite-gpu:+' + implementation 'org.tensorflow:tensorflow-lite:+' + implementation 'org.tensorflow:tensorflow-lite-gpu:+' } } diff --git a/android/gradle.properties b/android/gradle.properties index 4167249..94adc3a 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,3 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -android.enableR8=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c9d085 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/android/src/main/java/sq/flutter/tflite/TflitePlugin.java b/android/src/main/java/sq/flutter/tflite/TflitePlugin.java index 2579d35..84bffc6 100644 --- a/android/src/main/java/sq/flutter/tflite/TflitePlugin.java +++ b/android/src/main/java/sq/flutter/tflite/TflitePlugin.java @@ -1,5 +1,6 @@ package sq.flutter.tflite; +import android.app.Activity; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; @@ -19,22 +20,15 @@ import android.renderscript.Type; import android.util.Log; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; -import org.tensorflow.lite.DataType; -import org.tensorflow.lite.Interpreter; -import org.tensorflow.lite.Tensor; +import androidx.annotation.NonNull; +import org.tensorflow.lite.*; import org.tensorflow.lite.gpu.GpuDelegate; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -51,1536 +45,1608 @@ import java.util.PriorityQueue; import java.util.Vector; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry; -public class TflitePlugin implements MethodCallHandler { - private final Registrar mRegistrar; - private Interpreter tfLite; - private boolean tfLiteBusy = false; - private int inputSize = 0; - private Vector labels; - float[][] labelProb; - private static final int BYTES_PER_CHANNEL = 4; - - String[] partNames = { - "nose", "leftEye", "rightEye", "leftEar", "rightEar", "leftShoulder", - "rightShoulder", "leftElbow", "rightElbow", "leftWrist", "rightWrist", - "leftHip", "rightHip", "leftKnee", "rightKnee", "leftAnkle", "rightAnkle" - }; - - String[][] poseChain = { - {"nose", "leftEye"}, {"leftEye", "leftEar"}, {"nose", "rightEye"}, - {"rightEye", "rightEar"}, {"nose", "leftShoulder"}, - {"leftShoulder", "leftElbow"}, {"leftElbow", "leftWrist"}, - {"leftShoulder", "leftHip"}, {"leftHip", "leftKnee"}, - {"leftKnee", "leftAnkle"}, {"nose", "rightShoulder"}, - {"rightShoulder", "rightElbow"}, {"rightElbow", "rightWrist"}, - {"rightShoulder", "rightHip"}, {"rightHip", "rightKnee"}, - {"rightKnee", "rightAnkle"} - }; - - Map partsIds = new HashMap<>(); - List parentToChildEdges = new ArrayList<>(); - List childToParentEdges = new ArrayList<>(); - - public static void registerWith(Registrar registrar) { - final MethodChannel channel = new MethodChannel(registrar.messenger(), "tflite"); - channel.setMethodCallHandler(new TflitePlugin(registrar)); - } - - private TflitePlugin(Registrar registrar) { - this.mRegistrar = registrar; - } - @Override - public void onMethodCall(MethodCall call, Result result) { - if (call.method.equals("loadModel")) { - try { - String res = loadModel((HashMap) call.arguments); - result.success(res); - } catch (Exception e) { - result.error("Failed to load model", e.getMessage(), e); - } - } else if (call.method.equals("runModelOnImage")) { - try { - new RunModelOnImage((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runModelOnBinary")) { - try { - new RunModelOnBinary((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runModelOnFrame")) { - try { - new RunModelOnFrame((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("detectObjectOnImage")) { - try { - detectObjectOnImage((HashMap) call.arguments, result); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("detectObjectOnBinary")) { - try { - detectObjectOnBinary((HashMap) call.arguments, result); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("detectObjectOnFrame")) { - try { - detectObjectOnFrame((HashMap) call.arguments, result); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("close")) { - close(); - } else if (call.method.equals("runPix2PixOnImage")) { - try { - new RunPix2PixOnImage((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runPix2PixOnBinary")) { - try { - new RunPix2PixOnBinary((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runPix2PixOnFrame")) { - try { - new RunPix2PixOnFrame((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runSegmentationOnImage")) { - try { - new RunSegmentationOnImage((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runSegmentationOnBinary")) { - try { - new RunSegmentationOnBinary((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runSegmentationOnFrame")) { - try { - new RunSegmentationOnFrame((HashMap) call.arguments, result).executeTfliteTask(); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runPoseNetOnImage")) { - try { - runPoseNetOnImage((HashMap) call.arguments, result); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runPoseNetOnBinary")) { - try { - runPoseNetOnBinary((HashMap) call.arguments, result); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else if (call.method.equals("runPoseNetOnFrame")) { - try { - runPoseNetOnFrame((HashMap) call.arguments, result); - } catch (Exception e) { - result.error("Failed to run model", e.getMessage(), e); - } - } else { - result.error("Invalid method", call.method.toString(), ""); +/** + * TflitePlugin + */ +public class TflitePlugin implements FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private MethodChannel channel; + private Context applicationContext; + @SuppressWarnings("deprecation") + private PluginRegistry.Registrar registrar; + /** + * Plugin registration. + */ + @SuppressWarnings("deprecation") + public void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { + final TflitePlugin instance = new TflitePlugin(); + this.registrar = registrar; + instance.onAttachedToEngine(registrar.activity(), registrar.messenger()); } - } - - private String loadModel(HashMap args) throws IOException { - String model = args.get("model").toString(); - Object isAssetObj = args.get("isAsset"); - boolean isAsset = isAssetObj == null ? false : (boolean) isAssetObj; - MappedByteBuffer buffer = null; - String key = null; - AssetManager assetManager = null; - if (isAsset) { - assetManager = mRegistrar.context().getAssets(); - key = mRegistrar.lookupKeyForAsset(model); - AssetFileDescriptor fileDescriptor = assetManager.openFd(key); - FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor()); - FileChannel fileChannel = inputStream.getChannel(); - long startOffset = fileDescriptor.getStartOffset(); - long declaredLength = fileDescriptor.getDeclaredLength(); - buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength); - } else { - FileInputStream inputStream = new FileInputStream(new File(model)); - FileChannel fileChannel = inputStream.getChannel(); - long declaredLength = fileChannel.size(); - buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, declaredLength); + private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { + this.applicationContext = applicationContext; + MethodChannel methodChannel = new MethodChannel(messenger, "plugins.flutter.io/tensor"); + methodChannel.setMethodCallHandler(this); } - int numThreads = (int) args.get("numThreads"); - Boolean useGpuDelegate = (Boolean) args.get("useGpuDelegate"); - if (useGpuDelegate == null) { - useGpuDelegate = false; + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + Activity activity = binding.getActivity(); } - final Interpreter.Options tfliteOptions = new Interpreter.Options(); - tfliteOptions.setNumThreads(numThreads); - if (useGpuDelegate){ - GpuDelegate delegate = new GpuDelegate(); - tfliteOptions.addDelegate(delegate); + @Override + public void onDetachedFromActivityForConfigChanges() { + } - tfLite = new Interpreter(buffer, tfliteOptions); - String labels = args.get("labels").toString(); + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - if (labels.length() > 0) { - if (isAsset) { - key = mRegistrar.lookupKeyForAsset(labels); - loadLabels(assetManager, key); - } else { - loadLabels(null, labels); - } } - return "success"; - } + @Override + public void onDetachedFromActivity() { - private void loadLabels(AssetManager assetManager, String path) { - BufferedReader br; - try { - if (assetManager != null) { - br = new BufferedReader(new InputStreamReader(assetManager.open(path))); - } else { - br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path)))); - } - String line; - labels = new Vector<>(); - while ((line = br.readLine()) != null) { - labels.add(line); - } - labelProb = new float[1][labels.size()]; - br.close(); - } catch (IOException e) { - throw new RuntimeException("Failed to read label file", e); } - } - private List> GetTopN(int numResults, float threshold) { - PriorityQueue> pq = - new PriorityQueue<>( - 1, - new Comparator>() { - @Override - public int compare(Map lhs, Map rhs) { - return Float.compare((float) rhs.get("confidence"), (float) lhs.get("confidence")); - } - }); - - for (int i = 0; i < labels.size(); ++i) { - float confidence = labelProb[0][i]; - if (confidence > threshold) { - Map res = new HashMap<>(); - res.put("index", i); - res.put("label", labels.size() > i ? labels.get(i) : "unknown"); - res.put("confidence", confidence); - pq.add(res); - } + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "tflite"); + channel.setMethodCallHandler(this); + Context context = flutterPluginBinding.getApplicationContext(); } - final ArrayList> recognitions = new ArrayList<>(); - int recognitionsSize = Math.min(pq.size(), numResults); - for (int i = 0; i < recognitionsSize; ++i) { - recognitions.add(pq.poll()); + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + if (call.method.equals("getPlatformVersion")) { + result.success("Android " + android.os.Build.VERSION.RELEASE); + } else { + result.notImplemented(); + } + if (call.method.equals("loadModel")) { + try { + String res = loadModel((HashMap) call.arguments); + result.success(res); + } catch (Exception e) { + result.error("Failed to load model", e.getMessage(), e); + } + } else if (call.method.equals("runModelOnImage")) { + try { + new RunModelOnImage((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runModelOnBinary")) { + try { + new RunModelOnBinary((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runModelOnFrame")) { + try { + new RunModelOnFrame((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("detectObjectOnImage")) { + try { + detectObjectOnImage((HashMap) call.arguments, result); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("detectObjectOnBinary")) { + try { + detectObjectOnBinary((HashMap) call.arguments, result); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("detectObjectOnFrame")) { + try { + detectObjectOnFrame((HashMap) call.arguments, result); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("close")) { + close(); + } else if (call.method.equals("runPix2PixOnImage")) { + try { + new RunPix2PixOnImage((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runPix2PixOnBinary")) { + try { + new RunPix2PixOnBinary((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runPix2PixOnFrame")) { + try { + new RunPix2PixOnFrame((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runSegmentationOnImage")) { + try { + new RunSegmentationOnImage((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runSegmentationOnBinary")) { + try { + new RunSegmentationOnBinary((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runSegmentationOnFrame")) { + try { + new RunSegmentationOnFrame((HashMap) call.arguments, result).executeTfliteTask(); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runPoseNetOnImage")) { + try { + runPoseNetOnImage((HashMap) call.arguments, result); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runPoseNetOnBinary")) { + try { + runPoseNetOnBinary((HashMap) call.arguments, result); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else if (call.method.equals("runPoseNetOnFrame")) { + try { + runPoseNetOnFrame((HashMap) call.arguments, result); + } catch (Exception e) { + result.error("Failed to run model", e.getMessage(), e); + } + } else { + result.error("Invalid method", call.method.toString(), ""); + } } - return recognitions; + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + } + + private Interpreter tfLite; + private boolean tfLiteBusy = false; + private int inputSize = 0; + private Vector labels; + float[][] labelProb; + private static final int BYTES_PER_CHANNEL = 4; + + String[] partNames = { + "nose", "leftEye", "rightEye", "leftEar", "rightEar", "leftShoulder", + "rightShoulder", "leftElbow", "rightElbow", "leftWrist", "rightWrist", + "leftHip", "rightHip", "leftKnee", "rightKnee", "leftAnkle", "rightAnkle" + }; + + String[][] poseChain = { + {"nose", "leftEye"}, {"leftEye", "leftEar"}, {"nose", "rightEye"}, + {"rightEye", "rightEar"}, {"nose", "leftShoulder"}, + {"leftShoulder", "leftElbow"}, {"leftElbow", "leftWrist"}, + {"leftShoulder", "leftHip"}, {"leftHip", "leftKnee"}, + {"leftKnee", "leftAnkle"}, {"nose", "rightShoulder"}, + {"rightShoulder", "rightElbow"}, {"rightElbow", "rightWrist"}, + {"rightShoulder", "rightHip"}, {"rightHip", "rightKnee"}, + {"rightKnee", "rightAnkle"} + }; + + + Map partsIds = new HashMap<>(); + List parentToChildEdges = new ArrayList<>(); + List childToParentEdges = new ArrayList<>(); + + /*private TflitePlugin(Registrar registrar) { + this.mRegistrar = registrar; } +*/ + + + private String loadModel(HashMap args) throws IOException { + String model = args.get("model").toString(); + Object isAssetObj = args.get("isAsset"); + boolean isAsset = isAssetObj == null ? false : (boolean) isAssetObj; + MappedByteBuffer buffer = null; + String key = null; + AssetManager assetManager = registrar.context().getAssets(); + if (isAsset) { + assetManager = applicationContext.getAssets(); + key = registrar.lookupKeyForAsset(model); + AssetFileDescriptor fileDescriptor = assetManager.openFd(key); + FileInputStream inputStream = new FileInputStream(fileDescriptor.getFileDescriptor()); + FileChannel fileChannel = inputStream.getChannel(); + long startOffset = fileDescriptor.getStartOffset(); + long declaredLength = fileDescriptor.getDeclaredLength(); + buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength); + } else { + FileInputStream inputStream = new FileInputStream(new File(model)); + FileChannel fileChannel = inputStream.getChannel(); + long declaredLength = fileChannel.size(); + buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, declaredLength); + } - Bitmap feedOutput(ByteBuffer imgData, float mean, float std) { - Tensor tensor = tfLite.getOutputTensor(0); - int outputSize = tensor.shape()[1]; - Bitmap bitmapRaw = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888); - - if (tensor.dataType() == DataType.FLOAT32) { - for (int i = 0; i < outputSize; ++i) { - for (int j = 0; j < outputSize; ++j) { - int pixelValue = 0xFF << 24; - pixelValue |= ((Math.round(imgData.getFloat() * std + mean) & 0xFF) << 16); - pixelValue |= ((Math.round(imgData.getFloat() * std + mean) & 0xFF) << 8); - pixelValue |= ((Math.round(imgData.getFloat() * std + mean) & 0xFF)); - bitmapRaw.setPixel(j, i, pixelValue); - } - } - } else { - for (int i = 0; i < outputSize; ++i) { - for (int j = 0; j < outputSize; ++j) { - int pixelValue = 0xFF << 24; - pixelValue |= ((imgData.get() & 0xFF) << 16); - pixelValue |= ((imgData.get() & 0xFF) << 8); - pixelValue |= ((imgData.get() & 0xFF)); - bitmapRaw.setPixel(j, i, pixelValue); - } - } + int numThreads = (int) args.get("numThreads"); + Boolean useGpuDelegate = (Boolean) args.get("useGpuDelegate"); + if (useGpuDelegate == null) { + useGpuDelegate = false; + } + + final Interpreter.Options tfliteOptions = new Interpreter.Options(); + tfliteOptions.setNumThreads(numThreads); + if (useGpuDelegate) { + GpuDelegate delegate = new GpuDelegate(); + tfliteOptions.addDelegate(delegate); + } + tfLite = new Interpreter(buffer, tfliteOptions); + + String labels = args.get("labels").toString(); + + if (labels.length() > 0) { + if (isAsset) { + key = registrar.lookupKeyForAsset(labels); + loadLabels(assetManager, key); + } else { + loadLabels(null, labels); + } + } + + return "success"; } - return bitmapRaw; - } - ByteBuffer feedInputTensor(Bitmap bitmapRaw, float mean, float std) throws IOException { - Tensor tensor = tfLite.getInputTensor(0); - int[] shape = tensor.shape(); - inputSize = shape[1]; - int inputChannels = shape[3]; - - int bytePerChannel = tensor.dataType() == DataType.UINT8 ? 1 : BYTES_PER_CHANNEL; - ByteBuffer imgData = ByteBuffer.allocateDirect(1 * inputSize * inputSize * inputChannels * bytePerChannel); - imgData.order(ByteOrder.nativeOrder()); - - Bitmap bitmap = bitmapRaw; - if (bitmapRaw.getWidth() != inputSize || bitmapRaw.getHeight() != inputSize) { - Matrix matrix = getTransformationMatrix(bitmapRaw.getWidth(), bitmapRaw.getHeight(), - inputSize, inputSize, false); - bitmap = Bitmap.createBitmap(inputSize, inputSize, Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - if (inputChannels == 1){ - Paint paint = new Paint(); - ColorMatrix cm = new ColorMatrix(); - cm.setSaturation(0); - ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm); - paint.setColorFilter(f); - canvas.drawBitmap(bitmapRaw, matrix, paint); - } else { - canvas.drawBitmap(bitmapRaw, matrix, null); - } + private void loadLabels(AssetManager assetManager, String path) { + BufferedReader br; + try { + if (assetManager != null) { + br = new BufferedReader(new InputStreamReader(assetManager.open(path))); + } else { + br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path)))); + } + String line; + labels = new Vector<>(); + while ((line = br.readLine()) != null) { + labels.add(line); + } + labelProb = new float[1][labels.size()]; + br.close(); + } catch (IOException e) { + throw new RuntimeException("Failed to read label file", e); + } } - if (tensor.dataType() == DataType.FLOAT32) { - for (int i = 0; i < inputSize; ++i) { - for (int j = 0; j < inputSize; ++j) { - int pixelValue = bitmap.getPixel(j, i); - if (inputChannels > 1){ - imgData.putFloat((((pixelValue >> 16) & 0xFF) - mean) / std); - imgData.putFloat((((pixelValue >> 8) & 0xFF) - mean) / std); - imgData.putFloat(((pixelValue & 0xFF) - mean) / std); - } else { - imgData.putFloat((((pixelValue >> 16 | pixelValue >> 8 | pixelValue) & 0xFF) - mean) / std); - } - } - } - } else { - for (int i = 0; i < inputSize; ++i) { - for (int j = 0; j < inputSize; ++j) { - int pixelValue = bitmap.getPixel(j, i); - if (inputChannels > 1){ - imgData.put((byte) ((pixelValue >> 16) & 0xFF)); - imgData.put((byte) ((pixelValue >> 8) & 0xFF)); - imgData.put((byte) (pixelValue & 0xFF)); - } else { - imgData.put((byte) ((pixelValue >> 16 | pixelValue >> 8 | pixelValue) & 0xFF)); - } - } - } + private List> GetTopN(int numResults, float threshold) { + PriorityQueue> pq = + new PriorityQueue<>( + 1, + new Comparator>() { + @Override + public int compare(Map lhs, Map rhs) { + return Float.compare((float) rhs.get("confidence"), (float) lhs.get("confidence")); + } + }); + + for (int i = 0; i < labels.size(); ++i) { + float confidence = labelProb[0][i]; + if (confidence > threshold) { + Map res = new HashMap<>(); + res.put("index", i); + res.put("label", labels.size() > i ? labels.get(i) : "unknown"); + res.put("confidence", confidence); + pq.add(res); + } + } + + final ArrayList> recognitions = new ArrayList<>(); + int recognitionsSize = Math.min(pq.size(), numResults); + for (int i = 0; i < recognitionsSize; ++i) { + recognitions.add(pq.poll()); + } + + return recognitions; } - return imgData; - } + Bitmap feedOutput(ByteBuffer imgData, float mean, float std) { + Tensor tensor = tfLite.getOutputTensor(0); + int outputSize = tensor.shape()[1]; + Bitmap bitmapRaw = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888); + + if (tensor.dataType() == DataType.FLOAT32) { + for (int i = 0; i < outputSize; ++i) { + for (int j = 0; j < outputSize; ++j) { + int pixelValue = 0xFF << 24; + pixelValue |= ((Math.round(imgData.getFloat() * std + mean) & 0xFF) << 16); + pixelValue |= ((Math.round(imgData.getFloat() * std + mean) & 0xFF) << 8); + pixelValue |= ((Math.round(imgData.getFloat() * std + mean) & 0xFF)); + bitmapRaw.setPixel(j, i, pixelValue); + } + } + } else { + for (int i = 0; i < outputSize; ++i) { + for (int j = 0; j < outputSize; ++j) { + int pixelValue = 0xFF << 24; + pixelValue |= ((imgData.get() & 0xFF) << 16); + pixelValue |= ((imgData.get() & 0xFF) << 8); + pixelValue |= ((imgData.get() & 0xFF)); + bitmapRaw.setPixel(j, i, pixelValue); + } + } + } + return bitmapRaw; + } + + ByteBuffer feedInputTensor(Bitmap bitmapRaw, float mean, float std) throws IOException { + Tensor tensor = tfLite.getInputTensor(0); + int[] shape = tensor.shape(); + inputSize = shape[1]; + int inputChannels = shape[3]; + + int bytePerChannel = tensor.dataType() == DataType.UINT8 ? 1 : BYTES_PER_CHANNEL; + ByteBuffer imgData = ByteBuffer.allocateDirect(1 * inputSize * inputSize * inputChannels * bytePerChannel); + imgData.order(ByteOrder.nativeOrder()); + + Bitmap bitmap = bitmapRaw; + if (bitmapRaw.getWidth() != inputSize || bitmapRaw.getHeight() != inputSize) { + Matrix matrix = getTransformationMatrix(bitmapRaw.getWidth(), bitmapRaw.getHeight(), + inputSize, inputSize, false); + bitmap = Bitmap.createBitmap(inputSize, inputSize, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + if (inputChannels == 1) { + Paint paint = new Paint(); + ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0); + ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm); + paint.setColorFilter(f); + canvas.drawBitmap(bitmapRaw, matrix, paint); + } else { + canvas.drawBitmap(bitmapRaw, matrix, null); + } + } - ByteBuffer feedInputTensorImage(String path, float mean, float std) throws IOException { - InputStream inputStream = new FileInputStream(path.replace("file://", "")); - Bitmap bitmapRaw = BitmapFactory.decodeStream(inputStream); + if (tensor.dataType() == DataType.FLOAT32) { + for (int i = 0; i < inputSize; ++i) { + for (int j = 0; j < inputSize; ++j) { + int pixelValue = bitmap.getPixel(j, i); + if (inputChannels > 1) { + imgData.putFloat((((pixelValue >> 16) & 0xFF) - mean) / std); + imgData.putFloat((((pixelValue >> 8) & 0xFF) - mean) / std); + imgData.putFloat(((pixelValue & 0xFF) - mean) / std); + } else { + imgData.putFloat((((pixelValue >> 16 | pixelValue >> 8 | pixelValue) & 0xFF) - mean) / std); + } + } + } + } else { + for (int i = 0; i < inputSize; ++i) { + for (int j = 0; j < inputSize; ++j) { + int pixelValue = bitmap.getPixel(j, i); + if (inputChannels > 1) { + imgData.put((byte) ((pixelValue >> 16) & 0xFF)); + imgData.put((byte) ((pixelValue >> 8) & 0xFF)); + imgData.put((byte) (pixelValue & 0xFF)); + } else { + imgData.put((byte) ((pixelValue >> 16 | pixelValue >> 8 | pixelValue) & 0xFF)); + } + } + } + } - return feedInputTensor(bitmapRaw, mean, std); - } + return imgData; + } - ByteBuffer feedInputTensorFrame(List bytesList, int imageHeight, int imageWidth, float mean, float std, int rotation) throws IOException { - ByteBuffer Y = ByteBuffer.wrap(bytesList.get(0)); - ByteBuffer U = ByteBuffer.wrap(bytesList.get(1)); - ByteBuffer V = ByteBuffer.wrap(bytesList.get(2)); + ByteBuffer feedInputTensorImage(String path, float mean, float std) throws IOException { + InputStream inputStream = new FileInputStream(path.replace("file://", "")); + Bitmap bitmapRaw = BitmapFactory.decodeStream(inputStream); - int Yb = Y.remaining(); - int Ub = U.remaining(); - int Vb = V.remaining(); + return feedInputTensor(bitmapRaw, mean, std); + } - byte[] data = new byte[Yb + Ub + Vb]; + ByteBuffer feedInputTensorFrame(List bytesList, int imageHeight, int imageWidth, float mean, float std, int rotation) throws IOException { + ByteBuffer Y = ByteBuffer.wrap(bytesList.get(0)); + ByteBuffer U = ByteBuffer.wrap(bytesList.get(1)); + ByteBuffer V = ByteBuffer.wrap(bytesList.get(2)); - Y.get(data, 0, Yb); - V.get(data, Yb, Vb); - U.get(data, Yb + Vb, Ub); + int Yb = Y.remaining(); + int Ub = U.remaining(); + int Vb = V.remaining(); - Bitmap bitmapRaw = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888); - Allocation bmData = renderScriptNV21ToRGBA888( - mRegistrar.context(), - imageWidth, - imageHeight, - data); - bmData.copyTo(bitmapRaw); + byte[] data = new byte[Yb + Ub + Vb]; - Matrix matrix = new Matrix(); - matrix.postRotate(rotation); - bitmapRaw = Bitmap.createBitmap(bitmapRaw, 0, 0, bitmapRaw.getWidth(), bitmapRaw.getHeight(), matrix, true); + Y.get(data, 0, Yb); + V.get(data, Yb, Vb); + U.get(data, Yb + Vb, Ub); - return feedInputTensor(bitmapRaw, mean, std); - } + Bitmap bitmapRaw = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888); + Allocation bmData = renderScriptNV21ToRGBA888( + registrar.context(), + imageWidth, + imageHeight, + data); + bmData.copyTo(bitmapRaw); - public Allocation renderScriptNV21ToRGBA888(Context context, int width, int height, byte[] nv21) { - // https://stackoverflow.com/a/36409748 - RenderScript rs = RenderScript.create(context); - ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)); + Matrix matrix = new Matrix(); + matrix.postRotate(rotation); + bitmapRaw = Bitmap.createBitmap(bitmapRaw, 0, 0, bitmapRaw.getWidth(), bitmapRaw.getHeight(), matrix, true); - Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length); - Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT); + return feedInputTensor(bitmapRaw, mean, std); + } - Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height); - Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT); + public Allocation renderScriptNV21ToRGBA888(Context context, int width, int height, byte[] nv21) { + // https://stackoverflow.com/a/36409748 + RenderScript rs = RenderScript.create(context); + ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs)); - in.copyFrom(nv21); + Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(nv21.length); + Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT); - yuvToRgbIntrinsic.setInput(in); - yuvToRgbIntrinsic.forEach(out); - return out; - } + Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height); + Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT); - private abstract class TfliteTask extends AsyncTask { - Result result; - boolean asynch; + in.copyFrom(nv21); - TfliteTask(HashMap args, Result result) { - if (tfLiteBusy) throw new RuntimeException("Interpreter busy"); - else tfLiteBusy = true; - Object asynch = args.get("asynch"); - this.asynch = asynch == null ? false : (boolean) asynch; - this.result = result; + yuvToRgbIntrinsic.setInput(in); + yuvToRgbIntrinsic.forEach(out); + return out; } - abstract void runTflite(); - abstract void onRunTfliteDone(); + private abstract class TfliteTask extends AsyncTask { + Result result; + boolean asynch; - public void executeTfliteTask() { - if (asynch) execute(); - else { - runTflite(); - tfLiteBusy = false; - onRunTfliteDone(); - } - } + TfliteTask(HashMap args, Result result) { + if (tfLiteBusy) throw new RuntimeException("Interpreter busy"); + else tfLiteBusy = true; + Object asynch = args.get("asynch"); + this.asynch = asynch == null ? false : (boolean) asynch; + this.result = result; + } - protected Void doInBackground(Void... backgroundArguments) { - runTflite(); - return null; - } + abstract void runTflite(); - protected void onPostExecute(Void backgroundResult) { - tfLiteBusy = false; - onRunTfliteDone(); - } - } + abstract void onRunTfliteDone(); - private class RunModelOnImage extends TfliteTask { - int NUM_RESULTS; - float THRESHOLD; - ByteBuffer input; - long startTime; - - RunModelOnImage(HashMap args, Result result) throws IOException { - super(args, result); - - String path = args.get("path").toString(); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; - NUM_RESULTS = (int) args.get("numResults"); - double threshold = (double) args.get("threshold"); - THRESHOLD = (float) threshold; - - startTime = SystemClock.uptimeMillis(); - input = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); - } + public void executeTfliteTask() { + if (asynch) execute(); + else { + runTflite(); + tfLiteBusy = false; + onRunTfliteDone(); + } + } - protected void runTflite() { - tfLite.run(input, labelProb); - } + protected Void doInBackground(Void... backgroundArguments) { + runTflite(); + return null; + } - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); - result.success(GetTopN(NUM_RESULTS, THRESHOLD)); + protected void onPostExecute(Void backgroundResult) { + tfLiteBusy = false; + onRunTfliteDone(); + } } - } - private class RunModelOnBinary extends TfliteTask { - int NUM_RESULTS; - float THRESHOLD; - ByteBuffer imgData; + private class RunModelOnImage extends TfliteTask { + int NUM_RESULTS; + float THRESHOLD; + ByteBuffer input; + long startTime; - RunModelOnBinary(HashMap args, Result result) throws IOException { - super(args, result); + RunModelOnImage(HashMap args, Result result) throws IOException { + super(args, result); - byte[] binary = (byte[]) args.get("binary"); - NUM_RESULTS = (int) args.get("numResults"); - double threshold = (double) args.get("threshold"); - THRESHOLD = (float) threshold; + String path = args.get("path").toString(); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; + NUM_RESULTS = (int) args.get("numResults"); + double threshold = (double) args.get("threshold"); + THRESHOLD = (float) threshold; - imgData = ByteBuffer.wrap(binary); - } + startTime = SystemClock.uptimeMillis(); + input = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); + } - protected void runTflite() { - tfLite.run(imgData, labelProb); - } + protected void runTflite() { + tfLite.run(input, labelProb); + } - protected void onRunTfliteDone() { - result.success(GetTopN(NUM_RESULTS, THRESHOLD)); + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + result.success(GetTopN(NUM_RESULTS, THRESHOLD)); + } } - } - private class RunModelOnFrame extends TfliteTask { - int NUM_RESULTS; - float THRESHOLD; - long startTime; - ByteBuffer imgData; - - RunModelOnFrame(HashMap args, Result result) throws IOException { - super(args, result); - - List bytesList = (ArrayList) args.get("bytesList"); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; - int imageHeight = (int) (args.get("imageHeight")); - int imageWidth = (int) (args.get("imageWidth")); - int rotation = (int) (args.get("rotation")); - NUM_RESULTS = (int) args.get("numResults"); - double threshold = (double) args.get("threshold"); - THRESHOLD = (float) threshold; - - startTime = SystemClock.uptimeMillis(); - - imgData = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); - } + private class RunModelOnBinary extends TfliteTask { + int NUM_RESULTS; + float THRESHOLD; + ByteBuffer imgData; - protected void runTflite() { - tfLite.run(imgData, labelProb); - } + RunModelOnBinary(HashMap args, Result result) throws IOException { + super(args, result); - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); - result.success(GetTopN(NUM_RESULTS, THRESHOLD)); - } - } + byte[] binary = (byte[]) args.get("binary"); + NUM_RESULTS = (int) args.get("numResults"); + double threshold = (double) args.get("threshold"); + THRESHOLD = (float) threshold; - void detectObjectOnImage(HashMap args, Result result) throws IOException { - String path = args.get("path").toString(); - String model = args.get("model").toString(); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; - double threshold = (double) args.get("threshold"); - float THRESHOLD = (float) threshold; - List ANCHORS = (ArrayList) args.get("anchors"); - int BLOCK_SIZE = (int) args.get("blockSize"); - int NUM_BOXES_PER_BLOCK = (int) args.get("numBoxesPerBlock"); - int NUM_RESULTS_PER_CLASS = (int) args.get("numResultsPerClass"); - - ByteBuffer imgData = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); - - if (model.equals("SSDMobileNet")) { - new RunSSDMobileNet(args, imgData, NUM_RESULTS_PER_CLASS, THRESHOLD, result).executeTfliteTask(); - } else { - new RunYOLO(args, imgData, BLOCK_SIZE, NUM_BOXES_PER_BLOCK, ANCHORS, THRESHOLD, NUM_RESULTS_PER_CLASS, result).executeTfliteTask(); - } - } + imgData = ByteBuffer.wrap(binary); + } - void detectObjectOnBinary(HashMap args, Result result) throws IOException { - byte[] binary = (byte[]) args.get("binary"); - String model = args.get("model").toString(); - double threshold = (double) args.get("threshold"); - float THRESHOLD = (float) threshold; - List ANCHORS = (ArrayList) args.get("anchors"); - int BLOCK_SIZE = (int) args.get("blockSize"); - int NUM_BOXES_PER_BLOCK = (int) args.get("numBoxesPerBlock"); - int NUM_RESULTS_PER_CLASS = (int) args.get("numResultsPerClass"); - - ByteBuffer imgData = ByteBuffer.wrap(binary); - - if (model.equals("SSDMobileNet")) { - new RunSSDMobileNet(args, imgData, NUM_RESULTS_PER_CLASS, THRESHOLD, result).executeTfliteTask(); - } else { - new RunYOLO(args, imgData, BLOCK_SIZE, NUM_BOXES_PER_BLOCK, ANCHORS, THRESHOLD, NUM_RESULTS_PER_CLASS, result).executeTfliteTask(); - } - } + protected void runTflite() { + tfLite.run(imgData, labelProb); + } - void detectObjectOnFrame(HashMap args, Result result) throws IOException { - List bytesList = (ArrayList) args.get("bytesList"); - String model = args.get("model").toString(); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; - int imageHeight = (int) (args.get("imageHeight")); - int imageWidth = (int) (args.get("imageWidth")); - int rotation = (int) (args.get("rotation")); - double threshold = (double) args.get("threshold"); - float THRESHOLD = (float) threshold; - int NUM_RESULTS_PER_CLASS = (int) args.get("numResultsPerClass"); - - List ANCHORS = (ArrayList) args.get("anchors"); - int BLOCK_SIZE = (int) args.get("blockSize"); - int NUM_BOXES_PER_BLOCK = (int) args.get("numBoxesPerBlock"); - - ByteBuffer imgData = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); - - if (model.equals("SSDMobileNet")) { - new RunSSDMobileNet(args, imgData, NUM_RESULTS_PER_CLASS, THRESHOLD, result).executeTfliteTask(); - } else { - new RunYOLO(args, imgData, BLOCK_SIZE, NUM_BOXES_PER_BLOCK, ANCHORS, THRESHOLD, NUM_RESULTS_PER_CLASS, result).executeTfliteTask(); + protected void onRunTfliteDone() { + result.success(GetTopN(NUM_RESULTS, THRESHOLD)); + } } - } - private class RunSSDMobileNet extends TfliteTask { - int num; - int numResultsPerClass; - float threshold; - float[][][] outputLocations; - float[][] outputClasses; - float[][] outputScores; - float[] numDetections = new float[1]; - Object[] inputArray; - Map outputMap = new HashMap<>(); - long startTime; - - RunSSDMobileNet(HashMap args, ByteBuffer imgData, int numResultsPerClass, float threshold, Result result) { - super(args, result); - this.num = tfLite.getOutputTensor(0).shape()[1]; - this.numResultsPerClass = numResultsPerClass; - this.threshold = threshold; - this.outputLocations = new float[1][num][4]; - this.outputClasses = new float[1][num]; - this.outputScores = new float[1][num]; - this.inputArray = new Object[]{imgData}; - - outputMap.put(0, outputLocations); - outputMap.put(1, outputClasses); - outputMap.put(2, outputScores); - outputMap.put(3, numDetections); - - startTime = SystemClock.uptimeMillis(); - } + private class RunModelOnFrame extends TfliteTask { + int NUM_RESULTS; + float THRESHOLD; + long startTime; + ByteBuffer imgData; - protected void runTflite() { - tfLite.runForMultipleInputsOutputs(inputArray, outputMap); - } + RunModelOnFrame(HashMap args, Result result) throws IOException { + super(args, result); - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + List bytesList = (ArrayList) args.get("bytesList"); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; + int imageHeight = (int) (args.get("imageHeight")); + int imageWidth = (int) (args.get("imageWidth")); + int rotation = (int) (args.get("rotation")); + NUM_RESULTS = (int) args.get("numResults"); + double threshold = (double) args.get("threshold"); + THRESHOLD = (float) threshold; - Map counters = new HashMap<>(); - final List> results = new ArrayList<>(); + startTime = SystemClock.uptimeMillis(); - for (int i = 0; i < numDetections[0]; ++i) { - if (outputScores[0][i] < threshold) continue; + imgData = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); + } - String detectedClass = labels.get((int) outputClasses[0][i] + 1); + protected void runTflite() { + tfLite.run(imgData, labelProb); + } - if (counters.get(detectedClass) == null) { - counters.put(detectedClass, 1); + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + result.success(GetTopN(NUM_RESULTS, THRESHOLD)); + } + } + + void detectObjectOnImage(HashMap args, Result result) throws IOException { + String path = args.get("path").toString(); + String model = args.get("model").toString(); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; + double threshold = (double) args.get("threshold"); + float THRESHOLD = (float) threshold; + List ANCHORS = (ArrayList) args.get("anchors"); + int BLOCK_SIZE = (int) args.get("blockSize"); + int NUM_BOXES_PER_BLOCK = (int) args.get("numBoxesPerBlock"); + int NUM_RESULTS_PER_CLASS = (int) args.get("numResultsPerClass"); + + ByteBuffer imgData = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); + + if (model.equals("SSDMobileNet")) { + new RunSSDMobileNet(args, imgData, NUM_RESULTS_PER_CLASS, THRESHOLD, result).executeTfliteTask(); } else { - int count = counters.get(detectedClass); - if (count >= numResultsPerClass) { - continue; - } else { - counters.put(detectedClass, count + 1); - } - } - - Map rect = new HashMap<>(); - float ymin = Math.max(0, outputLocations[0][i][0]); - float xmin = Math.max(0, outputLocations[0][i][1]); - float ymax = outputLocations[0][i][2]; - float xmax = outputLocations[0][i][3]; - rect.put("x", xmin); - rect.put("y", ymin); - rect.put("w", Math.min(1 - xmin, xmax - xmin)); - rect.put("h", Math.min(1 - ymin, ymax - ymin)); - - Map ret = new HashMap<>(); - ret.put("rect", rect); - ret.put("confidenceInClass", outputScores[0][i]); - ret.put("detectedClass", detectedClass); - - results.add(ret); - } - - result.success(results); + new RunYOLO(args, imgData, BLOCK_SIZE, NUM_BOXES_PER_BLOCK, ANCHORS, THRESHOLD, NUM_RESULTS_PER_CLASS, result).executeTfliteTask(); + } } - } - private class RunYOLO extends TfliteTask { - ByteBuffer imgData; - int blockSize; - int numBoxesPerBlock; - List anchors; - float threshold; - int numResultsPerClass; - long startTime; - int gridSize; - int numClasses; - final float[][][][] output; - - RunYOLO(HashMap args, - ByteBuffer imgData, - int blockSize, - int numBoxesPerBlock, - List anchors, - float threshold, - int numResultsPerClass, - Result result) { - super(args, result); - this.imgData = imgData; - this.blockSize = blockSize; - this.numBoxesPerBlock = numBoxesPerBlock; - this.anchors = anchors; - this.threshold = threshold; - this.numResultsPerClass = numResultsPerClass; - this.startTime = SystemClock.uptimeMillis(); - - Tensor tensor = tfLite.getInputTensor(0); - inputSize = tensor.shape()[1]; - - this.gridSize = inputSize / blockSize; - this.numClasses = labels.size(); - this.output = new float[1][gridSize][gridSize][(numClasses + 5) * numBoxesPerBlock]; + void detectObjectOnBinary(HashMap args, Result result) throws IOException { + byte[] binary = (byte[]) args.get("binary"); + String model = args.get("model").toString(); + double threshold = (double) args.get("threshold"); + float THRESHOLD = (float) threshold; + List ANCHORS = (ArrayList) args.get("anchors"); + int BLOCK_SIZE = (int) args.get("blockSize"); + int NUM_BOXES_PER_BLOCK = (int) args.get("numBoxesPerBlock"); + int NUM_RESULTS_PER_CLASS = (int) args.get("numResultsPerClass"); + + ByteBuffer imgData = ByteBuffer.wrap(binary); + + if (model.equals("SSDMobileNet")) { + new RunSSDMobileNet(args, imgData, NUM_RESULTS_PER_CLASS, THRESHOLD, result).executeTfliteTask(); + } else { + new RunYOLO(args, imgData, BLOCK_SIZE, NUM_BOXES_PER_BLOCK, ANCHORS, THRESHOLD, NUM_RESULTS_PER_CLASS, result).executeTfliteTask(); + } } - protected void runTflite() { - tfLite.run(imgData, output); + void detectObjectOnFrame(HashMap args, Result result) throws IOException { + List bytesList = (ArrayList) args.get("bytesList"); + String model = args.get("model").toString(); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; + int imageHeight = (int) (args.get("imageHeight")); + int imageWidth = (int) (args.get("imageWidth")); + int rotation = (int) (args.get("rotation")); + double threshold = (double) args.get("threshold"); + float THRESHOLD = (float) threshold; + int NUM_RESULTS_PER_CLASS = (int) args.get("numResultsPerClass"); + + List ANCHORS = (ArrayList) args.get("anchors"); + int BLOCK_SIZE = (int) args.get("blockSize"); + int NUM_BOXES_PER_BLOCK = (int) args.get("numBoxesPerBlock"); + + ByteBuffer imgData = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); + + if (model.equals("SSDMobileNet")) { + new RunSSDMobileNet(args, imgData, NUM_RESULTS_PER_CLASS, THRESHOLD, result).executeTfliteTask(); + } else { + new RunYOLO(args, imgData, BLOCK_SIZE, NUM_BOXES_PER_BLOCK, ANCHORS, THRESHOLD, NUM_RESULTS_PER_CLASS, result).executeTfliteTask(); + } } - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + private class RunSSDMobileNet extends TfliteTask { + int num; + int numResultsPerClass; + float threshold; + float[][][] outputLocations; + float[][] outputClasses; + float[][] outputScores; + float[] numDetections = new float[1]; + Object[] inputArray; + Map outputMap = new HashMap<>(); + long startTime; + + RunSSDMobileNet(HashMap args, ByteBuffer imgData, int numResultsPerClass, float threshold, Result result) { + super(args, result); + this.num = tfLite.getOutputTensor(0).shape()[1]; + this.numResultsPerClass = numResultsPerClass; + this.threshold = threshold; + this.outputLocations = new float[1][num][4]; + this.outputClasses = new float[1][num]; + this.outputScores = new float[1][num]; + this.inputArray = new Object[]{imgData}; + + outputMap.put(0, outputLocations); + outputMap.put(1, outputClasses); + outputMap.put(2, outputScores); + outputMap.put(3, numDetections); + + startTime = SystemClock.uptimeMillis(); + } - PriorityQueue> pq = - new PriorityQueue<>( - 1, - new Comparator>() { - @Override - public int compare(Map lhs, Map rhs) { - return Float.compare((float) rhs.get("confidenceInClass"), (float) lhs.get("confidenceInClass")); - } - }); + protected void runTflite() { + tfLite.runForMultipleInputsOutputs(inputArray, outputMap); + } - for (int y = 0; y < gridSize; ++y) { - for (int x = 0; x < gridSize; ++x) { - for (int b = 0; b < numBoxesPerBlock; ++b) { - final int offset = (numClasses + 5) * b; + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); - final float confidence = sigmoid(output[0][y][x][offset + 4]); + Map counters = new HashMap<>(); + final List> results = new ArrayList<>(); - final float[] classes = new float[numClasses]; - for (int c = 0; c < numClasses; ++c) { - classes[c] = output[0][y][x][offset + 5 + c]; - } - softmax(classes); - - int detectedClass = -1; - float maxClass = 0; - for (int c = 0; c < numClasses; ++c) { - if (classes[c] > maxClass) { - detectedClass = c; - maxClass = classes[c]; - } + for (int i = 0; i < numDetections[0]; ++i) { + if (outputScores[0][i] < threshold) continue; + + String detectedClass = labels.get((int) outputClasses[0][i] + 1); + + if (counters.get(detectedClass) == null) { + counters.put(detectedClass, 1); + } else { + int count = counters.get(detectedClass); + if (count >= numResultsPerClass) { + continue; + } else { + counters.put(detectedClass, count + 1); + } + } + + Map rect = new HashMap<>(); + float ymin = Math.max(0, outputLocations[0][i][0]); + float xmin = Math.max(0, outputLocations[0][i][1]); + float ymax = outputLocations[0][i][2]; + float xmax = outputLocations[0][i][3]; + rect.put("x", xmin); + rect.put("y", ymin); + rect.put("w", Math.min(1 - xmin, xmax - xmin)); + rect.put("h", Math.min(1 - ymin, ymax - ymin)); + + Map ret = new HashMap<>(); + ret.put("rect", rect); + ret.put("confidenceInClass", outputScores[0][i]); + ret.put("detectedClass", detectedClass); + + results.add(ret); } - final float confidenceInClass = maxClass * confidence; - if (confidenceInClass > threshold) { - final float xPos = (x + sigmoid(output[0][y][x][offset + 0])) * blockSize; - final float yPos = (y + sigmoid(output[0][y][x][offset + 1])) * blockSize; + result.success(results); + } + } - final float w = (float) (Math.exp(output[0][y][x][offset + 2]) * anchors.get(2 * b + 0)) * blockSize; - final float h = (float) (Math.exp(output[0][y][x][offset + 3]) * anchors.get(2 * b + 1)) * blockSize; + private class RunYOLO extends TfliteTask { + ByteBuffer imgData; + int blockSize; + int numBoxesPerBlock; + List anchors; + float threshold; + int numResultsPerClass; + long startTime; + int gridSize; + int numClasses; + final float[][][][] output; + + RunYOLO(HashMap args, + ByteBuffer imgData, + int blockSize, + int numBoxesPerBlock, + List anchors, + float threshold, + int numResultsPerClass, + Result result) { + super(args, result); + this.imgData = imgData; + this.blockSize = blockSize; + this.numBoxesPerBlock = numBoxesPerBlock; + this.anchors = anchors; + this.threshold = threshold; + this.numResultsPerClass = numResultsPerClass; + this.startTime = SystemClock.uptimeMillis(); + + Tensor tensor = tfLite.getInputTensor(0); + inputSize = tensor.shape()[1]; + + this.gridSize = inputSize / blockSize; + this.numClasses = labels.size(); + this.output = new float[1][gridSize][gridSize][(numClasses + 5) * numBoxesPerBlock]; + } - final float xmin = Math.max(0, (xPos - w / 2) / inputSize); - final float ymin = Math.max(0, (yPos - h / 2) / inputSize); + protected void runTflite() { + tfLite.run(imgData, output); + } - Map rect = new HashMap<>(); - rect.put("x", xmin); - rect.put("y", ymin); - rect.put("w", Math.min(1 - xmin, w / inputSize)); - rect.put("h", Math.min(1 - ymin, h / inputSize)); + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + + PriorityQueue> pq = + new PriorityQueue<>( + 1, + new Comparator>() { + @Override + public int compare(Map lhs, Map rhs) { + return Float.compare((float) rhs.get("confidenceInClass"), (float) lhs.get("confidenceInClass")); + } + }); + + for (int y = 0; y < gridSize; ++y) { + for (int x = 0; x < gridSize; ++x) { + for (int b = 0; b < numBoxesPerBlock; ++b) { + final int offset = (numClasses + 5) * b; + + final float confidence = sigmoid(output[0][y][x][offset + 4]); + + final float[] classes = new float[numClasses]; + for (int c = 0; c < numClasses; ++c) { + classes[c] = output[0][y][x][offset + 5 + c]; + } + softmax(classes); + + int detectedClass = -1; + float maxClass = 0; + for (int c = 0; c < numClasses; ++c) { + if (classes[c] > maxClass) { + detectedClass = c; + maxClass = classes[c]; + } + } + + final float confidenceInClass = maxClass * confidence; + if (confidenceInClass > threshold) { + final float xPos = (x + sigmoid(output[0][y][x][offset + 0])) * blockSize; + final float yPos = (y + sigmoid(output[0][y][x][offset + 1])) * blockSize; + + final float w = (float) (Math.exp(output[0][y][x][offset + 2]) * anchors.get(2 * b + 0)) * blockSize; + final float h = (float) (Math.exp(output[0][y][x][offset + 3]) * anchors.get(2 * b + 1)) * blockSize; + + final float xmin = Math.max(0, (xPos - w / 2) / inputSize); + final float ymin = Math.max(0, (yPos - h / 2) / inputSize); + + Map rect = new HashMap<>(); + rect.put("x", xmin); + rect.put("y", ymin); + rect.put("w", Math.min(1 - xmin, w / inputSize)); + rect.put("h", Math.min(1 - ymin, h / inputSize)); + + Map ret = new HashMap<>(); + ret.put("rect", rect); + ret.put("confidenceInClass", confidenceInClass); + ret.put("detectedClass", labels.get(detectedClass)); + + pq.add(ret); + } + } + } + } - Map ret = new HashMap<>(); - ret.put("rect", rect); - ret.put("confidenceInClass", confidenceInClass); - ret.put("detectedClass", labels.get(detectedClass)); + Map counters = new HashMap<>(); + List> results = new ArrayList<>(); + + for (int i = 0; i < pq.size(); ++i) { + Map ret = pq.poll(); + String detectedClass = ret.get("detectedClass").toString(); + + if (counters.get(detectedClass) == null) { + counters.put(detectedClass, 1); + } else { + int count = counters.get(detectedClass); + if (count >= numResultsPerClass) { + continue; + } else { + counters.put(detectedClass, count + 1); + } + } + results.add(ret); + } + result.success(results); + } + } - pq.add(ret); + private class RunPix2PixOnImage extends TfliteTask { + String path, outputType; + float IMAGE_MEAN, IMAGE_STD; + long startTime; + ByteBuffer input, output; + + RunPix2PixOnImage(HashMap args, Result result) throws IOException { + super(args, result); + path = args.get("path").toString(); + double mean = (double) (args.get("imageMean")); + IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + IMAGE_STD = (float) std; + + outputType = args.get("outputType").toString(); + startTime = SystemClock.uptimeMillis(); + input = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); + output = ByteBuffer.allocateDirect(input.limit()); + output.order(ByteOrder.nativeOrder()); + if (input.limit() == 0) { + result.error("Unexpected input position, bad file?", null, null); + return; + } + if (output.position() != 0) { + result.error("Unexpected output position", null, null); + return; } - } } - } - Map counters = new HashMap<>(); - List> results = new ArrayList<>(); + protected void runTflite() { + tfLite.run(input, output); + } - for (int i = 0; i < pq.size(); ++i) { - Map ret = pq.poll(); - String detectedClass = ret.get("detectedClass").toString(); + protected void onRunTfliteDone() { + Log.v("time", "Generating took " + (SystemClock.uptimeMillis() - startTime)); + if (output.position() != input.limit()) { + result.error("Mismatching input/output position", null, null); + return; + } - if (counters.get(detectedClass) == null) { - counters.put(detectedClass, 1); - } else { - int count = counters.get(detectedClass); - if (count >= numResultsPerClass) { - continue; - } else { - counters.put(detectedClass, count + 1); - } - } - results.add(ret); - } - result.success(results); - } - } + output.flip(); + Bitmap bitmapRaw = feedOutput(output, IMAGE_MEAN, IMAGE_STD); - private class RunPix2PixOnImage extends TfliteTask { - String path, outputType; - float IMAGE_MEAN, IMAGE_STD; - long startTime; - ByteBuffer input, output; - - RunPix2PixOnImage(HashMap args, Result result) throws IOException { - super(args, result); - path = args.get("path").toString(); - double mean = (double) (args.get("imageMean")); - IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - IMAGE_STD = (float) std; - - outputType = args.get("outputType").toString(); - startTime = SystemClock.uptimeMillis(); - input = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); - output = ByteBuffer.allocateDirect(input.limit()); - output.order(ByteOrder.nativeOrder()); - if (input.limit() == 0) { - result.error("Unexpected input position, bad file?", null, null); - return; - } - if (output.position() != 0) { - result.error("Unexpected output position", null, null); - return; - } + if (outputType.equals("png")) { + result.success(compressPNG(bitmapRaw)); + } else { + result.success(bitmapRaw); + } + } } - protected void runTflite() { - tfLite.run(input, output); - } + private class RunPix2PixOnBinary extends TfliteTask { + long startTime; + String outputType; + ByteBuffer input, output; - protected void onRunTfliteDone() { - Log.v("time", "Generating took " + (SystemClock.uptimeMillis() - startTime)); - if (output.position() != input.limit()) { - result.error("Mismatching input/output position", null, null); - return; - } - - output.flip(); - Bitmap bitmapRaw = feedOutput(output, IMAGE_MEAN, IMAGE_STD); - - if (outputType.equals("png")) { - result.success(compressPNG(bitmapRaw)); - } else { - result.success(bitmapRaw); - } - } - } + RunPix2PixOnBinary(HashMap args, Result result) throws IOException { + super(args, result); + byte[] binary = (byte[]) args.get("binary"); + outputType = args.get("outputType").toString(); + startTime = SystemClock.uptimeMillis(); + input = ByteBuffer.wrap(binary); + output = ByteBuffer.allocateDirect(input.limit()); + output.order(ByteOrder.nativeOrder()); - private class RunPix2PixOnBinary extends TfliteTask { - long startTime; - String outputType; - ByteBuffer input, output; - - RunPix2PixOnBinary(HashMap args, Result result) throws IOException { - super(args, result); - byte[] binary = (byte[]) args.get("binary"); - outputType = args.get("outputType").toString(); - startTime = SystemClock.uptimeMillis(); - input = ByteBuffer.wrap(binary); - output = ByteBuffer.allocateDirect(input.limit()); - output.order(ByteOrder.nativeOrder()); - - if (input.limit() == 0) { - result.error("Unexpected input position, bad file?", null, null); - return; - } - if (output.position() != 0) { - result.error("Unexpected output position", null, null); - return; - } - } + if (input.limit() == 0) { + result.error("Unexpected input position, bad file?", null, null); + return; + } + if (output.position() != 0) { + result.error("Unexpected output position", null, null); + return; + } + } - protected void runTflite() { - tfLite.run(input, output); - } + protected void runTflite() { + tfLite.run(input, output); + } - protected void onRunTfliteDone() { - Log.v("time", "Generating took " + (SystemClock.uptimeMillis() - startTime)); - if (output.position() != input.limit()) { - result.error("Mismatching input/output position", null, null); - return; - } + protected void onRunTfliteDone() { + Log.v("time", "Generating took " + (SystemClock.uptimeMillis() - startTime)); + if (output.position() != input.limit()) { + result.error("Mismatching input/output position", null, null); + return; + } - output.flip(); - result.success(output.array()); + output.flip(); + result.success(output.array()); + } } - } - private class RunPix2PixOnFrame extends TfliteTask { - long startTime; - String outputType; - float IMAGE_MEAN, IMAGE_STD; - ByteBuffer input, output; - - RunPix2PixOnFrame(HashMap args, Result result) throws IOException { - super(args, result); - List bytesList = (ArrayList) args.get("bytesList"); - double mean = (double) (args.get("imageMean")); - IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - IMAGE_STD = (float) std; - int imageHeight = (int) (args.get("imageHeight")); - int imageWidth = (int) (args.get("imageWidth")); - int rotation = (int) (args.get("rotation")); - - outputType = args.get("outputType").toString(); - startTime = SystemClock.uptimeMillis(); - input = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); - output = ByteBuffer.allocateDirect(input.limit()); - output.order(ByteOrder.nativeOrder()); - - if (input.limit() == 0) { - result.error("Unexpected input position, bad file?", null, null); - return; - } - if (output.position() != 0) { - result.error("Unexpected output position", null, null); - return; - } - } + private class RunPix2PixOnFrame extends TfliteTask { + long startTime; + String outputType; + float IMAGE_MEAN, IMAGE_STD; + ByteBuffer input, output; + + RunPix2PixOnFrame(HashMap args, Result result) throws IOException { + super(args, result); + List bytesList = (ArrayList) args.get("bytesList"); + double mean = (double) (args.get("imageMean")); + IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + IMAGE_STD = (float) std; + int imageHeight = (int) (args.get("imageHeight")); + int imageWidth = (int) (args.get("imageWidth")); + int rotation = (int) (args.get("rotation")); + + outputType = args.get("outputType").toString(); + startTime = SystemClock.uptimeMillis(); + input = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); + output = ByteBuffer.allocateDirect(input.limit()); + output.order(ByteOrder.nativeOrder()); + + if (input.limit() == 0) { + result.error("Unexpected input position, bad file?", null, null); + return; + } + if (output.position() != 0) { + result.error("Unexpected output position", null, null); + return; + } + } - protected void runTflite() { - tfLite.run(input, output); - } + protected void runTflite() { + tfLite.run(input, output); + } + + protected void onRunTfliteDone() { + Log.v("time", "Generating took " + (SystemClock.uptimeMillis() - startTime)); + if (output.position() != input.limit()) { + result.error("Mismatching input/output position", null, null); + return; + } + + output.flip(); + Bitmap bitmapRaw = feedOutput(output, IMAGE_MEAN, IMAGE_STD); - protected void onRunTfliteDone() { - Log.v("time", "Generating took " + (SystemClock.uptimeMillis() - startTime)); - if (output.position() != input.limit()) { - result.error("Mismatching input/output position", null, null); - return; - } - - output.flip(); - Bitmap bitmapRaw = feedOutput(output, IMAGE_MEAN, IMAGE_STD); - - if (outputType.equals("png")) { - result.success(compressPNG(bitmapRaw)); - } else { - result.success(bitmapRaw); - } + if (outputType.equals("png")) { + result.success(compressPNG(bitmapRaw)); + } else { + result.success(bitmapRaw); + } + } } - } - private class RunSegmentationOnImage extends TfliteTask { - List labelColors; - String outputType; - long startTime; - ByteBuffer input, output; + private class RunSegmentationOnImage extends TfliteTask { + List labelColors; + String outputType; + long startTime; + ByteBuffer input, output; - RunSegmentationOnImage(HashMap args, Result result) throws IOException { - super(args, result); + RunSegmentationOnImage(HashMap args, Result result) throws IOException { + super(args, result); - String path = args.get("path").toString(); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; + String path = args.get("path").toString(); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; - labelColors = (ArrayList) args.get("labelColors"); - outputType = args.get("outputType").toString(); + labelColors = (ArrayList) args.get("labelColors"); + outputType = args.get("outputType").toString(); - startTime = SystemClock.uptimeMillis(); - input = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); - output = ByteBuffer.allocateDirect(tfLite.getOutputTensor(0).numBytes()); - output.order(ByteOrder.nativeOrder()); - } + startTime = SystemClock.uptimeMillis(); + input = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); + output = ByteBuffer.allocateDirect(tfLite.getOutputTensor(0).numBytes()); + output.order(ByteOrder.nativeOrder()); + } - protected void runTflite() { - tfLite.run(input, output); - } + protected void runTflite() { + tfLite.run(input, output); + } - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); - if (input.limit() == 0) { - result.error("Unexpected input position, bad file?", null, null); - return; - } - if (output.position() != output.limit()) { - result.error("Unexpected output position", null, null); - return; - } - output.flip(); + if (input.limit() == 0) { + result.error("Unexpected input position, bad file?", null, null); + return; + } + if (output.position() != output.limit()) { + result.error("Unexpected output position", null, null); + return; + } + output.flip(); - result.success(fetchArgmax(output, labelColors, outputType)); + result.success(fetchArgmax(output, labelColors, outputType)); + } } - } - private class RunSegmentationOnBinary extends TfliteTask { - List labelColors; - String outputType; - long startTime; - ByteBuffer input, output; + private class RunSegmentationOnBinary extends TfliteTask { + List labelColors; + String outputType; + long startTime; + ByteBuffer input, output; - RunSegmentationOnBinary(HashMap args, Result result) throws IOException { - super(args, result); + RunSegmentationOnBinary(HashMap args, Result result) throws IOException { + super(args, result); - byte[] binary = (byte[]) args.get("binary"); - labelColors = (ArrayList) args.get("labelColors"); - outputType = args.get("outputType").toString(); + byte[] binary = (byte[]) args.get("binary"); + labelColors = (ArrayList) args.get("labelColors"); + outputType = args.get("outputType").toString(); - startTime = SystemClock.uptimeMillis(); - input = ByteBuffer.wrap(binary); - output = ByteBuffer.allocateDirect(tfLite.getOutputTensor(0).numBytes()); - output.order(ByteOrder.nativeOrder()); - } + startTime = SystemClock.uptimeMillis(); + input = ByteBuffer.wrap(binary); + output = ByteBuffer.allocateDirect(tfLite.getOutputTensor(0).numBytes()); + output.order(ByteOrder.nativeOrder()); + } - protected void runTflite() { - tfLite.run(input, output); - } + protected void runTflite() { + tfLite.run(input, output); + } - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); - if (input.limit() == 0) { - result.error("Unexpected input position, bad file?", null, null); - return; - } - if (output.position() != output.limit()) { - result.error("Unexpected output position", null, null); - return; - } - output.flip(); + if (input.limit() == 0) { + result.error("Unexpected input position, bad file?", null, null); + return; + } + if (output.position() != output.limit()) { + result.error("Unexpected output position", null, null); + return; + } + output.flip(); - result.success(fetchArgmax(output, labelColors, outputType)); + result.success(fetchArgmax(output, labelColors, outputType)); + } } - } - private class RunSegmentationOnFrame extends TfliteTask { - List labelColors; - String outputType; - long startTime; - ByteBuffer input, output; - - RunSegmentationOnFrame(HashMap args, Result result) throws IOException { - super(args, result); - - List bytesList = (ArrayList) args.get("bytesList"); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; - int imageHeight = (int) (args.get("imageHeight")); - int imageWidth = (int) (args.get("imageWidth")); - int rotation = (int) (args.get("rotation")); - labelColors = (ArrayList) args.get("labelColors"); - outputType = args.get("outputType").toString(); - - startTime = SystemClock.uptimeMillis(); - input = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); - output = ByteBuffer.allocateDirect(tfLite.getOutputTensor(0).numBytes()); - output.order(ByteOrder.nativeOrder()); - } + private class RunSegmentationOnFrame extends TfliteTask { + List labelColors; + String outputType; + long startTime; + ByteBuffer input, output; + + RunSegmentationOnFrame(HashMap args, Result result) throws IOException { + super(args, result); + + List bytesList = (ArrayList) args.get("bytesList"); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; + int imageHeight = (int) (args.get("imageHeight")); + int imageWidth = (int) (args.get("imageWidth")); + int rotation = (int) (args.get("rotation")); + labelColors = (ArrayList) args.get("labelColors"); + outputType = args.get("outputType").toString(); + + startTime = SystemClock.uptimeMillis(); + input = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); + output = ByteBuffer.allocateDirect(tfLite.getOutputTensor(0).numBytes()); + output.order(ByteOrder.nativeOrder()); + } - protected void runTflite() { - tfLite.run(input, output); - } + protected void runTflite() { + tfLite.run(input, output); + } - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); - if (input.limit() == 0) { - result.error("Unexpected input position, bad file?", null, null); - return; - } - if (output.position() != output.limit()) { - result.error("Unexpected output position", null, null); - return; - } - output.flip(); + if (input.limit() == 0) { + result.error("Unexpected input position, bad file?", null, null); + return; + } + if (output.position() != output.limit()) { + result.error("Unexpected output position", null, null); + return; + } + output.flip(); - result.success(fetchArgmax(output, labelColors, outputType)); + result.success(fetchArgmax(output, labelColors, outputType)); + } } - } - byte[] fetchArgmax(ByteBuffer output, List labelColors, String outputType) { - Tensor outputTensor = tfLite.getOutputTensor(0); - int outputBatchSize = outputTensor.shape()[0]; - assert outputBatchSize == 1; - int outputHeight = outputTensor.shape()[1]; - int outputWidth = outputTensor.shape()[2]; - int outputChannels = outputTensor.shape()[3]; + byte[] fetchArgmax(ByteBuffer output, List labelColors, String outputType) { + Tensor outputTensor = tfLite.getOutputTensor(0); + int outputBatchSize = outputTensor.shape()[0]; + assert outputBatchSize == 1; + int outputHeight = outputTensor.shape()[1]; + int outputWidth = outputTensor.shape()[2]; + int outputChannels = outputTensor.shape()[3]; - Bitmap outputArgmax = null; - byte[] outputBytes = new byte[outputWidth * outputHeight * 4]; - if (outputType.equals("png")) { - outputArgmax = Bitmap.createBitmap(outputWidth, outputHeight, Bitmap.Config.ARGB_8888); - } + Bitmap outputArgmax = null; + byte[] outputBytes = new byte[outputWidth * outputHeight * 4]; + if (outputType.equals("png")) { + outputArgmax = Bitmap.createBitmap(outputWidth, outputHeight, Bitmap.Config.ARGB_8888); + } - if (outputTensor.dataType() == DataType.FLOAT32) { - for (int i = 0; i < outputHeight; ++i) { - for (int j = 0; j < outputWidth; ++j) { - int maxIndex = 0; - float maxValue = 0.0f; - for (int c = 0; c < outputChannels; ++c) { - float outputValue = output.getFloat(); - if (outputValue > maxValue) { - maxIndex = c; - maxValue = outputValue; + if (outputTensor.dataType() == DataType.FLOAT32) { + for (int i = 0; i < outputHeight; ++i) { + for (int j = 0; j < outputWidth; ++j) { + int maxIndex = 0; + float maxValue = 0.0f; + for (int c = 0; c < outputChannels; ++c) { + float outputValue = output.getFloat(); + if (outputValue > maxValue) { + maxIndex = c; + maxValue = outputValue; + } + } + int labelColor = labelColors.get(maxIndex).intValue(); + if (outputType.equals("png")) { + outputArgmax.setPixel(j, i, labelColor); + } else { + setPixel(outputBytes, i * outputWidth + j, labelColor); + } + } } - } - int labelColor = labelColors.get(maxIndex).intValue(); - if (outputType.equals("png")) { - outputArgmax.setPixel(j, i, labelColor); - } else { - setPixel(outputBytes, i * outputWidth + j, labelColor); - } - } - } - } else { - for (int i = 0; i < outputHeight; ++i) { - for (int j = 0; j < outputWidth; ++j) { - int maxIndex = 0; - int maxValue = 0; - for (int c = 0; c < outputChannels; ++c) { - int outputValue = output.get(); - if (outputValue > maxValue) { - maxIndex = c; - maxValue = outputValue; + } else { + for (int i = 0; i < outputHeight; ++i) { + for (int j = 0; j < outputWidth; ++j) { + int maxIndex = 0; + int maxValue = 0; + for (int c = 0; c < outputChannels; ++c) { + int outputValue = output.get(); + if (outputValue > maxValue) { + maxIndex = c; + maxValue = outputValue; + } + } + int labelColor = labelColors.get(maxIndex).intValue(); + if (outputType.equals("png")) { + outputArgmax.setPixel(j, i, labelColor); + } else { + setPixel(outputBytes, i * outputWidth + j, labelColor); + } + } } - } - int labelColor = labelColors.get(maxIndex).intValue(); - if (outputType.equals("png")) { - outputArgmax.setPixel(j, i, labelColor); - } else { - setPixel(outputBytes, i * outputWidth + j, labelColor); - } - } - } - } - if (outputType.equals("png")) { - return compressPNG(outputArgmax); - } else { - return outputBytes; + } + if (outputType.equals("png")) { + return compressPNG(outputArgmax); + } else { + return outputBytes; + } } - } - void setPixel(byte[] rgba, int index, long color) { - rgba[index * 4] = (byte) ((color >> 16) & 0xFF); - rgba[index * 4 + 1] = (byte) ((color >> 8) & 0xFF); - rgba[index * 4 + 2] = (byte) (color & 0xFF); - rgba[index * 4 + 3] = (byte) ((color >> 24) & 0xFF); - } + void setPixel(byte[] rgba, int index, long color) { + rgba[index * 4] = (byte) ((color >> 16) & 0xFF); + rgba[index * 4 + 1] = (byte) ((color >> 8) & 0xFF); + rgba[index * 4 + 2] = (byte) (color & 0xFF); + rgba[index * 4 + 3] = (byte) ((color >> 24) & 0xFF); + } - byte[] compressPNG(Bitmap bitmap) { - // https://stackoverflow.com/questions/4989182/converting-java-bitmap-to-byte-array#4989543 - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - byte[] byteArray = stream.toByteArray(); - // bitmap.recycle(); - return byteArray; - } + byte[] compressPNG(Bitmap bitmap) { + // https://stackoverflow.com/questions/4989182/converting-java-bitmap-to-byte-array#4989543 + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + byte[] byteArray = stream.toByteArray(); + // bitmap.recycle(); + return byteArray; + } - void runPoseNetOnImage(HashMap args, Result result) throws IOException { - String path = args.get("path").toString(); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; - int numResults = (int) args.get("numResults"); - double threshold = (double) args.get("threshold"); - int nmsRadius = (int) args.get("nmsRadius"); + void runPoseNetOnImage(HashMap args, Result result) throws IOException { + String path = args.get("path").toString(); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; + int numResults = (int) args.get("numResults"); + double threshold = (double) args.get("threshold"); + int nmsRadius = (int) args.get("nmsRadius"); - ByteBuffer imgData = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); + ByteBuffer imgData = feedInputTensorImage(path, IMAGE_MEAN, IMAGE_STD); - new RunPoseNet(args, imgData, numResults, threshold, nmsRadius, result).executeTfliteTask(); - } + new RunPoseNet(args, imgData, numResults, threshold, nmsRadius, result).executeTfliteTask(); + } - void runPoseNetOnBinary(HashMap args, Result result) throws IOException { - byte[] binary = (byte[]) args.get("binary"); - int numResults = (int) args.get("numResults"); - double threshold = (double) args.get("threshold"); - int nmsRadius = (int) args.get("nmsRadius"); + void runPoseNetOnBinary(HashMap args, Result result) throws IOException { + byte[] binary = (byte[]) args.get("binary"); + int numResults = (int) args.get("numResults"); + double threshold = (double) args.get("threshold"); + int nmsRadius = (int) args.get("nmsRadius"); - ByteBuffer imgData = ByteBuffer.wrap(binary); + ByteBuffer imgData = ByteBuffer.wrap(binary); - new RunPoseNet(args, imgData, numResults, threshold, nmsRadius, result).executeTfliteTask(); - } + new RunPoseNet(args, imgData, numResults, threshold, nmsRadius, result).executeTfliteTask(); + } - void runPoseNetOnFrame(HashMap args, Result result) throws IOException { - List bytesList = (ArrayList) args.get("bytesList"); - double mean = (double) (args.get("imageMean")); - float IMAGE_MEAN = (float) mean; - double std = (double) (args.get("imageStd")); - float IMAGE_STD = (float) std; - int imageHeight = (int) (args.get("imageHeight")); - int imageWidth = (int) (args.get("imageWidth")); - int rotation = (int) (args.get("rotation")); - int numResults = (int) args.get("numResults"); - double threshold = (double) args.get("threshold"); - int nmsRadius = (int) args.get("nmsRadius"); - - ByteBuffer imgData = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); - - new RunPoseNet(args, imgData, numResults, threshold, nmsRadius, result).executeTfliteTask(); - } + void runPoseNetOnFrame(HashMap args, Result result) throws IOException { + List bytesList = (ArrayList) args.get("bytesList"); + double mean = (double) (args.get("imageMean")); + float IMAGE_MEAN = (float) mean; + double std = (double) (args.get("imageStd")); + float IMAGE_STD = (float) std; + int imageHeight = (int) (args.get("imageHeight")); + int imageWidth = (int) (args.get("imageWidth")); + int rotation = (int) (args.get("rotation")); + int numResults = (int) args.get("numResults"); + double threshold = (double) args.get("threshold"); + int nmsRadius = (int) args.get("nmsRadius"); - void initPoseNet(Map outputMap) { - if (partsIds.size() == 0) { - for (int i = 0; i < partNames.length; ++i) - partsIds.put(partNames[i], i); + ByteBuffer imgData = feedInputTensorFrame(bytesList, imageHeight, imageWidth, IMAGE_MEAN, IMAGE_STD, rotation); - for (int i = 0; i < poseChain.length; ++i) { - parentToChildEdges.add(partsIds.get(poseChain[i][1])); - childToParentEdges.add(partsIds.get(poseChain[i][0])); - } + new RunPoseNet(args, imgData, numResults, threshold, nmsRadius, result).executeTfliteTask(); } - for (int i = 0; i < tfLite.getOutputTensorCount(); i++) { - int[] shape = tfLite.getOutputTensor(i).shape(); - float[][][][] output = new float[shape[0]][shape[1]][shape[2]][shape[3]]; - outputMap.put(i, output); - } - } + void initPoseNet(Map outputMap) { + if (partsIds.size() == 0) { + for (int i = 0; i < partNames.length; ++i) + partsIds.put(partNames[i], i); - private class RunPoseNet extends TfliteTask { - long startTime; - Object[] input; - Map outputMap = new HashMap<>(); - int numResults; - double threshold; - int nmsRadius; - - int localMaximumRadius = 1; - int outputStride = 16; - - RunPoseNet(HashMap args, - ByteBuffer imgData, - int numResults, - double threshold, - int nmsRadius, - Result result) throws IOException { - super(args, result); - this.numResults = numResults; - this.threshold = threshold; - this.nmsRadius = nmsRadius; - - input = new Object[]{imgData}; - initPoseNet(outputMap); - - startTime = SystemClock.uptimeMillis(); - } + for (int i = 0; i < poseChain.length; ++i) { + parentToChildEdges.add(partsIds.get(poseChain[i][1])); + childToParentEdges.add(partsIds.get(poseChain[i][0])); + } + } - protected void runTflite() { - tfLite.runForMultipleInputsOutputs(input, outputMap); + for (int i = 0; i < tfLite.getOutputTensorCount(); i++) { + int[] shape = tfLite.getOutputTensor(i).shape(); + float[][][][] output = new float[shape[0]][shape[1]][shape[2]][shape[3]]; + outputMap.put(i, output); + } } - protected void onRunTfliteDone() { - Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); + private class RunPoseNet extends TfliteTask { + long startTime; + Object[] input; + Map outputMap = new HashMap<>(); + int numResults; + double threshold; + int nmsRadius; + + int localMaximumRadius = 1; + int outputStride = 16; + + RunPoseNet(HashMap args, + ByteBuffer imgData, + int numResults, + double threshold, + int nmsRadius, + Result result) throws IOException { + super(args, result); + this.numResults = numResults; + this.threshold = threshold; + this.nmsRadius = nmsRadius; + + input = new Object[]{imgData}; + initPoseNet(outputMap); + + startTime = SystemClock.uptimeMillis(); + } + + protected void runTflite() { + tfLite.runForMultipleInputsOutputs(input, outputMap); + } - float[][][] scores = ((float[][][][]) outputMap.get(0))[0]; - float[][][] offsets = ((float[][][][]) outputMap.get(1))[0]; - float[][][] displacementsFwd = ((float[][][][]) outputMap.get(2))[0]; - float[][][] displacementsBwd = ((float[][][][]) outputMap.get(3))[0]; + protected void onRunTfliteDone() { + Log.v("time", "Inference took " + (SystemClock.uptimeMillis() - startTime)); - PriorityQueue> pq = buildPartWithScoreQueue(scores, threshold, localMaximumRadius); + float[][][] scores = ((float[][][][]) outputMap.get(0))[0]; + float[][][] offsets = ((float[][][][]) outputMap.get(1))[0]; + float[][][] displacementsFwd = ((float[][][][]) outputMap.get(2))[0]; + float[][][] displacementsBwd = ((float[][][][]) outputMap.get(3))[0]; - int numParts = scores[0][0].length; - int numEdges = parentToChildEdges.size(); - int sqaredNmsRadius = nmsRadius * nmsRadius; + PriorityQueue> pq = buildPartWithScoreQueue(scores, threshold, localMaximumRadius); - List> results = new ArrayList<>(); + int numParts = scores[0][0].length; + int numEdges = parentToChildEdges.size(); + int sqaredNmsRadius = nmsRadius * nmsRadius; - while (results.size() < numResults && pq.size() > 0) { - Map root = pq.poll(); - float[] rootPoint = getImageCoords(root, outputStride, numParts, offsets); + List> results = new ArrayList<>(); - if (withinNmsRadiusOfCorrespondingPoint( - results, sqaredNmsRadius, rootPoint[0], rootPoint[1], (int) root.get("partId"))) - continue; + while (results.size() < numResults && pq.size() > 0) { + Map root = pq.poll(); + float[] rootPoint = getImageCoords(root, outputStride, numParts, offsets); - Map keypoint = new HashMap<>(); - keypoint.put("score", root.get("score")); - keypoint.put("part", partNames[(int) root.get("partId")]); - keypoint.put("y", rootPoint[0] / inputSize); - keypoint.put("x", rootPoint[1] / inputSize); - - Map> keypoints = new HashMap<>(); - keypoints.put((int) root.get("partId"), keypoint); - - for (int edge = numEdges - 1; edge >= 0; --edge) { - int sourceKeypointId = parentToChildEdges.get(edge); - int targetKeypointId = childToParentEdges.get(edge); - if (keypoints.containsKey(sourceKeypointId) && !keypoints.containsKey(targetKeypointId)) { - keypoint = traverseToTargetKeypoint(edge, keypoints.get(sourceKeypointId), - targetKeypointId, scores, offsets, outputStride, displacementsBwd); - keypoints.put(targetKeypointId, keypoint); - } - } - - for (int edge = 0; edge < numEdges; ++edge) { - int sourceKeypointId = childToParentEdges.get(edge); - int targetKeypointId = parentToChildEdges.get(edge); - if (keypoints.containsKey(sourceKeypointId) && !keypoints.containsKey(targetKeypointId)) { - keypoint = traverseToTargetKeypoint(edge, keypoints.get(sourceKeypointId), - targetKeypointId, scores, offsets, outputStride, displacementsFwd); - keypoints.put(targetKeypointId, keypoint); - } - } - - Map result = new HashMap<>(); - result.put("keypoints", keypoints); - result.put("score", getInstanceScore(keypoints, numParts)); - results.add(result); - } - - result.success(results); - } - } + if (withinNmsRadiusOfCorrespondingPoint( + results, sqaredNmsRadius, rootPoint[0], rootPoint[1], (int) root.get("partId"))) + continue; - PriorityQueue> buildPartWithScoreQueue(float[][][] scores, - double threshold, - int localMaximumRadius) { - PriorityQueue> pq = - new PriorityQueue<>( - 1, - new Comparator>() { - @Override - public int compare(Map lhs, Map rhs) { - return Float.compare((float) rhs.get("score"), (float) lhs.get("score")); - } - }); - - for (int heatmapY = 0; heatmapY < scores.length; ++heatmapY) { - for (int heatmapX = 0; heatmapX < scores[0].length; ++heatmapX) { - for (int keypointId = 0; keypointId < scores[0][0].length; ++keypointId) { - float score = sigmoid(scores[heatmapY][heatmapX][keypointId]); - if (score < threshold) continue; - - if (scoreIsMaximumInLocalWindow( - keypointId, score, heatmapY, heatmapX, localMaximumRadius, scores)) { - Map res = new HashMap<>(); - res.put("score", score); - res.put("y", heatmapY); - res.put("x", heatmapX); - res.put("partId", keypointId); - pq.add(res); - } - } - } - } + Map keypoint = new HashMap<>(); + keypoint.put("score", root.get("score")); + keypoint.put("part", partNames[(int) root.get("partId")]); + keypoint.put("y", rootPoint[0] / inputSize); + keypoint.put("x", rootPoint[1] / inputSize); - return pq; - } + Map> keypoints = new HashMap<>(); + keypoints.put((int) root.get("partId"), keypoint); - boolean scoreIsMaximumInLocalWindow(int keypointId, - float score, - int heatmapY, - int heatmapX, - int localMaximumRadius, - float[][][] scores) { - boolean localMaximum = true; - int height = scores.length; - int width = scores[0].length; - - int yStart = Math.max(heatmapY - localMaximumRadius, 0); - int yEnd = Math.min(heatmapY + localMaximumRadius + 1, height); - for (int yCurrent = yStart; yCurrent < yEnd; ++yCurrent) { - int xStart = Math.max(heatmapX - localMaximumRadius, 0); - int xEnd = Math.min(heatmapX + localMaximumRadius + 1, width); - for (int xCurrent = xStart; xCurrent < xEnd; ++xCurrent) { - if (sigmoid(scores[yCurrent][xCurrent][keypointId]) > score) { - localMaximum = false; - break; - } - } - if (!localMaximum) { - break; - } - } + for (int edge = numEdges - 1; edge >= 0; --edge) { + int sourceKeypointId = parentToChildEdges.get(edge); + int targetKeypointId = childToParentEdges.get(edge); + if (keypoints.containsKey(sourceKeypointId) && !keypoints.containsKey(targetKeypointId)) { + keypoint = traverseToTargetKeypoint(edge, keypoints.get(sourceKeypointId), + targetKeypointId, scores, offsets, outputStride, displacementsBwd); + keypoints.put(targetKeypointId, keypoint); + } + } - return localMaximum; - } + for (int edge = 0; edge < numEdges; ++edge) { + int sourceKeypointId = childToParentEdges.get(edge); + int targetKeypointId = parentToChildEdges.get(edge); + if (keypoints.containsKey(sourceKeypointId) && !keypoints.containsKey(targetKeypointId)) { + keypoint = traverseToTargetKeypoint(edge, keypoints.get(sourceKeypointId), + targetKeypointId, scores, offsets, outputStride, displacementsFwd); + keypoints.put(targetKeypointId, keypoint); + } + } - float[] getImageCoords(Map keypoint, - int outputStride, - int numParts, - float[][][] offsets) { - int heatmapY = (int) keypoint.get("y"); - int heatmapX = (int) keypoint.get("x"); - int keypointId = (int) keypoint.get("partId"); - float offsetY = offsets[heatmapY][heatmapX][keypointId]; - float offsetX = offsets[heatmapY][heatmapX][keypointId + numParts]; + Map result = new HashMap<>(); + result.put("keypoints", keypoints); + result.put("score", getInstanceScore(keypoints, numParts)); + results.add(result); + } - float y = heatmapY * outputStride + offsetY; - float x = heatmapX * outputStride + offsetX; + result.success(results); + } + } - return new float[]{y, x}; - } + PriorityQueue> buildPartWithScoreQueue(float[][][] scores, + double threshold, + int localMaximumRadius) { + PriorityQueue> pq = + new PriorityQueue<>( + 1, + new Comparator>() { + @Override + public int compare(Map lhs, Map rhs) { + return Float.compare((float) rhs.get("score"), (float) lhs.get("score")); + } + }); + + for (int heatmapY = 0; heatmapY < scores.length; ++heatmapY) { + for (int heatmapX = 0; heatmapX < scores[0].length; ++heatmapX) { + for (int keypointId = 0; keypointId < scores[0][0].length; ++keypointId) { + float score = sigmoid(scores[heatmapY][heatmapX][keypointId]); + if (score < threshold) continue; + + if (scoreIsMaximumInLocalWindow( + keypointId, score, heatmapY, heatmapX, localMaximumRadius, scores)) { + Map res = new HashMap<>(); + res.put("score", score); + res.put("y", heatmapY); + res.put("x", heatmapX); + res.put("partId", keypointId); + pq.add(res); + } + } + } + } - boolean withinNmsRadiusOfCorrespondingPoint(List> poses, - float squaredNmsRadius, - float y, - float x, - int keypointId) { - for (Map pose : poses) { - Map keypoints = (Map) pose.get("keypoints"); - Map correspondingKeypoint = (Map) keypoints.get(keypointId); - float _x = (float) correspondingKeypoint.get("x") * inputSize - x; - float _y = (float) correspondingKeypoint.get("y") * inputSize - y; - float squaredDistance = _x * _x + _y * _y; - if (squaredDistance <= squaredNmsRadius) - return true; + return pq; + } + + boolean scoreIsMaximumInLocalWindow(int keypointId, + float score, + int heatmapY, + int heatmapX, + int localMaximumRadius, + float[][][] scores) { + boolean localMaximum = true; + int height = scores.length; + int width = scores[0].length; + + int yStart = Math.max(heatmapY - localMaximumRadius, 0); + int yEnd = Math.min(heatmapY + localMaximumRadius + 1, height); + for (int yCurrent = yStart; yCurrent < yEnd; ++yCurrent) { + int xStart = Math.max(heatmapX - localMaximumRadius, 0); + int xEnd = Math.min(heatmapX + localMaximumRadius + 1, width); + for (int xCurrent = xStart; xCurrent < xEnd; ++xCurrent) { + if (sigmoid(scores[yCurrent][xCurrent][keypointId]) > score) { + localMaximum = false; + break; + } + } + if (!localMaximum) { + break; + } + } + + return localMaximum; + } + + float[] getImageCoords(Map keypoint, + int outputStride, + int numParts, + float[][][] offsets) { + int heatmapY = (int) keypoint.get("y"); + int heatmapX = (int) keypoint.get("x"); + int keypointId = (int) keypoint.get("partId"); + float offsetY = offsets[heatmapY][heatmapX][keypointId]; + float offsetX = offsets[heatmapY][heatmapX][keypointId + numParts]; + + float y = heatmapY * outputStride + offsetY; + float x = heatmapX * outputStride + offsetX; + + return new float[]{y, x}; + } + + boolean withinNmsRadiusOfCorrespondingPoint(List> poses, + float squaredNmsRadius, + float y, + float x, + int keypointId) { + for (Map pose : poses) { + Map keypoints = (Map) pose.get("keypoints"); + Map correspondingKeypoint = (Map) keypoints.get(keypointId); + float _x = (float) correspondingKeypoint.get("x") * inputSize - x; + float _y = (float) correspondingKeypoint.get("y") * inputSize - y; + float squaredDistance = _x * _x + _y * _y; + if (squaredDistance <= squaredNmsRadius) + return true; + } + + return false; } - return false; - } + Map traverseToTargetKeypoint(int edgeId, + Map sourceKeypoint, + int targetKeypointId, + float[][][] scores, + float[][][] offsets, + int outputStride, + float[][][] displacements) { + int height = scores.length; + int width = scores[0].length; + int numKeypoints = scores[0][0].length; + float sourceKeypointY = (float) sourceKeypoint.get("y") * inputSize; + float sourceKeypointX = (float) sourceKeypoint.get("x") * inputSize; - Map traverseToTargetKeypoint(int edgeId, - Map sourceKeypoint, - int targetKeypointId, - float[][][] scores, - float[][][] offsets, - int outputStride, - float[][][] displacements) { - int height = scores.length; - int width = scores[0].length; - int numKeypoints = scores[0][0].length; - float sourceKeypointY = (float) sourceKeypoint.get("y") * inputSize; - float sourceKeypointX = (float) sourceKeypoint.get("x") * inputSize; - - int[] sourceKeypointIndices = getStridedIndexNearPoint(sourceKeypointY, sourceKeypointX, - outputStride, height, width); - - float[] displacement = getDisplacement(edgeId, sourceKeypointIndices, displacements); - - float[] displacedPoint = new float[]{ - sourceKeypointY + displacement[0], - sourceKeypointX + displacement[1] - }; + int[] sourceKeypointIndices = getStridedIndexNearPoint(sourceKeypointY, sourceKeypointX, + outputStride, height, width); - float[] targetKeypoint = displacedPoint; + float[] displacement = getDisplacement(edgeId, sourceKeypointIndices, displacements); - final int offsetRefineStep = 2; - for (int i = 0; i < offsetRefineStep; i++) { - int[] targetKeypointIndices = getStridedIndexNearPoint(targetKeypoint[0], targetKeypoint[1], - outputStride, height, width); + float[] displacedPoint = new float[]{ + sourceKeypointY + displacement[0], + sourceKeypointX + displacement[1] + }; - int targetKeypointY = targetKeypointIndices[0]; - int targetKeypointX = targetKeypointIndices[1]; + float[] targetKeypoint = displacedPoint; - float offsetY = offsets[targetKeypointY][targetKeypointX][targetKeypointId]; - float offsetX = offsets[targetKeypointY][targetKeypointX][targetKeypointId + numKeypoints]; + final int offsetRefineStep = 2; + for (int i = 0; i < offsetRefineStep; i++) { + int[] targetKeypointIndices = getStridedIndexNearPoint(targetKeypoint[0], targetKeypoint[1], + outputStride, height, width); - targetKeypoint = new float[]{ - targetKeypointY * outputStride + offsetY, - targetKeypointX * outputStride + offsetX - }; - } + int targetKeypointY = targetKeypointIndices[0]; + int targetKeypointX = targetKeypointIndices[1]; - int[] targetKeypointIndices = getStridedIndexNearPoint(targetKeypoint[0], targetKeypoint[1], - outputStride, height, width); + float offsetY = offsets[targetKeypointY][targetKeypointX][targetKeypointId]; + float offsetX = offsets[targetKeypointY][targetKeypointX][targetKeypointId + numKeypoints]; - float score = sigmoid(scores[targetKeypointIndices[0]][targetKeypointIndices[1]][targetKeypointId]); + targetKeypoint = new float[]{ + targetKeypointY * outputStride + offsetY, + targetKeypointX * outputStride + offsetX + }; + } - Map keypoint = new HashMap<>(); - keypoint.put("score", score); - keypoint.put("part", partNames[targetKeypointId]); - keypoint.put("y", targetKeypoint[0] / inputSize); - keypoint.put("x", targetKeypoint[1] / inputSize); + int[] targetKeypointIndices = getStridedIndexNearPoint(targetKeypoint[0], targetKeypoint[1], + outputStride, height, width); - return keypoint; - } + float score = sigmoid(scores[targetKeypointIndices[0]][targetKeypointIndices[1]][targetKeypointId]); - int[] getStridedIndexNearPoint(float _y, float _x, int outputStride, int height, int width) { - int y_ = Math.round(_y / outputStride); - int x_ = Math.round(_x / outputStride); - int y = y_ < 0 ? 0 : y_ > height - 1 ? height - 1 : y_; - int x = x_ < 0 ? 0 : x_ > width - 1 ? width - 1 : x_; - return new int[]{y, x}; - } + Map keypoint = new HashMap<>(); + keypoint.put("score", score); + keypoint.put("part", partNames[targetKeypointId]); + keypoint.put("y", targetKeypoint[0] / inputSize); + keypoint.put("x", targetKeypoint[1] / inputSize); - float[] getDisplacement(int edgeId, int[] keypoint, float[][][] displacements) { - int numEdges = displacements[0][0].length / 2; - int y = keypoint[0]; - int x = keypoint[1]; - return new float[]{displacements[y][x][edgeId], displacements[y][x][edgeId + numEdges]}; - } + return keypoint; + } - float getInstanceScore(Map> keypoints, int numKeypoints) { - float scores = 0; - for (Map.Entry> keypoint : keypoints.entrySet()) - scores += (float) keypoint.getValue().get("score"); - return scores / numKeypoints; - } + int[] getStridedIndexNearPoint(float _y, float _x, int outputStride, int height, int width) { + int y_ = Math.round(_y / outputStride); + int x_ = Math.round(_x / outputStride); + int y = y_ < 0 ? 0 : y_ > height - 1 ? height - 1 : y_; + int x = x_ < 0 ? 0 : x_ > width - 1 ? width - 1 : x_; + return new int[]{y, x}; + } - private float sigmoid(final float x) { - return (float) (1. / (1. + Math.exp(-x))); - } + float[] getDisplacement(int edgeId, int[] keypoint, float[][][] displacements) { + int numEdges = displacements[0][0].length / 2; + int y = keypoint[0]; + int x = keypoint[1]; + return new float[]{displacements[y][x][edgeId], displacements[y][x][edgeId + numEdges]}; + } - private void softmax(final float[] vals) { - float max = Float.NEGATIVE_INFINITY; - for (final float val : vals) { - max = Math.max(max, val); + float getInstanceScore(Map> keypoints, int numKeypoints) { + float scores = 0; + for (Map.Entry> keypoint : keypoints.entrySet()) + scores += (float) keypoint.getValue().get("score"); + return scores / numKeypoints; } - float sum = 0.0f; - for (int i = 0; i < vals.length; ++i) { - vals[i] = (float) Math.exp(vals[i] - max); - sum += vals[i]; + + private float sigmoid(final float x) { + return (float) (1. / (1. + Math.exp(-x))); } - for (int i = 0; i < vals.length; ++i) { - vals[i] = vals[i] / sum; + + private void softmax(final float[] vals) { + float max = Float.NEGATIVE_INFINITY; + for (final float val : vals) { + max = Math.max(max, val); + } + float sum = 0.0f; + for (int i = 0; i < vals.length; ++i) { + vals[i] = (float) Math.exp(vals[i] - max); + sum += vals[i]; + } + for (int i = 0; i < vals.length; ++i) { + vals[i] = vals[i] / sum; + } } - } - private static Matrix getTransformationMatrix(final int srcWidth, - final int srcHeight, - final int dstWidth, - final int dstHeight, - final boolean maintainAspectRatio) { - final Matrix matrix = new Matrix(); - - if (srcWidth != dstWidth || srcHeight != dstHeight) { - final float scaleFactorX = dstWidth / (float) srcWidth; - final float scaleFactorY = dstHeight / (float) srcHeight; - - if (maintainAspectRatio) { - final float scaleFactor = Math.max(scaleFactorX, scaleFactorY); - matrix.postScale(scaleFactor, scaleFactor); - } else { - matrix.postScale(scaleFactorX, scaleFactorY); - } + private static Matrix getTransformationMatrix(final int srcWidth, + final int srcHeight, + final int dstWidth, + final int dstHeight, + final boolean maintainAspectRatio) { + final Matrix matrix = new Matrix(); + + if (srcWidth != dstWidth || srcHeight != dstHeight) { + final float scaleFactorX = dstWidth / (float) srcWidth; + final float scaleFactorY = dstHeight / (float) srcHeight; + + if (maintainAspectRatio) { + final float scaleFactor = Math.max(scaleFactorX, scaleFactorY); + matrix.postScale(scaleFactor, scaleFactor); + } else { + matrix.postScale(scaleFactorX, scaleFactorY); + } + } + + matrix.invert(new Matrix()); + return matrix; + } + + private void close() { + if (tfLite != null) + tfLite.close(); + labels = null; + labelProb = null; } - matrix.invert(new Matrix()); - return matrix; - } - private void close() { - if (tfLite != null) - tfLite.close(); - labels = null; - labelProb = null; - } } diff --git a/example/.gitignore b/example/.gitignore index bc6181c..0fa6b67 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,23 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp .DS_Store -.dart_tool/ +.atom/ +.buildlog/ +.history +.svn/ -.packages -.pub/ +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ -build/ +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ .flutter-plugins .flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ -flutter_export_environment.sh -Flutter.podspec +# Web related +lib/generated_plugin_registrant.dart -# IntelliJ -*.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -.idea/caches \ No newline at end of file +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/.metadata b/example/.metadata index 1634cfb..be0f63d 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,5 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: 3b309bda072a6b326e8aa4591a5836af600923ce - channel: beta + revision: 4cc385b4b84ac2f816d939a49ea1f328c4e0b48e + channel: stable + +project_type: app diff --git a/example/README.md b/example/README.md index 849606f..8e39bb3 100644 --- a/example/README.md +++ b/example/README.md @@ -1,35 +1,16 @@ # tflite_example -Use tflite plugin to run model on images. The image is captured by camera or selected from gallery (with the help of [image_picker](https://pub.dartlang.org/packages/image_picker) plugin). +Demonstrates how to use the tflite plugin. -![](yolo.jpg) +## Getting Started -## Prerequisites +This project is a starting point for a Flutter application. -Create a `assets` folder. From https://github.com/shaqian/flutter_tflite/tree/master/example/assets -dowload the following files and place them in `assets` folder. - - mobilenet_v1_1.0_224.tflite - - mobilenet_v1_1.0_224.txt - - ssd_mobilenet.tflite - - ssd_mobilenet.txt - - yolov2_tiny.tflite - - yolov2_tiny.txt - - deeplabv3_257_mv_gpu.tflite - - deeplabv3_257_mv_gpu.txt - - posenet_mv1_075_float_from_checkpoints.tflite +A few resources to get you started if this is your first Flutter project: -## Install +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) -``` -flutter packages get -``` - -## Run - -``` -flutter run -``` - -## Caveat - -```recognizeImageBinary(image)``` (sample code for ```runModelOnBinary```) is slow on iOS when decoding image due to a [known issue](https://github.com/brendan-duncan/image/issues/55) with image package. +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore index 65b7315..6f56801 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -1,10 +1,13 @@ -*.iml -*.class -.gradle +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat /local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/.project b/example/android/.project index 3964dd3..e31d95a 100644 --- a/example/android/.project +++ b/example/android/.project @@ -1,7 +1,7 @@ - android - Project android created by Buildship. + android_ + Project android_ created by Buildship. @@ -14,4 +14,15 @@ org.eclipse.buildship.core.gradleprojectnature + + + 1635544201843 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs index e889521..016f0a1 100644 --- a/example/android/.settings/org.eclipse.buildship.core.prefs +++ b/example/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 +gradle.user.home= +java.home=C\:/Program Files/Eclipse Foundation/jdk-17.0.0.35-hotspot +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/example/android/app/.classpath b/example/android/app/.classpath deleted file mode 100644 index eb19361..0000000 --- a/example/android/app/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/example/android/app/.project b/example/android/app/.project deleted file mode 100644 index ac485d7..0000000 --- a/example/android/app/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - app - Project app created by Buildship. - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/example/android/app/.settings/org.eclipse.buildship.core.prefs b/example/android/app/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index b1886ad..0000000 --- a/example/android/app/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,2 +0,0 @@ -connection.project.dir=.. -eclipse.preferences.version=1 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index a627ccf..3e59f62 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -25,24 +25,20 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 31 - lintOptions { - disable 'InvalidPackage' - } - - aaptOptions { - noCompress 'tflite' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "sq.flutter.tfliteexample" - minSdkVersion 19 - targetSdkVersion 28 + applicationId "sq.flutter.tflite_example" + minSdkVersion 17 + targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } buildTypes { @@ -57,9 +53,3 @@ android { flutter { source '../..' } - -dependencies { - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' -} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..94529f8 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index aa3336f..08c92d0 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,39 +1,33 @@ - - - - - - + - + + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> + + + diff --git a/example/android/app/src/main/java/sq/flutter/tflite_example/MainActivity.java b/example/android/app/src/main/java/sq/flutter/tflite_example/MainActivity.java new file mode 100644 index 0000000..f21902a --- /dev/null +++ b/example/android/app/src/main/java/sq/flutter/tflite_example/MainActivity.java @@ -0,0 +1,6 @@ +package sq.flutter.tflite_example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/example/android/app/src/main/java/sq/flutter/tfliteexample/MainActivity.java b/example/android/app/src/main/java/sq/flutter/tfliteexample/MainActivity.java deleted file mode 100644 index 968a340..0000000 --- a/example/android/app/src/main/java/sq/flutter/tfliteexample/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package sq.flutter.tfliteexample; - -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; - -public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } -} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 00fa441..d74aa35 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ - + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..94529f8 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle index 83f114c..8bd9635 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,26 +1,24 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:4.1.3' } } allprojects { repositories { google() - jcenter() + mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { project.evaluationDependsOn(':app') } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 29bf260..94adc3a 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,3 @@ org.gradle.jvmargs=-Xmx1536M -target-platform=android-arm64 android.useAndroidX=true android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 46510f3..b8793d3 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Mar 28 00:33:22 ICT 2020 +#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 5a2f14f..44e62bc 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,15 +1,11 @@ include ':app' -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle deleted file mode 100644 index e7b4def..0000000 --- a/example/android/settings_aar.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/example/ios/.gitignore b/example/ios/.gitignore index 79cc4da..151026b 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -1,45 +1,33 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser *.mode1v3 *.mode2v3 +*.moved-aside +*.pbxuser *.perspectivev3 - -!default.pbxuser +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. !default.mode1v3 !default.mode2v3 +!default.pbxuser !default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/app.flx -/Flutter/app.zip -/Flutter/flutter_assets/ -/Flutter/App.framework -/Flutter/Flutter.framework -/Flutter/Generated.xcconfig -/ServiceDefinitions.json - -Pods/ -.symlinks/ diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 9367d48..8d4492f 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba1..592ceee 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 399e934..592ceee 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile deleted file mode 100644 index f7d6a5e..0000000 --- a/example/ios/Podfile +++ /dev/null @@ -1,38 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '9.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock deleted file mode 100644 index 6286399..0000000 --- a/example/ios/Podfile.lock +++ /dev/null @@ -1,35 +0,0 @@ -PODS: - - Flutter (1.0.0) - - image_picker (0.0.1): - - Flutter - - TensorFlowLiteC (2.2.0) - - tflite (1.1.2): - - Flutter - - TensorFlowLiteC - -DEPENDENCIES: - - Flutter (from `Flutter`) - - image_picker (from `.symlinks/plugins/image_picker/ios`) - - tflite (from `.symlinks/plugins/tflite/ios`) - -SPEC REPOS: - trunk: - - TensorFlowLiteC - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - image_picker: - :path: ".symlinks/plugins/image_picker/ios" - tflite: - :path: ".symlinks/plugins/tflite/ios" - -SPEC CHECKSUMS: - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c - image_picker: a211f28b95a560433c00f5cd3773f4710a20404d - TensorFlowLiteC: b3ab9e867b0b71052ca102a32a786555b330b02e - tflite: f0403a894740019d63ab5662253bba5b2dd37296 - -PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d - -COCOAPODS: 1.10.1 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 1252660..bf7a2c6 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,13 +9,11 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - A8FCB07931B147D0C738D807 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A4A034B01AB21E851714E03C /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -38,7 +36,6 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 864E0E2308AE5F3A9409E901 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -47,8 +44,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - A4A034B01AB21E851714E03C /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - E0C0C115F9024C6ADB3B2DB5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,30 +51,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A8FCB07931B147D0C738D807 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 7670CC45CF9B055E20C18D9C /* Frameworks */ = { - isa = PBXGroup; - children = ( - A4A034B01AB21E851714E03C /* libPods-Runner.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 8EE3D73475BA2048B61051C2 /* Pods */ = { - isa = PBXGroup; - children = ( - E0C0C115F9024C6ADB3B2DB5 /* Pods-Runner.debug.xcconfig */, - 864E0E2308AE5F3A9409E901 /* Pods-Runner.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -97,8 +74,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 8EE3D73475BA2048B61051C2 /* Pods */, - 7670CC45CF9B055E20C18D9C /* Frameworks */, + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; @@ -141,7 +117,6 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - FAEC5F0CFA3366178E53C4C5 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -164,21 +139,19 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0910; - ORGANIZATIONNAME = "The Chromium Authors"; + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = ZJG3P98JS9; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); @@ -199,7 +172,6 @@ files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -236,24 +208,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - FAEC5F0CFA3366178E53C4C5 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -289,6 +243,71 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = sq.flutter.tfliteExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -302,12 +321,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -334,7 +355,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -355,12 +376,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -381,10 +404,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; - ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -396,27 +419,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ZJG3P98JS9; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "'${SRCROOT}/Pods/TensorFlowLite/Frameworks/tensorflow_lite.framework/Headers'", - "\"${PODS_ROOT}/Headers/Public\"", - "\"${PODS_ROOT}/Headers/Public/Flutter\"", - "\"${PODS_ROOT}/Headers/Public/TensorFlowLite\"", - "\"${PODS_ROOT}/Headers/Public/tflite\"", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); PRODUCT_BUNDLE_IDENTIFIER = sq.flutter.tfliteExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; @@ -429,27 +434,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ZJG3P98JS9; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)", - "'${SRCROOT}/Pods/TensorFlowLite/Frameworks/tensorflow_lite.framework/Headers'", - "\"${PODS_ROOT}/Headers/Public\"", - "\"${PODS_ROOT}/Headers/Public/Flutter\"", - "\"${PODS_ROOT}/Headers/Public/TensorFlowLite\"", - "\"${PODS_ROOT}/Headers/Public/tflite\"", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); PRODUCT_BUNDLE_IDENTIFIER = sq.flutter.tfliteExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; @@ -464,6 +451,7 @@ buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -473,6 +461,7 @@ buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 6c78381..a28140c 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + - - + + + + - - diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m index 59a72e9..70e8393 100644 --- a/example/ios/Runner/AppDelegate.m +++ b/example/ios/Runner/AppDelegate.m @@ -1,5 +1,5 @@ -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" @implementation AppDelegate diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 3d43d11e66f4de3da27ed045ca4fe38ad8b48094..dc9ada4725e9b0ddb1deab583e5b5102493aa332 100644 GIT binary patch literal 10932 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 P-1+_A+?hGkL;m(J7kc&K diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 3fef927..bb0a45e 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -41,11 +41,5 @@ UIViewControllerBasedStatusBarAppearance - NSPhotoLibraryUsageDescription - We need your permission to access photo gallery - NSCameraUsageDescription - We need your permission to use phone camera - NSMicrophoneUsageDescription - We need your permission to use microsphone diff --git a/example/lib/main.dart b/example/lib/main.dart index 8a626d0..963577f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,7 +9,7 @@ import 'package:image/image.dart' as img; import 'package:tflite/tflite.dart'; import 'package:image_picker/image_picker.dart'; -void main() => runApp(new App()); +void main() => runApp(const App()); const String mobile = "MobileNet"; const String ssd = "SSD MobileNet"; @@ -18,6 +18,8 @@ const String deeplab = "DeepLab"; const String posenet = "PoseNet"; class App extends StatelessWidget { + const App({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return MaterialApp( @@ -27,8 +29,10 @@ class App extends StatelessWidget { } class MyApp extends StatefulWidget { + MyApp({Key? key}) : super(key: key); + @override - _MyAppState createState() => new _MyAppState(); + _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { @@ -40,12 +44,12 @@ class _MyAppState extends State { bool _busy = false; Future predictImagePicker() async { - var image = await ImagePicker().getImage(source: ImageSource.gallery); + var image = await ImagePicker().pickImage(source: ImageSource.gallery); if (image == null) return; setState(() { _busy = true; }); - var file = File(image.path); + File file = File(image.path); predictImage(file); } @@ -70,9 +74,7 @@ class _MyAppState extends State { // await recognizeImageBinary(image); } - new FileImage(image) - .resolve(new ImageConfiguration()) - .addListener(ImageStreamListener((ImageInfo info, bool _) { + FileImage(image).resolve(const ImageConfiguration()).addListener(ImageStreamListener((ImageInfo info, bool _) { setState(() { _imageHeight = info.image.height.toDouble(); _imageWidth = info.image.width.toDouble(); @@ -137,14 +139,13 @@ class _MyAppState extends State { // useGpuDelegate: true, ); } - print(res); + debugPrint(res); } on PlatformException { - print('Failed to load model.'); + debugPrint('Failed to load model.'); } } - Uint8List imageToByteListFloat32( - img.Image image, int inputSize, double mean, double std) { + Uint8List imageToByteListFloat32(img.Image image, int inputSize, double mean, double std) { var convertedBytes = Float32List(1 * inputSize * inputSize * 3); var buffer = Float32List.view(convertedBytes.buffer); int pixelIndex = 0; @@ -175,7 +176,7 @@ class _MyAppState extends State { } Future recognizeImage(File image) async { - int startTime = new DateTime.now().millisecondsSinceEpoch; + int startTime = DateTime.now().millisecondsSinceEpoch; var recognitions = await Tflite.runModelOnImage( path: image.path, numResults: 6, @@ -184,14 +185,14 @@ class _MyAppState extends State { imageStd: 127.5, ); setState(() { - _recognitions = recognitions!; + _recognitions = recognitions; }); - int endTime = new DateTime.now().millisecondsSinceEpoch; - print("Inference took ${endTime - startTime}ms"); + int endTime = DateTime.now().millisecondsSinceEpoch; + debugPrint("Inference took ${endTime - startTime}ms"); } Future recognizeImageBinary(File image) async { - int startTime = new DateTime.now().millisecondsSinceEpoch; + int startTime = DateTime.now().millisecondsSinceEpoch; var imageBytes = (await rootBundle.load(image.path)).buffer; img.Image oriImage = img.decodeJpg(imageBytes.asUint8List()); img.Image resizedImage = img.copyResize(oriImage, height: 224, width: 224); @@ -201,14 +202,14 @@ class _MyAppState extends State { threshold: 0.05, ); setState(() { - _recognitions = recognitions!; + _recognitions = recognitions; }); - int endTime = new DateTime.now().millisecondsSinceEpoch; - print("Inference took ${endTime - startTime}ms"); + int endTime = DateTime.now().millisecondsSinceEpoch; + debugPrint("Inference took ${endTime - startTime}ms"); } Future yolov2Tiny(File image) async { - int startTime = new DateTime.now().millisecondsSinceEpoch; + int startTime = DateTime.now().millisecondsSinceEpoch; var recognitions = await Tflite.detectObjectOnImage( path: image.path, model: "YOLO", @@ -227,14 +228,14 @@ class _MyAppState extends State { // numResultsPerClass: 1, // ); setState(() { - _recognitions = recognitions!; + _recognitions = recognitions; }); - int endTime = new DateTime.now().millisecondsSinceEpoch; - print("Inference took ${endTime - startTime}ms"); + int endTime = DateTime.now().millisecondsSinceEpoch; + debugPrint("Inference took ${endTime - startTime}ms"); } Future ssdMobileNet(File image) async { - int startTime = new DateTime.now().millisecondsSinceEpoch; + int startTime = DateTime.now().millisecondsSinceEpoch; var recognitions = await Tflite.detectObjectOnImage( path: image.path, numResultsPerClass: 1, @@ -247,14 +248,14 @@ class _MyAppState extends State { // numResultsPerClass: 1, // ); setState(() { - _recognitions = recognitions!; + _recognitions = recognitions; }); - int endTime = new DateTime.now().millisecondsSinceEpoch; - print("Inference took ${endTime - startTime}ms"); + int endTime = DateTime.now().millisecondsSinceEpoch; + debugPrint("Inference took ${endTime - startTime}ms"); } Future segmentMobileNet(File image) async { - int startTime = new DateTime.now().millisecondsSinceEpoch; + int startTime = DateTime.now().millisecondsSinceEpoch; var recognitions = await Tflite.runSegmentationOnImage( path: image.path, imageMean: 127.5, @@ -262,26 +263,26 @@ class _MyAppState extends State { ); setState(() { - _recognitions = recognitions!; + _recognitions = recognitions; }); - int endTime = new DateTime.now().millisecondsSinceEpoch; - print("Inference took ${endTime - startTime}"); + int endTime = DateTime.now().millisecondsSinceEpoch; + debugPrint("Inference took ${endTime - startTime}"); } Future poseNet(File image) async { - int startTime = new DateTime.now().millisecondsSinceEpoch; + int startTime = DateTime.now().millisecondsSinceEpoch; var recognitions = await Tflite.runPoseNetOnImage( path: image.path, numResults: 2, ); - print(recognitions); + debugPrint(recognitions.toString()); setState(() { - _recognitions = recognitions!; + _recognitions = recognitions; }); - int endTime = new DateTime.now().millisecondsSinceEpoch; - print("Inference took ${endTime - startTime}ms"); + int endTime = DateTime.now().millisecondsSinceEpoch; + debugPrint("Inference took ${endTime - startTime}ms"); } onSelect(model) async { @@ -292,12 +293,14 @@ class _MyAppState extends State { }); await loadModel(); - if (_image != null) + if (_image != null) { + predictImage(_image); predictImage(_image); - else + } else { setState(() { _busy = false; }); + } } List renderBoxes(Size screen) { @@ -306,7 +309,7 @@ class _MyAppState extends State { double factorX = screen.width; double factorY = _imageHeight! / _imageWidth! * screen.width; - Color blue = Color.fromRGBO(37, 213, 253, 1.0); + Color blue = const Color.fromRGBO(37, 213, 253, 1.0); return _recognitions!.map((re) { return Positioned( left: re["rect"]["x"] * factorX, @@ -315,7 +318,7 @@ class _MyAppState extends State { height: re["rect"]["h"] * factorY, child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderRadius: const BorderRadius.all(Radius.circular(8.0)), border: Border.all( color: blue, width: 2, @@ -343,8 +346,7 @@ class _MyAppState extends State { var lists = []; _recognitions!.forEach((re) { - var color = Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0) - .withOpacity(1.0); + var color = Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0).withOpacity(1.0); var list = re["keypoints"].values.map((k) { return Positioned( left: k["x"] * factorX - 6, @@ -378,13 +380,10 @@ class _MyAppState extends State { left: 0.0, width: size.width, child: _image == null - ? Text('No image selected.') + ? const Text('No image selected.') : Container( - decoration: BoxDecoration( - image: DecorationImage( - alignment: Alignment.topCenter, - image: MemoryImage(_recognitions!.first), - fit: BoxFit.fill)), + decoration: + BoxDecoration(image: DecorationImage(alignment: Alignment.topCenter, image: MemoryImage(_image!.readAsBytesSync()), fit: BoxFit.fill)), child: Opacity(opacity: 0.3, child: Image.file(_image!))), )); } else { @@ -392,8 +391,7 @@ class _MyAppState extends State { top: 0.0, left: 0.0, width: size.width, - child: - _image == null ? Text('No image selected.') : Image.file(_image!), + child: _image == null ? const Text('No image selected.') : Image.file(_image!), )); } @@ -468,7 +466,7 @@ class _MyAppState extends State { floatingActionButton: FloatingActionButton( onPressed: predictImagePicker, tooltip: 'Pick Image', - child: Icon(Icons.image), + child: const Icon(Icons.image), ), ); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index a9eab93..2c66d4d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,39 +1,50 @@ name: tflite_example description: Demonstrates how to use the tflite plugin. -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# Read more about versioning at semver.org. -version: 1.0.0+1 +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ">=2.12.0 <3.0.0" +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter + tflite: + # When depending on this package from a real application you should use: + # tflite: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 + cupertino_icons: ^1.0.2 + image_picker: ^0.8.4+3 + image: ^3.0.8 dev_dependencies: flutter_test: sdk: flutter - image_picker: ^0.7.5+2 - - image: ^3.0.2 + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 - tflite: - path: ../ - - test: ^1.12.0 # For information on the generic Dart part of this file, see the -# following page: https://www.dartlang.org/tools/pub/pubspec +# following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: @@ -44,23 +55,15 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - assets: - - assets/mobilenet_v1_1.0_224.txt - - assets/mobilenet_v1_1.0_224.tflite - - assets/yolov2_tiny.tflite - - assets/yolov2_tiny.txt - - assets/ssd_mobilenet.tflite - - assets/ssd_mobilenet.txt - - assets/deeplabv3_257_mv_gpu.tflite - - assets/deeplabv3_257_mv_gpu.txt - - assets/posenet_mv1_075_float_from_checkpoints.tflite - + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.io/assets-and-images/#resolution-aware. + # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see - # https://flutter.io/assets-and-images/#from-packages + # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a @@ -80,4 +83,4 @@ flutter: # weight: 700 # # For details regarding fonts from package dependencies, - # see https://flutter.io/custom-fonts/#from-packages + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index a921c2e..cdc58a7 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -1,8 +1,9 @@ // This is a basic Flutter widget test. -// To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter -// provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to -// find child widgets in the widget tree, read text, and verify that the values of widget properties -// are correct. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -12,14 +13,14 @@ import 'package:tflite_example/main.dart'; void main() { testWidgets('Verify Platform version', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(new MyApp()); + await tester.pumpWidget(MyApp()); // Verify that platform version is retrieved. expect( - find.byWidgetPredicate( - (Widget widget) => - widget is Text && widget.data!.startsWith('Running on:'), - ), - findsOneWidget); + find.byWidgetPredicate( + (Widget widget) => widget is Text && widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); }); } diff --git a/example/tflite_example_android.iml b/example/tflite_example_android.iml deleted file mode 100644 index b050030..0000000 --- a/example/tflite_example_android.iml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 0000000..7e667c6 --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + tflite_example + + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 0000000..9626c7e --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "tflite_example", + "short_name": "tflite_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Demonstrates how to use the tflite plugin.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/example/yolo.jpg b/example/yolo.jpg deleted file mode 100644 index 33f6714582e3c9e5899a2cfdcef97841b18c3aab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71470 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 diff --git a/lib/tflite.dart b/lib/tflite.dart index 66039fd..5c82a8d 100644 --- a/lib/tflite.dart +++ b/lib/tflite.dart @@ -1,36 +1,26 @@ import 'dart:async'; + +import 'package:flutter/services.dart'; import 'dart:typed_data'; import 'dart:ui' show Color; -import 'package:flutter/services.dart'; class Tflite { - static const MethodChannel _channel = const MethodChannel('tflite'); + static const MethodChannel _channel = MethodChannel('tflite'); + + static Future get platformVersion async { + final String? version = await _channel.invokeMethod('getPlatformVersion'); + return version; + } - static Future loadModel( - {required String model, - String labels = "", - int numThreads = 1, - bool isAsset = true, - bool useGpuDelegate = false}) async { + static Future loadModel({required String model, String labels = "", int numThreads = 1, bool isAsset = true, bool useGpuDelegate = false}) async { return await _channel.invokeMethod( 'loadModel', - { - "model": model, - "labels": labels, - "numThreads": numThreads, - "isAsset": isAsset, - 'useGpuDelegate': useGpuDelegate - }, + {"model": model, "labels": labels, "numThreads": numThreads, "isAsset": isAsset, 'useGpuDelegate': useGpuDelegate}, ); } static Future runModelOnImage( - {required String path, - double imageMean = 117.0, - double imageStd = 1.0, - int numResults = 5, - double threshold = 0.1, - bool asynch = true}) async { + {required String path, double imageMean = 117.0, double imageStd = 1.0, int numResults = 5, double threshold = 0.1, bool asynch = true}) async { return await _channel.invokeMethod( 'runModelOnImage', { @@ -44,11 +34,7 @@ class Tflite { ); } - static Future runModelOnBinary( - {required Uint8List binary, - int numResults = 5, - double threshold = 0.1, - bool asynch = true}) async { + static Future runModelOnBinary({required Uint8List binary, int numResults = 5, double threshold = 0.1, bool asynch = true}) async { return await _channel.invokeMethod( 'runModelOnBinary', { @@ -86,18 +72,7 @@ class Tflite { ); } - static const anchors = [ - 0.57273, - 0.677385, - 1.87446, - 2.06253, - 3.33843, - 5.47434, - 7.88282, - 3.52778, - 9.77052, - 9.16828 - ]; + static const anchors = [0.57273, 0.677385, 1.87446, 2.06253, 3.33843, 5.47434, 7.88282, 3.52778, 9.77052, 9.16828]; static Future detectObjectOnImage({ required String path, @@ -164,7 +139,7 @@ class Tflite { double imageStd = 127.5, double threshold = 0.1, int numResultsPerClass = 5, - int rotation: 90, // Android only + int rotation = 90, // Android only // Used in YOLO only List anchors = anchors, int blockSize = 32, @@ -196,11 +171,7 @@ class Tflite { } static Future runPix2PixOnImage( - {required String path, - double imageMean = 0, - double imageStd = 255.0, - String outputType = "png", - bool asynch = true}) async { + {required String path, double imageMean = 0, double imageStd = 255.0, String outputType = "png", bool asynch = true}) async { return await _channel.invokeMethod( 'runPix2PixOnImage', { @@ -213,10 +184,7 @@ class Tflite { ); } - static Future runPix2PixOnBinary( - {required Uint8List binary, - String outputType = "png", - bool asynch = true}) async { + static Future runPix2PixOnBinary({required Uint8List binary, String outputType = "png", bool asynch = true}) async { return await _channel.invokeMethod( 'runPix2PixOnBinary', { @@ -233,7 +201,7 @@ class Tflite { int imageWidth = 720, double imageMean = 0, double imageStd = 255.0, - int rotation: 90, // Android only + int rotation = 90, // Android only String outputType = "png", bool asynch = true, }) async { @@ -254,36 +222,31 @@ class Tflite { // https://github.com/meetshah1995/pytorch-semseg/blob/master/ptsemseg/loader/pascal_voc_loader.py static List pascalVOCLabelColors = [ - Color.fromARGB(255, 0, 0, 0).value, // background - Color.fromARGB(255, 128, 0, 0).value, // aeroplane - Color.fromARGB(255, 0, 128, 0).value, // biyclce - Color.fromARGB(255, 128, 128, 0).value, // bird - Color.fromARGB(255, 0, 0, 128).value, // boat - Color.fromARGB(255, 128, 0, 128).value, // bottle - Color.fromARGB(255, 0, 128, 128).value, // bus - Color.fromARGB(255, 128, 128, 128).value, // car - Color.fromARGB(255, 64, 0, 0).value, // cat - Color.fromARGB(255, 192, 0, 0).value, // chair - Color.fromARGB(255, 64, 128, 0).value, // cow - Color.fromARGB(255, 192, 128, 0).value, // diningtable - Color.fromARGB(255, 64, 0, 128).value, // dog - Color.fromARGB(255, 192, 0, 128).value, // horse - Color.fromARGB(255, 64, 128, 128).value, // motorbike - Color.fromARGB(255, 192, 128, 128).value, // person - Color.fromARGB(255, 0, 64, 0).value, // potted plant - Color.fromARGB(255, 128, 64, 0).value, // sheep - Color.fromARGB(255, 0, 192, 0).value, // sofa - Color.fromARGB(255, 128, 192, 0).value, // train - Color.fromARGB(255, 0, 64, 128).value, // tv-monitor + const Color.fromARGB(255, 0, 0, 0).value, // background + const Color.fromARGB(255, 128, 0, 0).value, // aeroplane + const Color.fromARGB(255, 0, 128, 0).value, // biyclce + const Color.fromARGB(255, 128, 128, 0).value, // bird + const Color.fromARGB(255, 0, 0, 128).value, // boat + const Color.fromARGB(255, 128, 0, 128).value, // bottle + const Color.fromARGB(255, 0, 128, 128).value, // bus + const Color.fromARGB(255, 128, 128, 128).value, // car + const Color.fromARGB(255, 64, 0, 0).value, // cat + const Color.fromARGB(255, 192, 0, 0).value, // chair + const Color.fromARGB(255, 64, 128, 0).value, // cow + const Color.fromARGB(255, 192, 128, 0).value, // diningtable + const Color.fromARGB(255, 64, 0, 128).value, // dog + const Color.fromARGB(255, 192, 0, 128).value, // horse + const Color.fromARGB(255, 64, 128, 128).value, // motorbike + const Color.fromARGB(255, 192, 128, 128).value, // person + const Color.fromARGB(255, 0, 64, 0).value, // potted plant + const Color.fromARGB(255, 128, 64, 0).value, // sheep + const Color.fromARGB(255, 0, 192, 0).value, // sofa + const Color.fromARGB(255, 128, 192, 0).value, // train + const Color.fromARGB(255, 0, 64, 128).value, // tv-monitor ]; static Future runSegmentationOnImage( - {required String path, - double imageMean = 0, - double imageStd = 255.0, - List? labelColors, - String outputType = "png", - bool asynch = true}) async { + {required String path, double imageMean = 0, double imageStd = 255.0, List? labelColors, String outputType = "png", bool asynch = true}) async { return await _channel.invokeMethod( 'runSegmentationOnImage', { @@ -297,11 +260,7 @@ class Tflite { ); } - static Future runSegmentationOnBinary( - {required Uint8List binary, - List? labelColors, - String outputType = "png", - bool asynch = true}) async { + static Future runSegmentationOnBinary({required Uint8List binary, List? labelColors, String outputType = "png", bool asynch = true}) async { return await _channel.invokeMethod( 'runSegmentationOnBinary', { @@ -319,7 +278,7 @@ class Tflite { int imageWidth = 720, double imageMean = 0, double imageStd = 255.0, - int rotation: 90, // Android only + int rotation = 90, // Android only List? labelColors, String outputType = "png", bool asynch = true}) async { @@ -362,11 +321,7 @@ class Tflite { } static Future runPoseNetOnBinary( - {required Uint8List binary, - int numResults = 5, - double threshold = 0.5, - int nmsRadius = 20, - bool asynch = true}) async { + {required Uint8List binary, int numResults = 5, double threshold = 0.5, int nmsRadius = 20, bool asynch = true}) async { return await _channel.invokeMethod( 'runPoseNetOnBinary', { @@ -385,7 +340,7 @@ class Tflite { int imageWidth = 720, double imageMean = 127.5, double imageStd = 127.5, - int rotation: 90, // Android only + int rotation = 90, // Android only int numResults = 5, double threshold = 0.5, int nmsRadius = 20, diff --git a/lib/tflite_web.dart b/lib/tflite_web.dart new file mode 100644 index 0000000..d357b1b --- /dev/null +++ b/lib/tflite_web.dart @@ -0,0 +1,44 @@ +import 'dart:async'; +// In order to *not* need this ignore, consider extracting the "web" version +// of your plugin as a separate package, instead of inlining it in the same +// package as the core of your plugin. +// ignore: avoid_web_libraries_in_flutter +import 'dart:html' as html show window; + +import 'package:flutter/services.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +/// A web implementation of the Tflite plugin. +class TfliteWeb { + static void registerWith(Registrar registrar) { + final MethodChannel channel = MethodChannel( + 'tflite', + const StandardMethodCodec(), + registrar, + ); + + final pluginInstance = TfliteWeb(); + channel.setMethodCallHandler(pluginInstance.handleMethodCall); + } + + /// Handles method calls over the MethodChannel of this plugin. + /// Note: Check the "federated" architecture for a new way of doing this: + /// https://flutter.dev/go/federated-plugins + Future handleMethodCall(MethodCall call) async { + switch (call.method) { + case 'getPlatformVersion': + return getPlatformVersion(); + default: + throw PlatformException( + code: 'Unimplemented', + details: 'tflite for web doesn\'t implement \'${call.method}\'', + ); + } + } + + /// Returns a [String] containing the version of the platform. + Future getPlatformVersion() { + final version = html.window.navigator.userAgent; + return Future.value(version); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index eb52b2c..ac2b02c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: tflite -description: A Flutter plugin for accessing TensorFlow Lite. Supports both iOS and Android. +description: A Flutter plugin for accessing TensorFlow Lite. Supports both iOS and Android, Web under development. version: 1.1.3 homepage: https://github.com/shaqian/flutter_tflite From 7a20c6120c2fbc2e0808cb944786a52824040566 Mon Sep 17 00:00:00 2001 From: puspharaj Date: Thu, 4 Nov 2021 10:12:01 +0530 Subject: [PATCH 4/4] example pubspec updated with old one --- .history/example/pubspec_20211030063242.yaml | 86 ++++++++++++++++++ .history/example/pubspec_20211104101124.yaml | 94 ++++++++++++++++++++ example/pubspec.yaml | 14 ++- 3 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 .history/example/pubspec_20211030063242.yaml create mode 100644 .history/example/pubspec_20211104101124.yaml diff --git a/.history/example/pubspec_20211030063242.yaml b/.history/example/pubspec_20211030063242.yaml new file mode 100644 index 0000000..2c66d4d --- /dev/null +++ b/.history/example/pubspec_20211030063242.yaml @@ -0,0 +1,86 @@ +name: tflite_example +description: Demonstrates how to use the tflite plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.12.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + tflite: + # When depending on this package from a real application you should use: + # tflite: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + image_picker: ^0.8.4+3 + image: ^3.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/.history/example/pubspec_20211104101124.yaml b/.history/example/pubspec_20211104101124.yaml new file mode 100644 index 0000000..7449391 --- /dev/null +++ b/.history/example/pubspec_20211104101124.yaml @@ -0,0 +1,94 @@ +name: tflite_example +description: Demonstrates how to use the tflite plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.12.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + tflite: + # When depending on this package from a real application you should use: + # tflite: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + image_picker: ^0.8.4+3 + image: ^3.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/mobilenet_v1_1.0_224.txt + - assets/mobilenet_v1_1.0_224.tflite + - assets/yolov2_tiny.tflite + - assets/yolov2_tiny.txt + - assets/ssd_mobilenet.tflite + - assets/ssd_mobilenet.txt + - assets/deeplabv3_257_mv_gpu.tflite + - assets/deeplabv3_257_mv_gpu.txt + - assets/posenet_mv1_075_float_from_checkpoints.tflite + + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2c66d4d..7449391 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -55,9 +55,17 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/mobilenet_v1_1.0_224.txt + - assets/mobilenet_v1_1.0_224.tflite + - assets/yolov2_tiny.tflite + - assets/yolov2_tiny.txt + - assets/ssd_mobilenet.tflite + - assets/ssd_mobilenet.txt + - assets/deeplabv3_257_mv_gpu.tflite + - assets/deeplabv3_257_mv_gpu.txt + - assets/posenet_mv1_075_float_from_checkpoints.tflite + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware.