Analyse du flux vidéo de la caméra oxymétrique avec Processing

J’ai déjà montré auparavant (voir l’article ici) la construction de ma caméra, qui permet d’imager la sortie des 49 fibres optiques récoltant le signal lumineux dans les deux longueurs d’onde, sur une surface donnée. À présent, mon objectif était de faire le traitement du flux vidéo en temps réel, afin de sortir un graphique de l’intensité lumineuse recueillie en fonction du temps, et ce, pour tous les canaux.

Mon choix d’environnement de programmation s’est porté sur Processing (https://processing.org/), parce qu’il a été conçu spécialement pour des artistes en art visuel et en multimédia, avec une foule de librairies et une communauté en ligne. Il est open source, le langage ressemble énormément à celui d’arduino, ce qui n’est pas étonnant puisqu’ils partagent la parenté de leurs créateurs. Étant donné que mon projet est assez complexe, passant du traitement d’image à l’implémentation du calcul du SpO2 et finalement la représentation 3D du résultat en temps réel, avoir un environnement de programmation épuré était essentiel. Et finalement, pour obtenir éventuellement (peut-être, un jour) un affichage des données esthétiquement attrayant, chose dans laquelle je suis assez novice, je me suis dit que Processing était la meilleure option, vu la quantité surprenante de tutoriels sous toutes les formes.

J’ai donc téléchargé la dernière version, et j’ai trouvé le tutoriel suivant, qui m’a permis d’afficher la sortie de ma caméra à l’écran de mon ordinateur :

Cette vidéo est très exhaustive, et j’ai réussi aisément à non seulement faire fonctionner ma caméra, mais en plus, à comprendre assez bien la philosophie qui dicte l’architecture de programmation de Processing : l’utilisation de la fonction void draw() et des interrupts de capture déjà implémentés. Le tout est vraiment simple et sympathique d’utilisation.

Pour la partie traçage d’un graphique en temps réel, après quelques recherches, j’ai fini par tomber sur le code disponible ici. Rien de bien compliqué, et cela utilise la libraire Grafica que l’on peut télécharger avec le gestionnaire de librairies inclus dans Processing.

Ma seule contribution provenant des entrailles de mon cerveau fut, pour l’instant, la création d’une fonction convertissant une image de 720×480 pixels en 49 valeurs d’intensité correspondant à chacun de mes canaux. La voici :

float[] getsignal() {
float[] ch = new float[49];
for (int xi = 0; xi <= 6; xi++) {
for (int yi = 0; yi <=6; yi++) {
int xin = 67*xi + 185;
int yin = 72*yi + 25;
int n = xi + 7*yi;
for (int x = xin-dc; x <= xin+dc; x++) {
for (int y = yin-dc; y<= yin+dc; y++) {
int loc = x + y * video.width;
float intensity = brightness(video.pixels[loc]);
if (intensity > 50) ch[n] = ch[n] + intensity;
}
}
}
}
return ch;
}

Il s’agit de 4 boucles for nichées les unes dans les autres. Les deux premières permettent de sauter d’une région d’intérêt à l’autre, selon une formule empirique en pixels donnant la position du centre de chaque fibre sur l’image. Les deux autres passent sur chaque pixel à l’intérieur d’un carré de largeur définie par 2*dc (j’ai mis dc=20 dans le programme, donc la région d’intérêt est un carré de 40×40 pixels centré sur la sortie de chaque fibre). On a vu dans l’article précédant que le signal d’intensité saturait et débordait sur une largeur variable de pixels, j’ai donc dû faire un compromis et définir des zones que j’estimais pouvoir être remplies seulement par la lumière d’une seule fibre. La vitesse d’exécution n’a pas été ma priorité, j’avoue que ce traitement d’image pourrait largement être optimisé, l’important étant simplement d’en faire une preuve de concept, j’utilise un ordi de bureau tout de même, ce qui me donne une bonne puissance de calcul. Donc à l’intérieur du carré, j’extrais, pour chaque pixel, la valeur d’intensité lumineuse à l’aide de la fonction brightness, qui extrait cette composante de la couleur dans l’espace HSB, à partir d’un pixel RGB (oui ma caméra est noir et blanc pour l’infrarouge et les faibles luminosités, mais le convertisseur analogique/digital que j’utilise numérise les pixels en RBG, même si concrètement, toutes les valeurs correspondent simplement à des niveaux de gris). J’ai ensuite rajouté un treshold de 50 sur cette valeur, afin d’éliminer les pixels noirs du calcul d’intensité, et de bien circonscrire le cercle lumineux produit par la fibre. Je fais une somme brute de toutes les valeurs d’intensité de tous les pixels d’intérêt, afin de produire une seule valeur représentative (on l’espère) de l’intensité lumineuse dans la fibre, et donc, de la quantité de lumière diffusée par l’organe qui a été rétroréfléchie.

Comme on peut le voir à la figure suivante, cette approche permet effectivement de tracer le signal lumineux en temps réel, sur le canal de notre choix. L’axe des x est le temps en nombre de points, puisqu’on a 30 images/s, cela fait 30 points/s donc la plage couvre une quinzaine de secondes. L’intensité est une valeur arbitraire, en première approximation linéairement proportionnelle à la quantité de lumière dans la fibre. Je l’ai vérifié rapidement, de manière qualitative, en observant ce que je crois être les pulsations cardiaques! (À vérifier, bien entendu!) Une courte vidéo montre le résultat. Cette partie du travail du projet a été effectué du 26 au 28 avril dernier, mais je n’ai pas pris le temps de le documenter auparavant. D’autres développements ont été faits et je les posterai sous peu!

Conception du détecteur de l’imagerie oxymétrique

Pour pouvoir mesurer le signal dans chaque fibre, je devais trouver un moyen de les imager avec ma caméra à haute sensibilité. J’ai tout d’abord réussi à trouver une lentille en ménisque convergent qui a une distance focale de 35 mm, en la collant directement sur l’objectif de la caméra, cela permet de rapprocher l’objet de la distance hyperfocale (approx 30 cm je crois) jusqu’à 35 mm, en diminuant le champ de vue par la même occasion.

J’ai donc imprimé deux pièces s’emboîtant en une boite en prisme cubique. La première est une plaque comportant 49 trous afin de faire passer toutes les fibres et de les aligner sur un même plan. La deuxième a un trou au centre pour y insérer la caméra ainsi qu’un petit remontant cylindrique afin d’y fixer la lentille. Le tout permet de bien maintenir toute l’optique solidement en position et dans l’obscurité, autant les fibres que la lentille et la caméra.

Une fois la boîte refermée, cela donne le détecteur assemblé visible sur les deux photos ci-dessus. D’un côté, il y a la caméra, bien insérée et collée, et de l’autre, le paquet de 49 fibres formant l’image. On peut voir le résultat capté par la caméra ci-dessous. L’espacement entre les fibres est tel qu’il permet à chacune d’être bien résolue et échantillonnée sur un bon nombre de pixels, même lorsque l’intensité lumineuse dans la fibre optique est très élevée. Dans le cas présent, j’avais pointé quelques fibres directement sur une ampoule incandescente. Bien évidemment, comme je n’utilise pas de lentille de champ à la sortie des fibres, celles sur le bord de l’image vont avoir une intensité captée par la caméra beaucoup plus faible que celles au centre, il va donc falloir faire une calibration pour appliquer la correction sur le signal mesuré.

 

Défrichage de fibre optique pour l’imagerie oxymétrique

Parce que oui, c’est possible de faire autre chose que des sapins de Noël avec de la fibre en PMMA à 10¢ le mètre. Je me suis lancé sérieusement, dans les derniers jours, dans mon projet d’imagerie oxymétrique. Pour commencer, j’ai découpé 147 bouts de 30 centimètres afin d’avoir une image composée d’un carré de 7×7 (ou toute autre forme comprenant 49 bouts de fibre sur l’image de la caméra).

Sources lumineuses

En utilisant deux sources de longueur d’onde différente, l’une dans le rouge, l’autre dans l’infrarouge, il est possible de déterminer le taux d’hémoglobine oxygénée par rapport à l’hémoglobine désoxygénée, en se basant sur les courbes d’absorption particulières. Ce principe est largement utilisé dans les sphygmo-oxymètres optiques, mesurant la concentration d’oxygène en un point (normalement un doigt) et supposant qu’elle est homogène dans le reste du corps. Or, la consommation d’oxygène par les organes varie selon l’effort énergétique qu’ils doivent fournir. En ayant une méthode qui permet de mesurer les variations d’oxygénation dans un volume donné, on ouvre la porte à toutes sortes d’applications intéressantes, comme l’imagerie spectroscopique proche infrarouge fonctionnelle (functional near-infrared imaging, fNIR, en anglais), ce qui est très excitant puisqu’elle permet de visualiser les processus mentaux en temps réel avec, ce que je souhaite vérifier, du matériel très peu coûteux et facile à trouver.

 (source : http://www.oximetry.org/pulseox/principles.htm)

Atténuation de la fibre

Résultats de recherche d'images pour « attenuation pmma fiber »

L’atténuation dans le proche-infrarouge pour le PMMA grimpe rapidement, comme on peut le voir sur le graphique. À 850nm, on a environ 5dB/m d’atténuation. Il faut donc garder les bouts de fibre très courts. Avec 30 centimètres, les pertes sont de 1,5dB (~71% du signal est transmis), ce qui fait mal, mais pas trop, espérons-le.

Matériel

Pour mes sources lumineuses, j’utilise deux DEL à très haute puissance optique (2,6W) afin d’être certain d’avoir suffisamment de signal. J’aurais pu utiliser des lasers, mais mon objectif était d’injecter simultanément toutes les fibres, le plus simplement possible. De plus, les DEL sont moins coûteuses et moins dangereuses (quoique à cette puissance, même si ce n’est pas un faisceau cohérent, j’ai tendance à être prudent quand même).

Je les avais déjà préalablement soudées l’année passée, lorsque j’avais commencé ce projet. Je leur ai rajouté un petit cylindre imprimé en 3D, qui se met directement autour du petit bulbe-lentille de la DEL et qui est suffisamment large pour y faire tenir les 49 fibres.

J’utilise pour les alimenter un power supply d’ordi, qui me donne 15V et 5A, le tout régulé à 700mA par diode par des régulateurs spécialisés pour cette application.

J’ai remarqué qu’allumées à pleine puissance, elles deviennent très chaudes très rapidement, ce qui va être à tenir en compte dans la suite du projet. Il va falloir soit les utiliser périodiquement avec un rapport cyclique faible, soit rajouter un système de refroidissement adéquat, ou bien y aller avec une combinaison de ces deux approches.

À 850nm, l’oeil perçoit un rouge très faible, sûrement la limite inférieure du spectre de la diode, qui est en fait bien plus lumineuse, tel qu’on peut l’observer avec la caméra qui lui donne une couleur mauve.

  

La DEL à 660nm est extrêmement brillante, puisque la quasi-totalité de son spectre est dans le visible. On peut voir que les fibres contiennent bien le faisceau.

Pour la caméra, j’utilise une caméra de drone de marque RunCam qui a une sensibilité absolument phénoménale pour son prix abordable : jusqu’à 0,0001 Lux (elle m’avait coûté 50$US si je me souviens bien). Je lui ai imprimé en 3D un petit adaptateur qui rassemble toutes les fibres et y fait tenir la caméra. Toutefois, comme on peut le voir sur la dernière image, ce sera à refaire puisque le focus de la caméra est à l’infini, ce qui mélange toutes les fibres en un gros blob de lumière. Il va donc falloir rajouter de l’optique pour imager correctement les fibres et arriver à les distinguer individuellement afin de pouvoir traiter le signal. C’est le pari à relever en ce moment dans ce projet.