SatQuery-api / backend /adaptation /download_subset.py
Sahil-nith's picture
Upload 47 files
0127c21 verified
Raw History Blame Contribute Delete
5.66 kB
"""
Pull a SMALL subset of GFM-Bench/BigEarthNet via Hugging Face streaming
(no 59GB/119GB bulk download) and write it out as:
data/bigearthnet_subset/images/patch_00000.png ...
data/bigearthnet_subset/manifest.csv (image_path,labels)
...ready to feed straight into prepare_bigearthnet.py.
Run this from backend/adaptation/ (see chat — paths assume that).
Confirmed schema (from the HF dataset card):
optical -> Sentinel-2 image, 12 bands, 120x120
radar -> Sentinel-1 image (not used here — VQA path is optical-only for now)
label -> multi-label classification target over the 19 CLASS_LABELS below
We build a true-color-ish preview PNG from bands B04/B03/B02 (Red/Green/Blue)
so BLIP-VQA (a natural-image model) gets something visually sensible instead
of a raw 12-band array it can't consume.
"""
from __future__ import annotations
import csv
import os
import numpy as np
OUT_DIR = "data/bigearthnet_subset"
IMG_DIR = f"{OUT_DIR}/images"
LIMIT = 500 # plenty for a LoRA demo fine-tune; raise later if you have time/VRAM to spare
# Order matches the dataset card's "Class Labels" list exactly — required to
# turn the numeric `label` feature back into readable text.
CLASS_LABELS = [
"Urban fabric", "Industrial or commercial units", "Arable land",
"Permanent crops", "Pastures", "Complex cultivation patterns",
"Land principally occupied by agriculture, with significant areas of natural vegetation",
"Agro-forestry areas", "Broad-leaved forest", "Coniferous forest", "Mixed forest",
"Natural grassland and sparsely vegetated areas",
"Moors, heathland and sclerophyllous vegetation", "Transitional woodland, shrub",
"Beaches, dunes, sands", "Inland wetlands", "Coastal wetlands",
"Inland waters", "Marine waters",
]
# Sentinel-2 band order in this dataset's `optical_channel_wv` metadata is
# B01,B02,B03,B04,B05,B06,B07,B08,B8A,B09,B11,B12 (12 bands) — so
# Red=B04=index 3, Green=B03=index 2, Blue=B02=index 1.
_R, _G, _B = 3, 2, 1
def _percentile_stretch(band: np.ndarray) -> np.ndarray:
band = band.astype("float32")
lo, hi = np.percentile(band, [2, 98])
if hi <= lo:
lo, hi = float(band.min()), float(band.max() or 1.0)
return np.clip((band - lo) / max(hi - lo, 1e-6), 0, 1)
def _optical_to_rgb_png(optical_array: np.ndarray):
"""optical_array expected shape: (bands, H, W) or (H, W, bands). Returns a PIL Image."""
from PIL import Image
arr = np.array(optical_array)
if arr.ndim != 3:
raise ValueError(f"Unexpected optical array shape {arr.shape} — inspect the raw feature and adjust this function.")
# Normalize to (bands, H, W)
if arr.shape[0] <= 13: # bands first
bands = arr
elif arr.shape[-1] <= 13: # bands last
bands = np.moveaxis(arr, -1, 0)
else:
raise ValueError(f"Can't tell which axis is bands for shape {arr.shape} — print(arr.shape) and fix _R/_G/_B indices.")
r = _percentile_stretch(bands[_R])
g = _percentile_stretch(bands[_G])
b = _percentile_stretch(bands[_B])
rgb = (np.dstack([r, g, b]) * 255).astype("uint8")
return Image.fromarray(rgb, mode="RGB")
def _labels_to_text(label_field) -> list[str]:
"""Handles either a multi-hot vector (length 19, 0/1) or a list of class indices."""
arr = np.array(label_field)
if arr.ndim == 1 and arr.size == len(CLASS_LABELS) and set(np.unique(arr)).issubset({0, 1}):
idxs = np.nonzero(arr)[0]
else:
idxs = arr.astype(int).flatten()
return [CLASS_LABELS[i] for i in idxs if 0 <= i < len(CLASS_LABELS)]
def main() -> None:
from datasets import load_dataset
os.makedirs(IMG_DIR, exist_ok=True)
print("Opening GFM-Bench/BigEarthNet in streaming mode (no bulk download)...")
ds = load_dataset(
"GFM-Bench/BigEarthNet", split="train", streaming=True,
trust_remote_code=True, # this dataset uses an old-style loading script —
# only works on datasets==2.21.0, see chat/README
)
rows = []
for i, example in enumerate(ds):
if i >= LIMIT:
break
if i == 0:
print("First example keys:", list(example.keys()))
print("optical type/shape:", type(example["optical"]), np.array(example["optical"]).shape)
print("label raw value:", example["label"])
try:
img = _optical_to_rgb_png(example["optical"])
except Exception as exc: # noqa: BLE001
print(f"Skipping row {i}: could not build RGB preview ({exc})")
continue
labels = _labels_to_text(example["label"])
if not labels:
continue
fname = f"patch_{i:05d}.png"
img.save(f"{IMG_DIR}/{fname}")
rows.append({"image_path": fname, "labels": ";".join(labels)})
if (i + 1) % 50 == 0:
print(f"...{i + 1} patches processed")
if not rows:
raise SystemExit("No usable rows produced — check the printed shape/keys above and fix _optical_to_rgb_png / _labels_to_text.")
manifest_path = f"{OUT_DIR}/manifest.csv"
with open(manifest_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["image_path", "labels"])
writer.writeheader()
writer.writerows(rows)
print(f"\nDone. Saved {len(rows)} patches to {IMG_DIR}")
print(f"Manifest written to {manifest_path}")
print(
"\nNext:\n"
f" python prepare_bigearthnet.py --manifest {manifest_path} "
f"--image-root {IMG_DIR} --out data/bigearthnet_vqa_train.jsonl"
)
if __name__ == "__main__":
main()