rad_explain2 / templates /index.html
seawolf2357's picture
Update templates/index.html
79b7afb verified
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>방사선 λ³΄κ³ μ„œ μ„€λͺ… 도ꡬ</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500&family=Google+Sans+Text:wght@500&display=swap"
rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200"
rel="stylesheet">
<!-- Link to the correct JS file -->
<script src="https://unpkg.com/compromise" defer></script>
<script src="{{ url_for('static', filename='js/demo.js') }}" defer></script>
</head>
<body>
<div class="info">
<!-- New Info Page Content -->
<div class="info-page-container">
<div class="info-content">
<div class="info-header">
<span class="info-title-demo">방사선 μ„€λͺ… 도ꡬ 데λͺ¨</span><br>
<span class="info-title-demo info-subtitle-demo">기반 기술:</span>
<span class="info-title-med info-subtitle-demo">MedGemma</span>
</div>
<div class="info-text">방사선 μ˜μƒκ³Όμ˜ μƒν˜Έμž‘μš©μ΄ ν•™μŠ΅μ„ 크게 ν–₯μƒμ‹œν‚¬ 수 μžˆλŠ” ꡐ윑 μ‹œλ‚˜λ¦¬μ˜€λ₯Ό μƒκ°ν•΄λ³΄μ„Έμš”. 이 데λͺ¨λŠ” MedGemmaλ₯Ό 기반으둜 방사선 μ˜μƒκ³Ό κ΄€λ ¨ λ³΄κ³ μ„œλ₯Ό μ‰¬μš΄ μ–Έμ–΄λ‘œ λ²ˆμ—­ν•˜κ³ , μ˜μƒμ˜ κ΄€λ ¨ μ˜μ—­μ„ μ‹œκ°μ μœΌλ‘œ κ°•μ‘°ν•˜μ—¬ 탐색할 수 μžˆλŠ” μœ μš©ν•œ 도ꡬλ₯Ό μ œκ³΅ν•˜λŠ” 방법을 λ³΄μ—¬μ€λ‹ˆλ‹€.</div>
<div class="info-disclaimer-text"><span class="info-disclaimer-title">λ©΄μ±… μ‘°ν•­</span> 이 데λͺ¨λŠ” μ„€λͺ… λͺ©μ μœΌλ‘œλ§Œ 제곡되며 μ™„μ„±λ˜κ±°λ‚˜ 승인된 μ œν’ˆμ„ λ‚˜νƒ€λ‚΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν’ˆμ§ˆ, μ•ˆμ „μ„± λ˜λŠ” 효λŠ₯에 λŒ€ν•œ κ·œμ •μ΄λ‚˜ ν‘œμ€€ μ€€μˆ˜λ₯Ό λ‚˜νƒ€λ‚΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ‹€μ œ μ‘μš© ν”„λ‘œκ·Έλž¨μ—λŠ” μΆ”κ°€ 개발, ꡐ윑 및 적응이 ν•„μš”ν•©λ‹ˆλ‹€. 이 데λͺ¨μ—μ„œ κ°•μ‘°λœ κ²½ν—˜μ€ ν‘œμ‹œλœ μž‘μ—…μ— λŒ€ν•œ MedGemma의 κΈ°λ³Έ κΈ°λŠ₯을 보여주며, κ°œλ°œμžμ™€ μ‚¬μš©μžκ°€ κ°€λŠ₯ν•œ μ‘μš© ν”„λ‘œκ·Έλž¨μ„ νƒμƒ‰ν•˜κ³  μΆ”κ°€ κ°œλ°œμ— μ˜κ°μ„ 주도둝 돕기 μœ„ν•œ κ²ƒμž…λ‹ˆλ‹€.</div>
<button class="info-button" id="view-demo-button">데λͺ¨ 보기</button>
</div>
</div>
</div>
<div class="main">
<div class="nav-button nav-button-back" id="back-to-info-button">
<div class="nav-button-inner">
<span class="material-symbols-outlined nav-button-icon">keyboard_arrow_left</span>
<span class="nav-button-text">λ’€λ‘œ</span>
</div>
</div>
<div class="case-selector-tabs-container" id="case-selector-tabs-container">
<div>X-레이</div>
<div class="case-selector-tabs" id="case-selector-tabs">
{% if available_reports %}
{% for report in available_reports %}
{% if report.image_type == 'CXR' %}
<div class="nav-button nav-button-case" data-report-name="{{ report.name }}">
<div class="nav-button-inner">
<span class="nav-button-text">{{ report.name }}</span>
</div>
</div>
{% endif %}
{% endfor %}
{% else %}
<span class="no-cases-available">μ‚¬μš© κ°€λŠ₯ν•œ μΌ€μ΄μŠ€κ°€ μ—†μŠ΅λ‹ˆλ‹€</span>
{% endif %}
</div>
<div>CT</div>
<div class="case-selector-tabs" id="case-selector-tabs2">
{% if available_reports %}
{% for report in available_reports %}
{% if report.image_type == 'CT' %}
<div class="nav-button nav-button-case" data-report-name="{{ report.name }}">
<div class="nav-button-inner">
<span class="nav-button-text">{{ report.name }}</span>
</div>
</div>
{% endif %}
{% endfor %}
{% else %}
<span class="no-cases-available">μ‚¬μš© κ°€λŠ₯ν•œ μΌ€μ΄μŠ€κ°€ μ—†μŠ΅λ‹ˆλ‹€</span>
{% endif %}
</div>
<div style="margin-top: 20px; border-top: 1px solid #ddd; padding-top: 20px;">이미지 μ—…λ‘œλ“œ</div>
<div class="upload-section" style="padding: 20px; background-color: #f5f5f5; border-radius: 8px; margin-top: 10px;">
<form id="upload-form" enctype="multipart/form-data">
<div style="margin-bottom: 15px;">
<label for="image-upload" style="display: block; margin-bottom: 5px; font-weight: 500;">의료 μ˜μƒ 선택:</label>
<input type="file" id="image-upload" name="image" accept=".png,.jpg,.jpeg,.gif,.bmp,.dcm,.dicom" required style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
</div>
<div style="margin-bottom: 15px;">
<label for="image-type-select" style="display: block; margin-bottom: 5px; font-weight: 500;">μ˜μƒ μœ ν˜•:</label>
<select id="image-type-select" name="image_type" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
<option value="CXR">흉뢀 X-레이</option>
<option value="CT">CT</option>
<option value="OTHER">기타 의료 μ˜μƒ</option>
</select>
</div>
<div style="margin-bottom: 15px;">
<label for="context-input" style="display: block; margin-bottom: 5px; font-weight: 500;">μΆ”κ°€ 정보 (선택사항):</label>
<textarea id="context-input" name="context" rows="3" placeholder="ν™˜μž μ¦μƒμ΄λ‚˜ 검사 λͺ©μ  등을 μž…λ ₯ν•˜μ„Έμš”" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;"></textarea>
</div>
<button type="submit" class="nav-button" style="width: 100%; padding: 12px; background-color: #4285f4; color: white; border: none; border-radius: 4px; font-size: 16px; cursor: pointer;">
<span class="material-symbols-outlined" style="vertical-align: middle; margin-right: 5px;">upload</span>
νŒλ… κ°€μ΄λ“œ λ°›κΈ°
</button>
</form>
</div>
</div>
<div class="nav-button nav-button-info" id="info-button">
<div class="nav-button-inner">
<span class="material-symbols-outlined nav-button-icon">code_blocks</span>
<span class="nav-button-text">이 데λͺ¨μ— λŒ€ν•œ 상세 정보</span>
</div>
</div>
<div class="image-section">
<div id="image-modality-header" class="image-header">{{ image_type | default('의료 μ˜μƒ', true) }}
</div> <!-- Updated: ID added, initial text changed -->
<div id="image-container" class="image-container"> <!-- Container for relative positioning -->
<img id="report-image" src="" alt="의료 μ˜μƒ" style="display: none;">
<div id="image-loading" class="loading" style="display: none;">이미지 λ‘œλ”© 쀑...</div>
<div id="image-error" class="error-message" style="display: none;"></div>
</div>
<div id="ct-image-note" class="image-note">
이것은 CT의 단일 슬라이슀λ₯Ό λ³΄μ—¬μ€λ‹ˆλ‹€. λ³΄κ³ μ„œμ˜ λͺ¨λ“  μš”μ†Œλ₯Ό μ‹œκ°ν™”ν•  μˆ˜λŠ” μ—†μŠ΅λ‹ˆλ‹€.
</div>
</div>
<div class="report-section">
<div id="app-loading" class="loading" style="display: none;">λ³΄κ³ μ„œ 상세 정보 λ‘œλ”© 쀑...</div>
<div id="app-error" class="error-message" style="display: none;"></div>
<div class="explanation-section">
<div id="explanation-output" class="explanation-box">
<div id="explanation-loading" class="loading">μ„€λͺ…을 μƒμ„±ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€... μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”.</div>
<div class="explanation-header">이것이 μ˜λ―Έν•˜λŠ” λ°”
</div>
<div id="explanation-content">
λ¬Έμž₯을 ν΄λ¦­ν•˜λ©΄ 여기에 μ„€λͺ…이 ν‘œμ‹œλ©λ‹ˆλ‹€.
</div>
</div>
</div>
<div class="report-text-area">
<div id="report-text-display" >
<!-- Report text will be loaded here -->
λ³΄κ³ μ„œλ₯Ό μ„ νƒν•˜λ©΄ ν…μŠ€νŠΈκ°€ ν‘œμ‹œλ©λ‹ˆλ‹€.
</div>
<div class="floating-disclaimer">
<span class="material-symbols-outlined disclaimer-icon">warning</span>
<span class="disclaimer-text">이 데λͺ¨λŠ” MedGemma의 κΈ°λ³Έ κΈ°λŠ₯을 μ„€λͺ…ν•˜κΈ° μœ„ν•œ λͺ©μ μœΌλ‘œλ§Œ μ œκ³΅λ©λ‹ˆλ‹€. μ™„μ„±λ˜κ±°λ‚˜ 승인된 μ œν’ˆμ„ λ‚˜νƒ€λ‚΄μ§€ μ•ŠμœΌλ©°, μ§ˆλ³‘μ΄λ‚˜ μƒνƒœλ₯Ό μ§„λ‹¨ν•˜κ±°λ‚˜ 치료λ₯Ό μ œμ•ˆν•˜κΈ° μœ„ν•œ 것이 μ•„λ‹ˆλ©°, μ˜ν•™μ  쑰언에 μ‚¬μš©ν•΄μ„œλŠ” μ•ˆ λ©λ‹ˆλ‹€.</span>
</div>
</div>
</div>
<!-- Embed available reports data for JavaScript -->
<script id="reports-data" type="application/json">
{{ available_reports | tojson | safe }}
</script>
<div id="explanation-error" class="error-message" style="display: none;"></div>
</div>
<!-- Immersive Info Dialog -->
<div id="immersive-info-dialog" class="dialog-overlay" style="display: none;" role="dialog" aria-modal="true"
aria-labelledby="dialog-title">
<div class="dialog-box">
<button id="dialog-close-button" class="dialog-close-btn" aria-label="λŒ€ν™”μƒμž λ‹«κΈ°">
<span class="material-symbols-outlined">close</span>
</button>
<h2 id="dialog-title" class="dialog-title-text">이 데λͺ¨μ— λŒ€ν•œ 상세 정보</h2>
<div class="dialog-body-scrollable">
<p><b>λͺ¨λΈ:</b> 이 데λͺ¨λŠ” κ΅¬κΈ€μ˜ MedGemma-4Bλ₯Ό λ…μ μ μœΌλ‘œ μ‚¬μš©ν•©λ‹ˆλ‹€. μ΄λŠ” Gemma 3 기반 λͺ¨λΈλ‘œ, 흉뢀 X-λ ˆμ΄μ™€ 같은 의료 ν…μŠ€νŠΈ 및 이미지λ₯Ό μ΄ν•΄ν•˜λ„λ‘ λ―Έμ„Έ μ‘°μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 의료 λ°μ΄ν„°μ˜ κ³ κΈ‰ 해석을 μ œκ³΅ν•˜μ—¬ AI 기반 의료 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ κ°œλ°œμ„ κ°€μ†ν™”ν•˜λŠ” MedGemma의 λŠ₯λ ₯을 λ³΄μ—¬μ€λ‹ˆλ‹€.</p>
<p><b>λͺ¨λΈ μ ‘κ·Ό 및 μ‚¬μš©:</b> κ΅¬κΈ€μ˜ MedGemma-4BλŠ” <a
href="https://huggingface.co/google/medgemma-4b-it" target="_blank">HuggingFace<img
class="hf-logo"
src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg">
</a>와
<a href="https://console.cloud.google.com/vertex-ai/publishers/google/model-garden/medgemma" target="_blank">Model
Garden <img class="hf-logo"
src="https://www.gstatic.com/cloud/images/icons/apple-icon.png"></a>μ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
λͺ¨λΈ μ‚¬μš© 및 μ œν•œ 사항에 λŒ€ν•œ μžμ„Έν•œ λ‚΄μš©μ€ <a
href="https://developers.google.com/health-ai-developer-foundations?referral=rad_explain"
target="_blank">HAI-DEF
개발자 μ‚¬μ΄νŠΈ</a>μ—μ„œ ν™•μΈν•˜μ„Έμš”.
</p>
<p><b>Health AI Developer Foundations (HAI-DEF)</b>λŠ” κ°œλ°œμžκ°€ 의료 λΆ„μ•Όμš© AI λͺ¨λΈμ„ ꡬ좕할 수 μžˆλ„λ‘ ν•˜λŠ” μ˜€ν”ˆ μ›¨μ΄νŠΈ λͺ¨λΈ 및 κ΄€λ ¨ λ¦¬μ†ŒμŠ€ λͺ¨μŒμ„ μ œκ³΅ν•©λ‹ˆλ‹€.</p>
<p><b>데λͺ¨κ°€ λ§ˆμŒμ— λ“œμ…¨λ‚˜μš”?</b> μ—¬λŸ¬λΆ„μ˜ ν”Όλ“œλ°±μ„ κΈ°λ‹€λ¦½λ‹ˆλ‹€! 이 데λͺ¨κ°€ 도움이 λ˜μ—ˆλ‹€λ©΄, 상단에 링크된 HuggingFace νŽ˜μ΄μ§€μ—μ„œ β™‘ λ²„νŠΌμ„ ν΄λ¦­ν•˜μ—¬ κ°μ‚¬μ˜ λ§ˆμŒμ„ ν‘œν˜„ν•΄μ£Όμ„Έμš”.</p>
<p><b>더 λ§Žμ€ 데λͺ¨ 탐색:</b> HuggingFace Spaces λ˜λŠ” Colab을 톡해 μΆ”κ°€ 데λͺ¨λ₯Ό ν™•μΈν•˜μ„Έμš”:
</p>
<ul>
<li><a href="https://huggingface.co/spaces/google/cxr-foundation-demo?referral=rad_explain"
target="_blank">
CXR Foundations 데λͺ¨ <img class="hf-logo"
src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg"></a>
-
λΈŒλΌμš°μ €μ—μ„œ μ‹€ν–‰λ˜λŠ” 데이터 효율적이고 μ œλ‘œμƒ· CXR 이미지 λΆ„λ₯˜λ₯Ό λ³΄μ—¬μ€λ‹ˆλ‹€.</li>
<li><a href="https://huggingface.co/spaces/google/path-foundation-demo?referral=rad_explain"
target="_blank">
Path Foundations 데λͺ¨ <img class="hf-logo"
src="https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.svg"></a>
-
λΈŒλΌμš°μ €μ—μ„œ μ‹€ν–‰λ˜λŠ” 데이터 효율적인 λΆ„λ₯˜ 및 병리 μŠ¬λΌμ΄λ“œ λ‚΄ μ΄μƒμΉ˜ 탐지λ₯Ό κ°•μ‘°ν•©λ‹ˆλ‹€.</li>
<li><a href="https://github.com/Google-Health/medgemma/tree/main/notebooks/fine_tune_with_hugging_face.ipynb" target="_blank">
MedGemma λ―Έμ„Έ μ‘°μ • Colab <img class="hf-logo"
src="https://upload.wikimedia.org/wikipedia/commons/d/d0/Google_Colaboratory_SVG_Logo.svg"></a>
-
이 λͺ¨λΈμ„ λ―Έμ„Έ μ‘°μ •ν•˜λŠ” λ°©λ²•μ˜ 예제λ₯Ό ν™•μΈν•˜μ„Έμš”.</li>
</ul>
</div>
</div>
</div>
<script>
// 이미지 μ—…λ‘œλ“œ 폼 처리
document.addEventListener('DOMContentLoaded', function() {
const uploadForm = document.getElementById('upload-form');
if (uploadForm) {
uploadForm.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const submitButton = this.querySelector('button[type="submit"]');
const originalButtonText = submitButton.innerHTML;
// λ‘œλ”© μƒνƒœ ν‘œμ‹œ
submitButton.disabled = true;
submitButton.innerHTML = '<span class="material-symbols-outlined" style="vertical-align: middle; margin-right: 5px;">hourglass_empty</span>κ°€μ΄λ“œ 생성 쀑...';
// κΈ°μ‘΄ μ—λŸ¬ λ©”μ‹œμ§€ 제거
const errorDiv = document.getElementById('explanation-error');
if (errorDiv) {
errorDiv.style.display = 'none';
errorDiv.textContent = '';
}
try {
const response = await fetch('/upload_and_analyze', {
method: 'POST',
body: formData
});
const result = await response.json();
if (response.ok) {
// μ—…λ‘œλ“œλœ 이미지 ν‘œμ‹œ
const fileInput = document.getElementById('image-upload');
const file = fileInput.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const reportImage = document.getElementById('report-image');
if (reportImage) {
reportImage.src = e.target.result;
reportImage.style.display = 'block';
}
};
reader.readAsDataURL(file);
}
// 뢄석 κ²°κ³Ό ν‘œμ‹œ
const explanationContent = document.getElementById('explanation-content');
if (explanationContent) {
explanationContent.innerHTML = result.analysis.replace(/\n/g, '<br>');
}
// λ³΄κ³ μ„œ ν…μŠ€νŠΈ μ˜μ—­μ— μ—…λ‘œλ“œ 정보 ν‘œμ‹œ
const reportTextDisplay = document.getElementById('report-text-display');
if (reportTextDisplay) {
reportTextDisplay.innerHTML = '<div style="padding: 20px; background-color: #e8f4f8; border-radius: 8px;">' +
'<h3 style="margin-top: 0;">μ˜μƒ νŒλ… κ°€μ΄λ“œ</h3>' +
'<p>파일λͺ…: ' + file.name + '</p>' +
'<p>μ˜μƒ μœ ν˜•: ' + document.getElementById('image-type-select').selectedOptions[0].text + '</p>' +
(document.getElementById('context-input').value ? '<p>μΆ”κ°€ 정보: ' + document.getElementById('context-input').value + '</p>' : '') +
'<p style="margin-top: 10px; padding: 10px; background-color: #fff3cd; border-radius: 4px;">⚠️ μ—…λ‘œλ“œν•˜μ‹  μ˜μƒμ— λŒ€ν•œ 일반적인 νŒλ… κ°€μ΄λ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.</p>' +
'</div>';
}
// μ„€λͺ… λ‘œλ”© λ©”μ‹œμ§€ 숨기기
const explanationLoading = document.getElementById('explanation-loading');
if (explanationLoading) {
explanationLoading.style.display = 'none';
}
// 이미지 헀더 μ—…λ°μ΄νŠΈ
const imageHeader = document.getElementById('image-modality-header');
if (imageHeader) {
imageHeader.textContent = document.getElementById('image-type-select').selectedOptions[0].text;
}
} else {
// μ—λŸ¬ ν‘œμ‹œ
if (errorDiv) {
errorDiv.textContent = result.error || 'κ°€μ΄λ“œ 생성 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.';
errorDiv.style.display = 'block';
}
}
} catch (error) {
console.error('μ—…λ‘œλ“œ 였λ₯˜:', error);
if (errorDiv) {
errorDiv.textContent = 'λ„€νŠΈμ›Œν¬ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.';
errorDiv.style.display = 'block';
}
} finally {
// λ²„νŠΌ μ›λž˜ μƒνƒœλ‘œ 볡원
submitButton.disabled = false;
submitButton.innerHTML = originalButtonText;
}
});
}
});
</script>
</body>
</html>