Changelog
Source:NEWS.md
opencanopy 1.0.3
Version de tests : le cache de modèle d’inférence, introduit en 1.0.1, est désormais couvert.
Tests
-
Le cache de modèle d’inférence est testé, sans Python ni checkpoint — le travail de performance de la 1.0.1 reposait sur une non-régression manuelle et jetable, qui exigeait un checkpoint de 520 Mo et des ortho locales. Or c’est la clé du cache qui décide si le checkpoint est relu, et elle se teste sans rien de tout cela : sa construction sort dans
.cle_modele_inference(), fonction pure, etreticulateest mocké dans les tests — aucun interpréteur n’est démarré. Onze tests couvrent l’arrondi d’img_sizeau multiple de 32 supérieur (deux tuiles de 200 et 224 px partagent le même modèle plutôt que d’en faire reconstruire un à chaque fois), le fait que chaque composante de l’identité du modèle — checkpoint, architecture, canaux,img_size— change bien la clé, le non-rechargement au second appel identique, le rechargement forcé lorsque l’interpréteur Python a été relancé alors que le cache R croit encore détenir le modèle, et qu’un chargement en échec remet la clé àNULL: sans quoi l’appel suivant sauterait le chargement etpredict_tile()partirait sur un_OC_MODELinexistant.
La suite passe de 57 à 72 tests, la couverture de 7 % à 9 %.
opencanopy 1.0.2
Version d’hygiène : R CMD check passe de six avertissements et trois NOTE à Status: OK.
Bug Fixes
-
Installer le paquet créait des répertoires dans le répertoire courant — trois
dir_create()au niveau supérieur deR/s’exécutaient au chargement du package, donc à l’installation, et créaientdata/,data/ign,data/open_canopyetoutputs/là où se trouvait le processus. D’où les deux avertissementsSubdirectory 'data' contains no data setsetFiles not of a type allowed in a 'data' directory. Les répertoires sont désormais créés par les fonctions qui écrivent dedans —export_raster(),export_stats(),export_to_gpkg(),run_inference_python(). -
compute_savi()documentait un NDRE inexistant — le bloc roxygen au-dessus de la fonction décrivait un « Normalized Difference Red Edge » approximé, avec sa propre formule et son@return: rien à voir avec le SAVI que la fonction calcule. Titre, description et paramètres réécrits,Ldocumenté.
Qualité du code
-
Caractères non-ASCII — les 185 chaînes littérales accentuées passent en séquences
\uXXXX. La sortie reste identique, en français ; commentaires et blocs roxygen gardent leurs accents,R CMD checkne vérifiant que le code analysé. -
library()dans le code du paquet —library(reticulate)disparaît au profit dereticulate::sur les 18 appels concernés, et le bloc de visualisation qualifie ses 43 appels enggplot2::,tidyterra::etpatchwork::. Les opérateurs|et/de patchwork continuent de fonctionner :requireNamespace()charge le namespace, ce qui suffit à enregistrer les méthodes S3. -
Dépendances non déclarées —
rprojrootrejoint lesSuggestset son unique appel est gardé parrequireNamespace(); les fonctions de base utilisées sans déclaration (colorRampPalette,pdf,par,dev.off,cor,write.csv,download.file…) sont importées explicitement dansNAMESPACE. -
Documentation — seize fonctions avaient un bloc roxygen réduit à son titre : leurs arguments sont documentés. Les
[0, 1]et[0, 255]du roxygen, pris pour des liens Rd manquants, passent en code inline.
Intégration continue
- Les actions GitHub passent aux versions ciblant Node 24 (
actions/checkout@v5,codecov/codecov-action@v5) : GitHub les y forçait déjà en émettant un avis de dépréciation à chaque exécution. L’avis restant provient d’une dépendance interne decodecov-action. -
.clauderejoint.Rbuildignore.
opencanopy 1.0.1
Version de performance et de remise en état de la chaîne d’intégration. Elle solde le brief du 2026-09-14 (coût de l’inférence, OOM au calcul des indices) et publie les correctifs de tuilage restés sans version depuis la 1.0.0.
Performance
-
Le modèle était reconstruit à chaque tuile —
predict_tile()exécutait un script Python complet qui relisait le checkpoint (~520 Mo), reconstruisait PVTv2-b3 et rejouait le remapping des 568 clés, pour chacune des tuiles. Sur un AOI de 15 tuiles : 7,8 Go de checkpoint relus et 24,3 Go d’E/S mesurés sur le processus enfant. Le script est coupé en deux :init_inference_model()construit le modèle une fois et le laisse dans l’environnementreticulatepersistant,predict_tile()ne fait plus que lire le raster, la passe avant et l’écriture. Un cache (checkpoint + architecture + canaux +img_size) évite toute reconstruction ;run_inference()fixeimg_sizesur la plus grande tuile pour que la clé reste stable sur toute la série. -
Le backbone tournait deux fois par tuile — un bloc de debug appelait
model.model.forward_features()uniquement pour imprimer les statistiques d’activation, puismodel()refaisait le même calcul ; le résultat du premier appel était jeté. Le backbone (attention spatiale du PVTv2) est la partie chère. Le diagnostic est conservé à l’identique mais calculé pendant l’unique passe avant. Mesuré sur une tuile 320x320 à 1,5 m, trois passages consécutifs : 67,1 / 65,0 / 60,9 s avant, 61,4 (chargement unique inclus) / 23,3 / 17,8 s après, soit environ 3,1x par tuile en régime établi. Non-régression vérifiée octet pour octet sur deux tuiles de tailles différentes.
Bug Fixes
-
terraaveugle au plafond du cgroup — SIGKILL au calcul des indices — soussystemd-run --property=MemoryMax=12G,terradimensionne ses décisions « en mémoire vs sur disque » à partir de/proc/meminfo, qui n’est pas cgroup-aware : il voyait ~23 Go libres et se faisait tuer à 12 Go, après une heure, sur une ortho IRC de 233 millions de cellules — les deux CHM étaient déjà écrits, mais le run perdait une heure à chaque fois..limite_memoire_cgroup()lit le plafond réel (cgroups v2 via/proc/self/cgroup, v1 en repli) en remontant toute la hiérarchie et en retenant le plus petit plafond ;configurer_memoire_terra()en donne la moitié àterraOptions(memmax). Les quatre indices passent parlapp(filename=), et les deuxresample()du masque végétation écrivent également en fichier : le pic mémoire ne dépend plus de l’heuristique. -
Le bloc de debug cassait le chemin UNet —
smp.Unetn’a pas d’attribut.model, doncmodel.model.forward_features()levaitAttributeErroret faisait échouer toutes les tuiles en architecture UNet. Le diagnostic est désormais conditionné à la présence deforward_features. -
pkgs_optionalnon déclaré danstest_pipeline_aoi.R— la déclaration était commentée alors que la boucle de vérification juste en dessous l’utilise : le script s’arrêtait surobject 'pkgs_optional' not founddès l’étape 1.
Intégration continue et publication
-
Les badges pointaient vers un dépôt inexistant — le dépôt a été renommé en
pobsteta/opencanopysans que rien ne suive : les quatre badges du README visaientopencanopynemeton,_pkgdown.ymlpubliait verspobsteta.github.io/opencanopynemeton/, etDESCRIPTIONportait une troisième orthographe,open_canopy_nemeton. Tout est aligné. -
Le site pkgdown n’a jamais été publié —
build_site_github_pages()abandonnait sur la vérification d’URL (URLdeDESCRIPTIONne contenait pas l’urlde_pkgdown.yml), donc l’étape de déploiement n’était jamais atteinte et la branchegh-pagesn’existait pas. L’index de référence échouait ensuite faute de fichiersman/, désormais générés. -
Le job
coverageéchouait sur une dépendance manquante —covr::to_cobertura()requiertxml2, un Suggests de covr que niextra-packagesnineeds: coveragene tiraient. Le workflow R-CMD-check était rouge alors que le check et les tests passaient, et aucun rapport n’a jamais atteint Codecov. -
Aucune Release n’était créée — les tags
v0.1.2etv1.0.0étaient poussés sans objet Release, la page du dépôt affichait donc encorev0.1.1. Un workflowrelease.ymlpublie désormais la Release au push d’un tagv*, avec la sectionNEWS.mdcorrespondante et un garde-fou sur la cohérence entre le tag etVersion:deDESCRIPTION. -
export_to_gpkg()n’était pas documenté et les fonctions internes du pipeline n’étaient pas marquées@keywords internal, ce qui bloquait l’index de référence pkgdown.
opencanopy 1.0.0
Première version majeure. Elle solde le balayage values() ouvert par la 0.1.3 et rend rattachable le CRS de tous les rasters produits par le pipeline.
Bug Fixes
-
Balayage
values()— les huit sites restants — la 0.1.3 corrigeaitpct_vegmais laissait en suspens legrep -n "values(" R/qu’elle réclamait elle-même. Deux sites portaient la même classe de risque :compute_irc_stats(), appelé sur une ortho IRC entière (boucle sur les dalles), etevaluate_predictions(), qui tenait trois couches pleine résolution en mémoire simultanément —prediction,referenceet leur différence — pour quatre scalaires. Les six autres :normalize_for_model()(le plus systématique du paquet : chaque tuile de chaque prédiction, alors que seul le maximum est utilisé),compute_chm_stats(),plot_chm_histogram(),plot_canopy_change()etplot_prediction_comparison()(deux occurrences). Mesuré àmemfrac = 0.3:compute_chm_stats()sur 1,44e8 cellules passe de 4,7 Go à 1,16 Go (85 s → 17 s),evaluate_predictions()sur 3,6e7 cellules de 2,18 Go à 1,20 Go. -
evaluate_predictions()—cor()sur des vecteurs de longueurs différentes — les valeurs depredictionet dereferenceétaient filtrées séparément parvalues(x, na.rm = TRUE)avant d’être passées àcor(). Dès que les deux masques deNAdifféraient — un CHM de référence troué, une prédiction rognée — les deux vecteurs n’avaient pas la même longueur et la fonction s’arrêtait sur une erreur. L’échantillonnage par paires écarte les cellulesNAde part ou d’autre, donc le cas fonctionne. -
setup_conda_env()ne vérifiait qu’un quart du stack Python — le contrôle portait surtorch, numpy, rasterio, huggingface_hub, alors quepipeline_aoi_to_chm()charge aussigeopandas, shapely, pyproj, rioxarray, xarray— plustorchvision, timm, segmentation_models_pytorchpour l’inférence. Un environnement créé à la main avec seulement torch/rasterio passait le diagnostic, puis échouait surModuleNotFoundError: geopandasà l’étape AOI/CHM, sans produire dechm_1_5m.tif. La liste complète est désormais centralisée dansOPEN_CANOPY_PY_MODULES(nom d’import → nom pip) et vérifiée en entier. -
CRS non rattachable sur tous les rasters produits — les GeoTIFF renvoyés par le WMS IGN portent un WKT dont le nom est
"EPSG:2154"mais qui n’a pas de bloc d’autoritéID["EPSG",2154]: le datum y est"unnamed"et l’ellipsoïde"unretrievable - using WGS84". Le gardeis.na(crs(r)) || crs(r) == ""ne voyait pas passer ce cas, et le WKT dégénéré se propageait jusqu’àchm_predicted_0_2m.tif, oùsf::st_crs(x)$epsglitNA. Tout aval qui écrit un GeoPackage depuis ces rasters — segmentation de houppiers, exports vectoriels — embarquait un CRS non rattachable.ancrer_crs_l93()re-tamponne àEPSG:2154les seuls rasters dépourvus de code d’autorité (un raster correctement identifié dans un autre CRS est laissé tel quel) et est appliqué en quatre points : la tuile WMS — la racine du problème —, la branche cache des mosaïques (ce qui rattrape lesortho_*.tifdéjà sur disque), leur écriture, et le CHM au retour de l’inférence Python, dont l’aller-retour par rasterio recopie le CRS d’entrée.
Changements de comportement
-
Statistiques descriptives : exactes ou estimées, selon ce que
global()sait faire. Min, max, moyenne, écart-type, comptages, biais, RMSE et MAE restent exacts. En revanche les quantiles (médiane, Q25, Q75), les parts de surface (pct_forest,pct_tall_trees,pct_vegetation,pct_dense_veg,pct_bare_soil) et le R² sont désormais estimés sur un échantillon régulier borné àmax_cellscellules — et redeviennent exacts dès que le raster tient dans cette limite. Écart mesuré sur les sorties IGN réelles à 0,20 m : 0,008 point de pourcentage sur les parts, moins d’un millimètre sur les quantiles de hauteur, 1e-4 sur le R². La voie « exacte » évidente pour les parts,global(chm >= 2, "mean"), a été mesurée puis écartée : elle matérialise une couche de plus et coûte 6 Go, soit plus quevalues()— c’est écrit dans la documentation des fonctions pour que le piège ne soit pas retenté. -
Attention à
global(x, "rms"): terra y divise par n−1, pas par n. Le prendre pour la RMSE introduit un biais silencieux ; elle est reconstruite depuis les moments,sqrt(sd_pop² + moyenne²).
New Features
-
setup_conda_env(install_missing = TRUE)installe via pip les modules Python manquants dans l’environnement. Par défaut (FALSE), la fonction se contente de les signaler avec la commande exacte à lancer, et retourne invisiblement le vecteur des modules absents. -
ancrer_crs_l93()est exportée : elle permet de réparer à la lecture les rasters produits par une version antérieure, sans relancer le pipeline. -
compute_chm_stats(),compute_irc_stats()etevaluate_predictions()acceptentmax_cells(défaut1e6), etplot_chm_histogram()/plot_prediction_comparison()acceptent respectivementmax_cellsetn_points, pour régler le compromis précision/mémoire.
opencanopy 0.1.3
Patch release ciblée sur l’empreinte mémoire des statistiques rasters.
Bug Fixes
-
pipeline_aoi_to_chm()— OOM à la dernière étape sur les grandes AOI — le pourcentage de végétation était calculé parsum(values(clean_mask, na.rm = TRUE)) / sum(!is.na(values(clean_mask))), qui rapatriait le masque entier en mémoire, deux fois, plus la copie temporaire du!is.na(). Sur Couchey (535,6 ha, 28 481 × 14 695 = 418 528 295 cellules à 0,20 m), cela représentait plusieurs Go de vecteurs R au pic de mémoire du pipeline — juste après unmask()qui, lui, streamait correctement vers le disque. Le processus était tué par l’OOM killer après 3 h 20 de CPU, tous les livrables écrits mais aucun indicateur calculé. Le calcul passe désormais parglobal(clean_mask, "mean", na.rm = TRUE), qui streame par blocs : sur un masque logique, la moyenne en ignorant lesNAest la proportion cherchée, au bit près. -
Même motif ailleurs —
values()sur une couche pleine résolution pour un simplemin/max/mean/suma été remplacé parglobal()danspipeline_aoi_to_chm()(statistiques du CHM, calculées une seule fois au lieu de quatre lectures complètes),load_chm(),compute_ndvi(),compute_ndwi(),mask_vegetation(),compute_irc_stats()(moyennes des trois bandes de l’ortho IRC),detect_canopy_loss()etexport_to_gpkg()(test binaire).compute_chm_stats()déduitn_nadencell()au lieu d’une seconde lecture complète. Résultats inchangés dans tous les cas, y compris sur les couches entièrementNA.
opencanopy 0.1.2
Patch release ciblée sur la robustesse des téléchargements WMS IGN.
Bug Fixes
-
WMS IGN — tuiles NULL après échec HTTP 502 — quand une tuile échouait (HTTP 5xx, timeout),
tile_rasters[[idx]] <- rétait sauté maisidxétait quand même incrémenté, laissant des trousNULLdans la liste.terra::mergeplantait alors avec[sprc] list element is a: NULL.download_ign_tiled()filtre désormais lesNULLavant le mosaïquage et émet un avertissement clair indiquant combien de tuiles ont échoué. -
WMS IGN — retry sur erreurs serveur transitoires —
download_wms_tile()retente jusqu’à 4 fois avec backoff exponentiel (1s, 2s, 4s, 8s) sur les erreurs HTTP 502/503/504, timeouts et resets. Élimine en pratique la plupart des échecs liés à la charge de la Géoplateforme IGN.
opencanopy 0.1.1
Maintenance release consolidating a session of downstream-integration fixes discovered while wiring the pipeline into the nemetonshiny Shiny app.
New Features
-
Phase + tile progress callback —
pipeline_aoi_to_chm(),download_ortho_for_aoi(),download_ign_tiled()andrun_inference()now accept an optionalprogress_callbackthat fires with a structured list at each pipeline boundary:list(type = "phase", step = <load_aoi | download_ortho | setup_python | download_model | inference | export>, total = 5L, model = <opt>)-
list(type = "tile_phase_start", prefix, n_tiles)+list(type = "tile", prefix, idx, n_tiles, bbox)per WMS tile -
list(type = "inference_phase_start", n_tiles, model)+list(type = "inference_tile", idx, n_tiles, model, tile_name)Used by nemetonshiny to paint a live status line instead of an 8-minute silent wait. Backward compatible:NULLcallback is a no-op.
Bug Fixes
-
Robust Python discovery —
setup_python()no longer trusts the firstpy_config()$pythonit finds. It now walks a prioritised list (RETICULATE_PYTHON→ currently-bound Python if it matches → canonical miniforge3 / miniconda3 / anaconda3 / mambaforge install paths →CONDA_PREFIX→conda_list()as last resort) and aborts with an explicit error when reticulate is already bound to a Python that is not theopen_canopyenv, telling the user exactly whichSys.setenv(RETICULATE_PYTHON = …)to run. -
do.call(merge, predictions)— now qualified asdo.call(terra::merge, unname(predictions))inrun_inference()anddownload_ign_tiled(). On AOIs large enough to need > 1 inference tile, the named list fed to the baremerge()used to dispatch tobase::merge.default()which errored with “l’argument x est manquant, avec aucune valeur par défaut” after 5+ minutes of successful inference. -
fs::file_delete()qualified everywhere — the unqualifiedfile_delete()call in the cleanup path ofdownload_ign_tiled()hitNAMESPACEgaps (onlyfs::dir_ls/dir_createwere imported) and failed with “impossible de trouver la fonction file_delete”, leaving 56 orphan tile rasters behind. -
Tile-sidecar cleanup — the post-mosaic cleanup now also removes the
*.tif.aux.xmland*.tif.ovrGDAL sidecars written by terra alongside each tile, so project caches no longer drift towards 100+ orphan.xmlfiles.
Documentation
-
setup_python()docstring and error messages clarified: the Rhfhubpackage replaces only the Pythonhuggingface_hubsub-module for the checkpoint download step. Python (torch + rasterio + segmentation-models-pytorch + timm) is always required for inference. The previous wording hinted thatinstall.packages ('hfhub')could bypass Python entirely, which was misleading.
opencanopy 0.1.0
Premier release du package opencanopy.
Point d’entree principal
-
pipeline_aoi_to_chm(aoi_path): pipeline complet AOI (GeoPackage) -> ortho IGN -> CHM predit + indices spectraux + PDF recapitulatif.
Fonctionnalites
- Telechargement automatique des orthophotos IGN (BD ORTHO RVB + IRC, 0.20 m) via le WMS Geoplateforme, avec decoupage par tuiles pour les AOI > 4096 px.
- Telechargement des checkpoints Open-Canopy (
unet,pvtv2) depuis Hugging Face viahfhub(R natif) ou Pythonhuggingface_huben fallback. - Inference PyTorch via
reticulatesur un environnement condaopen_canopy(UNet/SMP ou PVTv2/timm reconstruit via module Python embarqueinst/python/timmnet_standalone.py). - Calcul de 4 indices spectraux depuis l’IRC : NDVI, GNDVI, SAVI, NDWI.
- CHM masque vegetation (
chm_vegetation_0_2m.tif) via seuils NDVI/NDWI configurables. - Reechantillonnage automatique 0.20 m -> 1.5 m pour l’inference, puis disaggregation bilineaire 1.5 m -> 0.20 m sur la sortie.
- Visualisation recapitulative (
resultats_aoi.pdf) en grille 3x3 : orthos, indices, masque, CHM brut et masque. - Support des millesimes IGN (
millesime_ortho,millesime_irc).