Tonic commited on
Commit
d754091
·
verified ·
1 Parent(s): 3ca6dbe

ci: deploy terramind_tim (cf4492f1e576)

Browse files
README.package.md CHANGED
@@ -6,6 +6,8 @@ Optional **GPU/CPU** batch tool under `inference/*` (not the thin game `server/`
6
 
7
  **Authority:** `rules/06-server-vlm-tim-and-on-device-ml.md`, `rules/12-python-gradio-terramind-server.md`, `docs/PRO-TAB-VLM-ORCHESTRATION-SPEC.md` (Coordinates → `ai_lat` / `ai_lon` for catalog pipelines).
8
 
 
 
9
  ## Install (separate venv recommended)
10
 
11
  ```bash
 
6
 
7
  **Authority:** `rules/06-server-vlm-tim-and-on-device-ml.md`, `rules/12-python-gradio-terramind-server.md`, `docs/PRO-TAB-VLM-ORCHESTRATION-SPEC.md` (Coordinates → `ai_lat` / `ai_lon` for catalog pipelines).
8
 
9
+ **Default backbone:** NU:TONIC pins the largest TerraMind-1.0 TIM variant as ``nutonic_terramind_tim_local.tim_defaults.DEFAULT_TIM_MODEL_ID`` (**``terramind_v1_large_tim``**). Example YAMLs and the HF Space / Gradio demos use that unless you override ``model_id``.
10
+
11
  ## Install (separate venv recommended)
12
 
13
  ```bash
nutonic_terramind_tim_local/gradio_panel.py CHANGED
@@ -5,6 +5,7 @@ from typing import Any
5
  import gradio as gr
6
 
7
  from nutonic_terramind_tim_local.space_api import health, tim_infer
 
8
 
9
 
10
  def _infer_from_json(req: dict[str, Any]) -> dict[str, Any]:
@@ -26,7 +27,7 @@ def build_gradio_blocks() -> gr.Blocks:
26
  value={
27
  "analysis_profile": "brief_only",
28
  "config": {
29
- "model_id": "terramind_v1_tiny_tim",
30
  "pretrained": True,
31
  "modalities": ["RGB"],
32
  "tim_modalities": ["LULC", "location"],
 
5
  import gradio as gr
6
 
7
  from nutonic_terramind_tim_local.space_api import health, tim_infer
8
+ from nutonic_terramind_tim_local.tim_defaults import DEFAULT_TIM_MODEL_ID
9
 
10
 
11
  def _infer_from_json(req: dict[str, Any]) -> dict[str, Any]:
 
27
  value={
28
  "analysis_profile": "brief_only",
29
  "config": {
30
+ "model_id": DEFAULT_TIM_MODEL_ID,
31
  "pretrained": True,
32
  "modalities": ["RGB"],
33
  "tim_modalities": ["LULC", "location"],
nutonic_terramind_tim_local/inputs_build.py CHANGED
@@ -418,4 +418,10 @@ def _build_inputs(
418
  }
419
  for label, (_tensor, meta) in temporal_stac.items()
420
  }
 
 
 
 
 
 
421
  return out, aux
 
418
  }
419
  for label, (_tensor, meta) in temporal_stac.items()
420
  }
421
+ try:
422
+ s2p = stac_s2_params_from_cfg(in_cfg, row)
423
+ if s2p.get("lat") is not None and s2p.get("lon") is not None:
424
+ aux["request_wgs84"] = {"latitude": float(s2p["lat"]), "longitude": float(s2p["lon"])}
425
+ except Exception:
426
+ pass
427
  return out, aux
nutonic_terramind_tim_local/run.py CHANGED
@@ -2,6 +2,7 @@
2
 
3
  from __future__ import annotations
4
 
 
5
  import os
6
  import random
7
  import sys
@@ -73,6 +74,54 @@ def _set_ensemble_iteration_seed(seed: int) -> None:
73
  torch.cuda.manual_seed_all(s)
74
 
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  def _pair_from_wgs(wgs: Mapping[str, Any] | None) -> tuple[float | None, float | None]:
77
  if not wgs or not isinstance(wgs, dict):
78
  return None, None
@@ -165,6 +214,9 @@ def _export_row(
165
  tim_modality_outputs = build_tim_modality_outputs(
166
  model, tim_raw, tensor_sample_limit=sample_limit, policy=tim_policy
167
  )
 
 
 
168
  profile = str(
169
  analysis_profile
170
  or export_cfg.get("analysis_profile")
 
2
 
3
  from __future__ import annotations
4
 
5
+ import math
6
  import os
7
  import random
8
  import sys
 
74
  torch.cuda.manual_seed_all(s)
75
 
76
 
77
+ def _haversine_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
78
+ r = 6371.0
79
+ p1, p2 = math.radians(lat1), math.radians(lat2)
80
+ dphi = math.radians(lat2 - lat1)
81
+ dl = math.radians(lon2 - lon1)
82
+ a = math.sin(dphi / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
83
+ return 2 * r * math.asin(min(1.0, math.sqrt(max(0.0, a))))
84
+
85
+
86
+ def _sanitize_coordinates_in_row(
87
+ row: dict[str, Any],
88
+ *,
89
+ inputs_aux: Mapping[str, Any] | None,
90
+ max_decode_drift_km: float = 250.0,
91
+ ) -> None:
92
+ """If decoded TiM WGS84 is far from the requested batch lat/lon, replace with request (bad decode guard)."""
93
+ if not isinstance(inputs_aux, Mapping):
94
+ return
95
+ req = inputs_aux.get("request_wgs84")
96
+ if not isinstance(req, dict):
97
+ return
98
+ try:
99
+ elat = float(req["latitude"])
100
+ elon = float(req["longitude"])
101
+ except (KeyError, TypeError, ValueError):
102
+ return
103
+ tmo = row.get("tim_modality_outputs")
104
+ if not isinstance(tmo, dict):
105
+ return
106
+ coord = tmo.get("Coordinates")
107
+ if not isinstance(coord, dict):
108
+ return
109
+ try:
110
+ clat = float(coord["latitude"]) # type: ignore[arg-type]
111
+ clon = float(coord["longitude"]) # type: ignore[arg-type]
112
+ except (KeyError, TypeError, ValueError):
113
+ return
114
+ drift = _haversine_km(clat, clon, elat, elon)
115
+ if drift <= max_decode_drift_km:
116
+ return
117
+ coord["latitude"] = elat
118
+ coord["longitude"] = elon
119
+ coord["replaced_decode_invalid"] = True
120
+ coord["original_decode_km_drift"] = round(float(drift), 2)
121
+ coord["original_latitude"] = clat
122
+ coord["original_longitude"] = clon
123
+
124
+
125
  def _pair_from_wgs(wgs: Mapping[str, Any] | None) -> tuple[float | None, float | None]:
126
  if not wgs or not isinstance(wgs, dict):
127
  return None, None
 
214
  tim_modality_outputs = build_tim_modality_outputs(
215
  model, tim_raw, tensor_sample_limit=sample_limit, policy=tim_policy
216
  )
217
+ row_prelim: dict[str, Any] = {"tim_modality_outputs": tim_modality_outputs}
218
+ _sanitize_coordinates_in_row(row_prelim, inputs_aux=inputs_aux)
219
+ tim_modality_outputs = row_prelim["tim_modality_outputs"]
220
  profile = str(
221
  analysis_profile
222
  or export_cfg.get("analysis_profile")
nutonic_terramind_tim_local/space_api.py CHANGED
@@ -9,6 +9,7 @@ from fastapi import Body, FastAPI, HTTPException
9
 
10
  from nutonic_terramind_tim_local.spaces_zero import apply_zero_gpu
11
  from nutonic_terramind_tim_local.terramind_patches import terramind_patch_diagnostics
 
12
 
13
  app = FastAPI(title="NU:TONIC TerraMind TiM local (Space)", version="0.1.0")
14
 
@@ -39,7 +40,7 @@ def tim_export(
39
  examples=[
40
  {
41
  "config": {
42
- "model_id": "terramind_v1_tiny_tim",
43
  "pretrained": True,
44
  "modalities": ["RGB"],
45
  "tim_modalities": ["LULC", "location"],
 
9
 
10
  from nutonic_terramind_tim_local.spaces_zero import apply_zero_gpu
11
  from nutonic_terramind_tim_local.terramind_patches import terramind_patch_diagnostics
12
+ from nutonic_terramind_tim_local.tim_defaults import DEFAULT_TIM_MODEL_ID
13
 
14
  app = FastAPI(title="NU:TONIC TerraMind TiM local (Space)", version="0.1.0")
15
 
 
40
  examples=[
41
  {
42
  "config": {
43
+ "model_id": DEFAULT_TIM_MODEL_ID,
44
  "pretrained": True,
45
  "modalities": ["RGB"],
46
  "tim_modalities": ["LULC", "location"],
nutonic_terramind_tim_local/tim_defaults.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Canonical TerraMind TiM backbone id for NU:TONIC (TerraTorch ``BACKBONE_REGISTRY``)."""
2
+
3
+ # Largest TerraMind-1.0 TIM variant (IBM ``terratorch`` registry). Prefer this unless profiling or CI forces smaller.
4
+ DEFAULT_TIM_MODEL_ID = "terramind_v1_large_tim"
5
+
6
+ __all__ = ["DEFAULT_TIM_MODEL_ID"]
pyproject.toml CHANGED
@@ -13,7 +13,7 @@ dependencies = [
13
  "terratorch>=1.1.1,<1.3",
14
  "PyYAML>=6.0.1",
15
  "numpy>=1.26.0",
16
- "Pillow>=10.0.0",
17
  # Align with TerraTorch → peft: older peft imports HybridCache from transformers;
18
  # transformers 5.2+ reshaped exports — peft 0.18.1+ avoids the stale import path.
19
  "peft>=0.18.1",
@@ -31,7 +31,7 @@ s2 = ["pystac-client>=0.7.0", "rasterio>=1.3.9", "boto3>=1.34.0"]
31
  space = [
32
  "fastapi>=0.115.0",
33
  "uvicorn[standard]>=0.32.0",
34
- "gradio>=4.44.0,<6",
35
  "spaces>=0.44.0,<1",
36
  ]
37
 
 
13
  "terratorch>=1.1.1,<1.3",
14
  "PyYAML>=6.0.1",
15
  "numpy>=1.26.0",
16
+ "Pillow>=12.2.0",
17
  # Align with TerraTorch → peft: older peft imports HybridCache from transformers;
18
  # transformers 5.2+ reshaped exports — peft 0.18.1+ avoids the stale import path.
19
  "peft>=0.18.1",
 
31
  space = [
32
  "fastapi>=0.115.0",
33
  "uvicorn[standard]>=0.32.0",
34
+ "gradio>=6.7.0",
35
  "spaces>=0.44.0,<1",
36
  ]
37
 
requirements.txt CHANGED
@@ -2,7 +2,7 @@ torch>=2.2.0
2
  terratorch>=1.1.1,<1.3
3
  PyYAML>=6.0.1
4
  numpy>=1.26.0
5
- Pillow>=10.0.0
6
  peft>=0.18.1
7
  transformers>=4.48.0
8
  urllib3>=2.6.3
 
2
  terratorch>=1.1.1,<1.3
3
  PyYAML>=6.0.1
4
  numpy>=1.26.0
5
+ Pillow>=12.2.0
6
  peft>=0.18.1
7
  transformers>=4.48.0
8
  urllib3>=2.6.3