Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Add the ability for ML Kit to create image from bytes #971

Merged
merged 40 commits into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c21de5c
Make it possible to pass bytes
bparrishMines Oct 25, 2018
671b66c
Add metadata for images
bparrishMines Oct 26, 2018
956d20f
Change tests to reflect metadata
bparrishMines Oct 26, 2018
f1ccefa
Android side of byte images
bparrishMines Oct 26, 2018
09075d0
Change error string to image type
bparrishMines Oct 29, 2018
4c9c428
Use int as rotation
bparrishMines Oct 31, 2018
7def42f
Send int of rotation
bparrishMines Oct 31, 2018
d712e4f
Assertions for metadata
bparrishMines Oct 31, 2018
eb1e94f
Merge branch 'master' of github.com:flutter/plugins into mlkit_android
bparrishMines Nov 15, 2018
35ca899
Add ios implementation to take bytes
bparrishMines Nov 16, 2018
a94fdd3
Create UIImage for mlkit
bparrishMines Nov 16, 2018
26c77ae
fomatting
bparrishMines Nov 16, 2018
9aca7df
Merge branch 'master' of github.com:flutter/plugins into mlkit_android
bparrishMines Nov 16, 2018
eb2c5f6
Make single code place for creating image
bparrishMines Nov 16, 2018
3552446
Add error for wrong type
bparrishMines Nov 16, 2018
c5a2ca8
Add ios orientation
bparrishMines Nov 16, 2018
3671e81
Add TODOs and testing
bparrishMines Nov 17, 2018
724c181
Remove orientation
bparrishMines Nov 17, 2018
31b011c
Image documentation
bparrishMines Nov 17, 2018
30e3e31
version bump
bparrishMines Nov 17, 2018
857b303
formatting
bparrishMines Nov 17, 2018
c5e4894
Documentation
bparrishMines Nov 18, 2018
10e81e2
Switch statement for ImageRotation
bparrishMines Nov 18, 2018
8081a83
formatting
bparrishMines Nov 18, 2018
b139e08
Change default rotation to zero
bparrishMines Nov 26, 2018
d4ce4a0
Take yuv format instead
bparrishMines Nov 28, 2018
5ff2661
Add plane metadata
bparrishMines Dec 6, 2018
75b353c
Update test
bparrishMines Dec 6, 2018
46bf582
Setup iOS side of creating buffer
bparrishMines Dec 7, 2018
e7df772
Documentation
bparrishMines Dec 7, 2018
c18cd9c
Merge branch 'master' of github.com:flutter/plugins into mlkit_android_2
bparrishMines Dec 7, 2018
b513d12
Formatting
bparrishMines Dec 7, 2018
d14aa17
Only assert on iOS
bparrishMines Dec 7, 2018
e07ce35
Keep access to bytes
bparrishMines Dec 10, 2018
db798e7
formatting
bparrishMines Dec 10, 2018
11bf101
Use UIImage instead of CMSampleBufferRef
bparrishMines Dec 12, 2018
f71e279
Formatting
bparrishMines Dec 12, 2018
deca0b5
Style guide enums
bparrishMines Dec 20, 2018
d36c63a
Merge branch 'master' of github.com:flutter/plugins into mlkit_android_2
bparrishMines Jan 3, 2019
1a9ac52
Merge branch 'master' of github.com:flutter/plugins into mlkit_android_2
bparrishMines Jan 3, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/firebase_ml_vision/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.1

* Add capability to create image from bytes.

## 0.2.0+2

* Fix bug with empty text object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.net.Uri;
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 io.flutter.plugin.common.MethodChannel.MethodCallHandler;
Expand Down Expand Up @@ -29,55 +30,74 @@ public static void registerWith(Registrar registrar) {
@Override
public void onMethodCall(MethodCall call, 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;
}

switch (call.method) {
case "BarcodeDetector#detectInImage":
try {
image = filePathToVisionImage((String) call.argument("path"));
BarcodeDetector.instance.handleDetection(image, options, result);
} catch (IOException e) {
result.error("barcodeDetectorIOError", e.getLocalizedMessage(), null);
}
BarcodeDetector.instance.handleDetection(image, options, result);
break;
case "FaceDetector#detectInImage":
try {
image = filePathToVisionImage((String) call.argument("path"));
FaceDetector.instance.handleDetection(image, options, result);
} catch (IOException e) {
result.error("faceDetectorIOError", e.getLocalizedMessage(), null);
}
FaceDetector.instance.handleDetection(image, options, result);
break;
case "LabelDetector#detectInImage":
try {
image = filePathToVisionImage((String) call.argument("path"));
LabelDetector.instance.handleDetection(image, options, result);
} catch (IOException e) {
result.error("labelDetectorIOError", e.getLocalizedMessage(), null);
}
LabelDetector.instance.handleDetection(image, options, result);
break;
case "CloudLabelDetector#detectInImage":
try {
image = filePathToVisionImage((String) call.argument("path"));
CloudLabelDetector.instance.handleDetection(image, options, result);
} catch (IOException e) {
result.error("cloudLabelDetectorIOError", e.getLocalizedMessage(), null);
}
CloudLabelDetector.instance.handleDetection(image, options, result);
break;
case "TextRecognizer#processImage":
try {
image = filePathToVisionImage((String) call.argument("path"));
TextRecognizer.instance.handleDetection(image, options, result);
} catch (IOException e) {
result.error("textRecognizerIOError", e.getLocalizedMessage(), null);
}
TextRecognizer.instance.handleDetection(image, options, result);
break;
default:
result.notImplemented();
}
}

private FirebaseVisionImage filePathToVisionImage(String path) throws IOException {
File file = new File(path);
return FirebaseVisionImage.fromFilePath(registrar.context(), Uri.fromFile(file));
private FirebaseVisionImage dataToVisionImage(Map<String, Object> imageData) throws IOException {
String imageType = (String) imageData.get("type");

switch (imageType) {
case "file":
File file = new File((String) imageData.get("path"));
return FirebaseVisionImage.fromFilePath(registrar.context(), Uri.fromFile(file));
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();

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

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));
}
}
}
74 changes: 70 additions & 4 deletions packages/firebase_ml_vision/ios/Classes/FirebaseMlVisionPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ - (instancetype)init {
}

- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
FIRVisionImage *image = [self filePathToVisionImage:call.arguments[@"path"]];
FIRVisionImage *image = [self dataToVisionImage:call.arguments];
NSDictionary *options = call.arguments[@"options"];
if ([@"BarcodeDetector#detectInImage" isEqualToString:call.method]) {
[BarcodeDetector handleDetection:image options:options result:result];
Expand All @@ -53,8 +53,74 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
}
}

- (FIRVisionImage *)filePathToVisionImage:(NSString *)path {
UIImage *image = [UIImage imageWithContentsOfFile:path];
return [[FIRVisionImage alloc] initWithImage:image];
- (FIRVisionImage *)dataToVisionImage:(NSDictionary *)imageData {
NSString *imageType = imageData[@"type"];

if ([@"file" isEqualToString:imageType]) {
UIImage *image = [UIImage imageWithContentsOfFile:imageData[@"path"]];
return [[FIRVisionImage alloc] initWithImage:image];
} else if ([@"bytes" isEqualToString:imageType]) {
FlutterStandardTypedData *byteData = imageData[@"bytes"];
NSData *imageBytes = byteData.data;

NSDictionary *metadata = imageData[@"metadata"];
NSArray *planeData = metadata[@"planeData"];
size_t planeCount = planeData.count;

size_t widths[planeCount];
size_t heights[planeCount];
size_t bytesPerRows[planeCount];

void *baseAddresses[planeCount];
baseAddresses[0] = (void *)imageBytes.bytes;

size_t lastAddressIndex = 0; // Used to get base address for each plane
for (int i = 0; i < planeCount; i++) {
NSDictionary *plane = planeData[i];

NSNumber *width = plane[@"width"];
NSNumber *height = plane[@"height"];
NSNumber *bytesPerRow = plane[@"bytesPerRow"];

widths[i] = width.unsignedLongValue;
heights[i] = height.unsignedLongValue;
bytesPerRows[i] = bytesPerRow.unsignedLongValue;

if (i > 0) {
size_t addressIndex = lastAddressIndex + heights[i - 1] * bytesPerRows[i - 1];
baseAddresses[i] = (void *)imageBytes.bytes + addressIndex;
lastAddressIndex = addressIndex;
}
}

NSNumber *width = metadata[@"width"];
NSNumber *height = metadata[@"height"];

NSNumber *rawFormat = metadata[@"rawFormat"];
FourCharCode format = FOUR_CHAR_CODE(rawFormat.unsignedIntValue);

CVPixelBufferRef pxbuffer = NULL;
CVPixelBufferCreateWithPlanarBytes(kCFAllocatorDefault, width.unsignedLongValue,
height.unsignedLongValue, format, NULL, imageBytes.length, 2,
baseAddresses, widths, heights, bytesPerRows, NULL, NULL,
NULL, &pxbuffer);

CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pxbuffer];

CIContext *temporaryContext = [CIContext contextWithOptions:nil];
CGImageRef videoImage =
[temporaryContext createCGImage:ciImage
fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pxbuffer),
CVPixelBufferGetHeight(pxbuffer))];

UIImage *uiImage = [UIImage imageWithCGImage:videoImage];
CGImageRelease(videoImage);
return [[FIRVisionImage alloc] initWithImage:uiImage];
} else {
NSString *errorReason = [NSString stringWithFormat:@"No image type for: %@", imageType];
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:errorReason
userInfo:nil];
}
}
@end
2 changes: 2 additions & 0 deletions packages/firebase_ml_vision/lib/firebase_ml_vision.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ library firebase_ml_vision;
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
Expand Down
3 changes: 1 addition & 2 deletions packages/firebase_ml_vision/lib/src/barcode_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,10 @@ class BarcodeDetector extends FirebaseVisionDetector {
final List<dynamic> reply = await FirebaseVision.channel.invokeMethod(
'BarcodeDetector#detectInImage',
<String, dynamic>{
'path': visionImage.imageFile.path,
'options': <String, dynamic>{
'barcodeFormats': options.barcodeFormats.value,
},
},
}..addAll(visionImage._serialize()),
);

final List<Barcode> barcodes = <Barcode>[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CloudDetectorOptions {
/// The type of model to use for the detection.
final CloudModelType modelType;

Map<String, dynamic> _toMap() => <String, dynamic>{
Map<String, dynamic> _serialize() => <String, dynamic>{
'maxResults': maxResults,
'modelType': _enumToString(modelType),
};
Expand Down
3 changes: 1 addition & 2 deletions packages/firebase_ml_vision/lib/src/face_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ class FaceDetector extends FirebaseVisionDetector {
final List<dynamic> reply = await FirebaseVision.channel.invokeMethod(
'FaceDetector#detectInImage',
<String, dynamic>{
'path': visionImage.imageFile.path,
'options': <String, dynamic>{
'enableClassification': options.enableClassification,
'enableLandmarks': options.enableLandmarks,
'enableTracking': options.enableTracking,
'minFaceSize': options.minFaceSize,
'mode': _enumToString(options.mode),
},
},
}..addAll(visionImage._serialize()),
);

final List<Face> faces = <Face>[];
Expand Down
Loading