console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能医学影像分析系统</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.18.0/dist/tf.min.js"></script>
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2980b9;
--accent-color: #e74c3c;
--light-color: #ecf0f1;
--dark-color: #2c3e50;
--success-color: #2ecc71;
--warning-color: #f39c12;
--danger-color: #e74c3c;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: var(--dark-color);
line-height: 1.6;
}
header {
background-color: var(--primary-color);
color: white;
padding: 1.5rem 0;
text-align: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.app-title {
font-size: 2.2rem;
margin-bottom: 0.5rem;
}
.app-subtitle {
font-size: 1.1rem;
opacity: 0.9;
font-weight: 300;
}
.main-content {
display: flex;
flex-wrap: wrap;
gap: 30px;
margin: 30px 0;
}
.upload-section, .processing-section, .results-section {
background-color: white;
border-radius: 8px;
padding: 25px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
flex: 1;
min-width: 300px;
}
.section-title {
font-size: 1.4rem;
color: var(--primary-color);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.upload-area {
border: 2px dashed var(--primary-color);
border-radius: 6px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
margin-bottom: 20px;
}
.upload-area:hover {
background-color: rgba(52, 152, 219, 0.05);
}
.upload-icon {
font-size: 3rem;
color: var(--primary-color);
margin-bottom: 15px;
}
.btn {
display: inline-block;
background-color: var(--primary-color);
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s;
margin: 5px;
}
.btn:hover {
background-color: var(--secondary-color);
}
.btn-secondary {
background-color: var(--light-color);
color: var(--dark-color);
}
.btn-secondary:hover {
background-color: #d5dbdb;
}
.btn-danger {
background-color: var(--danger-color);
}
.btn-danger:hover {
background-color: #c0392b;
}
.image-preview {
width: 100%;
max-height: 300px;
object-fit: contain;
margin: 15px 0;
border: 1px solid #ddd;
border-radius: 4px;
display: none;
}
.controls {
margin: 20px 0;
}
.control-group {
margin-bottom: 15px;
}
.control-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.slider {
width: 100%;
height: 8px;
border-radius: 4px;
background: #ddd;
outline: none;
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
}
.result-card {
background-color: var(--light-color);
padding: 20px;
border-radius: 6px;
margin-bottom: 20px;
}
.result-title {
font-weight: 600;
margin-bottom: 10px;
color: var(--dark-color);
}
.result-value {
font-size: 1.2rem;
font-weight: 700;
}
.normal {
color: var(--success-color);
}
.abnormal {
color: var(--danger-color);
}
.confidence-meter {
height: 20px;
background-color: #eee;
border-radius: 10px;
margin: 10px 0;
overflow: hidden;
}
.confidence-level {
height: 100%;
background: linear-gradient(to right, var(--success-color), var(--warning-color), var(--danger-color));
width: 0%;
transition: width 0.5s ease;
}
.features {
margin-top: 20px;
}
.feature-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.feature-name {
font-weight: 500;
}
.loading {
display: none;
text-align: center;
padding: 20px;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top: 4px solid var(--primary-color);
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
footer {
text-align: center;
padding: 20px;
background-color: var(--dark-color);
color: white;
margin-top: 40px;
}
@media (max-width: 768px) {
.main-content {
flex-direction: column;
}
.app-title {
font-size: 1.8rem;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<h1 class="app-title">智能医学影像分析系统</h1>
<p class="app-subtitle">基于深度学习的医学影像分析与辅助诊断</p>
</div>
</header>
<div class="container">
<div class="main-content">
<section class="upload-section">
<h2 class="section-title">影像上传</h2>
<div id="uploadArea" class="upload-area">
<div class="upload-icon">��</div>
<p>点击或拖拽医学影像文件到此处</p>
<p><small>支持格式: JPG, PNG (最大5MB)</small></p>
<input type="file" id="fileInput" accept="image/jpeg,image/png" style="display: none;">
</div>
<button id="analyzeBtn" class="btn" disabled>分析影像</button>
<button id="resetBtn" class="btn btn-secondary" disabled>重置</button>
<div class="loading" id="loadingIndicator">
<div class="spinner"></div>
<p>正在分析影像,请稍候...</p>
</div>
</section>
<section class="processing-section">
<h2 class="section-title">影像处理</h2>
<img id="imagePreview" class="image-preview" alt="影像预览">
<div class="controls">
<div class="control-group">
<label class="control-label">亮度</label>
<input type="range" id="brightnessSlider" class="slider" min="-100" max="100" value="0">
</div>
<div class="control-group">
<label class="control-label">对比度</label>
<input type="range" id="contrastSlider" class="slider" min="-100" max="100" value="0">
</div>
<button id="applyBtn" class="btn" disabled>应用调整</button>
<button id="resetAdjustmentsBtn" class="btn btn-secondary" disabled>重置调整</button>
</div>
</section>
<section class="results-section">
<h2 class="section-title">分析结果</h2>
<div id="resultsContainer" style="display: none;">
<div class="result-card">
<h3 class="result-title">诊断结论</h3>
<p class="result-value" id="diagnosisResult">-</p>
<div class="confidence-meter">
<div class="confidence-level" id="confidenceLevel"></div>
</div>
<p>置信度: <span id="confidenceValue">0</span>%</p>
</div>
<div class="result-card">
<h3 class="result-title">影像特征</h3>
<div class="features">
<div class="feature-item">
<span class="feature-name">肺部清晰度</span>
<span id="lungClarity">-</span>
</div>
<div class="feature-item">
<span class="feature-name">异常阴影</span>
<span id="abnormalShadows">-</span>
</div>
<div class="feature-item">
<span class="feature-name">纹理特征</span>
<span id="textureFeatures">-</span>
</div>
</div>
</div>
<div class="result-card">
<h3 class="result-title">建议</h3>
<p id="recommendations">请上传医学影像进行分析后获取建议。</p>
</div>
</div>
<div id="noResultsMessage">
<p>上传医学影像后,点击"分析影像"按钮获取诊断结果。</p>
</div>
</section>
</div>
</div>
<footer>
<div class="container">
<p>智能医学影像分析系统 © 2023 | 仅供演示用途</p>
</div>
</footer>
<script>
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
const imagePreview = document.getElementById('imagePreview');
const analyzeBtn = document.getElementById('analyzeBtn');
const resetBtn = document.getElementById('resetBtn');
const applyBtn = document.getElementById('applyBtn');
const resetAdjustmentsBtn = document.getElementById('resetAdjustmentsBtn');
const brightnessSlider = document.getElementById('brightnessSlider');
const contrastSlider = document.getElementById('contrastSlider');
const resultsContainer = document.getElementById('resultsContainer');
const noResultsMessage = document.getElementById('noResultsMessage');
const loadingIndicator = document.getElementById('loadingIndicator');
const diagnosisResult = document.getElementById('diagnosisResult');
const confidenceLevel = document.getElementById('confidenceLevel');
const confidenceValue = document.getElementById('confidenceValue');
const lungClarity = document.getElementById('lungClarity');
const abnormalShadows = document.getElementById('abnormalShadows');
const textureFeatures = document.getElementById('textureFeatures');
const recommendations = document.getElementById('recommendations');
let originalImage = null;
let currentImage = null;
let model = null;
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.style.backgroundColor = 'rgba(52, 152, 219, 0.1)';
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.style.backgroundColor = '';
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.backgroundColor = '';
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
handleFileUpload(e.dataTransfer.files[0]);
}
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length) {
handleFileUpload(fileInput.files[0]);
}
});
analyzeBtn.addEventListener('click', analyzeImage);
resetBtn.addEventListener('click', resetAll);
applyBtn.addEventListener('click', applyAdjustments);
resetAdjustmentsBtn.addEventListener('click', resetAdjustments);
async function loadModel() {
loadingIndicator.style.display = 'block';
try {
model = {
predict: function(imgData) {
return new Promise((resolve) => {
setTimeout(() => {
const isPneumonia = Math.random() > 0.7;
const confidence = isPneumonia ?
(0.7 + Math.random() * 0.3) :
(Math.random() * 0.3);
resolve({
isPneumonia: isPneumonia,
confidence: confidence
});
}, 2000);
});
}
};
console.log('模型加载完成');
} catch (error) {
console.error('模型加载失败:', error);
alert('模型加载失败,请刷新页面重试');
} finally {
loadingIndicator.style.display = 'none';
}
}
function handleFileUpload(file) {
if (!file.type.match('image.*')) {
alert('请上传图片文件');
return;
}
if (file.size > 5 * 1024 * 1024) {
alert('文件大小不能超过5MB');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
originalImage = new Image();
originalImage.onload = function() {
imagePreview.src = e.target.result;
imagePreview.style.display = 'block';
currentImage = originalImage;
analyzeBtn.disabled = false;
resetBtn.disabled = false;
applyBtn.disabled = false;
resetAdjustmentsBtn.disabled = false;
brightnessSlider.value = 0;
contrastSlider.value = 0;
resultsContainer.style.display = 'none';
noResultsMessage.style.display = 'block';
if (!model) {
loadModel();
}
};
originalImage.src = e.target.result;
};
reader.readAsDataURL(file);
}
async function analyzeImage() {
if (!currentImage || !model) {
alert('请先上传图像并等待模型加载完成');
return;
}
loadingIndicator.style.display = 'block';
analyzeBtn.disabled = true;
try {
const result = await model.predict(currentImage);
displayResults(result);
} catch (error) {
console.error('分析失败:', error);
alert('分析过程中发生错误');
} finally {
loadingIndicator.style.display = 'none';
analyzeBtn.disabled = false;
}
}
function displayResults(result) {
resultsContainer.style.display = 'block';
noResultsMessage.style.display = 'none';
const isPneumonia = result.isPneumonia;
const confidence = Math.round(result.confidence * 100);
diagnosisResult.textContent = isPneumonia ? '疑似肺炎' : '正常';
diagnosisResult.className = isPneumonia ? 'result-value abnormal' : 'result-value normal';
confidenceLevel.style.width = `${confidence}%`;
confidenceValue.textContent = confidence;
lungClarity.textContent = isPneumonia ?
(confidence > 80 ? '模糊' : '轻微模糊') :
'清晰';
abnormalShadows.textContent = isPneumonia ?
(confidence > 80 ? '存在' : '可能存在') :
'无';
textureFeatures.textContent = isPneumonia ?
(confidence > 80 ? '异常' : '轻微异常') :
'正常';
if (isPneumonia) {
if (confidence > 80) {
recommendations.textContent = '强烈建议立即就医,进行进一步检查和治疗。可能需要抗生素治疗。';
} else {
recommendations.textContent = '建议尽快就医进行进一步检查,可能需要胸部CT确认诊断。';
}
} else {
if (confidence > 90) {
recommendations.textContent = '影像显示正常,如无症状无需进一步检查。';
} else {
recommendations.textContent = '影像显示基本正常,如有持续症状建议咨询医生。';
}
}
}
function applyAdjustments() {
if (!originalImage) return;
const brightnessValue = parseInt(brightnessSlider.value);
const contrastValue = parseInt(contrastSlider.value);
const canvas = document.createElement('canvas');
canvas.width = originalImage.width;
canvas.height = originalImage.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImage, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const brightnessFactor = brightnessValue / 100;
const contrastFactor = (contrastValue / 100) * 2;
for (let i = 0; i < data.length; i += 4) {
data[i] = data[i] + (255 * brightnessFactor);
data[i+1] = data[i+1] + (255 * brightnessFactor);
data[i+2] = data[i+2] + (255 * brightnessFactor);
const factor = (259 * (contrastFactor + 255)) / (255 * (259 - contrastFactor));
data[i] = factor * (data[i] - 128) + 128;
data[i+1] = factor * (data[i+1] - 128) + 128;
data[i+2] = factor * (data[i+2] - 128) + 128;
data[i] = Math.max(0, Math.min(255, data[i]));
data[i+1] = Math.max(0, Math.min(255, data[i+1]));
data[i+2] = Math.max(0, Math.min(255, data[i+2]));
}
ctx.putImageData(imageData, 0, 0);
currentImage = new Image();
currentImage.src = canvas.toDataURL('image/png');
imagePreview.src = currentImage.src;
}
function resetAdjustments() {
if (!originalImage) return;
brightnessSlider.value = 0;
contrastSlider.value = 0;
currentImage = originalImage;
imagePreview.src = originalImage.src;
}
function resetAll() {
fileInput.value = '';
imagePreview.src = '';
imagePreview.style.display = 'none';
brightnessSlider.value = 0;
contrastSlider.value = 0;
resultsContainer.style.display = 'none';
noResultsMessage.style.display = 'block';
analyzeBtn.disabled = true;
resetBtn.disabled = true;
applyBtn.disabled = true;
resetAdjustmentsBtn.disabled = true;
originalImage = null;
currentImage = null;
}
document.addEventListener('DOMContentLoaded', () => {
loadModel();
});
</script>
</body>
</html>