paddocktimeseries 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- paddocktimeseries-0.8.0/LICENSE +21 -0
- paddocktimeseries-0.8.0/PKG-INFO +459 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/__init__.py +1 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/__init__.py +0 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_256x128x256.tflite +0 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_256x64x256.tflite +0 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_32x32x32.tflite +0 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_64x64x64.tflite +0 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_unmix.py +45 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/check_if_valid_fractional_cover_exists.py +49 -0
- paddocktimeseries-0.8.0/PaddockTS/FractionalCover/compute_fractional_cover.py +208 -0
- paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/__init__.py +0 -0
- paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/_presegment.py +130 -0
- paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/check_if_valid_paddocks_exists.py +60 -0
- paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/check_if_valid_preseg_exists.py +51 -0
- paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/get_paddocks.py +232 -0
- paddocktimeseries-0.8.0/PaddockTS/Phenology/__init__.py +0 -0
- paddocktimeseries-0.8.0/PaddockTS/Phenology/_phenolopy.py +2525 -0
- paddocktimeseries-0.8.0/PaddockTS/Phenology/estimate_phenology.py +151 -0
- paddocktimeseries-0.8.0/PaddockTS/Phenology/make_paddock_time_series.py +233 -0
- paddocktimeseries-0.8.0/PaddockTS/Phenology/make_smoothed_paddock_time_series.py +157 -0
- paddocktimeseries-0.8.0/PaddockTS/Phenology/make_yearly_paddock_time_series.py +103 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/__init__.py +24 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/calendar_plot.py +523 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/fractional_cover_paddocks_video.py +157 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/fractional_cover_video.py +117 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/make_pdf.py +230 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/ozwald_plot.py +138 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/phenology_plot.py +200 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/sentinel2_paddocks_video.py +141 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/sentinel2_video.py +107 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/silo_plot.py +95 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/terrain_derivatives.py +213 -0
- paddocktimeseries-0.8.0/PaddockTS/Plotting/terrain_tiles_plot.py +207 -0
- paddocktimeseries-0.8.0/PaddockTS/__init__.py +0 -0
- paddocktimeseries-0.8.0/PaddockTS/check_status.py +15 -0
- paddocktimeseries-0.8.0/PaddockTS/daesim_forcing.py +146 -0
- paddocktimeseries-0.8.0/PaddockTS/get_outputs.py +688 -0
- paddocktimeseries-0.8.0/PaddockTS/paths.py +110 -0
- paddocktimeseries-0.8.0/PaddockTS/run_environmental.py +180 -0
- paddocktimeseries-0.8.0/PaddockTS/status.py +12 -0
- paddocktimeseries-0.8.0/PaddockTS/utils.py +79 -0
- paddocktimeseries-0.8.0/PaddockTS/viewer/__init__.py +13 -0
- paddocktimeseries-0.8.0/PaddockTS/viewer/__main__.py +18 -0
- paddocktimeseries-0.8.0/PaddockTS/viewer/app.py +149 -0
- paddocktimeseries-0.8.0/PaddockTS/viewer/scan.py +196 -0
- paddocktimeseries-0.8.0/PaddockTS/viewer/serve.py +80 -0
- paddocktimeseries-0.8.0/README.md +423 -0
- paddocktimeseries-0.8.0/paddocktimeseries.egg-info/PKG-INFO +459 -0
- paddocktimeseries-0.8.0/paddocktimeseries.egg-info/SOURCES.txt +64 -0
- paddocktimeseries-0.8.0/paddocktimeseries.egg-info/dependency_links.txt +1 -0
- paddocktimeseries-0.8.0/paddocktimeseries.egg-info/requires.txt +12 -0
- paddocktimeseries-0.8.0/paddocktimeseries.egg-info/top_level.txt +1 -0
- paddocktimeseries-0.8.0/pyproject.toml +86 -0
- paddocktimeseries-0.8.0/setup.cfg +4 -0
- paddocktimeseries-0.8.0/test/test_mode1.py +13 -0
- paddocktimeseries-0.8.0/test/test_mode2.py +19 -0
- paddocktimeseries-0.8.0/test/test_mode2_milgadara.py +21 -0
- paddocktimeseries-0.8.0/tests/test_cache_contract.py +51 -0
- paddocktimeseries-0.8.0/tests/test_fractional_cover.py +59 -0
- paddocktimeseries-0.8.0/tests/test_indices.py +65 -0
- paddocktimeseries-0.8.0/tests/test_paddock_time_series.py +87 -0
- paddocktimeseries-0.8.0/tests/test_paths_and_troi.py +57 -0
- paddocktimeseries-0.8.0/tests/test_phenology.py +52 -0
- paddocktimeseries-0.8.0/tests/test_smoothing.py +54 -0
- paddocktimeseries-0.8.0/tests/test_yearly_split.py +29 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Borevitz Lab, Australian National University, and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: paddocktimeseries
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Paddock segmentation and time-series analysis from Sentinel-2 imagery
|
|
5
|
+
Author-email: John Burley <john.burley@anu.edu.au>, Yasar Adeel Ansari <u6737670@anu.edu.au>
|
|
6
|
+
Maintainer-email: "John T. Burley" <john.burley@anu.edu.au>, Yasar Adeel Ansari <u6737670@anu.edu.au>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/johnburley3000/paddocktimeseries
|
|
9
|
+
Project-URL: Repository, https://github.com/johnburley3000/paddocktimeseries
|
|
10
|
+
Project-URL: Issues, https://github.com/johnburley3000/paddocktimeseries/issues
|
|
11
|
+
Project-URL: Documentation, https://johnburley3000.github.io/paddocktimeseries/
|
|
12
|
+
Project-URL: Preprint, https://doi.org/10.31223/X5821Z
|
|
13
|
+
Keywords: remote sensing,Earth observation,sentinel-2,satellite time series,agricultural field boundaries,field-level analysis,vegetation phenology,agroecosystem,agricultural monitoring,geospatial
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Provides-Extra: tests
|
|
27
|
+
Requires-Dist: pytest; extra == "tests"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
30
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
31
|
+
Requires-Dist: mkdocstrings[python]; extra == "docs"
|
|
32
|
+
Requires-Dist: mkdocs-jupyter; extra == "docs"
|
|
33
|
+
Provides-Extra: viewer
|
|
34
|
+
Requires-Dist: streamlit; extra == "viewer"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# PaddockTimeSeries (PaddockTS)
|
|
38
|
+
|
|
39
|
+
**Paddock-scale time-series analysis of Australian agricultural land,
|
|
40
|
+
end-to-end from a single bounding box.**
|
|
41
|
+
|
|
42
|
+
Built at the [Borevitz Lab, Australian National
|
|
43
|
+
University](https://biology.anu.edu.au/research/research-groups/borevitz-group-plant-genomics-climate-adaption) for ecologists, agronomists,
|
|
44
|
+
and remote-sensing researchers who want a reproducible pipeline for
|
|
45
|
+
Sentinel-2 imagery to paddock (or field) boundaries and paddock-level
|
|
46
|
+
summaries of greenness, ground cover, and phenology.
|
|
47
|
+
|
|
48
|
+
[](https://github.com/johnburley3000/paddocktimeseries/actions/workflows/tests.yml)
|
|
49
|
+
[](https://johnburley3000.github.io/paddocktimeseries/)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://www.python.org/)
|
|
52
|
+
[](https://doi.org/10.31223/X5821Z)
|
|
53
|
+
|
|
54
|
+
📚 **Documentation:** <https://johnburley3000.github.io/paddocktimeseries/>
|
|
55
|
+
|
|
56
|
+
**Preprint:** [Burley et al. (2026), EarthArXiv](https://doi.org/10.31223/X5821Z)
|
|
57
|
+
|
|
58
|
+
**Code-free web tool:** [paddocktimeseries.net](https://paddocktimeseries.net) by Yasar Adeel Ansari
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## What it does
|
|
63
|
+
|
|
64
|
+
Give PaddockTS a time and region of interest ([`troi`](https://github.com/thestochasticman/troi)). It produces:
|
|
65
|
+
|
|
66
|
+
- **Reusable Sentinel-2 raster time series** — multispectral observations
|
|
67
|
+
are stored in a spatial, time-indexed cache managed by
|
|
68
|
+
[`pysentinel2`](https://github.com/thestochasticman/pysentinel2).
|
|
69
|
+
The raster stack can be reopened for other analyses, with cloud masking
|
|
70
|
+
and spectral indices applied on read.
|
|
71
|
+
- **Paddock boundaries** — automatic field-boundary detection using
|
|
72
|
+
[Segment Anything](https://segment-anything.com/) through
|
|
73
|
+
[`segment-geospatial`](https://samgeo.gishub.org/) (`samgeo`), applied
|
|
74
|
+
to an NDWI Fourier-feature image. The resulting GeoPackage contains
|
|
75
|
+
paddock geometries, identifiers, areas, and shape compactness.
|
|
76
|
+
- **Compact paddock-level time series** — median surface reflectance,
|
|
77
|
+
spectral indices, and fractional cover for each paddock and observation
|
|
78
|
+
date, stored as Zarr datasets on `(paddock, time)`. PaddockTS also
|
|
79
|
+
produces resampled, gap-filled, and smoothed time series.
|
|
80
|
+
- **Spectral indices and fractional cover** — PaddockTS computes NDVI,
|
|
81
|
+
CFI, NIRv, NDTI, and CAI. It also estimates bare ground (`bg`), green
|
|
82
|
+
vegetation (`pv`), and non-green vegetation (`npv`) using a TFLite
|
|
83
|
+
model adapted from
|
|
84
|
+
[`fractionalcover3`](https://github.com/jrsrp/fractionalcover3).
|
|
85
|
+
- **Phenology metrics** — start, peak, and end of season, together with
|
|
86
|
+
seasonal amplitudes and integrals, calculated for each paddock and
|
|
87
|
+
year using a vendored version of
|
|
88
|
+
[`phenolopy`](https://github.com/lewistrotter/phenolopy).
|
|
89
|
+
- **Environmental context** — Copernicus 30 m elevation and derived
|
|
90
|
+
terrain variables; [OzWALD](https://www.wenfo.org/ozwald/) and
|
|
91
|
+
[SILO](https://www.longpaddock.qld.gov.au/silo/) daily climate data;
|
|
92
|
+
and [SLGA](https://esoil.io/TERNLandscapes/Public/Pages/SLGA/index.html)
|
|
93
|
+
90 m soil properties, all matched to the same area of interest.
|
|
94
|
+
- **Plots, videos, and reports** — true-colour and fractional-cover
|
|
95
|
+
time-lapse videos, paddock calendar plots, phenology curves, climate
|
|
96
|
+
and terrain panels, and a combined PDF report.
|
|
97
|
+
|
|
98
|
+
Alternatively, supply your own paddock boundaries, optionally labelled
|
|
99
|
+
with management categories or outcomes. PaddockTS can analyse these
|
|
100
|
+
instead of, or alongside, automatically segmented paddocks.
|
|
101
|
+
|
|
102
|
+
Shared data stores reuse previously downloaded observations across
|
|
103
|
+
overlapping areas and dates. Derived PaddockTS intermediates are reused
|
|
104
|
+
when the area and date range match. Incomplete cached writes are detected
|
|
105
|
+
and rebuilt on the next run.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Install
|
|
110
|
+
|
|
111
|
+
### Conda + pip (recommended)
|
|
112
|
+
|
|
113
|
+
The conda environment provides the native stack (GDAL/PROJ/GEOS,
|
|
114
|
+
PyTorch, TensorFlow, Segment Anything); `pip install .` then pulls the
|
|
115
|
+
lab packages — the `troi` core and the five data stores — straight
|
|
116
|
+
from their GitHub repositories:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
git clone https://github.com/johnburley3000/paddocktimeseries.git
|
|
120
|
+
cd paddocktimeseries
|
|
121
|
+
conda env update -n paddockts -f environment.yml
|
|
122
|
+
conda activate paddockts
|
|
123
|
+
pip install .
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
PaddockTS targets Python ≥ 3.11.
|
|
127
|
+
|
|
128
|
+
> **Hardened-kernel note (Fedora / recent glibc).** If `import
|
|
129
|
+
> tensorflow` fails with *"cannot enable executable stack as shared
|
|
130
|
+
> object requires"*, your kernel enforces non-executable stacks and
|
|
131
|
+
> conda-forge's TensorFlow libraries request one. Clear the flag once
|
|
132
|
+
> (the libraries are in your env, so no root needed):
|
|
133
|
+
>
|
|
134
|
+
> ```bash
|
|
135
|
+
> conda install -n paddockts -c conda-forge patchelf # or use execstack
|
|
136
|
+
> find "$CONDA_PREFIX" -name '*.so*' -exec sh -c \
|
|
137
|
+
> 'execstack -c "$1" 2>/dev/null || true' _ {} \;
|
|
138
|
+
> ```
|
|
139
|
+
>
|
|
140
|
+
> Most Linux systems don't hit this.
|
|
141
|
+
|
|
142
|
+
### From source (development)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git clone https://github.com/thestochasticman/troi.git
|
|
146
|
+
git clone https://github.com/thestochasticman/pysentinel2.git
|
|
147
|
+
git clone https://github.com/thestochasticman/pysilo.git
|
|
148
|
+
git clone https://github.com/thestochasticman/pyozwald.git
|
|
149
|
+
git clone https://github.com/thestochasticman/pycopdem.git
|
|
150
|
+
git clone https://github.com/thestochasticman/pyslga.git
|
|
151
|
+
git clone https://github.com/johnburley3000/paddocktimeseries.git
|
|
152
|
+
cd paddocktimeseries
|
|
153
|
+
conda env update -n paddockts -f environment.yml # native + scientific stack
|
|
154
|
+
conda activate paddockts
|
|
155
|
+
pip install --no-deps -e ../troi -e ../pysentinel2 -e ../pysilo \
|
|
156
|
+
-e ../pyozwald -e ../pycopdem -e ../pyslga -e .
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Configure (optional)
|
|
160
|
+
|
|
161
|
+
Default output and cache directories are `~/Documents/Troi-Outputs`
|
|
162
|
+
and `~/Downloads/Troi-Tmp`. Override and add credentials by
|
|
163
|
+
creating `~/.config/Troi.json`:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"out_dir": "/data/paddockts/outputs",
|
|
168
|
+
"tmp_dir": "/data/paddockts/tmp",
|
|
169
|
+
"email": "you@example.org",
|
|
170
|
+
"tern_api_key": "<your-tern-key>"
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Settings can also come from environment variables
|
|
175
|
+
(`TROI_OUTDIR`, `TROI_TMPDIR`, `TROI_EMAIL`,
|
|
176
|
+
`TROI_TERN_KEY`).
|
|
177
|
+
|
|
178
|
+
**Credentials:**
|
|
179
|
+
|
|
180
|
+
- `email` is required only by the SILO climate stage
|
|
181
|
+
- `tern_api_key` is required only by the SLGA soils stage — generate
|
|
182
|
+
one at <https://account.tern.org.au/>
|
|
183
|
+
|
|
184
|
+
The Sentinel-2 → PaddockTS chain itself works without any credentials.
|
|
185
|
+
|
|
186
|
+
You can also pass configuration directly to `Troi` via a `Config`
|
|
187
|
+
object — see the [Getting started](https://johnburley3000.github.io/paddocktimeseries/getting-started/)
|
|
188
|
+
page.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Runnable demos
|
|
193
|
+
|
|
194
|
+
Three Jupyter notebooks under [`demo/`](demo/) walk through the most
|
|
195
|
+
common workflows:
|
|
196
|
+
|
|
197
|
+
- [`demo/01_quickstart.ipynb`](demo/01_quickstart.ipynb) — bbox + dates → `get_outputs(troi)` → review the calendar / phenology / PDF.
|
|
198
|
+
- [`demo/02_pipeline_stages.ipynb`](demo/02_pipeline_stages.ipynb) — call each Sentinel-2 stage individually, inspect intermediate outputs.
|
|
199
|
+
- [`demo/03_custom_paddocks.ipynb`](demo/03_custom_paddocks.ipynb) — bring your own paddock boundaries and skip SAM.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
jupyter lab demo/
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Tests
|
|
208
|
+
|
|
209
|
+
The offline unit suite under [`tests/`](tests/) verifies the index
|
|
210
|
+
math, fractional-cover unmixing, per-paddock median aggregation,
|
|
211
|
+
smoothing, phenology metrics, and the caching contract — all against
|
|
212
|
+
synthetic inputs, with no network access or credentials. It runs on
|
|
213
|
+
every push via [GitHub Actions](.github/workflows/tests.yml):
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pip install -e '.[tests]'
|
|
217
|
+
pytest
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
End-to-end acceptance scripts that exercise the full pipeline against
|
|
221
|
+
the live data services live in [`test/`](test/) — see
|
|
222
|
+
[`test/about_testing.md`](test/about_testing.md).
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Quick example
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from datetime import date
|
|
230
|
+
from troi.troi import Troi
|
|
231
|
+
from PaddockTS.get_outputs import get_outputs
|
|
232
|
+
|
|
233
|
+
troi = Troi(
|
|
234
|
+
bbox=[148.36265, -33.52606, 148.38265, -33.50606], # [W, S, E, N]
|
|
235
|
+
start=date(2020, 1, 1),
|
|
236
|
+
end=date(2021, 12, 31),
|
|
237
|
+
stub="my_first_run",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
get_outputs(troi)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
This kicks off both pipelines (Sentinel-2 → PaddockTS and Environmental)
|
|
244
|
+
in parallel and renders a live dashboard. The next `get_outputs(troi)`
|
|
245
|
+
on the same `Troi` skips every cached step.
|
|
246
|
+
|
|
247
|
+
Outputs land under `~/Documents/Troi-Outputs/<stub>/`:
|
|
248
|
+
|
|
249
|
+
| File | What's in it |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `<stub>_paddocks.gpkg` | Segmented paddock polygons + `area_ha` + `compactness` |
|
|
252
|
+
| `<stub>_paddockTS.zarr` | Per-paddock medians for every band + index, on `(paddock, time)` |
|
|
253
|
+
| `<stub>_paddockTS_<year>.zarr` | Yearly slices with a DOY coordinate |
|
|
254
|
+
| `<stub>_sentinel2.mp4` | True-colour Sentinel-2 timeline |
|
|
255
|
+
| `<stub>_fractional_cover.mp4` | Bare/green/non-green RGB timeline |
|
|
256
|
+
| `<stub>_calendar_<year>_p01.png` | Per-paddock thumbnail calendar |
|
|
257
|
+
| `<stub>_phenology_p01.png` | SoS/PoS/EoS curves per paddock per year |
|
|
258
|
+
| `<stub>_topography.png` | Elevation, slope, aspect, flow accumulation |
|
|
259
|
+
| `<stub>.pdf` | Stitched report combining every plot |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Bring your own paddocks
|
|
264
|
+
|
|
265
|
+
If you already have field boundaries (QGIS export, cadastral layer,
|
|
266
|
+
previous run), skip SAM segmentation and use them directly.
|
|
267
|
+
|
|
268
|
+
Importantly, tell build_from_paddocks() which column of your .gpkg specifies paddock name using label_col="".
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
from datetime import date
|
|
272
|
+
from troi.troi import Troi
|
|
273
|
+
from PaddockTS.get_outputs import get_outputs
|
|
274
|
+
|
|
275
|
+
paddocks_fp = "/path/to/paddocks.gpkg" # .gpkg, .shp, or .geojson
|
|
276
|
+
|
|
277
|
+
troi = Troi.build_from_paddocks(
|
|
278
|
+
paddocks_filepath=paddocks_fp,
|
|
279
|
+
start=date(2024, 1, 1),
|
|
280
|
+
end=date(2024, 12, 31),
|
|
281
|
+
stub="my_farm",
|
|
282
|
+
label_col="paddock_name",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
get_outputs(
|
|
286
|
+
troi,
|
|
287
|
+
paddocks_filepath=paddocks_fp,
|
|
288
|
+
skip_sam=True,
|
|
289
|
+
label_col="paddock_name",
|
|
290
|
+
)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Pipeline at a glance
|
|
296
|
+
|
|
297
|
+
`get_outputs(troi)` runs two pipelines on parallel threads. The
|
|
298
|
+
Sentinel-2 chain segments paddocks and builds the per-paddock time
|
|
299
|
+
series, phenology, and plots; the Environmental chain pulls terrain,
|
|
300
|
+
climate, and soils. Both feed the final stitched PDF.
|
|
301
|
+
|
|
302
|
+
```mermaid
|
|
303
|
+
flowchart TD
|
|
304
|
+
Q["<b>Troi</b><br/>bbox · dates · stub"] --> GO(["get_outputs(troi)"])
|
|
305
|
+
|
|
306
|
+
GO -->|"thread 1 · Sentinel-2 → PaddockTS"| D["Download + clean<br/>Sentinel-2"]
|
|
307
|
+
D --> IDX["Spectral indices<br/>NDVI · CFI · NIRv · NDTI · CAI"]
|
|
308
|
+
D --> FC["Fractional cover<br/>bg · pv · npv"]
|
|
309
|
+
D --> SAM["Segment paddocks<br/>(SAM)"]
|
|
310
|
+
IDX --> TS["Per-paddock<br/>time series"]
|
|
311
|
+
SAM --> TS
|
|
312
|
+
TS --> YR["Yearly split<br/>+ DOY"]
|
|
313
|
+
YR --> PH["Phenology<br/>SoS · PoS · EoS"]
|
|
314
|
+
FC --> PLOT["Videos · calendar ·<br/>phenology plots"]
|
|
315
|
+
SAM --> PLOT
|
|
316
|
+
PH --> PLOT
|
|
317
|
+
|
|
318
|
+
GO -->|"thread 2 · Environmental"| TER["Terrain DEM<br/>slope · aspect · TWI"]
|
|
319
|
+
GO --> OZ["OzWALD<br/>daily climate"]
|
|
320
|
+
GO --> SILO["SILO<br/>climate"]
|
|
321
|
+
GO --> SLGA["SLGA<br/>soils"]
|
|
322
|
+
TER --> EPLOT["Climate +<br/>terrain plots"]
|
|
323
|
+
OZ --> EPLOT
|
|
324
|
+
SILO --> EPLOT
|
|
325
|
+
SLGA --> EPLOT
|
|
326
|
+
|
|
327
|
+
D -.->|"clean cube<br/>(terrain plot waits)"| EPLOT
|
|
328
|
+
PLOT --> PDF[["Stitched PDF report"]]
|
|
329
|
+
EPLOT --> PDF
|
|
330
|
+
|
|
331
|
+
classDef default color:#000;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The table below is the same chain, stage by stage:
|
|
335
|
+
|
|
336
|
+
| Sentinel-2 → PaddockTS | Environmental |
|
|
337
|
+
|---|---|
|
|
338
|
+
| Download Sentinel-2 + clean | Download terrain (Copernicus DEM) |
|
|
339
|
+
| Compute spectral indices | Download OzWALD daily climate |
|
|
340
|
+
| Compute fractional cover | Download SILO climate |
|
|
341
|
+
| Sentinel-2 video | Download SLGA soils |
|
|
342
|
+
| Segment paddocks (SAM) | OzWALD plot |
|
|
343
|
+
| Sentinel-2 + paddocks video | SILO plot |
|
|
344
|
+
| Fractional cover video | Terrain plot |
|
|
345
|
+
| Fractional cover + paddocks video | |
|
|
346
|
+
| Make paddock time series | |
|
|
347
|
+
| Make yearly paddock time series | |
|
|
348
|
+
| Estimate phenology | |
|
|
349
|
+
| Calendar plot | |
|
|
350
|
+
| Phenology plot | |
|
|
351
|
+
| PDF report | |
|
|
352
|
+
|
|
353
|
+
Every stage is a standalone function — pick any subset, swap in your
|
|
354
|
+
own segmentation, plug in your own phenology library. See the
|
|
355
|
+
[pipeline page](https://johnburley3000.github.io/paddocktimeseries/pipeline/)
|
|
356
|
+
for the full call-graph and per-stage caching behaviour.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Calling individual stages
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
from pysentinel2.cube import Cube
|
|
364
|
+
from PaddockTS.FractionalCover import compute_fractional_cover
|
|
365
|
+
from PaddockTS.PaddockSegmentation.get_paddocks import get_paddocks
|
|
366
|
+
|
|
367
|
+
cube = Cube(config=troi.config)
|
|
368
|
+
ds = cube.get_ds_troi(troi, clean=True, # cloud-masked window
|
|
369
|
+
indices=("NDVI", "CFI", "NIRv", "NDTI", "CAI"))
|
|
370
|
+
fc = compute_fractional_cover(troi, ds_sentinel2=ds) # bg / pv / npv
|
|
371
|
+
paddocks = get_paddocks(troi, ds_sentinel2=ds) # GeoDataFrame
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Every function loads its own inputs from the cache if you don't pass
|
|
375
|
+
them, so you can call them out of order or in isolation.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Data sources and acknowledgments
|
|
380
|
+
|
|
381
|
+
PaddockTS does not redistribute upstream data; it queries them on
|
|
382
|
+
demand:
|
|
383
|
+
|
|
384
|
+
- **Sentinel-2 ARD** — Geoscience Australia
|
|
385
|
+
[Digital Earth Australia](https://explorer.dea.ga.gov.au/) STAC.
|
|
386
|
+
- **Copernicus DEM 30 m** —
|
|
387
|
+
[AWS Open Data](https://registry.opendata.aws/copernicus-dem/).
|
|
388
|
+
- **OzWALD** — Australian Water and Landscape Dynamics, hosted by ANU.
|
|
389
|
+
- **SILO** — Queensland Government's gridded climate dataset.
|
|
390
|
+
- **SLGA** — TERN / CSIRO Soil and Landscape Grid of Australia
|
|
391
|
+
(TERN API key required).
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Citation
|
|
396
|
+
|
|
397
|
+
Please cite the associated EarthArXiv preprint when using PaddockTS in
|
|
398
|
+
research:
|
|
399
|
+
|
|
400
|
+
> Burley, J. T., Ansari, Y. A., Bradley, C., Norton, A., & Borevitz, J.
|
|
401
|
+
> (2026). *PaddockTS: paddock-level satellite time series analysis of
|
|
402
|
+
> agroecosystem dynamics*. EarthArXiv.
|
|
403
|
+
> <https://doi.org/10.31223/X5821Z>
|
|
404
|
+
|
|
405
|
+
Machine-readable citation metadata is provided in
|
|
406
|
+
[`CITATION.cff`](CITATION.cff).
|
|
407
|
+
|
|
408
|
+
Please also cite the upstream datasets and methods relevant to your analysis.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## License and attribution
|
|
413
|
+
|
|
414
|
+
PaddockTS is **MIT-licensed** — see [LICENSE](LICENSE).
|
|
415
|
+
|
|
416
|
+
### Vendored code
|
|
417
|
+
|
|
418
|
+
Third-party code shipped inside the package; see
|
|
419
|
+
[`PaddockTS/LICENSES/`](PaddockTS/LICENSES/) for full license texts:
|
|
420
|
+
|
|
421
|
+
- [`fractionalcover3`](https://gitlab.com/jrsrp/themes/cover/fractionalcover3) by
|
|
422
|
+
Robert Denham — MIT. The TFLite unmixing models and the unmixing
|
|
423
|
+
routine in `PaddockTS.FractionalCover._unmix` are adapted from this
|
|
424
|
+
work.
|
|
425
|
+
- [`phenolopy`](https://github.com/lewistrotter/phenolopy) by
|
|
426
|
+
Lewis Trotter — Apache 2.0. Vendored verbatim under
|
|
427
|
+
`PaddockTS.Phenology._phenolopy` (with minor NumPy 2.0 compatibility
|
|
428
|
+
fixes documented in the file header) and used through
|
|
429
|
+
`PaddockTS.Phenology.estimate_phenology`.
|
|
430
|
+
- [`DAESIM_preprocess`](https://github.com/ChristopherBradley/DAESIM_preprocess) by
|
|
431
|
+
Christopher Bradley — MIT. Environmental data harvesting functions
|
|
432
|
+
adapted in the pyozwald / pysilo / pyslga / pycopdem stores for
|
|
433
|
+
downloading and processing climate, vegetation, soil, and
|
|
434
|
+
topographic datasets.
|
|
435
|
+
|
|
436
|
+
### Key runtime dependencies
|
|
437
|
+
|
|
438
|
+
Installed as regular dependencies, not vendored — please cite if
|
|
439
|
+
relevant to your work:
|
|
440
|
+
|
|
441
|
+
- [`segment-geospatial`](https://samgeo.gishub.org/) (`samgeo`) by
|
|
442
|
+
Qiusheng Wu — MIT. Wraps Segment Anything for geospatial use; drives
|
|
443
|
+
the paddock segmentation stage. Cite:
|
|
444
|
+
[Wu & Osco (2023), J. Open Source Software](https://joss.theoj.org/papers/10.21105/joss.05663).
|
|
445
|
+
- [`Segment Anything Model (SAM)`](https://arxiv.org/abs/2304.02643) by
|
|
446
|
+
Meta AI Research — Apache 2.0. The underlying segmentation model.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Contributing & support
|
|
451
|
+
|
|
452
|
+
- **Bug reports / feature requests:**
|
|
453
|
+
[GitHub Issues](https://github.com/johnburley3000/paddocktimeseries/issues)
|
|
454
|
+
- **Documentation:**
|
|
455
|
+
<https://johnburley3000.github.io/paddocktimeseries/>
|
|
456
|
+
- **Known failure modes:** [`diagnostics.md`](diagnostics.md) (DEA STAC
|
|
457
|
+
cold-start, GDAL HTTP auth)
|
|
458
|
+
- **Maintainers:** [John T. Burley](https://github.com/johnburley3000)
|
|
459
|
+
and [Yasar Adeel Ansari](https://github.com/thestochasticman)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .compute_fractional_cover import compute_fractional_cover
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Fractional cover unmixing.
|
|
2
|
+
|
|
3
|
+
Inference logic and the bundled tflite model files in `_models/` are adapted
|
|
4
|
+
from the fractionalcover3 package by Robert Denham, distributed under the
|
|
5
|
+
MIT License. See PaddockTS/LICENSES/fractionalcover3.LICENSE.
|
|
6
|
+
"""
|
|
7
|
+
from importlib import resources
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from tensorflow import lite as tflite
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_MODELS = (
|
|
14
|
+
"fcModel_32x32x32.tflite",
|
|
15
|
+
"fcModel_64x64x64.tflite",
|
|
16
|
+
"fcModel_256x64x256.tflite",
|
|
17
|
+
"fcModel_256x128x256.tflite",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_model(n: int = 2):
|
|
22
|
+
"""Load the n-th bundled fractional cover tflite model (1-indexed, 1-4)."""
|
|
23
|
+
path = resources.files(__package__) / "_models" / _MODELS[n - 1]
|
|
24
|
+
return tflite.Interpreter(model_path=str(path))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def unmix_fractional_cover(surface_reflectance, fc_model, in_null=0, out_null=0):
|
|
28
|
+
"""Unmix a 6-band surface reflectance array into bare/green/non-green fractions.
|
|
29
|
+
|
|
30
|
+
The blue band is dropped before inference (the model was trained on the
|
|
31
|
+
remaining 5 bands). Returns a (3, nrows, ncols) float32 array.
|
|
32
|
+
"""
|
|
33
|
+
inshape = surface_reflectance[1:].shape
|
|
34
|
+
ref_data = np.reshape(surface_reflectance[1:], (inshape[0], -1)).T
|
|
35
|
+
|
|
36
|
+
input_details = fc_model.get_input_details()
|
|
37
|
+
output_details = fc_model.get_output_details()
|
|
38
|
+
fc_model.resize_tensor_input(input_details[0]["index"], ref_data.shape)
|
|
39
|
+
fc_model.allocate_tensors()
|
|
40
|
+
fc_model.set_tensor(input_details[0]["index"], ref_data.astype(np.float32))
|
|
41
|
+
fc_model.invoke()
|
|
42
|
+
fc_layers = fc_model.get_tensor(output_details[0]["index"]).T
|
|
43
|
+
output_fc = np.reshape(fc_layers, (3, inshape[1], inshape[2]))
|
|
44
|
+
output_fc[output_fc == in_null] = out_null
|
|
45
|
+
return output_fc
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Cache-validity check for the computed fractional-cover zarr.
|
|
2
|
+
|
|
3
|
+
A valid cache requires both the zarr directory and a ``_SUCCESS`` marker
|
|
4
|
+
file *inside* it (written by :func:`compute_fractional_cover` only after
|
|
5
|
+
the zarr write completes). Without the marker the zarr is treated as a
|
|
6
|
+
partial / interrupted write and recomputed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from os.path import exists
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def check_if_valid_fractional_cover_exists(fractional_cover_path: str) -> bool:
|
|
13
|
+
"""Return True iff a successfully-written fractional-cover zarr is on disk."""
|
|
14
|
+
return exists(fractional_cover_path) and exists(f'{fractional_cover_path}/_SUCCESS')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_no_zarr_no_marker():
|
|
18
|
+
import tempfile
|
|
19
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
20
|
+
return check_if_valid_fractional_cover_exists(f'{tmpdir}/missing.zarr') is False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_zarr_without_marker():
|
|
24
|
+
import tempfile, os
|
|
25
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
26
|
+
zarr_path = f'{tmpdir}/data.zarr'
|
|
27
|
+
os.makedirs(zarr_path)
|
|
28
|
+
return check_if_valid_fractional_cover_exists(zarr_path) is False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_zarr_with_marker():
|
|
32
|
+
import tempfile, os
|
|
33
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
34
|
+
zarr_path = f'{tmpdir}/data.zarr'
|
|
35
|
+
os.makedirs(zarr_path)
|
|
36
|
+
open(f'{zarr_path}/_SUCCESS', 'w').close()
|
|
37
|
+
return check_if_valid_fractional_cover_exists(zarr_path) is True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test():
|
|
41
|
+
return all([
|
|
42
|
+
test_no_zarr_no_marker(),
|
|
43
|
+
test_zarr_without_marker(),
|
|
44
|
+
test_zarr_with_marker(),
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == '__main__':
|
|
49
|
+
print(test())
|