// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #import "MNISTDataHandler.h" #import "OnnxruntimeModule.h" #import "TensorHelper.h" #import #import NS_ASSUME_NONNULL_BEGIN @implementation MNISTDataHandler RCT_EXPORT_MODULE(MNISTDataHandler) // It returns mode path in local device, // so that onnxruntime is able to load a model using a given path. RCT_EXPORT_METHOD(getLocalModelPath : (RCTPromiseResolveBlock)resolve rejecter : (RCTPromiseRejectBlock)reject) { @try { NSString* modelPath = [[NSBundle mainBundle] pathForResource:@"mnist" ofType:@"ort"]; NSFileManager* fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:modelPath]) { resolve(modelPath); } else { reject(@"mnist", @"no such a model", nil); } } @catch (NSException* exception) { reject(@"mnist", @"no such a model", nil); } } // It returns image path. RCT_EXPORT_METHOD(getImagePath : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) { @try { NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"3" ofType:@"jpg"]; NSFileManager* fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:imagePath]) { resolve(imagePath); } else { reject(@"mnist", @"no such an image", nil); } } @catch (NSException* exception) { reject(@"mnist", @"no such an image", nil); } } // It gets raw input data, which can be uri or byte array and others, // returns cooked data formatted as input of a model. RCT_EXPORT_METHOD(preprocess : (NSString*)uri resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) { @try { NSDictionary* inputDataMap = [self preprocess:uri]; resolve(inputDataMap); } @catch (NSException* exception) { reject(@"mnist", @"can't load an image", nil); } } // It gets a result from onnxruntime and a duration of session time for input data, // returns output data formatted as React Native map. RCT_EXPORT_METHOD(postprocess : (NSDictionary*)result resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) { @try { NSDictionary* cookedMap = [self postprocess:result]; resolve(cookedMap); } @catch (NSException* exception) { reject(@"mnist", @"can't pose-process an image", nil); } } - (NSDictionary*)preprocess:(NSString*)uri { UIImage* image = [UIImage imageNamed:@"3.jpg"]; CGSize scale = CGSizeMake(28, 28); UIGraphicsBeginImageContextWithOptions(scale, NO, 1.0); [image drawInRect:CGRectMake(0, 0, scale.width, scale.height)]; UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); CGImageRef imageRef = [scaledImage CGImage]; NSUInteger width = CGImageGetWidth(imageRef); NSUInteger height = CGImageGetHeight(imageRef); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); const NSUInteger rawDataSize = height * width * 4; std::vector rawData(rawDataSize); NSUInteger bytesPerPixel = 4; NSUInteger bytesPerRow = bytesPerPixel * width; CGContextRef context = CGBitmapContextCreate(rawData.data(), width, height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGImageByteOrder32Big); CGColorSpaceRelease(colorSpace); CGContextSetBlendMode(context, kCGBlendModeCopy); CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); CGContextRelease(context); const NSInteger dimSize = height * width; const NSInteger byteBufferSize = dimSize * sizeof(float); unsigned char* byteBuffer = static_cast(malloc(byteBufferSize)); NSData* byteBufferRef = [NSData dataWithBytesNoCopy:byteBuffer length:byteBufferSize]; float* floatPtr = (float*)[byteBufferRef bytes]; for (NSUInteger h = 0; h < height; ++h) { for (NSUInteger w = 0; w < width; ++w) { NSUInteger byteIndex = (bytesPerRow * h) + w * bytesPerPixel; *floatPtr++ = rawData[byteIndex]; } } floatPtr = (float*)[byteBufferRef bytes]; NSMutableDictionary* inputDataMap = [NSMutableDictionary dictionary]; NSMutableDictionary* inputTensorMap = [NSMutableDictionary dictionary]; // dims NSArray* dims = @[ [NSNumber numberWithInt:1], [NSNumber numberWithInt:1], [NSNumber numberWithInt:static_cast(height)], [NSNumber numberWithInt:static_cast(width)] ]; inputTensorMap[@"dims"] = dims; // type inputTensorMap[@"type"] = JsTensorTypeFloat; // encoded data NSString* data = [byteBufferRef base64EncodedStringWithOptions:0]; inputTensorMap[@"data"] = data; inputDataMap[@"Input3"] = inputTensorMap; return inputDataMap; } - (NSDictionary*)postprocess:(NSDictionary*)result { NSMutableString* detectionResult = [NSMutableString string]; NSDictionary* outputTensor = [result objectForKey:@"Plus214_Output_0"]; NSString* data = [outputTensor objectForKey:@"data"]; NSData* buffer = [[NSData alloc] initWithBase64EncodedString:data options:0]; float* values = (float*)[buffer bytes]; int count = (int)[buffer length] / 4; int argmax = 0; float maxValue = 0.0f; for (int i = 0; i < count; ++i) { if (values[i] > maxValue) { maxValue = values[i]; argmax = i; } } if (maxValue == 0.0f) { detectionResult = [NSMutableString stringWithString:@"No match"]; } else { detectionResult = [NSMutableString stringWithFormat:@"%d", argmax]; } NSDictionary* cookedMap = @{@"result" : detectionResult}; return cookedMap; } @end NS_ASSUME_NONNULL_END