2026-01-26 13:44:55 +07:00
<!DOCTYPE html>
< html lang = "vi" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Cloud Removal Training - Deep Learning</ title >
2026-02-24 21:36:02 +07:00
< script src = "https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" ></ script >
2026-01-26 13:44:55 +07:00
< style >
* {
margin : 0 ;
padding : 0 ;
box-sizing : border-box ;
}
body {
2026-02-15 19:47:58 +07:00
font-family : - apple-system , BlinkMacSystemFont , 'Segoe UI' , 'Roboto' , 'Oxygen' , 'Ubuntu' , 'Cantarell' , sans-serif ;
2026-01-26 13:44:55 +07:00
background : linear-gradient ( 135 deg , #667eea 0 % , #764ba2 100 % );
2026-02-15 19:47:58 +07:00
background-attachment : fixed ;
2026-01-26 13:44:55 +07:00
min-height : 100 vh ;
padding : 20 px ;
}
. container {
max-width : 1200 px ;
margin : 0 auto ;
2026-02-15 19:47:58 +07:00
background : rgba ( 255 , 255 , 255 , 0.95 );
backdrop-filter : blur ( 20 px );
border-radius : 24 px ;
box-shadow : 0 25 px 80 px rgba ( 0 , 0 , 0 , 0.2 ), 0 0 0 1 px rgba ( 255 , 255 , 255 , 0.1 );
2026-01-26 13:44:55 +07:00
overflow : hidden ;
}
. header {
background : linear-gradient ( 135 deg , #667eea 0 % , #764ba2 100 % );
color : white ;
2026-02-15 19:47:58 +07:00
padding : 40 px 30 px ;
2026-01-26 13:44:55 +07:00
text-align : center ;
2026-02-15 19:47:58 +07:00
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 8 s ease-in-out infinite ;
}
@ keyframes headerGlow {
0 %, 100 % { transform : translate ( 0 , 0 ); }
50 % { transform : translate ( -20 % , -20 % ); }
2026-01-26 13:44:55 +07:00
}
. header h1 {
2026-02-15 19:47:58 +07:00
font-size : 2.8 em ;
margin-bottom : 12 px ;
font-weight : 700 ;
position : relative ;
z-index : 1 ;
text-shadow : 0 2 px 20 px rgba ( 0 , 0 , 0 , 0.2 );
2026-01-26 13:44:55 +07:00
}
. header p {
2026-02-15 19:47:58 +07:00
font-size : 1.15 em ;
opacity : 0.95 ;
position : relative ;
z-index : 1 ;
font-weight : 400 ;
2026-01-26 13:44:55 +07:00
}
. nav {
2026-02-15 19:47:58 +07:00
background : rgba ( 255 , 255 , 255 , 0.8 );
backdrop-filter : blur ( 10 px );
padding : 18 px 30 px ;
border-bottom : 1 px solid rgba ( 0 , 0 , 0 , 0.08 );
box-shadow : 0 2 px 10 px rgba ( 0 , 0 , 0 , 0.03 );
display : flex ;
gap : 12 px ;
flex-wrap : wrap ;
justify-content : center ;
2026-01-26 13:44:55 +07:00
}
. nav a {
2026-02-15 19:47:58 +07:00
padding : 12 px 24 px ;
color : white ;
2026-01-26 13:44:55 +07:00
text-decoration : none ;
2026-02-15 19:47:58 +07:00
border-radius : 12 px ;
font-weight : 600 ;
transition : all 0.3 s ;
box-shadow : 0 4 px 12 px rgba ( 102 , 126 , 234 , 0.2 );
2026-01-26 13:44:55 +07:00
}
2026-02-15 19:47:58 +07:00
. nav a : nth-child ( 1 ) { background : linear-gradient ( 135 deg , #667eea , #764ba2 ); }
. nav a : nth-child ( 2 ) { background : linear-gradient ( 135 deg , #f093fb , #f5576c ); }
. nav a : nth-child ( 3 ) { background : linear-gradient ( 135 deg , #4facfe , #00f2fe ); }
. nav a : nth-child ( 4 ) { background : linear-gradient ( 135 deg , #43e97b , #38f9d7 ); }
2026-01-26 13:44:55 +07:00
. nav a : hover {
2026-02-15 19:47:58 +07:00
transform : translateY ( -2 px );
box-shadow : 0 6 px 20 px rgba ( 102 , 126 , 234 , 0.3 );
2026-01-26 13:44:55 +07:00
}
. content {
padding : 30 px ;
}
. section {
margin-bottom : 30 px ;
2026-02-15 19:47:58 +07:00
padding : 28 px ;
background : linear-gradient ( 135 deg , #f8f9fa 0 % , #ffffff 100 % );
border-radius : 16 px ;
border : 1 px solid rgba ( 0 , 0 , 0 , 0.06 );
box-shadow : 0 4 px 20 px rgba ( 0 , 0 , 0 , 0.04 );
transition : all 0.3 s ease ;
}
. section : hover {
box-shadow : 0 8 px 30 px rgba ( 102 , 126 , 234 , 0.12 );
transform : translateY ( -2 px );
2026-01-26 13:44:55 +07:00
}
. section-title {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #667eea 0 % , #764ba2 100 % );
-webkit- background-clip : text ;
-webkit- text-fill-color : transparent ;
background-clip : text ;
font-size : 1.6 em ;
font-weight : 700 ;
margin-bottom : 20 px ;
display : flex ;
align-items : center ;
gap : 10 px ;
2026-01-26 13:44:55 +07:00
}
. card {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #f8f9fa 0 % , #ffffff 100 % );
border-radius : 12 px ;
padding : 24 px ;
2026-01-26 13:44:55 +07:00
margin-bottom : 20 px ;
2026-02-15 19:47:58 +07:00
border : 1 px solid rgba ( 0 , 0 , 0 , 0.05 );
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.04 );
2026-01-26 13:44:55 +07:00
}
. form-group {
margin-bottom : 20 px ;
2026-02-15 19:47:58 +07:00
position : relative ;
2026-01-26 13:44:55 +07:00
}
label {
display : block ;
font-weight : 600 ;
margin-bottom : 8 px ;
2026-02-15 19:47:58 +07:00
color : #374151 ;
font-size : 0.95 em ;
letter-spacing : 0.01 em ;
2026-01-26 13:44:55 +07:00
}
input [ type = "text" ],
input [ type = "number" ],
select {
width : 100 % ;
2026-02-15 19:47:58 +07:00
padding : 12 px 16 px ;
border : 2 px solid #e5e7eb ;
border-radius : 12 px ;
2026-01-26 13:44:55 +07:00
font-size : 1 em ;
2026-02-15 19:47:58 +07:00
transition : all 0.3 s ease ;
background : white ;
font-family : inherit ;
}
input [ type = "text" ] : hover ,
input [ type = "number" ] : hover ,
select : hover {
border-color : #d1d5db ;
2026-01-26 13:44:55 +07:00
}
input [ type = "text" ] : focus ,
input [ type = "number" ] : focus ,
select : focus {
outline : none ;
border-color : #667eea ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 0 0 4 px rgba ( 102 , 126 , 234 , 0.1 );
transform : translateY ( -1 px );
2026-01-26 13:44:55 +07:00
}
. checkbox-group {
display : flex ;
2026-02-15 19:47:58 +07:00
align-items : flex-start ;
gap : 12 px ;
cursor : pointer ;
2026-01-26 13:44:55 +07:00
}
input [ type = "checkbox" ] {
width : 20 px ;
height : 20 px ;
cursor : pointer ;
2026-02-15 19:47:58 +07:00
margin-top : 2 px ;
2026-01-26 13:44:55 +07:00
}
. btn {
2026-02-15 19:47:58 +07:00
padding : 14 px 32 px ;
2026-01-26 13:44:55 +07:00
border : none ;
2026-02-15 19:47:58 +07:00
border-radius : 12 px ;
2026-01-26 13:44:55 +07:00
font-size : 1 em ;
font-weight : 600 ;
cursor : pointer ;
2026-02-15 19:47:58 +07:00
transition : all 0.3 s cubic-bezier ( 0.4 , 0 , 0.2 , 1 );
2026-01-26 13:44:55 +07:00
margin-right : 10 px ;
2026-02-15 19:47:58 +07:00
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.6 s , height 0.6 s ;
}
. btn : hover :: before {
width : 300 px ;
height : 300 px ;
2026-01-26 13:44:55 +07:00
}
. btn-primary {
background : linear-gradient ( 135 deg , #667eea 0 % , #764ba2 100 % );
color : white ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 4 px 15 px rgba ( 102 , 126 , 234 , 0.3 );
2026-01-26 13:44:55 +07:00
}
. btn-primary : hover {
2026-02-15 19:47:58 +07:00
transform : translateY ( -3 px );
box-shadow : 0 8 px 25 px rgba ( 102 , 126 , 234 , 0.5 );
2026-01-26 13:44:55 +07:00
}
. btn-secondary {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #6b7280 0 % , #4b5563 100 % );
2026-01-26 13:44:55 +07:00
color : white ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 4 px 15 px rgba ( 107 , 114 , 128 , 0.3 );
}
. btn-secondary : hover {
transform : translateY ( -3 px );
box-shadow : 0 8 px 25 px rgba ( 107 , 114 , 128 , 0.5 );
2026-01-26 13:44:55 +07:00
}
. btn-danger {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #dc3545 0 % , #c82333 100 % );
2026-01-26 13:44:55 +07:00
color : white ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 4 px 15 px rgba ( 220 , 53 , 69 , 0.3 );
}
. btn-danger : hover {
transform : translateY ( -3 px );
box-shadow : 0 8 px 25 px rgba ( 220 , 53 , 69 , 0.5 );
2026-01-26 13:44:55 +07:00
}
. btn-success {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #10b981 0 % , #059669 100 % );
2026-01-26 13:44:55 +07:00
color : white ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 4 px 15 px rgba ( 16 , 185 , 129 , 0.3 );
}
. btn-success : hover {
transform : translateY ( -3 px );
box-shadow : 0 8 px 25 px rgba ( 16 , 185 , 129 , 0.5 );
2026-01-26 13:44:55 +07:00
}
. model-list {
display : grid ;
grid-template-columns : repeat ( auto - fill , minmax ( 300 px , 1 fr ));
gap : 20 px ;
}
. model-card {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #ffffff 0 % , #f9fafb 100 % );
border : 1 px solid rgba ( 0 , 0 , 0 , 0.08 );
border-radius : 14 px ;
padding : 24 px ;
transition : all 0.3 s cubic-bezier ( 0.4 , 0 , 0.2 , 1 );
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.04 );
2026-01-26 13:44:55 +07:00
}
. model-card : hover {
border-color : #667eea ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 8 px 25 px rgba ( 102 , 126 , 234 , 0.15 );
transform : translateY ( -5 px );
2026-01-26 13:44:55 +07:00
}
. model-card h3 {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #667eea 0 % , #764ba2 100 % );
-webkit- background-clip : text ;
-webkit- text-fill-color : transparent ;
background-clip : text ;
margin-bottom : 12 px ;
font-size : 1.2 em ;
2026-01-26 13:44:55 +07:00
}
. model-info {
font-size : 0.9 em ;
2026-02-15 19:47:58 +07:00
color : #6b7280 ;
margin : 6 px 0 ;
line-height : 1.5 ;
2026-01-26 13:44:55 +07:00
}
. status-badge {
display : inline-block ;
2026-02-15 19:47:58 +07:00
padding : 6 px 16 px ;
2026-01-26 13:44:55 +07:00
border-radius : 20 px ;
2026-02-15 19:47:58 +07:00
font-size : 0.85 em ;
2026-01-26 13:44:55 +07:00
font-weight : 600 ;
2026-02-15 19:47:58 +07:00
margin-top : 12 px ;
2026-01-26 13:44:55 +07:00
}
. status-success {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #d4edda 0 % , #c3e6cb 100 % );
2026-01-26 13:44:55 +07:00
color : #155724 ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 2 px 8 px rgba ( 21 , 87 , 36 , 0.2 );
2026-01-26 13:44:55 +07:00
}
. status-training {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #fff3cd 0 % , #ffeaa7 100 % );
2026-01-26 13:44:55 +07:00
color : #856404 ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 2 px 8 px rgba ( 133 , 100 , 4 , 0.2 );
2026-01-26 13:44:55 +07:00
}
. status-error {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #f8d7da 0 % , #f5c6cb 100 % );
2026-01-26 13:44:55 +07:00
color : #721c24 ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 2 px 8 px rgba ( 114 , 28 , 36 , 0.2 );
2026-01-26 13:44:55 +07:00
}
. progress-bar {
width : 100 % ;
2026-02-15 19:47:58 +07:00
height : 32 px ;
background : linear-gradient ( to right , #e5e7eb , #f3f4f6 );
border-radius : 16 px ;
2026-01-26 13:44:55 +07:00
overflow : hidden ;
margin : 20 px 0 ;
2026-02-15 19:47:58 +07:00
box-shadow : inset 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.08 );
border : 1 px solid rgba ( 0 , 0 , 0 , 0.05 );
2026-01-26 13:44:55 +07:00
}
. progress-fill {
height : 100 % ;
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 90 deg , #667eea 0 % , #764ba2 50 % , #667eea 100 % );
background-size : 200 % 100 % ;
animation : shimmer 2 s infinite ;
2026-01-26 13:44:55 +07:00
transition : width 0.3 s ;
display : flex ;
align-items : center ;
justify-content : center ;
color : white ;
2026-02-15 19:47:58 +07:00
font-weight : 700 ;
font-size : 0.9 em ;
box-shadow : 0 2 px 8 px rgba ( 102 , 126 , 234 , 0.4 );
}
@ keyframes shimmer {
0 % { background-position : 200 % 0 ; }
100 % { background-position : -200 % 0 ; }
2026-01-26 13:44:55 +07:00
}
. info-box {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #e3f2fd 0 % , #f0f7ff 100 % );
border-left : 5 px solid #2196F3 ;
padding : 20 px ;
border-radius : 12 px ;
2026-01-26 13:44:55 +07:00
margin-bottom : 20 px ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 4 px 15 px rgba ( 33 , 150 , 243 , 0.1 );
transition : all 0.3 s ease ;
}
. info-box : hover {
box-shadow : 0 6 px 25 px rgba ( 33 , 150 , 243 , 0.15 );
transform : translateX ( 3 px );
2026-01-26 13:44:55 +07:00
}
. warning-box {
2026-02-15 19:47:58 +07:00
background : linear-gradient ( 135 deg , #fff3cd 0 % , #ffeaa7 100 % );
border-left : 5 px solid #ffc107 ;
padding : 20 px ;
border-radius : 12 px ;
2026-01-26 13:44:55 +07:00
margin-bottom : 20 px ;
2026-02-15 19:47:58 +07:00
box-shadow : 0 4 px 15 px rgba ( 255 , 193 , 7 , 0.1 );
transition : all 0.3 s ease ;
}
. warning-box : hover {
box-shadow : 0 6 px 25 px rgba ( 255 , 193 , 7 , 0.15 );
transform : translateX ( 3 px );
2026-01-26 13:44:55 +07:00
}
. grid-2 {
display : grid ;
grid-template-columns : 1 fr 1 fr ;
gap : 20 px ;
}
@ media ( max-width : 768px ) {
. grid-2 {
grid-template-columns : 1 fr ;
}
. model-list {
grid-template-columns : 1 fr ;
}
}
. logs {
background : #1e1e1e ;
color : #d4d4d4 ;
padding : 20 px ;
2026-02-15 19:47:58 +07:00
border-radius : 12 px ;
2026-01-26 13:44:55 +07:00
font-family : 'Courier New' , monospace ;
font-size : 0.9 em ;
max-height : 400 px ;
overflow-y : auto ;
margin-top : 20 px ;
2026-02-15 19:47:58 +07:00
box-shadow : inset 0 2 px 10 px rgba ( 0 , 0 , 0 , 0.3 );
2026-01-26 13:44:55 +07:00
}
. logs . log-entry {
margin : 5 px 0 ;
2026-02-15 19:47:58 +07:00
padding : 4 px 0 ;
2026-01-26 13:44:55 +07:00
}
. logs . log-info {
color : #4ec9b0 ;
}
. logs . log-warning {
color : #dcdcaa ;
}
. logs . log-error {
color : #f48771 ;
}
</ style >
</ head >
< body >
< div class = "container" >
< div class = "header" >
< h1 > 🌥️ Cloud Removal Training</ h1 >
< p > Train Deep Learning Models để khử mây từ ảnh Sentinel-2</ p >
</ div >
< div class = "nav" >
< a href = "/" > ← Trang chủ</ a >
< a href = "/training" > Land Classification</ a >
< a href = "/prediction" > Prediction</ a >
< a href = "#models" > Models đã train</ a >
</ div >
< div class = "content" >
<!-- Info Section -->
< div class = "section" >
< div class = "info-box" >
< strong > 📚 Dataset:</ strong > SEN12MS-CR (Sentinel-12 Multi-Seasonal Cloud Removal)< br >
< strong > 🏗️ Architecture:</ strong > U-Net với skip connections< br >
< strong > 📊 Input:</ strong > S2 cloudy (4 bands) + S1 radar (2 bands) = 6 channels< br >
< strong > 🎯 Output:</ strong > S2 clean (4 bands)< br >
< strong > ⏱️ Training time:</ strong > ~2-3 hours (GPU) / ~20-30 hours (CPU)
</ div >
</ div >
<!-- Training Configuration -->
< div class = "section" >
< h2 class = "section-title" > ⚙️ Cấu hình Training</ h2 >
< div class = "card" >
< form id = "trainingForm" >
< div class = "grid-2" >
< div class = "form-group" >
2026-02-15 19:47:58 +07:00
< label > 🏗️ Model Architecture</ label >
< select id = "modelArchitecture" required >
< option value = "unet" > U-Net (Classic CNN)</ option >
< option value = "crgan" > CR-GAN (Cloud Removal GAN)</ option >
< option value = "spagan" > SpA-GAN (Spatial Attention GAN)</ option >
< option value = "glfcr" > GLF-CR (Global-Local Fusion)</ option >
< option value = "sen12mscr" > SEN12MS-CR (Multi-modal)</ option >
< option value = "rsdehazenet" > RSDehazeNet (Remote Sensing)</ option >
< option value = "cloudnet" > Cloud-Net (Encoder-Decoder)</ option >
< option value = "dsen2cr" > DSen2-CR (Deep Sentinel-2)</ option >
</ select >
< small style = "color: #6c757d;" > Chọn kiến trúc deep learning cho cloud removal</ small >
2026-01-26 13:44:55 +07:00
</ div >
< div class = "form-group" >
< label > 🏷️ Model Name</ label >
< input type = "text" id = "modelName" value = "cloud_removal_unet" required >
< small style = "color: #6c757d;" > Tên model để lưu</ small >
</ div >
2026-02-15 19:47:58 +07:00
< div class = "form-group" >
< label > 📂 Data Directory</ label >
< input type = "text" id = "dataDir" value = "winter_dataset" required >
< small style = "color: #6c757d;" > Thư mục chứa dữ liệu SEN12MS-CR</ small >
</ div >
2026-01-26 13:44:55 +07:00
< div class = "form-group" >
< label > 📦 Batch Size</ label >
< input type = "number" id = "batchSize" value = "8" min = "1" max = "32" required >
< small style = "color: #6c757d;" > Giảm xuống 4 hoặc 2 nếu GPU hết RAM</ small >
</ div >
< div class = "form-group" >
< label > 🔄 Number of Epochs</ label >
< input type = "number" id = "numEpochs" value = "50" min = "1" max = "200" required >
< small style = "color: #6c757d;" > Số lượng epochs training</ small >
</ div >
< div class = "form-group" >
< label > 📈 Learning Rate</ label >
< input type = "number" id = "learningRate" value = "0.0001" step = "0.00001" min = "0.00001" max = "0.01" required >
< small style = "color: #6c757d;" > Learning rate (default: 1e-4)</ small >
</ div >
< div class = "form-group" >
< div class = "checkbox-group" >
< input type = "checkbox" id = "useS1" checked >
< label for = "useS1" > 📡 Use Sentinel-1 (Radar Data)</ label >
</ div >
< small style = "color: #6c757d;" > Sử dụng dữ liệu radar (VV, VH) để cải thiện kết quả</ small >
</ div >
< div class = "form-group" >
< div class = "checkbox-group" >
< input type = "checkbox" id = "useGPU" checked >
< label for = "useGPU" > 🚀 Use GPU</ label >
</ div >
< small style = "color: #6c757d;" > Sử dụng GPU để training nhanh hơn</ small >
</ div >
</ div >
< div class = "form-group" style = "margin-top: 20px;" >
< button type = "submit" class = "btn btn-primary" > 🚀 Start Training</ button >
< button type = "button" class = "btn btn-secondary" onclick = "refreshModels()" > 🔄 Refresh Models</ button >
</ div >
</ form >
</ div >
</ div >
<!-- Training Status -->
< div class = "section" id = "trainingStatus" style = "display: none;" >
2026-02-24 21:36:02 +07:00
< h2 class = "section-title" > 📊 Training Status - Real-time Baseline PSNR Tracking</ h2 >
2026-01-26 13:44:55 +07:00
< div class = "card" >
< div id = "statusMessage" ></ div >
2026-02-24 21:36:02 +07:00
<!-- Metrics Grid -->
< div class = "grid-2" style = "margin-top: 20px;" >
< div class = "card" style = "text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;" >
< h4 style = "margin-bottom: 10px;" > 📈 Model PSNR</ h4 >
< div style = "font-size: 2.5em; font-weight: bold;" id = "modelPSNR" > --</ div >
< small > dB (higher is better)</ small >
</ div >
< div class = "card" style = "text-align: center; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white;" >
< h4 style = "margin-bottom: 10px;" > 📉 Baseline PSNR</ h4 >
< div style = "font-size: 2.5em; font-weight: bold;" id = "baselinePSNR" > --</ div >
< small > dB (cận dưới - không làm gì)</ small >
</ div >
< div class = "card" style = "text-align: center; background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); color: white;" >
< h4 style = "margin-bottom: 10px;" > ⚡ Improvement</ h4 >
< div style = "font-size: 2.5em; font-weight: bold;" id = "improvement" > --</ div >
< small > dB (Model - Baseline)</ small >
</ div >
< div class = "card" style = "text-align: center; background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); color: white;" >
< h4 style = "margin-bottom: 10px;" > 🎯 Progress</ h4 >
< div style = "font-size: 2.5em; font-weight: bold;" id = "epochProgress" > 0/0</ div >
< small > Epochs completed</ small >
</ div >
</ div >
<!-- Progress Bar -->
< div class = "progress-bar" style = "margin-top: 20px;" >
2026-01-26 13:44:55 +07:00
< div class = "progress-fill" id = "progressBar" style = "width: 0%;" > 0%</ div >
</ div >
2026-02-24 21:36:02 +07:00
<!-- Performance Range Cards (Best & Worst) -->
< div style = "display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;" >
<!-- Best PSNR Checkpoint -->
< div id = "bestPSNRCard" style = "background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); padding: 20px; border-radius: 12px; box-shadow: 0 8px 24px rgba(255, 215, 0, 0.3); display: none;" >
< h3 style = "text-align: center; margin-bottom: 15px; color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.2);" > 🏆 Best PSNR (Cao Nhất)</ h3 >
< div class = "grid-2" style = "gap: 15px;" >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Best Model PSNR</ div >
< div style = "font-size: 2em; font-weight: bold; color: #667eea;" id = "bestModelPSNR" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > At Epoch</ div >
< div style = "font-size: 2em; font-weight: bold; color: #764ba2;" id = "bestEpoch" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > epoch</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Train Loss</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #43e97b;" id = "bestTrainLoss" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > MSE</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Val Loss</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #f5576c;" id = "bestValLoss" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > MSE</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Baseline PSNR</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #f093fb;" id = "bestBaselinePSNR" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB (cận dưới)</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Improvement</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #4facfe;" id = "bestImprovement" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB (vs baseline)</ div >
</ div >
</ div >
</ div >
<!-- Worst PSNR Checkpoint -->
< div id = "worstPSNRCard" style = "background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 12px; box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3); display: none;" >
< h3 style = "text-align: center; margin-bottom: 15px; color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.2);" > 📉 Worst PSNR (Thấp Nhất)</ h3 >
< div class = "grid-2" style = "gap: 15px;" >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Worst Model PSNR</ div >
< div style = "font-size: 2em; font-weight: bold; color: #667eea;" id = "worstModelPSNR" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB (cận dưới)</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > At Epoch</ div >
< div style = "font-size: 2em; font-weight: bold; color: #764ba2;" id = "worstEpoch" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > epoch</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Train Loss</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #43e97b;" id = "worstTrainLoss" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > MSE</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Val Loss</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #f5576c;" id = "worstValLoss" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > MSE</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Baseline PSNR</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #f093fb;" id = "worstBaselinePSNR" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Gap from Baseline</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #4facfe;" id = "worstImprovement" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB</ div >
</ div >
</ div >
</ div >
</ div >
<!-- Performance Range Summary -->
< div id = "performanceRangeCard" style = "margin-top: 20px; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 20px; border-radius: 12px; box-shadow: 0 8px 24px rgba(245, 87, 108, 0.3); display: none;" >
< h3 style = "text-align: center; margin-bottom: 15px; color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.2);" > 📊 Performance Range & Baseline</ h3 >
< div style = "display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px;" >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > PSNR Range</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #667eea;" id = "psnrRange" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB (max - min)</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Avg PSNR</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #43e97b;" id = "avgPSNR" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Stability</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #f093fb;" id = "stability" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > %</ div >
</ div >
< div style = "background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; text-align: center;" >
< div style = "font-size: 0.85em; color: #666; margin-bottom: 5px;" > Baseline PSNR</ div >
< div style = "font-size: 1.5em; font-weight: bold; color: #ff6b6b;" id = "baselinePSNRDisplay" > --</ div >
< div style = "font-size: 0.75em; color: #888; margin-top: 3px;" > dB (cận dưới)</ div >
</ div >
</ div >
< div id = "baselineComparisonText" style = "margin-top: 15px; padding: 12px; background: rgba(255,255,255,0.9); border-radius: 6px; text-align: center; font-size: 0.9em; font-weight: 600;" >
--
</ div >
</ div >
<!-- PSNR Chart -->
< div style = "margin-top: 30px; background: white; padding: 20px; border-radius: 12px;" >
< h3 style = "text-align: center; margin-bottom: 20px;" > 📊 PSNR vs Baseline - Live Chart</ h3 >
< canvas id = "psnrChart" width = "400" height = "200" ></ canvas >
</ div >
<!-- Training Logs -->
< div class = "logs" id = "trainingLogs" style = "margin-top: 20px;" >
2026-01-26 13:44:55 +07:00
< div class = "log-entry log-info" > Training logs will appear here...</ div >
</ div >
2026-02-24 21:36:02 +07:00
<!-- Stop Button -->
< div style = "margin-top: 20px; text-align: center;" >
< button class = "btn btn-danger" onclick = "stopTraining()" > ⏹️ Stop Training</ button >
</ div >
2026-01-26 13:44:55 +07:00
</ div >
</ div >
<!-- Models List -->
< div class = "section" id = "models" >
< h2 class = "section-title" > 🤖 Cloud Removal Models</ h2 >
< div class = "model-list" id = "modelsList" >
< div class = "model-card" >
< p style = "text-align: center; color: #6c757d;" > Loading models...</ p >
</ div >
</ div >
</ div >
<!-- Methods Info -->
< div class = "section" >
2026-02-15 19:47:58 +07:00
< h2 class = "section-title" > 📖 Cloud Removal Deep Learning Architectures</ h2 >
2026-01-26 13:44:55 +07:00
< div class = "grid-2" >
< div class = "card" >
2026-02-15 19:47:58 +07:00
< h3 > 🔹 U-Net</ h3 >
< p > Classic encoder-decoder with skip connections. Fast training, good baseline performance.</ p >
< div class = "status-badge status-success" > Recommended for beginners</ div >
</ div >
< div class = "card" >
< h3 > 🔹 CR-GAN</ h3 >
< p > Cloud Removal GAN - adversarial training cho kết quả chân thực hơn.</ p >
< div class = "status-badge status-training" > Advanced</ div >
</ div >
< div class = "card" >
< h3 > 🔹 SpA-GAN</ h3 >
< p > Spatial Attention GAN - attention mechanism tập trung vào vùng có mây.</ p >
< div class = "status-badge status-success" > Best quality</ div >
</ div >
< div class = "card" >
< h3 > 🔹 GLF-CR</ h3 >
< p > Global-Local Fusion - kết hợp features global và local cho chi tiết tốt hơn.</ p >
< div class = "status-badge status-training" > High accuracy</ div >
</ div >
< div class = "card" >
< h3 > 🔹 SEN12MS-CR</ h3 >
< p > Multi-modal fusion - kết hợp Sentinel-1 radar và Sentinel-2 optical.</ p >
< div class = "status-badge status-success" > Multi-sensor</ div >
2026-01-26 13:44:55 +07:00
</ div >
< div class = "card" >
2026-02-15 19:47:58 +07:00
< h3 > 🔹 RSDehazeNet</ h3 >
< p > Remote Sensing Dehaze Network - chuyên cho ảnh viễn thám.</ p >
< div class = "status-badge status-training" > RS specialized</ div >
2026-01-26 13:44:55 +07:00
</ div >
< div class = "card" >
2026-02-15 19:47:58 +07:00
< h3 > 🔹 Cloud-Net</ h3 >
< p > Encoder-Decoder architecture với residual connections.</ p >
< div class = "status-badge status-success" > Balanced</ div >
2026-01-26 13:44:55 +07:00
</ div >
< div class = "card" >
2026-02-15 19:47:58 +07:00
< h3 > 🔹 DSen2-CR</ h3 >
< p > Deep Sentinel-2 Cloud Removal - tận dụng temporal information.</ p >
< div class = "status-badge status-training" > Temporal fusion</ div >
2026-01-26 13:44:55 +07:00
</ div >
</ div >
</ div >
</ div >
</ div >
< script >
2026-02-24 21:36:02 +07:00
let psnrChart = null ;
let statusPolling = null ;
let bestPSNRData = {
modelPSNR : 0 ,
epoch : 0 ,
trainLoss : 0 ,
valLoss : 0 ,
baselinePSNR : 0 ,
improvement : 0
};
let worstPSNRData = {
modelPSNR : Infinity ,
epoch : 0 ,
trainLoss : 0 ,
valLoss : 0 ,
baselinePSNR : 0 ,
improvement : 0
};
let psnrHistory = [];
let baselinePSNR = 0 ; // PSNR cận dưới (cloudy vs clean without model)
2026-01-26 13:44:55 +07:00
// Load models on page load
window . addEventListener ( 'load' , () => {
refreshModels ();
loadCloudRemovalMethods ();
2026-02-24 21:36:02 +07:00
initPSNRChart ();
2026-01-26 13:44:55 +07:00
});
2026-02-24 21:36:02 +07:00
// Initialize PSNR Chart
function initPSNRChart () {
const ctx = document . getElementById ( 'psnrChart' ). getContext ( '2d' );
psnrChart = new Chart ( ctx , {
type : 'line' ,
data : {
labels : [],
datasets : [
{
label : 'Model PSNR (dB)' ,
data : [],
borderColor : 'rgb(102, 126, 234)' ,
backgroundColor : 'rgba(102, 126, 234, 0.1)' ,
borderWidth : 3 ,
tension : 0.4 ,
fill : true
},
{
label : 'Baseline PSNR (dB)' ,
data : [],
borderColor : 'rgb(245, 87, 108)' ,
backgroundColor : 'rgba(245, 87, 108, 0.1)' ,
borderWidth : 2 ,
borderDash : [ 5 , 5 ],
tension : 0 ,
fill : false
},
{
label : 'Improvement (dB)' ,
data : [],
borderColor : 'rgb(67, 233, 123)' ,
backgroundColor : 'rgba(67, 233, 123, 0.1)' ,
borderWidth : 2 ,
tension : 0.4 ,
fill : true ,
yAxisID : 'y1'
}
]
},
options : {
responsive : true ,
maintainAspectRatio : false ,
interaction : {
mode : 'index' ,
intersect : false ,
},
plugins : {
legend : {
display : true ,
position : 'top' ,
},
title : {
display : true ,
text : 'Model phải vượt qua Baseline (cận dưới) để có ý nghĩa!'
}
},
scales : {
y : {
type : 'linear' ,
display : true ,
position : 'left' ,
title : {
display : true ,
text : 'PSNR (dB)'
}
},
y1 : {
type : 'linear' ,
display : true ,
position : 'right' ,
title : {
display : true ,
text : 'Improvement (dB)'
},
grid : {
drawOnChartArea : false ,
},
}
}
}
});
}
// Update PSNR Chart with new data
function updatePSNRChart ( history , baselinePSNR ) {
if ( ! psnrChart || ! history ) return ;
psnrChart . data . labels = history . epochs ;
psnrChart . data . datasets [ 0 ]. data = history . val_psnrs ;
psnrChart . data . datasets [ 1 ]. data = history . epochs . map (() => baselinePSNR );
psnrChart . data . datasets [ 2 ]. data = history . improvements ;
psnrChart . update ();
}
// Poll training status
function startStatusPolling () {
if ( statusPolling ) clearInterval ( statusPolling );
statusPolling = setInterval ( async () => {
try {
const response = await fetch ( '/api/cloud-removal/training/status' );
const status = await response . json ();
if ( status . is_training ) {
// Update metrics
document . getElementById ( 'modelPSNR' ). textContent =
status . val_psnr > 0 ? status . val_psnr . toFixed ( 2 ) : '--' ;
document . getElementById ( 'baselinePSNR' ). textContent =
status . baseline_psnr > 0 ? status . baseline_psnr . toFixed ( 2 ) : '--' ;
document . getElementById ( 'improvement' ). textContent =
status . improvement !== 0 ? ( status . improvement > 0 ? '+' : '' ) + status . improvement . toFixed ( 2 ) : '--' ;
document . getElementById ( 'epochProgress' ). textContent =
` ${ status . current_epoch } / ${ status . total_epochs } ` ;
// Track PSNR history
if ( status . val_psnr > 0 ) {
psnrHistory . push ( status . val_psnr );
}
// Track best PSNR (cao nhất - cận trên)
if ( status . val_psnr > bestPSNRData . modelPSNR && status . val_psnr > 0 ) {
bestPSNRData = {
modelPSNR : status . val_psnr ,
epoch : status . current_epoch ,
trainLoss : status . train_loss ,
valLoss : status . val_loss ,
baselinePSNR : status . baseline_psnr ,
improvement : status . improvement
};
// Update best PSNR display
document . getElementById ( 'bestPSNRCard' ). style . display = 'block' ;
document . getElementById ( 'bestModelPSNR' ). textContent = bestPSNRData . modelPSNR . toFixed ( 2 );
document . getElementById ( 'bestEpoch' ). textContent = bestPSNRData . epoch ;
document . getElementById ( 'bestTrainLoss' ). textContent = bestPSNRData . trainLoss . toFixed ( 6 );
document . getElementById ( 'bestValLoss' ). textContent = bestPSNRData . valLoss . toFixed ( 6 );
document . getElementById ( 'bestBaselinePSNR' ). textContent = bestPSNRData . baselinePSNR . toFixed ( 2 );
document . getElementById ( 'bestImprovement' ). textContent =
( bestPSNRData . improvement > 0 ? '+' : '' ) + bestPSNRData . improvement . toFixed ( 2 );
// Add log for new best PSNR
addLog ( 'success' , `🏆 New Best PSNR! Epoch ${ bestPSNRData . epoch } : ${ bestPSNRData . modelPSNR . toFixed ( 2 ) } dB ` +
`(Improvement: + ${ bestPSNRData . improvement . toFixed ( 2 ) } dB vs baseline)` );
}
// Track worst PSNR (thấp nhất - cận dưới)
if ( status . val_psnr < worstPSNRData . modelPSNR && status . val_psnr > 0 ) {
worstPSNRData = {
modelPSNR : status . val_psnr ,
epoch : status . current_epoch ,
trainLoss : status . train_loss ,
valLoss : status . val_loss ,
baselinePSNR : status . baseline_psnr ,
improvement : status . improvement
};
// Update worst PSNR display
document . getElementById ( 'worstPSNRCard' ). style . display = 'block' ;
document . getElementById ( 'worstModelPSNR' ). textContent = worstPSNRData . modelPSNR . toFixed ( 2 );
document . getElementById ( 'worstEpoch' ). textContent = worstPSNRData . epoch ;
document . getElementById ( 'worstTrainLoss' ). textContent = worstPSNRData . trainLoss . toFixed ( 6 );
document . getElementById ( 'worstValLoss' ). textContent = worstPSNRData . valLoss . toFixed ( 6 );
document . getElementById ( 'worstBaselinePSNR' ). textContent = worstPSNRData . baselinePSNR . toFixed ( 2 );
document . getElementById ( 'worstImprovement' ). textContent =
( worstPSNRData . improvement > 0 ? '+' : '' ) + worstPSNRData . improvement . toFixed ( 2 );
// Add log for new worst PSNR
addLog ( 'warning' , `📉 Worst PSNR updated: Epoch ${ worstPSNRData . epoch } : ${ worstPSNRData . modelPSNR . toFixed ( 2 ) } dB (cận dưới)` );
}
// Update Performance Range Summary
if ( bestPSNRData . modelPSNR > 0 && worstPSNRData . modelPSNR < Infinity && psnrHistory . length > 0 ) {
document . getElementById ( 'performanceRangeCard' ). style . display = 'block' ;
const range = bestPSNRData . modelPSNR - worstPSNRData . modelPSNR ;
const avgPSNR = psnrHistory . reduce (( a , b ) => a + b , 0 ) / psnrHistory . length ;
const stability = avgPSNR > 0 ? (( 1 - range / avgPSNR ) * 100 ) : 0 ;
// Update baseline PSNR (cận dưới tuyệt đối)
if ( status . baseline_psnr > 0 ) {
baselinePSNR = status . baseline_psnr ;
}
document . getElementById ( 'psnrRange' ). textContent = range . toFixed ( 2 );
document . getElementById ( 'avgPSNR' ). textContent = avgPSNR . toFixed ( 2 );
document . getElementById ( 'stability' ). textContent = Math . max ( 0 , stability ). toFixed ( 1 );
document . getElementById ( 'baselinePSNRDisplay' ). textContent = baselinePSNR > 0 ? baselinePSNR . toFixed ( 2 ) : '--' ;
// Compare worst PSNR with baseline PSNR
const baselineComparisonDiv = document . getElementById ( 'baselineComparisonText' );
if ( baselinePSNR > 0 ) {
const margin = worstPSNRData . modelPSNR - baselinePSNR ;
if ( margin > 5 ) {
baselineComparisonDiv . innerHTML = `✅ <strong>Tốt:</strong> PSNR thấp nhất ( ${ worstPSNRData . modelPSNR . toFixed ( 2 ) } dB) vượt baseline + ${ margin . toFixed ( 2 ) } dB - Model học tốt ngay cả trong worst case` ;
baselineComparisonDiv . style . color = '#2ecc71' ;
} else if ( margin > 2 ) {
baselineComparisonDiv . innerHTML = `⚠️ <strong>Khá:</strong> PSNR thấp nhất ( ${ worstPSNRData . modelPSNR . toFixed ( 2 ) } dB) vượt baseline + ${ margin . toFixed ( 2 ) } dB - Cần cải thiện stability` ;
baselineComparisonDiv . style . color = '#f39c12' ;
} else if ( margin > 0 ) {
baselineComparisonDiv . innerHTML = `⚠️ <strong>Yếu:</strong> PSNR thấp nhất ( ${ worstPSNRData . modelPSNR . toFixed ( 2 ) } dB) chỉ vượt baseline + ${ margin . toFixed ( 2 ) } dB - Model không ổn định` ;
baselineComparisonDiv . style . color = '#e67e22' ;
} else {
baselineComparisonDiv . innerHTML = `❌ <strong>Kém:</strong> PSNR thấp nhất ( ${ worstPSNRData . modelPSNR . toFixed ( 2 ) } dB) không vượt baseline ( ${ baselinePSNR . toFixed ( 2 ) } dB) - Model thất bại` ;
baselineComparisonDiv . style . color = '#e74c3c' ;
}
} else {
baselineComparisonDiv . innerHTML = 'Đang chờ baseline PSNR...' ;
baselineComparisonDiv . style . color = '#95a5a6' ;
}
}
// Update progress bar
const progress = status . total_epochs > 0 ? ( status . current_epoch / status . total_epochs ) * 100 : 0 ;
const progressBar = document . getElementById ( 'progressBar' );
progressBar . style . width = progress + '%' ;
progressBar . textContent = Math . floor ( progress ) + '%' ;
// Update chart
if ( status . history && status . history . epochs . length > 0 ) {
updatePSNRChart ( status . history , status . baseline_psnr );
}
// Add log for significant events
if ( status . current_epoch > 0 && status . current_epoch % 5 === 0 ) {
const improvement = status . improvement ;
const emoji = improvement > 0 ? '✅' : '⚠️' ;
addLog ( 'info' , `Epoch ${ status . current_epoch } : Model PSNR= ${ status . val_psnr . toFixed ( 2 ) } dB, ` +
`Baseline= ${ status . baseline_psnr . toFixed ( 2 ) } dB, Improvement= ${ emoji } ${ improvement > 0 ? '+' : '' }${ improvement . toFixed ( 2 ) } dB` );
}
} else {
// Training finished
if ( status . progress === 'Completed!' ) {
addLog ( 'info' , '✅ Training completed successfully!' );
stopStatusPolling ();
// Regenerate report with best_checkpoint and worst_checkpoint if available
if ( status . result && bestPSNRData . modelPSNR > 0 ) {
setTimeout ( async () => {
try {
const reportData = {
training_result : status . result ,
best_checkpoint : {
modelPSNR : bestPSNRData . modelPSNR ,
epoch : bestPSNRData . epoch ,
trainLoss : bestPSNRData . trainLoss ,
valLoss : bestPSNRData . valLoss ,
baselinePSNR : bestPSNRData . baselinePSNR ,
improvement : bestPSNRData . improvement
}
};
// Add worst_checkpoint if available
if ( worstPSNRData . modelPSNR < Infinity ) {
reportData . worst_checkpoint = {
modelPSNR : worstPSNRData . modelPSNR ,
epoch : worstPSNRData . epoch ,
trainLoss : worstPSNRData . trainLoss ,
valLoss : worstPSNRData . valLoss ,
baselinePSNR : worstPSNRData . baselinePSNR ,
improvement : worstPSNRData . improvement
};
}
// Add baseline PSNR if available
if ( baselinePSNR > 0 ) {
reportData . baseline_psnr = baselinePSNR ;
}
const regenerateResponse = await fetch ( '/api/training/report/regenerate' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON . stringify ( reportData )
});
const regenerateData = await regenerateResponse . json ();
if ( regenerateData . success ) {
console . log ( 'Report regenerated with checkpoints:' , regenerateData . report_path );
addLog ( 'success' , '📊 Báo cáo đã được cập nhật với Best/Worst PSNR Checkpoint và Performance Range' );
}
} catch ( err ) {
console . error ( 'Failed to regenerate report:' , err );
}
refreshModels ();
}, 1000 );
} else {
setTimeout ( refreshModels , 2000 );
}
} else if ( status . error ) {
addLog ( 'error' , `❌ Error: ${ status . error } ` );
stopStatusPolling ();
}
}
} catch ( error ) {
console . error ( 'Error polling status:' , error );
}
}, 2000 ); // Poll every 2 seconds
}
function stopStatusPolling () {
if ( statusPolling ) {
clearInterval ( statusPolling );
statusPolling = null ;
}
}
// Stop training
async function stopTraining () {
if ( ! confirm ( 'Are you sure you want to stop training?' )) return ;
try {
const response = await fetch ( '/api/cloud-removal/training/stop' , {
method : 'POST'
});
if ( response . ok ) {
addLog ( 'warning' , '⏹️ Stopping training...' );
} else {
const error = await response . json ();
alert ( 'Error: ' + error . detail );
}
} catch ( error ) {
alert ( 'Error: ' + error . message );
}
}
2026-01-26 13:44:55 +07:00
// Handle training form submission
document . getElementById ( 'trainingForm' ). addEventListener ( 'submit' , async ( e ) => {
e . preventDefault ();
const config = {
data_dir : document . getElementById ( 'dataDir' ). value ,
model_name : document . getElementById ( 'modelName' ). value ,
2026-02-15 19:47:58 +07:00
architecture : document . getElementById ( 'modelArchitecture' ). value ,
2026-01-26 13:44:55 +07:00
use_s1 : document . getElementById ( 'useS1' ). checked ,
batch_size : parseInt ( document . getElementById ( 'batchSize' ). value ),
num_epochs : parseInt ( document . getElementById ( 'numEpochs' ). value ),
learning_rate : parseFloat ( document . getElementById ( 'learningRate' ). value ),
use_gpu : document . getElementById ( 'useGPU' ). checked
};
try {
const response = await fetch ( '/api/cloud-removal/train' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON . stringify ( config )
});
const result = await response . json ();
if ( response . ok ) {
2026-02-24 21:36:02 +07:00
// Reset tracking variables for new training
bestPSNRData = {
modelPSNR : 0 ,
epoch : 0 ,
trainLoss : 0 ,
valLoss : 0 ,
baselinePSNR : 0 ,
improvement : 0
};
worstPSNRData = {
modelPSNR : Infinity ,
epoch : 0 ,
trainLoss : 0 ,
valLoss : 0 ,
baselinePSNR : 0 ,
improvement : 0
};
psnrHistory = [];
baselinePSNR = 0 ;
// Hide checkpoint cards at start
document . getElementById ( 'bestPSNRCard' ). style . display = 'none' ;
document . getElementById ( 'worstPSNRCard' ). style . display = 'none' ;
document . getElementById ( 'performanceRangeCard' ). style . display = 'none' ;
2026-01-26 13:44:55 +07:00
// Show training status section
document . getElementById ( 'trainingStatus' ). style . display = 'block' ;
document . getElementById ( 'statusMessage' ). innerHTML = `
2026-02-24 21:36:02 +07:00
<div class="status-badge status-training">🚀 Training Started: ${ result . training_id } </div>
<p style="margin-top: 10px;">Model training has started. Real-time metrics will appear below.</p>
2026-01-26 13:44:55 +07:00
` ;
2026-02-24 21:36:02 +07:00
addLog ( 'info' , `✅ Training started: ${ result . training_id } ` );
2026-01-26 13:44:55 +07:00
addLog ( 'info' , `Config: ${ JSON . stringify ( config , null , 2 ) } ` );
2026-02-24 21:36:02 +07:00
// Start polling status
startStatusPolling ();
2026-01-26 13:44:55 +07:00
} else {
alert ( 'Error starting training: ' + ( result . detail || result . error ));
}
} catch ( error ) {
alert ( 'Error: ' + error . message );
}
});
// Refresh models list
async function refreshModels () {
try {
const response = await fetch ( '/api/cloud-removal/models' );
const data = await response . json ();
const modelsList = document . getElementById ( 'modelsList' );
if ( data . models && data . models . length > 0 ) {
modelsList . innerHTML = data . models . map ( model => `
<div class="model-card">
<h3>📦 ${ model . filename } </h3>
2026-02-15 19:47:58 +07:00
<div class="model-info">🏗️ Architecture: ${ model . architecture || 'U-Net' } </div>
2026-01-26 13:44:55 +07:00
<div class="model-info">📊 Epoch: ${ model . epoch } </div>
<div class="model-info">📉 Train Loss: ${ model . train_loss . toFixed ( 6 ) } </div>
<div class="model-info">📉 Val Loss: ${ model . val_loss . toFixed ( 6 ) } </div>
2026-02-24 21:36:02 +07:00
${ model . val_psnr ? `<div class="model-info">📈 Model PSNR: <strong> ${ model . val_psnr . toFixed ( 2 ) } dB</strong></div>` : '' }
${ model . baseline_psnr ? `<div class="model-info">📉 Baseline PSNR: ${ model . baseline_psnr . toFixed ( 2 ) } dB</div>` : '' }
${ model . val_psnr && model . baseline_psnr ? `<div class="model-info">⚡ Improvement: <strong style="color: ${ model . val_psnr > model . baseline_psnr ? 'green' : 'red' } "> ${ ( model . val_psnr - model . baseline_psnr > 0 ? '+' : '' ) }${ ( model . val_psnr - model . baseline_psnr ). toFixed ( 2 ) } dB</strong></div>` : '' }
2026-01-26 13:44:55 +07:00
<div class="model-info">📡 Use S1: ${ model . use_s1 ? 'Yes' : 'No' } </div>
<div class="model-info">💾 Size: ${ model . size_mb . toFixed ( 2 ) } MB</div>
<div class="model-info">📅 Created: ${ new Date ( model . created * 1000 ). toLocaleString () } </div>
<div style="margin-top: 15px;">
<button class="btn btn-danger" onclick="deleteModel(' ${ model . filename } ')">
🗑️ Delete
</button>
</div>
</div>
` ). join ( '' );
} else {
modelsList . innerHTML = `
<div class="model-card">
<p style="text-align: center; color: #6c757d;">
No cloud removal models found.<br>
Train your first model above!
</p>
</div>
` ;
}
} catch ( error ) {
console . error ( 'Error loading models:' , error );
}
}
// Delete model
async function deleteModel ( filename ) {
if ( ! confirm ( `Delete model ${ filename } ?` )) return ;
try {
const response = await fetch ( `/api/cloud-removal/models/ ${ filename } ` , {
method : 'DELETE'
});
if ( response . ok ) {
alert ( 'Model deleted successfully' );
refreshModels ();
} else {
const error = await response . json ();
alert ( 'Error deleting model: ' + error . detail );
}
} catch ( error ) {
alert ( 'Error: ' + error . message );
}
}
// Load cloud removal methods
async function loadCloudRemovalMethods () {
try {
const response = await fetch ( '/api/cloud-removal/methods' );
const data = await response . json ();
console . log ( 'Available cloud removal methods:' , data . methods );
} catch ( error ) {
console . error ( 'Error loading methods:' , error );
}
}
// Add log entry
function addLog ( type , message ) {
const logs = document . getElementById ( 'trainingLogs' );
const timestamp = new Date (). toLocaleTimeString ();
const logClass = type === 'error' ? 'log-error' : ( type === 'warning' ? 'log-warning' : 'log-info' );
const entry = document . createElement ( 'div' );
entry . className = `log-entry ${ logClass } ` ;
entry . textContent = `[ ${ timestamp } ] ${ message } ` ;
logs . appendChild ( entry );
logs . scrollTop = logs . scrollHeight ;
}
</ script >
</ body >
</ html >