Événements
Un canal pour ce qui arrive, deux lectures pour ce qui bouge à la cadence de capture.
Un seul canal
Tout ce que la librairie rapporte passe par options.onEvent, appelé avec un seul objet portant un type. Deux familles : realtime:* dit où en est la librairie, measure:* porte ce que le serveur mesure.
| Événement | Charge | Quand |
|---|---|---|
realtime:attached | width, height | start() a branché la source et chargé ce qu’il lui fallait ; il ne reste que la session à ouvrir. Réémis quand la source change. |
realtime:started | — | La session est ouverte et la capture tourne. |
realtime:stopped | reason | La mesure s’est arrêtée : requested une fois stop() achevé, aborted après abort() ou destroy(), transport-closed quand le lien est tombé. Un seul par session. |
realtime:destroyed | — | Le dernier événement d’une instance : rien ne suit. |
realtime:error | message | Le branchement ou le démarrage a échoué. |
token:error | reason | Le jeton n’a pas pu être obtenu. |
measure:bvp | indexBase, bvp | Un point d’onde de pouls, à la cadence de capture. |
measure:breathing | index, value | Un point d’onde respiratoire, par image de torse mesurée. |
measure:variables | variables | Les grandeurs, une fois par seconde. |
measure:presence | face, body, faceBox, bodyBox | Un sujet est entré dans le champ ou en est sorti. |
measure:stats | stats | Les compteurs de la session, une fois par seconde. |
RealtimeMeasure.create({
onEvent: event => {
switch (event.type) {
case "measure:bvp": chart.push(event.indexBase, event.bvp); break
case "measure:breathing": breathing.push(event.index, event.value); break
case "measure:variables": panel.show(event.variables); break
case "measure:presence": hint.toggle(event.face); break
case "realtime:error": banner.show(event.message); break
}
}
})
Ce que les grandeurs portent
measure:variables porte la forme même que le serveur calcule : une fréquence cardiaque, une fréquence respiratoire et ce dont elle a été lue, des indices de variabilité, un score de qualité, et deux drapeaux qu’il vaut la peine de lire avant d’afficher quoi que ce soit.
| Champ | Ce qu’il faut en faire |
|---|---|
heartRate, breathingRate | Les deux chiffres qu’un utilisateur attend. |
breathingSource, breathingQuality, breathingWindowS | D’où la fréquence respiratoire a été lue, avec quelle netteté, et sur combien de secondes. |
stars, sqi | La qualité du signal, en cinq crans et en indice brut. |
mature | false tant que la fenêtre est trop courte. Dites « mesure en cours », pas un chiffre. |
confident | false quand la qualité ne passe pas la barre. Présentez la valeur comme provisoire. |
sdnn, rmssd, pnn50, sd1, sd2, lfHf | La variabilité, null quand la qualité n’a pas permis de la calculer. |
heartRateIntervals, beats, rejected | La fréquence par les intervalles, et combien de battements ont été retenus et écartés. |
elapsed, healthIndex | Les secondes mesurées, et l’indice composite quand un âge a été fourni. |
Rien n’arrive du tout tant que la fenêtre est trop courte : un silence au début veut dire pas encore, jamais rien à mesurer. Et un champ de variabilité à null veut dire pas de mesure — jamais zéro, qui serait la lecture d’un cœur parfaitement régulier.
La fréquence respiratoire se lit de deux façons, et breathingSource dit laquelle. motion est le mouvement de la cage thoracique, mesuré sur les images de torse — là où la respiration a lieu. bvp est la lente modulation que porte l’onde de pouls, le repli d’une session qui n’envoie pas de torse. Les deux ne valent pas la même chose : un affichage qui montre le chiffre sans lire sa source montre deux choses différentes sous une même étiquette. breathingQuality, entre 0 et 1, dit avec quelle netteté le rythme s’est détaché du signal, et breathingWindowS l’intervalle sur lequel il a été lu — plusieurs sont essayés à chaque publication et celui sur lequel le signal se résout le mieux est retenu, si bien qu’il change en cours de mesure. Les deux valent null quand la source est bvp.
measure:bvp porte indexBase, qui désigne la capture à laquelle le point appartient. Il ne se déduit pas de l’ordre d’arrivée : une image sans visage n’est jamais envoyée, donc la suite saute. Ces trous sont l’image honnête d’une mesure — un visage sorti du champ, un moment de mouvement — et les dessiner comme des trous plutôt que de refermer le trait est ce qui empêche un lecteur de croire le signal continu.
measure:breathing est la même idée sur le torse : un point par image de torse mesurée, index désignant cette image et sautant de la même façon — une capture où aucun torse n’a été trouvé n’envoie rien. Sa value est le déplacement vertical cumulé de la cage thoracique, en pixels du recadrage, et seule sa variation veut dire quelque chose : son origine est là où se trouvait la première image, et elle dérive lentement. Calez un graphe sur le minimum et le maximum de ce qu’il montre, jamais sur zéro, et lisez-y un rythme plutôt qu’un niveau. La fréquence, elle, est dans les grandeurs.
Rien n'est calculé dans le navigateur
Les points des ondes — pouls comme respiration — arrivent tels que le serveur les a calculés. En tirer votre propre fréquence cardiaque ou respiratoire produirait un second chiffre, différent et sans autorité derrière lui.Lire la détection, plutôt que se la faire annoncer
Ce que le détecteur voit bouge à la cadence de capture. En faire un événement mettrait trente messages par seconde sur un canal qui, en intégration mobile, traverse un pont de WebView — c’est donc une lecture :
function drawOverlay() {
const { source, face, body } = measure.getDetection()
if (face !== null) {
face.box // { left, top, width, height } en pixels de la source — le recadrage envoyé
face.normalizedBox // le même, entre 0 et 1 : à multiplier par votre taille d'affichage
face.landmarks // 478 points, { x, y, z }, normalisés
}
// … dessinez, puis :
requestAnimationFrame(drawOverlay)
}
Appelez-la depuis votre propre boucle de rendu. Le cadre vient dans les deux repères parce qu’il fait huit nombres ; les points ne viennent que normalisés, parce qu’il y en a jusqu’à mille quatre cents et que source ({ width, height }) suffit à les replacer en pixels. Le cadre du torse n’est pas la boîte de ses points : il est bâti sur les seules épaules et hanches — un poignet levé l’étirerait sinon sur toute l’image — tandis que landmarks vous rend les trente-trois.
Le détecteur tourne entre start() et le geste qui met fin à la mesure — stop(), abort() ou destroy() : avant une session, face et body valent null.
measure:presence vous dit tout de même quand un sujet entre ou sort, ce qui est la partie qu’il faudrait sinon guetter, et il porte faceBox/bodyBox — le cadre à cet instant, dans les deux repères. Il n’est pas suivi ensuite : c’est à cela que sert la lecture ci-dessus.
Compteurs
getStats() rend des compteurs cumulés — images capturées et sautées, détections, octets envoyés par flux, octets épargnés par la compression, la file d’écriture et son pic, et l’aller-retour d’une image jusqu’à son point d’onde. Le même objet arrive comme measure:stats une fois par seconde, si bien que vous avez rarement à l’appeler vous-même.
Cumulés, jamais une cadence : une cadence demande une fenêtre, et cette fenêtre est une décision d’affichage. Faites la différence de deux relevés si vous voulez un débit.
Deux d’entre eux se lisent mal. savedBytes est la somme de faceSavedBytes et de bodySavedBytes, si bien que compter les trois compte l’épargne deux fois. Et latencySamples dit sur combien d’allers-retours les trois valeurs de latence sont assises : zéro veut dire que rien n’est encore revenu, ce qu’un affichage doit distinguer d’une latence nulle.
Une file qui ne redescend pas
queued et queuedPeak sont le signe avancé que la capture produit plus vite que le lien n’absorbe — visible bien avant que la latence ne le montre. Baisser fps est le levier.