Paddock time series¶
Three closely-related functions that turn per-pixel rasters into per-paddock summaries:
| Function | What it produces |
|---|---|
make_paddock_time_series |
Per-paddock medians for every band + index at every timestep, on dims (paddock, time). |
make_yearly_paddock_time_series |
The same dataset split into one slice per calendar year, with a doy (day-of-year) coordinate attached. |
make_smoothed_paddock_time_series |
The same data resampled to a fixed cadence, gap-filled with PCHIP interpolation, and smoothed with a Savitzky-Golay filter. |
All three persist Zarr v2 outputs under troi.tmp_dir, named from the
paddocks file's stem:
{stem}_timeseries.zarr{stem}_timeseries_{year}.zarr(one per year){stem}_timeseries_smoothed.zarr
These functions are the pivot from pixel-space to paddock-space — the central time-series dataset that phenology and plotting consume.
Example: produce a paddock × time table¶
from datetime import date
from troi.troi import Troi
from PaddockTS.Phenology.make_paddock_time_series import make_paddock_time_series
q = Troi(
bbox=[148.36265, -33.52606, 148.38265, -33.50606],
start=date(2024, 1, 1),
end=date(2024, 12, 31),
stub="ts_demo",
)
ts = make_paddock_time_series(q)
print(ts)
# <xarray.Dataset>
# Dimensions: (paddock: 12, time: 73)
# Coordinates:
# * paddock (paddock) <U3 '1' '2' '3' ... '12'
# * time (time) datetime64[ns] 2024-01-03 ... 2024-12-29
# spatial_ref int32 ...
# Data variables:
# nbart_blue (paddock, time) float64 ...
# nbart_green (paddock, time) float64 ...
# ...
# NDVI (paddock, time) float64 ...
# CFI (paddock, time) float64 ...
# NIRv (paddock, time) float64 ...
# NDTI (paddock, time) float64 ...
# CAI (paddock, time) float64 ...
# NDVI of paddock 1 over the year, as a pandas Series
ndvi_p1 = ts.NDVI.sel(paddock="1").to_pandas()
ndvi_p1.plot()
The paddock coordinate is always strings — '1', '2', … — so
ts.sel(paddock="1") works whether your IDs are numeric or
human-readable labels.
Example: split by year + plot DOY-aligned NDVI¶
make_yearly_paddock_time_series adds a doy (1–366) coordinate so
multi-year series can be overlaid on a common DOY axis:
import matplotlib.pyplot as plt
from PaddockTS.Phenology.make_yearly_paddock_time_series import make_yearly_paddock_time_series
# Use a multi-year troi
q = Troi(
bbox=[148.36265, -33.52606, 148.38265, -33.50606],
start=date(2022, 1, 1),
end=date(2024, 12, 31),
stub="yearly_demo",
)
yearly = make_yearly_paddock_time_series(q)
# {2022: <Dataset>, 2023: <Dataset>, 2024: <Dataset>}
fig, ax = plt.subplots()
for year, ds in yearly.items():
ax.plot(ds.doy, ds.NDVI.sel(paddock="1"), label=str(year))
ax.set_xlabel("DOY")
ax.set_ylabel("NDVI")
ax.legend()
Example: smoothed series for phenology¶
Sentinel-2 revisit gaps and cloud-mask drops leave irregular series.
make_smoothed_paddock_time_series produces a uniform, smoothed
version suitable for phenology fitting and plotting:
from PaddockTS.Phenology.make_smoothed_paddock_time_series import make_smoothed_paddock_time_series
smoothed = make_smoothed_paddock_time_series(
q,
days=10, # 10-day median resample
window_length=7, # Savitzky-Golay window (odd; coerced if even)
polyorder=2, # SG polynomial order (< window_length)
)
# Compare raw vs. smoothed for one paddock
raw = ts.NDVI.sel(paddock="1")
smo = smoothed.NDVI.sel(paddock="1")
ax = raw.plot.scatter(x="time", label="raw")
smo.plot(ax=ax.axes, color="red", label="smoothed")
ax.axes.legend()
Bring your own paddocks¶
All three functions accept paddocks_filepath. If None, they default
to the SAM paddocks at troi.sam_paddocks_path (running SAM if it
hasn't been run yet). Any GeoPackage / Shapefile / GeoJSON with a
paddock column works; load_user_paddocks will derive missing
columns.
The output Zarr is named from the file stem
(my_paddocks_timeseries.zarr), so SAM and user runs coexist in the
same tmp_dir without overwriting each other.
Reference¶
make_paddock_time_series¶
PaddockTS.Phenology.make_paddock_time_series.make_paddock_time_series ¶
Compute per-paddock medians for every band at every timestep.
Steps:
- Compute the five spectral indices (NDVI, CFI, NIRv, NDTI, CAI) and add them to the input dataset.
- Rasterise paddock polygons to integer IDs aligned with the Sentinel-2 grid.
- For each band, in parallel across processes, compute the per-paddock NaN-aware median across pixels at every timestep.
- Stitch results back into an xarray Dataset on dims
(paddock, time)and persist as Zarr v2 to{paddocks_filepath stem}_timeseries.zarr.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
troi
|
Troi
|
The :class: |
required |
ds_sentinel2
|
Optional in-memory Sentinel-2 dataset (with the five
indices already added, or they will be computed). If |
None
|
|
paddocks_filepath
|
Path to a GeoPackage (.gpkg) containing paddock
polygons (must include a |
None
|
|
crs
|
Equal-area CRS to write onto the dataset for georeferencing the rasterised mask. Defaults to EPSG:6933 (WGS84 / NSIDC EASE-Grid 2.0 Global). |
'epsg:6933'
|
Returns:
| Type | Description |
|---|---|
|
xarray.Dataset: Per-paddock medians on dims |
|
|
with one data variable per Sentinel-2 band and per spectral |
|
|
index. Also persisted to |
make_yearly_paddock_time_series¶
PaddockTS.Phenology.make_yearly_paddock_time_series.make_yearly_paddock_time_series ¶
Persist one Zarr per year and return the same data as a dict.
Loads the paddockTS Zarr if not provided, calls
:func:split_paddock_time_series_by_year, and writes each per-year slice as
Zarr v2 to {paddocks_filepath stem}_timeseries_{year}.zarr.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
troi
|
The :class: |
required | |
ds_paddockTS
|
Optional in-memory paddockTS dataset (typically the
smoothed series). If |
None
|
|
paddocks_filepath
|
Path to the paddocks GeoPackage. Used to derive
the timeseries zarr path. If |
None
|
Returns:
| Type | Description |
|---|---|
|
dict[int, xarray.Dataset]: Mapping |
|
|
per-year slice is also persisted to disk. |
make_smoothed_paddock_time_series¶
PaddockTS.Phenology.make_smoothed_paddock_time_series.make_smoothed_paddock_time_series ¶
make_smoothed_paddock_time_series(troi, ds_paddockTS=None, paddocks_filepath=None, days=10, window_length=7, polyorder=2)
Resample-then-interpolate-then-smooth all time-dependent variables.
Pipeline applied to each paddock × variable series:
- Split out non-time-dependent vars (static metadata, etc.).
- Resample time-dependent data with a
days-day median. - Fill gaps with PCHIP (monotone cubic) interpolation; fall back to mean-fill for series with fewer than 2 valid points.
- Smooth with a Savitzky-Golay filter
(
window_lengthandpolyorderconfigurable). - Re-attach static variables and persist as Zarr v2 to
{paddocks_filepath stem}_timeseries_smoothed.zarr.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
troi
|
The :class: |
required | |
ds_paddockTS
|
Optional in-memory paddockTS dataset. If |
None
|
|
paddocks_filepath
|
Path to the paddocks GeoPackage. Used to derive
the timeseries zarr path. If |
None
|
|
days
|
Resampling cadence in days. Default 10. |
10
|
|
window_length
|
Savitzky-Golay window size in number of resampled samples. Coerced to the next odd integer if even and clipped to fit short series. Default 7. |
7
|
|
polyorder
|
Savitzky-Golay polynomial order. Must be less than
|
2
|
Returns:
| Type | Description |
|---|---|
|
xarray.Dataset: Smoothed dataset on dims |
|
|
with the same data variables as the input, plus an |
|
|
boolean variable marking which resampled bins contained at least |
|
|
one real observation (False = gap-filled). Also persisted to |
|
|
|
split_paddock_time_series_by_year¶
PaddockTS.Phenology.make_yearly_paddock_time_series.split_paddock_time_series_by_year ¶
Group a paddock time-series dataset into one slice per calendar year.
Adds a doy coordinate (day-of-year, 1–366) on the time
dimension of each per-year slice so seasonal curves can be aligned
across years on a common DOY axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ds
|
An xarray.Dataset on dims |
required |
Returns:
| Type | Description |
|---|---|
|
dict[int, xarray.Dataset]: Mapping |
|
|
|
|
|
coordinate alongside |