From b49a11b2915c66448a5735a1a7c6b570ff001867 Mon Sep 17 00:00:00 2001 From: Victor Phan Date: Mon, 22 Dec 2025 15:43:23 +0700 Subject: [PATCH] =?UTF-8?q?s=E1=BB=ADa=20c=C3=A1c=20l=E1=BB=97i=20t?= =?UTF-8?q?=E1=BA=A1i=20m=C3=A0n=20h=C3=ACnh=20predict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api_server.py | 324 +++++++++---- batch_interface.html | 1 + index.html | 1 + ndvi_interface.html | 1 + prediction_interface.html | 81 ++++ .../prediction_report_20251222_115713.html | 176 +++++++ .../prediction_report_20251222_153138.html | 176 +++++++ .../prediction_report_20251222_154215.html | 176 +++++++ .../prediction_report_20251222_154237.html | 176 +++++++ reports_interface.html | 436 ++++++++++++++++++ start.sh | 4 +- training_interface.html | 1 + 12 files changed, 1452 insertions(+), 101 deletions(-) create mode 100644 reports/prediction_report_20251222_115713.html create mode 100644 reports/prediction_report_20251222_153138.html create mode 100644 reports/prediction_report_20251222_154215.html create mode 100644 reports/prediction_report_20251222_154237.html create mode 100644 reports_interface.html diff --git a/api_server.py b/api_server.py index 2850c4b..fd4926e 100644 --- a/api_server.py +++ b/api_server.py @@ -218,6 +218,16 @@ async def ndvi_page(): raise HTTPException(status_code=404, detail="NDVI interface không tồn tại") +@app.get("/reports", response_class=HTMLResponse) +async def reports_page(): + """Serve reports management interface""" + html_file = Path(__file__).parent / "reports_interface.html" + if html_file.exists(): + return FileResponse(html_file) + else: + raise HTTPException(status_code=404, detail="Reports interface không tồn tại") + + @app.get("/api/config/presets") async def get_presets(): """Lấy các preset cấu hình sẵn""" @@ -324,7 +334,7 @@ async def clear_cache(): @app.get("/api/cache/info") async def get_cache_info(): - """Lấy thông tin về cache với metadata đầy đủ""" + """Lấy thông tin về cache với metadata đầy đủ, tự động xóa cache cũ có lazy data""" cache_dir = Path("dataset_cache") if not cache_dir.exists(): @@ -332,16 +342,37 @@ async def get_cache_info(): cache_files = [] total_size = 0 + deleted_count = 0 for cache_file in cache_dir.glob("*.joblib"): + # Skip if file doesn't exist (race condition) + if not cache_file.exists(): + continue + size = cache_file.stat().st_size - total_size += size - # Try to load metadata from cache + # Try to load metadata from cache and check if it's valid metadata = {} + is_valid = True try: cached_data = joblib.load(cache_file) - if isinstance(cached_data, dict): + + # Check if cache contains lazy data (will cause 403 errors) + if isinstance(cached_data, dict) and "s2_data" in cached_data: + s2_data_temp = cached_data["s2_data"] + is_lazy = False + try: + is_lazy = any(hasattr(s2_data_temp[var].data, 'chunks') for var in s2_data_temp.data_vars) + except: + pass + + if is_lazy: + print(f"[CLEANUP] Deleting cache with lazy data: {cache_file.name}") + cache_file.unlink() + deleted_count += 1 + is_valid = False + + if is_valid and isinstance(cached_data, dict): metadata = { "bbox": cached_data.get("bbox", []), "time_range": cached_data.get("time_range", ""), @@ -362,23 +393,36 @@ async def get_cache_info(): metadata["max_lon"] = metadata["bbox"][2] metadata["max_lat"] = metadata["bbox"][3] except Exception as e: - print(f"Error loading cache metadata: {e}") + print(f"[CLEANUP] Error loading cache {cache_file.name}: {e}. Deleting...") + try: + cache_file.unlink() + deleted_count += 1 + is_valid = False + except: + pass - 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 - }) + # Only add valid cache files to the list + if is_valid: + total_size += size + 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) + if deleted_count > 0: + print(f"[CLEANUP] Deleted {deleted_count} invalid cache files") + return { "exists": True, "files": cache_files, "count": len(cache_files), - "total_size_mb": round(total_size / 1024 / 1024, 2) + "total_size_mb": round(total_size / 1024 / 1024, 2), + "deleted_invalid": deleted_count } @@ -717,15 +761,44 @@ async def run_prediction(config: PredictionConfig): bbox = [config.min_lon, config.min_lat, config.max_lon, config.max_lat] time_range = f"{config.start_date}/{config.end_date}" + # Try to load from cache first + s2_data = None + use_cache = False + if cache_file.exists(): prediction_status["progress"] = "Đang load dữ liệu từ cache..." - cached = joblib.load(cache_file) - s2_data = cached["s2_data"] - s2_items = cached["s2_items"] - vh_monthly = cached.get("vh_monthly") - vv_monthly = cached.get("vv_monthly") - use_radar = cached.get("use_radar", False) - else: + try: + cached = joblib.load(cache_file) + s2_data_temp = cached["s2_data"] + + # Verify that cached data is not lazy (to avoid 403 errors from expired URLs) + # If s2_data has chunks attribute, it's a dask array (lazy) + is_lazy = False + try: + is_lazy = any(hasattr(s2_data_temp[var].data, 'chunks') for var in s2_data_temp.data_vars) + except: + pass + + if is_lazy: + print(f"[WARNING] Cache contains lazy data with potentially expired URLs. Deleting cache...") + cache_file.unlink() + raise ValueError("Cache invalid - contains lazy data") + + # Cache is valid, use it + s2_data = s2_data_temp + s2_items = cached.get("s2_items", []) + vh_monthly = cached.get("vh_monthly") + vv_monthly = cached.get("vv_monthly") + use_radar = cached.get("use_radar", False) + use_cache = True + print(f"[INFO] Loaded valid cache from {cache_file.name}") + + except Exception as e: + print(f"[WARNING] Failed to load cache: {e}. Fetching fresh data...") + s2_data = None + + # If cache not available or invalid, fetch from Microsoft + if s2_data is None: prediction_status["progress"] = "Đang kết nối Microsoft Planetary Computer..." import pystac_client import planetary_computer @@ -747,13 +820,17 @@ async def run_prediction(config: PredictionConfig): raise ValueError("Không tìm thấy dữ liệu Sentinel-2 cho khu vực và thời gian này") s2_items = s2_items[:config.max_scenes] prediction_status["progress"] = f"Đang xử lý {len(s2_items)} scenes Sentinel-2..." - s2_data = load( + s2_data_lazy = load( s2_items, bbox=bbox, chunks={"time": 1, "x": 2048, "y": 2048}, groupby="solar_day", resolution=config.resolution ) + # Compute s2_data to load into memory (avoid lazy loading from expired URLs) + prediction_status["progress"] = "Đang tải dữ liệu Sentinel-2 vào bộ nhớ..." + s2_data = s2_data_lazy.compute() + # ============ BƯỚC 4: TẢI DỮ LIỆU SENTINEL-1 (Radar)... ============ prediction_status["progress"] = "Đang tải dữ liệu Sentinel-1 (Radar)..." s1_search = catalog.search( @@ -828,59 +905,65 @@ async def run_prediction(config: PredictionConfig): # Only load radar if not already in cache if not cache_file.exists() or (cache_file.exists() and not use_radar): prediction_status["progress"] = "Đang tải dữ liệu Sentinel-1 (Radar)..." - - # Initialize catalog if not already done - if not cache_file.exists(): - # catalog already initialized in the else block above - pass - else: - # Need to initialize catalog for radar search - import pystac_client - import planetary_computer - from odc.stac import load - catalog = pystac_client.Client.open( - "https://planetarycomputer.microsoft.com/api/stac/v1", - modifier=planetary_computer.sign_inplace, - ) - - # Search Sentinel-1 data - s1_search = catalog.search( - collections=["sentinel-1-rtc"], - bbox=bbox, - datetime=time_range, - ) - - s1_items = list(s1_search.items()) - - if s1_items: - s1_items = s1_items[:config.max_scenes] - prediction_status["progress"] = f"Đang xử lý {len(s1_items)} scenes Sentinel-1..." - - # 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 - ) - - # Extract VH and VV bands - if "vh" in s1_data and "vv" in s1_data: - vh = s1_data["vh"].astype('float32') - vv = s1_data["vv"].astype('float32') - - # Resample to monthly average - prediction_status["progress"] = "Đang tính trung bình VH/VV theo tháng..." - vh_monthly = vh.resample(time="1ME").mean().compute() - vv_monthly = vv.resample(time="1ME").mean().compute() - - use_radar = True + try: + # Initialize catalog if not already done + if not cache_file.exists(): + pass else: - prediction_status["progress"] = "Không tìm thấy bands VH/VV, tiếp tục với NDVI..." + import pystac_client + import planetary_computer + from odc.stac import load + catalog = pystac_client.Client.open( + "https://planetarycomputer.microsoft.com/api/stac/v1", + modifier=planetary_computer.sign_inplace, + ) + # Search Sentinel-1 data + s1_search = catalog.search( + collections=["sentinel-1-rtc"], + bbox=bbox, + datetime=time_range, + ) + s1_items = list(s1_search.items()) + if s1_items: + s1_items = s1_items[:config.max_scenes] + prediction_status["progress"] = f"Đang xử lý {len(s1_items)} scenes Sentinel-1..." + try: + s1_data = load( + s1_items, + bbox=bbox, + chunks={"time": 1, "x": 2048, "y": 2048}, + groupby="sat:absolute_orbit", + resolution=config.resolution + ) + if "vh" in s1_data and "vv" in s1_data: + vh = s1_data["vh"].astype('float32') + vv = s1_data["vv"].astype('float32') + prediction_status["progress"] = "Đang tính trung bình VH/VV theo tháng..." + try: + vh_monthly = vh.resample(time="1ME").mean().compute() + vv_monthly = vv.resample(time="1ME").mean().compute() + use_radar = True + except Exception as radar_exc: + print(f"[RADAR WARNING] Không thể tính radar monthly: {radar_exc}") + vh_monthly = None + vv_monthly = None + use_radar = False + else: + prediction_status["progress"] = "Không tìm thấy bands VH/VV, tiếp tục với NDVI..." + use_radar = False + except Exception as radar_exc: + print(f"[RADAR WARNING] Không thể tải dữ liệu Sentinel-1: {radar_exc}") + vh_monthly = None + vv_monthly = None + use_radar = False + else: + prediction_status["progress"] = "Không có dữ liệu Sentinel-1, tiếp tục với NDVI..." use_radar = False - else: - prediction_status["progress"] = "Không có dữ liệu Sentinel-1, tiếp tục với NDVI..." + except Exception as radar_exc: + print(f"[RADAR WARNING] Không thể truy cập Sentinel-1: {radar_exc}") + prediction_status["progress"] = "Không thể truy cập Sentinel-1, tiếp tục với NDVI..." + vh_monthly = None + vv_monthly = None use_radar = False # ============ BƯỚC 5: CHUẨN BỊ FEATURES CHO DỰ ĐOÁN ============ @@ -1776,46 +1859,87 @@ async def predict_with_ndvi(config: PredictionWithNDVIConfig, background_tasks: import odc.stac import rasterio from rasterio.transform import from_bounds + import hashlib, os # Load model model_path = Path(f"model_train/{config.model_filename}") if not model_path.exists(): raise HTTPException(status_code=404, detail=f"Model {config.model_filename} không tồn tại") - model = joblib.load(model_path) + model_data = joblib.load(model_path) + + # Extract model from dict (models are saved as {'model': xgb_model, 'label_encoder': encoder}) + if isinstance(model_data, dict): + model = model_data.get('model') + label_encoder = model_data.get('label_encoder') + else: + model = model_data + label_encoder = None + print(f"[PREDICT+NDVI] Loaded model: {config.model_filename}") - # Connect to Microsoft Planetary Computer - catalog = Client.open( - "https://planetarycomputer.microsoft.com/api/stac/v1", - modifier=planetary_computer.sign_inplace - ) + # Check cache first + cache_dir = Path("dataset_cache") + cache_dir.mkdir(exist_ok=True) + cache_key = f"pred_{config.min_lon}_{config.min_lat}_{config.max_lon}_{config.max_lat}_{config.start_date}_{config.end_date}_{config.max_scenes}_{config.cloud_cover}_{config.resolution}" + cache_hash = hashlib.md5(cache_key.encode()).hexdigest() + cache_file = cache_dir / f"prediction_input_{cache_hash}.joblib" bbox = [config.min_lon, config.min_lat, config.max_lon, config.max_lat] - time_range = f"{config.start_date}/{config.end_date}" - # Search for Sentinel-2 data - search = catalog.search( - collections=["sentinel-2-l2a"], - bbox=bbox, - datetime=time_range, - query={"eo:cloud_cover": {"lt": config.cloud_cover}} - ) - - items = list(search.items())[:config.max_scenes] - print(f"[PREDICT+NDVI] Found {len(items)} Sentinel-2 scenes") - - if len(items) == 0: - raise HTTPException(status_code=404, detail="Không tìm thấy dữ liệu vệ tinh") - - # Load all bands needed for features - data = odc.stac.load( - items, - bbox=bbox, - bands=["B02", "B03", "B04", "B08"], # Blue, Green, Red, NIR - resolution=config.resolution, - chunks={"x": 2048, "y": 2048} - ).compute() + # Load from cache or fetch from Microsoft + if cache_file.exists(): + print(f"[PREDICT+NDVI] Loading from cache: {cache_file.name}") + cached = joblib.load(cache_file) + # Extract s2_data from cache (already computed in cache) + s2_data = cached["s2_data"] + # Check if needed bands are available in cache + available_bands = list(s2_data.data_vars.keys()) + needed_bands = ["B02", "B03", "B04", "B08"] + if all(band in available_bands for band in needed_bands): + # Use cached data directly (no need to compute again) + data = s2_data[needed_bands] + print(f"[PREDICT+NDVI] Using cached bands: {needed_bands}") + # Set items to match the number of time slices in the cached data + items = [None] * s2_data.sizes.get("time", 1) + else: + raise HTTPException(status_code=400, + detail=f"Cache thiếu bands cần thiết. Có: {available_bands}, Cần: {needed_bands}") + else: + print(f"[PREDICT+NDVI] No cache found, fetching from Microsoft Planetary Computer") + # Connect to Microsoft Planetary Computer + catalog = Client.open( + "https://planetarycomputer.microsoft.com/api/stac/v1", + modifier=planetary_computer.sign_inplace + ) + + time_range = f"{config.start_date}/{config.end_date}" + + # Search for Sentinel-2 data + search = catalog.search( + collections=["sentinel-2-l2a"], + bbox=bbox, + datetime=time_range, + query={"eo:cloud_cover": {"lt": config.cloud_cover}} + ) + + items = list(search.items())[:config.max_scenes] + print(f"[PREDICT+NDVI] Found {len(items)} Sentinel-2 scenes") + + if len(items) == 0: + raise HTTPException(status_code=404, detail="Không tìm thấy dữ liệu vệ tinh") + + # Sign items to refresh SAS tokens (keep as pystac.Item, not dict) + signed_items = [planetary_computer.sign(item) for item in items] + + # Load all bands needed for features + data = odc.stac.load( + signed_items, + bbox=bbox, + bands=["B02", "B03", "B04", "B08"], # Blue, Green, Red, NIR + resolution=config.resolution, + chunks={"x": 2048, "y": 2048} + ).compute() print(f"[PREDICT+NDVI] Loaded data shape: {data.dims}") diff --git a/batch_interface.html b/batch_interface.html index ec9e140..8f879f3 100644 --- a/batch_interface.html +++ b/batch_interface.html @@ -209,6 +209,7 @@ 🗺️ Prediction 🚀 Batch Processing (Active) 🌿 NDVI Analysis + 📝 Reports
diff --git a/index.html b/index.html index f4cc1c8..e95b2fc 100644 --- a/index.html +++ b/index.html @@ -433,6 +433,7 @@ 🗺️ Prediction 🚀 Batch Processing 🌿 NDVI Analysis + 📝 Reports
diff --git a/ndvi_interface.html b/ndvi_interface.html index e77f16e..447d11f 100644 --- a/ndvi_interface.html +++ b/ndvi_interface.html @@ -209,6 +209,7 @@ 🗺️ Prediction 🚀 Batch Processing 🌿 NDVI Analysis (Active) + 📝 Reports
diff --git a/prediction_interface.html b/prediction_interface.html index f39197f..97cd69a 100644 --- a/prediction_interface.html +++ b/prediction_interface.html @@ -298,6 +298,7 @@ 🗺️ Prediction (Active) 🚀 Batch Processing 🌿 NDVI Analysis + 📝 Reports
@@ -957,6 +958,10 @@ const data = await response.json(); const select = document.getElementById('cacheSelect'); select.innerHTML = ''; + + // Store cache data globally for later use + window.cacheFiles = data.files || []; + if (data.files && data.files.length > 0) { data.files.forEach((file, idx) => { if (file.filename.startsWith('prediction_input_')) { @@ -975,6 +980,80 @@ console.warn('Không thể tải danh sách cache:', e); } } + + // Apply cache preset - auto fill bbox and other params + function applyCachePreset() { + const selectValue = document.getElementById('cacheSelect').value; + if (!selectValue || !window.cacheFiles) return; + + // Find selected cache file + const cacheFile = window.cacheFiles.find(f => f.filename === selectValue); + if (!cacheFile || !cacheFile.metadata) { + console.warn('No metadata found for selected cache'); + return; + } + + const meta = cacheFile.metadata; + + // Auto-fill bbox from cache + if (meta.bbox && meta.bbox.length === 4) { + const [min_lon, min_lat, max_lon, max_lat] = meta.bbox; + + // Set selectedBbox directly without drawing on map + selectedBbox = { + min_lon: min_lon, + min_lat: min_lat, + max_lon: max_lon, + max_lat: max_lat + }; + + // Save to localStorage + localStorage.setItem('prediction_bbox', JSON.stringify(selectedBbox)); + + // Draw rectangle on map to visualize + if (currentRectangle) { + drawnItems.removeLayer(currentRectangle); + } + + const bounds = L.latLngBounds( + [min_lat, min_lon], + [max_lat, max_lon] + ); + + currentRectangle = L.rectangle(bounds, { + color: '#4facfe', + weight: 3, + fillOpacity: 0.2 + }); + + drawnItems.addLayer(currentRectangle); + map.fitBounds(bounds); + + console.log(`✅ Auto-applied bbox from cache: [${min_lon}, ${min_lat}, ${max_lon}, ${max_lat}]`); + } + + // Auto-fill time range if available + if (meta.start_date) { + document.getElementById('predStartDate').value = meta.start_date; + } + if (meta.end_date) { + document.getElementById('predEndDate').value = meta.end_date; + } + + // Auto-fill other params + if (meta.resolution) { + document.getElementById('predResolution').value = meta.resolution; + } + if (meta.max_scenes) { + document.getElementById('predMaxScenes').value = meta.max_scenes; + } + if (meta.cloud_cover) { + document.getElementById('predCloudCover').value = meta.cloud_cover; + } + + // Show notification + alert(`✅ Đã áp dụng cache preset!\n\nBBox: [${meta.bbox?.join(', ') || 'N/A'}]\nTime: ${meta.start_date || '?'} → ${meta.end_date || '?'}\n\nBạn có thể predict ngay mà không cần vẽ bbox!`); + } // Initialize on page load window.onload = function() { @@ -984,6 +1063,8 @@ loadCacheList(); // Add event listener for model selection document.getElementById('modelSelect').addEventListener('change', updateModelInfo); + // Add event listener for cache selection + document.getElementById('cacheSelect').addEventListener('change', applyCachePreset); }; // Cleanup on page unload diff --git a/reports/prediction_report_20251222_115713.html b/reports/prediction_report_20251222_115713.html new file mode 100644 index 0000000..1f96621 --- /dev/null +++ b/reports/prediction_report_20251222_115713.html @@ -0,0 +1,176 @@ + + + + + + + Prediction Report - 20251222_115713 + + + +
+
+

🗺️ Báo Cáo Dự Đoán

+

Land Classification Prediction - 22/12/2025 11:57:13

+
+ +
+
+

📈 Tóm Tắt Kết Quả

+
+
+
165
+
Tổng số Pixels
+
+
+
11x15
+
Kích thước (px)
+
+
+
0.1
+
Diện tích (km²)
+
+
+
1
+
Số Classes
+
+
+
3
+
Số Features
+
+
+
+
Sử dụng Radar
+
+
+
+ +
+

⚙️ Thông Tin Chi Tiết

+
+
+ 🤖 Model sử dụng: + model_xgboost_20251221_172351.joblib +
+
+ 📍 Khu vực (bbox): + [105.47426033018384, 9.250032954766686, 105.47683525083814, 9.251917848893436] +
+
+ 📅 Thời gian: + 2023-03-01/2023-05-31 +
+
+ 💾 Output file: + predictions/prediction_20251222_115712.tif +
+
+
+ +
+

🏷️ Các Classes Phát Hiện

+
+ 6 +
+
+
+ + +
+ + diff --git a/reports/prediction_report_20251222_153138.html b/reports/prediction_report_20251222_153138.html new file mode 100644 index 0000000..6770e93 --- /dev/null +++ b/reports/prediction_report_20251222_153138.html @@ -0,0 +1,176 @@ + + + + + + + Prediction Report - 20251222_153138 + + + +
+
+

🗺️ Báo Cáo Dự Đoán

+

Land Classification Prediction - 22/12/2025 15:31:38

+
+ +
+
+

📈 Tóm Tắt Kết Quả

+
+
+
165
+
Tổng số Pixels
+
+
+
11x15
+
Kích thước (px)
+
+
+
0.1
+
Diện tích (km²)
+
+
+
1
+
Số Classes
+
+
+
3
+
Số Features
+
+
+
+
Sử dụng Radar
+
+
+
+ +
+

⚙️ Thông Tin Chi Tiết

+
+
+ 🤖 Model sử dụng: + model_xgboost_20251221_172351.joblib +
+
+ 📍 Khu vực (bbox): + [105.47426033018384, 9.250032954766686, 105.47683525083814, 9.251917848893436] +
+
+ 📅 Thời gian: + 2023-03-01/2023-05-31 +
+
+ 💾 Output file: + predictions/prediction_20251222_153137.tif +
+
+
+ +
+

🏷️ Các Classes Phát Hiện

+
+ 6 +
+
+
+ + +
+ + diff --git a/reports/prediction_report_20251222_154215.html b/reports/prediction_report_20251222_154215.html new file mode 100644 index 0000000..2996f1b --- /dev/null +++ b/reports/prediction_report_20251222_154215.html @@ -0,0 +1,176 @@ + + + + + + + Prediction Report - 20251222_154215 + + + +
+
+

🗺️ Báo Cáo Dự Đoán

+

Land Classification Prediction - 22/12/2025 15:42:15

+
+ +
+
+

📈 Tóm Tắt Kết Quả

+
+
+
1,053
+
Tổng số Pixels
+
+
+
27x39
+
Kích thước (px)
+
+
+
0.3
+
Diện tích (km²)
+
+
+
2
+
Số Classes
+
+
+
3
+
Số Features
+
+
+
+
Sử dụng Radar
+
+
+
+ +
+

⚙️ Thông Tin Chi Tiết

+
+
+ 🤖 Model sử dụng: + model_xgboost_20251221_172351.joblib +
+
+ 📍 Khu vực (bbox): + [105.54108810418259, 9.340180964398723, 105.54791164391646, 9.344839034909683] +
+
+ 📅 Thời gian: + 2023-03-01/2023-05-31 +
+
+ 💾 Output file: + predictions/prediction_20251222_154215.tif +
+
+
+ +
+

🏷️ Các Classes Phát Hiện

+
+ 36 +
+
+
+ + +
+ + diff --git a/reports/prediction_report_20251222_154237.html b/reports/prediction_report_20251222_154237.html new file mode 100644 index 0000000..10486c1 --- /dev/null +++ b/reports/prediction_report_20251222_154237.html @@ -0,0 +1,176 @@ + + + + + + + Prediction Report - 20251222_154237 + + + +
+
+

🗺️ Báo Cáo Dự Đoán

+

Land Classification Prediction - 22/12/2025 15:42:37

+
+ +
+
+

📈 Tóm Tắt Kết Quả

+
+
+
1,053
+
Tổng số Pixels
+
+
+
27x39
+
Kích thước (px)
+
+
+
0.3
+
Diện tích (km²)
+
+
+
1
+
Số Classes
+
+
+
3
+
Số Features
+
+
+
+
Sử dụng Radar
+
+
+
+ +
+

⚙️ Thông Tin Chi Tiết

+
+
+ 🤖 Model sử dụng: + model_cnn_20251221_163841.joblib +
+
+ 📍 Khu vực (bbox): + [105.54108810418259, 9.340180964398723, 105.54791164391646, 9.344839034909683] +
+
+ 📅 Thời gian: + 2023-03-01/2023-05-31 +
+
+ 💾 Output file: + predictions/prediction_20251222_154237.tif +
+
+
+ +
+

🏷️ Các Classes Phát Hiện

+
+ 6 +
+
+
+ + +
+ + diff --git a/reports_interface.html b/reports_interface.html new file mode 100644 index 0000000..9ca6a6c --- /dev/null +++ b/reports_interface.html @@ -0,0 +1,436 @@ + + + + + + Reports Management + + + + +
+
+

📝 Reports Management

+

Quản lý báo cáo training và prediction

+
+ +
+ 🏠 Trang Chủ + 🎓 Training + 🗺️ Prediction + 🚀 Batch Processing + 🌿 NDVI Analysis + 📝 Reports (Active) +
+ +
+ +
+
+

📊 Total Reports

+
0
+
+
+

🎓 Training Reports

+
0
+
+
+

🗺️ Prediction Reports

+
0
+
+
+

🚀 Batch Reports

+
0
+
+
+ + +
+ + + + + +
+ + +
+
+
+

⏳ Đang tải...

+
+
+
+
+
+ + + + diff --git a/start.sh b/start.sh index a40958f..f27551f 100755 --- a/start.sh +++ b/start.sh @@ -1 +1,3 @@ -uvicorn api_server:app --reload --host 0.0.0.0 --port 8000 + +#uvicorn api_server:app --reload --host 0.0.0.0 --port 8000 +pkill -f "uvicorn api_server:app" && sleep 1 && nohup uvicorn api_server:app --host 0.0.0.0 --port 8000 > server.log 2>&1 & diff --git a/training_interface.html b/training_interface.html index d623bda..24039c5 100644 --- a/training_interface.html +++ b/training_interface.html @@ -296,6 +296,7 @@ 🗺️ Prediction 🚀 Batch Processing 🌿 NDVI Analysis + 📝 Reports