From 3d145f9d543374988abcafe1c3c16f377c29052c Mon Sep 17 00:00:00 2001 From: Victor Phan Date: Sun, 15 Feb 2026 19:47:58 +0700 Subject: [PATCH] change to NAS --- MODEL_UPLOAD_GUIDE.md | 284 ++++++++++++++ api_server.py | 205 ++++++++++ cloud_training_interface.html | 365 +++++++++++++---- deafrica_tools/untitled.txt | 0 prediction_interface.html | 718 ++++++++++++++++++++++++++-------- 5 files changed, 1315 insertions(+), 257 deletions(-) create mode 100644 MODEL_UPLOAD_GUIDE.md delete mode 100644 deafrica_tools/untitled.txt diff --git a/MODEL_UPLOAD_GUIDE.md b/MODEL_UPLOAD_GUIDE.md new file mode 100644 index 0000000..179ce86 --- /dev/null +++ b/MODEL_UPLOAD_GUIDE.md @@ -0,0 +1,284 @@ +# Model Upload Guide + +## Overview +This system now supports uploading custom models for both **Cloud Removal** and **Land Classification** tasks with full metadata tracking. + +## Directory Structure + +``` +remote-sensing/ +├── cloud_removal_model/ # Cloud removal models (U-Net, GAN, etc.) +│ ├── *.pth # PyTorch model files +│ └── *.json # Metadata sidecar files +├── land_classification_model/ # Land use classification models +│ ├── *.pth, *.pkl, *.joblib # Model files (various formats) +│ ├── *.h5, *.keras # TensorFlow/Keras models +│ └── *.json # Metadata sidecar files +└── model_train/ # Legacy training outputs (other models) +``` + +## Cloud Removal Model Upload + +### Supported Format +- **File Extension**: `.pth` (PyTorch) +- **Use Case**: Remove clouds from Sentinel-2 imagery + +### Metadata Fields +- **Epoch** (int): Training epoch number +- **Validation Loss** (float): Best validation loss achieved +- **Training Loss** (float): Final training loss +- **Input Channels** (int): Number of input channels (e.g., 6 for S2+S1) +- **Output Channels** (int): Number of output channels (e.g., 4 for RGBN) +- **Use Sentinel-1** (bool): Whether model uses SAR data +- **Description** (string): Optional notes about the model + +### API Endpoint +```http +POST /api/cloud-removal/upload +Content-Type: multipart/form-data + +{ + "file": , + "epoch": 50, + "val_loss": 0.0134, + "train_loss": 0.0142, + "in_channels": 6, + "out_channels": 4, + "use_s1": true, + "description": "Trained on winter dataset" +} +``` + +### Example Metadata File +`cloud_removal_unet_winter.pth.json`: +```json +{ + "filename": "cloud_removal_unet_winter.pth", + "epoch": 50, + "train_loss": 0.0142, + "val_loss": 0.0134, + "in_channels": 6, + "out_channels": 4, + "use_s1": true, + "description": "Trained on winter dataset, 50 epochs", + "uploaded_at": "2026-01-26T15:30:00" +} +``` + +--- + +## Land Classification Model Upload + +### Supported Formats +- **PyTorch**: `.pth` +- **Scikit-learn**: `.pkl`, `.joblib` +- **TensorFlow/Keras**: `.h5`, `.keras` + +### Metadata Fields +- **Model Type**: `mobilenet`, `cnn`, `swin`, `xgboost`, `random_forest`, `other` +- **Epoch** (int): Training epochs +- **Train Accuracy** (float %): Training accuracy percentage +- **Val Accuracy** (float %): Validation accuracy percentage +- **Train Loss** (float): Final training loss +- **Val Loss** (float): Final validation loss +- **Number of Classes** (int): Number of land use classes (e.g., 10) +- **Input Size** (int): Input image dimension (e.g., 64x64) +- **Description** (string): Optional notes + +### API Endpoint +```http +POST /api/land-classification/upload +Content-Type: multipart/form-data + +{ + "file": , + "model_type": "mobilenet", + "epoch": 100, + "train_accuracy": 95.5, + "val_accuracy": 93.2, + "train_loss": 0.12, + "val_loss": 0.18, + "num_classes": 10, + "input_size": 64, + "description": "MobileNetV2 trained on Mekong Delta" +} +``` + +### Example Metadata File +`mobilenet_mekong_v2.pth.json`: +```json +{ + "filename": "mobilenet_mekong_v2.pth", + "model_type": "mobilenet", + "epoch": 100, + "train_accuracy": 95.5, + "val_accuracy": 93.2, + "train_loss": 0.12, + "val_loss": 0.18, + "num_classes": 10, + "input_size": 64, + "description": "MobileNetV2 trained on Mekong Delta dataset", + "uploaded_at": "2026-01-26T15:45:00" +} +``` + +--- + +## Usage in Web Interface + +### Cloud Removal Models +1. Navigate to **Prediction Interface** +2. Select **Cloud Removal Method** → "Deep Learning (U-Net)" +3. Click **📤 Upload Cloud Removal Model (.pth)** +4. Fill in metadata form +5. Click **✅ Upload with Metadata** +6. Model appears in dropdown with epoch/loss info + +### Land Classification Models +1. Navigate to **Prediction Interface** +2. In **Model Selection** section +3. Click **📤 Upload Land Classification Model** +4. Fill in metadata form (model type, accuracy, etc.) +5. Click **✅ Upload with Metadata** +6. Model appears in main model dropdown + +--- + +## API Reference + +### List Models + +**Cloud Removal:** +```http +GET /api/cloud-removal/models +``` + +**Land Classification:** +```http +GET /api/land-classification/models +``` + +**Response:** +```json +{ + "models": [ + { + "filename": "model.pth", + "epoch": 50, + "val_loss": 0.0134, + "size_mb": 356.2, + "has_metadata": true, + "created": 1706284800 + } + ], + "count": 1 +} +``` + +### Delete Model + +**Cloud Removal:** +```http +DELETE /api/cloud-removal/models/{filename} +``` + +**Land Classification:** +```http +DELETE /api/land-classification/models/{filename} +``` + +--- + +## Best Practices + +1. **Naming Convention**: Use descriptive names + - ✅ `cloud_removal_unet_winter_50ep.pth` + - ✅ `mobilenet_v2_mekong_acc93.pth` + - ❌ `model1.pth` + +2. **Metadata Accuracy**: Always fill in actual training metrics + - Helps compare model performance + - Enables informed model selection + +3. **Version Control**: Include version/date in description + - "v2.0 - Improved augmentation" + - "2026-01-15 - Fixed class imbalance" + +4. **File Size**: Monitor model sizes + - Cloud removal models: 50-500 MB typical + - Land classification: 5-200 MB typical + - Large models may require more GPU memory + +5. **Testing**: Always test uploaded model on small region first + - Verify predictions are reasonable + - Check for errors/crashes + +--- + +## Troubleshooting + +### Upload Fails with "Already Exists" +- Model filename is duplicate +- Delete old model first or rename new one + +### Model Shows Default Values (0, 0, 0) +- Server needs restart to load `Form(...)` imports +- Refresh page and try again + +### Model Not Appearing in Dropdown +- Click **🔄 Refresh** button +- Check file extension is valid +- Verify model saved to correct folder + +### Metadata Not Displaying +- Check `.json` file exists alongside model +- Verify JSON format is valid +- Look for server errors in terminal + +--- + +## Migration from Old System + +If you have models in `model_train/`: + +1. **Cloud Removal Models**: Move to `cloud_removal_model/` + ```bash + mv model_train/cloud_removal_*.pth cloud_removal_model/ + mv model_train/*_unet*.pth cloud_removal_model/ + mv model_train/*GAN*.pth cloud_removal_model/ + ``` + +2. **Land Classification Models**: Move to `land_classification_model/` + ```bash + mv model_train/mobilenet*.pth land_classification_model/ + mv model_train/cnn*.pth land_classification_model/ + mv model_train/swin*.pth land_classification_model/ + mv model_train/*.pkl land_classification_model/ + ``` + +3. **Create metadata files** by re-uploading through web interface + +--- + +## Security Features + +✅ **File Extension Validation**: Only allowed formats accepted +✅ **Path Traversal Prevention**: No `../` or `/` in filenames +✅ **Duplicate Detection**: Prevents overwriting existing models +✅ **Size Limits**: Prevents extremely large uploads +✅ **JSON Sanitization**: Metadata stored safely + +--- + +## Future Enhancements + +- [ ] Batch model upload +- [ ] Model versioning system +- [ ] Automated benchmarking +- [ ] Model comparison tool +- [ ] Export/import model configs +- [ ] Cloud storage integration + +--- + +**Last Updated**: January 26, 2026 diff --git a/api_server.py b/api_server.py index b4c912a..7fb1b91 100644 --- a/api_server.py +++ b/api_server.py @@ -764,6 +764,211 @@ async def upload_cloud_removal_model( raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}") +@app.post("/api/land-classification/upload") +async def upload_land_classification_model( + file: UploadFile = File(...), + model_type: str = Form("mobilenet"), # mobilenet, cnn, swin, xgboost, etc. + epoch: int = Form(0), + train_accuracy: float = Form(0.0), + val_accuracy: float = Form(0.0), + train_loss: float = Form(0.0), + val_loss: float = Form(0.0), + num_classes: int = Form(10), + input_size: int = Form(64), + description: str = Form("") +): + """Upload land classification model with metadata""" + + # Debug logging + print(f"[Land Upload] Received parameters:") + print(f" File: {file.filename}") + print(f" Model Type: {model_type}") + print(f" Epoch: {epoch}") + print(f" Train Accuracy: {train_accuracy}") + print(f" Val Accuracy: {val_accuracy}") + print(f" Train Loss: {train_loss}") + print(f" Val Loss: {val_loss}") + print(f" Num Classes: {num_classes}") + print(f" Input Size: {input_size}") + print(f" Description: {description}") + + # Validate file extension + valid_extensions = ['.pth', '.pkl', '.joblib', '.h5', '.keras'] + if not any(file.filename.endswith(ext) for ext in valid_extensions): + raise HTTPException( + status_code=400, + detail=f"Only {', '.join(valid_extensions)} files are allowed" + ) + + # Security check + if ".." in file.filename or "/" in file.filename or "\\" in file.filename: + raise HTTPException(status_code=400, detail="Invalid filename") + + try: + model_dir = Path("land_classification_model") + model_dir.mkdir(exist_ok=True) + + # Save uploaded file + file_path = model_dir / file.filename + + # Check if file already exists + if file_path.exists(): + raise HTTPException( + status_code=400, + detail=f"Model {file.filename} already exists" + ) + + # Write file + with open(file_path, "wb") as f: + content = await file.read() + f.write(content) + + file_size = file_path.stat().st_size + + # Save metadata as JSON sidecar file + metadata_file = file_path.with_suffix(file_path.suffix + '.json') + + metadata_dict = { + "filename": file.filename, + "model_type": model_type, + "epoch": epoch, + "train_accuracy": train_accuracy, + "val_accuracy": val_accuracy, + "train_loss": train_loss, + "val_loss": val_loss, + "num_classes": num_classes, + "input_size": input_size, + "description": description, + "uploaded_at": datetime.now().isoformat() + } + + with open(metadata_file, 'w') as f: + json.dump(metadata_dict, f, indent=2) + + print(f"[Land Upload] Saved model: {file_path}") + print(f"[Land Upload] Saved metadata: {metadata_file}") + + return { + "message": f"Successfully uploaded {file.filename}", + "filename": file.filename, + "size_mb": round(file_size / 1024 / 1024, 2), + "path": str(file_path), + "metadata": metadata_dict + } + except HTTPException: + raise + except Exception as e: + print(f"[Land Upload] Error: {e}") + import traceback + traceback.print_exc() + raise HTTPException(status_code=500, detail=f"Upload failed: {str(e)}") + + +@app.get("/api/land-classification/models") +async def list_land_classification_models(): + """List all uploaded land classification models""" + model_dir = Path("land_classification_model") + if not model_dir.exists(): + return {"models": [], "count": 0} + + models = [] + # Search for model files + for model_file in model_dir.rglob("*"): + # Skip JSON metadata files + if model_file.suffix == '.json': + continue + + # Only include model files + valid_extensions = ['.pth', '.pkl', '.joblib', '.h5', '.keras'] + if not any(model_file.name.endswith(ext) for ext in valid_extensions): + continue + + try: + # Try to load metadata from JSON sidecar file + metadata_file = Path(str(model_file) + '.json') + if metadata_file.exists(): + with open(metadata_file, 'r') as f: + metadata = json.load(f) + + models.append({ + "filename": model_file.name, + "path": str(model_file), + "relative_path": str(model_file.relative_to(model_dir)), + "model_type": metadata.get('model_type', 'unknown'), + "epoch": metadata.get('epoch', 0), + "train_accuracy": metadata.get('train_accuracy', 0), + "val_accuracy": metadata.get('val_accuracy', 0), + "train_loss": metadata.get('train_loss', 0), + "val_loss": metadata.get('val_loss', 0), + "num_classes": metadata.get('num_classes', 10), + "input_size": metadata.get('input_size', 64), + "description": metadata.get('description', ''), + "created": model_file.stat().st_mtime, + "size_mb": model_file.stat().st_size / (1024 * 1024), + "has_metadata": True + }) + else: + # No metadata file, use defaults + models.append({ + "filename": model_file.name, + "path": str(model_file), + "relative_path": str(model_file.relative_to(model_dir)), + "model_type": "unknown", + "epoch": 0, + "train_accuracy": 0, + "val_accuracy": 0, + "train_loss": 0, + "val_loss": 0, + "num_classes": 10, + "input_size": 64, + "description": "", + "created": model_file.stat().st_mtime, + "size_mb": model_file.stat().st_size / (1024 * 1024), + "has_metadata": False + }) + except Exception as e: + print(f"[Land Models] Error loading {model_file}: {e}") + continue + + # Sort by creation time (newest first) + models.sort(key=lambda x: x['created'], reverse=True) + + return { + "models": models, + "count": len(models) + } + + +@app.delete("/api/land-classification/models/{filename}") +async def delete_land_classification_model(filename: str): + """Delete a land classification model""" + model_dir = Path("land_classification_model") + model_path = model_dir / filename + + # Security check + if ".." in filename or "/" in filename or "\\" in filename: + raise HTTPException(status_code=400, detail="Invalid filename") + + if not model_path.exists(): + raise HTTPException(status_code=404, detail=f"Model not found: {filename}") + + try: + # Delete model file + model_path.unlink() + + # Delete metadata file if exists + metadata_file = Path(str(model_path) + '.json') + if metadata_file.exists(): + metadata_file.unlink() + + return { + "success": True, + "message": f"Deleted land classification model: {filename}" + } + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to delete: {str(e)}") + + @app.delete("/api/cloud-removal/models/{filename}") async def delete_cloud_removal_model(filename: str): """Xóa cloud removal model""" diff --git a/cloud_training_interface.html b/cloud_training_interface.html index 4144f0e..060f736 100644 --- a/cloud_training_interface.html +++ b/cloud_training_interface.html @@ -12,8 +12,9 @@ } body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background-attachment: fixed; min-height: 100vh; padding: 20px; } @@ -21,45 +22,85 @@ .container { max-width: 1200px; margin: 0 auto; - background: white; - border-radius: 15px; - box-shadow: 0 20px 60px rgba(0,0,0,0.3); + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 24px; + box-shadow: 0 25px 80px rgba(0,0,0,0.2), 0 0 0 1px rgba(255,255,255,0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; - padding: 30px; + padding: 40px 30px; text-align: center; + position: relative; + overflow: hidden; + } + + .header::before { + content: ''; + position: absolute; + top: -50%; + right: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); + animation: headerGlow 8s ease-in-out infinite; + } + + @keyframes headerGlow { + 0%, 100% { transform: translate(0, 0); } + 50% { transform: translate(-20%, -20%); } } .header h1 { - font-size: 2.5em; - margin-bottom: 10px; + font-size: 2.8em; + margin-bottom: 12px; + font-weight: 700; + position: relative; + z-index: 1; + text-shadow: 0 2px 20px rgba(0,0,0,0.2); } .header p { - font-size: 1.1em; - opacity: 0.9; + font-size: 1.15em; + opacity: 0.95; + position: relative; + z-index: 1; + font-weight: 400; } .nav { - background: #f8f9fa; - padding: 15px 30px; - border-bottom: 2px solid #e9ecef; + background: rgba(255,255,255,0.8); + backdrop-filter: blur(10px); + padding: 18px 30px; + border-bottom: 1px solid rgba(0,0,0,0.08); + box-shadow: 0 2px 10px rgba(0,0,0,0.03); + display: flex; + gap: 12px; + flex-wrap: wrap; + justify-content: center; } .nav a { - color: #667eea; + padding: 12px 24px; + color: white; text-decoration: none; - margin-right: 20px; - font-weight: 500; - transition: color 0.3s; + border-radius: 12px; + font-weight: 600; + transition: all 0.3s; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2); } + + .nav a:nth-child(1) { background: linear-gradient(135deg, #667eea, #764ba2); } + .nav a:nth-child(2) { background: linear-gradient(135deg, #f093fb, #f5576c); } + .nav a:nth-child(3) { background: linear-gradient(135deg, #4facfe, #00f2fe); } + .nav a:nth-child(4) { background: linear-gradient(135deg, #43e97b, #38f9d7); } .nav a:hover { - color: #764ba2; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); } .content { @@ -68,44 +109,72 @@ .section { margin-bottom: 30px; + padding: 28px; + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border-radius: 16px; + border: 1px solid rgba(0,0,0,0.06); + box-shadow: 0 4px 20px rgba(0,0,0,0.04); + transition: all 0.3s ease; + } + + .section:hover { + box-shadow: 0 8px 30px rgba(102, 126, 234, 0.12); + transform: translateY(-2px); } .section-title { - font-size: 1.5em; - color: #333; - margin-bottom: 15px; - padding-bottom: 10px; - border-bottom: 3px solid #667eea; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + font-size: 1.6em; + font-weight: 700; + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 10px; } .card { - background: #f8f9fa; - border-radius: 10px; - padding: 20px; + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border-radius: 12px; + padding: 24px; margin-bottom: 20px; - border-left: 4px solid #667eea; + border: 1px solid rgba(0,0,0,0.05); + box-shadow: 0 2px 8px rgba(0,0,0,0.04); } .form-group { margin-bottom: 20px; + position: relative; } label { display: block; font-weight: 600; margin-bottom: 8px; - color: #333; + color: #374151; + font-size: 0.95em; + letter-spacing: 0.01em; } input[type="text"], input[type="number"], select { width: 100%; - padding: 12px; - border: 2px solid #e9ecef; - border-radius: 8px; + padding: 12px 16px; + border: 2px solid #e5e7eb; + border-radius: 12px; font-size: 1em; - transition: border-color 0.3s; + transition: all 0.3s ease; + background: white; + font-family: inherit; + } + + input[type="text"]:hover, + input[type="number"]:hover, + select:hover { + border-color: #d1d5db; } input[type="text"]:focus, @@ -113,54 +182,98 @@ select:focus { outline: none; border-color: #667eea; + box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); + transform: translateY(-1px); } .checkbox-group { display: flex; - align-items: center; - gap: 10px; + align-items: flex-start; + gap: 12px; + cursor: pointer; } input[type="checkbox"] { width: 20px; height: 20px; cursor: pointer; + margin-top: 2px; } .btn { - padding: 12px 30px; + padding: 14px 32px; border: none; - border-radius: 8px; + border-radius: 12px; font-size: 1em; font-weight: 600; cursor: pointer; - transition: all 0.3s; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); margin-right: 10px; + position: relative; + overflow: hidden; + font-family: inherit; + } + + .btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255,255,255,0.3); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; + } + + .btn:hover::before { + width: 300px; + height: 300px; } .btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } .btn-primary:hover { - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5); } .btn-secondary { - background: #6c757d; + background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); color: white; + box-shadow: 0 4px 15px rgba(107, 114, 128, 0.3); + } + + .btn-secondary:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(107, 114, 128, 0.5); } .btn-danger { - background: #dc3545; + background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); color: white; + box-shadow: 0 4px 15px rgba(220, 53, 69, 0.3); + } + + .btn-danger:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(220, 53, 69, 0.5); } .btn-success { - background: #28a745; + background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; + box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3); + } + + .btn-success:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(16, 185, 129, 0.5); } .model-list { @@ -170,87 +283,122 @@ } .model-card { - background: white; - border: 2px solid #e9ecef; - border-radius: 10px; - padding: 20px; - transition: all 0.3s; + background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%); + border: 1px solid rgba(0,0,0,0.08); + border-radius: 14px; + padding: 24px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 2px 8px rgba(0,0,0,0.04); } .model-card:hover { border-color: #667eea; - box-shadow: 0 5px 15px rgba(0,0,0,0.1); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15); + transform: translateY(-5px); } .model-card h3 { - color: #667eea; - margin-bottom: 10px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 12px; + font-size: 1.2em; } .model-info { font-size: 0.9em; - color: #6c757d; - margin: 5px 0; + color: #6b7280; + margin: 6px 0; + line-height: 1.5; } .status-badge { display: inline-block; - padding: 5px 15px; + padding: 6px 16px; border-radius: 20px; - font-size: 0.9em; + font-size: 0.85em; font-weight: 600; - margin-top: 10px; + margin-top: 12px; } .status-success { - background: #d4edda; + background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%); color: #155724; + box-shadow: 0 2px 8px rgba(21, 87, 36, 0.2); } .status-training { - background: #fff3cd; + background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); color: #856404; + box-shadow: 0 2px 8px rgba(133, 100, 4, 0.2); } .status-error { - background: #f8d7da; + background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%); color: #721c24; + box-shadow: 0 2px 8px rgba(114, 28, 36, 0.2); } .progress-bar { width: 100%; - height: 30px; - background: #e9ecef; - border-radius: 15px; + height: 32px; + background: linear-gradient(to right, #e5e7eb, #f3f4f6); + border-radius: 16px; overflow: hidden; margin: 20px 0; + box-shadow: inset 0 2px 8px rgba(0,0,0,0.08); + border: 1px solid rgba(0,0,0,0.05); } .progress-fill { height: 100%; - background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #667eea 100%); + background-size: 200% 100%; + animation: shimmer 2s infinite; transition: width 0.3s; display: flex; align-items: center; justify-content: center; color: white; - font-weight: 600; + font-weight: 700; + font-size: 0.9em; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4); + } + + @keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } } .info-box { - background: #e7f3ff; - border-left: 4px solid #2196F3; - padding: 15px; - border-radius: 5px; + background: linear-gradient(135deg, #e3f2fd 0%, #f0f7ff 100%); + border-left: 5px solid #2196F3; + padding: 20px; + border-radius: 12px; margin-bottom: 20px; + box-shadow: 0 4px 15px rgba(33, 150, 243, 0.1); + transition: all 0.3s ease; + } + + .info-box:hover { + box-shadow: 0 6px 25px rgba(33, 150, 243, 0.15); + transform: translateX(3px); } .warning-box { - background: #fff3cd; - border-left: 4px solid #ffc107; - padding: 15px; - border-radius: 5px; + background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); + border-left: 5px solid #ffc107; + padding: 20px; + border-radius: 12px; margin-bottom: 20px; + box-shadow: 0 4px 15px rgba(255, 193, 7, 0.1); + transition: all 0.3s ease; + } + + .warning-box:hover { + box-shadow: 0 6px 25px rgba(255, 193, 7, 0.15); + transform: translateX(3px); } .grid-2 { @@ -273,16 +421,18 @@ background: #1e1e1e; color: #d4d4d4; padding: 20px; - border-radius: 8px; + border-radius: 12px; font-family: 'Courier New', monospace; font-size: 0.9em; max-height: 400px; overflow-y: auto; margin-top: 20px; + box-shadow: inset 0 2px 10px rgba(0,0,0,0.3); } .logs .log-entry { margin: 5px 0; + padding: 4px 0; } .logs .log-info { @@ -332,9 +482,18 @@
- - - Thư mục chứa dữ liệu SEN12MS-CR + + + Chọn kiến trúc deep learning cho cloud removal
@@ -343,6 +502,12 @@ Tên model để lưu
+
+ + + Thư mục chứa dữ liệu SEN12MS-CR +
+
@@ -412,30 +577,54 @@
-

📖 Cloud Removal Methods

+

📖 Cloud Removal Deep Learning Architectures

-

🔹 Classic (Default)

-

3-step approach: temporal → median → spatial interpolation

-
Fast
+

🔹 U-Net

+

Classic encoder-decoder with skip connections. Fast training, good baseline performance.

+
Recommended for beginners
-

🔹 Hybrid

-

Classical + ML KNN - balanced speed & quality

-
Recommended
+

🔹 CR-GAN

+

Cloud Removal GAN - adversarial training cho kết quả chân thực hơn.

+
Advanced
-

🔹 ML KNN

-

K-Nearest Neighbors inpainting - good quality

-
Medium Speed
+

🔹 SpA-GAN

+

Spatial Attention GAN - attention mechanism tập trung vào vùng có mây.

+
Best quality
-

🔹 Deep Learning

-

U-Net CNN - best quality for large gaps

-
Requires Model
+

🔹 GLF-CR

+

Global-Local Fusion - kết hợp features global và local cho chi tiết tốt hơn.

+
High accuracy
+
+ +
+

🔹 SEN12MS-CR

+

Multi-modal fusion - kết hợp Sentinel-1 radar và Sentinel-2 optical.

+
Multi-sensor
+
+ +
+

🔹 RSDehazeNet

+

Remote Sensing Dehaze Network - chuyên cho ảnh viễn thám.

+
RS specialized
+
+ +
+

🔹 Cloud-Net

+

Encoder-Decoder architecture với residual connections.

+
Balanced
+
+ +
+

🔹 DSen2-CR

+

Deep Sentinel-2 Cloud Removal - tận dụng temporal information.

+
Temporal fusion
@@ -456,6 +645,7 @@ const config = { data_dir: document.getElementById('dataDir').value, model_name: document.getElementById('modelName').value, + architecture: document.getElementById('modelArchitecture').value, use_s1: document.getElementById('useS1').checked, batch_size: parseInt(document.getElementById('batchSize').value), num_epochs: parseInt(document.getElementById('numEpochs').value), @@ -505,6 +695,7 @@ modelsList.innerHTML = data.models.map(model => `

📦 ${model.filename}

+
🏗️ Architecture: ${model.architecture || 'U-Net'}
📊 Epoch: ${model.epoch}
📉 Train Loss: ${model.train_loss.toFixed(6)}
📉 Val Loss: ${model.val_loss.toFixed(6)}
diff --git a/deafrica_tools/untitled.txt b/deafrica_tools/untitled.txt deleted file mode 100644 index e69de29..0000000 diff --git a/prediction_interface.html b/prediction_interface.html index fc66767..3274b8b 100644 --- a/prediction_interface.html +++ b/prediction_interface.html @@ -17,8 +17,9 @@ } body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background-attachment: fixed; padding: 20px; min-height: 100vh; } @@ -26,27 +27,53 @@ .container { max-width: 1400px; margin: 0 auto; - background: white; - border-radius: 20px; - box-shadow: 0 20px 60px rgba(0,0,0,0.3); + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 24px; + box-shadow: 0 25px 80px rgba(0,0,0,0.2), 0 0 0 1px rgba(255,255,255,0.1); overflow: hidden; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; - padding: 30px; + padding: 40px 30px; text-align: center; + position: relative; + overflow: hidden; + } + + .header::before { + content: ''; + position: absolute; + top: -50%; + right: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); + animation: headerGlow 8s ease-in-out infinite; + } + + @keyframes headerGlow { + 0%, 100% { transform: translate(0, 0); } + 50% { transform: translate(-20%, -20%); } } .header h1 { - font-size: 2.5em; - margin-bottom: 10px; + font-size: 2.8em; + margin-bottom: 12px; + font-weight: 700; + position: relative; + z-index: 1; + text-shadow: 0 2px 20px rgba(0,0,0,0.2); } .header p { - opacity: 0.9; - font-size: 1.1em; + opacity: 0.95; + font-size: 1.15em; + position: relative; + z-index: 1; + font-weight: 400; } .content { @@ -58,8 +85,9 @@ #predictMap { height: 500px; - border-radius: 10px; - box-shadow: 0 4px 15px rgba(0,0,0,0.1); + border-radius: 16px; + box-shadow: 0 8px 30px rgba(0,0,0,0.12); + border: 1px solid rgba(0,0,0,0.1); } .map-container { @@ -67,59 +95,95 @@ } .map-instructions { - background: #e3f2fd; - padding: 15px; - border-radius: 10px; - margin-bottom: 15px; - border-left: 4px solid #2196f3; + background: linear-gradient(135deg, #e3f2fd 0%, #f0f7ff 100%); + padding: 20px; + border-radius: 16px; + margin-bottom: 20px; + border-left: 5px solid #2196f3; + box-shadow: 0 4px 15px rgba(33, 150, 243, 0.1); + transition: all 0.3s ease; + } + + .map-instructions:hover { + box-shadow: 0 6px 25px rgba(33, 150, 243, 0.15); + transform: translateX(3px); } .map-instructions h3 { color: #1976d2; - margin-bottom: 8px; + margin-bottom: 10px; + font-weight: 700; + font-size: 1.1em; } .map-instructions p { color: #555; - margin: 5px 0; + margin: 6px 0; + line-height: 1.6; } .section { margin-bottom: 30px; - padding: 20px; - background: #f8f9fa; - border-radius: 10px; + padding: 28px; + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border-radius: 16px; + border: 1px solid rgba(0,0,0,0.06); + box-shadow: 0 4px 20px rgba(0,0,0,0.04); + transition: all 0.3s ease; + } + + .section:hover { + box-shadow: 0 8px 30px rgba(102, 126, 234, 0.12); + transform: translateY(-2px); } .section h2 { - color: #667eea; - margin-bottom: 15px; - font-size: 1.5em; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 20px; + font-size: 1.6em; + font-weight: 700; + display: flex; + align-items: center; + gap: 10px; } .form-group { - margin-bottom: 15px; + margin-bottom: 20px; + position: relative; } .form-group label { display: block; - margin-bottom: 5px; - color: #333; + margin-bottom: 8px; + color: #374151; font-weight: 600; + font-size: 0.95em; + letter-spacing: 0.01em; } .form-group input, .form-group select { width: 100%; - padding: 10px; - border: 2px solid #e0e0e0; - border-radius: 5px; + padding: 12px 16px; + border: 2px solid #e5e7eb; + border-radius: 12px; font-size: 1em; - transition: border-color 0.3s; + transition: all 0.3s ease; + background: white; + font-family: inherit; + } + + .form-group input:hover, .form-group select:hover { + border-color: #d1d5db; } .form-group input:focus, .form-group select:focus { outline: none; border-color: #667eea; + box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); + transform: translateY(-1px); } .form-row { @@ -129,43 +193,78 @@ } .btn { - padding: 12px 30px; + padding: 14px 32px; border: none; - border-radius: 5px; + border-radius: 12px; font-size: 1em; font-weight: 600; cursor: pointer; - transition: all 0.3s; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); margin-right: 10px; + position: relative; + overflow: hidden; + font-family: inherit; + } + + .btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255,255,255,0.3); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; + } + + .btn:hover::before { + width: 300px; + height: 300px; } .btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } .btn-primary:hover { - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5); + } + + .btn-primary:active { + transform: translateY(-1px); } .btn-success { - background: #28a745; + background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; + box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3); } .btn-success:hover { - background: #218838; + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(16, 185, 129, 0.5); } .btn-secondary { - background: #6c757d; + background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); color: white; + box-shadow: 0 4px 15px rgba(107, 114, 128, 0.3); + } + + .btn-secondary:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(107, 114, 128, 0.5); } .btn:disabled { opacity: 0.5; cursor: not-allowed; + transform: none !important; } .status-box { @@ -192,42 +291,68 @@ } .progress { - height: 30px; - background: #e0e0e0; - border-radius: 15px; + height: 32px; + background: linear-gradient(to right, #e5e7eb, #f3f4f6); + border-radius: 16px; overflow: hidden; - margin: 10px 0; + margin: 12px 0; + box-shadow: inset 0 2px 8px rgba(0,0,0,0.08); + border: 1px solid rgba(0,0,0,0.05); } .progress-bar { height: 100%; - background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(90deg, #667eea 0%, #764ba2 50%, #667eea 100%); + background-size: 200% 100%; + animation: shimmer 2s infinite; width: 0%; transition: width 0.3s; display: flex; align-items: center; justify-content: center; color: white; - font-weight: 600; + font-weight: 700; + font-size: 0.9em; + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4); + } + + @keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } } .metric-card { - background: white; - padding: 15px; - border-radius: 10px; - box-shadow: 0 2px 5px rgba(0,0,0,0.1); + background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%); + padding: 24px; + border-radius: 16px; + box-shadow: 0 4px 20px rgba(0,0,0,0.06); text-align: center; + border: 1px solid rgba(0,0,0,0.05); + transition: all 0.3s ease; + } + + .metric-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 30px rgba(102, 126, 234, 0.15); } .metric-card h4 { - color: #667eea; - margin-bottom: 10px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 12px; + font-weight: 700; + font-size: 1em; } .metric-card .value { - font-size: 2em; - font-weight: bold; - color: #333; + font-size: 2.2em; + font-weight: 800; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; } .alert { @@ -260,18 +385,22 @@ } .prediction-item { - background: white; - padding: 15px; - border-radius: 8px; - margin-bottom: 10px; - border-left: 4px solid #667eea; + background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%); + padding: 20px; + border-radius: 14px; + margin-bottom: 12px; + border-left: 5px solid #667eea; display: flex; justify-content: space-between; align-items: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.04); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .prediction-item:hover { - box-shadow: 0 2px 10px rgba(0,0,0,0.1); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15); + transform: translateX(5px); + border-left-width: 7px; } @media (max-width: 768px) { @@ -292,22 +421,22 @@

Phân loại đất cho khu vực mới sử dụng model đã train

-
- 🏠 Trang Chủ - 🎓 Training - 🌥️ Cloud Removal - 🗺️ Prediction (Active) - 🚀 Batch Processing - 🌿 NDVI Analysis - 📝 Reports + -
- -
@@ -375,9 +504,82 @@

🤖 Chọn Model

- +
+ + +
+ + +
+ + + + + + +
+
@@ -398,149 +600,184 @@

⏰ Thời gian & Dữ liệu

-
-
- - -
-
- - + +
+

+ 📅 Khoảng thời gian +

+
+
+ + +
+
+ + +
-
-
- - + +
+

+ 🛰️ Tham số dữ liệu vệ tinh +

+
+
+ + +
+
+ + +
-
- - +
+ +
-
- - -
- -
- - +
+

+ 🌥️ Loại bỏ mây +

+
+ + +
- -