tessera-eval 1.11.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 (54) hide show
  1. tessera_eval-1.11.0/LICENSE +21 -0
  2. tessera_eval-1.11.0/PKG-INFO +278 -0
  3. tessera_eval-1.11.0/README.md +238 -0
  4. tessera_eval-1.11.0/pyproject.toml +70 -0
  5. tessera_eval-1.11.0/setup.cfg +4 -0
  6. tessera_eval-1.11.0/tessera_eval/__init__.py +44 -0
  7. tessera_eval-1.11.0/tessera_eval/classify.py +315 -0
  8. tessera_eval-1.11.0/tessera_eval/cli.py +766 -0
  9. tessera_eval-1.11.0/tessera_eval/data.py +503 -0
  10. tessera_eval-1.11.0/tessera_eval/evaluate.py +1118 -0
  11. tessera_eval-1.11.0/tessera_eval/rasterize.py +187 -0
  12. tessera_eval-1.11.0/tessera_eval/server.py +3598 -0
  13. tessera_eval-1.11.0/tessera_eval/unet.py +720 -0
  14. tessera_eval-1.11.0/tessera_eval.egg-info/PKG-INFO +278 -0
  15. tessera_eval-1.11.0/tessera_eval.egg-info/SOURCES.txt +52 -0
  16. tessera_eval-1.11.0/tessera_eval.egg-info/dependency_links.txt +1 -0
  17. tessera_eval-1.11.0/tessera_eval.egg-info/entry_points.txt +3 -0
  18. tessera_eval-1.11.0/tessera_eval.egg-info/requires.txt +34 -0
  19. tessera_eval-1.11.0/tessera_eval.egg-info/top_level.txt +1 -0
  20. tessera_eval-1.11.0/tests/test_align_raster_sentinels.py +123 -0
  21. tessera_eval-1.11.0/tests/test_cli.py +465 -0
  22. tessera_eval-1.11.0/tests/test_compute_proxy.py +118 -0
  23. tessera_eval-1.11.0/tests/test_create_map_compression.py +78 -0
  24. tessera_eval-1.11.0/tests/test_create_map_multi_tile_crs.py +219 -0
  25. tessera_eval-1.11.0/tests/test_create_map_preview.py +97 -0
  26. tessera_eval-1.11.0/tests/test_create_map_regression.py +281 -0
  27. tessera_eval-1.11.0/tests/test_create_map_unique_download_url.py +100 -0
  28. tessera_eval-1.11.0/tests/test_create_map_year.py +148 -0
  29. tessera_eval-1.11.0/tests/test_evaluate_wrapper.py +48 -0
  30. tessera_eval-1.11.0/tests/test_extract_tile_patches_memory.py +91 -0
  31. tessera_eval-1.11.0/tests/test_extract_tile_patches_regression.py +152 -0
  32. tessera_eval-1.11.0/tests/test_fit_heartbeat.py +137 -0
  33. tessera_eval-1.11.0/tests/test_kfold.py +390 -0
  34. tessera_eval-1.11.0/tests/test_list_shapefiles.py +49 -0
  35. tessera_eval-1.11.0/tests/test_load_raster.py +99 -0
  36. tessera_eval-1.11.0/tests/test_map_task_type_persisted_early.py +253 -0
  37. tessera_eval-1.11.0/tests/test_predict_raster_clamp.py +90 -0
  38. tessera_eval-1.11.0/tests/test_rasterize_encoder.py +105 -0
  39. tessera_eval-1.11.0/tests/test_run_large_area_kfold.py +233 -0
  40. tessera_eval-1.11.0/tests/test_run_large_area_regression.py +194 -0
  41. tessera_eval-1.11.0/tests/test_run_large_area_test_file.py +187 -0
  42. tessera_eval-1.11.0/tests/test_run_learning_curve_regression.py +339 -0
  43. tessera_eval-1.11.0/tests/test_sample_points_within_budget.py +78 -0
  44. tessera_eval-1.11.0/tests/test_seed_threading.py +137 -0
  45. tessera_eval-1.11.0/tests/test_server_zarr.py +89 -0
  46. tessera_eval-1.11.0/tests/test_spatial_split.py +165 -0
  47. tessera_eval-1.11.0/tests/test_start_event_task_on_cache_hit.py +111 -0
  48. tessera_eval-1.11.0/tests/test_tile_cache_reload.py +151 -0
  49. tessera_eval-1.11.0/tests/test_tile_disk_cache.py +74 -0
  50. tessera_eval-1.11.0/tests/test_train_models_regression.py +122 -0
  51. tessera_eval-1.11.0/tests/test_train_test_year_split.py +173 -0
  52. tessera_eval-1.11.0/tests/test_unet_regression.py +132 -0
  53. tessera_eval-1.11.0/tests/test_vq_loader.py +124 -0
  54. tessera_eval-1.11.0/tests/test_xgboost_label_gap.py +145 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 S. Keshav 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,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: tessera-eval
3
+ Version: 1.11.0
4
+ Summary: Evaluate habitat classifiers on Tessera satellite embeddings
5
+ Author: S. Keshav
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ucam-eo/tessera-eval
8
+ Project-URL: Repository, https://github.com/ucam-eo/tessera-eval
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy>=1.24
13
+ Requires-Dist: geopandas>=0.14
14
+ Requires-Dist: rasterio>=1.3
15
+ Requires-Dist: scikit-learn>=1.3
16
+ Requires-Dist: affine>=2.4
17
+ Requires-Dist: typer>=0.12
18
+ Provides-Extra: geotessera
19
+ Requires-Dist: geotessera>=0.10.1; extra == "geotessera"
20
+ Provides-Extra: xgboost
21
+ Requires-Dist: xgboost>=1.7; extra == "xgboost"
22
+ Provides-Extra: torch
23
+ Requires-Dist: torch>=2.0; extra == "torch"
24
+ Provides-Extra: plot
25
+ Requires-Dist: matplotlib>=3.5; extra == "plot"
26
+ Provides-Extra: server
27
+ Requires-Dist: flask>=3.0; extra == "server"
28
+ Requires-Dist: waitress>=2.1; extra == "server"
29
+ Requires-Dist: requests>=2.28; extra == "server"
30
+ Requires-Dist: geotessera>=0.10.1; extra == "server"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0; extra == "dev"
33
+ Requires-Dist: ruff<0.17,>=0.16; extra == "dev"
34
+ Requires-Dist: mypy>=1.8; extra == "dev"
35
+ Provides-Extra: all
36
+ Requires-Dist: geotessera>=0.10.1; extra == "all"
37
+ Requires-Dist: xgboost>=1.7; extra == "all"
38
+ Requires-Dist: matplotlib>=3.5; extra == "all"
39
+ Dynamic: license-file
40
+
41
+ # tessera-eval
42
+
43
+ Evaluate land-cover / habitat classifiers on **Tessera** satellite embeddings.
44
+
45
+ [Tessera](https://github.com/ucam-eo/tessera) is a geospatial foundation model
46
+ that produces a **128-dimensional embedding for every ~10 m pixel** of the Earth's
47
+ surface, per year. `tessera-eval` is a small, framework-independent Python library
48
+ for the question that immediately follows: *how well can you map a category of
49
+ interest (a habitat, crop, or land-cover class) from those embeddings, given some
50
+ labelled polygons?*
51
+
52
+ It handles the unglamorous-but-fiddly parts end to end:
53
+
54
+ - **Loading + dequantizing** embeddings from the formats Tessera tooling emits
55
+ (GeoTessera `int8 × per-pixel-scale` tiles, and TEE per-dim `uint8` vector
56
+ directories).
57
+ - **Rasterizing** a labelled shapefile/GeoJSON onto the embedding pixel grid with
58
+ stable class IDs.
59
+ - **Training + scoring** a panel of classifiers/regressors (k-NN, random forest,
60
+ MLP, spatial MLP, optional XGBoost, optional U-Net) with **learning curves**,
61
+ **k-fold cross-validation**, and **spatial hold-out** splits.
62
+ - An optional **local compute server** (`tee-compute`) so you can run the ML on
63
+ your own machine while pulling tiles/UI from a hosted service.
64
+
65
+ > The library core (`data`, `rasterize`, `classify`, `evaluate`) is pure NumPy /
66
+ > scikit-learn / rasterio and has no web-framework or hosting dependency. The
67
+ > compute server reads zarr embeddings through geotessera's own `GeoTesseraZarr`
68
+ > interface, falling back to NPY tiles when the store lacks coverage.
69
+
70
+ ## Install
71
+
72
+ ```bash
73
+ pip install tessera-eval # core library
74
+ pip install "tessera-eval[geotessera]" # + tile access (load_embeddings_for_shapefile)
75
+ pip install "tessera-eval[server]" # + the tee-compute local server
76
+ pip install "tessera-eval[all]" # geotessera + xgboost + matplotlib
77
+ ```
78
+
79
+ Optional extras: `geotessera` (fetch tiles), `xgboost` (gradient-boosted models),
80
+ `torch` (the U-Net), `plot` (matplotlib), `server` (Flask compute server),
81
+ `dev` (pytest/ruff/mypy). Python ≥ 3.10.
82
+
83
+ ## Quickstart
84
+
85
+ Cross-validate a classifier on labelled polygons, pulling embeddings tile-by-tile:
86
+
87
+ ```python
88
+ import geopandas as gpd
89
+ from geotessera import GeoTessera
90
+ from tessera_eval import load_embeddings_for_shapefile, run_kfold_cv
91
+
92
+ # 1. Labelled polygons (any CRS — reprojected internally) with a class column.
93
+ gdf = gpd.read_file("habitats.geojson")
94
+
95
+ # 2. Pull a 128-d embedding for every pixel under the polygons (memory-bounded:
96
+ # one GeoTessera tile at a time, keeping only labelled pixels).
97
+ gt = GeoTessera()
98
+ vectors, labels, class_names, stats = load_embeddings_for_shapefile(
99
+ gdf, field="habitat", year=2024, gt_instance=gt
100
+ )
101
+ print(f"{stats['total_pixels']:,} labelled pixels over {stats['n_classes']} classes")
102
+
103
+ # 3. 5-fold cross-validation of a random forest and a nearest-neighbour baseline.
104
+ for event in run_kfold_cv(vectors, labels, ["rf", "nn"], k=5):
105
+ if event["type"] == "aggregate":
106
+ for name, m in event["models"].items():
107
+ print(f"{name:>4}: macro-F1 {m['mean_f1']:.3f} ± {m['std_f1']:.3f}")
108
+ ```
109
+
110
+ Already have a TEE vector directory on disk? Load it directly:
111
+
112
+ ```python
113
+ from tessera_eval import load_tee_vectors
114
+
115
+ vectors, coords, metadata = load_tee_vectors("/path/to/vectors/aoi/2024")
116
+ # vectors: float32 (N, 128); coords: int32 (N, 2) pixel (x, y); metadata: dict
117
+ ```
118
+
119
+ See the [tutorial](docs/tutorial.md) for the full workflow (labels → learning
120
+ curve → confusion matrix → interpretation).
121
+
122
+ ## Command-line interface
123
+
124
+ The workflow covered in the [tutorial](docs/tutorial.md) can also be run through the command line.
125
+
126
+ First, install the `geotessera` package needed for the `load` step below:
127
+
128
+ ```
129
+ pip install -e ".[geotessera]"
130
+ ```
131
+
132
+ Optional installation for using xgboost:
133
+
134
+ ```
135
+ pip install -e ".[xgboost]"
136
+ ```
137
+
138
+ Download the Tessera embeddings for your labelled ground truth, and save the result to a file (vectors.npz by default, change the name with argument `--output`). `--data` accepts either a shapefile/GeoJSON of labelled polygons or a GeoTIFF of an already-rasterized reference layer.
139
+
140
+ For a shapefile/GeoJSON, `--field` is the column holding the class or target values (e.g. `habitat`):
141
+
142
+ ```bash
143
+ tessera-eval load --data /path/to/habitats.geojson --field habitat --year 2024
144
+ ```
145
+
146
+ For a GeoTIFF, `--bbox` is required, in EPSG:4326 (longitude,latitude in degrees), since a raster has no natural area boundary the way labelled polygons do. `--nodata` marks any missing-value codes:
147
+
148
+ ```bash
149
+ tessera-eval load --data site_type.tif --bbox 27.1,67.75,27.2,67.85 --year 2024 --nodata 32766,32767
150
+ ```
151
+
152
+ `kfold` and `learning-curve` reuse the cached `vectors.npz` automatically. Pass `--vectors <path>` to use a different cached file instead.
153
+
154
+ Run k-fold cross-validation and print accuracy per model.
155
+
156
+ ```bash
157
+ tessera-eval kfold --models rf,nn,mlp # for classification
158
+ tessera-eval kfold --models rf_reg,nn_reg # for regression
159
+ ```
160
+
161
+ Optional arguments:
162
+
163
+ ```bash
164
+ # --k: number of cross-validation folds (default: 5)
165
+ # --seed: random seed for reproducible fold splits (default: 42)
166
+ # --confusion-matrix: also print the full confusion matrix (raw counts), on top of the summary shown by default (for classification only)
167
+ # --vectors: path to a different cached .npz (default: vectors.npz from `load`)
168
+ # --max-samples: cap the training set size per fold (random, not stratified by class) -
169
+ # usually needed for raster-derived data, which labels every pixel, not a hand-picked subset
170
+ tessera-eval kfold --models rf --k 10 --seed 1 --confusion-matrix --max-samples 50000
171
+ ```
172
+
173
+ Investigate how accuracy changes using different fractions of training labels (currently only for classification task).
174
+ ```bash
175
+ tessera-eval learning-curve --models rf --training-pcts 1,5,10,30,50,80 --repeats 5 # --training-pcts: % of labels per step (default: 1,5,10,30,50,80); --repeats: random repeats per step (default: 5)
176
+ ```
177
+
178
+
179
+
180
+ Since neighbouring pixels are usually very similar, a random split can overstate how accurate the model really is (see [tutorial](docs/tutorial.md)). For a more reliable estimate, train on one geographic half of your area and test on the other, splitting by longitude:
181
+
182
+ ```bash
183
+ tessera-eval learning-curve --models rf --spatial-holdout
184
+ ```
185
+
186
+ You can also choose exactly which region to hold out, by passing a bounding box (for a shapefile/GeoJSON, in the same CRS as your data; for a GeoTIFF, always EPSG:4326):
187
+
188
+
189
+ ```bash
190
+ tessera-eval learning-curve --models rf --spatial-holdout --test-bbox 27.16,67.77,27.23,67.82
191
+ ```
192
+
193
+ You can also use a completely separate file as the test set - a different region, a different year, or both. If you are evaluating on a different year, pass `--test-year` along with `--test-data`.
194
+
195
+ ```bash
196
+ tessera-eval learning-curve --models rf --test-data /path/to/other_region.geojson --test-year 2023
197
+ ```
198
+
199
+ For a GeoTIFF, `--test-bbox` is also required, defining the test region within that file:
200
+
201
+ ```bash
202
+ tessera-eval learning-curve --test-data site_type_2023.tif --test-bbox 27.1,67.75,27.2,67.85 --test-year 2023 --models rf
203
+ ```
204
+
205
+ This reuses the cached `vectors.npz` as the training data by default. To use a different training area or year instead, pass `--data`/`--bbox`/`--year` explicitly:
206
+
207
+ ```bash
208
+ tessera-eval learning-curve --data site_type_2019.tif --bbox 27.1,67.75,27.2,67.85 --year 2019 --test-data site_type_2023.tif --test-bbox 27.1,67.75,27.2,67.85 --test-year 2023 --models rf
209
+ ```
210
+
211
+ Run any command with `--help` for a list of all possible arguments.
212
+
213
+
214
+
215
+ ## Documentation
216
+
217
+ - **[Data formats](docs/data-formats.md)** — the Tessera embedding formats this
218
+ library reads and the exact dequantization maths. *Start here if you're wiring in
219
+ your own data.*
220
+ - **[API reference](docs/api-reference.md)** — every public function, with array
221
+ shapes and dtypes.
222
+ - **[Tutorial](docs/tutorial.md)** — an end-to-end worked example.
223
+ - **[Compute server](docs/compute-server.md)** — running `tee-compute` (local ML,
224
+ hosted data).
225
+
226
+ ## What's in the box
227
+
228
+ | Module | Purpose |
229
+ |---|---|
230
+ | `tessera_eval.data` | Load + dequantize embeddings (`load_tee_vectors`, `dequantize_int8`, `dequantize_uint8`, `load_embeddings_for_shapefile`, `load_embeddings_for_shapefile_vq`, `load_embeddings_for_raster`). |
231
+ | `tessera_eval.rasterize` | Burn shapefile polygons onto a pixel grid with stable, 1-based class IDs|
232
+ | `tessera_eval.classify` | Classifier/regressor factory + spatial neighbourhood features. |
233
+ | `tessera_eval.evaluate` | Learning curves, k-fold CV, spatial split, metrics, field-type detection. |
234
+ | `tessera_eval.unet` | Optional PyTorch U-Net for sparse-label tile segmentation. |
235
+ | `tessera_eval.server` | `tee-compute`: local Flask compute server, proxies data/UI to a hosted TEE. |
236
+ | `tessera_eval.cli` | `tessera-eval` command-line interface: `load`, `kfold`, `learning-curve`. |
237
+
238
+ The compute server reads zarr embeddings directly through geotessera's
239
+ `GeoTesseraZarr` interface as a fast path, probing coverage first and falling
240
+ back to NPY tiles whenever the store is unavailable or lacks the requested
241
+ region or year.
242
+
243
+ Available models: `nn`, `rf`, `mlp`, `spatial_mlp`, `spatial_mlp_5x5`, `xgboost`
244
+ (if installed), `unet` (if torch installed); regressors `nn_reg`, `rf_reg`,
245
+ `mlp_reg`, `xgboost_reg`. See `available_classifiers()` / `available_regressors()`.
246
+
247
+ ## Design notes
248
+
249
+ - **Class imbalance is expected and fine.** Macro-F1 is reported alongside
250
+ weighted-F1 precisely so rare classes are visible.
251
+ - **Determinism.** Estimators use `random_state=42`; evaluation takes an explicit
252
+ `seed`. Same inputs → same numbers.
253
+ - **Spatial leakage.** For honest accuracy on contiguous habitats, prefer the
254
+ spatial hold-out (`run_learning_curve(..., test_vectors=, test_labels=)`) over a
255
+ random pixel split — neighbouring pixels are highly autocorrelated.
256
+ - **Memory.** `load_embeddings_for_shapefile` streams one tile at a time and keeps
257
+ only labelled pixels, so county/country-scale shapefiles are tractable.
258
+
259
+ ## Development
260
+
261
+ ```bash
262
+ git clone https://github.com/ucam-eo/tessera-eval && cd tessera-eval
263
+ python -m venv .venv && source .venv/bin/activate
264
+ pip install -e ".[server,dev]"
265
+ ruff check . && ruff format --check . && pytest
266
+ ```
267
+
268
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
269
+
270
+ ## Citing
271
+
272
+ If this is useful in academic work, please cite the Tessera model and link back to
273
+ this repository. (A `CITATION.cff` will be added alongside the Tessera paper
274
+ reference.)
275
+
276
+ ## License
277
+
278
+ [MIT](LICENSE).
@@ -0,0 +1,238 @@
1
+ # tessera-eval
2
+
3
+ Evaluate land-cover / habitat classifiers on **Tessera** satellite embeddings.
4
+
5
+ [Tessera](https://github.com/ucam-eo/tessera) is a geospatial foundation model
6
+ that produces a **128-dimensional embedding for every ~10 m pixel** of the Earth's
7
+ surface, per year. `tessera-eval` is a small, framework-independent Python library
8
+ for the question that immediately follows: *how well can you map a category of
9
+ interest (a habitat, crop, or land-cover class) from those embeddings, given some
10
+ labelled polygons?*
11
+
12
+ It handles the unglamorous-but-fiddly parts end to end:
13
+
14
+ - **Loading + dequantizing** embeddings from the formats Tessera tooling emits
15
+ (GeoTessera `int8 × per-pixel-scale` tiles, and TEE per-dim `uint8` vector
16
+ directories).
17
+ - **Rasterizing** a labelled shapefile/GeoJSON onto the embedding pixel grid with
18
+ stable class IDs.
19
+ - **Training + scoring** a panel of classifiers/regressors (k-NN, random forest,
20
+ MLP, spatial MLP, optional XGBoost, optional U-Net) with **learning curves**,
21
+ **k-fold cross-validation**, and **spatial hold-out** splits.
22
+ - An optional **local compute server** (`tee-compute`) so you can run the ML on
23
+ your own machine while pulling tiles/UI from a hosted service.
24
+
25
+ > The library core (`data`, `rasterize`, `classify`, `evaluate`) is pure NumPy /
26
+ > scikit-learn / rasterio and has no web-framework or hosting dependency. The
27
+ > compute server reads zarr embeddings through geotessera's own `GeoTesseraZarr`
28
+ > interface, falling back to NPY tiles when the store lacks coverage.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install tessera-eval # core library
34
+ pip install "tessera-eval[geotessera]" # + tile access (load_embeddings_for_shapefile)
35
+ pip install "tessera-eval[server]" # + the tee-compute local server
36
+ pip install "tessera-eval[all]" # geotessera + xgboost + matplotlib
37
+ ```
38
+
39
+ Optional extras: `geotessera` (fetch tiles), `xgboost` (gradient-boosted models),
40
+ `torch` (the U-Net), `plot` (matplotlib), `server` (Flask compute server),
41
+ `dev` (pytest/ruff/mypy). Python ≥ 3.10.
42
+
43
+ ## Quickstart
44
+
45
+ Cross-validate a classifier on labelled polygons, pulling embeddings tile-by-tile:
46
+
47
+ ```python
48
+ import geopandas as gpd
49
+ from geotessera import GeoTessera
50
+ from tessera_eval import load_embeddings_for_shapefile, run_kfold_cv
51
+
52
+ # 1. Labelled polygons (any CRS — reprojected internally) with a class column.
53
+ gdf = gpd.read_file("habitats.geojson")
54
+
55
+ # 2. Pull a 128-d embedding for every pixel under the polygons (memory-bounded:
56
+ # one GeoTessera tile at a time, keeping only labelled pixels).
57
+ gt = GeoTessera()
58
+ vectors, labels, class_names, stats = load_embeddings_for_shapefile(
59
+ gdf, field="habitat", year=2024, gt_instance=gt
60
+ )
61
+ print(f"{stats['total_pixels']:,} labelled pixels over {stats['n_classes']} classes")
62
+
63
+ # 3. 5-fold cross-validation of a random forest and a nearest-neighbour baseline.
64
+ for event in run_kfold_cv(vectors, labels, ["rf", "nn"], k=5):
65
+ if event["type"] == "aggregate":
66
+ for name, m in event["models"].items():
67
+ print(f"{name:>4}: macro-F1 {m['mean_f1']:.3f} ± {m['std_f1']:.3f}")
68
+ ```
69
+
70
+ Already have a TEE vector directory on disk? Load it directly:
71
+
72
+ ```python
73
+ from tessera_eval import load_tee_vectors
74
+
75
+ vectors, coords, metadata = load_tee_vectors("/path/to/vectors/aoi/2024")
76
+ # vectors: float32 (N, 128); coords: int32 (N, 2) pixel (x, y); metadata: dict
77
+ ```
78
+
79
+ See the [tutorial](docs/tutorial.md) for the full workflow (labels → learning
80
+ curve → confusion matrix → interpretation).
81
+
82
+ ## Command-line interface
83
+
84
+ The workflow covered in the [tutorial](docs/tutorial.md) can also be run through the command line.
85
+
86
+ First, install the `geotessera` package needed for the `load` step below:
87
+
88
+ ```
89
+ pip install -e ".[geotessera]"
90
+ ```
91
+
92
+ Optional installation for using xgboost:
93
+
94
+ ```
95
+ pip install -e ".[xgboost]"
96
+ ```
97
+
98
+ Download the Tessera embeddings for your labelled ground truth, and save the result to a file (vectors.npz by default, change the name with argument `--output`). `--data` accepts either a shapefile/GeoJSON of labelled polygons or a GeoTIFF of an already-rasterized reference layer.
99
+
100
+ For a shapefile/GeoJSON, `--field` is the column holding the class or target values (e.g. `habitat`):
101
+
102
+ ```bash
103
+ tessera-eval load --data /path/to/habitats.geojson --field habitat --year 2024
104
+ ```
105
+
106
+ For a GeoTIFF, `--bbox` is required, in EPSG:4326 (longitude,latitude in degrees), since a raster has no natural area boundary the way labelled polygons do. `--nodata` marks any missing-value codes:
107
+
108
+ ```bash
109
+ tessera-eval load --data site_type.tif --bbox 27.1,67.75,27.2,67.85 --year 2024 --nodata 32766,32767
110
+ ```
111
+
112
+ `kfold` and `learning-curve` reuse the cached `vectors.npz` automatically. Pass `--vectors <path>` to use a different cached file instead.
113
+
114
+ Run k-fold cross-validation and print accuracy per model.
115
+
116
+ ```bash
117
+ tessera-eval kfold --models rf,nn,mlp # for classification
118
+ tessera-eval kfold --models rf_reg,nn_reg # for regression
119
+ ```
120
+
121
+ Optional arguments:
122
+
123
+ ```bash
124
+ # --k: number of cross-validation folds (default: 5)
125
+ # --seed: random seed for reproducible fold splits (default: 42)
126
+ # --confusion-matrix: also print the full confusion matrix (raw counts), on top of the summary shown by default (for classification only)
127
+ # --vectors: path to a different cached .npz (default: vectors.npz from `load`)
128
+ # --max-samples: cap the training set size per fold (random, not stratified by class) -
129
+ # usually needed for raster-derived data, which labels every pixel, not a hand-picked subset
130
+ tessera-eval kfold --models rf --k 10 --seed 1 --confusion-matrix --max-samples 50000
131
+ ```
132
+
133
+ Investigate how accuracy changes using different fractions of training labels (currently only for classification task).
134
+ ```bash
135
+ tessera-eval learning-curve --models rf --training-pcts 1,5,10,30,50,80 --repeats 5 # --training-pcts: % of labels per step (default: 1,5,10,30,50,80); --repeats: random repeats per step (default: 5)
136
+ ```
137
+
138
+
139
+
140
+ Since neighbouring pixels are usually very similar, a random split can overstate how accurate the model really is (see [tutorial](docs/tutorial.md)). For a more reliable estimate, train on one geographic half of your area and test on the other, splitting by longitude:
141
+
142
+ ```bash
143
+ tessera-eval learning-curve --models rf --spatial-holdout
144
+ ```
145
+
146
+ You can also choose exactly which region to hold out, by passing a bounding box (for a shapefile/GeoJSON, in the same CRS as your data; for a GeoTIFF, always EPSG:4326):
147
+
148
+
149
+ ```bash
150
+ tessera-eval learning-curve --models rf --spatial-holdout --test-bbox 27.16,67.77,27.23,67.82
151
+ ```
152
+
153
+ You can also use a completely separate file as the test set - a different region, a different year, or both. If you are evaluating on a different year, pass `--test-year` along with `--test-data`.
154
+
155
+ ```bash
156
+ tessera-eval learning-curve --models rf --test-data /path/to/other_region.geojson --test-year 2023
157
+ ```
158
+
159
+ For a GeoTIFF, `--test-bbox` is also required, defining the test region within that file:
160
+
161
+ ```bash
162
+ tessera-eval learning-curve --test-data site_type_2023.tif --test-bbox 27.1,67.75,27.2,67.85 --test-year 2023 --models rf
163
+ ```
164
+
165
+ This reuses the cached `vectors.npz` as the training data by default. To use a different training area or year instead, pass `--data`/`--bbox`/`--year` explicitly:
166
+
167
+ ```bash
168
+ tessera-eval learning-curve --data site_type_2019.tif --bbox 27.1,67.75,27.2,67.85 --year 2019 --test-data site_type_2023.tif --test-bbox 27.1,67.75,27.2,67.85 --test-year 2023 --models rf
169
+ ```
170
+
171
+ Run any command with `--help` for a list of all possible arguments.
172
+
173
+
174
+
175
+ ## Documentation
176
+
177
+ - **[Data formats](docs/data-formats.md)** — the Tessera embedding formats this
178
+ library reads and the exact dequantization maths. *Start here if you're wiring in
179
+ your own data.*
180
+ - **[API reference](docs/api-reference.md)** — every public function, with array
181
+ shapes and dtypes.
182
+ - **[Tutorial](docs/tutorial.md)** — an end-to-end worked example.
183
+ - **[Compute server](docs/compute-server.md)** — running `tee-compute` (local ML,
184
+ hosted data).
185
+
186
+ ## What's in the box
187
+
188
+ | Module | Purpose |
189
+ |---|---|
190
+ | `tessera_eval.data` | Load + dequantize embeddings (`load_tee_vectors`, `dequantize_int8`, `dequantize_uint8`, `load_embeddings_for_shapefile`, `load_embeddings_for_shapefile_vq`, `load_embeddings_for_raster`). |
191
+ | `tessera_eval.rasterize` | Burn shapefile polygons onto a pixel grid with stable, 1-based class IDs|
192
+ | `tessera_eval.classify` | Classifier/regressor factory + spatial neighbourhood features. |
193
+ | `tessera_eval.evaluate` | Learning curves, k-fold CV, spatial split, metrics, field-type detection. |
194
+ | `tessera_eval.unet` | Optional PyTorch U-Net for sparse-label tile segmentation. |
195
+ | `tessera_eval.server` | `tee-compute`: local Flask compute server, proxies data/UI to a hosted TEE. |
196
+ | `tessera_eval.cli` | `tessera-eval` command-line interface: `load`, `kfold`, `learning-curve`. |
197
+
198
+ The compute server reads zarr embeddings directly through geotessera's
199
+ `GeoTesseraZarr` interface as a fast path, probing coverage first and falling
200
+ back to NPY tiles whenever the store is unavailable or lacks the requested
201
+ region or year.
202
+
203
+ Available models: `nn`, `rf`, `mlp`, `spatial_mlp`, `spatial_mlp_5x5`, `xgboost`
204
+ (if installed), `unet` (if torch installed); regressors `nn_reg`, `rf_reg`,
205
+ `mlp_reg`, `xgboost_reg`. See `available_classifiers()` / `available_regressors()`.
206
+
207
+ ## Design notes
208
+
209
+ - **Class imbalance is expected and fine.** Macro-F1 is reported alongside
210
+ weighted-F1 precisely so rare classes are visible.
211
+ - **Determinism.** Estimators use `random_state=42`; evaluation takes an explicit
212
+ `seed`. Same inputs → same numbers.
213
+ - **Spatial leakage.** For honest accuracy on contiguous habitats, prefer the
214
+ spatial hold-out (`run_learning_curve(..., test_vectors=, test_labels=)`) over a
215
+ random pixel split — neighbouring pixels are highly autocorrelated.
216
+ - **Memory.** `load_embeddings_for_shapefile` streams one tile at a time and keeps
217
+ only labelled pixels, so county/country-scale shapefiles are tractable.
218
+
219
+ ## Development
220
+
221
+ ```bash
222
+ git clone https://github.com/ucam-eo/tessera-eval && cd tessera-eval
223
+ python -m venv .venv && source .venv/bin/activate
224
+ pip install -e ".[server,dev]"
225
+ ruff check . && ruff format --check . && pytest
226
+ ```
227
+
228
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
229
+
230
+ ## Citing
231
+
232
+ If this is useful in academic work, please cite the Tessera model and link back to
233
+ this repository. (A `CITATION.cff` will be added alongside the Tessera paper
234
+ reference.)
235
+
236
+ ## License
237
+
238
+ [MIT](LICENSE).
@@ -0,0 +1,70 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tessera-eval"
7
+ version = "1.11.0"
8
+ description = "Evaluate habitat classifiers on Tessera satellite embeddings"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "S. Keshav"},
14
+ ]
15
+ dependencies = [
16
+ "numpy>=1.24",
17
+ "geopandas>=0.14",
18
+ "rasterio>=1.3",
19
+ "scikit-learn>=1.3",
20
+ "affine>=2.4",
21
+ "typer>=0.12",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ geotessera = ["geotessera>=0.10.1"]
26
+ xgboost = ["xgboost>=1.7"]
27
+ torch = ["torch>=2.0"]
28
+ plot = ["matplotlib>=3.5"]
29
+ server = [
30
+ "flask>=3.0",
31
+ "waitress>=2.1",
32
+ "requests>=2.28",
33
+ "geotessera>=0.10.1",
34
+ ]
35
+ dev = ["pytest>=7.0", "ruff>=0.16,<0.17", "mypy>=1.8"]
36
+ all = ["geotessera>=0.10.1", "xgboost>=1.7", "matplotlib>=3.5"]
37
+
38
+ [project.scripts]
39
+ tee-compute = "tessera_eval.server:main"
40
+ tessera-eval = "tessera_eval.cli:main"
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/ucam-eo/tessera-eval"
44
+ Repository = "https://github.com/ucam-eo/tessera-eval"
45
+
46
+ [tool.setuptools.packages.find]
47
+ include = ["tessera_eval*"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py310"
55
+
56
+ [tool.ruff.lint]
57
+ # Conservative default set; the codebase predates linting, so keep it gentle.
58
+ select = ["E", "F", "W", "I"]
59
+ ignore = [
60
+ "E501", # long lines abound in docstrings/tables; not worth churn
61
+ "E402", # several modules deliberately order a logger/guard before imports
62
+ ]
63
+
64
+ [tool.ruff.lint.per-file-ignores]
65
+ "__init__.py" = ["F401"] # re-exports define the public API
66
+
67
+ [tool.mypy]
68
+ python_version = "3.10"
69
+ ignore_missing_imports = true
70
+ warn_unused_ignores = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ """tessera-eval: Evaluate habitat classifiers on Tessera satellite embeddings."""
2
+
3
+ # Must be set before numpy/scipy import to avoid OpenBLAS crash on >128-core machines.
4
+ # Use 1 thread per BLAS call — joblib handles higher-level parallelism in sklearn.
5
+ import os as _os
6
+
7
+ for _var in ("OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS", "OMP_NUM_THREADS"):
8
+ if _var not in _os.environ:
9
+ _os.environ[_var] = "1"
10
+
11
+ # Single source of truth: the installed package metadata (pyproject version),
12
+ # so __version__ can't drift from the release tag.
13
+ from importlib.metadata import PackageNotFoundError as _PkgNotFound
14
+ from importlib.metadata import version as _pkg_version
15
+
16
+ try:
17
+ __version__ = _pkg_version("tessera-eval")
18
+ except _PkgNotFound: # running from a source tree that isn't installed
19
+ __version__ = "0+unknown"
20
+
21
+ from tessera_eval.classify import (
22
+ available_classifiers,
23
+ available_regressors,
24
+ gather_spatial_features,
25
+ gather_spatial_features_2d,
26
+ make_classifier,
27
+ make_regressor,
28
+ )
29
+ from tessera_eval.data import (
30
+ dequantize_int8,
31
+ dequantize_uint8,
32
+ load_embeddings_for_raster,
33
+ load_embeddings_for_shapefile,
34
+ load_embeddings_for_shapefile_vq,
35
+ load_tee_vectors,
36
+ )
37
+ from tessera_eval.evaluate import (
38
+ detect_field_type,
39
+ evaluate,
40
+ regression_metrics,
41
+ run_kfold_cv,
42
+ run_learning_curve,
43
+ )
44
+ from tessera_eval.rasterize import align_raster_to_grid, rasterize_shapefile