bổ sung chức năng load 64 tỉnh thành và 32 tỉnh thành/ bổ sung mô hình Swin-Unet

This commit is contained in:
Victor Phan
2026-01-03 22:35:33 +07:00
parent 5342df77b4
commit fda2852dd2
10 changed files with 2722 additions and 62 deletions
+251 -10
View File
@@ -370,16 +370,45 @@
<form id="trainingForm">
<h3 style="margin-bottom: 15px; color: #667eea;">📍 Khu Vực Training</h3>
<!-- Province Selection Section -->
<div class="form-group" style="margin-bottom: 20px;">
<label>
<strong>🗺️ Chọn theo Tỉnh Thành:</strong>
<span style="color: #999; font-size: 13px; font-weight: normal;">(Hoặc vẽ bbox thủ công bên dưới)</span>
</label>
<!-- Toggle between 63 and 32 provinces -->
<div style="margin-bottom: 10px; display: flex; gap: 10px; align-items: center;">
<button type="button" id="btn63Provinces" onclick="switchProvinceList('63')" style="padding: 8px 16px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">63 Tỉnh (Cũ)</button>
<button type="button" id="btn32Provinces" onclick="switchProvinceList('32')" style="padding: 8px 16px; background: #f0f0f0; color: #333; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">32 Tỉnh (Sau sáp nhập)</button>
<span id="provinceListMode" style="color: #667eea; font-weight: bold;">Danh sách: 63 tỉnh</span>
</div>
<select id="provinceSelect" style="padding: 12px; width: 100%; border: 2px solid #ddd; border-radius: 8px; font-size: 14px; cursor: pointer;">
<option value="">-- Chọn tỉnh thành để tải bbox tự động --</option>
</select>
</div>
<!-- Region Filter -->
<div class="form-group" style="margin-bottom: 20px;">
<label><strong>🌍 Lọc theo Vùng:</strong></label>
<div id="regionFilterContainer" style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="button" class="region-filter-btn" data-region="all" style="padding: 8px 16px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600;">Tất cả</button>
<!-- Dynamic region buttons will be added here -->
</div>
</div>
<div class="map-container">
<div class="map-instructions">
<strong>💡 Hướng dẫn:</strong> Sử dụng công cụ vẽ hình chữ nhật
<strong>💡 Hướng dẫn:</strong> Chọn tỉnh thành ở trên để tự động điền bbox, hoặc sử dụng công cụ vẽ hình chữ nhật
<span style="display: inline-block; width: 24px; height: 24px; background: white; border: 2px solid #333; vertical-align: middle; margin: 0 5px;"></span>
ở góc trên bên trái của bản đồ để chọn khu vực training
ở góc trên bên trái của bản đồ để vẽ khu vực tùy chỉnh
</div>
<div id="map"></div>
<div style="margin-top: 10px; font-size: 13px; color: #666;">
<strong>Khu vực đã chọn:</strong>
<span id="bboxDisplay">Chưa chọn khu vực</span>
<span id="provinceDisplay" style="margin-left: 10px; color: #667eea; font-weight: 600;"></span>
</div>
</div>
@@ -443,6 +472,7 @@
<option value="decision_tree">🌳 Decision Tree (Đơn giản, Nhanh nhất)</option>
<option value="svm">🎯 SVM (Chính xác, Chậm với dữ liệu lớn)</option>
<option value="cnn">🧠 CNN - Deep Learning (PyTorch, Tốt với ảnh vệ tinh, Hỗ trợ GPU)</option>
<option value="swin-unet">🌟 Swin-UNet (Transformer + U-Net, Độ chính xác cao, Hỗ trợ GPU)</option>
</select>
<div style="margin-top: 8px; padding: 10px; background: #e7f3ff; border-radius: 5px; font-size: 12px;">
<span id="modelTypeDesc" style="color: #1976d2;">
@@ -463,7 +493,7 @@
</div>
<div class="form-group" id="learningRateGroup">
<label>Learning Rate:</label>
<input type="number" step="0.01" id="learningRate" value="0.1" min="0.01" max="1" required>
<input type="number" step="0.0001" id="learningRate" value="0.1" min="0.0001" max="1" required>
</div>
<div class="form-group" id="testSizeGroup">
<label>Tỷ lệ dữ liệu test (0-1):</label>
@@ -764,8 +794,8 @@
<h3>📦 ${model.filename}</h3>
<p><strong>Tạo lúc:</strong> ${new Date(model.created).toLocaleString('vi-VN')}</p>
<p><strong>Kích thước:</strong> ${model.size_mb} MB</p>
${model.info.train_accuracy ? `<p><strong>Train Accuracy:</strong> ${(model.info.train_accuracy * 100).toFixed(2)}%</p>` : ''}
${model.info.test_accuracy ? `<p><strong>Test Accuracy:</strong> ${(model.info.test_accuracy * 100).toFixed(2)}%</p>` : ''}
${model.info && model.info.train_accuracy ? `<p><strong>Train Accuracy:</strong> ${(model.info.train_accuracy * 100).toFixed(2)}%</p>` : ''}
${model.info && model.info.test_accuracy ? `<p><strong>Test Accuracy:</strong> ${(model.info.test_accuracy * 100).toFixed(2)}%</p>` : ''}
`;
container.appendChild(item);
@@ -1101,7 +1131,8 @@
'random_forest': '✓ Random Forest: Ổn định, không overfitting, phù hợp mọi kích thước dữ liệu',
'decision_tree': '✓ Decision Tree: Đơn giản nhất, nhanh nhất, dễ hiểu, phù hợp để test nhanh',
'svm': '✓ SVM: Chính xác cao với dữ liệu nhỏ, chậm với dữ liệu lớn',
'cnn': '✓ 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'
'cnn': '✓ 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',
'swin-unet': '✓ Swin-UNet: Kết hợp Transformer + U-Net, độ chính xác cao nhất, phù hợp dataset lớn, tốc độ training trung bình'
};
desc.textContent = descriptions[modelType];
@@ -1130,10 +1161,19 @@
document.getElementById('nEstimators').value = 50;
learningRateGroup.style.display = 'none';
useGpuGroup.style.display = ''; // Show GPU option for CNN
} else if (modelType === 'swin-unet') {
// Swin-UNet uses n_estimators as epochs and supports GPU
nEstimatorsGroup.style.display = '';
document.querySelector('#nEstimatorsGroup label').textContent = 'Epochs (số lần training):';
document.getElementById('nEstimators').value = 100;
learningRateGroup.style.display = ''; // Show learning rate for Swin-UNet
document.querySelector('#learningRateGroup label').textContent = 'Learning Rate (mặc định: 0.0005):';
document.getElementById('learningRate').value = 0.0005;
useGpuGroup.style.display = ''; // Show GPU option for Swin-UNet
}
// Reset n_estimators label for non-CNN
if (modelType !== 'cnn' && modelType !== 'decision_tree' && modelType !== 'svm') {
// Reset n_estimators label for non-CNN/non-Swin-UNet
if (modelType !== 'cnn' && modelType !== 'swin-unet' && modelType !== 'decision_tree' && modelType !== 'svm') {
document.querySelector('#nEstimatorsGroup label').textContent = 'N Estimators:';
}
}
@@ -1278,12 +1318,213 @@
}
}
// === PROVINCE SELECTION FUNCTIONS ===
let allProvinces = {};
let allProvincesMerged = {};
let currentProvinceName = '';
let currentProvinceMode = '63'; // '63' or '32'
// Load provinces list (both 63 and 32)
async function loadProvinces() {
try {
// Load 63 provinces
const response63 = await fetch(`${API_BASE}/provinces/by-region`);
allProvinces = await response63.json();
// Load 32 merged provinces
const response32 = await fetch(`${API_BASE}/provinces-32/by-region`);
allProvincesMerged = await response32.json();
// Default to 63 provinces
populateRegionButtons();
populateProvinceSelect();
} catch (error) {
console.error('Error loading provinces:', error);
document.getElementById('provinceSelect').innerHTML = '<option value="">Lỗi tải danh sách tỉnh</option>';
}
}
// Switch between 63 and 32 province lists
function switchProvinceList(mode) {
currentProvinceMode = mode;
// Update button styles
const btn63 = document.getElementById('btn63Provinces');
const btn32 = document.getElementById('btn32Provinces');
const modeLabel = document.getElementById('provinceListMode');
if (mode === '63') {
btn63.style.background = '#667eea';
btn63.style.color = 'white';
btn32.style.background = '#f0f0f0';
btn32.style.color = '#333';
modeLabel.textContent = 'Danh sách: 63 tỉnh';
} else {
btn63.style.background = '#f0f0f0';
btn63.style.color = '#333';
btn32.style.background = '#667eea';
btn32.style.color = 'white';
modeLabel.textContent = 'Danh sách: 32 tỉnh (sau sáp nhập)';
}
// Update region filter buttons
populateRegionButtons();
// Reload province list
populateProvinceSelect();
}
// Populate province select dropdown
function populateProvinceSelect(filterRegion = 'all') {
const select = document.getElementById('provinceSelect');
select.innerHTML = '<option value="">-- Chọn tỉnh thành để tải bbox tự động --</option>';
// Choose which province list to use
const provinceData = currentProvinceMode === '63' ? allProvinces : allProvincesMerged;
// Get all regions dynamically from data
const regions = Object.keys(provinceData);
regions.forEach(region => {
if (filterRegion !== 'all' && filterRegion !== region) {
return;
}
const provinces = provinceData[region];
if (!provinces || provinces.length === 0) return;
const optgroup = document.createElement('optgroup');
optgroup.label = `${region} (${provinces.length} tỉnh)`;
provinces.forEach(province => {
const option = document.createElement('option');
option.value = province.name;
// For merged provinces, show additional info
if (currentProvinceMode === '32' && province.merged_from) {
option.textContent = `${province.name} (${province.merged_from.join(', ')})`;
} else {
option.textContent = `${province.name} - ${province.name_en || ''}`;
}
option.dataset.bbox = JSON.stringify(province.bbox);
optgroup.appendChild(option);
});
select.appendChild(optgroup);
});
}
// Handle province selection
function onProvinceSelect(event) {
const select = event.target;
const selectedOption = select.options[select.selectedIndex];
if (!selectedOption.value) {
currentProvinceName = '';
document.getElementById('provinceDisplay').textContent = '';
return;
}
const provinceName = selectedOption.value;
const bbox = JSON.parse(selectedOption.dataset.bbox);
currentProvinceName = provinceName;
// Update bbox inputs
document.getElementById('minLon').value = bbox[0];
document.getElementById('minLat').value = bbox[1];
document.getElementById('maxLon').value = bbox[2];
document.getElementById('maxLat').value = bbox[3];
// Update bbox display
document.getElementById('bboxDisplay').textContent =
`Lon: ${bbox[0]}${bbox[2]}, Lat: ${bbox[1]}${bbox[3]}`;
document.getElementById('provinceDisplay').textContent = `📍 ${provinceName}`;
// Draw rectangle on map
const bounds = [[bbox[1], bbox[0]], [bbox[3], bbox[2]]];
// Remove previous rectangle
if (currentRectangle) {
drawnItems.removeLayer(currentRectangle);
}
// Add new rectangle
currentRectangle = L.rectangle(bounds, {
color: '#667eea',
weight: 3,
fillOpacity: 0.2
});
drawnItems.addLayer(currentRectangle);
// Fit map to bounds
map.fitBounds(bounds, { padding: [50, 50] });
// Show success notification
showNotification('success', `Đã chọn tỉnh: ${provinceName}`);
}
// Populate region filter buttons
function populateRegionButtons() {
const container = document.getElementById('regionFilterContainer');
// Keep the "Tất cả" button
const allButton = container.querySelector('[data-region="all"]');
container.innerHTML = '';
container.appendChild(allButton);
// Get regions from current data
const provinceData = currentProvinceMode === '63' ? allProvinces : allProvincesMerged;
const regions = Object.keys(provinceData);
// Add button for each region
regions.forEach(region => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'region-filter-btn';
button.dataset.region = region;
button.textContent = region;
button.style.cssText = 'padding: 8px 16px; background: #f0f0f0; color: #333; border: none; border-radius: 6px; cursor: pointer;';
container.appendChild(button);
});
// Re-setup event listeners
setupRegionFilters();
}
// Handle region filter
function setupRegionFilters() {
const filterButtons = document.querySelectorAll('.region-filter-btn');
filterButtons.forEach(btn => {
btn.addEventListener('click', function() {
// Update active button style
filterButtons.forEach(b => {
b.style.background = '#f0f0f0';
b.style.color = '#333';
b.style.fontWeight = 'normal';
});
this.style.background = '#667eea';
this.style.color = 'white';
this.style.fontWeight = '600';
// Filter provinces
const region = this.dataset.region;
populateProvinceSelect(region);
});
});
}
// Initialize map when page loads
document.addEventListener('DOMContentLoaded', function() {
initMap();
initPredictionMap();
loadPredictionsList(); // Load predictions list on page load
loadCacheInfo(); // Load cache info
loadProvinces(); // Load provinces list
// Add event listeners
document.getElementById('provinceSelect').addEventListener('change', onProvinceSelect);
setupRegionFilters();
// Add model type change listener
document.getElementById('modelType').addEventListener('change', updateModelTypeUI);