feat: implement comprehensive land cover classification pipeline with model benchmarking and experiment logging

This commit is contained in:
2026-07-17 18:54:25 +07:00
parent a258db54cd
commit abab846884
69 changed files with 155558 additions and 105 deletions
+26 -43
View File
@@ -510,6 +510,7 @@ def train_model(
bbox=bbox,
patch_url=planetary_computer.sign,
fail_on_error=False,
chunks={"time": 1, "x": 2048, "y": 2048}
)
# Debug: Print S2 data info
@@ -556,6 +557,7 @@ def train_model(
bbox=bbox,
patch_url=planetary_computer.sign,
fail_on_error=False,
chunks={"time": 1, "x": 2048, "y": 2048}
)
# Convert to dB
@@ -734,8 +736,8 @@ def train_model(
labels = np.array(labels)
elif feature_mode in ['odc', 'extended']:
# For odc/extended: Extract features for full raster first, then sample at points
update_status(f"Extracting {feature_mode} features from full raster...", 62)
# For odc/extended: Extract points FIRST, then compute features to save RAM
update_status(f"Extracting points from {feature_mode} raster before computing features...", 62)
# Apply cloud mask first
if 'SCL' in ds_s2:
@@ -745,68 +747,49 @@ def train_model(
if band != 'SCL':
ds_s2[band] = ds_s2[band].where(~cloud_mask)
# Extract features using FeatureExtractor for entire raster
# Use advanced indexing to extract exactly the 1130 points
x_coords = xr.DataArray(train_gdf.geometry.x.values, dims="point")
y_coords = xr.DataArray(train_gdf.geometry.y.values, dims="point")
update_status("Downloading and extracting point data from Dask array (this is fast)...", 65)
points_s2 = ds_s2.sel(x=x_coords, y=y_coords, method='nearest').compute()
update_status("Computing spectral indices for extracted points...", 66)
# Extract features using FeatureExtractor for ONLY the extracted points
raster_features = extractor.extract(
s2_data=ds_s2,
s2_data=points_s2,
vh_data=None, # ODC/extended don't use radar in aggregate
vv_data=None
)
print(f"[DEBUG] Extracted raster features: shape={raster_features.shape}")
print(f"[DEBUG] Feature range: [{raster_features.min()}, {raster_features.max()}]")
print(f"[DEBUG] Extracted point features: shape={raster_features.shape}")
print(f"[DEBUG] Feature range: [{np.nanmin(raster_features)}, {np.nanmax(raster_features)}]")
# Now sample at each training point
features = []
labels = []
failed_extractions = 0
# Get spatial dimensions
y_coords = ds_s2.y.values
x_coords = ds_s2.x.values
print(f"[DEBUG] S2 spatial grid: x=[{x_coords.min()}, {x_coords.max()}], y=[{y_coords.min()}, {y_coords.max()}]")
for idx, row in train_gdf.iterrows():
point = row.geometry
x_coord = point.x
y_coord = point.y
label = row[label_column]
try:
# Find nearest pixel indices
x_idx = np.argmin(np.abs(x_coords - x_coord))
y_idx = np.argmin(np.abs(y_coords - y_coord))
if idx < len(raster_features):
feature_vec = raster_features[idx]
# Get features at this pixel
# raster_features shape: (n_pixels, n_features)
# Need to convert 2D (y, x) index to 1D pixel index
pixel_idx = y_idx * len(x_coords) + x_idx
if idx < 3:
print(f"[DEBUG] Point {idx}: features={feature_vec[:3]}...")
if pixel_idx < len(raster_features):
feature_vec = raster_features[pixel_idx]
if idx < 3:
print(f"[DEBUG] Point {idx}: coords=({x_coord:.2f}, {y_coord:.2f}) -> pixel[{y_idx},{x_idx}] -> idx={pixel_idx}, features={feature_vec[:3]}...")
if not np.isnan(feature_vec).any():
features.append(feature_vec)
labels.append(label)
else:
failed_extractions += 1
if idx < 3:
print(f"[DEBUG] Point {idx} has NaN features")
if not np.isnan(feature_vec).any():
features.append(feature_vec)
labels.append(label)
else:
failed_extractions += 1
if idx < 3:
print(f"[DEBUG] Point {idx} pixel_idx {pixel_idx} out of range (max={len(raster_features)})")
except Exception as e:
print(f"[DEBUG] Point {idx} has NaN features")
else:
failed_extractions += 1
if idx < 3:
print(f"[DEBUG] Point {idx} extraction failed: {e}")
continue
if failed_extractions > 0:
update_status(f"⚠️ {failed_extractions}/{len(train_gdf)} points had NaN/missing data", 65)
update_status(f"⚠️ {failed_extractions}/{len(train_gdf)} points had NaN/missing data", 68)
features = np.array(features)
labels = np.array(labels)