hoàn thành chức năng remove cloud train
This commit is contained in:
+349
-1
@@ -295,6 +295,7 @@
|
||||
<div style="background: white; padding: 15px; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; border-bottom: 2px solid #e0e0e0;">
|
||||
<a href="/" style="padding: 10px 20px; background: #667eea; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🏠 Trang Chủ</a>
|
||||
<a href="/training" style="padding: 10px 20px; background: #f093fb; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🎓 Training</a>
|
||||
<a href="/cloud-training" style="padding: 10px 20px; background: #00bcd4; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🌥️ Cloud Removal</a>
|
||||
<a href="/prediction" style="padding: 10px 20px; background: #4facfe; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🗺️ Prediction (Active)</a>
|
||||
<a href="/batch" style="padding: 10px 20px; background: #764ba2; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🚀 Batch Processing</a>
|
||||
<a href="/ndvi" style="padding: 10px 20px; background: #2ecc71; color: white; border-radius: 8px; text-decoration: none; font-weight: 600;">🌿 NDVI Analysis</a>
|
||||
@@ -427,6 +428,97 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Cloud Removal Configuration -->
|
||||
<div class="form-group" style="margin-top: 15px; padding: 15px; background: #e3f2fd; border-radius: 8px; border-left: 4px solid #2196F3;">
|
||||
<label for="cloudRemovalMethod" style="font-weight: 600; color: #1976d2; margin-bottom: 10px; display: block;">
|
||||
🌥️ Cloud Removal Method
|
||||
</label>
|
||||
<select id="cloudRemovalMethod" onchange="handleCloudMethodChange()" style="padding: 10px; width: 100%; border: 2px solid #2196F3; border-radius: 6px; font-size: 14px; cursor: pointer; margin-bottom: 10px;">
|
||||
<option value="none">🚫 No Cloud Removal - Keep Original Data</option>
|
||||
<option value="classic">Classic (3-step: temporal + median + spatial)</option>
|
||||
<option value="hybrid" selected>Hybrid (Classical + ML KNN) - Recommended</option>
|
||||
<option value="temporal_only">Temporal Only (Fast)</option>
|
||||
<option value="median_composite">Median Composite</option>
|
||||
<option value="ml_knn">ML KNN (K-Nearest Neighbors)</option>
|
||||
<option value="ml_rf">ML Random Forest</option>
|
||||
<option value="deep">Deep Learning (U-Net)</option>
|
||||
</select>
|
||||
|
||||
<!-- Deep Learning Model Selection (only shown when method is 'deep') -->
|
||||
<div id="cloudModelSelection" style="display: none; margin-top: 10px;">
|
||||
<label for="cloudModelSelect" style="font-weight: 500; color: #1565c0; margin-bottom: 5px; display: block;">
|
||||
📦 Select Trained Model:
|
||||
</label>
|
||||
<div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
|
||||
<select id="cloudModelSelect" style="flex: 1; padding: 10px; border: 2px solid #64b5f6; border-radius: 6px; font-size: 14px; cursor: pointer;">
|
||||
<option value="">Loading models...</option>
|
||||
</select>
|
||||
<button onclick="loadCloudRemovalModels(); return false;" style="padding: 10px 15px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; white-space: nowrap;">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Upload Model Button -->
|
||||
<div style="margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 6px;">
|
||||
<input type="file" id="cloudModelUpload" accept=".pth" style="display: none;" onchange="showMetadataForm()">
|
||||
<button onclick="document.getElementById('cloudModelUpload').click()" style="padding: 8px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">
|
||||
📤 Upload Cloud Removal Model (.pth)
|
||||
</button>
|
||||
<span id="uploadCloudStatus" style="margin-left: 10px; font-size: 12px; color: #666;"></span>
|
||||
|
||||
<!-- Metadata Form (shown after file selection) -->
|
||||
<div id="cloudMetadataForm" style="display: none; margin-top: 15px; padding: 15px; background: white; border: 2px solid #4CAF50; border-radius: 6px;">
|
||||
<h4 style="margin: 0 0 10px 0; color: #2e7d32;">📝 Model Metadata</h4>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px;">
|
||||
<div>
|
||||
<label style="font-size: 12px; color: #666; display: block; margin-bottom: 3px;">Epoch:</label>
|
||||
<input type="number" id="uploadEpoch" min="0" placeholder="e.g., 50" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size: 12px; color: #666; display: block; margin-bottom: 3px;">Validation Loss:</label>
|
||||
<input type="number" id="uploadValLoss" step="0.0001" min="0" placeholder="e.g., 0.0134" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size: 12px; color: #666; display: block; margin-bottom: 3px;">Train Loss:</label>
|
||||
<input type="number" id="uploadTrainLoss" step="0.0001" min="0" placeholder="e.g., 0.0142" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size: 12px; color: #666; display: block; margin-bottom: 3px;">Input Channels:</label>
|
||||
<input type="number" id="uploadInChannels" min="1" placeholder="e.g., 6" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size: 12px; color: #666; display: block; margin-bottom: 3px;">Output Channels:</label>
|
||||
<input type="number" id="uploadOutChannels" min="1" placeholder="e.g., 4" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size: 12px; color: #666; display: block; margin-bottom: 3px;">Use Sentinel-1:</label>
|
||||
<select id="uploadUseS1" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<option value="true">Yes</option>
|
||||
<option value="false">No</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<label style="font-size: 12px; color: #666; display: block; margin-bottom: 3px;">Description (optional):</label>
|
||||
<input type="text" id="uploadDescription" placeholder="e.g., Trained on winter dataset, 50 epochs" style="width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button onclick="uploadCloudModelWithMetadata()" style="flex: 1; padding: 8px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 600;">
|
||||
✅ Upload with Metadata
|
||||
</button>
|
||||
<button onclick="cancelUpload()" style="padding: 8px 15px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
❌ Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-size: 12px; color: #1976d2; margin-top: 8px;">
|
||||
💡 Hybrid method balances speed and quality. Deep learning provides best results but requires trained model.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 15px; padding: 15px; background: #fff3e0; border-radius: 8px; border-left: 4px solid #ff9800;">
|
||||
<label style="display: flex; align-items: center; cursor: pointer; margin: 0;">
|
||||
<input type="checkbox" id="useGpuPred" checked style="width: 18px; height: 18px; margin-right: 10px;">
|
||||
@@ -872,6 +964,243 @@
|
||||
} catch (error) {
|
||||
console.error('Error loading models:', error);
|
||||
}
|
||||
|
||||
// Load cloud removal models as well
|
||||
await loadCloudRemovalModels();
|
||||
}
|
||||
|
||||
// Load cloud removal models
|
||||
async function loadCloudRemovalModels() {
|
||||
try {
|
||||
console.log('[Cloud Models] Loading cloud removal models...');
|
||||
const response = await fetch('/api/cloud-removal/models');
|
||||
const data = await response.json();
|
||||
|
||||
console.log('[Cloud Models] Received data:', data);
|
||||
|
||||
const select = document.getElementById('cloudModelSelect');
|
||||
select.innerHTML = '<option value="">No model (will use classical methods)</option>';
|
||||
|
||||
if (data.models && data.models.length > 0) {
|
||||
console.log(`[Cloud Models] Found ${data.models.length} models`);
|
||||
data.models.forEach(model => {
|
||||
const option = document.createElement('option');
|
||||
option.value = model.filename;
|
||||
|
||||
// Handle missing metadata gracefully
|
||||
const epoch = model.epoch || 'N/A';
|
||||
const loss = model.val_loss ? model.val_loss.toFixed(4) : 'N/A';
|
||||
option.textContent = `${model.filename} (Epoch ${epoch}, Loss: ${loss})`;
|
||||
|
||||
select.appendChild(option);
|
||||
console.log(`[Cloud Models] Added: ${model.filename}`);
|
||||
});
|
||||
|
||||
// Auto-select first model
|
||||
select.value = data.models[0].filename;
|
||||
console.log('[Cloud Models] Auto-selected:', data.models[0].filename);
|
||||
} else {
|
||||
console.log('[Cloud Models] No models found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Cloud Models] Error loading cloud removal models:', error);
|
||||
// Show user-friendly error
|
||||
const select = document.getElementById('cloudModelSelect');
|
||||
select.innerHTML = '<option value="">Error loading models - check console</option>';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cloud removal method change
|
||||
function handleCloudMethodChange() {
|
||||
const method = document.getElementById('cloudRemovalMethod').value;
|
||||
const modelSelection = document.getElementById('cloudModelSelection');
|
||||
|
||||
if (method === 'deep') {
|
||||
modelSelection.style.display = 'block';
|
||||
} else {
|
||||
modelSelection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Upload cloud removal model
|
||||
async function uploadCloudModel() {
|
||||
const fileInput = document.getElementById('cloudModelUpload');
|
||||
const file = fileInput.files[0];
|
||||
const statusSpan = document.getElementById('uploadCloudStatus');
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.pth')) {
|
||||
statusSpan.textContent = '❌ Only .pth files allowed';
|
||||
statusSpan.style.color = 'red';
|
||||
return;
|
||||
}
|
||||
|
||||
statusSpan.textContent = '⏳ Uploading...';
|
||||
statusSpan.style.color = '#2196F3';
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await fetch('/api/cloud-removal/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
statusSpan.textContent = `✅ Uploaded: ${result.filename} (${result.size_mb} MB)`;
|
||||
statusSpan.style.color = '#4CAF50';
|
||||
|
||||
// Reload cloud removal models list
|
||||
await loadCloudRemovalModels();
|
||||
|
||||
// Auto-select the newly uploaded model
|
||||
document.getElementById('cloudModelSelect').value = result.filename;
|
||||
} else {
|
||||
statusSpan.textContent = `❌ ${result.detail || 'Upload failed'}`;
|
||||
statusSpan.style.color = 'red';
|
||||
}
|
||||
} catch (error) {
|
||||
statusSpan.textContent = `❌ Error: ${error.message}`;
|
||||
statusSpan.style.color = 'red';
|
||||
}
|
||||
|
||||
// Clear file input
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
// Show metadata form after file selection
|
||||
function showMetadataForm() {
|
||||
const fileInput = document.getElementById('cloudModelUpload');
|
||||
const file = fileInput.files[0];
|
||||
const statusSpan = document.getElementById('uploadCloudStatus');
|
||||
const metadataForm = document.getElementById('cloudMetadataForm');
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.pth')) {
|
||||
statusSpan.textContent = '❌ Only .pth files allowed';
|
||||
statusSpan.style.color = 'red';
|
||||
fileInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Show form
|
||||
metadataForm.style.display = 'block';
|
||||
statusSpan.textContent = `📁 Selected: ${file.name} (${(file.size / (1024 * 1024)).toFixed(2)} MB)`;
|
||||
statusSpan.style.color = '#2196F3';
|
||||
}
|
||||
|
||||
// Upload with metadata
|
||||
async function uploadCloudModelWithMetadata() {
|
||||
const fileInput = document.getElementById('cloudModelUpload');
|
||||
const file = fileInput.files[0];
|
||||
const statusSpan = document.getElementById('uploadCloudStatus');
|
||||
const metadataForm = document.getElementById('cloudMetadataForm');
|
||||
|
||||
if (!file) {
|
||||
alert('No file selected');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get values from form with defaults for empty fields
|
||||
const epoch = document.getElementById('uploadEpoch').value || '0';
|
||||
const valLoss = document.getElementById('uploadValLoss').value || '0';
|
||||
const trainLoss = document.getElementById('uploadTrainLoss').value || '0';
|
||||
const inChannels = document.getElementById('uploadInChannels').value || '6';
|
||||
const outChannels = document.getElementById('uploadOutChannels').value || '4';
|
||||
const useS1 = document.getElementById('uploadUseS1').value || 'true';
|
||||
const description = document.getElementById('uploadDescription').value || '';
|
||||
|
||||
// Debug log
|
||||
console.log('[Upload] Form values:', {
|
||||
epoch, valLoss, trainLoss, inChannels, outChannels, useS1, description
|
||||
});
|
||||
|
||||
// Confirm upload
|
||||
if (!confirm(`Upload ${file.name} with metadata?\nEpoch: ${epoch}\nVal Loss: ${valLoss}\nTrain Loss: ${trainLoss}`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
statusSpan.textContent = '⏳ Uploading with metadata...';
|
||||
statusSpan.style.color = '#2196F3';
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('epoch', epoch);
|
||||
formData.append('val_loss', valLoss);
|
||||
formData.append('train_loss', trainLoss);
|
||||
formData.append('in_channels', inChannels);
|
||||
formData.append('out_channels', outChannels);
|
||||
formData.append('use_s1', useS1);
|
||||
formData.append('description', description);
|
||||
|
||||
console.log('[Upload] Sending FormData...');
|
||||
|
||||
const response = await fetch('/api/cloud-removal/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
console.log('[Upload] Response:', result);
|
||||
|
||||
if (response.ok) {
|
||||
statusSpan.textContent = `✅ Uploaded: ${result.filename} (${result.size_mb} MB) - Epoch ${result.metadata?.epoch || 0}`;
|
||||
statusSpan.style.color = '#4CAF50';
|
||||
|
||||
// Hide form
|
||||
metadataForm.style.display = 'none';
|
||||
|
||||
// Clear form
|
||||
document.getElementById('uploadEpoch').value = '';
|
||||
document.getElementById('uploadValLoss').value = '';
|
||||
document.getElementById('uploadTrainLoss').value = '';
|
||||
document.getElementById('uploadInChannels').value = '';
|
||||
document.getElementById('uploadOutChannels').value = '';
|
||||
document.getElementById('uploadUseS1').value = 'true';
|
||||
document.getElementById('uploadDescription').value = '';
|
||||
|
||||
// Reload cloud removal models list
|
||||
await loadCloudRemovalModels();
|
||||
|
||||
// Auto-select the newly uploaded model
|
||||
document.getElementById('cloudModelSelect').value = result.filename;
|
||||
|
||||
console.log('[Upload] Success:', result);
|
||||
} else {
|
||||
statusSpan.textContent = `❌ ${result.detail || 'Upload failed'}`;
|
||||
statusSpan.style.color = 'red';
|
||||
}
|
||||
} catch (error) {
|
||||
statusSpan.textContent = `❌ Error: ${error.message}`;
|
||||
statusSpan.style.color = 'red';
|
||||
console.error('[Upload] Error:', error);
|
||||
}
|
||||
|
||||
// Clear file input
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
// Cancel upload
|
||||
function cancelUpload() {
|
||||
const fileInput = document.getElementById('cloudModelUpload');
|
||||
const statusSpan = document.getElementById('uploadCloudStatus');
|
||||
const metadataForm = document.getElementById('cloudMetadataForm');
|
||||
|
||||
// Clear and hide
|
||||
fileInput.value = '';
|
||||
metadataForm.style.display = 'none';
|
||||
statusSpan.textContent = '';
|
||||
}
|
||||
|
||||
// Update model info display
|
||||
@@ -927,6 +1256,23 @@
|
||||
const exportNDVI = document.getElementById('exportNDVI').checked;
|
||||
const useGpu = document.getElementById('useGpuPred').checked;
|
||||
|
||||
// Get cloud removal configuration
|
||||
const cloudRemovalMethod = document.getElementById('cloudRemovalMethod').value;
|
||||
const cloudRemovalConfig = {
|
||||
method: cloudRemovalMethod
|
||||
};
|
||||
|
||||
// If deep learning method is selected, include model filename
|
||||
if (cloudRemovalMethod === 'deep') {
|
||||
const cloudModelFilename = document.getElementById('cloudModelSelect').value;
|
||||
if (cloudModelFilename) {
|
||||
cloudRemovalConfig.model_filename = cloudModelFilename;
|
||||
} else {
|
||||
alert('⚠️ Deep learning method selected but no model chosen. Will fall back to hybrid method.');
|
||||
cloudRemovalConfig.method = 'hybrid';
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
model_filename: modelFilename,
|
||||
min_lon: selectedBbox.min_lon,
|
||||
@@ -940,7 +1286,9 @@
|
||||
resolution: parseInt(document.getElementById('predResolution').value),
|
||||
use_gpu: useGpu,
|
||||
export_ndvi: exportNDVI,
|
||||
export_classification: true
|
||||
export_classification: true,
|
||||
cloud_removal_method: cloudRemovalConfig.method,
|
||||
cloud_removal_model: cloudRemovalConfig.model_filename || null
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user