HPC Tiling¶
agribound.hpc backs the agribound tiles commands. See
HPC and large areas.
hpc ¶
Batch execution of large study areas (HPC job arrays).
- :mod:
agribound.hpc.tiles: cut a study area into tiles with halos, write a manifest with one configuration per tile, run tiles idempotently, report their status and merge the results. - :mod:
agribound.hpc.regions: read the region definitions inexamples/regions/*.yaml. - :mod:
agribound.hpc.cli: theagribound tilescommand group.
Submodules are imported on first attribute access, so importing this package
(which agribound does to register the CLI group) stays cheap.
Tiles¶
tiles ¶
Tiling of large study areas for batch (HPC) runs.
A region that is too large for one composite download or one GPU job is cut into square tiles. Each tile is an ordinary Agribound run with its own YAML configuration, output file and provenance record, so tiles can be executed as independent Slurm array tasks and restarted at any time.
Workflow
- :func:
make_tilescuts the study area into a grid of core cells and adds a halo around each core. - :func:
write_tile_manifestwritestiles.gpkg,tiles.txt, one configuration per tile (tiles/<tile_id>/config.yaml) andmanifest.json. - :func:
run_tileruns one tile:stage="composite"(downloads only: composite or embeddings, the LULC raster forlulc_mode="raster"and the FTW window composites of two-window FTW models, also as ensemble members),stage="delineate"(delineation of an already staged tile) orstage="all"(the composite stage, then the delineation), using :func:agribound.pipeline.build_compositeand :func:agribound.pipeline.delineate. - :func:
merge_tilescombines the tile outputs into one file and writes a merged provenance summary; :func:tile_statusreports progress.
No-data tiles
A rectangular grid over a real region contains tiles without input data:
open water, or areas outside a source's coverage (NAIP outside the US,
missing TESSERA tiles). The composite builders raise
:class:agribound.composites.base.NoDataError (a :class:ValueError) for
them: no image intersects the study-area extent, no valid pixel inside the
study area, no TESSERA / Google embedding data, no USGS NAIP Plus imagery,
a local raster that does not overlap. :func:run_tile recognises these
errors by their type (:func:no_data_reason; a :class:ValueError whose
message matches :data:NO_DATA_PATTERNS is accepted as a fallback) and
records the tile as "no-data" instead of failing: it writes a content-addressed
nodata_<key>.json marker in the tile's cache directory (plus
no_data.json in the tile directory) with the error, and returns
status="no-data". The marker is keyed like the stage markers, so it is
reused by later runs with the same inputs and ignored after a configuration
change; overwrite=True (--overwrite) retries the download. Any other
error still fails the tile. :func:merge_tiles merges the other tiles and
lists the no-data tiles, their reasons and their core area in the summary; it
raises if no tile produced output, which usually means that the source has
no data for the year.
Grids
crs="utm" (default)
The study area is split at UTM zone boundaries (6 degree longitude bands,
zone = floor((lon + 180) / 6) + 1) and at the equator. Each part is
tiled on its own WGS 84 / UTM grid (EPSG:326xx north, 327xx south), whose
origin is the lower-left corner of that part rounded down to whole metres.
Tile IDs are "<zone><N|S>_<col>_<row>", e.g. "55S_003_012".
crs="equal-area"
One Lambert azimuthal equal-area grid centred on the study-area centroid
(+proj=laea, WGS 84). Tile IDs are "ea_<col>_<row>". Distances
and tile sizes are close to true within about 1,000 km of the centre.
In both cases every tile's composite is exported in the UTM zone of the tile
(export_crs="EPSG:<utm_epsg>") unless the base configuration sets an
explicit EPSG: code.
Cores and halos
The core of a tile is its grid cell; with clip=True (default) it is the
cell intersected with the study area, otherwise the full cell (always cut at
UTM zone and equator boundaries for crs="utm"). The halo is the core's
bounding box in the grid CRS expanded by halo_m on every side. The tile's
study_area is the EPSG:4326 bounding box of the halo (a "bbox:..."
string), so the downloaded composite covers at least the halo.
Halo rule
A field is delineated whole only if it lies completely inside the halo of the
tile that owns it. Every field that crosses a core boundary extends at most
its own size past that boundary, so the halo must exceed the largest expected
field dimension: fields larger than the halo that cross a core boundary can be
truncated. Centre pivots are about 800 m across, so the default
halo_m=1000 is a minimum; use more for regions with larger fields.
:func:merge_tiles counts kept polygons that reach the edge of their halo
(n_reaching_halo_edge), which flags possible truncation.
Merge rule
A polygon from tile T is kept only if T owns its representative point
(:func:shapely.point_on_surface, computed in EPSG:4326). Ownership is
decided arithmetically from the grid definition (zone/hemisphere of the point,
then floor of its grid coordinates), which assigns every point to exactly
one grid cell; with clip=True the point must also lie in the study area.
Identical polygons delineated by two neighbouring tiles are therefore kept
exactly once. Two different detections of the same field (one per tile)
have different representative points and can, rarely, both be kept or both be
dropped; :func:merge_tiles reports overlapping polygons from different tiles
(n_cross_tile_overlap_pairs) so this can be checked. Study areas that
cross the antimeridian are not supported.
With clip=True this representative-point test is the only selection at
the region's study-area outline; with clip=False there is none. The base
configuration's aoi_selection is applied in each tile run to the tile's
study area, which is the tile's halo box, not to the region's outline.
Reference polygons given to :func:merge_tiles are
selected with the matching rule (representative point with clip=True,
else intersecting the study area).
make_tiles ¶
make_tiles(study_area: Any, tile_size_m: float = 20000.0, halo_m: float = 1000.0, crs: str = 'utm', clip: bool = True, config: Any = None) -> gpd.GeoDataFrame
Cut a study area into square tiles with halos.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
study_area
|
str, GeoDataFrame, GeoSeries or shapely geometry
|
Anything :func: |
required |
tile_size_m
|
float
|
Edge length of the core cells in metres of the grid CRS (default 20,000). |
20000.0
|
halo_m
|
float
|
Margin added around each core's bounding box (default 1,000 m). See the halo rule in the module docstring. |
1000.0
|
crs
|
str
|
|
'utm'
|
clip
|
bool
|
Intersect the cores with the study area (default True). With
False the cores are whole grid cells (still cut at UTM zone and
equator boundaries for |
True
|
config
|
AgriboundConfig or None
|
Base configuration; used only to read a GEE-asset study area with the
configured Earth Engine project and credentials
(:func: |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
One row per tile in EPSG:4326, ordered by grid system, row and column,
with columns |
Raises:
| Type | Description |
|---|---|
ValueError
|
For invalid sizes, an unknown crs, or an empty study area. |
Source code in agribound/hpc/tiles.py
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 | |
write_tile_manifest ¶
write_tile_manifest(tiles: GeoDataFrame, base_config: Any, out_dir: str | Path, *, cache_root: str | Path | None = None, overwrite: bool = False, keep_reference: bool = False, allow_fine_tune_per_tile: bool = False) -> Path
Write the tile manifest, per-tile configurations and tiles.gpkg.
Files written under out_dir:
tiles.gpkgwith layerstiles(cores),halosandstudy_area(EPSG:4326);tiles.txt: one tile ID per line, in index order (linei + 1is tilei, i.e. Slurm array indexi);tiles/<tile_id>/config.yaml: the base configuration withstudy_areaset to the tile's halo bbox,output_pathset totiles/<tile_id>/fields.<ext>,cache_dirset to<cache_root>/<tile_id>,export_crsset to the tile's UTM EPSG code when the base uses"utm",overwrite=Falseandprovenance=True(both needed for idempotent restarts; a base value that differs is replaced with a WARNING), and relative paths of the base configuration made absolute against the current directory, so tile jobs can start in any directory:local_tif_path,gee_service_account_key,reference_boundaries,sam_modeland the engine parameterscheckpoint_path,weights_path,model_pathwhen they name an existing file or directory (a relativelocal_tif_path, key or reference that does not exist is kept, with a WARNING), andembedding_cache_diralways;manifest.json(paths relative to out_dir).
Writing is idempotent: an existing manifest with the same signature (output directory, grid, tile entries, base configuration and cache root) is kept, and a different one is refused unless overwrite.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tiles
|
GeoDataFrame
|
Output of :func: |
required |
base_config
|
(AgriboundConfig, dict or path)
|
Configuration shared by all tiles (a YAML path is loaded with
:meth: |
required |
out_dir
|
str or Path
|
Directory for the manifest and all tile outputs. |
required |
cache_root
|
(str, Path or None)
|
Parent of the per-tile cache directories. Default: the base
configuration's |
None
|
overwrite
|
bool
|
Replace an existing manifest that differs from the new one. Tile outputs are never deleted; outputs whose configuration changed are protected by their provenance hash. |
False
|
keep_reference
|
bool
|
Keep |
False
|
allow_fine_tune_per_tile
|
bool
|
Allow |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
Path of |
Raises:
| Type | Description |
|---|---|
FileExistsError
|
If a different manifest exists in out_dir and overwrite is False. |
ValueError
|
If tiles did not come from :func: |
Source code in agribound/hpc/tiles.py
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 | |
load_manifest ¶
Load manifest.json (or a directory containing it).
Returns:
| Type | Description |
|---|---|
dict
|
The manifest with an extra key |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the manifest does not exist. |
ValueError
|
If the file is not a manifest of a supported schema version. |
Source code in agribound/hpc/tiles.py
load_tile_config ¶
Load the :class:~agribound.config.AgriboundConfig of one tile.
Source code in agribound/hpc/tiles.py
run_tile ¶
run_tile(manifest: str | Path | dict, index: int | str, stage: str = 'all', *, overwrite: bool = False) -> dict[str, Any]
Run one tile of a manifest (idempotent).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
(str, Path or dict)
|
|
required |
index
|
int or str
|
Tile index (Slurm array index, 0-based) or tile ID. |
required |
stage
|
str
|
|
'all'
|
overwrite
|
bool
|
Re-run even if the stage is already done, and retry tiles recorded as
no-data (the delineation output is replaced; builders still reuse
cached rasters -- delete the tile's cache directory to force new
downloads). A re-run delineation gets a new run ID, so a merged
output made before it no longer matches the tile outputs:
:func: |
False
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
For |
Exception
|
Any other pipeline error is re-raised after |
Notes
Completion is decided from content, not from markers alone: a
delineation is done when the tile output exists and its provenance
record reports success with the current configuration hash and results
versions (the pipeline's reuse test,
:func:agribound.provenance.reuse_mismatch); a composite
stage is done when the content-addressed stage markers in the tile's
cache directory point to existing rasters (composite; LULC raster when
needed; FTW window rasters when needed).
stage="delineate" guarantees only that these inputs are cached.
Other network access during delineation is not prevented: model weights
(agribound tiles prefetch), and Earth Engine for
lulc_mode="server". Ensemble tiles stage the window composites of
their two-window FTW members; an ensemble member whose staging failed
with on_member_error="skip" builds its inputs again (online) during
delineation or is skipped.
Source code in agribound/hpc/tiles.py
1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 | |
tile_status ¶
Report the state of every tile.
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per tile with columns |
Source code in agribound/hpc/tiles.py
1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 | |
merge_tiles ¶
merge_tiles(manifest: str | Path | dict, output: str | Path | None = None, *, crs: str = 'EPSG:4326', allow_missing: bool = False, overwrite: bool = False, reference: str | Path | None = None, check_overlaps: bool = True) -> gpd.GeoDataFrame
Merge finished tile outputs into one vector file.
Each tile keeps only the polygons whose representative point it owns
(see the merge rule in the module docstring); the kept polygons get an
agribound:tile_id column, are reprojected to crs and concatenated.
A summary is written to <output>.provenance.json.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
(str, Path or dict)
|
Tile manifest. |
required |
output
|
(str, Path or None)
|
Output file (format from the extension). Default
:func: |
None
|
crs
|
str
|
CRS of the merged output (default EPSG:4326, valid for any region). |
'EPSG:4326'
|
allow_missing
|
bool
|
Merge even if some tiles are not done (they are listed in the summary
and a WARNING is logged). Default: raise. Tiles recorded as
|
False
|
overwrite
|
bool
|
Recompute and replace an existing merged output. Without it, an
output made from exactly the same tile runs is returned without
recomputation, and one made from other tile runs raises
:class: |
False
|
reference
|
(str, Path or None)
|
Reference boundaries; when given, they are restricted to the study
area (the |
None
|
check_overlaps
|
bool
|
Count pairs of kept polygons from different tiles whose overlap exceeds half of the smaller polygon's area (default True). |
True
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
The merged polygons. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If tiles are missing and allow_missing is False, or if no tile is done and some are no-data (nothing to merge; usually the source has no data for the year). |
FileExistsError
|
If output exists, was made from different tile runs, and overwrite is False. |
Source code in agribound/hpc/tiles.py
1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 | |
representative_points ¶
Return :func:shapely.point_on_surface of each geometry, computed in EPSG:4326.
Source code in agribound/hpc/tiles.py
assign_tile_ids ¶
Return the ID of the grid cell that owns each EPSG:4326 point.
The cell is found arithmetically: for UTM grids the point's zone
(floor((lon + 180) / 6) + 1) and hemisphere (lat < 0 is south)
select the grid system, then col = floor((x - x0) / size) and
row = floor((y - y0) / size) in that system's CRS. Every point is
assigned to exactly one cell; cells on a shared edge belong to the cell
on their east/north side.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points_4326
|
array-like of shapely Points
|
Points in EPSG:4326 (e.g. from :func: |
required |
grid
|
dict
|
Grid definition ( |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Object array of tile IDs; None for empty points or points outside every grid system. An ID is returned whether or not a tile with that ID exists; callers compare it with their tile's ID. |
Source code in agribound/hpc/tiles.py
no_data_reason ¶
Return the no-data message in exc's chain, or None.
Walks exc, its __cause__ and __context__ (e.g. the FTW
engine's :class:RuntimeError wrapping a window composite's
:class:~agribound.composites.base.NoDataError) and returns
"<ExceptionType>: <message>" of the first
:class:~agribound.composites.base.NoDataError in the chain; if there is
none, of the first :class:ValueError whose message matches
:data:NO_DATA_PATTERNS (fallback).
Source code in agribound/hpc/tiles.py
format_index_ranges ¶
Compress integers into a Slurm --array style list, e.g. "0-3,7,9-10".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
indices
|
iterable of int
|
Indices (duplicates and order are ignored). |
required |
Returns:
| Type | Description |
|---|---|
str
|
Comma-separated ranges; empty string for no indices. |
Source code in agribound/hpc/tiles.py
Regions¶
regions ¶
Region definitions for large-area runs (examples/regions/*.yaml).
A region file describes one agricultural region: a verified EPSG:4326
bounding box, a small test box, recommended years, sources and engines,
data availability notes and tiling parameters. The run block holds the
machine-read defaults used by examples/run_region_delineation.sh::
name: iowa_corn_belt_us
title: ...
bbox: [minx, miny, maxx, maxy] # EPSG:4326
test_bbox: [minx, miny, maxx, maxy] # optional small box for quick checks
study_area: null # optional vector file (relative to this file)
run:
years: [2023, 2024]
sources: [sentinel2, landsat]
engines: [delineate-anything, ftw]
tile_size_km: 20
halo_m: 1500
reference: null # optional, relative to this file
tessera_version: v1 # optional
lulc_dataset: auto # optional (--lulc-dataset)
All other keys are free-form documentation (verification results, source
availability, reference data, FTW notes). :func:plan_runs expands the
years x sources x engines matrix with the registry rules.
load_region ¶
Load and validate a region file.
Returns:
| Type | Description |
|---|---|
dict
|
The YAML content plus |
Raises:
| Type | Description |
|---|---|
ValueError
|
If required keys are missing or the boxes are invalid. |
Source code in agribound/hpc/regions.py
find_region_file ¶
Resolve a region name (e.g. "punjab_in") or a path to a YAML file.
Names are looked up as <name>.yaml in $AGRIBOUND_REGIONS_DIR
(os.pathsep-separated), ./examples/regions and the
examples/regions directory of the agribound source checkout.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If no region file is found (the message lists the searched places). |
Source code in agribound/hpc/regions.py
plan_runs ¶
plan_runs(years: list[int], sources: list[str], engines: list[str], *, tessera_version: str | None = None, fine_tune: bool = False, has_checkpoint: bool = False, include_restricted: bool = False) -> list[dict[str, Any]]
Expand years x sources x engines into runs and skipped combinations.
A combination is skipped (with a reason) when the engine does not support
the source (:func:agribound.registry.engine_supports_source), the year
is outside the source's range
(:func:agribound.registry.source_year_range, TESSERA by version), the
source is restricted (SPOT) and include_restricted is False, or the
engine is not label-free and neither fine_tune (the engine must be
fine-tunable) nor has_checkpoint is set.
Returns:
| Type | Description |
|---|---|
list of dict
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For unknown sources or engines. |
Source code in agribound/hpc/regions.py
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |