Contributing¶
Setting Up the Development Environment¶
Clone the repository and install in editable mode with all dependencies:
Running Tests¶
Agribound uses pytest. Tests are located in the tests/ directory.
# Run all tests
pytest
# Run tests excluding GPU and GEE requirements
pytest -m "not gpu and not gee"
# Run with coverage
pytest --cov=agribound --cov-report=html
# Run only fast tests
pytest -m "not slow"
Test markers:
| Marker | Description |
|---|---|
gpu |
Requires a CUDA-capable GPU |
gee |
Requires GEE authentication |
slow |
Long-running tests |
Code Style¶
Agribound uses Ruff for linting and formatting. Configuration is in pyproject.toml:
- Target: Python 3.10
- Line length: 100
- Selected rules: E, F, W, I, N, UP, B, SIM
Run the linter:
Adding a New Engine¶
To add a new delineation engine:
-
Create the engine module at
agribound/engines/my_engine.py:from agribound.engines.base import DelineationEngine from agribound.config import AgriboundConfig import geopandas as gpd class MyEngine(DelineationEngine): name = "my-engine" supported_sources = ["sentinel2", "local"] requires_bands = ["R", "G", "B"] def delineate( self, raster_path: str, config: AgriboundConfig ) -> gpd.GeoDataFrame: # Implement delineation logic here. # Must return a GeoDataFrame with at least a geometry column. ... -
Register the engine in
agribound/engines/base.pyby adding an entry toENGINE_REGISTRY:ENGINE_REGISTRY["my-engine"] = { "name": "My Engine", "approach": "Description of the approach", "strengths": "When this engine excels", "gpu_required": True, "requires_bands": ["R", "G", "B"], "supported_sources": ["sentinel2", "local"], "reference": "Paper or project reference", "install_extra": "my-engine", } -
Add the factory branch in
get_engine()inagribound/engines/base.py: -
Add the engine name to
VALID_ENGINESinagribound/config.py. -
Add optional dependencies (if any) as a new extra in
pyproject.toml: -
Write tests in
tests/test_my_engine.py.
Adding a New Satellite Source¶
To add a new satellite source:
-
Register the source in
agribound/composites/base.pyby adding toSOURCE_REGISTRY:SOURCE_REGISTRY["my-source"] = { "name": "My Source", "collection": "GEE/COLLECTION/ID", "resolution_m": 10, "all_bands": ["B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"], "canonical_bands": {"R": "B4", "G": "B3", "B": "B2", "NIR": "B8"}, "coverage": "Description of spatial and temporal coverage", "requires_gee": True, } -
Add the source name to
VALID_SOURCESinagribound/config.py. -
Implement a cloud masking function (if GEE-based) in
agribound/composites/gee.py. -
Add a factory branch in
get_composite_builder()inagribound/composites/base.pyif the source requires a custom builder. Otherwise, the existingGEECompositeBuilderhandles it automatically for GEE sources. -
Write tests covering composite generation for the new source.
Building the Documentation¶
pip install agribound[docs]
mkdocs serve # local preview at http://127.0.0.1:8000
mkdocs build # build static site to site/
Project Structure¶
agribound/
__init__.py # Public API exports
_version.py # Version string
auth.py # GEE authentication
cli.py # Click CLI
config.py # AgriboundConfig dataclass
pipeline.py # Main pipeline orchestrator
evaluate.py # Accuracy metrics
visualize.py # Interactive map visualization (folium/leafmap)
composites/
__init__.py
base.py # CompositeBuilder ABC + source registry
gee.py # GEE composite builder
local.py # Local/embedding composite builders
dynamic_world.py # Dynamic World crop probability utilities
engines/
__init__.py
base.py # DelineationEngine ABC + engine registry
delineate_anything.py
ftw.py
geoai_field.py
dinov3.py # DINOv3 ViT + DPT segmentation
prithvi.py
embedding.py
ensemble.py
samgeo_engine.py # SAM2 boundary refinement
finetune.py # Fine-tuning orchestrator
io/
__init__.py
raster.py # GeoTIFF read/write
vector.py # Vector read/write
crs.py # CRS utilities
postprocess/
__init__.py
polygonize.py # Mask to polygon conversion
simplify.py # Douglas-Peucker simplification
regularize.py # Polygon regularization
filter.py # Area and hole filtering
lulc_filter.py # LULC crop filtering (NLCD / Dynamic World / C3S)
merge.py # Cross-tile polygon merging