// // VisionIndexer.swift // Focale // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // // Stage 1 — Vision framework, on everything (CLAUDE.md §5). Fast, mature, // cheap. OCR is the silent winner: receipts, whiteboards, screenshots, // serial numbers — what people search most and Photos finds worst. // import CoreGraphics import Foundation import Vision /// Stage-1 output for one photo. All of it is real data, never generated. struct VisionSignals: Sendable { var ocrText: String? var featurePrint: Data? var classificationLabels: [String] /// Count only — local grouping, never named identification without /// an explicit user action (CLAUDE.md §5). var faceCount: Int } struct VisionIndexer: Sendable { /// Each request runs independently: one unsupported or failing request /// (e.g. feature prints on the simulator) must never cost us the OCR. func index(_ image: CGImage) async throws -> VisionSignals { let handler = VNImageRequestHandler(cgImage: image, options: [:]) let textRequest = VNRecognizeTextRequest() textRequest.recognitionLevel = .accurate textRequest.usesLanguageCorrection = true textRequest.recognitionLanguages = ["fr-CA", "en-US"] try? handler.perform([textRequest]) let featurePrintRequest = VNGenerateImageFeaturePrintRequest() try? handler.perform([featurePrintRequest]) let classifyRequest = VNClassifyImageRequest() try? handler.perform([classifyRequest]) let faceRequest = VNDetectFaceRectanglesRequest() try? handler.perform([faceRequest]) let ocrText = textRequest.results? .compactMap { $0.topCandidates(1).first?.string } .joined(separator: "\n") let featurePrint = (featurePrintRequest.results?.first).map(Self.data(from:)) let labels = (classifyRequest.results ?? []) .filter { $0.confidence > 0.7 } .prefix(10) .map(\.identifier) return VisionSignals( ocrText: (ocrText?.isEmpty ?? true) ? nil : ocrText, featurePrint: featurePrint, classificationLabels: Array(labels), faceCount: faceRequest.results?.count ?? 0 ) } // MARK: - Feature print storage & distance private static func data(from observation: VNFeaturePrintObservation) -> Data { observation.data } /// Euclidean distance between two stored feature prints (Float32 vectors). /// Powers "photos like this one" and near-duplicate grouping with no LLM. static func distance(_ lhs: Data, _ rhs: Data) -> Float? { guard lhs.count == rhs.count, !lhs.isEmpty, lhs.count % MemoryLayout.stride == 0 else { return nil } return lhs.withUnsafeBytes { lhsRaw in rhs.withUnsafeBytes { rhsRaw in let a = lhsRaw.bindMemory(to: Float.self) let b = rhsRaw.bindMemory(to: Float.self) var sum: Float = 0 for i in 0..