Skip to content

Commit

Permalink
[firebase_ml_vision] v2 embedding API (#275)
Browse files Browse the repository at this point in the history
  • Loading branch information
bparrishMines committed Oct 31, 2019
1 parent cf038c8 commit 921aad0
Show file tree
Hide file tree
Showing 16 changed files with 371 additions and 210 deletions.
5 changes: 5 additions & 0 deletions packages/firebase_ml_vision/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.9.3

* Support v2 embedding. This plugin will remain compatible with the original embedding and won't
require app migration.

## 0.9.2+3

* Use `BoxDecoration` `const` constructor in example app.
Expand Down
26 changes: 26 additions & 0 deletions packages/firebase_ml_vision/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,30 @@ android {
}
}

// TODO(bparrishMines): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348
afterEvaluate {
def containsEmbeddingDependencies = false
for (def configuration : configurations.all) {
for (def dependency : configuration.dependencies) {
if (dependency.group == 'io.flutter' &&
dependency.name.startsWith('flutter_embedding') &&
dependency.isTransitive())
{
containsEmbeddingDependencies = true
break
}
}
}
if (!containsEmbeddingDependencies) {
android {
dependencies {
def lifecycle_version = "1.1.1"
compileOnly "android.arch.lifecycle:runtime:$lifecycle_version"
compileOnly "android.arch.lifecycle:common:$lifecycle_version"
compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version"
}
}
}
}

apply from: file("./user-agent.gradle")
2 changes: 0 additions & 2 deletions packages/firebase_ml_vision/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
org.gradle.jvmargs=-Xmx1536M
android.enableJetifier=true
android.useAndroidX=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package io.flutter.plugins.firebasemlvision;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.util.SparseArray;
import androidx.exifinterface.media.ExifInterface;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import java.io.File;
import java.io.IOException;
import java.util.Map;

class FirebaseMlVisionHandler implements MethodChannel.MethodCallHandler {
private final SparseArray<Detector> detectors = new SparseArray<>();
private final Context applicationContext;

FirebaseMlVisionHandler(Context applicationContext) {
this.applicationContext = applicationContext;
}

@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
switch (call.method) {
case "BarcodeDetector#detectInImage":
case "FaceDetector#processImage":
case "ImageLabeler#processImage":
case "TextRecognizer#processImage":
handleDetection(call, result);
break;
case "BarcodeDetector#close":
case "FaceDetector#close":
case "ImageLabeler#close":
case "TextRecognizer#close":
closeDetector(call, result);
break;
default:
result.notImplemented();
}
}

private void handleDetection(MethodCall call, MethodChannel.Result result) {
Map<String, Object> options = call.argument("options");

FirebaseVisionImage image;
Map<String, Object> imageData = call.arguments();
try {
image = dataToVisionImage(imageData);
} catch (IOException exception) {
result.error("MLVisionDetectorIOError", exception.getLocalizedMessage(), null);
return;
}

Detector detector = getDetector(call);
if (detector == null) {
switch (call.method.split("#")[0]) {
case "BarcodeDetector":
detector = new BarcodeDetector(FirebaseVision.getInstance(), options);
break;
case "FaceDetector":
detector = new FaceDetector(FirebaseVision.getInstance(), options);
break;
case "ImageLabeler":
detector = new ImageLabeler(FirebaseVision.getInstance(), options);
break;
case "TextRecognizer":
detector = new TextRecognizer(FirebaseVision.getInstance(), options);
break;
}

final Integer handle = call.argument("handle");
addDetector(handle, detector);
}

detector.handleDetection(image, result);
}

private void closeDetector(final MethodCall call, final MethodChannel.Result result) {
final Detector detector = getDetector(call);

if (detector == null) {
final Integer handle = call.argument("handle");
final String message = String.format("Object for handle does not exists: %s", handle);
throw new IllegalArgumentException(message);
}

try {
detector.close();
result.success(null);
} catch (IOException e) {
final String code = String.format("%sIOError", detector.getClass().getSimpleName());
result.error(code, e.getLocalizedMessage(), null);
} finally {
final Integer handle = call.argument("handle");
detectors.remove(handle);
}
}

private FirebaseVisionImage dataToVisionImage(Map<String, Object> imageData) throws IOException {
String imageType = (String) imageData.get("type");
assert imageType != null;

switch (imageType) {
case "file":
final String imageFilePath = (String) imageData.get("path");
final int rotation = getImageExifOrientation(imageFilePath);

if (rotation == 0) {
File file = new File(imageFilePath);
return FirebaseVisionImage.fromFilePath(applicationContext, Uri.fromFile(file));
}

Matrix matrix = new Matrix();
matrix.postRotate(rotation);

final Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
final Bitmap rotatedBitmap =
Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

return FirebaseVisionImage.fromBitmap(rotatedBitmap);
case "bytes":
@SuppressWarnings("unchecked")
Map<String, Object> metadataData = (Map<String, Object>) imageData.get("metadata");

FirebaseVisionImageMetadata metadata =
new FirebaseVisionImageMetadata.Builder()
.setWidth((int) (double) metadataData.get("width"))
.setHeight((int) (double) metadataData.get("height"))
.setFormat(FirebaseVisionImageMetadata.IMAGE_FORMAT_NV21)
.setRotation(getRotation((int) metadataData.get("rotation")))
.build();

byte[] bytes = (byte[]) imageData.get("bytes");
assert bytes != null;

return FirebaseVisionImage.fromByteArray(bytes, metadata);
default:
throw new IllegalArgumentException(String.format("No image type for: %s", imageType));
}
}

private int getImageExifOrientation(String imageFilePath) throws IOException {
ExifInterface exif = new ExifInterface(imageFilePath);
int orientation =
exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);

switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
default:
return 0;
}
}

private int getRotation(int rotation) {
switch (rotation) {
case 0:
return FirebaseVisionImageMetadata.ROTATION_0;
case 90:
return FirebaseVisionImageMetadata.ROTATION_90;
case 180:
return FirebaseVisionImageMetadata.ROTATION_180;
case 270:
return FirebaseVisionImageMetadata.ROTATION_270;
default:
throw new IllegalArgumentException(String.format("No rotation for: %d", rotation));
}
}

private void addDetector(final int handle, final Detector detector) {
if (detectors.get(handle) != null) {
final String message = String.format("Object for handle already exists: %s", handle);
throw new IllegalArgumentException(message);
}

detectors.put(handle, detector);
}

private Detector getDetector(final MethodCall call) {
final Integer handle = call.argument("handle");
return detectors.get(handle);
}
}
Loading

0 comments on commit 921aad0

Please sign in to comment.