đã áp dụng file shapefile vào train và predict

This commit is contained in:
Victor Phan
2026-01-05 11:19:34 +07:00
parent 03048d9503
commit 10219df149
23 changed files with 4148 additions and 85 deletions
+183 -6
View File
@@ -277,7 +277,7 @@ def train_model(
use_gpu=True,
use_cache=True,
test_size=0.2,
feature_mode='simple',
feature_mode='simple', # Changed from 'odc' - simple mode works with B04, B08, SCL only
output_model_path=None,
status_callback=None,
cancel_check=None
@@ -300,7 +300,7 @@ def train_model(
status_callback: Optional callback function to report progress
cancel_check: Optional function that returns True if training should be cancelled
test_size: Fraction of data to use for test set (0-1)
feature_mode: 'simple' (3 features), 'temporal' (39 features), or 'extended' (15 features)
feature_mode: 'simple' (3 features), 'temporal' (39 features), 'extended' (15 features), or 'odc' (8 features)
Returns:
Dictionary containing training results
@@ -346,14 +346,29 @@ def train_model(
# Try to load from cache
if use_cache and cache_file.exists():
update_status(f"📦 Loading cached dataset from {cache_file.name}...", 5)
update_status(f"📦 Đang load cache: {cache_file.name}...", 5)
try:
cached_data = joblib.load(cache_file)
features = cached_data['features']
labels = cached_data['labels']
update_status(f"✅ Loaded {len(features)} samples from cache (skipped satellite download!)", 50)
# Validate cached data
if len(features) == 0:
update_status(
f"❌ Cache rỗng (0 samples)! Đây là cache từ lần training thất bại trước.\n"
f" Nguyên nhân: Bbox không overlap với shapefile HOẶC tất cả điểm bị NaN.\n"
f" Đang xóa cache lỗi và tải lại dữ liệu...", 10
)
cache_file.unlink() # Delete empty cache
features = None
else:
update_status(
f"✅ Loaded {len(features)} samples từ cache!\n"
f" ⚡ Đã bỏ qua download위성 data (tiết kiệm thời gian)", 50
)
print(f"[CACHE HIT] Using cached dataset with {len(features)} samples")
except Exception as e:
update_status(f"⚠️ Cache load failed: {str(e)}, downloading fresh data...", 10)
update_status(f"⚠️ Cache bị lỗi: {str(e)}\n Đang tải lại dữ liệu mới...", 10)
features = None
# If no cache or cache failed, download data
@@ -404,6 +419,15 @@ def train_model(
fail_on_error=False,
)
# Debug: Print S2 data info
print(f"[DEBUG S2] Loaded S2 data")
print(f"[DEBUG S2] Dimensions: {dict(ds_s2.dims)}")
print(f"[DEBUG S2] Bands: {list(ds_s2.data_vars)}")
print(f"[DEBUG S2] CRS: {ds_s2.rio.crs if hasattr(ds_s2, 'rio') else 'No CRS'}")
print(f"[DEBUG S2] Spatial bounds: x=[{float(ds_s2.x.min())}, {float(ds_s2.x.max())}], y=[{float(ds_s2.y.min())}, {float(ds_s2.y.max())}]")
if 'time' in ds_s2.dims:
print(f"[DEBUG S2] Time range: {ds_s2.time.min().values} to {ds_s2.time.max().values}")
# Rename for compatibility (simple mode)
if "B04" in ds_s2 and "red" not in ds_s2:
ds_s2 = ds_s2.rename({"B04": "red", "B08": "nir", "SCL": "scl"})
@@ -443,14 +467,99 @@ def train_model(
ds_s1['vv_db'] = 10 * np.log10(ds_s1['vv'].where(ds_s1['vv'] > 0))
ds_s1['vh_db'] = 10 * np.log10(ds_s1['vh'].where(ds_s1['vh'] > 0))
# Debug: Print S1 data info
print(f"[DEBUG S1] Loaded S1 data")
print(f"[DEBUG S1] Dimensions: {dict(ds_s1.dims)}")
print(f"[DEBUG S1] Bands: {list(ds_s1.data_vars)}")
print(f"[DEBUG S1] Spatial bounds: x=[{float(ds_s1.x.min())}, {float(ds_s1.x.max())}], y=[{float(ds_s1.y.min())}, {float(ds_s1.y.max())}]")
check_cancellation()
# Load training data
update_status("Loading training data...", 55)
# Normalize training shapefile path
# If path doesn't start with 'train/', add it
if not training_shapefile.startswith('train/'):
training_shapefile = f'train/{training_shapefile}'
print(f"[DEBUG] Original training shapefile: {training_shapefile}")
print(f"[DEBUG] Current working directory: {os.getcwd()}")
# Try to find the file with exact name first
if not os.path.exists(training_shapefile):
# File not found, try to find similar files in train directory
train_dir = Path('train')
if train_dir.exists():
# List all .shp files
shp_files = list(train_dir.glob('*.shp'))
print(f"[DEBUG] Available shapefile files in train/:")
for f in shp_files:
print(f" - {f.name}")
# Try to find a matching file (case-insensitive, ignore underscores vs spaces)
filename_normalized = os.path.basename(training_shapefile).lower().replace('_', ' ')
for shp_file in shp_files:
if shp_file.name.lower().replace('_', ' ') == filename_normalized:
print(f"[DEBUG] Found matching file: {shp_file}")
training_shapefile = str(shp_file)
break
if not os.path.exists(training_shapefile):
raise FileNotFoundError(
f"Training shapefile not found: {training_shapefile}\n"
f"Available files: {[f.name for f in shp_files]}"
)
else:
raise FileNotFoundError(f"Train directory not found: {train_dir}")
print(f"[DEBUG] Final training shapefile path: {training_shapefile}")
print(f"[DEBUG] File exists: {os.path.exists(training_shapefile)}")
train_gdf = gpd.read_file(training_shapefile)
if train_gdf.crs != 'EPSG:32648':
# Print initial shapefile info
update_status(f"📍 Loaded {len(train_gdf)} points from shapefile", 56)
print(f"[DEBUG] Shapefile CRS: {train_gdf.crs}")
print(f"[DEBUG] Shapefile bounds: {train_gdf.total_bounds}")
# Convert to WGS84 first (if not already) to match bbox coordinates
original_crs = train_gdf.crs
if train_gdf.crs and train_gdf.crs.to_epsg() != 4326:
print(f"📍 Converting training shapefile from {train_gdf.crs} to WGS84")
train_gdf = train_gdf.to_crs("EPSG:4326")
print(f"[DEBUG] WGS84 bounds: {train_gdf.total_bounds}")
# Check bbox overlap in WGS84
shp_bounds = train_gdf.total_bounds # [minx, miny, maxx, maxy]
bbox_wgs84 = bbox # [min_lon, min_lat, max_lon, max_lat]
# Check if there's overlap
overlap_x = not (shp_bounds[2] < bbox_wgs84[0] or shp_bounds[0] > bbox_wgs84[2])
overlap_y = not (shp_bounds[3] < bbox_wgs84[1] or shp_bounds[1] > bbox_wgs84[3])
if not (overlap_x and overlap_y):
update_status(f"⚠️ WARNING: Shapefile and bbox may not overlap!", 57)
print(f"[WARNING] Shapefile bounds (WGS84): {shp_bounds}")
print(f"[WARNING] Requested bbox (WGS84): {bbox_wgs84}")
print(f"[WARNING] This may result in 0 training samples!")
else:
# Crop to bbox to see how many points are actually in the region
train_gdf_cropped = train_gdf.cx[bbox_wgs84[0]:bbox_wgs84[2], bbox_wgs84[1]:bbox_wgs84[3]]
update_status(f"📍 {len(train_gdf_cropped)} points within bbox", 57)
if len(train_gdf_cropped) == 0:
raise ValueError(
f"No training points found within bbox!\n"
f"Shapefile bounds: {shp_bounds}\n"
f"Requested bbox: {bbox_wgs84}\n"
f"Please adjust bbox to cover your training data."
)
# Then convert to UTM Zone 48N (EPSG:32648) for extraction
if train_gdf.crs.to_epsg() != 32648:
print(f"📍 Converting training shapefile from WGS84 to UTM Zone 48N (EPSG:32648)")
train_gdf = train_gdf.to_crs('EPSG:32648')
print(f"[DEBUG] UTM bounds: {train_gdf.total_bounds}")
# Auto-detect label column
label_column = None
@@ -465,6 +574,12 @@ def train_model(
# Extract features using FeatureExtractor
update_status("Extracting features from satellite data...", 60)
print(f"[DEBUG] Starting feature extraction...")
print(f"[DEBUG] Training GDF has {len(train_gdf)} points")
print(f"[DEBUG] Training GDF CRS: {train_gdf.crs}")
print(f"[DEBUG] Training GDF bounds (UTM): {train_gdf.total_bounds}")
print(f"[DEBUG] Label column: {label_column}")
if feature_mode == 'simple':
# For simple mode: calculate NDVI first
ndvi = (ds_s2['nir'] - ds_s2['red']) / (ds_s2['nir'] + ds_s2['red'] + 1e-8)
@@ -472,9 +587,19 @@ def train_model(
cloud_mask = ds_s2['scl'].isin([1, 3, 8, 9, 10])
ndvi_masked = ndvi.where(~cloud_mask)
print(f"[DEBUG] NDVI shape: {ndvi_masked.shape}")
print(f"[DEBUG] NDVI range: [{float(ndvi_masked.min())}, {float(ndvi_masked.max())}]")
# Extract features at training points
features = []
labels = []
failed_extractions = 0
# Test first point to see what's happening
first_point = train_gdf.iloc[0]
print(f"[DEBUG] Testing first point:")
print(f" Coords: ({first_point.geometry.x}, {first_point.geometry.y})")
print(f" Label: {first_point[label_column]}")
for idx, row in train_gdf.iterrows():
point = row.geometry
@@ -489,12 +614,26 @@ def train_model(
feature_vec = [float(ndvi_val), float(vh_val), float(vv_val)]
# Debug first few points
if idx < 3:
print(f"[DEBUG] Point {idx}: coords=({x_coord:.2f}, {y_coord:.2f}), ndvi={ndvi_val:.3f}, vh={vh_val:.3f}, vv={vv_val:.3f}")
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: {feature_vec}")
except Exception as e:
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)
features = np.array(features)
labels = np.array(labels)
@@ -510,6 +649,7 @@ def train_model(
# Extract features at training points
features = []
labels = []
failed_extractions = 0
for idx, row in train_gdf.iterrows():
point = row.geometry
@@ -557,9 +697,15 @@ def train_model(
if not np.isnan(feature_vec).any():
features.append(feature_vec)
labels.append(label)
else:
failed_extractions += 1
except Exception as e:
failed_extractions += 1
continue
if failed_extractions > 0:
update_status(f"⚠️ {failed_extractions}/{len(train_gdf)} points had NaN/missing data", 65)
features = np.array(features)
labels = np.array(labels)
@@ -567,6 +713,25 @@ def train_model(
update_status(f"Extracted {len(features)} valid training samples", 70)
# ============ VALIDATE SAMPLES ============
if len(features) == 0:
error_msg = (
f"❌ No valid training samples extracted!\n"
f"Possible reasons:\n"
f"1. Training shapefile points don't overlap with bbox: {bbox}\n"
f"2. All points have NaN values (cloud cover, missing data)\n"
f"3. Coordinate system mismatch\n"
f"Suggestions:\n"
f"- Check if bbox matches your region\n"
f"- Try a different time range with less cloud cover\n"
f"- Verify training shapefile coordinates are correct"
)
raise ValueError(error_msg)
# Warn if very few samples
if len(features) < 20:
update_status(f"⚠️ Warning: Only {len(features)} samples extracted. Results may be unreliable.", 70)
# ============ SAVE TO CACHE ============
if use_cache:
update_status(f"💾 Saving dataset to cache for future use...", 72)
@@ -585,6 +750,18 @@ def train_model(
except Exception as e:
update_status(f"⚠️ Cache save failed: {str(e)}", 75)
# Validate samples after cache loading
if len(features) == 0:
error_msg = (
f"❌ No training samples available!\n"
f"The cached or loaded dataset is empty.\n"
f"Please try:\n"
f"1. Clear cache and reload data\n"
f"2. Check training shapefile and bbox overlap\n"
f"3. Adjust time range and cloud cover settings"
)
raise ValueError(error_msg)
# Encode labels
label_encoder = LabelEncoder()
labels_encoded = label_encoder.fit_transform(labels)