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.
Files changed (66) hide show
  1. paddocktimeseries-0.8.0/LICENSE +21 -0
  2. paddocktimeseries-0.8.0/PKG-INFO +459 -0
  3. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/__init__.py +1 -0
  4. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/__init__.py +0 -0
  5. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_256x128x256.tflite +0 -0
  6. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_256x64x256.tflite +0 -0
  7. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_32x32x32.tflite +0 -0
  8. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_models/fcModel_64x64x64.tflite +0 -0
  9. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/_unmix.py +45 -0
  10. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/check_if_valid_fractional_cover_exists.py +49 -0
  11. paddocktimeseries-0.8.0/PaddockTS/FractionalCover/compute_fractional_cover.py +208 -0
  12. paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/__init__.py +0 -0
  13. paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/_presegment.py +130 -0
  14. paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/check_if_valid_paddocks_exists.py +60 -0
  15. paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/check_if_valid_preseg_exists.py +51 -0
  16. paddocktimeseries-0.8.0/PaddockTS/PaddockSegmentation/get_paddocks.py +232 -0
  17. paddocktimeseries-0.8.0/PaddockTS/Phenology/__init__.py +0 -0
  18. paddocktimeseries-0.8.0/PaddockTS/Phenology/_phenolopy.py +2525 -0
  19. paddocktimeseries-0.8.0/PaddockTS/Phenology/estimate_phenology.py +151 -0
  20. paddocktimeseries-0.8.0/PaddockTS/Phenology/make_paddock_time_series.py +233 -0
  21. paddocktimeseries-0.8.0/PaddockTS/Phenology/make_smoothed_paddock_time_series.py +157 -0
  22. paddocktimeseries-0.8.0/PaddockTS/Phenology/make_yearly_paddock_time_series.py +103 -0
  23. paddocktimeseries-0.8.0/PaddockTS/Plotting/__init__.py +24 -0
  24. paddocktimeseries-0.8.0/PaddockTS/Plotting/calendar_plot.py +523 -0
  25. paddocktimeseries-0.8.0/PaddockTS/Plotting/fractional_cover_paddocks_video.py +157 -0
  26. paddocktimeseries-0.8.0/PaddockTS/Plotting/fractional_cover_video.py +117 -0
  27. paddocktimeseries-0.8.0/PaddockTS/Plotting/make_pdf.py +230 -0
  28. paddocktimeseries-0.8.0/PaddockTS/Plotting/ozwald_plot.py +138 -0
  29. paddocktimeseries-0.8.0/PaddockTS/Plotting/phenology_plot.py +200 -0
  30. paddocktimeseries-0.8.0/PaddockTS/Plotting/sentinel2_paddocks_video.py +141 -0
  31. paddocktimeseries-0.8.0/PaddockTS/Plotting/sentinel2_video.py +107 -0
  32. paddocktimeseries-0.8.0/PaddockTS/Plotting/silo_plot.py +95 -0
  33. paddocktimeseries-0.8.0/PaddockTS/Plotting/terrain_derivatives.py +213 -0
  34. paddocktimeseries-0.8.0/PaddockTS/Plotting/terrain_tiles_plot.py +207 -0
  35. paddocktimeseries-0.8.0/PaddockTS/__init__.py +0 -0
  36. paddocktimeseries-0.8.0/PaddockTS/check_status.py +15 -0
  37. paddocktimeseries-0.8.0/PaddockTS/daesim_forcing.py +146 -0
  38. paddocktimeseries-0.8.0/PaddockTS/get_outputs.py +688 -0
  39. paddocktimeseries-0.8.0/PaddockTS/paths.py +110 -0
  40. paddocktimeseries-0.8.0/PaddockTS/run_environmental.py +180 -0
  41. paddocktimeseries-0.8.0/PaddockTS/status.py +12 -0
  42. paddocktimeseries-0.8.0/PaddockTS/utils.py +79 -0
  43. paddocktimeseries-0.8.0/PaddockTS/viewer/__init__.py +13 -0
  44. paddocktimeseries-0.8.0/PaddockTS/viewer/__main__.py +18 -0
  45. paddocktimeseries-0.8.0/PaddockTS/viewer/app.py +149 -0
  46. paddocktimeseries-0.8.0/PaddockTS/viewer/scan.py +196 -0
  47. paddocktimeseries-0.8.0/PaddockTS/viewer/serve.py +80 -0
  48. paddocktimeseries-0.8.0/README.md +423 -0
  49. paddocktimeseries-0.8.0/paddocktimeseries.egg-info/PKG-INFO +459 -0
  50. paddocktimeseries-0.8.0/paddocktimeseries.egg-info/SOURCES.txt +64 -0
  51. paddocktimeseries-0.8.0/paddocktimeseries.egg-info/dependency_links.txt +1 -0
  52. paddocktimeseries-0.8.0/paddocktimeseries.egg-info/requires.txt +12 -0
  53. paddocktimeseries-0.8.0/paddocktimeseries.egg-info/top_level.txt +1 -0
  54. paddocktimeseries-0.8.0/pyproject.toml +86 -0
  55. paddocktimeseries-0.8.0/setup.cfg +4 -0
  56. paddocktimeseries-0.8.0/test/test_mode1.py +13 -0
  57. paddocktimeseries-0.8.0/test/test_mode2.py +19 -0
  58. paddocktimeseries-0.8.0/test/test_mode2_milgadara.py +21 -0
  59. paddocktimeseries-0.8.0/tests/test_cache_contract.py +51 -0
  60. paddocktimeseries-0.8.0/tests/test_fractional_cover.py +59 -0
  61. paddocktimeseries-0.8.0/tests/test_indices.py +65 -0
  62. paddocktimeseries-0.8.0/tests/test_paddock_time_series.py +87 -0
  63. paddocktimeseries-0.8.0/tests/test_paths_and_troi.py +57 -0
  64. paddocktimeseries-0.8.0/tests/test_phenology.py +52 -0
  65. paddocktimeseries-0.8.0/tests/test_smoothing.py +54 -0
  66. 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
+ [![Tests](https://github.com/johnburley3000/paddocktimeseries/actions/workflows/tests.yml/badge.svg)](https://github.com/johnburley3000/paddocktimeseries/actions/workflows/tests.yml)
49
+ [![Docs](https://img.shields.io/badge/docs-johnburley3000.github.io-2ea44f)](https://johnburley3000.github.io/paddocktimeseries/)
50
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
51
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
52
+ [![EarthArXiv preprint](https://img.shields.io/badge/EarthArXiv-10.31223%2FX5821Z-b31b1b)](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
@@ -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())