Version scientifique

Architecture concurrente d’EIP

Organisation modulaire, acquisition RAW, prévisualisation partagée et services web embarqués sur Raspberry Pi.

Architecture logicielleC++17ConcurrenceMJPEGOpenMP (calcul parallèle)

Comment EIP découple-t-il acquisition, traitement, enregistrement et service web tout en conservant une chronologie unique des trames ?

Objet, question et périmètre

Cet article décrit l’état d’Embedded Imaging Platform au 5 octobre 2026. Il s’appuie sur le code, la documentation d’architecture, les mesures de performance archivées et l’historique Git de ce dépôt, qui commence avec le premier commit de cette nouvelle mouture, le 14 août 2026. Il ne cherche pas à déduire la qualité du logiciel de sa taille : le nombre de lignes est utilisé uniquement comme indicateur d’ampleur et de progression.

J’imagine PolarScope dès 2019 comme un viseur numérique pour ma monture d’astronomie EM-10 Takahashi ; mes notes sur ce prototype, avec caméra Raspberry Pi et Pi Zero, documentent aussi des essais de calibration et de corrélation stellaire. Cette première version inachevée sert de base à la nouvelle mouture de 2026, beaucoup plus ambitieuse.

Le support imprimé adaptait un objectif CS à la caméra et permettait son montage sur l’EM-10. La vue éclatée ci-dessous documente la conception mécanique de ce premier prototype avec son support de test avant l’adaptation monture.

Vue éclatée dans OpenSCAD des pièces du support adaptateur de caméra pour objectif CS.
Vue éclatée OpenSCAD, document de conception mécanique de l’ébauche de l’adaptateur pour objectif CS.

EIP est un serveur d’imagerie embarquée C++17 complété par des applications web et un client natif secondaire. Son rôle couvre l’acquisition, le transport des données brutes du capteur (RAW), la prévisualisation, l’enregistrement, le stockage, la télémétrie, l’astronomie et la caractérisation de capteurs.

Invariant d’architecture : un propriétaire, un monolithe modulaire

Le déploiement principal repose sur un processus imaging-server. Ce choix donne un propriétaire unique au matériel, évite une sérialisation interprocessus des trames et maintient les métadonnées dans la même chronologie que les pixels. Il ne signifie pas que le code forme un bloc indivisible.

Les dépendances suivent quatre niveaux :

ApplicationsPolarScope, PiNox, Dashboard, Projection, Caractérisation et Fichiers
Astronomiecatalogues, positions apparentes, détection, identification et résolution de champ
Cœur d’imageriecapture, image, transport, enregistrement, stockage, télémétrie, configuration et HTTP
Piloteslibcamera, ASICamera2, simulation, fixtures et relecture AVI

Le sens des dépendances est Applications → Astronomie → Cœur d’imagerie → Pilotes. Toutes les applications ne mobilisent pas nécessairement les services astronomiques. Les backends implémentent un contrat commun et publient leurs capacités ; le serveur ne suppose donc ni une famille de capteurs ni une profondeur RAW unique.

Évolution observée dans Git

Repères de développement jusqu’au 5 octobre 2026.
DateÉtapeConséquence architecturale
14-17 aoûtlibcamera en mémoire, acquisition continue, client asynchroneLa cadence du capteur n’est plus liée à la boucle d’affichage.
19 aoûtSéparation client/serveurLe protocole caméra devient générique.
24-29 aoûtContrôle web, MJPEG, analyse asynchrone, double tamponLe navigateur devient un client complet sans bloquer l’acquisition.
17-23 septembrePiNox, Dashboard, portail et configuration EIPLe projet initial évolue vers un socle générique multi-application.
25 septembreInterfaces compilées et PIMPLRéduction du couplage et du coût de recompilation sur les petits Pi.
28 septembreImagerie, stockage et fichiers consolidésLa capture locale haute cadence rejoint le portail commun.
29 septembre-1er octobreChemin ISP mesuréLe backend peut produire RAW et RGB appairés avec repli logiciel.
2-5 octobreCaractérisation C++ en mémoireAcquisition, analyse et rapport sont intégrés au même serveur.

Plan de données et plan de contrôle

Le serveur distingue implicitement deux plans. Le plan de données transporte les charges utiles volumineuses : /snapshot/raw, /stream/raw, /stream.mjpg, /preview.png et les écrivains locaux AVI, SER ou PNG. Le plan de contrôle utilise des réponses structurées pour les réglages, la télémétrie et l’état des modules.

Cette séparation évite d’utiliser la prévisualisation compressée comme source scientifique. En plein format IMX477, le flux RAW HTTP mesuré plafonne autour de 105 à 115 MB/s alors que la capture peut être plus rapide. Le chemin d’enregistrement local se branche donc avant le réseau.

Capture
Enregistrement localRAW écrit directement sur le stockage
Transmission RAW réseau, optionnelleflux séparé, issu de la capture
Schéma non à l’échelle : la capture alimente deux voies distinctes. Le réseau n’est pas en aval du fichier local, qui n’est pas la source du flux transmis.

Cycle d’une trame et mutualisation par frame_id

  1. Le backend acquiert le buffer et les métadonnées du capteur.
  2. Le producteur publie un instantané de trame identifié par frame_id.
  3. Le RAW peut être transmis, analysé ou placé dans la file d’enregistrement sans passer par un fichier intermédiaire.
  4. La prévisualisation sélectionne le rendu brut, luminance ou couleur et applique la réduction demandée.
  5. La luminance, les buffers de travail et le JPEG encodé sont mis en cache pour cette trame.
  6. Chaque client reçoit au plus une partie MJPEG pour ce frame_id.

Avec le backend libcamera et le processeur de signal d’image (ISP) actif, le RAW et la sortie couleur rouge-vert-bleu (RGB) du pipeline matériel proviennent de la même requête. Le traitement scientifique continue à consommer le RAW ; cette sortie sert uniquement de point de départ à la prévisualisation.

Domaines d’exécution, files et tampons

Le serveur ne repose pas sur une unique boucle événementielle. Les domaines présents ou activés selon la configuration comprennent :

Les opérations par pixel utilisent OpenMP (Open Multi-Processing, interface de programmation parallèle) au-dessus d’un seuil de taille afin que le coût d’ordonnancement et de synchronisation du travail parallèle ne dépasse pas le gain attendu. Le solveur de résolution de champ sans centre initial connu fonctionne lorsque l’échantillonnage approximatif est disponible : il teste des régions du catalogue et peut appliquer un filtre d’horizon si la position et l’heure de l’observateur sont valides. Il utilise le même mécanisme, avec au plus quatre fils de calcul et un cœur laissé disponible lorsque plusieurs cœurs sont annoncés. Il s’agit donc d’une résolution sans centre initial connu, mais pas sans a priori : l’échantillonnage approximatif est fourni et, lorsque la position et l’heure de l’observateur sont disponibles, le filtre d’horizon peut réduire le domaine de recherche.

Cette architecture utilise des verrous, variables de condition et files protégées. Elle n’est pas présentée comme sans verrou. Sa propriété recherchée est l’isolement des rythmes : une écriture lente ou une analyse coûteuse ne doit pas imposer son temps de service à la caméra.

Files d’écriture, tampons et surcharge

L’enregistreur reçoit une référence vers la charge utile RAW et la confie à un fil d’écriture dédié. Sa file est limitée par un budget mémoire ; un arrêt normal la vide avant de fermer le conteneur, tandis qu’un abandon explicite comptabilise les trames rejetées. Cette différence évite de produire silencieusement un fichier incomplet.

La prévisualisation utilise le double tampon : pendant que le JPEG de la trame N est encodé, la trame N+1 peut être préparée. La frame courante, le JPEG et les buffers intermédiaires sont réutilisés pour limiter les allocations, les copies et le swap sur Pi Zero.

Schéma de principe, non à l’échelle : le temps progresse de gauche à droite. Traitement N débute après Capture N et recouvre une partie de Capture N+1. Le décalage est volontaire ; le schéma ne suppose pas un démarrage ni une fin parfaitement simultanés.

Cache partagé et cadence d’analyse

Le cache est indexé par l’identité de la source et par les paramètres qui affectent le rendu. Le JPEG n’est pas réencodé pour chaque navigateur. La luminance sur cellules Bayer peut être partagée entre la vidéo et l’overlay. Lorsque l’analyse d’un ciel vide ne produit aucun changement utile, sa cadence peut descendre vers une analyse par seconde afin de préserver la vidéo.

Le correctif du 23 septembre impose également l’attente d’une nouvelle trame. Avant lui, une boucle plus rapide que la caméra pouvait renvoyer plusieurs fois le même JPEG. Après correction, les mesures IMX477 en 1332 × 990 donnent 47,7 images capturées, 47,7 images RAW et 47,7 images MJPEG distinctes par seconde, avec 0 % de doublons.

Performances mesurées : double tampon et ISP

Le benchmark du 29 août utilise une OV5647, une exposition de 20 ms et un gain 8. Les images JPEG distinctes sont comptées, sans confondre répétition HTTP et nouvelle trame.

Cadence MJPEG avant et après double tampon, benchmark OV5647 du 29 août.
Plateforme et modeRenduAvantAprèsGain
Pi 4, bin2Couleur31,1 IPS45,4 IPS+46 %
Pi 4, bin2Couleur + balance31,2 IPS45,4 IPS+45 %
Pi Zero 2 W, bin2Luminance16,5 IPS27,2 IPS+65 %
Pi Zero 2 W, bin2Couleur11,2 IPS19,2 IPS+71 %
Pi Zero 2 W, bin2Couleur + balance11,2 IPS19,3 IPS+72 %

Sur Pi 4 en bin1, la cadence reste proche de 15 IPS avant et après : la lecture du capteur est alors le facteur limitant. Le résultat est important méthodologiquement, car il montre que le parallélisme ne crée pas un gain lorsque le goulot se situe en amont.

Intégration de l’ISP

Le chemin ISP introduit un accélérateur matériel sans modifier le contrat du cœur. La sélection auto, software ou isp appartient aux réglages de prévisualisation. Un profil de tuning neutralisé doit être présent et validé pour le capteur ; sinon le mode automatique revient au logiciel et publie la raison.

Sur IMX477/Pi 4 en 4056 × 3040, la cadence mesurée reste voisine de 11,2 IPS, limitée par l’acquisition, mais la charge du processus passe de 232,6 % à 93,4 % et la latence de 55,7 à 40,2 ms. Sur OV5647/Pi 3 en 2592 × 1944, la cadence passe de 12,80 à 14,92 IPS, la charge du processeur central (CPU) de 299,0 % à 113,7 % et la latence de 72,8 à 42,2 ms.

Modularisation et coût de compilation

Le serveur historique était largement défini dans les fichiers d’en-tête. Toute modification pouvait forcer la recompilation d’une grande partie du projet et augmenter fortement la mémoire du compilateur, ce qui est pénalisant sur Pi Zero. La séquence de restructuration du 25 septembre déplace les implémentations dans des unités .cpp, réduit les inclusions transitives et masque les détails lourds derrière des façades PIMPL.

Ont notamment été isolés le transport HTTP, les assets web, les réglages caméra, les backends, l’enregistrement RAW, la supervision, les catalogues, les solveurs, les opérations d’image et les modules applicatifs. Le serveur HTTP conserve encore une responsabilité d’orchestration importante : cette dette est documentée plutôt que masquée.

Progression du volume de code

Progression des lignes non vides du logiciel EIP et de ses tests, de 12 071 lignes le 14 août à 111 900 le 5 octobre 2026.
Le total atteint 111 900 lignes non vides au 5 octobre : 87 870 pour le logiciel livré et 24 030 pour les tests. La rupture du 28 septembre correspond notamment à la consolidation des chaînes d’imagerie et de stockage et à une forte augmentation de la couverture de tests. Télécharger le CSV.

La série utilise douze instantanés du dernier commit disponible à la fin de chaque date. Le périmètre de production est limité aux extensions C/C++, JavaScript, CSS et HTML sous src/ et assets/web/. Le périmètre de test contient les sources C/C++, JavaScript et shell sous tests/. Les lignes vides sont retirées. Sont exclus : documentation, site éditorial web/, bases, catalogues, données scientifiques, figures, dépendances, benchmarks, scripts de déploiement et outils ponctuels.

Sur ce périmètre, le logiciel livré est multiplié par 7,45 et l’ensemble logiciel plus tests par 9,27 en 52 jours. Les tests représentent 21,5 % du total final. Ces ratios décrivent la croissance du dépôt ; ils ne constituent pas une mesure de maintenabilité ou de qualité.

Résilience et fonctionnement embarqué

Le serveur supervise les blocages de capture, borne le nombre de flux MJPEG par adresse, sépare la veille des poses longues et peut maintenir l’acquisition active pendant une opération qui interdit l’économie d’énergie. Les reconfigurations caméra sont sérialisées. Les longues poses utilisent une requête unique à partir d’une seconde, indépendamment du mode ou de la résolution, afin d’éviter une trame de prévisualisation longue supplémentaire.

Le portail repose sur la découverte de services : une application non compilée ou masquée n’est pas affichée. Un profil léger peut retirer PiNox pour les cibles à faibles ressources. La Projection fonctionne dans un navigateur et peut aussi être lancée localement lorsqu’une sortie HDMI est détectée.

Conclusion

L’originalité d’EIP ne réside pas dans le nombre de fils d’exécution pris isolément. Elle tient à la combinaison d’un propriétaire unique de la caméra, de deux chemins de données cohérents, de caches associés aux trames, de traitements asynchrones et d’une API partagée entre plusieurs applications.

Les mesures montrent où cette architecture produit un gain réel et où elle atteint simplement la limite du capteur. Cette distinction est essentielle : l’objectif n’est pas d’occuper tous les cœurs, mais de préserver l’acquisition, la traçabilité scientifique et la réactivité du service lorsque plusieurs usages coexistent sur le même Raspberry Pi.

Pour continuer

Autre niveau de lecture

Dans le moteur d’EIP — version découverte · Comprendre les chemins de données et les choix d’architecture par les schémas.