0

I'm using CoreML and Vision to analyze a photo taken with the camera or imported from the library. Once the photo is obtained I run some code to make sure the photo is valid and if it is it returns true otherwise it returns false. I use it like so:

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {

    if let error = error {
        // display alert there is a problem
        return
    }

    guard let imageData = photo.fileDataRepresentation(), let previewImage = UIImage(data: imageData) else {
        // display alert there is a problem
        return
    }

    if useVisionAndCoreMLToCheckIfIsImageValid(image: previewImage) {

        tableData.append(previewImage)

    } else {

        // display alert image was not valid
    }
}

The problem is there are 4 points inside the useVisionAndCoreMLToCheckIfIsImageValid function that can go wrong and I need to return false so I can jump out of the function and if it is valid there is 1 point where it can go right and I need to return true. But since the function returns a Bool I keep getting errors when trying to return true or false at those points:

enter image description here

How can I get rid of the above errors?

func useVisionAndCoreMLToCheckIfIsImageValid(image: UIImage) -> Bool {

    if let cgImage = image.cgImage {

        let foodModel = CustomFoodModel()
        guard let model = try? VNCoreMLModel(for: foodModel.model) else {
            return false
        }

        let request = VNCoreMLRequest(model: model) { [weak self](request, error) in

            if let error = error {
                // 1st point - if there is an error return false
                return false
            }


            guard let results = request.results as? [VNClassificationObservation], let topResult = results.first else {
                // 2nd point - if there is a nil value here return false
                return false
            }

            if topResult.confidence > 0.8 {

                // 3rd point - if confidence is greater then 80% return true
                return true
            } else {

                // 4th point - if confidence is less then 80% return false
                return false
            }
        }

        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            do {

                try handler.perform([request])

            } catch let err as NSError {


                // 5th point - if there is a try error return false                   
                return false
            }
        }
    }

    // if the cgImage is nil return false
    return false
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
  • Please see https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function – vadian Dec 05 '18 at 13:29
  • @vadian thanks for the link. When I encountered the problem I said to myself I want to use 2 different completion handlers and invoke 1 for the parts that will give a problem and the other where it is successful. I thought there was perhaps a better way with less code. I'll have to use those, thanks for the help! – Lance Samaria Dec 05 '18 at 13:34
  • Basically there is no better way. Use an enum with associated value for success and failure, – vadian Dec 05 '18 at 13:43
  • @vadian check my answer. I used completionHandlers, ran the code, and it works. – Lance Samaria Dec 05 '18 at 14:01
  • I thought you want less code. Why not declaring **one** closure as `(Bool) -> Void` and pass `true` or `false`? – vadian Dec 05 '18 at 14:03
  • @vadian that didn't even cross my mind, once that answer said it cannot be done and it said to use callbacks I already had the below code on my mind. I'll try your approach later. Thanks for the suggestion and the link! – Lance Samaria Dec 05 '18 at 14:09

2 Answers2

0

The return statements you get errors for are indeed return statements for the closures e.g. - VNCoreMLRequest(model: model) { - return for this block - }, VNCoreMLRequest(model: model) { -return for this block- } and not for the useVisionAndCoreMLToCheckIfIsImageValid function itself.

You need to rework your strategy and understand the async picture in your case. I strongly recommend to first get some knowledge on the topic rather than copy-paste a solution you don't truly understand.

Some blog post on the async programming: https://ashfurrow.com/blog/comparative-asynchronous-programming/

0

I was initially going to use completionHandlers instead of a bool but I thought there was an easier way which there isn't. @vadian sent me a link in the comments to a similar SO question that basically said what I'm trying to do cannot be done because as DenislavaShentova's answer said the return statements with error are for the blocks and not the function itself.

Here's the useVisionAndCoreMLToCheckIfIsImageValid code signature using 2 completionHandlers instead of returning a Bool

func useVisionAndCoreMLToCheckIfIsImageValid(image: UIImage, falseCompletion: @escaping ()->(), trueCompletion: @escaping ()->()) {

    if let cgImage = image.cgImage {

        let foodModel = CustomFoodModel()
        guard let model = try? VNCoreMLModel(for: foodModel.model) else {
            falseCompletion()
            return
        }

        let request = VNCoreMLRequest(model: model) { [weak self](request, error) in

            if let error = error {
                // 1st point - run code for false
                falseCompletion()
                return
            }


            guard let results = request.results as? [VNClassificationObservation], let topResult = results.first else {
                // 2nd point - run code for false
                falseCompletion()
                return
            }

            if topResult.confidence > 0.8 {

                // 3rd point - run code for false
                trueCompletion()
            } else {

                // 4th point - run code for false
                falseCompletion()
            }
        }

        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            do {

                try handler.perform([request])

            } catch let err as NSError {


                // 5th point - run code for false
                falseCompletion()
            }
        }
    }

    // if the cgImage is nil run code for false
    falseCompletion()
}

and here is the function is use:

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {

    if let error = error {
        // display alert there is a problem
        return
    }

    guard let imageData = photo.fileDataRepresentation(), let previewImage = UIImage(data: imageData) else {
        // display alert there is a problem
        return
    }

    useVisionAndCoreMLToCheckIfIsImageValid(image: previewImage, falseCompletion: thereIsAProblem, trueCompletion: imageIsValid)

}

func imageIsValid() {

    tableData.append(previewImage)
}

func thereIsAProblem() {

    // do something
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256