Skip to content

ocr

ocr

Le texte EXACT de l'écran — OCR natif Apple, sans quitter la machine.

Atlas, panier « Ensuite », 24 août 2026. « Regarde mon écran » passait par gemma3:4b, qui décrit bien mais PARAPHRASE : un numéro de dossier, un montant, un message d'erreur ressortaient réinterprétés. Le framework Vision d'Apple (VNRecognizeTextRequest) lit les caractères eux-mêmes — plus précis, plus rapide, et sans toucher au créneau Ollama unique.

Import de Vision paresseux (PyObjC, macOS seulement) ; les résultats sont triés en ordre de lecture — Vision parle en coordonnées normalisées dont l'origine est en BAS à gauche, donc y décroissant puis x croissant.

Functions:

ocr_available

ocr_available() -> bool

Le framework Vision répond-il ? (PyObjC installé, macOS.)

Source code in src/diapason/desktop/ocr.py
def ocr_available() -> bool:
    """Le framework Vision répond-il ? (PyObjC installé, macOS.)"""
    try:
        import Vision  # noqa: F401

        return True
    except Exception:  # noqa: BLE001 - absent = indisponible, pas une panne
        return False

recognize_text

recognize_text(
    path: str,
    *,
    languages: tuple[str, ...] = ("fr-FR", "en-US"),
    fast: bool = False,
) -> List[dict]

Les lignes de texte d'une image : [{text, confidence, x, y}].

x/y sont normalisés [0,1], origine bas-gauche (convention Vision) — les appelants trient, ils ne devinent pas.

Source code in src/diapason/desktop/ocr.py
def recognize_text(
    path: str,
    *,
    languages: tuple[str, ...] = ("fr-FR", "en-US"),
    fast: bool = False,
) -> List[dict]:
    """Les lignes de texte d'une image : [{text, confidence, x, y}].

    ``x``/``y`` sont normalisés [0,1], origine bas-gauche (convention
    Vision) — les appelants trient, ils ne devinent pas.
    """
    import Vision
    from Foundation import NSURL

    handler = Vision.VNImageRequestHandler.alloc().initWithURL_options_(
        NSURL.fileURLWithPath_(str(path)), None
    )
    request = Vision.VNRecognizeTextRequest.alloc().init()
    request.setRecognitionLevel_(
        Vision.VNRequestTextRecognitionLevelFast
        if fast
        else Vision.VNRequestTextRecognitionLevelAccurate
    )
    request.setUsesLanguageCorrection_(True)
    request.setRecognitionLanguages_(list(languages))

    # performRequests rend un COUPLE (ok, erreur) — même piège que
    # get_engine (screen_vision_tools.py) : ne pas prendre le couple pour
    # la valeur.
    ok, erreur = handler.performRequests_error_([request], None)
    if not ok:
        raise RuntimeError(f"Vision OCR failed: {erreur}")

    lignes: List[dict] = []
    for observation in request.results() or []:
        candidats = observation.topCandidates_(1)
        if not candidats or not len(candidats):
            continue
        premier = candidats[0]
        boite = observation.boundingBox()
        lignes.append(
            {
                "text": str(premier.string()),
                "confidence": float(premier.confidence()),
                "x": float(boite.origin.x),
                "y": float(boite.origin.y),
            }
        )
    # Ordre de lecture : haut de l'écran d'abord (y décroissant), puis
    # gauche → droite.
    lignes.sort(key=lambda ligne: (-ligne["y"], ligne["x"]))
    return lignes