Vers un effondrement civilisationnel ?

22 February 2024

Tags: ecologie climat politique civilisation

Coucher des mots sur le papier, aussi numérique soit-il, est une forme de thérapie : faire sortir ce qui doit sortir, organiser ses idées, prendre le temps de la réflexion, se rassurer parfois. Prédire le futur est bien évidemment impossible, mais il y a des ressentis, des signaux plus ou moins faibles, qui peuvent nous donner une idée de ce qui nous attend. Ce billet n’a aucune prétention scientifique, il s’agit plutôt d’une synthèse des nombreuses sources que j’ai lues ou visionnées sur le sujet au cours des dernières années. Au centre de ce texte, une interrogation : est-on en train d’assister à la fin de notre civilisation ? Après tout, l’Histoire est faite de disparitions de civilisations, pourquoi la nôtre survivrait plus qu’une autre ? En trame de fond, la question qui me préoccupe le plus : a-t-on déjà scellé le sort de nos enfants ? A qui s’adresse ce billet ? A ma famille et mes amis, probablement. À mes enfants, sûrement, pour exprimer mes regrets, mes craintes, mais aussi mon espoir envers eux. Enfin à tous celles et ceux qui devraient le lire mais qui ne le feront jamais, certainement.

La genèse

J’ai une sensibilité à la fois sur les sujets écologiques et scientifiques depuis ma jeunesse. Je me souviens encore des conversations avec mon copain Erwan, au collège. Nous arrivions tôt le matin, bien avant le début des cours, et avions entre autres de longues conversations sur la Terre et comment la sauver. A l’époque, nous ne parlions pas de réchauffement climatique : les sujets tournaient essentiellement autour de la pollution, du trou dans la couche d’ozone, de la disparition des papillons de nuit ou des ours polaires.

Dans ce terreau fertile aux idées dites écologistes, j’étais biberonné aux séries SF, et en particulier par Star Trek, série humaniste qui m’a profondément marqué. Il en est ressorti une double culture, à la fois la prise en compte de la sauvegarde de l’environnement, mais aussi un profond respect pour la science et ce qu’elle permet d’expliquer du monde. J’aime la rigueur de la démarche scientifique, ainsi que la capacité à se remettre en question, en opposition aux points de vue dogmatiques que l’on trouve par exemple dans les religions. Agnostique, j’ai cherché mes réponses dans la science et continue de le faire aujourd’hui, notamment en appliquant le principe du rasoir d’Okham.

J’avais déja sur ce blog abordé le sujet du rationnel et des reproches qu’on m’en a fait. Cette rationalité est aussi celle qui me pousse à écrire et partager mon angoisse : lorsqu’on a un "esprit scientifique" et que l’on a les informations dont on dispose aujourd’hui, il est difficile, voire impossible d’être optimiste. Pire, il est difficile d’imaginer autre chose qu’un effondrement pur et simple de notre société. Cette prise de conscience, ce pessimisme, doit se convertir en énergie, en action.

Précisons cependant que lorsque je parle d’effondrement civilisationnel, je me moque éperdument des fantasmes de l’extrême-droite sur le grand remplacement (culturel), de leur racisme, de leur xénophobie : je suis à l’opposé de tout cela, pour moi la "tradition chrétienne de la France" n’est qu’une vaste blague à l’échelle de l’Humanité et je suis de ceux qui pensent que l’égalité des droits n’est pas négociable, quelle que soit son origine, son orientation sexuelle ou politique.

Non, je parle ici d’un effondrement de notre mode de vie basé sur la consommation, la propriété privée et la mondialisation : je parle de la disparition possible de la démocratie et du retour de la guerre et des horreurs qui vont avec.

De quoi parle-t-on ?

Une fois que vous savez ce qui se passe au niveau climatique, il n’y a que trois options possibles : le renoncement, le cynisme ou l’action. Pour mes enfants, j’ai choisi d’agir : c’est notamment pour cela que je me suis lancé en politique, certes à un niveau local puisque limité aux municipales, il y a quelques années. C’est aussi pour celà que je communique beaucoup sur les sujets écologique et politique, quitte à en agacer quelques uns.

Une des premières choses dont il faut prendre conscience, c’est l’urgence climatique. Je discute souvent du réchauffement avec des personnes sensibilisées au sujet, mais paradoxalement, rares sont ceux qui ont vraiment conscience de l’urgence de la situation. Au contraire, on entend dire que les écolos sont pénibles, le RN va même jusqu’à affirmer que le GIEC exagère. Rappelons que le GIEC est un organe international qui synthétise les conclusions de centaines d’études et que de part la nature extrêmement politique de ses rapports, c’est précisément plutôt l’inverse qui se produit, avec des rapports qui proposent différents scénarios, les politiques préférant regarder les plus optimistes.

Récemment, Jean-Marc Jancovici, spécialiste mondial du sujet, inventeur du concept de "bilan carbone" était auditionné au Sénat devant des élus parfois médusés par ses réponses.

1.5 degrés, c’est mort. 2 degrés, sauf chute de comète ou effondrement économique, c’est parti pour être mort.
— Jean-Marc Jancovici
Audition au Sénat du 12 Février 2024

Pourtant, aucune des affirmations de Jancovici, dont je me sens proche idéologiquement au moins sur ces sujets, ne devraient être surprenantes : on le sait depuis longtemps, les rapports s’enchaînent, mais l’inaction persiste.

Une grosse partie du problème est qu’il est difficile pour un être humain, de quantifier par nos simples sens une augmentation de 2 degrés. Aujourd’hui encore, une canicule est systématiquement illustrée par des vacanciers à la plage, là où on devrait montrer des ruisseaux asséchés, des forêts qui partent en fumée, des personnes âgées en détresse, des agriculteurs qui ne peuvent plus arroser leurs cultures.

Il est important de comprendre que 2 degrés, c’est une moyenne mondiale : le réchauffement est plus important dans certaines zones de la planète (les pôles par exemple) et moins dans d’autres. Une différence de 2 degrés à l’échelle planétaire, c’est 3 ou 4 degrés en plus en France (en 2022, nous avons atteint +2.7⁰) Entre -1.8 et -18 degrés, c’est précisément ce qui séparait l’ère glacière de l’ère pré-industrielle. A l’époque, nous avions des glaciers de plusieurs centaines de mètres de haut, l’Angleterre était reliée au continent (les océans étaient 120 mètres plus bas). Si une si petite différence de température pouvait provoquer une telle conséquence, imaginez ce que 2 ou 3 degrés en plus pourraient donner. Précisément, on a du mal à s’imaginer, et c’est pour celà que le GIEC établit des scénarios, des prédictions. Ainsi, il est plutôt rassurant que la France se prépare à un scénario à +4⁰.

Pourtant, lorsque la France est touchée par une "vague de froid" (je le mets entre guillemets parce que ces vagues sont moins fréquentes et moins fortes qu’il y a 20 ans), nous retrouvons ces réflexes climatosceptiques, en confondant météo (temps court) et climat (temps long).

Aujourd’hui nous sommes, mondialement, au niveau de la barre des 1.5 degrés et les conséquences au niveau local se font déja sentir :

A 1.5 degrés, nous avons déja scellé le destin de pays entiers. Les îles Tuvalu, par exemple, vont disparaître. Ce pays est le premier du monde à avoir signé un accord d’asile climatique avec l’Australie. Imaginez-vous votre pays entier disparaître ? Ceci nous semble lointain, pourtant c’est la réalité que sont en train de vivre des millions de personnes dans le monde : leur existence même est menacée. Que feriez-vous si vous ne pouviez, physiquement, plus vivre en France ? Laissez-moi penser que les obsessions migratoires de politiciens et autres consultants du bar du coin sont d’un tel ridicule comparé à ce qui nous attend…​

Nous sommes en avance sur les 1.5 degrés prévus par le GIEC: pour la première fois, les +1.5⁰ ont été dépassés pendant 12 mois consécutifs.

A 2 degrés, nous savons déjà que la sécurité alimentaire ne sera plus assurée, que des conflits liés aux migrations climatiques vont éclater, pour l’accès aux ressources et pour justement empêcher les migrations de personnes qui cherchent simplement à survivre. Fantasme ? Regardez par exemple ce qui se passe entre l’Iran et l’Afghanistan pour l’accès à l’eau potable.

A 2.5 degrés, certaines régions du monde, en particulier en zone tropicale, deviennent inhabitables. Les rassuristes avancent que l’homme a toujours su s’adapter, mais il existe cependant des limites physiologiques, la réalité scientifique s’impose à tous. Au-delà d’un certain seuil, la combinaison de la température et de l’humidité ambiante rend inefficace le mécanisme de transpiration. Nous mourrons, il ne s’agit pas d’une opinion, mais d’une réalité devenue tristement célèbre l’été dernier lors d’un concert de Taylor Swift.

De la difficulté des ordres de grandeur

Un des problèmes majeurs du réchauffement climatique est qu’il est, même en étant sensible au sujet, difficile de se rendre compte des ordres de grandeur. J’étais par exemple déjà sensibilisé au sujet, mais, malgré tout, je continuais à prendre l’avion en particulier pour mon travail. Je savais que l’avion était mauvais pour le climat, mais je n’avais aucune idée d’à quel point : on sait que c’est mauvais, mais rares sont ceux qui nous donnent les ordres de grandeur. On se retrouve ainsi avec des politiques qui nous incitent à éteindre nos appareils en veille mais nous proposent de faire un aller-retour à Rome pour 100€ ou moins par avion ! Des non-sens écologiques, mais pas économiques : un aller-retour Paris-Dubaï par exemple, c’est l’équivalent de votre budget carbone annuel selon les accords de Paris ! Des outils existent désormais pour évaluer votre impact et des activistes comme Bon Pote font un excellent travail pour décrire les problèmes inhérents à l’industrie aéronautique.

Habitué des conférences à l’international pour mon travail, ma prise de conscience sur ce sujet particulier fut aussi tardive que brutale. J’en ai ai encore honte. J’ai cependant pris la décision, voici un peu plus de 4 ans, de ne plus prendre l’avion et de n’assister qu’aux conférences auxquelles je peux me rendre par train. C’est pourtant un vœux pieux, puisque je reste un être humain : que ferais-je lorsque mon entreprise me donnera le choix entre prendre la porte ou donner une conférence à San Francisco, ou une réunion de travail au Maroc ? Ce problème est aussi sujet de tensions familiales, entre les vœux légitimes de "voyager", d’explorer le monde et ses merveilles, et mon obstination à dire non. Combien de temps résisterais-je à cette pression ? Une bonne partie de ma carrière s’est construite sur le fait de rencontrer des experts de mon domaine à l’étranger, mais aussi à partager mes connaissances : je suis le fruit du réchauffement climatique. Quelle légitimité ai-je à vous "faire la morale" et vous demander, à vous, d’y renoncer ? Qui suis-je pour refuser ce que j’ai moi-même fait ?

Je n’ai pas de bonne réponse à cette question, si ce n’est de faire appel à votre sensibilité. D’autre part, je ne serai pas de ceux qui vous jugent pour vos choix : je suis le premier plein de contradictions et nous faisons, chacun, nos choix en fonction de ce que nous savons à un instant T, de nos contraintes, et finalement de nos convictions. Pour certaines décisions, nous avons le choix et certaines décisions seront plus faciles à prendre pour vous que pour moi, et inversement !

Il est bien plus facile pour un citadin de se séparer de son SUV que pour quelqu’un qui n’a pas accès aux transports au commun en zone rurale. Inversement, manger local et faire travailler producteurs locaux est plus simple pour un rural qu’un citadin. Chacun de nous, à notre échelle, faisons des choix qui nous semblent opportuns compte-tenu de nos situations personnelles.

Prenons donc ces ordres de grandeur en exemple. Pour respecter les accords de Paris et limiter le réchauffement climatique à 2 degrés, notre empreinte carbone, par personne, doit tomber à 2 tonnes par an. A ce jour, les estimations varient, mais l’empreinte carbone d’un français est de l’ordre de 10 tonnes par an.

empreinte carbone

En France, 31% des émissions sont liées au transport, donc à la voiture individuelle et aux transports de marchandises, c’est dire si le sujet de la voiture est important. Pourtant, que constate-t-on ? L’automobile est le 2d plus gros contributeur à la publicité en France, avec environ 1500€ de budget communication par voiture. Notre Président aime la bagnole et envoie un message désastreux. Bruno Le Maire, devant trouver 10 milliards d’économies, choisit de le faire sur l’environnement et les mobilités. En clair, on nous vend des voitures au lieu de nous vendre des vélos électriques, dont l’empreinte carbone est 20 fois inférieure.

Ce site montre l’évolution de l’empreinte carbone par tête et par pays, depuis le début de l’ère industrielle. Il y a 2 choses importantes à comprendre :

  • l’empreinte carbone est directement corrélée au niveau de vie des habitants. Plus on est riche, plus on consomme. Plus on consomme, plus notre empreinte est forte. L’empreinte carbone est 10 à 50 fois plus forte dans les pays industrialisés qu’en Afrique. Ainsi, il est complètement faux d’affirmer, comme Nicolas Sarkozy, que la crise démographique est responsable de la crise climatique. Ça n’est pas le nombre de personnes qui compte, mais leur capacité à consommer. Diminuer la population serait donc une solution…​ à condition de le faire dans les pays développés !

  • l’empreinte carbone est essentiellement impactée par la consommation d’énergies fossiles (pétrole et charbon). C’est ce qui explique que l’empreinte carbone d’un français est plus faible que celle d’un allemand ou d’un polonais : là où ils utilisent des centrales à charbon pour se chauffer et faire tourner leurs industries, nous avons des centrales nucléaires.

Sur ce sujet du nucléaire, soyons clairs : je suis écolo et pour. Lorsqu’on connaît ces ordres de grandeurs, lorsqu’on sait qu’environ 240 000 personnes meurent en Europe tous les ans à cause de la pollution atmosphérique principalement liée à la combustion de charbon, la position écologique anti-nucléaire traditionnelle est difficilement tenable. Cette position anti-nucléaire, je l’avais de part de mes lectures lorsque j’étais gamin (Pif’Gadget, Greenpeace, des positions essentiellement liées à la peur du nucléaire, des accidents et de leurs terribles conséquences. Ça, c’était avant d’avoir pris le temps d’étudier le sujet. J’ai depuis largement révisé ma position et suis convaincu qu’on ne pourra pas s’en sortir sans nucléaire. Il d’ailleurs assez ironique de constater que la biodiversité a fortement augmenté à Tchernobyl, depuis que la zone n’est plus occupée, de quoi être optimiste sur la résilience des écosystèmes si nous devions disparaître !

Ceci ne veut pas dire qu’on ne peut pas développer les énergies renouvelables, bien au contraire, mais il faut encore une fois avoir les ordres de grandeur en tête. Vous trouverez des chiffres différents selon les estimations plus ou moins pessimistes (notamment le facteur de charge), mais il faut comprendre que pour remplacer 1 seul réacteur nucléaire, il faut environ 1000 éoliennes ou 55 km² de panneaux solaires. Nous avons 56 réacteurs en France…​ Sans réduire notre consommation électrique, ceci signifie que nous devons exacerber encore plus la concurrence pour l’occupation des sols : doit-on s’en servir pour produire de la nourriture, de l’énergie, du logement ou la laisser libre pour la biodiversité ?

Est-il dès lors si surprenant que l’Allemagne ait dù raser des villages entiers pour exploiter le charbon nécessaire à la remise en marche de ses centrales ? Une bombe climatique ! Doit-on ensuite s’étonner que ces exemples soient repris par les réactionnaires pour décrédibiliser toute action de protection de l’environnement ?

Voilà le noeud du problème : notre société moderne est bâtie sur la consommation massive d’énergie, en particulier du pétrole, et à moins de réduire drastiquement cette consommation, nous ne pourrons tenir nos engagements. A l’heure actuelle, il faudrait réduire de 5% par an notre consommation d’énergies fossiles pour y arriver. Cette consommation étant directement corrélée à la sacro-sainte croissance, il s’agit ni plus ni moins que d’avoir l’équivalent d’un COVID, tous les ans ! Nos politiques ont déja renoncé, personne n’étant prêt aux sacrifices que celà implique.

Note
On m’a posé la question de la chaîne d’approvisionnement d’uranium, qui peut être problématique. C’est vrai, mais uniquement si nous restons à cette génération de réacteurs. La 4ème génération, les surgénérateurs sont capables d’utiliser les produits dérivés de la fission et nous donnent suffisamment de ressources pour des siècles de consommation. Pourtant, ce sont tous les expérimentateurs qui ont été mis à l’arrêt (Superphénix ou Astrid). Un gâchis dénoncé par Yves Bréchet lors de son audition au Sénat.

De sacrifices, pourtant, c’est de cela qu’il s’agit. Sacrifier nos générations futures, ou sacrifier notre mode de vie. Nous vivons toujours selon le mythe d’une croissance infinie : toute notre société est bâtie sur ce seul concept économique qui n’a aucun sens physique. La réalité physique des choses est pourtant implacable : il n’existe pas de croissance infinie dans un monde fini. Là encore, il est indispensable de parler d’ordres de grandeur : une croissance à 2% signifie un doublement de la valeur tous les 36 ans. Qui dit valeur dit production, dit consommation d’énergie, dit impact sur l’environnement.

Dit autrement, la croissance est une courbe exponentielle : il s’agit du genre de courbes que l’on ne souhaite pas voir.

annual co2 emissions per country

N’est-il pas inquiétant de constater que dans ce graphique, des crises majeures comme les chocs pétroliers des années 70 ou le COVID n’ont eu que peu ou pas d’influence sur notre consommation ?

Une autre visualisation de la notion d’exponentielle, c’est celle des anomalies de température, produite par la NASA. Une animation que je trouve particulièrement efficace pour comprendre l’effet d’emballement :

Cette exponentielle explique pourquoi le développement des énergies renouvelables ne s’est pas faite au détriment des énergies fossiles : elle s’est faite essentiellement en complément, parce que nous avons toujours besoin de plus d’énergie pour maintenir cette croissance.

Ainsi, si la consommation de charbon dans la production énergétique mondiale baisse en pourcentage, le volume n’a jamais été aussi élevé ! Une mauvaise nouvelle à cependant relativiser, puisque la production de charbon baisse : en effet, dans des pays tels que l’Allemagne, les renouvelables sont destinés à remplacer les centrales à charbon.

Le problème des courbes comme celles-ci est que les prévisions deviennent presque impossible à formuler, on entre dans le domaine de l’inconnu, et les esprits scientifiques comme moi n’aiment pas cela. Serge Zaka, docteur en agroclimatologie, décrit des phénomènes de réchauffement des océans statistiquement impossibles (autrement dit, les marges dépassent ce que la statistique classique considère comme possible).

Cette question de la croissance, pourtant, est au cœur de notre survie. Attention, je ne parle pas de la survie de l’espèce, ni même de "sauver la planète". En effet, même en étant pessimiste, je pense que l’espèce humaine survivra. En revanche, ne comptez pas sur moi pour vous dire combien survivront : mon intuition, compte-tenu des paramètres dont nous parlons ici, est que nous risquons de voir une nouvelle crise majeure. Et quand bien même l’espèce humaine ne survivrait pas, la vie, elle, continuera sans nous : elle existait avant, elle existera après.

C’est l’histoire d’un skieur qui se répétait "jusqu’ici tout va bien"

la haine

Pour prendre une analogie, nous sommes un peu dans la situation d’un skieur du dimanche : il descend sa piste, il à se sent à l’aise. Il accélère, prend plaisir, jusqu’à se rendre compte qu’il a pris trop de vitesse, que la pente est bien trop raide et qu’il ne peut plus maîtriser sa course. En bas, un croisement, d’autres skieurs, il est trop tard pour s’arrêter et il n’y a que 2 solutions :

  1. virer, se laisser tomber, quitte à se casser une jambe, pour éviter le groupe

  2. continuer et entrer en collision, en entraînant de nombreuses autres personnes dans la chute et potentiellement de nombreuses victimes

M’est avis que beaucoup de personnes choisissent instinctivement la 2ème solution, parce que la première n’est pas confortable et que notre instinct de préservation nous joue des tours : incapable d’anticiper des conséquences encore pires à long terme, notre espèce se mure dans le déni et choisit la solution la moins logique. Pire, notre tendance naturelle est, souvent paradoxalement de bonne foi, de déresponsabiliser. Les exemples sont nombreux : mettre des radars automatiques au lieu d’apprendre à respecter les limites, demander aux enfants de mettre des gilets jaunes au lieu de sécuriser les routes, dire aux filles de se couvrir au lieu de demander aux garçons de cesser de les voir comme des objets sexuels, demander d’éteindre vos box internet au lieu de prendre votre vélo pour aller chercher du pain…​

Rassurisme et techno-solutionnisme

Maintenant que le réchauffement climatique est palpable et subi par les plus sceptiques d’entre nous, un nouveau mal est en marche : le rassurisme. Se substituant au climato-scepticisme, il consiste à affirmer que nous nous en sortirons toujours, que l’homme s’est toujours adapté, que nous trouverons des solutions techniques, et cætera, et cætera. Le rassurisme est une plaie parce qu'il incite à l’inaction. Agréable discours à entendre, il est le parfait compagnon du status quo, sous couvert de discours "de bon sens", mais aussi un risque encore plus grand pour l’avenir. Je classe dans le rassurisme le discours techno-solutioniste que nous entendons de plus en plus.

Prenons par exemple l’idée souvent commentée de la séquestration du carbone : pourquoi ne pourrions-nous pas l’extraire de l’atmosphère ? A priori, l’idée n’est pas idiote, si on peut le faire il serait idiot de s’en passer. Pourtant, il suffit d’un tout petit peu de recherche pour se rendre compte des problèmes. Lorsqu’on parle de concentrations de CO², nous parlons de parties par million. Par exemple, 200ppm, ce sont 200 molécules de CO² sur 1 million : c’est une concentration extrêmement faible, mais qui a des effets dévastateurs. Si vous avez quelques souvenirs de physique, je vous conseille d’ailleurs cette excellente vidéo de Science Etonnante sur les mécanismes du réchauffement et le mythe de la saturation du réchauffement. Si tant est que nous disposions d’une technologie de "nettoyage", il faudrait brasser une quantité d’air phénoménale pour l’extraire et pour brasser cet air, il faudrait une quantité non négligeable d’énergie…​ En clair, il s’agit donc souvent plus de "coups de com'" d’entreprises cherchant avant tout à faire de l’argent sur le dos du climat, peu recommandables…​ mais pour lesquels des investisseurs peuvent se laisser séduire.

Un autre exemple, c’est la la colonisation de Mars, annoncée par Elon Musk. Peut-on être sérieux seulement 5 minutes ? Il n’est pas surprenant que les seuls qui y croient soient des économistes, c’est avant tout le modèle de la fuite en avant : puisqu’on ne peut pas sauver notre planète, rendons-en une autre vivable ! L’idée est d’autant plus ridicule qu’on ne sait même pas contrôler le climat ici, en premier lieu lutter contre le réchauffement climatique, alors que dire lorsque l’on parle de changer le climat d’une planète dont on ne sait presque rien…​ C’est une chose que de vouloir fouler Mars du pied, s’en est une autre que de la terraformer.

Mais un des problèmes de la croissance et de l’évolution de la technologie et de l’information, c’est qu’elle rend possible des actions individuelles aux conséquences potentiellement catastrophiques, au premier rang desquelles la géoingénierie. Ainsi, un article particulièrement inquiétant montre que nous disposons de moyens de diffuser économiquement du sulfure dans l’atmosphère, pour refroidir l’atmosphère'. Même avec des conséquences terrifiantes, telles que des pluies acides, ou le simple fait qu’arrêter d’en diffuser entraînerait un réchauffement massif encore plus grand, le fait est que ça pourrait fonctionner en pratique. Nous sommes donc en plein dans ce que j’expliquais plus haut : plutôt que de résoudre le problème à sa source, on continue, quitte à avoir des conséquences bien pires plus tard. Lorsqu’une technologie est disponible, elle est utilisée. La question n’est donc pas de savoir SI, mais QUAND elle sera utilisée, soit par un riche milliardaire qui souhaite continuer à faire du profit en dépit du bon sens, ou d’un Etat qui lutte pour sa survie. Si vous vous rappelez de l’horloge de l’apocalypse, il me semble qu’on se rapproche dangereusement de minuit…​

L’autre effet pervers du techno-solutionnisme, c’est que si tant est qu’il fonctionne, il n’incite pas à la sobriété. Ainsi, toutes les évolutions technologiques qui ont permis de faire des économies n’ont in fine pas eu pour conséquence de réduire la consommation. Par exemple, les moteurs thermiques sont aujourd’hui beaucoup plus efficaces qu’avant, mais les économies ont servi à augmenter l’autonomie ou à avoir des voitures plus grosses : c’est l’effet rebond.

Ainsi, toutes ces évolutions technologiques ne sont en pratique là que pour supporter un modèle de consommation constant. Un autre exemple ? Le rendement à l’hectare de la production agricole a été multiplié 3 depuis 1960, par plus de 10 depuis le début de l’ère industrielle :

On se rend bien compte du mensonge qui consiste à dire qu’il faut bien continuer ce modèle pour nourrir la planète : nous pourrions utiliser les gains de productivité à l’hectare pour nourrir plus de monde mais nous avons choisi de les investir dans des biocarburants pour faire rouler des voitures, ou dans de la production industrielle de produits transformés qui menacent notre santé. Pour autant, les revenus des agriculteurs ne cessent de chuter, les famines n’ont pas disparu (essentiellement pour des questions de logistique) et les maladies cardiovasculaires explosent. Nous produisons largement plus que ce que dont nous avons besoin pour survivre, mais nous "produisons de la croissance" en transformant les produits. Là où au début du siècle, l’essentiel de la consommation se faisait du produit brut au consommateur, désormais l’essentiel se fait par des produits transformés. L’abondance est illustrée par ce graphique représentant l’apport calorique par habitant :

daily per capita caloric supply

A celles et ceux qui répondront que plus d’apport calorique c’est une meilleure santé, rappelons que nous avons besoin d’entre 2000 et 2500 calories par jour, un seuil qui a été franchi au début des années 1820. Depuis, nous sommes bien au-delà, ce qui explique notamment l’explosion de l’obésité et des maladies cardiovasculaires (en combinaison avec la sédentarité permise par l’exploitation des machines).

Ainsi, la croissance n’est pas nécessairement synonyme de progrès : au delà d’un certain seuil, elle devient maladive et entraîne plus de maux que de bien.

Les prémisses de cette constatation ne datent pas d’hier, le club de Rome s’en faisait écho il y a 50 ans déja. De nos jours, rares encore sont les économistes qui défendent la décroissance. En France, des chercheurs comme Timothée Parrique montrent avec brio que la décroissance ne peut plus être considérée comme un gros mot. Au contraire, elle devient nécessaire, comme le laisse entendre ce titre "ralentir ou périr".

Il ne faut pas non plus confondre le techno-solutionnisme avec l’utilisation des techniques permettant de limiter l’impact de notre consommation. Certains outils seront indispensables, mais si nous devons répondre à une solution d’urgence, il est préférable de le faire avec les technologies dont on dispose, pas de celles dont on ne sait pas si elles seront disponibles dans 10 ans.

Pourquoi un effondrement ?

Nous l’avons vu, la conjoncture n’est pas favorable, loin de là. Nous savons, nos gouvernements savent, mais rien ne change. N’était-ce pas Emmanuel Macron qui nous a promis que son quinquennat "sera écologique ou ne sera pas" ? Nous avons la réponse : après le une convention citoyenne sur le Climat vidée de sa substance, après le sacrifice de l’écologie au profit des agroindustriels qui nous confortent dans ce modèle, il n’y a a que du cynisme dans les décisions politiques. Même au niveau local, dans ma commune, la majorité se gausse à coups de croissance verte, un concept qui ne parle qu’aux économistes et qui n’a jamais démontré le moindre succès. Encore une fois, il n’y a rien de surprenant : un niveau de base en mathématiques ou de physique suffit à comprendre qu’on ne peut faire de croissance sans sacrifier de ressources, ce qui se traduit soit par de la pollution, soit par du réchauffement climatique. Dans ma commune, on construit encore des supermarchés en périphérie comme dans les années 70 et on moque les écologistes "décroissants", "contre l’emploi" et "pour l’insécurité". Qu’importe l’état catastrophique des cours d’eau, que l’eau doivent être importée de Loire-Atlantique pour subvenir aux besoins d’industries agroalimentaires locales bien connues, puisqu’on a la croissance ! Qu’importe que l’on doive construire sur des terres agricoles pour loger tous les néo-ruraux attirés par la croissance économique du territoire…​

Nous constatons là que malgré toutes les informations disponibles, nous sommes dans la situation du skieur qui ne peut plus s’arrêter; il y aura des dégâts !

Cependant, pas de quoi prophétiser un effondrement civilisationnel, me direz-vous. Certes, mais il y a plus : c’est la conjonction de facteurs qui peut entraîner notre chute.

Nous avons mentionné que dans le dérèglement climatique, l’essentiel du problème était concentré autour des énergies fossiles. Cependant, d’autres problèmes d’ampleur sont eux aussi liés à notre modèle de développement : l’effondrement de la biodiversité par exemple. Nous sommes entrés dans une nouvelle phase d’extinction de masse, tellement bien documentée qu’elle porte un nom: l’extinction de l’Holocène :

global living planet index

En 50 ans, plus de la moitié du monde sauvage a disparu. Je ne sais pas si vous vous rendez bien compte : l’homme est présent sur Terre depuis plus de 4 millions d’années, Homo Sapiens depuis 300000 ans environ, et en seulement 50 ans, nous avons détruit plus de la moitié des espèces. Pour la biodiversité marine, la situation est encore pire, avec la surpêche, l’augmentation de la température et l’acidification des océans. Depuis plus de 6 mois, nous vivons une véritable canicule marine, mais qui en a entendu parler ?

Or, l’homme fait partie d’un écosystème : nous vantons notre adaptabilité, mais nous sommes les premiers dépendants de notre environnement. Le détruire, c’est directement menacer notre survie : il faut être fou pour croire que l’homme peut survivre seul.

Devant l’opposition à la décroissance, ou même simplement les appels à la sobriété, les discours les plus réactionnaires sont en marche. Il n’y a pas de quoi être optimiste, lorsqu’en France, un ministre traite des militants écologistes d’éco-terroristes, mais en même temps, soutient des agro-industriels qui mettent le feu à des mutuelles…​ Là encore on pourrait croire à de l’ignorance, mais il s’agit d’un cynisme sans nom : tous savent pertinemment ce qui se profile, mais aucun n’a le courage politique pour faire ce qui est vraiment nécessaire : un nouveau modèle de société basé sur la sobriété. Que dire d’un pays où l’on s’émeut plus facilement d’une boîte de soupe versée sur un tableau protégé derrière une vitre en verre, que de plusieurs centaines de morts en Inde suite à la canicule…​ Pas vraiment de quoi être optimiste.

On pourrait s’arrêter là mais d’autres signaux sont tout aussi inquiétants. Je rappelais par exemple à quel point notre société moderne est basée sur les énergies fossiles. Le graphique ci-dessous montre par exemple la répartition de la consommation énergétique par filière :

energie par filiere

La baisse en 2020 est liée au COVID, mais ne nous y trompons pas : à l’échelle mondiale, les énergies fossiles dominent : les renouvelables représentent une part croissante de la production, mais ne sont comparables qu’au parc nucléaire, les énergies fossiles sont largement dominantes. Dans ce portrait, la situation du pétrole est plus critique : nous savons que les réserves s’épuisent. Que l’on décide de s’en passer volontairement ou non, nous arriverons avant la fin du siècle à la fin du pétrole.

Or, dans de nombreux domaines, nous sommes complètement dépendants du pétrole, non pas en tant que source d’énergie, mais de matière première :

  • pharmacologie

  • agriculture (machines, mais aussi engrais)

  • santé

  • textiles

  • cosmétiques

  • …​

Il ne vous aura pas échappé que l’Europe ne dispose pas ou peu de cette ressource. Préférons-nous continuer à brûler ce qui nous reste pour voyager pour 500€ à l’autre bout du monde, ou pour produire notre nourriture et fabriquer nos médicaments ? Mon choix est vite fait…​

A court ou moyen terme, la dépendance de l’Europe au pétrole signifiera un asservissement aux pays qui en disposent (si tant est qu’ils soient disposés à nous en vendre). Sachant que ces pays ne sont pas ce qu’il y a de plus démocratique, la question du respect des droits humains pourrait à moyen terme devenir un vague souvenir. Dès lors quels choix s’offriront à nous ? Entrer en guerre pour leur "voler" ces ressources ? Les forcer à nous en vendre ? A quel prix ? Comment ?

Le remplacement du pétrole en tant que source d’énergie est possible mais requiert une électrification massive de nos moyens de subsistance (transports, machines outils, industries, …​) et une relocalisation de la production, le transport maritime dépendant essentiellement de cette ressource. Or, qui dit électrification dit augmentation de la production. De quels moyens disposons nous pour remplacer une telle quantité d’énergie ? L’écologie n’est jamais aussi mauvaise que lorsqu’elle ignore la réalité physique des choses.

Les pays qui disposent de ces ressources, donc, ne sont pas particulièrement amicaux. Nous parlons de pays dont les actions récentes n’augurent pas d’un avenir radieux pour l’Europe, mais aussi pour leurs propres populations : la Russie, la Chine, …​ La Russie, qui ne cesse d’étendre son influence sur un continent Africain avide de "revanche" sur la colonisation et l’exploitation de leurs ressources par l’Occident. Certes la Russie n’est pas animée par la bonté de réparer les erreurs de l’Occident, elle souhaite tout autant s’accaparer les ressources minières du continent et par là donc disposer de moyens de pression…​ pour gagner sa "guerre civilisationnelle", mais le message est passé, la France doit s’en aller !

Cette guerre civilisationnelle, des gens comme moi y participent malgré eux. Ceux qui me connaissent savent à quel point, notamment, je lutte contre mon envie de quitter mon métier. A vrai dire, si je n’avais ni famille, ni crédits sur le dos (comme tout le monde), je crois que j’aurais déjà abandonné ce qui est pourtant une de mes passions.

En effet, en tant que développeur, non seulement je contribue massivement au réchauffement climatique (le numérique représente une part de la consommation d’énergie mondiale en explosion, notamment à cause de l’explosion du nombre de terminaux), mais j’ai aussi donné des outils de manipulation de masse, utilisés comme tels, par des puissances qui cherchent à nous déstabiliser. Les réseaux sociaux, notamment, sont devenus de véritables poubelles où les idées complotistes, antivax, climatosceptiques sont promues bien plus que les autres. Les idées dites "de droite" sont favorisées par les algorithmes de Twitter/X et les manipulations de la Russie déstabilisent nos démocraties. Savoir que ce que je développe sert à l’effondrement de la société et à la propagation des idées les plus nauséabondes me rend malade.

Force est de constater que le populisme monte en flèche : il a gagné au Brésil (Bolsonaro), aux Etats-Unis (Trump), au Royaume-Uni (Jonhson), en Hongrie (Orban), aux Pays-Bas (Wilders), en Argentine (Milei). Il faut être sacrément optimiste pour croire que la France puisse miraculeusement échapper à ce fléau. Pour autant, une fois en place, nous savons ce que ces gouvernements pensent de l’économie et donc de l’écologie. Pire, nous savons ce que ces personnes pensent du droit des femmes et plus largement de toute personne ne pensant pas comme eux. Je vous invite d’ailleurs à écouter cette interview de Véra Nikolski et Jancovici sur la fin de l’ère du pétrole et ses conséquences sur la démocratie et le droit des femmes.

Les idées qui sont promues dans nos sociétés modernes sont incompatibles avec nos objectifs climatiques : nous devons tous travailler, devons être encore et toujours plus productifs. Les outils que nous concevons, en informatique, font gagner de la productivité, mais cette productivité n’est pas rendue au travailleur pour du temps libre, elle est réinvestie en plus de croissance.

Produire, consommer, produire, consommer, produire…​ "pouvoir d’achat" et zéro chômage érigés en totems.

Ce que je constate donc tous les jours est une fuite en avant, doublée d’un déni, mais pire encore, une réaction exactement inverse à ce qu’il faudrait faire pour maintenir notre consommation sous les limites planétaires. Dès lors, la question de l’effondrement civilisationnel se pose : si tant est que nous prenions le virage, qu’en est-il des pays qui ne le feront pas et auront la capacité à nous menacer, précisément parce qu’ils auront fait le choix inverse ? C’est un dilemme auquel je n’ai pas de réponse, mais d’autres questions ne sont pas agréables à entendre :

Que ferons-nous lorsque les populations qui ne pourront plus vivre dans leur pays frapperont à notre porte ? Que ferons-nous lorsque nous ne pourrons plus importer nos médicaments, faute de moyens de transport longue distance ? Que ferons-nous lorsque l’Afrique nous demandera des comptes pour l’exploitation de ses ressources ? Que ferons-nous si nous n’avons plus de pétrole pour faire rouler nos tanks, voler nos avions et nous défendre contre des pays en quête de conquêtes territoriale, économique ou culturelle ? Que ferons-nous lorsque nous aurons tellement détruit nos services publics (transports, écoles, hôpitaux, production électrique) que nous serons dépendants de services commerciaux dont la seule survie ne dépend que de leurs marges ?

La mondialisation nous a paradoxalement mis dans une situation extrêmement précaire, l’Europe d’aujourd’hui est incroyablement fragile. Tout ça au nom de la sacro-sainte croissance, un concept qui rappelons-le, n’a aucun sens physique, c’est une pure construction mathématique permettant d’évaluer l’activité économique d’un pays.

Or, l’activité économique ne se mesure pas qu’à l’aube de ce qui s’achète. C’est pourtant ce que nous faisons tous les jours. Ainsi, le comptable qui travaille bénévolement dans une association humanitaire fait le même travail que le comptable qui travaille dans une entreprise d’extraction de minerais. L’un ne contribue pas au PIB, l’autre oui. Leurs contributions au bien-être de l’humanité sont elles pour autant comparables ?

Se passer de la croissance n’est pas sans poser des problèmes, tant elle est au cœur de notre société. Sans croissance, point de retraites, tout un monde de solidarité à réinventer ! Quelle entreprise accepterait volontairement de ne pas croître, alors que cela menacerait directement sa survie face aux concurrents qui, eux, prendront la décision de continuer ?

Pourtant, décroître l’industrie automobile, responsable d’une grande partie du réchauffement, de morts sur la route, de la pollution aux particules fines, ne serait que bénéfique pour notre société. Nous ne parlons pas de la supprimer du jour au lendemain, mais de planifier notre sortie. De même, l’opulence de agro-industrie devra disparaître, c’est une question de survie, au profit d’une agriculture raisonnée : produisons moins, mais de meilleure qualité, avec des revenus décents et moins de transformations. A court terme, achetons des Fairphone plutôt que des Vision Pro, achetons des produits réparables plutôt que des produits pas chers mais écologiquement aberrants. Les leviers sont nombreux, il faut "simplement" une volonté politique. Réapprenons à mutualiser, nous pouvons inventer de nouveaux modèles basés sur la coopération.

Plus nous attendons, moins les mesures que nous devrons prendre seront socialement acceptables et plus le risque de révolution sera important. Il n’est pas surprenant qu’on utilise l’expression d’écologie punitive, dans ce contexte, alors que le plus punitif, ce sont les conséquences directes de la surexploitation de notre environnement : inondations, sécheresses, maladies, incendies, …​ La punition est vécue de plein fouet par les agriculteurs qui perdent leurs récoltes, des entrepreneurs qui voient leur camping partir en fumée, des habitants qui voient leur habitation détruite par des inondations ou des tornades…​

En conclusion, tout semble converger vers l’idée d’un crash massif à venir : c’est la combinaison du réchauffement climatique, de l’effondrement de la biodiversité, du mythe de la croissance infinie, de la montée du populisme et de la raréfaction des ressources fossiles qui rend cet avenir possible.

Jamais ne n’aurais pensé, lorsque je discutais dans cette cour de récréation, voir cela de mon vivant.

Désormais, non seulement je le vois venir mais je le vois de plus en plus probable. Je ne saurais quantifier, mais pour moi nous sommes sortis du domaine du possible pour entrer dans celui du probable. Je n’ai pas de solutions miracles, sans changement fondamental de notre mode de vie, mais j’ai des questions, des craintes et aussi un message à faire passer : il est encore temps. La première de nos libertés, c’est encore de voter, faisons le tant que nous en avons encore la possibilité.

Stacking and mosaic creation with JSol’Ex 2.0

04 January 2024

Tags: astronomy astro4j solex java

A couple days ago I have released JSol’Ex 2.0. This software can be used as an alternative to INTI to process solar images acquired using Christian Buil’s Sol’Ex. This new version introduces 2 new features that I would like to describe in more details in this blog post: stacking and mosaic stitching.

Stacking

Stacking should be something familiar to anyone doing planetary imaging. One of the most popular sofware for doing stacking is AutoStakkert! which has recently seen a new major version. Stacking usually consists of taking a large number of images, selecting a few reference points and trying to align these images to reconstruct a single, stacked image which increases the signal-to-noise ratio. Each of the individual images are usually small and there are a large number of images (since the goal is to reduce the impact of turbulence, typically, videos are taken with a high frame rate, often higher than 100 frames per second). In addition, there are little to no changes between the details of a series (granted that you limit the capture to a few seconds, to avoid the rotation of the planet typically) so the images are really "only" disformed by turbulence. In the context of Sol’Ex image processing, the situation is a bit different: we have a few captures of large images: in practice, capturing an image takes time (it’s a scan of the sun which will consist of a video of several seconds just to build a single image) and the solar details can move quickly between captures. In practice, it means that you can reasonably stack 2 to 5 images, maybe more if the scans are quick enough and that there are not too many changes between scans.

The question for me was how to implement such an algorithm for JSol’Ex? Compared to planetary image stacking, we have a few advantages:

  • images have a great resolution: depending on the camera that you use and the binning mode, you can have images which range from several hundreds pixels large to a few thousands pixels

  • the images are well defined : in planetary observation, there are a few "high quality" images in a video of several thousand frames, but most images are either fully or partially disformed

  • there’s little movement between images: anyone who has stacked a planetary video can see that it’s frequent to see jumps of several pixels between 2 images, just because of turbulence

  • for each image, we already have determined the ellipse which makes the solar disk and should also have corrected the geometry, so that all solar disks are "perfect circles"

Therefore, a naive approach, which I tried without success a few months ago, is a geometric approach where we simply make all solar disks the same (by resizing), align them then create an average image. To illustrate this approach, let’s look at some images:

In this video we can see that there is quite some movement visible between each image. Each of them is already of quite good quality, but we can notice some noise and more importantly, a shear effect due to the fact that a scan takes several seconds and that we reconstruct line by line :

stack ref

The average image is therefore quite blurry:

stack average

Therefore, using the average image is not a good option for stacking and that’s why I recommended to use AutoStakkert! instead, which gave better results.

In order to achieve better results, I opted for a simple yet effective algorithm:

  • first, estimate the sharpness of each image. This is done by computing the Laplacian of each image. The image with the best sharpness is selected as the reference image

  • divide each image into tiles (by default, a tile has a width of 32 pixels)

  • for each tile, try to align it with the reference image by computing an error between the reference tile and the image

  • the error is based on root mean squared error of the pixel intensities : the better the tiles are aligned, the closer to 0 the error will be

  • this is the most expensive operation, because it requires computing the error for various positions

  • we’re only looking for displacements with a maximum shift of 2/3 of the tile size (so, by default, 21 pixels maximum)

  • if the shift between 2 tiles is higher than this limit, we won’t be able to align the tiles properly

We could have stopped here and already reconstruct an image at this stage, but the result wouldn’t be great: the fact that we use tiles would be visible at the edges of the tiles, with square artifacts clearly visible. To reduce the artifacts, I opted for a "sliding window" algorith, where the next tile will overlap the previous one by a factor between 0 (no overlap) and 1 (100% overlap). This means that for each pixel, we will get a "stack" of pixel values computed from the alignment of several tiles. The final pixel value is then computed by taking the median value of the stack. Even so, some stacking vertical or horizontal artifacts can still be sometimes visible, so the last "trick" I used is to build the stacks by only taking pixels within a certain radius, instead of the whole square.

The resulting, stacked image is here:

stack jsolex

We can see that:

  • noise from the original images is gone

  • shearing artifacts are significantly reduced

  • the resulting image is not as blurry as the average version

There were, however, some compromises I had to make, in order to avoid that the stacking process takes too long. In particular, the tile alignment process (in particular error computation) is very expensive, since for each tile, we have to compute 21*21 = 441 errors by default. With an overlap factor of 0.3, that’s, for an image of 1024 pixels large, more than 5 million errors to compute. Even computing them in parallel takes long, therefore I added local search optimization: basically, instead of searching in the whole space, I’m only looking for errors within a restricted radius (8 pixels). Then, we take the minimal error of this area and resume searching from that position: step by step we’re moving "closer" to a local optimum which will hopefully be the best possible error. While this doesn’t guarantee to find the best possible solution, it proved to provide very good results while significantly cutting down the computation times.

From several tests I made, the quality of the stacked image matches that of Autostakkert!.

Mosaic composition

The next feature I added in JSol’Ex 2, which is also the one which took me most time to implement, is mosaic composition. To some extent, this feature is similar to stacking, except that in stacking, we know that all images represent the same region of the solar disk and that they are roughly aligned. With mosaics, we have to work with different regions of the solar disk which overlap, and need to be stitched together in order to compose a larger image.

On December 7th, 2024, I had given a glimpse of that feature for the french astrophotograhers association AIP, but I wasn’t happy enough with the result so decided to delay the release. Even today, I’m not fully satisfied, but it gives reasonable results on several images I tried so decided it was good enough for public release and getting feedback about this feature.

Mosaic composition is not an easy task: there are several problems we have to solve:

  • first, we need to identify, in each image, the regions which "overlap"

  • then for each image, we need to be able to tell if the pixel value we read at a particular place is relevant for the whole composition or not

  • then we have to do the alignment

  • and finally avoid mosaicing artifacts, typically vertical or horizontal lines at the "edges"

In addition, mosaic composition is not immune to the problem that each image can have different illumination, or even that the regions which are overlapping have slightly (or even sometimes significantly) moved between the captures. Therefore, the idea is to "warp" images together in order to make them stitch smoothly.

Preparing panels for integration

Here are the main steps of the algorithm I have implemented:

  1. resize images so that they all solar disks have the same radius (in pixels), and that all images are square

  2. normalize the histograms of each panel so that all images have similar lightness

  3. estimate the background level of each panel, in order to have a good estimate of when a pixel of an image is relevant or not and perform background neutralization

  4. there can be more than 2 panels to integrate. My algorithm works by stitching them 2 by 2, which implies sorting the panels by putting the panels which overlap the most in front, then stitching the 2 most overlapping panels together. The result of the operation is then stitched together with the next panel, until we have integrated all of them.

The stitching part works quite differently than with typical stacking. In stacking, we have complete data for each image: we "only" have to align them. With mosaics, there are "missing" parts in the image that we need to fill in. To do this, we have to identify which part of a panel can be blended into the reconstructed image in order to complete it. This means that the alignment process is significanly more complicated than with typical stacking, since we will work on "missing" data. Part of the difficulty is precisely identifying if something is missing or not, that is to say if the signal of a pixel in one of the panels is relevant to the composition of the final image. This is done by comparing it with the estimated background level, but that’s not the only trick.

Despite the fact that our panels are supposedly aligned and that the circles representing the solar disks are supposed to be the same, in practice, depending on the quality of the capture and the ellipse regression success, the disks may be slightly off, with deformations. There can even be slight rotations between panels (because of flexions at capture time, or processing artifacts). As a consequence, a naive approach consisting of trying to minimize the error between 2 panels by moving them a few pixels in each direction like in stacking doesn’t work:

  • first of all, while you may properly align one edge of the solar disk, we can see that some regions will be misaligned. If these regions correspond to high contrast areas like filaments, it gives real bad results. If it happens at the edges of the sun, you can even see part of the disk being shifted a few pixels away from the other panel, which is clearly wrong.

  • second, estimating the error is not so simple, since we have incomplete disks. And in this case, the error has to be computed on large areas, which means that the operation is very expensive.

  • third, because we have to decide whether to pick a pixel from one panel or the other, this has the tendency to create very strong artifacts (vertical or horizontal lines) at the stitching edges

The stitching algorithm

Giving all the issues I described above, I chose to implement an algorithm which would work similarly to stacking, by "warping" a panel into another. This process is iterative, and the idea is to take a "reference" panel, which is the one which has the most "relevant" pixels, and align tiles from the 2d panel into this reference panel.

To do this, we compute a grid of "reference points" which are in the "overlapping" area. These points belong to the reference image, and one difficulty is to filter out points which belong to "incomplete" tiles. Once we have these points, for each of them, we compute an alignment between the reference tile and the tile of the panel we’re trying to integrate. This gives us, roughly, a "model" of how tiles are displaced in the overlapping area. The larger the overlapping area is, the better the model will be, but experience shows that distorsion on one edge of the solar disk can be significanly different at the other edge.

The next step consists of trying to align tiles of the panel we integrate to the reference panel using this model. This is where the iteration process happens. In a nutshell, we have an area where the solar disk is "truncated". Even if we split the image in tiles like with stacking, we cannot really tell whether a tile is "complete" or not, because it depends both on the pixel intensities of the reference panel and the second panel, and the background level. In particular, calcium images may have dark areas within the solar disk which are sometimes as dark as the background.

If you are struggling to understand how difficult it can be to determine if part of the image we consider is relevant or not, let’s illustrate with this image:

panel noise

Can you see what’s wrong in this image? Let’s increase constrast to make it clearly visible:

panel noise2

Now it should be pretty obvious that below the south edge of the truncated disk, we have an area which has pixels which are above the value of the background, but do not constitute actual signal! This problem took me quite some time to solve, and it’s only recently that I figured out a solution: before mosaicing, I am performing a background neutralization step, by modeling the background and substracting it from the image. While this doesn’t fully solve the problem, it makes it much less relevant for composition.

In addition, we have to compose the image using tiles which are incomplete, and we don’t know the orientation of the panels: they can be assembled north/south, or west/east, and nothing tells us. Potentially, it can even be a combination of these for a large number of panels.

Therefore, the algorithm works by creating a "mask" of the image being reconstructed. This mask tells us "for this particular pixel, I have reconstructed a value, or the value of the reference image is good enough and we won’t touch it". Then, for each tile, we consider tiles for which the mask is incomplete.

In order to determine how to align the truncated disk with data from the other image, we compute an estimate of the distortion of the tile based on the displacements models we have determined earlier. Basically, for a new "tile" to be integrated, we will consider the sample "reference points" which are within a reasonable distance of the tile. For this set of reference points, we know that they are "close enough" to compute an average model of the distorsion, that I call the "local distorsion": we can estimate, based on the distance of each reference point, how much they contribute to the final distorsion model for that particular point.

The key is really to consider enough samples to have a good distorsion model, but not too many because then the "locality" of alignment would become too problematic and we’d face misalignments. Because there are not so many samples for each "incomplete" tile, we are in fact going to reconstruct, naturally, the image from the edges where there’s missing data: when there are no samples, it basically means we cannot compute a model, so we don’t know how to align tiles. If we have enough samples, then we can compute a reliable model of the distorsion, and then we can reconstruct the missing part of each tile, by properly aligning the tiles together. If the number of samples is not sufficient to consider a good model, then we assume that no distorsion happens, which is often the case for "background" tiles.

Most of the difficulty in this algorithm is properly identifying "when" we can stitch tiles together, that is to say when we can tell that the alignment between tiles makes sense and that the alignment is correct. Often, I got good results for one kind of images (e.g, h-alpha images) but horrible results with others (e.g calcium) or the other way around. I cannot really say I took a very scientific approach to this problem, but more an empirical approach, tweaking parameters of my algorithm until it gave good enough results in all cases.

I mentioned that the algorithm is iterative, but didn’t explain why yet: when we compute the tile alignments, we only do so because we have enough local samples for alignment. We do this for all tiles that match this criteria, but we won’t, for example, be able to align a tile which is in the top of the image, if the bottom hasn’t been reconstructed. Therefore, the iteration happens when we have reconstructed all the tiles we could in one step: then we recompute new reference points, and complete the image, not forgetting, of course, to update our mask to tell that some pixels were completed.

Overall the algorithm is fairly fast, and can be stopped once we have completed all tiles, or after a number of fixed iterations in case of difficulties (often due to the background itself).

One last step

The algorithm we’ve described provides us with a way to "roughly" reconstruct an image, but it doesn’t work like what you’d intuititvely think of mosaic composition, by "moving" 2 panels until they properly align and blend them toghether. Instead, it will reconstruct an image by assembling tiles together, from what is already reconstructed: it is more fine grained, which will fix a number of the issues we’ve faced before: local distorsions, or images which are not properly aligned because the details at the surface at the sun have moved between the moment the first panel was captured and the second one did.

If we stopped there, we would see an image which looks like this:

mosaic reconstructed

We can see a clear horizontal line, which is due to the fact that we’re reconstructing using tiles, and that depending on the alignment of tiles with the "missing" areas, we can have strong or weak artifacts at the borders. Errors are even more visible in this image in calcium K line:

mosaic error calcium

This time it’s very problematic and we are facing several of the issues we attempted to avoid: details have significantly moved between the north and south panel were captured, which leads to "shadowing" artifacts, and there are also tiling artifacts visible.

However, the image we get is good enough to perform one last step: use it as a reference image in the stacking algorithm we described in the first section of this blog post. The reason stacking works well is because we know we have complete images that we can align. Here, we have roughly reconstructed an image that we can use as a "complete reference". The idea is therefore to take each tile of each panel and "blend" it using the reconstructed reference. Of course, there is one big difference between the stacking in the first section and the stacking we have to do now. We’re not really going to use the reconstructed image, except for aligning tiles together and computing a "weight" for each tile, which depends on the relative luminosity between the reference image tile we’re considering and the corresponding panel tile.

This gives us a pretty good result:

mosaic halpha final
mosaic calcium final

The images we got there are not perfect, which is why I’m not fully satisfied yet, but they are however already quite good, given that it’s all done in a few seconds, using the same software that you’d use to reconstruct Sol’Ex images! In other words, the goal of these features is not to get the same level of quality that you’d get by using your favorite post-processing or mosaic composition software, but good enough to get you a reasonable result in a reasonable amount of time.

For example, on my machine, it takes less than one minute to:

  • stack images of the north and south panels (~10 images to stack)

  • stitch them together in a mosaic

It would have taken several minutes, or even more, using external software, especially for the mosaic part.

Conclusion

In this blog post, I have described how I got to implement 2 algorithms, the stacking algorithm and the mosaic composition one, in JSol’Ex. None of the algorithms were based on any research paper: they were really designed in an "adhoc" way, as my intuition of how things could work. It proved to be quite difficult, and it is very likely that better algorithms are described in the wild: I will consider them for future versions.

Nevertheless, I’m quite happy with the outcome, since, remember, I have started this program as an experiment and for learning purposes only. Now I sincerely hope that it will help you get amazing solar images!

Introducing astro4j

22 April 2023

Tags: astronomy astro4j solex java graalvm

This blog introduces astro4j, my latest toy project, a open source collection of libraries and applications for astronomy, written in Java. In particular, I will discuss JSol’Ex, a program aimed at reconstructing solar disk images from video files captured using the amazing Sol’Ex instrument.

Why astro4j?

I’m a software developer, and if you are following me, you may also know that I’m an amateur astrophotographer. For a long time, I’ve been fascinated by the quality of software we have in astronomy, to process images. If you are french speaking, you can watch a presentation I gave about this topic. Naturally, I have been curious about how all these things work, but it’s actually extremely rare to find open source software, and when you do, it’s rarely written in Java. For example, both Firecapture (software to capture video streams) and Astro Pixel Processor are written in Java, but both of them are closed source, commercial software.

Last month, for my birthday, I got a Sol’Ex, an instrument which combines spectrography and software to realize amazing solar pictures in different spectral lines. To process those images, the easiest solution is to use the amazing INTI software, written in Python, but for which sources are not published, as far as I know, neither on GitHub or GitLab.

Note
After announcing this project, I have been notified that the sources of INTI are indeed available, as GPL. It’s a pity they are not linked on the webpage, this would have helped a lot.

To give you an example of what you can do, here’s the first photography I’ve done with Sol’Ex and processed with INTI (color was added in Gimp):

To get this result, one has to combine images which look like this:

spectrum

Interesting, no? At the same time, I was a bit frustrated by INTI. While it clearly does the job and is extremely easy to use, there are a few things which I didn’t like:

  • the first, which I mentioned, is that it’s using Python and that the sources are not published (as far as I understand, some algorithms are not published yet). I am not surprised that Python is used, because it’s a language which is extremely popular in academics, with lots of libraries for image processing, science oriented libs, etc. However, because it’s popular in academics also means that programs are often written by and for academics. When we’re talking about maths, it’s often short variable names, cryptic function names, etc…​

  • second, after processing, INTI pops up a lot of images as individual windows. If you want to process a new file, you have to close all of them. The problem is that I still haven’t figured out in which order you have to do this so that you can restart from the initial window which lets you select a video file! Apparently, depending on the order, it will, or will not, show the selector. And sometimes, it takes several seconds before it does so.

  • INTI seems to be regenerating a font cache every time I reboot. This operation takes several minutes. It’s probably an artifact of packaging the application for Windows, but still, not very user friendly.

  • INTI generates a number of images, but puts them alongside the videos. I like things organized (well, at least virtually, because if you looked at my desk right now, it is likely you’d feel faint), so I wish it was creating one directory per processed video.

inti popups

Confronting the old demons

When I started studying at University, back in 1998, I was planning to do astrophysics. However, I quickly forgot about this idea when I saw the amount of maths one has to master to do modern physics. Clearly, I was reaching my limits, and it was extremely complicated for me. Fortunately, I had been doing software development for years already, because I started very young, on my father’s computer. So I decided to switch to computer science, where I was reasonably successful.

However, not being able to do what I wanted to do has always been a frustration. It is still, today, to the point that a lot of what I’m reading is about this topic, but still, I lack the maths.

It was time for me to confront my old demons, and answer a few questions:

  • am I still capable of understanding maths, in order to implement algorithms which I use everyday when I do astronomy image processing with software written by others?

  • can I read academic papers, for example to implement a FFT (Fast Fourier Transform) algorithm, although I clearly remember that I failed to understand the principles when I was at school?

  • can I do this while writing something which could be useful to others, and publish it as open source software?

Astro4j is there to answer those questions. I don’t have the answers yet and time will tell if I’m successful.

Using modern Java

One question you may have is why Java? If you are not familiar with this language, you may have this old misconception that Java is slow. It’s not. Especially, if you compare to Python, it’s definitely not.

This project is also for me a way to prove that you can implement "serious science" in Java. You can already find some science libraries in Java, but they tend to me impractical to use, because not following the industry standards (e.g published on Maven Central) or platform-dependent.

I also wanted to leverage this to learn something new. So this project:

  • uses Java 17 (at least for libraries, so that they can be consumed by a larger number of developers, for applications I’m considering moving to Java 20)

  • uses JavaFX (OpenJFX) for the application UI

  • experiments with the Vector API for faster processing

As I said, my initial goal is to obtain a software which can basically do what INTI does. It is not a goal to make it faster, but if I can do it, I will.

Introducing JSol’Ex

After a few evenings (and a couple week-ends ;)), I already have something which performs basic processing, that is to say that it can process a SER video file and generate a reconstructed solar disk. It does not perform geometry correction, nor tilt correction, like INTI does. It doesn’t generate shifted images either (for example the doppler images), but it works.

Since the only source of information I had to do this was Christian Buil’s website and Valérie Desnoux INTI’s website, I basically had to implement my own algorithms from A to Z, and just "guess" how it works.

In order to do this, I had to:

  • implement a SER video file decoder. The library is ready and performs both decoding the SER file and performs demosaicing of images

  • on top of the decoder, I implemented a SER file player, which is still very basic at this stage, and uses JavaFX. This player can even be compiled to a native binary using GraalVM!

Here’s an example:

Then I could finally start working on the Sol’Ex video processor. As I said, I don’t know how INTI works, so this is all trial and error, in the end…​

In the beginning, as I said, you have a SER video file which contains a lot of frames (for example, in my case, it’s a file from 500MB to 1GB) that we have to process in order to generate a solar disk. Each frame consists of a view of the light spectrum, centered on a particular spectral line.

For example, in the following image, we have the H-alpha spectral line:

spectrum

Because of optics, you can see that the line is not horizontal: each frame is distorted. Therefore, in order to reconstruct an image, we have to deal with that distortion first. For this, we have to:

  • detect the spectral line in the frame, which I’m doing by implementing a simple contrast detection

  • perform a linear regression in order to compute a 2d order polynomial which models the distortion

Note that before doing this, I had no idea how to do a 2d order regression, but I searched and found that it was possible to do so using the least squares method, so I did so. The result is that we can identify precisely the line with this technique:

spectrum line

In the beginning, I tought I would have to perform distortion correction in order to reconstruct the image, because I was (wrongly) assuming that, because each frame represents one line in the reconstructed image, I had to compute the average of the colums of each frame to determine the color of a single pixel in the output. I was wrong (we’ll come to that later), but I did implement a distortion correction algorithm:

spectrum corrected

When I computed the average, the resulting image was far from the quality and constrast of what I got with INTI. What a failure! So I thought that maybe I had to compute the average of the spectral line itself. I tried this, and indeed, the resulting image was much better, but still not the quality of INTI. The last thing I did, therefore, was to pick the middle of the spectral line itself, and then, magically, I got the same level of quality as with INTI (for the raw images, as I said I didn’t implement any geometry or tilt correction yet).

The reason I was assuming that I had to compute an average, is that it wasn’t clear to me that the absorption ray would actually contain enough data to reconstruct an image. As it was an absorption ray, I assumed that the value would be 0, and therefore that nothing would come out of using the ray itself. In fact, my physics were wrong, and you must use that.

A direct consequence is that there is actually no need to perform a distortion correction. Instead, you can just use the 2d order polynomial that we’ve computed, and "follow the line", that’s it!

Now, we can generate an image, but it will be very dark. The reason is obvious: by taking the middle of the spectral line, we’re basically using dark pixels, so the dynamics of the image are extremely low. So, in order to have something which "looks nice", you actually have to perform brightness correction.

The first algorithm I have used is simply a linear correction: we’re computing the max and min value of the image, then rescaling that so that the max value is the maximum representable (255).

Here’s the result:

linear

However, I felt that this technique wouldn’t give the best results, in particular because linear images tend to give results which are not what the eye would see: our eye performs a bit like an "exponential" accumulator, the more photos you get, the "brighter" we’ll see it.

So I implemented another algorithm which I had seen in PixInsight, which is called inverse hyperbolic (Arcsinh) correction:

streched

Last, you can see that the image has lots of vertical line artifacts. This is due to the presence of dust either on the optics or the sensors. INTI performs correction of those lines, and I wanted to do something similar.

Again, I don’t know what INTI is doing, so I figured out my own technique, which is using "multipass" correction. In a nutshell, for each row, I am computing the average value of the row. Then, for a particular row, I compute the average of the averages of the surrounding lines (for example, 16 rows before and after). If the average of this line is below the average of the averages(!), then I’m considering that the line is darker than it should be, computing a correction factor and applying it.

The result is a corrected image:

banding

We’re still not a the level of quality that INTI produces, but getting close!

So what’s next? I already have added some issues for things I want to fix, and in particular, I’m looking at improving the banding reduction and performing geometry correction. For both, I think I will need to use fast fourier transforms, in order to identify the noise in one case (banding) and detect edges in the other (geometry correction).

Therefore, I started to implement FFT transforms, a domain I had absolutely no knowledge of. Luckily, I could ask ChatGPT to explain to me the concepts, which made it faster to implement! For now, I have only implemented the Cooley-Tukey algorithm. The issue is that this algorithm is quite slow, and requires that the input data has a length which is a power of 2. Given the size of the image we generate, it’s quite costly.

I took advantage of this to learn about the Vector API to leverage SIMD instructions of modern CPUs, and it indeed made things significantly faster (about twice as fast), but still not at the level of performance that I expect.

I am trying to understand the split radix but I’m clearly intimidated by the many equations here…​ In any case I printed some papers which I hope I’ll be able to understand.

Conclusion

In conclusion, in this article, I’ve introduced astro4j, an open source suite of libraries and applications written in Java for astronomy software. While the primary goal for me is to learn and improve my skills and knowledge of the maths behind astronomy software processing, it may be that it produces something useful. In any case, since it’s open source, if you want to contribute, feel free!

And you can do so in different domains, for example, I pretty much s* at UI, so if you are a JavaFX expert, I would appreciate your pull requests!

Finally, here is a video showing JSol’Ex in action:

How the Micronaut team leverages Gradle’s version catalogs for improved developer productivity

12 March 2023

Tags: micronaut gradle version catalogs graalvm maven

This blog post discusses how the Micronaut development team makes use of a feature of Gradle, version catalogs, to improve the team’s developer productivity, reduce the risks of publishing broken releases, coordinate the releases of a large number of modules and, last but not least, provide additional features to our Gradle users.

The backstory

The Micronaut Framework is a modern open-source framework for building JVM applications. It can be used to build all kinds of applications, from CLI applications to microservices or even good old monoliths. It supports deploying both to the JVM and native executables (using GraalVM), making it particularly suitable for all kind of environments. A key feature of the Micronaut framework is developer productivity: we do everything we can to make things faster for developers. In particular, Micronaut has a strong emphasis on easing how you test your applications, even in native mode. For this we have built a number of tools, including our Maven and Gradle plugins.

When I joined the Micronaut team almost a couple years back, I was given the responsibility of improving the team’s own developer productivity. It was an exciting assignment, not only because I knew the team’s love about Gradle, but because I also knew that there were many things we could do to reduce the feedback time, to provide more insights about failures, to detect flaky tests, etc. As part of this work we have put in place a partnership with Gradle Inc which kindly provides us with a Gradle Enterprise instance, but this is not what I want to talk about today.

Lately I was listening to an interview of Aurimas Liutikas of the AndroidX team, who was saying that he didn’t think that version catalogs were a good solution for library authors to share their recommendations of versions, and that BOMs are probably a better solution for this. I pinged him saying that I disagreed with this statement and offered to provide more details why, if he was interested. This is therefore a long answer, but one which will be easier to find than a thread on social media.

What are version catalogs?

Let’s start with the basics: a version catalog is, like the name implies, a catalog of versions to pick from, nothing more. That doesn’t sound too much exciting, and what versions are we talking about? That’s version of libraries or plugins that you use in your build.

As an illustration, here is a version catalog, defined as a TOML file:

[versions]
javapoet = "1.13.0"

[libraries]
javapoet = { module = "com.squareup:javapoet", version.ref = "javapoet" }

Then this library can be used in a dependencies declaration block in any of the project’s build script using a type-safe notation:

dependencies {
    implementation(libs.javapoet) {
        because("required for Java source code generation")
    }
}

which is strictly equivalent to writing:

dependencies {
    implementation("com.squareup:javapoet:1.13.0") {
        because("required for Java source code generation")
    }
}

There are many advantages of using version catalogs to declare your library versions, but most notably it provides a single, standard location where those versions are declared. It is important to understand that a catalog is simply a list of dependencies you can pick from, a bit like going to the supermarket and choosing whatever you need for your particular meal: it’s not because a catalog declares libraries that you have to use them. However, a catalog provides you with recommendations of libraries to pick from.

Version catalogs for Micronaut users

An interesting aspect of version catalogs is that they can be published, for others to consume: they are an artifact. Micronaut users can already make use of catalogs, as I have explained in a previous blog post. This makes it possible for a user who doesn’t know which version of Micronaut Data to use, to simply declare:

dependencies {
    implementation mn.micronaut.data
}

People familiar with Maven BOMs can easily think that it is the same feature, but there are key differences which are described in the Gradle docs.

In the rest of this post we will now focus on how we generate those catalogs, and how they effectively help us in improving our own developer productivity.

How the Micronaut team uses version catalogs

One catalog per module

As I said, the Micronaut framework consists of a large number of modules which live in their own Git repository. All the projects share the same layout, the same conventions in order to make things easier to maintain. For this purpose, we use our own collection of internal build plugins as well as a project template.

Those build plugins provide features like:

  • defining the default Java language level, setting up code conventions and code quality plugins

  • standardizing how documentation is built (using Asciidoctor)

  • setting up integration with Gradle Enterprise, to publish build scans, configure the build cache and predictive test selection

  • implementing binary compatibility checks between releases

  • configuring publication to Maven Central

  • providing a high-level model of what a Micronaut module is

The last item is particularly important: in every Micronaut project, we have different kind of modules: libraries (which are published to Maven Central for users to consume), internal support libraries (which are not intended for external consumption), or a BOM module (which also publishes a version catalog as we’re going to see).

Long story short: we heavily rely on conventions to reduce the maintenance costs, have consistent builds, with improved performance and higher quality standards. If you are interested in why we have such plugins, Sergio Delamo and I gave an interview about this a few months ago (alert: the thumbnail shows I have hair, this is fake news!).

Each of our projects declares a version catalog, for example:

Automatic version upgrades

One of the advantages of version catalogs is that it provides a centralized place for versions, which can be easily used by bots to provide pull requests for dependency upgrades. For this, we use Renovatebot which integrates particularly well with version catalogs (GitHub’s dependabot lacks behind in terms of support). This allows us to get pull requests like this one which are very easy to review.

BOM and version catalog generation

Each of the Micronaut projects is now required to provide a BOM (Bill of Materials) for users. Another term for a BOM that is used in the Gradle ecosystem is a platform: a platform has however slightly different semantics in Maven and Gradle. The main goal of a BOM is to provide a list of dependencies a project works with, and, in Maven, it can be used to override the dependency versions of transitive dependencies. While in Maven, a BOM will only influence the dependency resolution of the project which imports the BOM, in Gradle a platform fully participates in dependency resolution, including when a transitive dependency depends on a a BOM. To simplify, a user who imports a BOM may use dependencies declared in the BOM without specifying a version: the version will be fetched from the BOM. In that regards, it looks exactly the same as a version catalog, but there are subtle differences.

For example, if a user imports a BOM, any transitive dependency matching a dependency found in the BOM will be overridden (Maven) or participate in conflict resolution (Gradle). That is not the case for a catalog: it will not influence the dependency resolution unless you explicitly add a dependency which belongs to the catalog.

That’s why Micronaut publishes both a BOM and a catalog, because they address different use cases, and they work particularly well when combined together.

In Micronaut modules, you will systematically find a project with the -bom suffix. For example, Micronaut Security will have subprojects like micronaut-security-jwt, micronaut-security-oauth2 and micronaut-security-bom.

The BOM project will aggregate dependencies used by the different modules. In order to publish a BOM file, the only thing a project has to do is to apply our convention plugin:

plugins {
    id "io.micronaut.build.internal.bom"
}

Note how we don’t have to declare the coordinates of the BOM (group, artifact, version), nor that we have to declare how to publish to Maven Central, what dependencies should be included in the BOM, etc: everything is done by convention, that’s the magic of composition over inheritance.

Should we want to change how we generate the BOM, the only thing we would have to do is to update our internal convention plugin, then all projects would benefit from the change once they upgrade.

Convention over configuration

In order to determine which dependencies should be included in our BOM, we defined conventions that we use in our catalog files. In our internal terminology, when we want a dependency to be handled by the Micronaut framework, we call that a managed dependency: a dependency that is managed by Micronaut and that users shouldn’t care about in most cases: they don’t have to think about a version, we will provide one for them.

This directly translates to a convention in the version catalogs of the Micronaut projects: dependencies which are managed need to be declared with a managed- prefix in the catalog:

[versions]
...
managed-kafka = '3.4.0'
...
zipkin-brave-kafka-clients = '5.15.0'

[libraries]
...
managed-kafka-clients = { module = 'org.apache.kafka:kafka-clients', version.ref = 'managed-kafka' }
managed-kafka-streams = { module = 'org.apache.kafka:kafka-streams', version.ref = 'managed-kafka' }
...
zipkin-brave-kafka-clients = { module = 'io.zipkin.brave:brave-instrumentation-kafka-clients', version.ref = 'zipkin-brave-kafka-clients' }

Those dependencies will end up in the version catalog that we generate, but without the managed- prefix. This means that we would generate a BOM which contains the following:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <!-- This module was also published with a richer model, Gradle metadata,  -->
  <!-- which should be used instead. Do not delete the following line which  -->
  <!-- is to indicate to Gradle or any Gradle module metadata file consumer  -->
  <!-- that they should prefer consuming it instead. -->
  <!-- do_not_remove: published-with-gradle-metadata -->
  <modelVersion>4.0.0</modelVersion>
  <groupId>io.micronaut.kafka</groupId>
  <artifactId>micronaut-kafka-bom</artifactId>
  <version>5.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>Micronaut Kafka</name>
  <description>Integration between Micronaut and Kafka Messaging</description>
  <url>https://micronaut.io</url>
  <licenses>
    <license>
      <name>The Apache Software License, Version 2.0</name>
      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
  <scm>
    <url>scm:git@github.com:micronaut-projects/micronaut-kafka.git</url>
    <connection>scm:git@github.com:micronaut-projects/micronaut-kafka.git</connection>
    <developerConnection>scm:git@github.com:micronaut-projects/micronaut-kafka.git</developerConnection>
  </scm>
  <developers>
    <developer>
      <id>graemerocher</id>
      <name>Graeme Rocher</name>
    </developer>
  </developers>
  <properties>
    <micronaut.kafka.version>5.0.0-SNAPSHOT</micronaut.kafka.version>
    <kafka.version>3.4.0</kafka.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>${kafka.compat.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-streams</artifactId>
        <version>${kafka.version}</version>
      </dependency>
      <dependency>
        <groupId>io.micronaut.kafka</groupId>
        <artifactId>micronaut-kafka</artifactId>
        <version>${micronaut.kafka.version}</version>
      </dependency>
      <dependency>
        <groupId>io.micronaut.kafka</groupId>
        <artifactId>micronaut-kafka-streams</artifactId>
        <version>${micronaut.kafka.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Note how we automatically translated the managed-kafka property into a BOM property kafka.version, which is used in the <dependencyManagement> block. Dependencies which do not start with managed- are not included in our generated BOM.

Let’s now look at the version catalog that we generate:

#
# This file has been generated by Gradle and is intended to be consumed by Gradle
#
[metadata]
format.version = "1.1"

[versions]
kafka = "3.4.0"
kafka-compat = "3.4.0"
micronaut-kafka = "5.0.0-SNAPSHOT"

[libraries]
kafka = {group = "org.apache.kafka", name = "kafka-clients", version.ref = "kafka-compat" }
kafka-clients = {group = "org.apache.kafka", name = "kafka-clients", version.ref = "kafka" }
kafka-streams = {group = "org.apache.kafka", name = "kafka-streams", version.ref = "kafka" }
micronaut-kafka = {group = "io.micronaut.kafka", name = "micronaut-kafka", version.ref = "micronaut-kafka" }
micronaut-kafka-bom = {group = "io.micronaut.kafka", name = "micronaut-kafka-bom", version.ref = "micronaut-kafka" }
micronaut-kafka-streams = {group = "io.micronaut.kafka", name = "micronaut-kafka-streams", version.ref = "micronaut-kafka" }

Given a single input, the version catalog that we use to build our Micronaut module, our build conventions let us automatically declare which dependencies should land in the output BOM and version catalogs that we generate for that project! Unlike Maven BOMs which either have to be a parent POM or redeclare all dependencies in an independent module, in Gradle we can generate these automatically and completely decouple the output BOM from what is required to build our project.

In general, all api dependencies must be managed, so in the example above, the Micronaut Kafka build scripts would have an API dependency on kafka-clients, which we can find in the main project build script:

dependencies {
    api libs.managed.kafka.clients
    ...
}

The benefit of generating a version catalog for a user is that there is now a Micronaut Kafka version catalog published on Maven Central, alongside the BOM file.

This catalog can be imported by a user in their settings file:

settings.gradle
dependencyResolutionManagement {
    versionCatalogs {
         create("mnKafka") {
             from("io.micronaut.kafka:micronaut-kafka-bom:4.5.2")
         }
    }
}

Then the dependency on Micronaut Kafka and its managed dependencies can be used in a build script using the mnKafka prefix:

build.gradle
dependencies {
    implementation mnKafka.micronaut.kafka
    implementation mnKafka.kafka.clients
}

A user doesn’t have to know about the dependency coordinates of Kafka clients: the IDE (at least IntelliJ IDEA) would provide completion automatically!

BOM composition

In Micronaut 3.x, there is a problem that we intend to fix in Micronaut 4 regarding our "main" BOM: the Micronaut core BOM is considered as our "platform" BOM, in the sense that it aggregates BOMs of various Micronaut modules. This makes it hard to release newer versions of Micronaut which, for example, only upgrade particular modules of Micronaut.

Therefore in Micronaut 4, we are cleanly separating the "core" BOM, from the new platform BOM. It is interesting in this blog post because it offers us the opportunity to show how we are capable of generating aggregating BOMs and aggregated catalogs.

In the platform BOM module, you can find the "input" catalog that we use, and only consists of managed- dependencies. Most of those dependencies are simply dependencies on other Micronaut BOMs: this is an "aggregating" BOM, which imports other BOMs. This is, therefore, the only BOM that a user would effectively have to use when migrating to Micronaut 4: instead of importing all BOMs for the different Micronaut modules they use, they can simply import the Micronaut Platform BOM, which will then automatically include the BOMs of other modules which "work well together".

This allows us to decouple the releases of the framework from the releases of Micronaut core itself.

However, there is a subtlety about aggregating BOMs in Maven: they are not regular dependencies, but dependencies with the import scope. This means that we must make a difference between a "managed dependency" and an "imported BOM" in our input catalog.

To do this, we have another naming convention, which is to use the boms- prefix for imported BOMs:

[versions]
...
managed-micronaut-aws = "4.0.0-SNAPSHOT"
managed-micronaut-azure = "5.0.0-SNAPSHOT"
managed-micronaut-cache = "4.0.0-SNAPSHOT"
managed-micronaut-core = "4.0.0-SNAPSHOT"
...

[libraries]
...
boms-micronaut-aws = { module = "io.micronaut.aws:micronaut-aws-bom", version.ref = "managed-micronaut-aws" }
boms-micronaut-azure = { module = "io.micronaut.azure:micronaut-azure-bom", version.ref = "managed-micronaut-azure" }
boms-micronaut-cache = { module = "io.micronaut.cache:micronaut-cache-bom", version.ref = "managed-micronaut-cache" }
boms-micronaut-core = { module = "io.micronaut:micronaut-core-bom", version.ref = "managed-micronaut-core" }
...

This results in the following BOM file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>io.micronaut.platform</groupId>
  <artifactId>micronaut-platform</artifactId>
  <version>4.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>Micronaut Platform</name>
  <description>Bill-Of-Materials (BOM) and Gradle version catalogs for Micronaut</description>

  ...

  <properties>
    ...
    <micronaut.aws.version>4.0.0-SNAPSHOT</micronaut.aws.version>
    <micronaut.azure.version>5.0.0-SNAPSHOT</micronaut.azure.version>
    <micronaut.cache.version>4.0.0-SNAPSHOT</micronaut.cache.version>
    <micronaut.core.version>4.0.0-SNAPSHOT</micronaut.core.version>
    ...
  </properties>
  <dependencyManagement>
    <dependencies>
      ...
      <dependency>
        <groupId>io.micronaut.aws</groupId>
        <artifactId>micronaut-aws-bom</artifactId>
        <version>${micronaut.aws.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>io.micronaut.azure</groupId>
        <artifactId>micronaut-azure-bom</artifactId>
        <version>${micronaut.azure.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>io.micronaut.cache</groupId>
        <artifactId>micronaut-cache-bom</artifactId>
        <version>${micronaut.cache.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>io.micronaut</groupId>
        <artifactId>micronaut-core-bom</artifactId>
        <version>${micronaut.core.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      ...
    </dependencies>
  </dependencyManagement>
</project>

A more interesting topic to discuss is what we can do with version catalogs that we publish for users: we can inline dependency aliases from each of the imported catalogs into the platform catalog. All dependencies in the catalog files of each modules are directly available in the platform catalog:

[versions]
dekorate = "1.0.3"
elasticsearch = "7.17.8"
...
micronaut-aws = "4.0.0-SNAPSHOT"
micronaut-azure = "5.0.0-SNAPSHOT"
micronaut-cache = "4.0.0-SNAPSHOT"
micronaut-core = "4.0.0-SNAPSHOT"
...

[libraries]
alexa-ask-sdk = {group = "com.amazon.alexa", name = "ask-sdk", version = "" }
alexa-ask-sdk-core = {group = "com.amazon.alexa", name = "ask-sdk-core", version = "" }
alexa-ask-sdk-lambda = {group = "com.amazon.alexa", name = "ask-sdk-lambda-support", version = "" }
aws-java-sdk-core = {group = "com.amazonaws", name = "aws-java-sdk-core", version = "" }
aws-lambda-core = {group = "com.amazonaws", name = "aws-lambda-java-core", version = "" }
aws-lambda-events = {group = "com.amazonaws", name = "aws-lambda-java-events", version = "" }
aws-serverless-core = {group = "com.amazonaws.serverless", name = "aws-serverless-java-container-core", version = "" }
awssdk-secretsmanager = {group = "software.amazon.awssdk", name = "secretsmanager", version = "" }
azure-cosmos = {group = "com.azure", name = "azure-cosmos", version = "" }
azure-functions-java-library = {group = "com.microsoft.azure.functions", name = "azure-functions-java-library", version = "" }
...

The alexa-ask-sdk is for example an alias which was originally declared in the micronaut-aws module. Because we aggregate all catalogs, we can inline those aliases and make them directly available in user build scripts:

settings.gradle
dependencyResolutionManagement {
    versionCatalogs {
         create("mnKafka") {
             from("io.micronaut.platform:micronaut-platform:4.0.0-SNAPSHOT")
         }
    }
}
build.gradle
dependencies {
...
    implementation(mn.micronaut.aws.alexa)
    implementation(mn.alexa.sdk)
}

Generating a version catalog offers us a very pragmatic way to define all dependencies that users can use in their build scripts with guarantees that they work well together.

Technical details

If you survived reading up to this point, you may be interested in learning how, technically, we implemented this. You can take a look at our internal build plugins, but more specifically at the BOM plugin.

In order to generate our BOM and version catalogs, we have mainly 2 inputs:

  1. the list of subprojects which need to participate in the BOM: in a Micronaut modules, we explained that we have several kinds of projects: libraries which are published, test suites, etc. Only a subset of these need to belong to the BOM, and we can determine that list automatically because each project applies a convention plugin which determines its kind. Only projects of a particular kind are included. Should exceptions be required, we have a MicronautBomExtension which allows us to configure more precisely what to include or not, via a nice DSL.

  2. the list of dependencies, which is determined from the project’s version catalog

One issue is that while Gradle provides automatically the generated, type-safe accessors for version catalogs, there is actually no built-in model that you can access to represent the catalog model itself (what is an alias, references to versions, etc): the type-safe API represents a "realized" catalog, but not a low-level model that we can easily manipulate. This means that we had to implement our own model for this.

We have also seen that we can generate a single platform, aggregating all Micronaut modules for a release, that the users can import into their build scripts. Unfortunately it is not the case for the Micronaut modules themselves: for example, Micronaut Core must not depend on other Micronaut modules, but, for example, Micronaut Data can depend on Micronaut SQL and use dependencies from the Micronaut SQL catalog. Those modules cannot depend on the platform BOM, because this is the aggregating BOM, so we would create a cyclic dependency and wouldn’t be able to release any module.

To mitigate this problem, our internal build plugins expose a DSL which allows each projects to declare which other modules they use:

settings.gradle
micronautBuild {
    importMicronautCatalog() // exposes a `mn` catalog
    importMicronautCatalog("micronaut-reactor") // exposes a `mnReactor` catalog
    importMicronautCatalog("micronaut-rxjava2") // exposes a `mnRxjava2` catalog
    ...
}

While this is simple from the declaration site point of view, it is less practical from a consuming point of view, since it forces us to use different namespaces for each imported catalog:

dependencies {
    ...
    testImplementation mn.micronaut.inject.groovy
    testImplementation mnRxjava2.micronaut.rxjava2
    ...
}

It would have been better if we could actually merge several catalogs into a single one, but unfortunately that feature has been removed from Gradle. I still have hope that this will eventually be implemented, because not having this creates unnecessary boilerplate in build scripts and redundancy in names (e.g implementation mnValidation.micronaut.validation).

Additional benefits and conclusion

All that I described in this article aren’t the only benefits that we have on standardizing on version catalogs. For example, we have tasks which allow us to check that our generated BOM files only reference dependencies which are actually published on Maven Central, or that there are no SNAPSHOT dependencies when we perform a release. In the end, while most of the Micronaut developers had no idea what a version catalog was when I joined the team, all of them pro-actively migrated projects to use them because, I think, they immediately saw the benefits and value. It also streamlined the dependency upgrade process which was still a bit cumbersome before, despite using dependabot.

We now have a very pragmatic way to both use catalogs for building our own projects, and generating BOMs and version catalogs which can be used by both our Maven and Gradle users. Of course, only the Gradle users will benefit from the version catalogs, but we did that in a way which doesn’t affect our Maven users (and if you use Maven, I strongly encourage you to evaluate building Micronaut projects with Gradle instead, since the UX is much better).

I cannot end this blog post without mentioning a "problem" that we have today, which is that if you use Micronaut Launch to generate a Micronaut project, then it will not use version catalogs. We have an issue for this and pull requests are very welcome!


Older posts are available in the archive.