SPB Git

spb/focale Public

Swift 100%
3.2 KB · 93 lines swift
Raw Blame History
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