How to pass an extra parameter to Vision framework? - swift

I have a Swift / CoreML code that works fine. I want to simplify my code by using the Vision framework.
In this code, there is a UIImage -> CGImage -> CVPixelBuffer conversion that I would like to get rid of. I know that using Vision one can pass directly a CGImage as input parameter. The problem I have is that my model takes 2 inputs (image + MLMultiArray) and output and image:
Inputs
my_input : Image (Color 512 x 512)
my_sigma : MultiArray (Float32 1)
Outputs
my_output : Image (Color 512 x 512)
I've tried to pass the sigma parameter as follow:
guard let cgImage = uiImage.cgImage else {
return nil
}
let options:[VNImageOption: Any] = [VNImageOption(rawValue: "my_sigma"): 0.1]
let handler = VNImageRequestHandler(cgImage: cgImage, options: options)
do {
try handler.perform(visionRequest)
} catch {
print(error)
}
By doing so, I get the following error:
[coreml] Failure verifying inputs. no results:Error Domain=com.apple.vis Code=3 "The VNCoreMLTransform request failed" UserInfo={NSLocalizedDescription=The VNCoreMLTransform request failed, NSUnderlyingError=0x280cbbab0 {Error Domain=com.apple.CoreML Code=0 "Required input feature not passed to neural network."
So, it seems that I didn't pass correctly the second parameter to the request handler. I have not been able to find the answer.
What is the correct way to pass such a parameter to Vision?
Do you know if I can use Vision to output an image directly?
Thank you for you help.

The code I provide bellow seems to be working fine. It might not be optimal, but at least it works which is a good first step. The main difficulty was to understand how the MLFeatureProvider works:
class SigmaProvider: MLFeatureProvider {
let sigma: Double
init(sigma: Double) {
self.sigma = sigma
}
var featureNames: Set<String> {
get {
return ["my_sigma"]
}
}
func featureValue(for featureName: String) -> MLFeatureValue? {
if (featureName == "my_sigma") {
let array = try! MLMultiArray(shape: [1], dataType: .float32)
array[0] = NSNumber(value: self.sigma)
return MLFeatureValue(multiArray: array)
}
return nil
}
}
This class defines an input named my_sigma and of type MLMultiArray. Bellow is the code that applies the CoreML model (named model) to an input UIImage:
// Input CGImage
guard let cgImage = uiImage.cgImage else {
return nil
}
// Load and setup the Vision model
guard let visionModel = try? VNCoreMLModel(for: model.model) else {
fatalError("Cannot load Vision ML model")
}
visionModel.inputImageFeatureName = "my_input"
visionModel.featureProvider = SigmaProvider(sigma: self.sigma)
// Create the request
let request = VNCoreMLRequest(model: visionModel)
request.usesCPUOnly = false
// Handler
let handler = VNImageRequestHandler(cgImage: cgImage)
// Process
do {
try handler.perform([request])
}
catch {
print("Error while performing Vision request: \(error)")
return nil
}
// Get the result
guard let results = request.results else {
print("No request results")
return nil
}
// Convert the resulting CVPixelBuffer into a UIImage
for case let resultingImage as VNPixelBufferObservation in results {
if resultingImage.featureName == "my_output" {
let ciOutput = CIImage(cvPixelBuffer: resultingImage.pixelBuffer)
UIGraphicsBeginImageContextWithOptions(uiImage.size, true, 1.0)
UIImage(ciImage: ciOutput).draw(at: CGPoint(x: 0, y: 0))
let output = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return output
}
}
Note that the first argument of my model, my_input, is not needed in the SigmaProvider class. It is specified using the inputImageFeatureName property. Finally, I access the my_output by iterating through all results.
I hope this will help someone.

Have a look at the featureProvider property of VNCoreMLRequest. This is an object that provides additional inputs, i.e. anything other than the image input.

Related

CoreML: "Failed to evaluate model 0 in pipeline" with image classifier

I'm trying to classify an image with a custom model as shown below. The input image is of correct resolution (299 by 299). However, when calling prediction(image:) I get an error "Failed to evaluate model 0 in pipeline"
// Image is given
let pixelBuffer = pixelBuffer(from: image)
if let pictureClassifier = try? PictureClassifier() {
var prediction: PictureClassifierOutput?
do {
prediction = try pictureClassifier?.prediction(image: pixelBuffer)
} catch {
if let mlModelError = error as? MLModelError {
print(mlModelError.errorCode, mlModelError.localizedDescription)
}
}
}

Swift 4 Get multiples places from Google Places API

I have trouble with fetching multiples places from Google Places API. The problem is... if i fetch only one pin type like Bars, its ok, no problem. But if i trying to get Restaurants, Bars, Casino... multiples types, it gives my only first place, in our case Restaurants.
I tried make same request with Postman with link below... but for example
restaurants 1030 lines of JSON
political 83 lines
trying to get restaurants + political = 965 lines of JSON.
I use this code to get places i want:
func fetchPlacesNearCoordinate(_ coordinate: CLLocationCoordinate2D, radius: Double, types:[String], completion: #escaping PlacesCompletion) -> Void {
var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(coordinate.latitude),\(coordinate.longitude)&radius=\(50000)&rankby=prominence&sensor=true&key=\(googleApiKey)"
let typesString = types.count > 0 ? types.joined(separator: "|") : "food"
urlString += "&types=\(typesString)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? urlString
guard let url = URL(string: urlString) else { completion([]); return }
if let task = placesTask, task.taskIdentifier > 0 && task.state == .running {
task.cancel()
}
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
placesTask = session.dataTask(with: url) { data, response, error in
if data != nil{
for el in data!{
//print(el)
}
}
var placesArray: [PlaceContent] = []
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(placesArray)
}
}
guard let data = data else { return }
do{
let decode = try JSONDecoder().decode(GooglePlacesAnswer.self, from: data)
placesArray = (decode.results?.map{ $0.toPlaceContent() }) ?? []
} catch let value{
print(value.localizedDescription)
}
}
placesTask?.resume()
}
You can't get places for multiple types as stated in official documentation. You have to make multiple requests and combine the results.
https://developers.google.com/maps/documentation/javascript/places
type — Restricts the results to places matching the specified type.
Only one type may be specified (if more than one type is provided, all
types following the first entry are ignored). See the list of
supported types.

Can't get data returned from dataTask()

For one week I have been trying to get a string returned from dataTask().
I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.
So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:
func requestOGD(code gtin: String, completion: #escaping (_ result: String) -> String) {
// MARK: Properties
var answerList: [String.SubSequence] = []
var answerDic: [String:String] = [:]
var product_name = String()
var producer = String()
// Set up the URL request
let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")
guard let url = URL(string: ogdAPI) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result, which is String. It willbecome split and placed in a dictionary
do {
let answer = (String(decoding: responseData, as: UTF8.self))
answerList = answer.split(separator: "\n")
for entry in answerList {
let entry1 = entry.split(separator: "=")
if entry1.count > 1 {
let foo = String(entry1[0])
let bar = String(entry1[1])
answerDic[foo] = "\(bar)"
}
}
if answerDic["error"] == "0" {
product_name = answerDic["detailname"]!
producer = answerDic["vendor"]!
completion(product_name)
} else {
print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
return
}
}
}
task.resume()
Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:
// Configure the cell...
var foo:String = ""
requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
print(result)
foo = result
return result
}
print("Foo:", foo)
cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""
return cell
}
So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.
You used a completion handler in your call to requestOGD:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
// result comes back here
}
But then you tried to capture and return that result:
foo = result
return result
So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.
To put it in simple terms, this is the order of operations:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
foo = result // 2
}
print("Foo:", foo) // 1
You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.
In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.

How to initialize a MLMultiArray in CoreML

I've got an array of 40 arrays with 12 double features, so the type is [[double]]. Currently I'm sending this data to Google Cloud ML API to get a related prediction.
Since Apple recently introduced CoreML and coremltools, I converted my model from keras to .mlmodel to avoid thousand of google cloud api calls and do inference directly on my iPhone:
coreml_model = coremltools.converters.keras.convert(new_Model, input_names=['accelerations'],
output_names=['scores'])
coreml_model.save('PredictionModel.mlmodel')
After adding the model to my Xcode Project, it looks like:
I have no idea, where these others inputs and outputs are comming from.
To get a prediction, I need to convert my Array of Arrays of 12 doubles to an MLMultiArray, but I don't know how to do this. Has anyone faced a similar problem? Here is my current unfinished approach:
_predictionModel = PredictionModel()
guard let mlMultiArray = try? MLMultiArray(dataPointer: <#T##UnsafeMutableRawPointer#>, shape: <#T##[NSNumber]#>, dataType: <#T##MLMultiArrayDataType#>, strides: <#T##[NSNumber]#>, deallocator: <#T##((UnsafeMutableRawPointer) -> Void)?##((UnsafeMutableRawPointer) -> Void)?##(UnsafeMutableRawPointer) -> Void#>) else {
fatalError("Unexpected runtime error.")
}
guard let predictionOutput = try? _predictionModel.prediction(accelerations: mlMultiArray, lstm_1_h_in: nil, lstm_1_c_in: nil, lstm_2_h_in: nil, lstm_2_c_in: nil) else {
fatalError("Unexpected runtime error.")
}
The related documentation can be found here.
I achieved it by reading this blog :)
let data = _currentScaledMotionArrays.reduce([], +) //result is of type [Double] with 480 elements
guard let mlMultiArray = try? MLMultiArray(shape:[40,12], dataType:MLMultiArrayDataType.double) else {
fatalError("Unexpected runtime error. MLMultiArray")
}
for (index, element) in data.enumerated() {
mlMultiArray[index] = NSNumber(floatLiteral: element)
}
let input = PredictionModelInput(accelerations: mlMultiArray)
guard let predictionOutput = try? _predictionModel.prediction(input: input) else {
fatalError("Unexpected runtime error. model.prediction")
}
This is how I did it. Probably not the best way to handle optionals but gets the job done for testing
Create an instance of the MLMultiArray object with shape and data type
let mlArray = try? MLMultiArray(shape: [3], dataType: MLMultiArrayDataType.float32)
mlArray does not have an append function so you literally have to iterate through it and add values
for i in 0..<array.count {
mlArray?[i] = NSNumber(value: input[i])
}
Full function
func convertToMLArray(_ input: [Int]) -> MLMultiArray {
let mlArray = try? MLMultiArray(shape: [3], dataType: MLMultiArrayDataType.float32)
for i in 0..<array.count {
mlArray?[i] = NSNumber(value: input[i])
}
return arr!
}

Cannot convert Value type MPOFace Swift 3

I am currently working on a tutorial for Face recognition using MS cognitive services.
I get to this point in the tutorial (who uses Swift 2 and Xcode 7) but it throws up this error that says:
Cannot convert value type[MPOFace]!, err:NSError!) to expected argument type MPOFace completionBlock.
Does anyone have any answer to this and if so a little explanation I am new to this and like to learn as much as I can.
func downloadFaceId() {
if let image = personimage, let imageData = UIImageJPEGRepresentation(image, 0.8) {
FaceService.instance.client.detect(with: imageData, returnFaceId: true, returnFaceLandmarks: false, returnFaceAttributes: nil, completionBlock: { (face:[MPOFace]!, err:NSError!) in
if err == nil {
var faceId: String?
for face in faces {
faceId = face.faceId
break
}
self.faceId = faceId
}
})