Tirtha builds a graph from satellite imagery, joins it with the OpenStreetMap road network, and computes how far every populated pixel is from the nearest essential service (clinic, school, water point, shelter). The output is an accessibility map plus a unified pixel-and-road graph artifact you can run any graph algorithm on.
Concretely:
- Pull Sentinel-1 (radar), Sentinel-2 (optical), and NASADEM (elevation) tiles for the region of interest, plus the OSM road network and the OSM-tagged destination facilities.
- Feed the satellite imagery to IBM/ESA's TerraMind foundation model to estimate per-pixel walkability (the "friction surface").
- Treat the friction raster as a graph: each pixel is a node, adjacent pixels are connected, edge weights are walking time. Stitch the OSM road graph onto the same node set.
- Run shortest-path (Dijkstra) from every destination facility outward. The resulting raster has a walking-time number per pixel.
- Weight by population (WorldPop) and report standard accessibility metrics (percent of population within 30, 60, 120 minutes), plus calibrated per-pixel uncertainty bounds.
Built for humanitarian accessibility work. Ships with a healthcare preset because that is where the standard benchmark (Weiss et al. 2020 MAP) and the standard supervision signal (DHS HEALTHFACTIM) exist. The pipeline itself does not know about healthcare; the destination set is a configuration choice.
git clone https://github.com/msradam/tirtha.git
cd tirtha
uv sync # core
uv sync --extra ml # add torch + terratorch for `--friction fm` (TerraMind)
uv sync --extra dev # add pytest + ruffuv run tirtha accessibility run --region "Sierra Leone" --resolution 100 --out ./slWrites to ./sl/:
travel_time.tif: walking-time raster in minutesfriction.tif: input friction surface in min/mfacilities.geojson: destinations actually usedmetrics.json: population-weighted accessibility numberssummary.txt: human-readable summaryfigures/headline.png: four-panel summary figure
About 4 minutes wall clock for a country at 100m, 30 seconds for a 2 km chip at 10m, on a MacBook.
--preset schools|water|shelter swaps the destination set. --bbox W,S,E,N overrides geocoding for places Nominatim does not have as a polygon.
tirtha
├── version
├── cache-info
├── accessibility
│ └── run walking-time raster + metrics + figure
└── graph
├── build unified pixel + road graph (.npz)
└── inspect one-line graph summary
tirtha graph build produces a scipy.sparse.csr_matrix of the unified pixel-plus-OSM-road graph plus per-node attributes (coordinates, type, friction). Load and run any algorithm:
from tirtha.graph import load_graph
import scipy.sparse.csgraph as csg
g = load_graph("sl.graph.npz")
dist = csg.dijkstra(g.adj, indices=g.facility_node_ids)Per-pixel Spearman of 0.674 against the Weiss et al. 2020 MAP raster on the Blantyre chip, MAE 2.89 minutes. After split-conformal calibration the 95% CI on travel time has empirical coverage 94.9% (ECE 0.003). At country scale, Tirtha and MAP disagree by 1.71 million people on "more than 3 hours walking from healthcare in Sierra Leone." Full numbers, figures, and methodology in docs/methodology.md.
uv run pytest # 29 tests, under 2 seconds, no network or GPUnotebooks/02_bench_vs_map.py takes any tirtha accessibility run output and produces a head-to-head against the published MAP 2020 raster (Spearman, MAE, Weiss-bin accessibility, three-panel figure).
Pre-computed results for Sierra Leone (national), Blantyre, Cox's Bazar, and Brownsville live under docs/cases/. The notebooks/03_showcase.py renders them as a static site:
uv run marimo export html-wasm notebooks/03_showcase.py -o siteCI deploys this to GitHub Pages on every push to master.
docs/methodology.md: design choices, the on/off-road fusion, calibration, validation numbersdocs/plain_english.md: the project explained without jargonCHANGELOG.md: development arcdemo/: vhs terminal recording, reproducible withbrew install vhs && vhs demo/tirtha.tapearchive/django-api: the 2019 UNICEF Magicbox intern code this transforms
Apache 2.0.