RUN IT YOURSELF
The model is portable.
CS-Mamba ships as a single ONNX file. The browser demo is one way to run it — here's the exact input/output contract and copy-paste code so you can run the same weights on your own machine, in a script, or behind your own API. No browser, no account, no GPU required.
By design this is a portable artifact, not a locked cloud API — we hand you the model to own and run anywhere, with nothing to meter, bill, or shut down.
The contract
| Model | cs_mamba_fp16.onnx · fp16 weights, ~82 MB |
|---|---|
| Input | float32 [1, 6, 224, 224] |
| Channels | cat([pre-2, pre-1, post]) × [VV, VH] — six bands, oldest first |
| Preprocess | clamp to [0, C] · NaN→C · standardize per channel (x − μ) / σ |
| Output | float32 [1, 3, 224, 224] logits → argmax(axis=1) |
| Classes | 0 = no water · 1 = permanent water · 2 = flood |
Normalization is the load-bearing choice (the "clamp story"): in-distribution KuroSiwo tiles use clamp 0.15; the bright Banda Aceh OOD scene needs the adapted clamp 0.30 to recover the flood. Both preset values are in the Python example below.
Python · 30-second quickstart
Runs the model on a tile we already ship — no data prep.
import numpy as np
import onnxruntime as ort
import urllib.request
# 1. Grab the model (fp16 weights, ~82 MB) and one shipped sample tile.
urllib.request.urlretrieve("https://pub-c834930e7f05452caf062e40c59ef9e8.r2.dev/cs_mamba_fp16.onnx", "cs_mamba.onnx")
urllib.request.urlretrieve("https://remote-sensing-platform.pages.dev/cases/ks-germany-497/input.bin", "tile.bin")
# 2. input.bin is already normalized: float32 [6, 224, 224].
x = np.fromfile("tile.bin", dtype="<f4").reshape(1, 6, 224, 224)
sess = ort.InferenceSession("cs_mamba.onnx", providers=["CPUExecutionProvider"])
logits = sess.run(None, {sess.get_inputs()[0].name: x})[0] # [1, 3, 224, 224]
mask = logits.argmax(1)[0] # 0=land 1=permanent 2=flood
for k, name in enumerate(["No water", "Permanent water", "Flood"]):
print(f"{name:16s} {(mask == k).mean() * 100:5.1f}%")Python · your own 6 GeoTIFF bands
Bring raw linear σ0 bands; this applies the training preprocessing.
import numpy as np, rasterio, onnxruntime as ort
# Normalization presets (clamp ceiling + per-pol mean/std).
PRESETS = {
# In-distribution (KuroSiwo). Use for KuroSiwo-domain tiles.
"kurosiwo": dict(clamp=0.15, vv=(0.0953, 0.0427), vh=(0.0264, 0.0215)),
# Banda Aceh out-of-distribution, recommended config (recovers the flood).
"banda_clamp03": dict(clamp=0.30, vv=(0.0538, 0.0489), vh=(0.2078, 0.0916)),
}
def preprocess(arr, pol, p): # arr: raw linear sigma0 (224,224)
mean, std = p["vv"] if pol == "vv" else p["vh"]
arr = np.nan_to_num(arr, nan=p["clamp"])
arr = np.clip(arr, 0.0, p["clamp"])
return (arr - mean) / std
def band(path): # one single-band GeoTIFF -> (224,224)
with rasterio.open(path) as ds:
return ds.read(1, out_shape=(224, 224)).astype("float32")
p = PRESETS["banda_clamp03"]
# Channel order: cat([pre2, pre1, post]) x [VV, VH]
files = [("pre2_VV","vv"),("pre2_VH","vh"),("pre1_VV","vv"),
("pre1_VH","vh"),("post_VV","vv"),("post_VH","vh")]
x = np.stack([preprocess(band(f"{n}.tif"), pol, p) for n, pol in files])[None] # [1,6,224,224]
sess = ort.InferenceSession("cs_mamba.onnx", providers=["CPUExecutionProvider"])
mask = sess.run(None, {sess.get_inputs()[0].name: x.astype("<f4")})[0].argmax(1)[0]Node / JavaScript
import * as ort from "onnxruntime-node"; // or onnxruntime-web
import { readFileSync } from "fs";
const data = new Float32Array(readFileSync("tile.bin").buffer); // [6*224*224]
const sess = await ort.InferenceSession.create("cs_mamba.onnx");
const out = await sess.run({
[sess.inputNames[0]]: new ort.Tensor("float32", data, [1, 6, 224, 224]),
});
const logits = out[sess.outputNames[0]].data; // length 3*224*224
const HW = 224 * 224, hist = [0, 0, 0];
for (let p = 0; p < HW; p++) {
let a = 0, v = logits[p];
if (logits[HW + p] > v) { v = logits[HW + p]; a = 1; }
if (logits[2 * HW + p] > v) a = 2;
hist[a]++;
}
console.log("flood %", (hist[2] / HW * 100).toFixed(1));Self-host an HTTP API
The repo ships a FastAPI wrapper so you can serve the model to your own tools.
# Self-host the same model as an HTTP service # https://github.com/yyy735934-prog/rs-observatory (wsl-inference/server.py) git clone https://github.com/yyy735934-prog/rs-observatory && cd rs-observatory/wsl-inference pip install fastapi uvicorn onnxruntime rasterio numpy uvicorn server:app --host 0.0.0.0 --port 8000 # POST a 224x224 tile, get back the 3-class mask + class histogram: # GET /health # POST /predict/tile (config = original | clamp015 | clamp03 | clamp05)
Weights are the paper's CS-Mamba (UNetRSMamba_FloodFocus), trained on KuroSiwo. Full source — site, inference tools, and training code — is open at github.com/yyy735934-prog/rs-observatory. Please cite the project and KuroSiwo if you build on it. The exported ONNX is deterministic — same input, identical output, byte for byte.