|
|
<!DOCTYPE html> |
|
|
<html lang="th"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Lesion Detection</title> |
|
|
|
|
|
<link rel="stylesheet" href="{{ url_for('static', path='css/detect.css') }}"> |
|
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
|
|
<link rel="preconnect" href="https://fonts.gstatic.com"> |
|
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Thai:wght@100;200;300;400;500;600;700&display=swap" |
|
|
rel="stylesheet"> |
|
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', path='image/production.png') }}"> |
|
|
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> |
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css"> |
|
|
|
|
|
<style> |
|
|
:root { --primary-color:#1da2e0; --secondary-color:#c8e5fa; } |
|
|
body { font-family:'IBM Plex Sans Thai',sans-serif; display:flex; flex-direction:column; min-height:100vh; } |
|
|
.background-color { background-color:#f8f9fa; } |
|
|
|
|
|
|
|
|
.loading-overlay { |
|
|
position:fixed; top:0; left:0; width:100%; height:100%; |
|
|
background:rgba(0,0,0,0.7); z-index:2000; |
|
|
display:flex; justify-content:center; align-items:center; |
|
|
} |
|
|
.spinner { animation:rotate 2s linear infinite; width:50px; height:50px; } |
|
|
.path { stroke:#93d3f4; stroke-linecap:round; animation:dash 1.5s ease-in-out infinite; } |
|
|
@keyframes rotate { 100% { transform:rotate(360deg); } } |
|
|
@keyframes dash { |
|
|
0% { stroke-dasharray:1,150; stroke-dashoffset:0; } |
|
|
50% { stroke-dasharray:90,150; stroke-dashoffset:-35; } |
|
|
100% { stroke-dasharray:90,150; stroke-dashoffset:-124; } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
|
|
|
<body> |
|
|
|
|
|
|
|
|
<nav class="navbar navbar-expand-lg navbar-light bg-white shadow-sm"> |
|
|
<div class="container-fluid"> |
|
|
<a class="navbar-brand" href="/"> |
|
|
<img src="{{ url_for('static', path='image/Logo.png') }}" class="img-fluid" style="max-width:100px;"> |
|
|
</a> |
|
|
<button class="navbar-toggler" data-bs-toggle="collapse" data-bs-target="#navbarNav"> |
|
|
<span class="navbar-toggler-icon"></span> |
|
|
</button> |
|
|
<div class="collapse navbar-collapse" id="navbarNav"> |
|
|
<ul class="navbar-nav ms-auto"> |
|
|
<li class="nav-item"><a class="nav-link active" href="/detect">ตรวจจับรอยโรค</a></li> |
|
|
</ul> |
|
|
</div> |
|
|
</div> |
|
|
</nav> |
|
|
|
|
|
|
|
|
<div class="detect-section flex-grow-1 background-color"> |
|
|
<div class="container py-4"> |
|
|
<div class="row justify-content-center"> |
|
|
<div class="col-md-8"> |
|
|
|
|
|
<div class="input-card shadow-lg p-4 bg-white rounded-3"> |
|
|
|
|
|
{% if error %} |
|
|
<div class="alert alert-danger text-center">{{ error }}</div> |
|
|
{% endif %} |
|
|
|
|
|
<h2 class="text-center mb-4 fw-bold">ตรวจจับรอยโรคในช่องปาก</h2> |
|
|
|
|
|
<form id="sectionForm" method="post" enctype="multipart/form-data" action="/uploaded"> |
|
|
|
|
|
|
|
|
<div class="mb-4"> |
|
|
<label class="form-label fs-5">อัปโหลดรูปภาพ</label> |
|
|
<input class="form-control" type="file" name="file" required accept="image/*"> |
|
|
</div> |
|
|
|
|
|
<p class="fs-5">ประวัติ/อาการผู้ป่วย</p> |
|
|
<p class="text-muted small">เลือกจากรายการ หรือกรอกเพิ่ม (ถ้าไม่เลือก = ไม่มีอาการ)</p> |
|
|
|
|
|
<div class="row"> |
|
|
|
|
|
|
|
|
<div class="col-md-6"> |
|
|
<div class="form-check mb-1"> |
|
|
<input class="form-check-input symptom-checkbox" id="check6" type="checkbox" name="checkboxes" value="noSymptoms"> |
|
|
<label class="form-check-label" for="check6">ไม่มีอาการผิดปกติ</label> |
|
|
</div> |
|
|
|
|
|
<div class="form-check mb-1"> |
|
|
<input class="form-check-input symptom-checkbox" type="checkbox" name="checkboxes" value="drinkAlcohol"> |
|
|
<label class="form-check-label">ดื่มเครื่องดื่มแอลกอฮอล์</label> |
|
|
</div> |
|
|
|
|
|
<div class="form-check mb-1"> |
|
|
<input class="form-check-input symptom-checkbox" type="checkbox" name="checkboxes" value="smoking"> |
|
|
<label class="form-check-label">สูบบุหรี่</label> |
|
|
</div> |
|
|
|
|
|
<div class="form-check mb-1"> |
|
|
<input class="form-check-input symptom-checkbox" type="checkbox" name="checkboxes" value="chewBetelNut"> |
|
|
<label class="form-check-label">เคี้ยวหมาก</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="col-md-6"> |
|
|
<div class="form-check mb-1"> |
|
|
<input class="form-check-input symptom-checkbox" type="checkbox" name="checkboxes" value="eatSpicyFood"> |
|
|
<label class="form-check-label">รับประทานอาหารเผ็ดแล้วรู้สึกระคายเคือง</label> |
|
|
</div> |
|
|
<div class="form-check mb-1"> |
|
|
<input class="form-check-input symptom-checkbox" type="checkbox" name="checkboxes" value="wipeOff"> |
|
|
<label class="form-check-label">คราบขาวที่สามารถลอกออก</label> |
|
|
</div> |
|
|
<div class="form-check mb-1"> |
|
|
<input class="form-check-input symptom-checkbox" type="checkbox" name="checkboxes" value="alwaysHurts"> |
|
|
<label class="form-check-label">เจ็บหรือแสบตลอดเวลา</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mt-4"> |
|
|
<label class="form-label">รายละเอียดอาการเพิ่มเติม:</label> |
|
|
<textarea class="form-control" id="symptomTextArea" name="symptom_text" rows="4" |
|
|
placeholder="เช่น มีอาการปวดเป็นบางครั้ง, เป็นมานาน 2 สัปดาห์ ..."></textarea> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="cf-turnstile mt-4 mb-3" |
|
|
data-sitekey="0x4AAAAAACEfyPjr3pfV21Mm" |
|
|
data-callback="onTurnstileSuccess"> |
|
|
</div> |
|
|
|
|
|
<input type="hidden" id="turnstile-token" name="cf-turnstile-response"> |
|
|
|
|
|
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> |
|
|
<script> |
|
|
function onTurnstileSuccess(token) { |
|
|
document.getElementById("turnstile-token").value = token; |
|
|
} |
|
|
</script> |
|
|
|
|
|
|
|
|
<div class="mt-4 d-flex justify-content-center"> |
|
|
<button class="button btn btn-primary px-4" type="submit">Submit</button> |
|
|
</div> |
|
|
|
|
|
</form> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="loading" class="loading-overlay d-none"> |
|
|
<div class="text-center text-white"> |
|
|
<svg class="spinner" viewBox="0 0 50 50"> |
|
|
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle> |
|
|
</svg> |
|
|
<p class="mt-2">กำลังประมวลผล กรุณารอสักครู่...</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
{% if image_b64_data %} |
|
|
<div class="modal fade" id="outputModal" tabindex="-1"> |
|
|
<div class="modal-dialog modal-xl modal-dialog-centered"> |
|
|
<div class="modal-content"> |
|
|
|
|
|
<div class="modal-header"> |
|
|
<h5 class="modal-title">ผลการตรวจจับรอยโรค</h5> |
|
|
<button class="btn-close" data-bs-dismiss="modal"></button> |
|
|
</div> |
|
|
|
|
|
<div class="modal-body"> |
|
|
<div class="row g-3"> |
|
|
|
|
|
<div class="col-lg-8"> |
|
|
<div class="row"> |
|
|
|
|
|
<div class="col-md-6 text-center"> |
|
|
<h6>ภาพต้นฉบับ</h6> |
|
|
<img src="data:image/jpeg;base64, {{ image_b64_data }}" |
|
|
class="img-fluid rounded shadow-sm"> |
|
|
</div> |
|
|
|
|
|
<div class="col-md-6 text-center"> |
|
|
<h6 class="d-inline-block">Grad-CAM Heatmap</h6> |
|
|
<i class="bi bi-info-circle-fill ms-1" |
|
|
data-bs-toggle="tooltip" |
|
|
title="พื้นที่สีแดงคือบริเวณที่โมเดลใช้ตัดสินใจมากที่สุด"></i> |
|
|
|
|
|
<img src="data:image/jpeg;base64, {{ gradcam_b64_data }}" |
|
|
class="img-fluid rounded shadow-sm"> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="col-lg-4 d-flex align-items-center"> |
|
|
<div class="w-100"> |
|
|
<h4 class="mb-3">ผลการวิเคราะห์</h4> |
|
|
|
|
|
<div class="alert alert-primary"> |
|
|
<h5>ประเภทรอยโรค:</h5> |
|
|
<p class="fs-4 fw-bold">{{ name_out }}</p> |
|
|
</div> |
|
|
|
|
|
<div class="alert alert-danger"> |
|
|
<h5>ความมั่นใจ:</h5> |
|
|
<p class="fs-4 fw-bold">{{ eva_output }}%</p> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="modal-footer"> |
|
|
<a href="/detect" class="btn btn-secondary">ตรวจใหม่</a> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
{% endif %} |
|
|
|
|
|
<footer class="footer bg-dark text-white p-4 text-center mt-auto"> |
|
|
© 2025 MYCompany. All Rights Reserved. |
|
|
</footer> |
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
|
|
const form = document.getElementById('sectionForm'); |
|
|
const loadingOverlay = document.getElementById('loading'); |
|
|
|
|
|
form.addEventListener('submit', () => { |
|
|
if (form.checkValidity()) loadingOverlay.classList.remove('d-none'); |
|
|
}); |
|
|
|
|
|
const modalElement = document.getElementById('outputModal'); |
|
|
if (modalElement) new bootstrap.Modal(modalElement).show(); |
|
|
|
|
|
const noSymptoms = document.getElementById('check6'); |
|
|
const other = document.querySelectorAll('.symptom-checkbox:not(#check6)'); |
|
|
|
|
|
noSymptoms.addEventListener('change', function() { |
|
|
if (this.checked) { |
|
|
other.forEach(cb => { |
|
|
if (!['check1','check2','check3'].includes(cb.id)) { |
|
|
cb.checked = false; |
|
|
cb.disabled = true; |
|
|
} |
|
|
}); |
|
|
} else { |
|
|
other.forEach(cb => cb.disabled = false); |
|
|
} |
|
|
}); |
|
|
|
|
|
}); |
|
|
</script> |
|
|
|
|
|
</body> |
|
|
</html> |
|
|
|