Skip to content

Troi & Config

The Troi (Time and Region Of Interest) is the immutable, content-addressed object that flows through every stage of the pipeline. A Troi describes a region (bounding box, EPSG:4326) and a time window; every stage derives its input and output paths from it.

The Config controls global behavior — where outputs go, where caches live, and credentials for the SILO and SLGA stages. A Config is attached to every Troi (defaulting to the one loaded from ~/.config/Troi.json, or built-in defaults if that file is absent).


Key ideas

  • Content addressing. If you don't pass a stub, it's computed as sha256(bbox + start + end) so two queries with identical inputs share outputs on disk.
  • Region identity. Bounding boxes are snapped to ~100 m precision (3 decimal places) before hashing into bbox_hash, so near-identical bboxes share one region identity. bbox_hash keys the registry and the PaddockTS region × time cache; the data stores dedup at their own finer granularity (grid chunks / points) regardless.
  • Registry. Every constructed Troi is recorded in {config.out_dir}/queries.json under its bbox_hash. Reusing a stub for a different (bbox, start, end) raises ValueError.
  • Derived paths live elsewhere. Troi carries no storage layout: input data (Sentinel-2, climate, terrain, soils) is cached by the machine-wide stores, and PaddockTS's own artifacts (fractional cover, SAM paddocks, …) live on PaddockTS.paths.Paths, keyed by the Troi's identity hashes.

The diagram below shows how the four inputs fan out. Input data never lives per-troi: the machine-wide stores (pysentinel2, pysilo, pyozwald, pycopdem, pyslga) cache it once per machine at chunk/point granularity. bbox_hash × time_hash keys only PaddockTS's own derived artifacts, and the human-readable stub names the per-troi scratch and final-output directories.

flowchart TD
  Q["<b>Troi</b><br/>bbox · start · end · stub"]

  Q -->|"snap to ~100 m,<br/>then sha256"| BH["<b>bbox_hash</b>"]
  Q -->|"sha256(start+end)"| TH["<b>time_hash</b>"]
  Q -->|"verbatim"| ST["<b>stub</b>"]

  Q -.->|"windows served on demand,<br/>cached machine-wide"| STORES["pysentinel2 · pysilo · pyozwald ·<br/>pycopdem · pyslga<br/><i>shared data stores</i>"]

  BH --> QD["{tmp_dir}/paddockts/{bbox_hash}/{time_hash}/<br/><i>PaddockTS region × time cache</i>"]
  TH --> QD
  QD --> ART["fractional_cover.zarr · preseg.tif<br/>sam_mask.tif · sam_raw.gpkg · sam_paddocks.gpkg"]

  ST --> TMP["{tmp_dir}/{stub}/<br/><i>per-troi intermediates (time series)</i>"]
  ST --> OUT["{out_dir}/{stub}/<br/><i>final outputs + queries.json</i>"]

  classDef default color:#000;

Construct a Troi

From a bounding box

from datetime import date
from troi.troi import Troi

q = Troi(
    bbox=[148.36265, -33.52606, 148.38265, -33.50606],  # [W, S, E, N]
    start=date(2020, 1, 1),
    end=date(2021, 12, 31),
    stub="my_first_run",
)

print(q.out_dir)
# ~/Documents/Troi-Outputs/my_first_run

From a centre point + buffer in km

q = Troi.from_lat_lon(
    lat=-35.098087,
    lon=148.929983,
    buffer_km=2.0,       # ~ 4 km × 4 km AOI
    start=date(2025, 1, 1),
    end=date(2025, 6, 30),
    stub="point_buffered",
)

From an existing paddocks file

q = Troi.build_from_paddocks(
    paddocks_filepath="/path/to/paddocks.gpkg",
    start=date(2024, 1, 1),
    end=date(2024, 12, 31),
    stub="my_farm",
    label_col="paddock_name",
)

Reads the file (.gpkg, .shp, .geojson, or .json), reprojects to EPSG:4326 if needed, takes the envelope of all features as the bbox, and optionally renames label_col"paddock" for downstream compatibility.


Custom config

The default Config reads from ~/.config/Troi.json if present, otherwise uses ~/Documents/Troi-Outputs and ~/Downloads/Troi-Tmp. Override per-Troi by passing a Config explicitly:

from troi.config import Config
from troi.troi import Troi

cfg = Config(
    out_dir="/data/paddockts/outputs",
    tmp_dir="/data/paddockts/tmp",
    email="you@example.org",          # required for SILO
    tern_api_key="<your-tern-key>",   # required for SLGA
)

q = Troi(
    bbox=[148.36265, -33.52606, 148.38265, -33.50606],
    start=date(2020, 1, 1),
    end=date(2021, 12, 31),
    stub="my_run",
    config=cfg,
)

Troi reference

The generic core (bbox, dates, stub, cache directories, registry, alternate constructors) lives in the shared troi package; troi.troi.Troi subclasses it to add the Sentinel-2 / SAM output paths.

troi.troi

Troi

A request to run a pipeline over a region and time range.

The identity layer of the Borevitz Lab ecosystem: this region, these dates, this name. Immutable and hashable — two queries with the same inputs are the same troi and share every cached artefact downstream. Storage layout is deliberately NOT this class's concern: packages compose with a Troi (no inheritance) and derive their own cache locations in a per-package Paths class, typically from bbox_hash / time_hash.

Attributes:

Name Type Description
bbox list[float]

Bounding box [west, south, east, north] in EPSG:4326 (decimal degrees).

start date

Inclusive start date.

end date

Inclusive end date.

stub str

Short identifier used in every output filename. Defaults to a SHA-256 hash of (bbox, start, end) so repeat runs collide with their cached outputs. Pass an explicit string for human-readable filenames.

tmp_dir str

Per-troi intermediates directory ({config.tmp_dir}/{stub}). Created on init.

out_dir str

Per-troi final-outputs directory ({config.out_dir}/{stub}). Created on init.

bbox_hash str

Region identity — bbox snapped to ~100 m, then SHA-256. Keys the persistent registry.

time_hash str

Date-range identity (SHA-256 of start + end).

centre_lon float

Centre longitude of bbox (derived).

centre_lat float

Centre latitude of bbox (derived).

Example
from datetime import date
from troi.troi import Troi

q = Troi(
    bbox=[148.46, -34.39, 148.50, -34.36],
    start=date(2023, 1, 1),
    end=date(2023, 12, 31),
    stub='milgadara',
)
q.out_dir  # '.../Troi-Outputs/milgadara'

register

register(s: Self) -> None

Insert this troi into the persistent registry indexed by bbox_hash.

Idempotent on exact match — re-registering an identical troi updates its last_run_at instead of appending a duplicate.

Raises:

Type Description
ValueError

If stub is already registered with any different attribute (bbox, start, or end). Stubs must uniquely identify a troi.

from_lat_lon classmethod

from_lat_lon(lat: float, lon: float, buffer_km: float, start: date, end: date, stub: str = None, config: Config = default_config)

Build a Troi from a centre point and a square buffer in kilometres.

Convenience constructor for users who think in "X km around a point" rather than bounding-box corners. The km-to-degrees conversion is approximate (treats the Earth locally as a sphere) which is fine for the buffer sizes typical of paddock-scale work (≲50 km).

Parameters:

Name Type Description Default
lat float

Centre latitude in decimal degrees (EPSG:4326).

required
lon float

Centre longitude in decimal degrees (EPSG:4326).

required
buffer_km float

Half-side of the square buffer, in kilometres. The resulting bbox spans 2 * buffer_km on each side.

required
start date

Inclusive start date.

required
end date

Inclusive end date.

required
stub str

Optional human-readable stub identifier. If omitted, a SHA-256 hash of the inputs is used.

None

Returns:

Name Type Description
Troi

Instance with bbox = [west, south, east, north].

Example
from datetime import date
from troi.troi import Troi

q = Troi.from_lat_lon(
    lat=-34.38,
    lon=148.48,
    buffer_km=2.0,
    start=date(2023, 1, 1),
    end=date(2023, 12, 31),
    stub='milgadara',
)

build_from_paddocks classmethod

build_from_paddocks(paddocks_filepath: str, start: date, end: date, stub: str = None, label_col: str = None, geometry_col: str = None, crs: str = 'EPSG:4326', config=default_config)

Build a Troi from a paddocks file, enveloping all geometries into a bbox.

Reads paddock geometries from a GeoPackage, Shapefile, or GeoJSON and computes a bounding box that contains all features.

Parameters:

Name Type Description Default
paddocks_filepath str

Path to the paddocks file. Supported formats: - GeoPackage (.gpkg) - Shapefile (.shp) - GeoJSON (.geojson, .json)

required
start date

Inclusive start date.

required
end date

Inclusive end date.

required
stub str

Optional human-readable stub identifier. If omitted, a SHA-256 hash of the inputs is used.

None
label_col str

Column name containing paddock labels/names (e.g. 'title', 'name', 'paddock'). If provided, the column is renamed to 'paddock' for downstream compatibility.

None
geometry_col str

Column name containing geometry data. For standard geo formats this is auto-detected.

None
crs str

Coordinate reference system to assume if the file has none. Default 'EPSG:4326'. The bbox is always returned in EPSG:4326.

'EPSG:4326'

Returns:

Name Type Description
Troi

Instance with bbox = [west, south, east, north]

encompassing all paddock geometries.

Example
from datetime import date
from troi.troi import Troi

# From GeoJSON with custom label column
q = Troi.build_from_paddocks(
    paddocks_filepath='/path/to/paddocks.json',
    start=date(2023, 1, 1),
    end=date(2023, 12, 31),
    label_col='title',
    stub='my_farm',
)

Config reference

troi.config