hoàn thành cơ bản các chức năng
This commit is contained in:
+185
-5
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user