Skip to content

vision_mains

vision_mains

Des pixels aux points articulaires — Vision d'Apple, sur la machine.

Spatial Mesh, gestes — 25 août 2026. Le pont entre une image et le moteur de gestes. Il ne décide de rien : il lit vingt-et-un points par main et les passe. Tout ce qui ressemble à une intention vit dans gestes_main, qui n'a jamais vu un pixel.

Deux propriétés, et le §10 du cahier des charges les exige :

Rien ne quitte la machine. Vision tourne sur le Neural Engine ; aucun octet ne part sur le réseau, et le créneau Ollama — unique — n'est pas touché. Un modèle de langue et un suivi de main peuvent donc coexister.

Aucune image n'est gardée. L'entrée est un tampon de pixels vivant en mémoire ; la sortie est une liste de coordonnées. Rien n'est écrit sur le disque, à aucun moment.

Classes

MainDetectee dataclass

MainDetectee(points: list, lateralite: str)

Une main lue par Vision, avec sa latéralité.

La latéralité sert au retournement paume/dos : sans elle, le sens du parcours des bases serait ambigu (main gauche vs droite).

VisionIndisponible

Bases: RuntimeError

Le framework manque — dit, jamais deviné.

Functions:

mains_dans_le_tampon

mains_dans_le_tampon(
    pixels: Any, *, mains_max: int = 2
) -> list[MainDetectee]

Les mains vues dans un tampon de pixels — [] si aucune.

Le tampon vient directement de la caméra : pas de fichier intermédiaire, donc pas d'image qui traîne sur le disque.

Source code in src/diapason/desktop/vision_mains.py
def mains_dans_le_tampon(pixels: Any, *, mains_max: int = 2) -> list[MainDetectee]:
    """Les mains vues dans un tampon de pixels — [] si aucune.

    Le tampon vient directement de la caméra : pas de fichier intermédiaire,
    donc pas d'image qui traîne sur le disque.
    """
    import Vision

    requete = _requete(mains_max)
    gestionnaire = Vision.VNImageRequestHandler.alloc().initWithCVPixelBuffer_options_(
        pixels, None
    )
    ok, erreur = gestionnaire.performRequests_error_([requete], None)
    if not ok:
        raise VisionIndisponible(f"Vision a échoué : {erreur}")
    return [_lire_main(o) for o in (requete.results() or [])]

mains_dans_le_fichier

mains_dans_le_fichier(
    chemin: str, *, mains_max: int = 2
) -> list[MainDetectee]

Les mains vues dans une image sur disque — pour les bancs d'essai.

Source code in src/diapason/desktop/vision_mains.py
def mains_dans_le_fichier(chemin: str, *, mains_max: int = 2) -> list[MainDetectee]:
    """Les mains vues dans une image sur disque — pour les bancs d'essai."""
    import Vision
    from Foundation import NSURL

    requete = _requete(mains_max)
    gestionnaire = Vision.VNImageRequestHandler.alloc().initWithURL_options_(
        NSURL.fileURLWithPath_(str(chemin)), None
    )
    ok, erreur = gestionnaire.performRequests_error_([requete], None)
    if not ok:
        raise VisionIndisponible(f"Vision a échoué : {erreur}")
    return [_lire_main(o) for o in (requete.results() or [])]

mains_dans_les_octets

mains_dans_les_octets(
    octets: bytes, *, mains_max: int = 2
) -> list[MainDetectee]

Les mains vues dans une image reçue en mémoire — rien sur le disque.

C'est le chemin de l'interface : elle capture, elle poste, on lit. Écrire l'image pour la relire serait laisser une trace de ce que la caméra a vu.

Source code in src/diapason/desktop/vision_mains.py
def mains_dans_les_octets(octets: bytes, *, mains_max: int = 2) -> list[MainDetectee]:
    """Les mains vues dans une image reçue en mémoire — rien sur le disque.

    C'est le chemin de l'interface : elle capture, elle poste, on lit. Écrire
    l'image pour la relire serait laisser une trace de ce que la caméra a vu.
    """
    import Vision
    from Foundation import NSData

    requete = _requete(mains_max)
    donnees = NSData.dataWithBytes_length_(octets, len(octets))
    gestionnaire = Vision.VNImageRequestHandler.alloc().initWithData_options_(
        donnees, None
    )
    ok, erreur = gestionnaire.performRequests_error_([requete], None)
    if not ok:
        raise VisionIndisponible(f"Vision a échoué : {erreur}")
    return [_lire_main(o) for o in (requete.results() or [])]

points_de_main

points_de_main(main: Any) -> list

Compatibilité tests : une MainDetectee ou une ancienne liste de points.

Source code in src/diapason/desktop/vision_mains.py
def points_de_main(main: Any) -> list:
    """Compatibilité tests : une MainDetectee ou une ancienne liste de points."""
    if main is None:
        return []
    if isinstance(main, MainDetectee):
        return list(main.points)
    return list(main)