#!/usr/bin/env python # coding: utf-8 # In[ ]: get_ipython().run_cell_magic('time', '', '%matplotlib inline\n\nimport importlib\nimport new_import_ODC \n\nimportlib.reload(new_import_ODC)\n\nfrom new_import_ODC import *\n\nprint("✅ All modules loaded successfully")\n') # In[2]: get_ipython().run_cell_magic('time', '', 'import os\nimport sys\n\nprint("✅ AWS credentials loaded from environment variables")\n\n# Cấu hình Dask local\nfrom dask.distributed import Client, LocalCluster\n\ncluster = LocalCluster(n_workers=4)\nclient = Client(cluster)\nprint("✅ Dask cluster initialized")\nprint(f" Cluster: {cluster}")\n\n# Khai báo Datacube (chỉ để lấy metadata, không dùng load())\nimport datacube\ntry:\n dc = datacube.Datacube()\n print("✅ Datacube connected (metadata only)")\nexcept Exception as e:\n print(f"⚠️ Datacube connection not critical: {e}")\n dc = None\n\nprint("\\n" + "="*70)\n') # In[3]: get_ipython().run_cell_magic('time', '', '# 🔧 Get Sentinel-2 scene metadata from datacube\nprint("="*70)\nprint("GETTING SENTINEL-2 SCENE METADATA")\nprint("="*70)\n\ndate_range = ("2023-03-01", "2023-12-31")\nlongtitude_range = (105.5, 106.4)\nlatitude_range = (9.2, 10.0)\n\ntry:\n print(f"\\n[1] Loading metadata from datacube...")\n datasets = list(dc.find_datasets(product=\'s2_l2a\', time=date_range))\n print(f" ✅ Found {len(datasets)} scenes")\n \n if datasets:\n selected = datasets[0]\n print(f"\\n[2] Selected scene: {selected.metadata.label}")\n scene_datetime = selected.time.begin if hasattr(selected.time, \'begin\') else selected.time\n print(f" Date: {scene_datetime}")\n \n # Display measurement paths\n print(f"\\n[3] Available bands:")\n for name, measurement in selected.measurements.items():\n print(f" - {name}: {measurement[\'path\'][:80]}")\n \nexcept Exception as e:\n print(f"❌ Error: {e}")\n import traceback\n traceback.print_exc()\n\nprint("="*70)\n') # In[4]: get_ipython().run_cell_magic('time', '', '# 🔍 CHECK IF DATASET CACHE EXISTS (Skip download if available)\nprint("="*70)\nprint("CHECKING FOR CACHED DATASET")\nprint("="*70)\n\nimport os\nimport xarray as xr\n\ncache_dir = "dataset_cache"\ncache_file = f"{cache_dir}/sentinel2_timeseries_40scenes.nc"\n\nuse_cache = False\n\nif os.path.exists(cache_file):\n print(f"\\n✅ Cache file found: {cache_file}")\n \n # Get file info\n file_size_gb = os.path.getsize(cache_file) / (1024**3)\n print(f" File size: {file_size_gb:.2f} GB")\n \n # Try to load\n try:\n print(f"\\n🔄 Loading dataset from cache...")\n data = xr.open_dataset(cache_file)\n \n print(f"✅ Dataset loaded from cache!")\n print(f" Total scenes: {len(data[\'time\'])}")\n print(f" Variables: {len(data.data_vars)}")\n print(f" Dimensions: {dict(data.dims)}")\n print(f"\\n ⏭️ Skipping S3 download (using cached data)")\n \n use_cache = True\n \n except Exception as e:\n print(f"❌ Error loading cache: {e}")\n print(f" Will download fresh data from S3")\n use_cache = False\nelse:\n print(f"\\n⏳ Cache file not found: {cache_file}")\n print(f" Will download from S3 and save cache")\n print(f" (Next run will use cache automatically)")\n\nprint("="*70)\n') # In[5]: get_ipython().run_cell_magic('time', '', '# 💾 LOAD SENTINEL-2 DATA DIRECTLY FROM S3 COGS USING RASTERIO - WITH TEMPORAL FEATURES\nprint("="*70)\nprint("LOADING SENTINEL-2 DATA FROM S3 COGs (RASTERIO) - OPTIMAL ACCURACY")\nprint("="*70)\n\ntry:\n import rasterio\n import xarray as xr\n import numpy as np\n from scipy import ndimage\n \n # ===== CHECK IF SHOULD SKIP DOWNLOAD =====\n if use_cache and data is not None:\n print(f"\\n✅ Using cached dataset - skipping download!")\n print(f" Variables: {len(data.data_vars)}")\n print(f" Shape: {data.dims}")\n display(data)\n \n else:\n # ===== DOWNLOAD FROM S3 =====\n print(f"\\n📥 Downloading from S3...")\n \n # Get all scenes from datacube metadata\n datasets = list(dc.find_datasets(\n product=\'s2_l2a\',\n time=date_range\n ))\n \n if not datasets:\n raise ValueError("No datasets found for date range")\n \n print(f"\\n📦 Found {len(datasets)} available scenes")\n print(f" Date range: {date_range[0]} to {date_range[1]}")\n \n # ===== LOAD ALL SCENES WITH ALL AVAILABLE BANDS (NO MAGNIFICATION) =====\n print(f"\\n[LOADING] Loading ALL {len(datasets)} scenes with ALL available bands...")\n print(f" (Keeping NATIVE resolution - NO upsampling/magnification)")\n \n # num_scenes = len(datasets) # Load ALL scenes\n num_scenes = 1 # Load ALL scenes\n all_data_dict = {}\n failed_scenes = []\n scene_dates = []\n \n # Discover all available bands from first scene\n first_scene = datasets[0]\n all_available_bands = list(first_scene.measurements.keys())\n print(f" Available bands: {all_available_bands}")\n \n for scene_idx in range(num_scenes):\n selected = datasets[scene_idx]\n scene_label = selected.metadata.label\n scene_datetime = selected.time.begin if hasattr(selected.time, \'begin\') else selected.time\n scene_dates.append(scene_datetime)\n \n # Print progress every 5 scenes\n if scene_idx % 5 == 0 or scene_idx == 0 or scene_idx == num_scenes - 1:\n print(f"\\n [{scene_idx + 1:2d}/{num_scenes}] {scene_label} ({scene_datetime.date()})")\n \n # Load ALL available bands from S3 COGs\n scene_data_dict = {}\n \n for band_name in all_available_bands:\n if band_name in selected.measurements:\n band_path = selected.measurements[band_name][\'path\']\n \n try:\n with rasterio.open(band_path) as src:\n data_band = src.read(1)\n scene_data_dict[band_name] = data_band\n except Exception as e:\n if scene_idx % 5 == 0:\n print(f" ⚠️ Error loading {band_name}: {str(e)[:30]}")\n failed_scenes.append((scene_idx, scene_label, band_name, str(e)))\n \n if scene_data_dict:\n all_data_dict[scene_idx] = scene_data_dict\n if scene_idx % 5 == 0 or scene_idx == num_scenes - 1:\n print(f" ✅ {len(scene_data_dict)} bands loaded")\n else:\n failed_scenes.append((scene_idx, scene_label, "all", "No bands loaded"))\n \n if not all_data_dict:\n raise ValueError("Could not load any bands from any scene")\n \n print(f"\\n✅ Successfully loaded {len(all_data_dict)} scenes!")\n if failed_scenes:\n print(f"⚠️ Failed to load {len(failed_scenes)} band instances (will be skipped)")\n \n # ===== NORMALIZE RESOLUTION (No upsampling - just match to highest) =====\n print(f"\\n[RESOLUTION NORMALIZATION] Aligning all bands to native resolution (NO magnification)...")\n \n # Find max resolution\n ref_resolution = None\n max_size = 0\n max_band = None\n \n for scene_idx in all_data_dict.keys():\n for band_name, data_band in all_data_dict[scene_idx].items():\n size = data_band.shape[0]\n if size > max_size:\n max_size = size\n ref_resolution = size\n max_band = band_name\n \n print(f" Reference resolution: {max_size}×{max_size} pixels (native {max_band})")\n \n # Resample all bands to match reference resolution (both up and down)\n resampled_count = 0\n for scene_idx in all_data_dict.keys():\n for band_name in list(all_data_dict[scene_idx].keys()):\n band_data_arr = all_data_dict[scene_idx][band_name]\n current_size = band_data_arr.shape[0]\n \n if current_size != ref_resolution:\n scale_factor = ref_resolution / current_size\n \n # Resample to match reference resolution (both up and down)\n if band_name == \'scl\':\n resampled_data = ndimage.zoom(band_data_arr, scale_factor, order=0)\n else:\n resampled_data = ndimage.zoom(band_data_arr, scale_factor, order=1)\n \n all_data_dict[scene_idx][band_name] = resampled_data\n new_size = resampled_data.shape[0]\n if scene_idx == 0: # Print for first scene only to reduce clutter\n print(f" Resampling {band_name}: {current_size}×{current_size} → {new_size}×{new_size}")\n resampled_count += 1\n \n print(f"✅ Resolution normalization complete! ({resampled_count} bands resampled)")\n \n # ===== CALCULATE SPECTRAL INDICES FOR EACH SCENE =====\n print(f"\\n[SPECTRAL INDICES] Calculating spectral indices for each scene...")\n \n indices_count = 0\n for scene_idx in all_data_dict.keys():\n scene_data = all_data_dict[scene_idx]\n \n try:\n # NDVI: (NIR - Red) / (NIR + Red)\n if \'nir\' in scene_data and \'red\' in scene_data:\n nir = scene_data[\'nir\'].astype(float)\n red = scene_data[\'red\'].astype(float)\n ndvi = (nir - red) / (nir + red + 1e-8)\n scene_data[\'ndvi\'] = ndvi.astype(np.float32)\n indices_count += 1\n \n # NDBI: (SWIR - NIR) / (SWIR + NIR)\n if \'b11\' in scene_data and \'nir\' in scene_data:\n swir = scene_data[\'b11\'].astype(float)\n nir = scene_data[\'nir\'].astype(float)\n ndbi = (swir - nir) / (swir + nir + 1e-8)\n scene_data[\'ndbi\'] = ndbi.astype(np.float32)\n indices_count += 1\n \n # NDWI: (NIR - SWIR) / (NIR + SWIR)\n if \'nir\' in scene_data and \'b11\' in scene_data:\n nir = scene_data[\'nir\'].astype(float)\n swir = scene_data[\'b11\'].astype(float)\n ndwi = (nir - swir) / (nir + swir + 1e-8)\n scene_data[\'ndwi\'] = ndwi.astype(np.float32)\n indices_count += 1\n \n # EVI: Enhanced Vegetation Index\n if \'nir\' in scene_data and \'red\' in scene_data and \'blue\' in scene_data:\n nir = scene_data[\'nir\'].astype(float)\n red = scene_data[\'red\'].astype(float)\n blue = scene_data[\'blue\'].astype(float)\n evi = 2.5 * (nir - red) / (nir + 6*red - 7.5*blue + 1)\n scene_data[\'evi\'] = evi.astype(np.float32)\n indices_count += 1\n \n except Exception as e:\n pass\n \n print(f"✅ Calculated {indices_count} spectral indices per scene")\n \n # ===== STACK SCENES ALONG TIME DIMENSION =====\n print(f"\\n[STACKING] Stacking all {len(all_data_dict)} scenes to create time-series...")\n \n data_vars = {}\n band_names = list(all_data_dict[0].keys())\n \n for band_name in band_names:\n band_data_list = []\n for scene_idx in sorted(all_data_dict.keys()):\n if band_name in all_data_dict[scene_idx]:\n band_data_list.append(all_data_dict[scene_idx][band_name])\n \n if band_data_list:\n stacked = np.stack(band_data_list, axis=0)\n data_vars[band_name] = ([\'time\', \'y\', \'x\'], stacked)\n \n # Create xarray Dataset with time dimension\n first_band_data = list(all_data_dict[0].values())[0]\n y_size, x_size = first_band_data.shape\n \n data = xr.Dataset(\n data_vars,\n coords={\n \'time\': np.arange(len(all_data_dict)),\n \'x\': np.arange(x_size),\n \'y\': np.arange(y_size)\n }\n )\n \n # ===== CALCULATE TEMPORAL FEATURES FOR ACCURACY =====\n print(f"\\n[TEMPORAL FEATURES] Computing temporal features from time-series...")\n \n temporal_features_added = 0\n \n # For NDVI: temporal statistics\n if \'ndvi\' in data.data_vars:\n ndvi_ts = data[\'ndvi\']\n \n # Min NDVI (vegetation stress indicator)\n data[\'ndvi_min\'] = ndvi_ts.min(dim=\'time\')\n temporal_features_added += 1\n \n # Max NDVI (peak vegetation)\n data[\'ndvi_max\'] = ndvi_ts.max(dim=\'time\')\n temporal_features_added += 1\n \n # Mean NDVI\n data[\'ndvi_mean\'] = ndvi_ts.mean(dim=\'time\')\n temporal_features_added += 1\n \n # NDVI range (variability)\n data[\'ndvi_range\'] = data[\'ndvi_max\'] - data[\'ndvi_min\']\n temporal_features_added += 1\n \n # NDVI std (temporal consistency)\n data[\'ndvi_std\'] = ndvi_ts.std(dim=\'time\')\n temporal_features_added += 1\n \n # For all indices: mean values (aggregate features)\n for band_name in [\'ndbi\', \'ndwi\', \'evi\']:\n if band_name in data.data_vars:\n band_ts = data[band_name]\n data[f\'{band_name}_mean\'] = band_ts.mean(dim=\'time\')\n temporal_features_added += 1\n \n print(f"✅ Added {temporal_features_added} temporal/aggregate features")\n \n # ===== SAVE TO CACHE =====\n print(f"\\n[CACHE] Saving dataset to cache...")\n try:\n data.to_netcdf(cache_file, engine=\'netcdf4\')\n cache_size = os.path.getsize(cache_file) / (1024**3)\n print(f"✅ Dataset saved to cache: {cache_file}")\n print(f" Cache size: {cache_size:.2f} GB")\n except Exception as e:\n print(f"⚠️ Error saving cache: {e}")\n \n print(f"\\n✅ OPTIMAL Dataset with native resolution + temporal features created!")\n print(f" {\'=\'*70}")\n print(f" 🎬 Total scenes (time steps): {len(all_data_dict)}")\n print(f" 📊 Total bands/variables: {len(data.data_vars)}")\n print(f" 🖼️ Spatial size: {x_size} × {y_size} pixels (NATIVE resolution)")\n print(f" 📏 Native resolution: 10m (Sentinel-2 L2A)")\n print(f" ⏰ Temporal range: {scene_dates[0].date()} to {scene_dates[-1].date()}")\n print(f" 💾 Total dataset size: {notebook_utils.xarray_object_size(data)}")\n print(f" 💿 Cached at: {cache_file}")\n print(f" {\'=\'*70}")\n \n print(f"\\n Dataset dimensions:")\n for dim, size in data.dims.items():\n print(f" {dim}: {size}")\n \n print(f"\\n Variables ({len(data.data_vars)}):")\n spatial_vars = []\n temporal_vars = []\n for var_name in sorted(data.data_vars):\n if len(data[var_name].shape) == 3:\n spatial_vars.append(f"{var_name} {data[var_name].shape}")\n else:\n temporal_vars.append(f"{var_name} {data[var_name].shape}")\n \n print(f" Spatial time-series ({len(spatial_vars)}):")\n for v in spatial_vars:\n print(f" - {v}")\n print(f" Temporal aggregates ({len(temporal_vars)}):")\n for v in temporal_vars:\n print(f" - {v}")\n \n print(f" {\'=\'*70}")\n \n display(data)\n \n # ===== EXTRACT NDVI FOR TRAINING =====\n print(f"\\n[NDVI EXTRACTION] Extracting NDVI for model training...")\n if \'ndvi_mean\' in data.data_vars:\n # Use mean NDVI across time\n ndvi = data[\'ndvi_mean\']\n print(f"✅ NDVI extracted (mean across time)")\n print(f" Shape: {ndvi.shape}")\n elif \'ndvi\' in data.data_vars:\n # Use first time step if mean not available\n ndvi = data[\'ndvi\'].isel(time=0)\n print(f"✅ NDVI extracted (first time step)")\n print(f" Shape: {ndvi.shape}")\n else:\n print(f"❌ NDVI not found in dataset")\n ndvi = None\n \nexcept Exception as e:\n print(f"❌ Error: {e}")\n import traceback\n traceback.print_exc()\n data = None\n ndvi = None\n\nprint("="*70)\n') # In[6]: # 🎯 LOAD TRAINING DATA & EXTRACT FEATURES print("="*70) print("TRAINING DATA SETUP") print("="*70) # Load training points train_path = "train/ST_training data_updated_1130points_new.shp" print(f"\n[1] Loading training data: {train_path}") try: train = load_train_data(train_path) print(f" ✅ Loaded {len(train)} training points") print(f" Columns: {list(train.columns)}") train.head() except Exception as e: print(f" ❌ Error: {e}") train = None # Label mapping label_mapping = { "Lua tom": "0", "Lua": "1", "CHN": "2", "CLN": "3", "TS": "4", "Song": "5", "Dat xay dung": "6", "Rung": "7", } print(f"\n[2] Label mapping:") for label, code in label_mapping.items(): print(f" {code}: {label}") print("\n" + "="*70) # In[ ]: get_ipython().run_cell_magic('time', '', '# 🤖 LAND USE CLASSIFICATION MODEL TRAINING (MỤC TIÊU CHÍNH)\nprint("="*70)\nprint("LAND USE CLASSIFICATION TRAINING")\nprint("="*70)\nprint("\\n🎯 Mục tiêu: Dự đoán phân loại sử dụng đất (8 lớp)")\nprint(" - NDVI/NDWI/NDBI/EVI là INPUT FEATURES")\nprint(" - Sau khi predict xong → có thể hiển thị NDVI map như chỉ số phụ")\nprint("="*70)\n\nif train is not None and data is not None:\n print("\\n[1] Extracting MULTIPLE features from satellite data...")\n print(" (Sử dụng nhiều spectral indices để cải thiện accuracy)")\n \n try:\n # Extract features at training point locations\n X = []\n y = []\n \n # Available features from data\n available_features = [\'ndvi_mean\', \'ndvi_min\', \'ndvi_max\', \'ndvi_std\', \'ndvi_range\',\n \'ndwi_mean\', \'ndbi_mean\', \'evi_mean\']\n \n # Check which features are actually available\n features_to_use = [f for f in available_features if f in data.data_vars]\n \n if not features_to_use:\n print(" ❌ No spectral features found in dataset!")\n print(" Available variables:", list(data.data_vars))\n model = None\n else:\n print(f" Using {len(features_to_use)} features: {features_to_use}")\n \n for idx, point in train.iterrows():\n try:\n # Extract all available features at this point\n feature_vec = []\n for feat_name in features_to_use:\n feat_val = float(data[feat_name].sel(\n x=point.geometry.x, \n y=point.geometry.y, \n method=\'nearest\'\n ).values)\n feature_vec.append(feat_val)\n \n # Get label\n label = label_mapping[point.Hientrang]\n \n # Only add if no NaN values\n if not np.isnan(feature_vec).any():\n X.append(feature_vec)\n y.append(int(label))\n except Exception as e:\n # Skip points with errors\n continue\n \n if len(X) > 0:\n X = np.array(X)\n y = np.array(y)\n print(f" ✅ Extracted {len(X)} samples with {X.shape[1]} features each")\n \n # Show feature statistics\n print(f"\\n Feature statistics:")\n for i, feat_name in enumerate(features_to_use):\n print(f" {feat_name:15s}: mean={X[:,i].mean():.3f}, std={X[:,i].std():.3f}")\n \n # Split data\n print(f"\\n[2] Splitting data (80-20)...")\n from sklearn.model_selection import train_test_split\n X_train, X_test, y_train, y_test = train_test_split(\n X, y, test_size=0.2, random_state=42, stratify=y\n )\n print(f" Train: {len(X_train)}, Test: {len(X_test)}")\n \n # Show class distribution\n unique, counts = np.unique(y_train, return_counts=True)\n print(f"\\n Class distribution in training set:")\n for cls, count in zip(unique, counts):\n cls_name = [k for k, v in label_mapping.items() if v == str(cls)][0]\n print(f" {cls}: {cls_name:15s} - {count:4d} samples ({count/len(y_train)*100:.1f}%)")\n \n # Train model\n print(f"\\n[3] Training Random Forest for LAND USE CLASSIFICATION...")\n from xgboost import XGBClassifier\n from sklearn.metrics import accuracy_score, classification_report\n \n model = XGBClassifier(\n n_estimators=200,\n max_depth=30,\n tree_method="hist",\n device="cuda",\n random_state=42,\n n_jobs=-1,\n verbosity=1\n )\n model.fit(X_train, y_train)\n \n # Evaluate\n y_pred = model.predict(X_test)\n accuracy = accuracy_score(y_test, y_pred)\n \n print(f"\\n ✅ Model trained!")\n print(f" Training accuracy: {model.score(X_train, y_train)*100:.2f}%")\n print(f" Testing accuracy: {accuracy*100:.2f}%")\n \n # Show feature importance\n print(f"\\n Feature importance:")\n importances = model.feature_importances_\n indices = np.argsort(importances)[::-1]\n for i, idx in enumerate(indices):\n print(f" {i+1}. {features_to_use[idx]:15s}: {importances[idx]:.4f}")\n \n # Classification report\n print(f"\\n[4] Classification Report:")\n class_names = [k for k, v in sorted(label_mapping.items(), key=lambda x: x[1])]\n print(classification_report(y_test, y_pred, target_names=class_names, zero_division=0))\n \n else:\n print(f" ❌ No samples extracted")\n model = None\n \n except Exception as e:\n print(f" ❌ Error: {e}")\n import traceback\n traceback.print_exc()\n model = None\nelse:\n print("❌ Missing training data or satellite data")\n model = None\n\nprint("\\n" + "="*70)\nprint("📝 NOTE: Model này dự đoán PHÂN LOẠI SỬ DỤNG ĐẤT (8 lớp)")\nprint(" NDVI là một trong các features đầu vào, không phải mục tiêu dự đoán")\nprint(" Sau khi predict → có thể hiển thị NDVI map như chỉ số phụ")\nprint("="*70)\n') # In[ ]: # 💾 SAVE MODEL WITH METADATA print("="*70) print("MODEL SAVING") print("="*70) if model is not None: print("\n🔄 Saving trained LAND USE CLASSIFICATION model with metadata...") try: from datetime import datetime # Prepare metadata for ModelManager metadata = { "timestamp": datetime.now().isoformat(), "data_source": "Local S3 ODC (Open Data Cube)", "collections": ["sentinel-2-l2a"], "features": features_to_use, # All features used "feature_mode": "extended", # Using extended aggregate features "training_samples": len(X_train), "testing_samples": len(X_test), "test_size": 0.2, "train_accuracy": float(model.score(X_train, y_train)), "test_accuracy": float(accuracy), "model_type": "random_forest", "device": "cpu", "n_estimators": 200, "max_depth": 30, "learning_rate": None, "cnn_epochs": None, "n_features": X_train.shape[1], "n_classes": len(np.unique(y)), "class_names": list(label_mapping.keys()), "classification_report": classification_report(y_test, y_pred, target_names=class_names, output_dict=True, zero_division=0), "bbox": None, "time_range": f"{date_range[0]}/{date_range[1]}", "resolution": 10, "notes": "LAND USE CLASSIFICATION model trained from 01.train_ODC.ipynb. Predicts 8 land use classes using multiple spectral indices. NDVI is one of the input features, not the prediction target." } # Save model with metadata using updated save_model function save_model("model_land_use_odc.joblib", model, metadata=metadata, label_encoder=None) print("✅ Model saved to model_train/model_land_use_odc.joblib") print(f" - Purpose: Land Use Classification (8 classes)") print(f" - Features: {len(features_to_use)} ({', '.join(features_to_use[:3])}...)") print(f" - Train Accuracy: {metadata['train_accuracy']*100:.2f}%") print(f" - Test Accuracy: {metadata['test_accuracy']*100:.2f}%") print(f" - Classes: {metadata['n_classes']}") print(f"\n📝 NDVI là một trong các features, không phải prediction target") print(f" Sau khi predict → có thể tính NDVI map riêng để hiển thị") except Exception as e: print(f"❌ Error saving model: {e}") import traceback traceback.print_exc() else: print("❌ No model to save") print("="*70) # # 📖 Hướng dẫn sử dụng Model # # ## Mục đích của Model # # Model này được train để **DỰ ĐOÁN PHÂN LOẠI SỬ DỤNG ĐẤT** với 8 lớp: # # 1. **Lua tom** (0) - Lúa tôm # 2. **Lua** (1) - Lúa # 3. **CHN** (2) - Cây hàng năm # 4. **CLN** (3) - Cây lâu năm # 5. **TS** (4) - Thủy sản # 6. **Song** (5) - Sông # 7. **Dat xay dung** (6) - Đất xây dựng # 8. **Rung** (7) - Rừng # # ## Features đầu vào # # Model sử dụng **nhiều spectral indices** làm features: # - NDVI (mean, min, max, std, range) # - NDWI (mean) # - NDBI (mean) # - EVI (mean) # # ## NDVI là gì trong hệ thống này? # # ⚠️ **QUAN TRỌNG**: NDVI **KHÔNG PHẢI** là mục tiêu dự đoán! # # - **NDVI là INPUT FEATURE**: Một trong các chỉ số dùng để train model # - **Mục tiêu dự đoán**: Phân loại sử dụng đất (8 lớp) # - **NDVI map**: Có thể hiển thị NDVI map như chỉ số phụ sau khi predict xong # # ## Workflow Prediction # # ```python # # 1. Load model # model, label_encoder, metadata = model_manager.load_model("model_land_use_odc.joblib") # # # 2. Extract features từ satellite data # features = extract_features(satellite_data) # NDVI, NDWI, NDBI, EVI # # # 3. Predict land use classification # land_use_prediction = model.predict(features) # # → Kết quả: Mảng với giá trị 0-7 (8 lớp sử dụng đất) # # # 4. (Optional) Tính NDVI map riêng để hiển thị # ndvi_map = (NIR - Red) / (NIR + Red) # # → NDVI map chỉ để visualize, không phải prediction target # ``` # # ## So sánh với approach cũ # # | Approach | Features | Target | NDVI Role | # |----------|----------|--------|-----------| # | ❌ Cũ (sai) | Chỉ NDVI | 8 lớp đất | Input duy nhất | # | ✅ Mới (đúng) | NDVI + NDWI + NDBI + EVI | 8 lớp đất | Một trong nhiều features | # # ## Test Model # # ```python # # Test với website # # 1. Upload model_land_use_odc.joblib lên server # # 2. Chọn model trong prediction interface # # 3. Chọn vùng và thời gian # # 4. System sẽ tự động: # # - Extract features (NDVI, NDWI, NDBI, EVI) # # - Predict land use classification # # - (Optional) Generate NDVI visualization map # ``` # In[ ]: # 🛑 CLEANUP print("="*70) print("CLEANUP") print("="*70) print("\n🔄 Closing Dask client and cluster...") try: client.close() cluster.close() print("✅ Cleanup complete") except Exception as e: print(f"⚠️ Error during cleanup: {e}") print("\n" + "="*70) print("✅ PIPELINE COMPLETE") print("="*70)