How can we get bounding box results from VNRecognizedTextObservation after we filter compactMap results - swift

I have this code to get observation of recognized texts.
guard let observations =
request.results as? [VNRecognizedTextObservation] else {
return
}
To get top candidates
let recognized = observations.compactMap { observation in
return observation.topCandidates(1).first
}
And to get recognized text with high confidence
let confidenceL = recognized.filter{$0.confidence > 0.3}
Now I want to draw something around recognized higher confidence text but
I can only could get coordinates from observation like this
observations[k].topRight
and confidenceL array different from observations as we can guess so how can I find which observations contain confidenceL[k] text.

Well
let recognized = observations.compactMap { observation in
return observation.topCandidates(1).first
}
and
guard let observations =
request.results as? [VNRecognizedTextObservation] else {
return
}
arrays sizes are matching so I just gave up using
let confidenceL = recognized.filter{$0.confidence > 0.3}
with tears and did something like this
for k in 0..< recognized.count {
if recognized[k].confidence > 0.3 {
// Doing drawing things here
}
}

Related

Numpy's argmax() in Swift

I'm working on a project which converts user's face to emoji. I use Apple's ARKit in this purpose.
I need to get the most probable option. I wrote this code:
func renderer(for anchor: ARAnchor) {
guard let faceAnchor = anchor as? ARFaceAnchor else {
return
}
let shapes = faceAnchor.blendShapes
let browDownLeft = shapes[.browDownLeft]!.doubleValue
let browInnerUp = shapes[.browInnerUp]!.doubleValue
let browOuterUpLeft = shapes[.browOuterUpLeft]!.doubleValue
let leftBrowMax = max(browDownLeft, browInnerUp, browOuterUpLeft)
switch leftBrowMax {
case browDownLeft:
userFace.leftBrow = .browDown
case browInnerUp:
userFace.leftBrow = .browInnerUp
case browOuterUpLeft:
userFace.leftBrow = .browOuterUp
default:
userFace.leftBrow = .any
}
}
I need to duplicate function's body six time (for brows, eyes and mouth sides), so I want to write it in a more convenient way. Is there any options in Swift like numpy's argmax function? Also I need to specify arguments range, because arguments for mouth should not be compared with arguments for brows.
You can use something like this:
func maxBlendShape(for blendShapes: [ARFaceAnchor.BlendShapeLocation], in shape: [ARFaceAnchor.BlendShapeLocation: NSNumber]) -> Double? {
blendShapes
.compactMap { shape[$0] }
.map(\.doubleValue)
.max()
}
Usage would then be something like this:
maxBlendShape(for: [.browDownLeft, .browInnerUp, .browOuterUpLeft], in: faceAnchor.blendShapes)
Note: Nothing here is specific to ARKit, you just filter some keys from the dictionary and find their max value. A generic solution could look like this:
extension Dictionary where Value == NSNumber {
func maxDouble(for keys: [Key]) -> Double? {
keys
.compactMap({self[$0]})
.map(\.doubleValue)
.max()
}
}
faceAnchor.blendShapes.maxDouble(for: [.browInnerUp, .browDownLeft, .browOuterUpLeft])

how do i get an average value for scores in a table view (swift)?

I want to display an average for all values that are inputted into an Entity. Basically, one would press add on the TableView and a pop up would appear asking for a score, which would then add that “exam” to the tableview. I want to be able to add up all the scores and receive an average for them which i can then add to a label. I’ve tried following some other tutorial but it gives me an error.
https://i.stack.imgur.com/P7exB.jpg
https://i.stack.imgur.com/WWITI.jpg
The images above are for context.
var i = 0
var sum = 0
for i in 0...methodsExam.count {
let title = methodsExam[i]
let str : String = title.value(forKey: "score") as! String
sum = sum + Int(str)!
}
let avg = sum/methodsExam.count
averageScore.text = "Average: \(avg)"
Assuming methodsExam is an array of [MethodsExam] you can change the loop to
var sum = 0.0
for item in methodsExam {
if let scoreStr = item.score, let score = Double(scoreStr) {
sum += score
}
}
let average = sum / Double(methodsExam.count)
I am using Double here for more precision.
This could also be done with a high-order function
let average = methodsExam.reduce(into: 0.0) {
if let scoreStr = $1.score, let score = Double(scoreStr) { $0 += score }
} / Double(methodsExam.count)
Since we are dividing with the array count we must also check that the array isn't empty to avoid division by 0, so we can put the code into a function and do this
func calculateAverage(_ array: [MethodsExam]) -> Double {
guard !array.isEmpty else { return 0.0 }
return array.reduce(into: 0.0) {
if let scoreStr = $1.score, let score = Double(scoreStr) { $0 += score }
} / Double(array.count)
}
One caveat, I assumed that score can be nil because of MethodsExam being an NSManagedObject and not because it will hold nil values. If it indeed can hold nil values then you need to consider what to calculate the average on, all values in the array or only non-nil values.

VNRecognizeTextRequest digital / seven-segment numbers

I basically followed this great tutorial on VNRecognizeTextRequest and modified some things:
https://bendodson.com/weblog/2019/06/11/detecting-text-with-vnrecognizetextrequest-in-ios-13/
I am trying to recognise text from devices with seven-segment-style displays which seems to get a bit tricky for this framework. Often it works, but numbers with comma are hard and if there's a a gap as well. I'm wondering whether there is the possibility to "train" this recognition engine. Another possibility might be to somehow tell it to specifically look for numbers, maybe then it can focus more processing power on that instead of generically looking for text?
I use this modified code for the request:
ocrRequest = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
for observation in observations {
guard let topCandidate = observation.topCandidates(1).first else { continue }
let topCandidateText = topCandidate.string
if let float = Float(topCandidateText), topCandidate.confidence > self.bestConfidence {
self.bestCandidate = float
self.bestConfidence = topCandidate.confidence
}
}
if self.bestConfidence >= 0.5 {
self.captureSession?.stopRunning()
DispatchQueue.main.async {
self.found(measurement: self.bestCandidate!)
}
}
}
ocrRequest.recognitionLevel = .accurate
ocrRequest.minimumTextHeight = 1/10
ocrRequest.recognitionLanguages = ["en-US", "en-GB"]
ocrRequest.usesLanguageCorrection = true
There are 3 global variables in this class regarding the text recognition:
private var ocrRequest = VNRecognizeTextRequest(completionHandler: nil)
private var bestConfidence: Float = 0
private var bestCandidate: Float?
Thanks in advance for your answers, even though this is not directly code-related, but more concept-related (i.e. "am I doing something wrong / did I overlook an important feature?" etc.).
Example image that work:
Example that half works:
(recognises 58)
Example that does not work:
(it has a very low confidence for "91" and often thinks it's just 9 or 9!)

How would I create a function to get rid of repetitive code in the specific example below?

The if let intensity = intensity {} else {} is repetitive. How would I create a function to replace this repetitive code? Or what other tool could I use?
I'm new to this all. Any help appreciated.
func process(filters: [String], intensity: Int? = nil) {
for filter in filters {
switch filter {
case "blue":
if let intensity = intensity {
self.blue(value: intensity)
} else {
self.blue()
}
case "contrast":
if let intensity = intensity {
self.contrast(value: intensity)
} else {
self.contrast()
}
case "blackAndWhite":
if let intensity = intensity {
self.blackAndWhite(value: intensity)
} else {
self.blackAndWhite()
}
case "halfBrightness":
if let intensity = intensity {
self.halfBrightness(value: intensity)
} else {
self.halfBrightness()
}
case "doubleBrightness":
if let intensity = intensity {
self.doubleBrightness(value: intensity)
} else {
self.doubleBrightness()
}
default:
print("The filter you specified doesn't exist.")
}
}
}
One possible option is to change all of those xxx(value:) methods so they accept an optional value. Let the implementation of each method deal with the optional value being nil as appropriate. Then each of your case statements becomes:
case "whatever":
self.someFunction(value: intensity)
Much cleaner.
You can use reflection to call the method based on the filter name, replacing all of the cases with a single block of code, with the added benefit that new filters added later will not require any change in this section of code.
Here's a starting point for you:
let selector_name = filter + ":" + (intensity != nil ? "_:" : "")
let selector: Selector = Selector(selector_name)
let method = class_getClassMethod(Test.self, selector)
let implementation = method_getImplementation(method)
typealias Function = #convention(c) (AnyObject, Selector, Int) -> Void
let function = unsafeBitCast(implementation, to: Function.self)
function(self, selector, intensity!)
This code replaces your "switch" block. This isn't complete, since it doesn't handle your intensity == nil case, and it doesn't handle not finding the method that matches the filter name. And "Test.self" needs to be replaced with whatever you class name is, with the .self suffix. This is just intended to get you started.
You will probably take a performance hit, since using reflection usually does. Unless this code is being called many times per second, it should be insignificant.

How do i return coordinates after forward geocoding?

I am trying to see whether the user is within a certain distance of an address. I have successfully managed to get the users location, and convert the address with forward geocoding. I am left with two sets of coordinates. I am trying to make an if statement saying if they are within "a distance", print something!
Currently when i print the coordinates inside the placemark function i get the desired coordinates. When i call them to create eventLatitude and eventLongitude they become 0.0. I know this is a ascycronous problem, but i am unsure on who to resolve this. Can someone give me an example.
My code is below
before the viewdidload i have these variables
var placemarkLongitude = CLLocationDegrees()
var placemarkLatitude = CLLocationDegrees()
then inside the function i set these variables to the placemark coordinates
if let objects = objects {
for object in objects {
self.geocoder = CLGeocoder()
//get address from object
let COAddress = object.objectForKey("Address")as! String
let COCity = object.objectForKey("City")as! String
let COState = object.objectForKey("State")as! String
let COZipCode = object.objectForKey("ZipCode")as! String
let combinedAddress = "\(COAddress) \(COCity) \(COState) \(COZipCode)" //all parts of address
print(combinedAddress)
//make address a location
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {(placemarks, error) -> Void in
if(error != nil)
{
print("Error", error)
}
else if let placemark = placemarks?[0]
{
let placemark = placemarks![0]
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
print("Longitude: ", self.placemarkLongitude, " Latitude: ", self.placemarkLatitude)
}
})
// user location
let userLatitude = self.locationManager.location?.coordinate.latitude //THIS RETURNS A VALUE
let userLongitude = self.locationManager.location?.coordinate.longitude //THIS RETURNS A VALUE
print("User Location is ", userLatitude, ", " ,userLongitude)
let userLocation = CLLocation(latitude: userLatitude!, longitude: userLongitude!)
// event location
let eventLatitude = self.placemarkLatitude // THIS RETURNS 0.0
let eventLongitude = self.placemarkLatitude // THIS RETURNS 0.0
print("Event Location is ", eventLatitude, ", " ,eventLongitude)
let eventLocation = CLLocation(latitude: eventLatitude, longitude: eventLongitude)
//Measuring my distance to my buddy's (in km)
let distance = userLocation.distanceFromLocation(eventLocation) / 1000
//Display the result in km
print("The distance to event is ", distance)
if (distance < 100) {
print("yay")
}
}
}
You are correct about the asynchronous issue. Basically, you cannot do anything after this code:
// [A1]
self.geocoder.geocodeAddressString(combinedAddress, completionHandler: {
(placemarks, error) -> Void in
// [B] ... put everything _here_
})
// [A2] ... nothing _here_
The reason is that the stuff inside the curly braces (B) happens later than the stuff outside it (including the stuff afterward, A2). In other words, the code in my schematic above runs in the order A1, A2, B. But you are dependent on what happens inside the curly braces, so you need that dependent code to be inside the curly braces so that it executes in sequence with the results of the geocoding.
Of course this also means that the surrounding function cannot return a result, because it returns before the stuff in curly braces has even happened. The code in my schematic goes A1, A2, return! Only later does B happen. So clearly you cannot return anything that happens in B because it hasn't happened yet.
Just pass the coordinate values obtained from the completionHandler to any other method and do what you like to do.
{
self.placemarkLatitude = (placemark.location?.coordinate.latitude)! //THIS RETURNS A VALUE
self.placemarkLongitude = (placemark.location?.coordinate.longitude)! //THIS RETURNS A VALUE
// After this code pass the values like,
passingTheCoordinates(placemarkLatitude, placemarkLongitude)
}
func passingTheCoordinates(latitude:CLLocationDegrees, _ longitude:CLLocationDegrees){
}
Did not have enough reputation to reply your question but I also have this same problem today. I don't know much about your app design but for my case (which is stuck at the same place like you, same func, same problem, can't save to variable). My solution (maybe kinda temporally, does not good) is to save (placemark.location?.coordinate.latitude)! and (placemark.location?.coordinate.longitude)! to CoreData as Double.
This is how I implemented it. As I said before, since I don't know your app much so depend on your need, you might want something else.
LocationManager.sharedInstance.getReverseGeoCodedLocation(address: searchBar.text!, completionHandler: { (location:CLLocation?, placemark:CLPlacemark?, error:NSError?) in
if error != nil {
print((error?.localizedDescription)!)
return
}
if placemark == nil {
print("Location can't be fetched")
return
}
//Saving geo code to Core Data
newEntry.lat = (placemark?.location?.coordinate.latitude)!
newEntry.long = (placemark?.location?.coordinate.longitude)!
})
Credit to this repo for the LocationManager.swift file