Contributing¶
Contributions are welcome: bug fixes, engines, sources, documentation and examples. The repository's CONTRIBUTING.md has the short version of this page.
Development environment¶
git clone https://github.com/montimaj/agribound.git
cd agribound
conda env create -f environment.yml # core: pip install -e .[all,dev]
conda activate agribound
pip install -e ".[docs]" # documentation toolchain
Prithvi (terratorch) needs the separate environment-gfm.yml environment,
because terratorch and ftw-tools 2.x require incompatible lightning
versions.
Tests¶
# offline suite (what CI runs)
python -m pytest -m "not gpu and not gee and not slow and not network"
# everything, including Earth Engine and network tests
python -m pytest
# coverage
python -m pytest --cov=agribound --cov-report=html
| Marker | Meaning |
|---|---|
gpu |
needs a GPU |
gee |
needs Earth Engine authentication |
network |
needs network access |
slow |
long-running |
Every behaviour change needs a unit test that runs offline (no network, GPU or Earth Engine); stub the remote service or the model. CI runs Ruff and a check that the example notebooks match their scripts, the offline suite on Python 3.12 and 3.13 on Linux, macOS and Windows (core install), an engine job on Linux, and a strict documentation build with a link check.
Style¶
Ruff is configured in pyproject.toml
(line length 100, target Python 3.12, rules E, F, W, I, N, UP, B, SIM). CI
checks the whole repository:
Shell scripts (*.sh, *.sbatch) and the HPC profiles
(examples/hpc/profiles/*.env, which bash sources) keep LF line endings in
every checkout through .gitattributes, also on Windows with
core.autocrlf=true: bash cannot run a script with CRLF endings.
tests/unit/test_repo_files.py checks the endings and the attributes.
Code conventions: numpy-style docstrings; from __future__ import annotations;
heavy or optional dependencies imported inside functions;
logger = logging.getLogger(__name__). Docstrings and documentation must be
true of the code. No silent fallbacks that change the method (a different
model, band set or engine): raise an actionable error or require an explicit
opt-in recorded in engine_meta, and log at WARNING when anything degrades.
Adding an engine¶
-
Write the engine class, for example
agribound/engines/my_engine.py:from __future__ import annotations import geopandas as gpd from agribound.config import AgriboundConfig from agribound.engines.base import DelineationEngine from agribound.registry import ENGINE_REGISTRY class MyEngine(DelineationEngine): name = "my-engine" supported_sources = list(ENGINE_REGISTRY["my-engine"]["supported_sources"]) requires_bands = list(ENGINE_REGISTRY["my-engine"]["requires_bands"]) def delineate(self, raster_path: str, config: AgriboundConfig) -> gpd.GeoDataFrame: self.validate_input(raster_path, config) ... # return polygons with a CRS gdf.attrs["engine_meta"] = {"model": "...", "weights_sha256": "..."} return gdf @classmethod def prefetch(cls, config: AgriboundConfig) -> list[str]: """Download the weights for offline nodes; return their local paths.""" -
Register it in
agribound/registry.py: anENGINE_REGISTRYentry with all keys (name,approach,strengths,gpu_recommended,requires_bands,supported_sources,label_free,fine_tunable,reference,install_extra,notes, optionallysource_notes) and anENGINE_CLASSESentry"my-engine": "agribound.engines.my_engine:MyEngine". - Read bands with
get_canonical_band_indices(config.source, names, bands=config.bands)and convert values withagribound.io.raster(to_unit_reflectance,to_s2_dn,percentile_stretch_uint8) according toregistry.source_value_scale(config.source); never assume Sentinel-2 units. - Name every intermediate with
agribound._cache.cache_pathand seed random choices withagribound._repro.get_rng. - If it is fine-tunable, add a trainer module to
agribound/engines/finetune/and an entry to its dispatcher (_TRAINERSin its__init__.py), and give the engine a chip format (CHIP_FORMATS) and a default chip size inagribound/engines/finetune/_data.py. A default that is derived rather than fixed (as for Delineate-Anything and GeoAI) also needs its own branch inchip_size_rule(): the rule's identifier is part of the fine-tuning cache key, so a changed rule does not reuse old checkpoints. - Add an optional-dependency extra in
pyproject.toml, tests, and documentation (engines page, README table).
Adding a source¶
- Add a
SOURCE_REGISTRYentry inagribound/registry.pywith every key (name,collection,resolution_m,native_resolution_m,all_bands,canonical_bands,value_scale,year_range,coverage,requires_gee,restricted); facts must be checked against the provider's catalogue. - For an Earth Engine collection, add a collection builder to
_COLLECTION_BUILDERSinagribound/composites/gee.pyand the source to_OPTICAL_GEE_SOURCESinagribound/registry.py(which definesGEE_IMAGERY_SOURCES); otherwise write aCompositeBuilderand register it in_BUILDERSinagribound/composites/base.py. - The builder must write a GeoTIFF on the grid of
agribound.composites.gee.compute_export_grid(the study-area bounding box inexport_crs), cache it withcache_path, and raiseagribound.composites.NoDataErrorwhen the source has no data for the study area. - Add the source to the
supported_sourcesof the engines that can use it.
Documentation¶
The API reference is generated from the docstrings with mkdocstrings.
Examples, notebooks and figures¶
The scripts examples/NN_*.py are the source of the examples. The notebooks
in examples/notebooks/ hold the same code and are generated from them, so
edit the script and regenerate (the tool needs nbformat):
python tools/sync_notebooks.py # rewrite out-of-date notebooks
python tools/sync_notebooks.py --check # what CI runs; exit 1 if a notebook differs
The gallery images in assets/gallery_1.0/ (and the facts their captions
quote, gallery_stats.json) are rendered from the example outputs by
tools/make_gallery.py, except the comparison with the 0.1.x Pampas image
(Pampas_0.1x_comparison_example.png, by tools/make_gallery_pampas_0.1x.py,
with no gallery_stats.json entry). The workflow diagram
assets/agribound_workflow_1.0.{png,svg} is rendered by
tools/make_workflow_diagram.py; assets/README.md lists the files.
Packaging¶
The licence metadata follows PEP 639 (license = "Apache-2.0" and
license-files = ["LICENSE"] in pyproject.toml, no licence classifier), so
the build needs setuptools>=77; python -m build installs it in its
isolated build environment. The version comes from agribound/_version.py.
setuptools-scm puts the files tracked by git into the sdist, and
MANIFEST.in adds or removes others: it adds every agribound/*.py and
prunes assets/, paper/, outputs/, examples/notebooks/ and .github/.
Other new files (tests, documentation) reach the sdist only once they are
committed.
Pull requests¶
- Branch from
main. - Add tests and documentation for the change.
- Run the offline test suite and
ruff check. - Describe what changed and why.