hoàn thành cơ bản các chức năng

This commit is contained in:
Victor Phan
2025-12-14 18:26:25 +07:00
parent 97ab1f464e
commit 7952cfdd53
20 changed files with 1105 additions and 168 deletions
+185 -5
View File
@@ -68,11 +68,15 @@ class TrainingConfig(BaseModel):
resolution: int = 20 # 10m hoặc 20m
# Model parameters
model_type: str = "xgboost" # xgboost, random_forest, decision_tree, svm, cnn
n_estimators: int = 100
max_depth: int = 20
learning_rate: float = 0.1
use_gpu: bool = True
# Cache
use_cache: bool = True # Cache dataset để test nhanh hơn
# Training data
training_shapefile: str = "train/ST_training data_updated_1130points_new.shp"
@@ -205,6 +209,90 @@ async def stop_training():
return {"message": "Đang dừng training..."}
@app.post("/api/cache/clear")
async def clear_cache():
"""Xóa cache dataset"""
import shutil
cache_dir = Path("dataset_cache")
if not cache_dir.exists():
return {"message": "Không có cache để xóa", "deleted": 0}
# Count files
cache_files = list(cache_dir.glob("*.joblib"))
count = len(cache_files)
# Delete all cache files
for cache_file in cache_files:
try:
cache_file.unlink()
except:
pass
return {"message": f"Đã xóa {count} file cache", "deleted": count}
@app.get("/api/cache/info")
async def get_cache_info():
"""Lấy thông tin về cache với metadata đầy đủ"""
cache_dir = Path("dataset_cache")
if not cache_dir.exists():
return {"exists": False, "files": [], "total_size_mb": 0}
cache_files = []
total_size = 0
for cache_file in cache_dir.glob("*.joblib"):
size = cache_file.stat().st_size
total_size += size
# Try to load metadata from cache
metadata = {}
try:
cached_data = joblib.load(cache_file)
if isinstance(cached_data, dict):
metadata = {
"bbox": cached_data.get("bbox", []),
"time_range": cached_data.get("time_range", ""),
"resolution": cached_data.get("resolution", 20),
"n_samples": len(cached_data.get("features", [])),
"created": cached_data.get("timestamp", "")
}
# Parse time_range to get start/end dates
if metadata["time_range"]:
time_parts = metadata["time_range"].split("/")
if len(time_parts) == 2:
metadata["start_date"] = time_parts[0]
metadata["end_date"] = time_parts[1]
# Parse bbox to get min/max lon/lat
if metadata["bbox"] and len(metadata["bbox"]) == 4:
metadata["min_lon"] = metadata["bbox"][0]
metadata["min_lat"] = metadata["bbox"][1]
metadata["max_lon"] = metadata["bbox"][2]
metadata["max_lat"] = metadata["bbox"][3]
except Exception as e:
print(f"Error loading cache metadata: {e}")
cache_files.append({
"filename": cache_file.name,
"size_mb": round(size / 1024 / 1024, 2),
"modified": datetime.fromtimestamp(cache_file.stat().st_mtime).isoformat(),
"metadata": metadata
})
# Sort by modified time (newest first)
cache_files.sort(key=lambda x: x["modified"], reverse=True)
return {
"exists": True,
"files": cache_files,
"count": len(cache_files),
"total_size_mb": round(total_size / 1024 / 1024, 2)
}
@app.get("/api/models/list")
async def list_models():
"""Liệt kê các model đã train"""
@@ -288,10 +376,12 @@ async def run_training(config: TrainingConfig):
cloud_cover=config.cloud_cover,
resolution=config.resolution,
training_shapefile=config.training_shapefile,
model_type=config.model_type,
n_estimators=config.n_estimators,
max_depth=config.max_depth,
learning_rate=config.learning_rate,
use_gpu=config.use_gpu,
use_cache=config.use_cache,
status_callback=lambda msg: update_progress(msg),
cancel_check=should_cancel
)
@@ -360,6 +450,16 @@ async def run_prediction(config: PredictionConfig):
model = model_data
label_encoder = None
# Check if it's a CNN model (PyTorch)
is_cnn_model = hasattr(model, '__class__') and 'CNN' in model.__class__.__name__
if is_cnn_model:
prediction_status["progress"] = "Phát hiện PyTorch CNN model..."
# Import PyTorch if needed
try:
import torch
except ImportError:
raise ImportError("PyTorch is required for CNN prediction. Install: pip install torch")
prediction_status["progress"] = "Đang kết nối Microsoft Planetary Computer..."
# Import and use Microsoft Planetary Computer STAC API
@@ -447,14 +547,13 @@ async def run_prediction(config: PredictionConfig):
s1_items = s1_items[:config.max_scenes]
prediction_status["progress"] = f"Đang xử lý {len(s1_items)} scenes Sentinel-1..."
# Load Sentinel-1 data
# Load Sentinel-1 data (without like= to avoid conflict with bbox/resolution)
s1_data = load(
s1_items,
bbox=bbox,
chunks={"time": 1, "x": 2048, "y": 2048},
groupby="sat:absolute_orbit",
resolution=config.resolution,
like=ndvi_monthly # Align with NDVI grid
resolution=config.resolution
)
# Extract VH and VV bands
@@ -514,14 +613,52 @@ async def run_prediction(config: PredictionConfig):
vv_t = np.resize(vv_t, n_pixels)
features = np.column_stack([features, vv_t])
# Handle NaN values in features
# Handle NaN values in features✓ CNN PyTorch: Mạnh nhất với ảnh vệ tinh, tự học features, tương thích GPU tốt, cần pip install torch
features = np.nan_to_num(features, nan=0.0)
# ============ BƯỚC 6: DỰ ĐOÁN ============
# Check model's expected feature count and adjust
try:
# Get expected number of features from model
if is_cnn_model:
# For PyTorch CNN, get n_features from model
expected_features = model.n_features
elif hasattr(model, 'n_features_in_'):
expected_features = model.n_features_in_
elif hasattr(model, 'feature_names_in_'):
expected_features = len(model.feature_names_in_)
else:
# Try to get from booster for XGBoost
try:
expected_features = model.get_booster().num_features()
except:
expected_features = features.shape[1]
prediction_status["progress"] = f"Model cần {expected_features} features, đang có {features.shape[1]} features..."
# Adjust features to match model
if features.shape[1] > expected_features:
# Trim to expected number (use only first N features - NDVI only)
prediction_status["progress"] = f"Cắt bớt features từ {features.shape[1]} xuống {expected_features}..."
features = features[:, :expected_features]
elif features.shape[1] < expected_features:
# Pad with zeros or repeat last features
prediction_status["progress"] = f"Thêm features từ {features.shape[1]} lên {expected_features}..."
n_missing = expected_features - features.shape[1]
# Repeat last feature column to fill
padding = np.tile(features[:, -1:], (1, n_missing))
features = np.column_stack([features, padding])
except Exception as e:
prediction_status["progress"] = f"Không thể xác định số features của model, tiếp tục với {features.shape[1]} features..."
prediction_status["progress"] = f"Đang dự đoán với {features.shape[1]} features..."
# Make prediction
predictions = model.predict(features)
if is_cnn_model:
# PyTorch CNN prediction
predictions = model.predict(features)
else:
predictions = model.predict(features)
# Decode labels if label_encoder exists
if label_encoder is not None:
@@ -594,6 +731,49 @@ async def run_prediction(config: PredictionConfig):
print(traceback.format_exc())
@app.get("/api/predictions/list")
async def list_predictions():
"""Lấy danh sách các file prediction đã tạo"""
predictions_dir = Path("predictions")
predictions_dir.mkdir(exist_ok=True)
predictions = []
for pred_file in predictions_dir.glob("*.tif"):
predictions.append({
"filename": pred_file.name,
"created": datetime.fromtimestamp(pred_file.stat().st_mtime).isoformat(),
"size_mb": round(pred_file.stat().st_size / 1024 / 1024, 2),
"download_url": f"/api/predictions/download/{pred_file.name}"
})
# Sort by creation time (newest first)
predictions.sort(key=lambda x: x["created"], reverse=True)
return {"predictions": predictions}
@app.get("/api/predictions/download/{filename}")
async def download_prediction(filename: str):
"""Download file prediction GeoTIFF"""
predictions_dir = Path("predictions")
file_path = predictions_dir / filename
# Security check: ensure filename doesn't contain path traversal
if ".." in filename or "/" in filename or "\\" in filename:
raise HTTPException(status_code=400, detail="Invalid filename")
if not file_path.exists():
raise HTTPException(status_code=404, detail=f"File không tồn tại: {filename}")
return FileResponse(
path=str(file_path),
filename=filename,
media_type="image/tiff",
headers={
"Content-Disposition": f"attachment; filename={filename}"
}
)
if __name__ == "__main__":
print("=" * 70)
print("🚀 LAND CLASSIFICATION TRAINING API SERVER")