Add mlstac loader, weights, examples and model card
Browse files
load.py
CHANGED
|
@@ -286,4 +286,122 @@ def predict_large(image, model, **kwargs):
|
|
| 286 |
apply_nodata_mask=kwargs.get("apply_nodata_mask", True),
|
| 287 |
return_probs=kwargs.get("return_probs", False),
|
| 288 |
verbose=kwargs.get("verbose", False),
|
| 289 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
apply_nodata_mask=kwargs.get("apply_nodata_mask", True),
|
| 287 |
return_probs=kwargs.get("return_probs", False),
|
| 288 |
verbose=kwargs.get("verbose", False),
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
# ============================================================================
|
| 293 |
+
# CHRIS/PROBA-1 preprocessing (the 6 acquisition modes)
|
| 294 |
+
# ============================================================================
|
| 295 |
+
# For each CHRIS mode, which raw band indices (1-based) average into each of
|
| 296 |
+
# the R, G, B, NIR groups. Taken from the inference pipeline.
|
| 297 |
+
BAND_SELECTION = {
|
| 298 |
+
1: {"B4": [23, 24, 25],
|
| 299 |
+
"B3": [13, 14, 15],
|
| 300 |
+
"B2": [4, 5, 6, 7, 8, 9, 10],
|
| 301 |
+
"B8": [43, 44, 45, 46, 47, 48, 49, 50, 51]},
|
| 302 |
+
2: {"B4": [10, 11, 12], "B3": [6, 7], "B2": [3, 4], "B8": [17]},
|
| 303 |
+
3: {"B4": [7, 8], "B3": [4, 5], "B2": [2], "B8": [15]},
|
| 304 |
+
4: {"B4": [4], "B3": [2], "B2": [1], "B8": [18]},
|
| 305 |
+
5: {"B4": [7, 8], "B3": [4, 5], "B2": [2], "B8": [23, 24, 25, 26]},
|
| 306 |
+
6: {"B2": [1], "B3": [2], "B4": [3], "B8": [4]},
|
| 307 |
+
}
|
| 308 |
+
BAND_KEYS_RGBN = ["B4", "B3", "B2", "B8"] # R, G, B, NIR
|
| 309 |
+
|
| 310 |
+
DN_SCALE = 100_000.0
|
| 311 |
+
TOA_SCALE = 10_000.0
|
| 312 |
+
CAP = 5.0 # clip ceiling used during training; keep it identical at inference
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def _avg_bands(cube, band_list):
|
| 316 |
+
"""Average the given 1-based raw bands into a single (H, W) layer."""
|
| 317 |
+
idx = [b - 1 for b in band_list]
|
| 318 |
+
return cube[idx[0]] if len(idx) == 1 else cube[idx].mean(axis=0)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def _infer_source_from_name(tif_path):
|
| 322 |
+
"""Guess 'dn' or 'toa' from the file name. Returns None if unclear."""
|
| 323 |
+
name = Path(tif_path).name.lower()
|
| 324 |
+
if "toa" in name:
|
| 325 |
+
return "toa"
|
| 326 |
+
if "dn" in name:
|
| 327 |
+
return "dn"
|
| 328 |
+
return None
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def build_rgbn(cube, mode_n, source):
|
| 332 |
+
"""Build the (4, H, W) RGBN stack and the nodata mask from a raw cube.
|
| 333 |
+
|
| 334 |
+
Args:
|
| 335 |
+
cube: (bands, H, W) float array read from the CHRIS GeoTIFF.
|
| 336 |
+
mode_n: CHRIS acquisition mode, 1 to 6.
|
| 337 |
+
source: 'dn' or 'toa', selects the radiometric scale.
|
| 338 |
+
|
| 339 |
+
Returns:
|
| 340 |
+
(stack, nodata) where stack is (4, H, W) float32 and nodata is (H, W) bool.
|
| 341 |
+
"""
|
| 342 |
+
sel = BAND_SELECTION.get(int(mode_n))
|
| 343 |
+
if sel is None:
|
| 344 |
+
raise ValueError(f"Unsupported CHRIS mode {mode_n}; expected 1-6.")
|
| 345 |
+
|
| 346 |
+
needed_max = max(max(sel[k]) for k in BAND_KEYS_RGBN)
|
| 347 |
+
if cube.shape[0] < needed_max:
|
| 348 |
+
raise ValueError(
|
| 349 |
+
f"Cube has {cube.shape[0]} bands but mode {mode_n} needs {needed_max}."
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
# nodata = pixel is 0 in every RGBN group
|
| 353 |
+
H, W = cube.shape[1:]
|
| 354 |
+
nodata = np.ones((H, W), dtype=bool)
|
| 355 |
+
for k in BAND_KEYS_RGBN:
|
| 356 |
+
nodata &= (_avg_bands(cube, sel[k]) == 0)
|
| 357 |
+
|
| 358 |
+
scale = DN_SCALE if source == "dn" else TOA_SCALE
|
| 359 |
+
layers = [_avg_bands(cube, sel[k]) for k in BAND_KEYS_RGBN]
|
| 360 |
+
stack = np.stack(layers, axis=0).astype(np.float32) / scale
|
| 361 |
+
stack = np.clip(stack, 0.0, CAP)
|
| 362 |
+
stack[:, nodata] = 0.0
|
| 363 |
+
return stack, nodata
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def predict_chris(tif_path, model, mode_n, source=None, **kwargs):
|
| 367 |
+
"""Segment a raw CHRIS/PROBA-1 GeoTIFF end to end.
|
| 368 |
+
|
| 369 |
+
Reads the cube, builds the RGBN stack for the given mode, runs the
|
| 370 |
+
ensemble, and restores nodata as 99.
|
| 371 |
+
|
| 372 |
+
Args:
|
| 373 |
+
tif_path: path to the CHRIS GeoTIFF.
|
| 374 |
+
model: object returned by compiled_model().
|
| 375 |
+
mode_n: CHRIS acquisition mode, 1 to 6.
|
| 376 |
+
source: 'dn' or 'toa'. If None, it is guessed from the file name.
|
| 377 |
+
return_probs: if True, return (num_classes, H, W) probabilities.
|
| 378 |
+
|
| 379 |
+
Returns:
|
| 380 |
+
(H, W) uint8 label map (nodata = 99), or probabilities if requested.
|
| 381 |
+
"""
|
| 382 |
+
import rasterio as rio
|
| 383 |
+
|
| 384 |
+
if source is None:
|
| 385 |
+
source = _infer_source_from_name(tif_path)
|
| 386 |
+
if source is None:
|
| 387 |
+
raise ValueError(
|
| 388 |
+
"Could not tell DN from TOA by the file name; "
|
| 389 |
+
"pass source='dn' or source='toa'."
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
with rio.open(tif_path) as src:
|
| 393 |
+
cube = src.read().astype(np.float32)
|
| 394 |
+
|
| 395 |
+
stack, nodata = build_rgbn(cube, mode_n, source)
|
| 396 |
+
|
| 397 |
+
return_probs = kwargs.get("return_probs", False)
|
| 398 |
+
pred = model.predict_array(
|
| 399 |
+
stack,
|
| 400 |
+
apply_nodata_mask=False, # we apply the CHRIS nodata mask below
|
| 401 |
+
return_probs=return_probs,
|
| 402 |
+
verbose=kwargs.get("verbose", False),
|
| 403 |
+
)
|
| 404 |
+
if not return_probs:
|
| 405 |
+
pred = np.asarray(pred).astype(np.uint8)
|
| 406 |
+
pred[nodata] = 99
|
| 407 |
+
return pred
|