mirror of
https://github.com/ClovertaTheTrilobita/cellpose-web.git
synced 2026-04-01 23:14:50 +00:00
feat(frontend): 前端样式优化
This commit is contained in:
parent
723bfdf2d9
commit
2089efb1cb
4 changed files with 437 additions and 163 deletions
|
|
@ -140,22 +140,47 @@ def run_upload():
|
|||
|
||||
@app.post("/train_upload")
|
||||
def train_upload():
|
||||
|
||||
def _to_float(x, default):
|
||||
try:
|
||||
return float(x)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
def _to_int(x, default):
|
||||
try:
|
||||
return int(x)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + f"-{int(time.time()*1000)%1000:03d}"
|
||||
model_name = request.args.get("model_name") or f"custom_model-{ts}"
|
||||
image_filter = request.args.get("image_filter") or "_img"
|
||||
mask_filter = request.args.get("mask_filter") or "_masks"
|
||||
base_model = request.args.get("base_model") or "cpsam"
|
||||
batch_size = request.args.get("batch_size") or 8
|
||||
learning_rate = request.args.get("learning_rate") or 5e-5
|
||||
n_epochs = request.args.get("n_epochs") or 100
|
||||
weight_decay = request.args.get("weight_decay") or 0.1
|
||||
normalize = request.args.get("normalize") or True
|
||||
compute_flows = request.args.get("compute_flows") or False
|
||||
min_train_masks = request.args.get(" min_train_masks") or 5
|
||||
nimg_per_epoch = request.args.get("nimg_per_epoch") or None
|
||||
rescale = request.args.get("rescale") or False
|
||||
scale_range = request.args.get("scale_range") or None
|
||||
channel_axis = request.args.get("channel_axis") or None
|
||||
batch_size = _to_int(request.args.get("batch_size"), 8)
|
||||
learning_rate = _to_float(request.args.get("learning_rate"), 5e-5)
|
||||
n_epochs = _to_int(request.args.get("n_epochs"), 100)
|
||||
weight_decay = _to_float(request.args.get("weight_decay"), 0.1)
|
||||
normalize = request.args.get(
|
||||
"normalize",
|
||||
default=True,
|
||||
type=lambda v: str(v).strip().lower() in ("1","true","t","yes","y","on")
|
||||
)
|
||||
compute_flows = request.args.get(
|
||||
"compute_flows",
|
||||
default=True,
|
||||
type=lambda v: str(v).strip().lower() in ("1","true","t","yes","y","on")
|
||||
)
|
||||
min_train_masks = _to_int(request.args.get(" min_train_masks"), 5)
|
||||
nimg_per_epoch = _to_int(request.args.get("nimg_per_epoch"), None)
|
||||
rescale = request.args.get(
|
||||
"rescale",
|
||||
default=False,
|
||||
type=lambda v: str(v).strip().lower() in ("1","true","t","yes","y","on")
|
||||
)
|
||||
scale_range = _to_float(request.args.get("scale_range"), None)
|
||||
channel_axis = _to_int(request.args.get("channel_axis"), None)
|
||||
|
||||
train_files = request.files.getlist("train_files")
|
||||
test_files = request.files.getlist("test_files")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
<title>Bootstrap demo</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -35,31 +35,59 @@
|
|||
background: rgba(17,20,34,.6);
|
||||
border: 1px solid rgba(255,255,255,.08);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display:flex; align-items:center; gap:.6rem;
|
||||
margin-bottom:0;
|
||||
display: flex; align-items: center; gap: .6rem; margin-bottom: 0;
|
||||
}
|
||||
.hint { font-size: .9rem; opacity: .75 }
|
||||
|
||||
/* 原生 progress 美化(保持 id 不变) */
|
||||
#bar{
|
||||
width:100%; height:1rem; border:none; border-radius:.75rem;
|
||||
background-color:rgba(13,110,253,.15); overflow:hidden; vertical-align:middle;
|
||||
/* 小卡片(参数分组) */
|
||||
.subcard {
|
||||
background: rgba(255,255,255,.6);
|
||||
border: 1px solid rgba(0,0,0,.05);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
[data-bs-theme="dark"] .subcard {
|
||||
background: rgba(255,255,255,.04);
|
||||
border-color: rgba(255,255,255,.08);
|
||||
}
|
||||
#bar::-webkit-progress-bar{background:transparent}
|
||||
#bar::-webkit-progress-value{background:linear-gradient(90deg,#0d6efd,#6ea8fe); border-radius:.75rem}
|
||||
#bar::-moz-progress-bar{background:linear-gradient(90deg,#0d6efd,#6ea8fe); border-radius:.75rem}
|
||||
|
||||
/* 上传按钮动效(保持 id 不变) */
|
||||
/* input-group 统一图标透明度 */
|
||||
.input-group-text i { opacity: .8; }
|
||||
|
||||
/* progress 美化(保持 id 不变) */
|
||||
#bar {
|
||||
width: 100%;
|
||||
height: 1rem;
|
||||
border: none;
|
||||
border-radius: .75rem;
|
||||
background-color: rgba(13,110,253,.15);
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#bar::-webkit-progress-bar { background: transparent; }
|
||||
#bar::-webkit-progress-value {
|
||||
background: linear-gradient(90deg, #0d6efd, #6ea8fe);
|
||||
border-radius: .75rem;
|
||||
}
|
||||
#bar::-moz-progress-bar {
|
||||
background: linear-gradient(90deg, #0d6efd, #6ea8fe);
|
||||
border-radius: .75rem;
|
||||
}
|
||||
|
||||
/* 上传/启动按钮动效(保持 id 不变) */
|
||||
#uploadBtn {
|
||||
padding:.65rem 1.1rem; border-radius:.8rem;
|
||||
padding: .65rem 1.1rem;
|
||||
border-radius: .8rem;
|
||||
transition: transform .12s ease, box-shadow .12s ease;
|
||||
}
|
||||
#uploadBtn:hover, #uploadBtn:focus-visible {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 .75rem 1.25rem rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
/* 更紧凑的行距 */
|
||||
.g-tight { row-gap: .75rem; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
@ -68,7 +96,9 @@
|
|||
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5">
|
||||
<!-- 标题 -->
|
||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
|
||||
<h1 class="section-title h4"><i class="bi bi-cpu"></i> 训练配置</h1>
|
||||
<h1 class="section-title h4">
|
||||
<i class="bi bi-cpu"></i> 训练配置
|
||||
</h1>
|
||||
<div class="hint">选择训练/测试数据并设置模型参数</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -76,28 +106,41 @@
|
|||
<div class="mb-4">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-lg-6">
|
||||
<label class="form-label fw-semibold"><i class="bi bi-folder2-open me-1"></i>选择训练文件</label>
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="bi bi-folder2-open me-1"></i>选择训练文件
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-cloud-arrow-up"></i></span>
|
||||
<input id="trainFileInput" type="file" class="form-control" multiple />
|
||||
</div>
|
||||
<div class="form-text">可多选;支持拖拽到输入框。</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<label class="form-label fw-semibold"><i class="bi bi-folder2 me-1"></i>选择测试文件</label>
|
||||
<label class="form-label fw-semibold">
|
||||
<i class="bi bi-folder2 me-1"></i>选择测试文件
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-cloud-arrow-up"></i></span>
|
||||
<input id="testFileInput" type="file" class="form-control" multiple />
|
||||
</div>
|
||||
<div class="form-text">可选;用于评估训练效果。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- 参数区(仅美化,不改 id) -->
|
||||
<!-- 参数区 -->
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="row g-3">
|
||||
<!-- 基础设置 -->
|
||||
<div class="subcard mb-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<i class="bi bi-sliders2-vertical"></i><strong>基础设置</strong>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 g-tight">
|
||||
<div class="col-12">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-type me-1"></i>model name</span>
|
||||
|
|
@ -110,6 +153,7 @@
|
|||
<span class="input-group-text"><i class="bi bi-image me-1"></i>image filter</span>
|
||||
<input id="image_filter" type="text" class="form-control" placeholder="_img" />
|
||||
</div>
|
||||
<div class="form-text">如 <code>*_img.png</code></div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
|
|
@ -117,6 +161,7 @@
|
|||
<span class="input-group-text"><i class="bi bi-brush me-1"></i>masks filter</span>
|
||||
<input id="masks_filter" type="text" class="form-control" placeholder="_masks" />
|
||||
</div>
|
||||
<div class="form-text">如 <code>*_masks.png</code></div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
|
|
@ -130,7 +175,81 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧提示(可删,不影响逻辑) -->
|
||||
<!-- 训练超参 -->
|
||||
<div class="subcard">
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<i class="bi bi-rocket-takeoff"></i><strong>训练超参数</strong>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 g-tight">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-speedometer2 me-1"></i>learning rate</span>
|
||||
<input id="learning_rate" type="text" class="form-control" placeholder="0.005" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-hash me-1"></i>n epochs</span>
|
||||
<input id="n_epochs" type="text" class="form-control" placeholder="100" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-diagram-2 me-1"></i>weight decay</span>
|
||||
<input id="weight_decay" type="text" class="form-control" placeholder="0.1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-arrows-move me-1"></i>normalize</span>
|
||||
<select id="normalize" class="form-select">
|
||||
<option selected value="1">True</option>
|
||||
<option value="0">False</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-gear me-1"></i>compute flows</span>
|
||||
<!-- 注意:此处 id 仍为 normalize(你原文如此)。建议后续改名避免重复 -->
|
||||
<select id="compute_flows" class="form-select">
|
||||
<option selected value="0">False</option>
|
||||
<option value="1">True</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-text">与光流/流场相关,通常仅在特定任务启用。</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-123 me-1"></i>min train masks</span>
|
||||
<input id="min_train_masks" type="text" class="form-control" placeholder="5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-collection me-1"></i>nimg_per_epoch</span>
|
||||
<input id="nimg_per_epoch" type="text" class="form-control" placeholder="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="bi bi-layers me-1"></i>channel axis</span>
|
||||
<input id="channel_axis" type="text" class="form-control" placeholder="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧提示 -->
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="p-3 rounded-3 border h-100">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
|
|
@ -139,7 +258,8 @@
|
|||
<div class="hint">
|
||||
• 名称用于区分训练产物(日志/权重)。<br>
|
||||
• filter 用于匹配文件名,如 <code>*_img.png</code>、<code>*_masks.png</code>。<br>
|
||||
• 选择合适的预训练模型可加速收敛。
|
||||
• 选择合适的预训练模型可加速收敛。<br>
|
||||
• 建议仅微调必要超参,保持可复现性。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -153,12 +273,11 @@
|
|||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center justify-content-between mb-1">
|
||||
<span class="hint"><i class="bi bi-activity me-1"></i>进度</span>
|
||||
<span class="hint" id="barText"></span><!-- 如需 JS 写入文字可用;不用也不影响 -->
|
||||
<span class="hint" id="barText"></span>
|
||||
</div>
|
||||
<progress id="bar" max="100" value="0"></progress>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -239,13 +358,29 @@
|
|||
const model_name = (document.getElementById('model_name')?.value || '').trim();
|
||||
const image_filter = (document.getElementById('image_filter')?.value || '').trim();
|
||||
const masks_filter = (document.getElementById('masks_filter')?.value || '').trim();
|
||||
const learning_rate = (document.getElementById('learning_rate')?.value || '').trim();
|
||||
const n_epochs = (document.getElementById('n_epochs')?.value || '').trim();
|
||||
const weight_decay = (document.getElementById('weight_decay')?.value || '').trim();
|
||||
const normalize = document.getElementById('normalize')?.value;
|
||||
const compute_flows = document.getElementById('compute_flows')?.value;
|
||||
const min_train_masks = (document.getElementById('min_train_masks')?.value || '').trim();
|
||||
const nimg_per_epoch = (document.getElementById('nimg_per_epoch')?.value || '').trim();
|
||||
const channel_axis = (document.getElementById('weight_decay')?.value || '').trim();
|
||||
|
||||
// 用 URLSearchParams 组装查询串
|
||||
const qs = new URLSearchParams({
|
||||
model: model,
|
||||
model_name: model_name,
|
||||
image_filter: image_filter,
|
||||
masks_filter: masks_filter
|
||||
masks_filter: masks_filter,
|
||||
learning_rate: learning_rate,
|
||||
n_epochs: n_epochs,
|
||||
weight_decay: weight_decay,
|
||||
normalize: normalize,
|
||||
compute_flows: compute_flows,
|
||||
min_train_masks: min_train_masks,
|
||||
nimg_per_epoch: nimg_per_epoch,
|
||||
channel_axis: channel_axis
|
||||
});
|
||||
|
||||
return `${API_UPLOAD}?${qs.toString()}`;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,118 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<h1>训练结果预览</h1>
|
||||
<p id="none-exist" hidden></p>
|
||||
<title>训练结果预览</title>
|
||||
|
||||
<!-- Bootstrap & Icons(与前页一致) -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
/* 背景与玻璃卡片 —— 与你其它页保持一致 */
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background:
|
||||
radial-gradient(1200px 600px at 20% 10%, rgba(13, 110, 253, .15), transparent 60%),
|
||||
radial-gradient(800px 400px at 80% 90%, rgba(32, 201, 151, .18), transparent 60%),
|
||||
linear-gradient(180deg, #f8f9fa, #eef2f7);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background:
|
||||
radial-gradient(1200px 600px at 20% 10%, rgba(13, 110, 253, .25), transparent 60%),
|
||||
radial-gradient(800px 400px at 80% 90%, rgba(32, 201, 151, .25), transparent 60%),
|
||||
linear-gradient(180deg, #0b1020, #0e1326);
|
||||
}
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
background: rgba(255, 255, 255, .65);
|
||||
border: 1px solid rgba(255, 255, 255, .5);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .glass-card {
|
||||
background: rgba(17, 20, 34, .6);
|
||||
border: 1px solid rgba(255, 255, 255, .08);
|
||||
}
|
||||
|
||||
/* 标题行 */
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .6rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: .9rem;
|
||||
opacity: .75
|
||||
}
|
||||
|
||||
/* 画布容器:响应式 16:9,可根据需要改比例 */
|
||||
.chart-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
/* 最小高度,防止数据少时过扁 */
|
||||
min-height: 280px;
|
||||
}
|
||||
|
||||
/* 让 canvas 完整填充容器(不改 id,仅加样式) */
|
||||
#lossChart {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* “无结果”提示:沿用 id,不改逻辑,仅美化 */
|
||||
#none-exist {
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container py-4">
|
||||
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5">
|
||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
|
||||
<h1 class="section-title h4">
|
||||
<i class="bi bi-graph-up-arrow"></i> 训练结果预览
|
||||
</h1>
|
||||
<div class="hint">Loss / Metric 曲线将根据你的训练日志自动绘制</div>
|
||||
</div>
|
||||
|
||||
<!-- “无结果”占位:保持 id,不改 hidden 的使用方式 -->
|
||||
<p id="none-exist" class="alert alert-warning d-flex align-items-center" role="status" hidden>
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
暂无可预览的数据,请先开始训练或检查日志输出。
|
||||
</p>
|
||||
|
||||
<!-- 图表区域 -->
|
||||
<div class="chart-wrap rounded-3 border">
|
||||
<canvas id="lossChart"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- 可选:底部小提示(不影响现有 JS) -->
|
||||
<div class="mt-3 text-secondary small">
|
||||
提示:如果你使用 Chart.js,建议设置 <code>responsive: true</code> 与 <code>maintainAspectRatio: false</code>,
|
||||
本页样式已自动保证画布自适应容器尺寸。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Bundle(含 Popper) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
|
@ -94,3 +204,6 @@
|
|||
document.body.removeChild(a);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
Reference in a new issue