spb/focale Public
Swift 100%
1//2// VisionIndexer.swift3// Focale4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7//8// Stage 1 — Vision framework, on everything (CLAUDE.md §5). Fast, mature,9// cheap. OCR is the silent winner: receipts, whiteboards, screenshots,10// serial numbers — what people search most and Photos finds worst.11//1213import CoreGraphics14import Foundation15import Vision1617/// Stage-1 output for one photo. All of it is real data, never generated.18struct VisionSignals: Sendable {19 var ocrText: String?20 var featurePrint: Data?21 var classificationLabels: [String]22 /// Count only — local grouping, never named identification without23 /// an explicit user action (CLAUDE.md §5).24 var faceCount: Int25}2627struct VisionIndexer: Sendable {2829 /// Each request runs independently: one unsupported or failing request30 /// (e.g. feature prints on the simulator) must never cost us the OCR.31 func index(_ image: CGImage) async throws -> VisionSignals {32 let handler = VNImageRequestHandler(cgImage: image, options: [:])3334 let textRequest = VNRecognizeTextRequest()35 textRequest.recognitionLevel = .accurate36 textRequest.usesLanguageCorrection = true37 textRequest.recognitionLanguages = ["fr-CA", "en-US"]38 try? handler.perform([textRequest])3940 let featurePrintRequest = VNGenerateImageFeaturePrintRequest()41 try? handler.perform([featurePrintRequest])4243 let classifyRequest = VNClassifyImageRequest()44 try? handler.perform([classifyRequest])4546 let faceRequest = VNDetectFaceRectanglesRequest()47 try? handler.perform([faceRequest])4849 let ocrText = textRequest.results?50 .compactMap { $0.topCandidates(1).first?.string }51 .joined(separator: "\n")5253 let featurePrint = (featurePrintRequest.results?.first).map(Self.data(from:))5455 let labels = (classifyRequest.results ?? [])56 .filter { $0.confidence > 0.7 }57 .prefix(10)58 .map(\.identifier)5960 return VisionSignals(61 ocrText: (ocrText?.isEmpty ?? true) ? nil : ocrText,62 featurePrint: featurePrint,63 classificationLabels: Array(labels),64 faceCount: faceRequest.results?.count ?? 065 )66 }6768 // MARK: - Feature print storage & distance6970 private static func data(from observation: VNFeaturePrintObservation) -> Data {71 observation.data72 }7374 /// Euclidean distance between two stored feature prints (Float32 vectors).75 /// Powers "photos like this one" and near-duplicate grouping with no LLM.76 static func distance(_ lhs: Data, _ rhs: Data) -> Float? {77 guard lhs.count == rhs.count, !lhs.isEmpty,78 lhs.count % MemoryLayout<Float>.stride == 0 else { return nil }79 return lhs.withUnsafeBytes { lhsRaw in80 rhs.withUnsafeBytes { rhsRaw in81 let a = lhsRaw.bindMemory(to: Float.self)82 let b = rhsRaw.bindMemory(to: Float.self)83 var sum: Float = 084 for i in 0..<a.count {85 let d = a[i] - b[i]86 sum += d * d87 }88 return sum.squareRoot()89 }90 }91 }92}93