import Vision import CoreGraphics import ImageIO class FeatureMatcher { static let revision = VNGenerateImageFeaturePrintRequest.Revision.revision1 static func generateFingerprint(from image: CGImage, orientation: CGImagePropertyOrientation = .up) async throws -> VNFeaturePrintObservation { let req = VNGenerateImageFeaturePrintRequest() req.revision = revision req.imageCropAndScaleOption = .scaleFill let handler = VNImageRequestHandler(cgImage: image, orientation: orientation, options: [:]) try handler.perform([req]) guard let result = req.results?.first as? VNFeaturePrintObservation else { throw NSError(domain: "FeatureMatcher", code: -1, userInfo: [NSLocalizedDescriptionKey: "No features detected"]) } return result } static func identify(scan: VNFeaturePrintObservation, database: [CardMetadata], cache: [UUID: VNFeaturePrintObservation]) -> MatchResult { var candidates: [(CardMetadata, Float)] = [] for card in database { guard let obs = cache[card.id] else { continue } var dist: Float = 0 if (try? scan.computeDistance(&dist, to: obs)) != nil && dist < 18.0 { candidates.append((card, dist)) } } let sorted = candidates.sorted { $0.1 < $1.1 } guard let best = sorted.first else { return .unknown } if best.1 < 6.0 { return .exact(best.0) } let close = sorted.filter { $0.1 < (best.1 + 3.0) } if close.count > 1 { return .ambiguous(name: best.0.name, candidates: close.map { $0.0 }) } return .exact(best.0) } }