Files
haptika/README.md
T
2026-07-31 16:20:10 +02:00

15 KiB
Raw Blame History

Haptika — PPG caméra iPhone

Prototype React Native iPhone-only qui réalise une mesure PPG de contact avec la caméra arrière et le flash, estime un BPM et une PRV expérimentale lorsque le signal le permet, construit une baseline personnelle, puis affiche séparément un indice d’activation potentielle et sa confiance.

L’application fonctionne hors ligne. Aucune frame, image ou vidéo n’est enregistrée ni envoyée. HealthKit est facultatif, en lecture seule, et ne remplace jamais la mesure caméra actuelle.

Faisabilité vérifiée

Le prototype est réalisable sur iPhone physique avec des limites importantes :

  • VisionCamera 5 expose les frames iOS à un plugin Nitro Swift sans faire traverser les pixels à JavaScript.
  • Les timestamps proviennent du CMSampleBuffer et ne sont pas supposés réguliers.
  • Le format YUV bi-planar est traité directement à faible résolution, avec un chemin BGRA de secours dans l’extracteur.
  • HealthKit expose bien heartRate, restingHeartRate, heartRateVariabilitySDNN et les séries de battements existantes.
  • Une app iPhone ne peut pas demander à une Apple Watch ou à HealthKit de produire une nouvelle série beat-to-beat. Cette donnée reste donc historique et facultative.
  • Une caméra ne mesure pas la pression physique du doigt. Le message « relâchez légèrement » repose uniquement sur une heuristique de saturation et d’aplatissement du signal.
  • La mesure caméra fournit une PRV expérimentale, pas une HRV ECG clinique.

Références officielles principales :

Versions verrouillées

Élément Version
React Native 0.86.0
React 19.2.3
VisionCamera 5.1.0
VisionCamera Worklets 5.1.0
React Native Worklets 0.10.2
Nitro Modules / Nitrogen 0.36.1
Nitro Image 0.15.1
TypeScript ^5.8.3, strict
iOS minimum 16.0
Node.js >= 22.11

VisionCamera V5 n’utilise plus l’ancien useFrameProcessor, FrameProcessorPluginRegistry ou react-native-worklets-core. Le code utilise useFrameOutput, react-native-worklets et un HybridObject Nitro typé.

Architecture

React Native / TypeScript
  UI, permissions, machine à états, baseline, indice, confiance,
  auto-report, historique et orchestration
                    │
                    │ métriques compactes à ~6 Hz
                    ▼
VisionCamera Worklet + Nitro Swift
  CVPixelBuffer → ROI → statistiques couleur → contact → buffer numérique
                    │
                    │ fin de mesure
                    ▼
Signal Swift déterministe
  timestamps → gaps → resampling → detrend/MAD → FIR → canaux
  → pics/IPI → BPM temporel + autocorrélation → PRV → qualité

HealthKit Swift (séparé)
  historique sourcé/daté, lecture seule, poids du score principal = 0

Couche React Native

  • src/features/ppg/camera/ : caméra VisionCamera, worklet et arrêt fail-safe du flash.
  • src/features/ppg/measurement/ : machine à états, configuration et coordination native.
  • src/features/ppg/stress/ : baseline robuste, indice potentiel, confiance et interprétation.
  • src/features/ppg/health/ : modèle HealthKit et évaluation de récence.
  • src/features/ppg/storage/ : mesures numériques, réglages et auto-report locaux.
  • src/features/ppg/screens/ et components/ : préparation, mesure, résultat, historique et composants d’état.
  • src/features/ppg/tests/ : tests Jest des transitions et calculs métier.

Couche Swift / Nitro

Le package local packages/react-native-haptika-ppg contient :

  • la spec TypeScript Nitrogen src/specs/HaptikaPPG.nitro.ts ;
  • les bindings générés dans nitrogen/generated/ ;
  • PPGFrameMetricsExtractor.swift pour YUV/BGRA et ROI ;
  • PPGFingerContactDetector.swift pour le contact multi-caractéristiques avec hystérésis ;
  • HybridHaptikaPPGFrameProcessor.swift pour le cycle de mesure et le buffer numérique ;
  • ios/Signal/ pour le pipeline final ;
  • PPGThermalMonitor.swift pour ProcessInfo.thermalState ;
  • HybridHaptikaHealthKit.swift pour HealthKit en lecture seule.

Les bindings Nitro générés remplacent un bridge Objective-C manuel. C’est l’architecture officielle requise par VisionCamera 5 pour un plugin de frame natif.

Flux d’une frame

  1. VisionCamera négocie 30 FPS et une sortie cible 640 × 480.
  2. Le worklet appelle le HybridObject Swift et libère toujours la frame dans finally.
  3. Swift récupère temporairement le CMSampleBuffer et son CVPixelBuffer.
  4. Le buffer est verrouillé en lecture seule et systématiquement déverrouillé avec defer.
  5. Une grille centrale est échantillonnée directement dans les plans Y et UV. Le chemin BGRA est également supporté.
  6. Seuls les moyennes/variances couleur, la luminance, la saturation, le contact et le mouvement sont conservés.
  7. Le CMSampleBuffer et le CVPixelBuffer ne sont jamais retenus après le callback.
  8. Une mise à jour compacte est envoyée à React Native tous les cinq frames, soit environ 6 Hz.

Pipeline du signal

  • validation des valeurs finies et des timestamps monotones ;
  • diagnostic FPS, dispersion, frames manquantes et lacunes longues ;
  • interpolation uniquement des petites lacunes ;
  • masque invalide autour des interruptions ;
  • rééchantillonnage régulier à 30 Hz ;
  • suppression de tendance et normalisation médiane/MAD ;
  • filtre FIR passe-bande recalculé pour le sample rate réel ;
  • comparaison rouge, vert, luminance et blend normalisé ;
  • détection adaptative des pics dans les deux polarités ;
  • raffinement temporel et rejet des doubles pics/intervalles aberrants ;
  • BPM temporel par médiane des IPI ;
  • estimation indépendante par autocorrélation ;
  • publication du BPM seulement si les méthodes et la qualité concordent ;
  • RMSSD/SDNN des IPI uniquement comme PRV expérimentale ;
  • score de qualité global et causes d’invalidation explicites.

Le FIR Swift porte sur environ quatre canaux × 1 800 échantillons. Cette charge est faible et déterministe ; Accelerate/vDSP n’a pas été ajouté sans bénéfice mesuré. La convolution est isolée et pourra être remplacée par vDSP après profilage sur les modèles d’iPhone ciblés.

Baseline, indice et confiance

  • seules les mesures caméra valides, au repos et de qualité ≥ 0,65 alimentent la baseline ;
  • le contexte « au repos » est confirmé avant chaque capture ; une mesure hors repos peut afficher le BPM et la qualité, mais aucun indice comparatif ;
  • médiane et MAD limitent l’effet des valeurs extrêmes ;
  • avant cinq mesures comparables, aucun indice personnalisé n’est affiché ;
  • de cinq à neuf mesures antérieures, le résultat est provisoire et sa confiance plafonnée ;
  • à partir de dix mesures, le statut peut devenir personnalisé ;
  • le BPM et le log-RMSSD caméra sont normalisés séparément ;
  • si la PRV est indisponible, l’indice utilise uniquement le BPM et la confiance est fortement réduite ;
  • healthKitWeight est validé comme strictement égal à zéro ;
  • HealthKit peut seulement contribuer à la cohérence contextuelle de la confiance.

HealthKit

L’autorisation est demandée séparément et n’empêche jamais la caméra de fonctionner. Le module demande uniquement la lecture de :

  • fréquence cardiaque ;
  • fréquence cardiaque au repos ;
  • HRV SDNN ;
  • série de battements existante.

Chaque valeur conserve son unité, ses dates, son application source, son bundle source et son appareil lorsqu’il est disponible. Apple ne permet pas de distinguer un refus de lecture d’un historique vide : l’app mémorise donc seulement que la demande s’est terminée, jamais un faux statut « autorisé ». Une valeur ancienne est marquée comme contexte obsolète et n’est jamais affichée comme une valeur actuelle. SDNN HealthKit, PRV caméra, BPM caméra et fréquence au repos restent des métriques distinctes.

Pour la minimisation, chaque mesure persistée conserve au plus la dernière fréquence cardiaque historique, la dernière fréquence au repos et le dernier SDNN accessibles. La confiance contextuelle est recalculée à partir de ce même sous-ensemble ; les 100 échantillons que la requête native peut consulter transitoirement ne sont pas dupliqués dans l’historique local.

Installation

Module 7 — Miroir cardiaque

Le miroir cardiaque est une expérience différentielle isolée du pipeline PPG. Son pattern haptique propriétaire est identifié dans le code par HAPTIKA.CARDIAC_MIRROR_BEAT et versionné 1.0.0. Aucune promesse de latence par rapport à un battement biologique n’est faite : les API Workout publiques fournissent des mises à jour de fréquence, pas des événements battement par battement.

Deux modes sont visibles dans l’interface :

  • Connecté · Watch+ : la Watch mesure sans vibration et transmet ses mises à jour de fréquence. L’iPhone reconstruit le rythme visuel et Core Haptics.
  • Estimation · iPhone : utilise une fréquence de départ saisie par l’utilisateur et applique une décélération dynamique de 1 à 2 BPM/minute. Ce mode est explicitement dégradé et ne mesure pas chaque battement.

La cible SwiftUI watchOS 11 fonctionne aussi hors connexion, puis synchronise ses résumés et séances avec l’iPhone appairé via WatchConnectivity.

Une bibliothèque de régulation est également disponible sans Watch ni caméra. Elle reprend les quatre protocoles du Sprint 0 avec une interface bleu nuit et des ondes visuelles synchronisées :

  • Cohérence 5–5, pendant 5 minutes ;
  • Expiration 4–6, pendant 3 minutes ;
  • SOS tension, pendant environ 1 minute ;
  • Micro-ancrage, pendant 37 secondes.

Les patterns sont produits par Core Haptics et non par une vibration JavaScript générique. Ces exercices sont des guidages sensoriels, pas des mesures ni des protocoles médicaux de traitement du stress.

Prérequis : macOS, Xcode 26 ou version compatible RN 0.86, Node 22+, CocoaPods 1.16+, compte Apple Developer pour HealthKit et un iPhone physique.

cd HaptikaApp
npm ci
npm run native:specs
cd ios
pod install
cd ..
open ios/HaptikaApp.xcworkspace

Dans Xcode :

  1. sélectionner la cible HaptikaApp ;
  2. choisir l’équipe de signature ;
  3. vérifier le bundle ID com.haptika.iphone ;
  4. vérifier la capability HealthKit et HaptikaApp.entitlements ;
  5. choisir un iPhone physique ;
  6. lancer l’application.

Le projet inclut déjà NSCameraUsageDescription, NSHealthShareUsageDescription, l’entitlement HealthKit, la protection de fichiers complète et PrivacyInfo.xcprivacy.

Tests

cd HaptikaApp
npm run typecheck
npm run lint
npm run test:ci

cd packages/react-native-haptika-ppg
swift test

cd ../../../SignalProcessingKit
swift test
swift test -c release

État de validation du prototype : 42 tests Jest, 3 tests Swift CVPixelBuffer et 17 tests Swift du pipeline signal passent. Le workspace iOS compile pour iPhone Simulator ; la calibration PPG et le cycle réel du flash restent à valider sur appareil physique.

Le simulateur valide l’UI et le build, mais pas le contact PPG, le comportement réel du flash, la chauffe, les interruptions matérielles ou les données HealthKit de production.

Procédure iPhone physique

  1. retirer une coque qui masque la caméra ou le flash ;
  2. autoriser la caméra ;
  3. laisser HealthKit désactivé pour le premier test ;
  4. poser doucement le doigt sur la caméra arrière et le flash ;
  5. rester immobile et respirer normalement pendant la stabilisation puis l’acquisition ;
  6. vérifier l’arrêt immédiat du flash après succès, erreur, annulation et mise en arrière-plan ;
  7. répéter à 60, 75 et 120 BPM avec un simulateur optique ou une référence externe si disponible ;
  8. tester plusieurs modèles d’iPhone et niveaux de batterie/température ;
  9. répéter au moins cinq à dix mesures au repos pour évaluer la stabilité personnelle ;
  10. comparer les résultats à une référence validée avant toute affirmation de précision.

Confidentialité

  • aucune frame enregistrée ou transférée à JavaScript ;
  • aucune photo/vidéo écrite dans le conteneur ou la photothèque ;
  • aucune dépendance réseau fonctionnelle ;
  • métriques numériques et auto-report stockés localement ;
  • protection par défaut NSFileProtectionComplete ;
  • HealthKit en lecture seule et facultatif ;
  • contexte HealthKit minimal, daté et sourcé (au plus trois métriques par mesure) ;
  • manifeste de confidentialité sans tracking ni collecte distante ;
  • audit d’archive Xcode à refaire pour chaque mise à jour de dépendance ;
  • politique de suppression/export des données à finaliser avant production.

Checklist App Store

  • tester et calibrer sur les iPhone réellement supportés ;
  • fournir une méthodologie de validation et des limites de précision ;
  • conserver partout le vocabulaire expérimental/non médical ;
  • ne jamais afficher « stress détecté », « diagnostic » ou « crise » ;
  • vérifier les captures App Store et textes de permission ;
  • vérifier le rapport de confidentialité de l’archive ;
  • remplir les réponses App Privacy conformément au traitement strictement local ;
  • vérifier que HealthKit n’est pas utilisé pour publicité, profilage ou revente ;
  • confirmer l’arrêt de la torche dans tous les parcours d’interruption ;
  • effectuer une revue accessibilité et localisation finale.

Limites scientifiques et techniques

Ce prototype ne constitue ni un dispositif médical, ni un ECG, ni une mesure clinique du stress ou du système nerveux autonome. Les artefacts de mouvement, la pigmentation, la position du doigt, la géométrie caméra/flash, l’exposition automatique, la température et le modèle d’iPhone influencent la mesure. La PRV caméra ne doit pas être assimilée à une HRV ECG. Les écarts isolés sont insuffisants : l’interprétation repose sur des mesures répétées, une baseline personnelle, le contexte et un auto-report séparé.