Projet

Général

Profil

Jhodi Avizara

Ce document présente de façon résumé les différents projets auxquels j'ai pu rendre part ou que j'ai lancé.

Ruche connectée

Décembre 2025

Après la réunion de Novembre avec les autres membres du projet OpenBeeLab, j’ai reçu comme consigne de faire l’inventaire des pièces et un budget prévisionnel pour la conception de 5 balances. On m’a demandé, pour la partie balance électronique de prévoir des pièces pour 20.
_
Problème : la documentation sur la page du projet et sur le git n’est pas complète, je manque d’info sur les dimensions et nombre de pièces utilisé.
Solution : la maquette 3D de la balance est disponible sur le git openhivescale

Problème : la maquette 3d est conçu sur Openscad (je sais pas utiliser)
Solution : apprendre à me servir de Openscad_

J'ai pu calculé que la balance et tout ses composants revenais à 453.80€, ce qui est 2 à 3 fois plus chers que le modèle que nous avons actuellement. La raison étant que durant ma recherche, je suis allé chercher les pièces sur le RoiMerlin. Mais jusqu'ici on peut voir que les pièces à usiner comptent pour la moitié des coûts de la balance (comme vu dans le diagramme si-dessous).

Pour l'instant je peux me contenter de ces valeurs car de nombreuses modifications vont être apporté par les étudiant de GNP. En attend je dois assister les étudiants en électronique.

Janvier 2026

Les étudiants ont commencé à prendre la main sur la balance et la carte électronique de Miella.

_Problème : le programme ne compile pas correctement, cela nous a fait perdre du temps au démarrage
Solution : Attendre que Nicolas règle le problème

Problème : Nous devions au départ partir avec 6 étudiants mais nous sommes maintenant à 3. De plus un des 3 n'est pas souvent présent au fablab, à ce stade du projet seule 2 personnes travaillent réellement._

Quelques tests sur la balance ont montré de nombreux défauts, comme des mesures variant brusquement entre 2 mesures et un endstop légèrement endommagé mais toujours fonctionnel. Nous avons pu calculer que la balance avait une marge d'erreur de 800 g environ.

Le démontage et remontage de la balance s'est avéré compliqué, nous devions nous y prendre à 3.

Robot Go West

Décembre - Janvier

Je reprends le projet avec Aurel, nous sommes à la phase d’entraînement du modèle, j'ai modélisé un support pour photorésistance.

Coque en maïzena

Un projet lancé sur un coup de tête avec Léo. On voudrait se fabriquer une coque rempli d'un fluide non newtonien.
Pour ce projet nous avons besoin :
  1. être capable de mesurer la quantité de G reçu par le téléphone avec et sans la coque afin de pouvoir évaluer l'efficacité de ce dernier
  2. modéliser une coque qui pourra contenir le fluide sans fuite

Pour le premier axe, j'ai pu à l'aide d'un montage comprenant une ESP32 et un accéléromètre ADXL335 arrivé à mesurer les G en temps réel. Et à l'aide d'un simple script bash, je peux afficher les données en temps réel.

Monture de remplacement imprimées en 3D

Un autre projet démarré sur un coup de tête après avoir perdu mes lunettes.

Cahier des charges :
  1. Confortable
  2. Des branches déclipsables (pour les changer)
  3. Des verres fonctionnels
Difficultés :
  1. Les pièces sont fines et rend le système de clips difficile à implémenter sans fragiliser des parties$
  2. L'optique c'est compliqué
  3. La résine transparente et résistante aux UV coûte cher

Thumb book holder

Un petit objet pour tenir les pages de son livre.
Fichier freecad pour impression 3D * (Fichier : basic.FCStd)
Fichier SGV pour découpe * (Fichier : basic-Sketch.svg)

Portoir à pile (modélisation dynamique)

Pratique pour ranger des piles...surtout quand on se retrouve du jour au lendemain avec un énorme stocke.
Le fichier CAD est dynamique, c'est-à-dire, que vous pouvez modifier le nombre et la taille des pile dans le "spreadsheet" pour automatiquement ajuster l'objet 3D.
L'objet en lui-même est conçu pour utiliser un minimum de file. Dans PrusaSlicer, en PLA et avec 15% de remplissage, une boite de 25 pile AA consomme 44 m de filament.


Fichier freecad pour impression 3D * (Fichier : boite_a_pile.FCStd)

Et pour aider les gens à s'y retrouver avec toutes ces piles, entre celles qui sont chargée et celles qui ne sont pas, voici un petit panneau.

Quand la face (+) est visible, la pile est chargée et prête à être utilisée.
Quand la face (-) est visible, la pile est déchargée ou a été utilisée.

RobotGoWest: Étude de signaux électrophysiologiques de la Monstera Deliciosa

Ce projet rentre dans le cadre du projet RobotGoWest, dans l'idée de mettre au point un robot sur roue capable de se diriger à vers le soleil. Nous cherchons à mettre au point un réseau de neurones capable d'interpréter les signaux électrophysiologiques d'une plante afin de dire si ladite plante se trouve à l'ombre ou au soleil.
A cette fin, nous avons élaborer un pipeline d'analyse des signaux électrique, récoltés à l'aide des capteurs de VegetalSignal. Dans ce projet, nous cherchons à:
1. Démontrer que les signaux électrophysiologique de la Monstera Deliciosa sont porteurs d'informations
2. Identifier le signal associé au cycle circadien de la plante ou au phototropisme de la plante.
3. Entraîner un réseau de neurone capable de distinguer une plante en phase de nuit d'une plante en phase de jour.

En raison des conditions expérimentales inadéquats pour ce type d'expérience (environnement non contrôlé et absence de répliqua) ce travail ne fait office que de preuve de concept pour consolider un pipeline.

Signaux électrophysiologiques de la Monstera

Acquisition des données brutes

Les électrodes du capteur (numéroté 1 à 8) ont été placé à différent organe de la plante.

Capteur Position sur la plante Type d'organe
1 Tige Aérien
2 Racine (Sol) Souterrain
3 Tige Aérien
4 Pétiole Aérien
5 Pétiole Aérien
6 Tige Aérien
7 Pétiole Aérien
8 Racine (Sol) Souterrain

Les enregistrements ont été fait sur environ 7 jours à deux périodes entre le 7 mai et 9 mai 2026 (45 heures d'enregistrement) et entre le 13 mai et le 18 mai 2026 (122 heures d'enregistrement).
Pendant le premier enregistrement, les piles se sont usées au bout de 2 jours, nous obligeant à utiliser un chargeur branché sur le secteur afin de poursuivre les enregistrements.
Nous avons ainsi récolter environ 167 heures d'enregistrement que nous avons séparé en portion d'environ 2 sec labélisés en fonction du jour et de la nuit.
Cette lablisation a été faite en fonctions des heures de levé et de couché de soleil pendant les périodes d'enregistrement.

Date_Complete Date_Jour Heure Label Dataset
2026-05-07 Jeu., 7 mai 17:05 DEBUT Dataset 1
2026-05-07 Jeu., 7 mai 21:14 Couché Dataset 1
2026-05-08 Ven., 8 mai 06:41 Levé Dataset 1
2026-05-08 Ven., 8 mai 21:15 Couché Dataset 1
2026-05-09 Sam., 9 mai 06:40 Levé Dataset 1
2026-05-09 Sam., 9 mai 21:16 Couché Dataset 1
2026-05-13 Mer., 13 mai 16:24 DEBUT Dataset 2
2026-05-13 Mer., 13 mai 21:21 Couché Dataset 2
2026-05-14 Jeu., 14 mai 06:34 Levé Dataset 2
2026-05-14 Jeu., 14 mai 21:22 Couché Dataset 2
2026-05-15 Ven., 15 mai 06:33 Levé Dataset 2
2026-05-15 Ven., 15 mai 21:23 Couché Dataset 2
2026-05-16 Sam., 16 mai 06:32 Levé Dataset 2
2026-05-16 Sam., 16 mai 21:25 Couché Dataset 2
2026-05-17 Dim., 17 mai 06:31 Levé Dataset 2
2026-05-17 Dim., 17 mai 21:26 Couché Dataset 2
2026-05-18 Lun., 18 mai 06:30 Levé Dataset 2

Ainsi nous nous retrouvons avec un total 289 942 échantillons. Les données brute en sortie des capteurs ont été lu à l'aide d'un parseur implémenté par Julien Oculi. Ou tout simplement être lu par une commande bash suivante:

cat VS20B71.TXT|od -t x1 -w26

Pour pouvoir être traité analyser dans le réseau de neurone, les fichiers sont stockées sous la forme de tableau numpy aux dimensions suivantes [1, 8, n].
1 correspond au nombre de channels (dans une image RGB ce chiffre passe à 3), 8 correspond aux nombre de paire d'électrodes du capteur et n le nombre de mesure (512 dans notre cas).

Visualisation des données brutes

Sur une heure d'enregistrement nous observons une hétérogénéité entre les électrodes avec le capteur 8 se distingue des 7 autres, de part son amplitude et son signe. De plus, nous observons de fortes variations dans 40 premières minutes d'enregistrements associés au stress provoqué par l'introduction des électrodes.

Fig 2: Donnée brutes sur 1 heure

La transformé de fourrier révèle 3 pics à 50, 150 et 250 Hz (fig 1), présent également dans les échantillons (Fig 3). On suppose que les pics à 150 et 250 Hz sont des harmonique du 50 Hz du réseau électrique.
Sur l'ensemble du dataset nous observons une variation périodique de l'amplitude des signaux.

Fig 3: Variation des amplitudes médians, des quartiles (Q1 et Q3) sur chaque heure pendant 167 heures. Le trait marque la limite entre les deux datasets
Fig 4: Échantillon brute de jour (à gauche) et de nuit (à droite)

Filtration du signal

Au vu de la présence d'artefacts, nous avons appliqué un filtre coupe bande (notch filter) afin de supprimer le 50 Hz et ses harmoniques.

from scipy import signal
def notch_filter(input_signal, fs=250.0, f0=50.0, Q=1.0):
    b, a = signal.iirnotch(f0, Q, fs)
    filtered_signal = signal.filtfilt(b, a, input_signal)
    return filtered_signal
Fig 5: Échantillon après filtration à 50Hz (Q = 1.0) de jour (à gauche) et de nuit (à droite)

Bien que le signal paresse moins bruité, nous n'avons pas pu retirer le 50 Hz ou le 250 Hz mais uniquement de 150 Hz.

Étude statistique des différences interclasse

ANOVA

Afin de démontrer qu'il existe une différence entre les deux groupes de signaux (jour et nuit), nous avons calculer le coefficient de variation de chaque échantillons.
Le graphique qu'on obtient montre des différences entre les capteurs et entre les deux groupes.

Fig 6: Évolution du coefficient de variation des échantillons entre le jour et la nuit et par capteur. A gauche avant filtration, à droite à près filtration des 50 Hz (Q=1.0)

Une ANOVA a révélé une différence significative entre les deux groupes (p_value <<< 0.05 | F value = 118.51) et après filtration, cette différence reste significative mais avec une différence plus parquée (p_value <<< 0.05 | F value = 6048.1).
De plus, et au vu du montage (fig 1) et des observations (fig 2 à 5), nous pouvons faire le constat que les électrodes, placés à différents organes de la plante, ne semblent pas donner la même information. Effet, l' ANOVA révèle une variabilité entre les capteurs, pour les données non filtrés (p_value <<< 0.05 | F value = 132 750.44) et les données filtrés (p_value <<< 0.05 | F value = 93 925.7)

Analyse multivariée: clusturing via UMAP

Cependant le calcul du coefficient de variation ne donne qu'une information condensée, on en perd alors une grande partie. Le défi est alors d'observer des différences entre des vecteurs de dimension 1x512 en les alternant le moins possible. C'est pourquoi nous effectuant une projection UMAP afin de projeter ces vecteurs dans un espace réduit et de les rassembler en fonction de leur proximité.
Fig 7: Projection UMAP sur 3000 échantillons (à gauche) et sur la FFT (à droite)

Aucun cluster de point ne ce démarque concrètement, à ce stade nous pouvons faire raisonnablement l'hypothèse que nos données sont beaucoup trop bruités pour distinguer des différences entre les échantillons.
Il est possible de normaliser les données à l'aides des capteurs 2 et 8 (placés dans le substrat des plantes) étant données qu'elle ne renvoie pas d'information venant directement de la plante.

Fig 8: Projection UMAP sur 3000 échantillons (à gauche) et sur la FFT (à droite) après normalisation par le capteur 2 (en haut) et le capteur 8 (en bas)

En somme travailler sur les FFT plutôt de les signaux brutes permet d'avoir des clusters de points plus resserrés et la normalisation par le capteur 8 semble donner les meilleurs résultats. Mais avons toujours un chevauchement très important des groupes.
Nous faisons l'hypothèse que le problème vient vient des données en eux même, nous avons mal labélisé des échantillons au caractères ambiguës.
Cette fois ci en omettant les données récolter pendant les phases d'aurore et de crépuscules, nous arrivons à une meilleur séparation de classes.

Fig 9: Projection UMAP sur 3000 échantillons (à gauche) et sur la FFT (à droite) normalisé par le capteur 8. Sans 4h d'échantillons enregistrés à l'intersection des phases de jour et de nuit

Mais nous remarque qu'on a une bonne séparation de classes (jour/nuit) mais un 3e cluster regroupant les deux classes apparaît.

La visualisation des signaux avec cette méthode (normalisation par le capteur 8) donne un signal plus propre et d'autant plus proche de l'allure d'un EEG humain (une fois filtré).
Nous observons toujours un pic à 150 Hz mais aucun à 50.

Fig 10: Échantillon brute de jour (à gauche) et après filtration à 50Hz (Q = 30.0) (à droite)

Réseau de Neurone: Compact CNN

Le réseau que nous avons implémenté est tiré d'un pipeline d'analyse de signaux EEG de J. Lawhern et al (2018) et a été adapté pour les dimensions de nos échantillons.

import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from torchmetrics import Accuracy

### SET MODEL SPECIFICATIONS ###
fs= 250                  #sampling frequency
channel= 8               #number of electrode
num_input= 1             #number of channel picture (for EEG signal is always : 1)
num_class= 2             #number of classes 
signal_length = 512      #number of sample in each tarial
F1= 8                    #number of temporal filters
D= 3                     #depth multiplier (number of spatial filters)
F2= D*F1                 #number of pointwise filters
kernel_size_1= (1,round(fs/2)) 
kernel_size_2= (channel, 1)
kernel_size_3= (1, round(fs/8))
kernel_size_4= (1, 1)
kernel_avgpool_1= (1,4)
kernel_avgpool_2= (1,8)
dropout_rate= 0.5
ks0= int(round((kernel_size_1[0]-1)/2))
ks1= int(round((kernel_size_1[1]-1)/2))
kernel_padding_1= (ks0, ks1-1)
ks0= int(round((kernel_size_3[0]-1)/2))
ks1= int(round((kernel_size_3[1]-1)/2))
kernel_padding_3= (ks0, ks1)

### DESIGN MODEL ###
class EEGNet(nn.Module): 
    def __init__(self):
        super().__init__()
        # layer 1
        self.conv2d = nn.Conv2d(num_input, F1, kernel_size_1, padding=kernel_padding_1)
        self.Batch_normalization_1 = nn.BatchNorm2d(F1)
        # layer 2
        self.Depthwise_conv2D = nn.Conv2d(F1, D*F1, kernel_size_2, groups= F1)
        self.Batch_normalization_2 = nn.BatchNorm2d(D*F1)
        self.Elu = nn.ELU()
        self.Average_pooling2D_1 = nn.AvgPool2d(kernel_avgpool_1)
        self.Dropout = nn.Dropout2d(dropout_rate)
        # layer 3
        self.Separable_conv2D_depth = nn.Conv2d( D*F1, D*F1, kernel_size_3, padding=kernel_padding_3, groups= D*F1)
        self.Separable_conv2D_point = nn.Conv2d(D*F1, F2, kernel_size_4)
        self.Batch_normalization_3 = nn.BatchNorm2d(F2)
        self.Average_pooling2D_2 = nn.AvgPool2d(kernel_avgpool_2)
        # layer 4
        self.Flatten = nn.Flatten()
        self.Dense = nn.Linear(F2*round(signal_length/34), num_class) # self.Dense = nn.Linear(F2*round(signal_length/32), num_class)
        self.Sigmoid = nn.Sigmoid() # self.Softmax = nn.Softmax(dim= 1) # for multiclass        

    def forward(self, x):
        # layer 1
        y = self.Batch_normalization_1(self.conv2d(x)) #.relu()
        # layer 2
        y = self.Batch_normalization_2(self.Depthwise_conv2D(y))
        y = self.Elu(y)
        y = self.Dropout(self.Average_pooling2D_1(y))
        # layer 3
        y = self.Separable_conv2D_depth(y)
        y = self.Batch_normalization_3(self.Separable_conv2D_point(y))
        y = self.Elu(y)
        y = self.Dropout(self.Average_pooling2D_2(y))
        # layer 4
        y = self.Flatten(y)
        y = self.Dense(y)
        y = self.Sigmoid(y) # y = self.Softmax(y) # for multiclass

        return y