Skip to main content

PaddockTimeSeries (PaddockTS)

Paddock-scale time-series analysis of Australian agricultural land, end-to-end from a single bounding box.

Built at the Borevitz Lab, Australian National University for ecologists, agronomists, and remote-sensing researchers who want a reproducible pipeline for Sentinel-2 imagery to paddock (or field) boundaries and paddock-level summaries of greenness, ground cover, and phenology.

Tests Docs License: MIT Python 3.11+ EarthArXiv preprint

📚 Documentation: https://johnburley3000.github.io/paddocktimeseries/

Preprint: Burley et al. (2026), EarthArXiv

Code-free web tool: paddocktimeseries.net by Yasar Adeel Ansari


What it does

Give PaddockTS a time and region of interest (troi). It produces:

  • Reusable Sentinel-2 raster time series — multispectral observations are stored in a spatial, time-indexed cache managed by pysentinel2. The raster stack can be reopened for other analyses, with cloud masking and spectral indices applied on read.
  • Paddock boundaries — automatic field-boundary detection using Segment Anything through segment-geospatial (samgeo), applied to an NDWI Fourier-feature image. The resulting GeoPackage contains paddock geometries, identifiers, areas, and shape compactness.
  • Compact paddock-level time series — median surface reflectance, spectral indices, and fractional cover for each paddock and observation date, stored as Zarr datasets on (paddock, time). PaddockTS also produces resampled, gap-filled, and smoothed time series.
  • Spectral indices and fractional cover — PaddockTS computes NDVI, CFI, NIRv, NDTI, and CAI. It also estimates bare ground (bg), green vegetation (pv), and non-green vegetation (npv) using a TFLite model adapted from fractionalcover3.
  • Phenology metrics — start, peak, and end of season, together with seasonal amplitudes and integrals, calculated for each paddock and year using a vendored version of phenolopy.
  • Environmental context — Copernicus 30 m elevation and derived terrain variables; OzWALD and SILO daily climate data; and SLGA 90 m soil properties, all matched to the same area of interest.
  • Plots, videos, and reports — true-colour and fractional-cover time-lapse videos, paddock calendar plots, phenology curves, climate and terrain panels, and a combined PDF report.

Alternatively, supply your own paddock boundaries, optionally labelled with management categories or outcomes. PaddockTS can analyse these instead of, or alongside, automatically segmented paddocks.

Shared data stores reuse previously downloaded observations across overlapping areas and dates. Derived PaddockTS intermediates are reused when the area and date range match. Incomplete cached writes are detected and rebuilt on the next run.


Install

Conda + pip (recommended)

The conda environment provides the native stack (GDAL/PROJ/GEOS, PyTorch, TensorFlow, Segment Anything); pip install . then pulls the lab packages — the troi core and the five data stores — from PyPI, pinned to the releases tested with this version:

git clone https://github.com/johnburley3000/paddocktimeseries.git
cd paddocktimeseries
conda env update -n paddockts -f environment.yml
conda activate paddockts
pip install .

PaddockTS targets Python ≥ 3.11.

Hardened-kernel note (Fedora / recent glibc). If import tensorflow fails with "cannot enable executable stack as shared object requires", your kernel enforces non-executable stacks and conda-forge's TensorFlow libraries request one. Clear the flag once (the libraries are in your env, so no root needed):

conda install -n paddockts -c conda-forge patchelf   # or use execstack
find "$CONDA_PREFIX" -name '*.so*' -exec sh -c \
  'execstack -c "$1" 2>/dev/null || true' _ {} \;

Most Linux systems don't hit this.

From source (development)

git clone https://github.com/thestochasticman/troi.git
git clone https://github.com/thestochasticman/pysentinel2.git
git clone https://github.com/thestochasticman/pysilo.git
git clone https://github.com/thestochasticman/pyozwald.git
git clone https://github.com/thestochasticman/pycopdem.git
git clone https://github.com/thestochasticman/pyslga.git
git clone https://github.com/johnburley3000/paddocktimeseries.git
cd paddocktimeseries
conda env update -n paddockts -f environment.yml   # native + scientific stack
conda activate paddockts
pip install --no-deps -e ../troi -e ../pysentinel2 -e ../pysilo \
            -e ../pyozwald -e ../pycopdem -e ../pyslga -e .

Configure (optional)

Default output and cache directories are ~/Documents/Troi-Outputs and ~/Downloads/Troi-Tmp. Override and add credentials by creating ~/.config/Troi.json:

{
  "out_dir": "/data/paddockts/outputs",
  "tmp_dir": "/data/paddockts/tmp",
  "email": "you@example.org",
  "tern_api_key": "<your-tern-key>"
}

Settings can also come from environment variables (TROI_OUTDIR, TROI_TMPDIR, TROI_EMAIL, TROI_TERN_KEY).

Credentials:

  • email is required only by the SILO climate stage
  • tern_api_key is required only by the SLGA soils stage — generate one at https://account.tern.org.au/

The Sentinel-2 → PaddockTS chain itself works without any credentials.

You can also pass configuration directly to Troi via a Config object — see the Getting started page.


Runnable demos

Three Jupyter notebooks under demo/ walk through the most common workflows:

jupyter lab demo/

Tests

The offline unit suite under tests/ verifies the index math, fractional-cover unmixing, per-paddock median aggregation, smoothing, phenology metrics, and the caching contract — all against synthetic inputs, with no network access or credentials. It runs on every push via GitHub Actions:

pip install -e '.[tests]'
pytest

End-to-end acceptance scripts that exercise the full pipeline against the live data services live in test/ — see test/about_testing.md.


Quick example

from datetime import date
from troi.troi import Troi
from PaddockTS.get_outputs import get_outputs

troi = 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",
)

get_outputs(troi)

This kicks off both pipelines (Sentinel-2 → PaddockTS and Environmental) in parallel and renders a live dashboard. The next get_outputs(troi) on the same Troi skips every cached step.

Outputs land under ~/Documents/Troi-Outputs/<stub>/:

File What's in it
<stub>_paddocks.gpkg Segmented paddock polygons + area_ha + compactness
<stub>_paddockTS.zarr Per-paddock medians for every band + index, on (paddock, time)
<stub>_paddockTS_<year>.zarr Yearly slices with a DOY coordinate
<stub>_sentinel2.mp4 True-colour Sentinel-2 timeline
<stub>_fractional_cover.mp4 Bare/green/non-green RGB timeline
<stub>_calendar_<year>_p01.png Per-paddock thumbnail calendar
<stub>_phenology_p01.png SoS/PoS/EoS curves per paddock per year
<stub>_topography.png Elevation, slope, aspect, flow accumulation
<stub>.pdf Stitched report combining every plot

Bring your own paddocks

If you already have field boundaries (QGIS export, cadastral layer, previous run), skip SAM segmentation and use them directly.

Importantly, tell build_from_paddocks() which column of your .gpkg specifies paddock name using label_col="".

from datetime import date
from troi.troi import Troi
from PaddockTS.get_outputs import get_outputs

paddocks_fp = "/path/to/paddocks.gpkg"  # .gpkg, .shp, or .geojson

troi = Troi.build_from_paddocks(
    paddocks_filepath=paddocks_fp,
    start=date(2024, 1, 1),
    end=date(2024, 12, 31),
    stub="my_farm",
    label_col="paddock_name",
)

get_outputs(
    troi,
    paddocks_filepath=paddocks_fp,
    skip_sam=True,
    label_col="paddock_name",
)

Pipeline at a glance

get_outputs(troi) runs two pipelines on parallel threads. The Sentinel-2 chain segments paddocks and builds the per-paddock time series, phenology, and plots; the Environmental chain pulls terrain, climate, and soils. Both feed the final stitched PDF.

flowchart TD
    Q["<b>Troi</b><br/>bbox · dates · stub"] --> GO(["get_outputs(troi)"])

    GO -->|"thread 1 · Sentinel-2 → PaddockTS"| D["Download + clean<br/>Sentinel-2"]
    D --> IDX["Spectral indices<br/>NDVI · CFI · NIRv · NDTI · CAI"]
    D --> FC["Fractional cover<br/>bg · pv · npv"]
    D --> SAM["Segment paddocks<br/>(SAM)"]
    IDX --> TS["Per-paddock<br/>time series"]
    SAM --> TS
    TS --> YR["Yearly split<br/>+ DOY"]
    YR --> PH["Phenology<br/>SoS · PoS · EoS"]
    FC --> PLOT["Videos · calendar ·<br/>phenology plots"]
    SAM --> PLOT
    PH --> PLOT

    GO -->|"thread 2 · Environmental"| TER["Terrain DEM<br/>slope · aspect · TWI"]
    GO --> OZ["OzWALD<br/>daily climate"]
    GO --> SILO["SILO<br/>climate"]
    GO --> SLGA["SLGA<br/>soils"]
    TER --> EPLOT["Climate +<br/>terrain plots"]
    OZ --> EPLOT
    SILO --> EPLOT
    SLGA --> EPLOT

    D -.->|"clean cube<br/>(terrain plot waits)"| EPLOT
    PLOT --> PDF[["Stitched PDF report"]]
    EPLOT --> PDF

    classDef default color:#000;

The table below is the same chain, stage by stage:

Sentinel-2 → PaddockTS Environmental
Download Sentinel-2 + clean Download terrain (Copernicus DEM)
Compute spectral indices Download OzWALD daily climate
Compute fractional cover Download SILO climate
Sentinel-2 video Download SLGA soils
Segment paddocks (SAM) OzWALD plot
Sentinel-2 + paddocks video SILO plot
Fractional cover video Terrain plot
Fractional cover + paddocks video
Make paddock time series
Make yearly paddock time series
Estimate phenology
Calendar plot
Phenology plot
PDF report

Every stage is a standalone function — pick any subset, swap in your own segmentation, plug in your own phenology library. See the pipeline page for the full call-graph and per-stage caching behaviour.


Calling individual stages

from pysentinel2.cube import Cube
from PaddockTS.FractionalCover import compute_fractional_cover
from PaddockTS.PaddockSegmentation.get_paddocks import get_paddocks

cube = Cube(config=troi.config)
ds = cube.get_ds_troi(troi, clean=True,                # cloud-masked window
                      indices=("NDVI", "CFI", "NIRv", "NDTI", "CAI"))
fc = compute_fractional_cover(troi, ds_sentinel2=ds)   # bg / pv / npv
paddocks = get_paddocks(troi, ds_sentinel2=ds)         # GeoDataFrame

Every function loads its own inputs from the cache if you don't pass them, so you can call them out of order or in isolation.


Data sources and acknowledgments

PaddockTS does not redistribute upstream data; it queries them on demand:

  • Sentinel-2 ARD — Geoscience Australia Digital Earth Australia STAC.
  • Copernicus DEM 30 mAWS Open Data.
  • OzWALD — Australian Water and Landscape Dynamics, hosted by ANU.
  • SILO — Queensland Government's gridded climate dataset.
  • SLGA — TERN / CSIRO Soil and Landscape Grid of Australia (TERN API key required).

Citation

Please cite the associated EarthArXiv preprint when using PaddockTS in research:

Burley, J. T., Ansari, Y. A., Bradley, C., Norton, A., & Borevitz, J. (2026). PaddockTS: paddock-level satellite time series analysis of agroecosystem dynamics. EarthArXiv. https://doi.org/10.31223/X5821Z

Machine-readable citation metadata is provided in CITATION.cff.

Please also cite the upstream datasets and methods relevant to your analysis.


License and attribution

PaddockTS is MIT-licensed — see LICENSE.

Vendored code

Third-party code shipped inside the package; see PaddockTS/LICENSES/ for full license texts:

  • fractionalcover3 by Robert Denham — MIT. The TFLite unmixing models and the unmixing routine in PaddockTS.FractionalCover._unmix are adapted from this work.
  • phenolopy by Lewis Trotter — Apache 2.0. Vendored verbatim under PaddockTS.Phenology._phenolopy (with minor NumPy 2.0 compatibility fixes documented in the file header) and used through PaddockTS.Phenology.estimate_phenology.
  • DAESIM_preprocess by Christopher Bradley — MIT. Environmental data harvesting functions adapted in the pyozwald / pysilo / pyslga / pycopdem stores for downloading and processing climate, vegetation, soil, and topographic datasets.

Key runtime dependencies

Installed as regular dependencies, not vendored — please cite if relevant to your work:


Contributing & support

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paddocktimeseries-1.0.0.tar.gz (533.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paddocktimeseries-1.0.0-py3-none-any.whl (539.4 kB view details)

Uploaded Python 3

File details

Details for the file paddocktimeseries-1.0.0.tar.gz.

File metadata

  • Download URL: paddocktimeseries-1.0.0.tar.gz
  • Upload date:
  • Size: 533.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for paddocktimeseries-1.0.0.tar.gz
Algorithm Hash digest
SHA256 bb40d3b43e113dca00cc2cccfb343a382d395ea2160e56a05f486b7d8681c05c
MD5 6b34c10c067e98c6108a0509b40a52bb
BLAKE2b-256 226ea77d728a9739519011f2fdb425ce6a2bc57a771ba6633ce7cf4892d88faa

See more details on using hashes here.

File details

Details for the file paddocktimeseries-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for paddocktimeseries-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0c505284bd3bd64f58fbd278d2bc226c91f7ce97c1962f4d366a5b0f10450f7f
MD5 912a8e5a490ad10e07ab0cde5c0a2348
BLAKE2b-256 557a8c0539fe6e7d55ab0f67d2172d987691556b252107c979199fce39b6026d

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0.1

2 files

This release

1.0.0 This release

2 files

0.8.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page