mirror of
https://github.com/ClovertaTheTrilobita/cellpose-web.git
synced 2026-04-01 23:14:50 +00:00
feature(train): 重构前端
This commit is contained in:
parent
d51439dee0
commit
f8edafb9c0
2 changed files with 383 additions and 0 deletions
188
frontend/run.html
Normal file
188
frontend/run.html
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<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">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="padding: 1rem 1rem;">
|
||||||
|
<div class="mb-3 border" style="padding: 1rem 1rem;">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input id="fileInput" type="file" class="form-control" multiple />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<div style="padding-right: 50rem;">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">flow threshold:</span>
|
||||||
|
<input id="flow" type="text" class="form-control" placeholder="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">cellprob threshold:</span>
|
||||||
|
<input id="cellprob" type="text" class="form-control" placeholder="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">diameter:</span>
|
||||||
|
<input id="diameter" type="text" class="form-control" placeholder="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<select id="model" class="form-select"></select>
|
||||||
|
</label>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
<button id="uploadBtn" class="btn btn-success">Upload</button>
|
||||||
|
<progress id="bar" max="100" value="0" style="width:300px;"></progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"
|
||||||
|
integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-ep+dxp/oz2RKF89ALMPGc7Z89QFa32C8Uv1A3TcEK8sMzXVysblLA3+eJWTzPJzT"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
const API = "http://10.147.18.141:5000/";
|
||||||
|
const APT_UPLOAD = API + "run_upload";
|
||||||
|
const API_MODEL = API + "models";
|
||||||
|
|
||||||
|
async function loadModels() {
|
||||||
|
const select = document.getElementById('model');
|
||||||
|
// 占位 & 禁用,避免用户在加载中点选
|
||||||
|
select.disabled = true;
|
||||||
|
select.innerHTML = '<option value="">加载中…</option>';
|
||||||
|
|
||||||
|
// 可选:超时控制(5 秒)
|
||||||
|
const controller = new AbortController();
|
||||||
|
const t = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(API_MODEL, {
|
||||||
|
headers: { 'Accept': 'application/json' },
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||||
|
|
||||||
|
const data = await resp.json();
|
||||||
|
const list = Array.isArray(data.models) ? data.models : [];
|
||||||
|
|
||||||
|
// 记住之前的选择(如果有)
|
||||||
|
const prev = select.value;
|
||||||
|
|
||||||
|
// 重新渲染选项
|
||||||
|
select.innerHTML = '<option value="" disabled selected>请选择模型</option>';
|
||||||
|
const seen = new Set();
|
||||||
|
for (const name of list) {
|
||||||
|
if (!name || seen.has(name)) continue;
|
||||||
|
seen.add(name);
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = String(name);
|
||||||
|
opt.textContent = String(name);
|
||||||
|
select.appendChild(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果原选择仍然存在,则恢复它
|
||||||
|
if (prev && seen.has(prev)) {
|
||||||
|
select.value = prev;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载模型列表失败:', err);
|
||||||
|
select.innerHTML = '<option value="">加载失败(点击重试)</option>';
|
||||||
|
// 点击下拉框时重试
|
||||||
|
select.addEventListener('click', loadModels, { once: true });
|
||||||
|
} finally {
|
||||||
|
clearTimeout(t);
|
||||||
|
select.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', loadModels);
|
||||||
|
|
||||||
|
function buildUrl() {
|
||||||
|
// 获取参数
|
||||||
|
const model = document.getElementById('model')?.value;
|
||||||
|
const flow = (document.getElementById('flow')?.value || '').trim();
|
||||||
|
const cellp = (document.getElementById('cellprob')?.value || '').trim();
|
||||||
|
const diameter = (document.getElementById('diameter')?.value || '').trim();
|
||||||
|
|
||||||
|
// 用 URLSearchParams 组装查询串
|
||||||
|
const qs = new URLSearchParams({
|
||||||
|
model: model,
|
||||||
|
flow_threshold: flow,
|
||||||
|
cellprob_threshold: cellp,
|
||||||
|
diameter: diameter
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${API_UPLOAD}?${qs.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("uploadBtn").addEventListener("click", async () => {
|
||||||
|
const input = document.getElementById("fileInput");
|
||||||
|
if (!input.files.length) return alert("请选择文件");
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
for (const f of input.files) fd.append("files", f);
|
||||||
|
|
||||||
|
const bar = document.getElementById("bar");
|
||||||
|
try {
|
||||||
|
const URL = buildUrl();
|
||||||
|
const res = await axios.post(URL, fd, {
|
||||||
|
onUploadProgress: (e) => {
|
||||||
|
if (e.total) bar.value = Math.round((e.loaded * 100) / e.total);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// alert("上传成功:" + JSON.stringify(res.data));
|
||||||
|
|
||||||
|
// 创建一个提示元素
|
||||||
|
const notice = document.createElement("div");
|
||||||
|
notice.style.position = "fixed";
|
||||||
|
notice.style.top = "20px";
|
||||||
|
notice.style.left = "50%";
|
||||||
|
notice.style.transform = "translateX(-50%)";
|
||||||
|
notice.style.padding = "10px 20px";
|
||||||
|
notice.style.background = "#4caf50";
|
||||||
|
notice.style.color = "white";
|
||||||
|
notice.style.borderRadius = "8px";
|
||||||
|
notice.style.fontSize = "16px";
|
||||||
|
notice.style.zIndex = "9999";
|
||||||
|
|
||||||
|
let seconds = 3;
|
||||||
|
notice.textContent = `上传成功!${seconds} 秒后跳转预览页面…`;
|
||||||
|
document.body.appendChild(notice);
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
seconds--;
|
||||||
|
if (seconds > 0) {
|
||||||
|
notice.textContent = `上传成功!${seconds} 秒后跳转预览页面…`;
|
||||||
|
} else {
|
||||||
|
clearInterval(timer);
|
||||||
|
document.body.removeChild(notice);
|
||||||
|
window.location.href = `preview.html?id=${encodeURIComponent(res.data['id'])}`;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} catch (e) {
|
||||||
|
alert("上传失败:" + (e.response?.data?.message || e.message));
|
||||||
|
} finally {
|
||||||
|
bar.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
195
frontend/train.html
Normal file
195
frontend/train.html
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<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">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div style="padding: 1rem 1rem;">
|
||||||
|
<div class="mb-3 border" style="padding: 1rem 1rem;">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<h5>选择训练文件</h5>
|
||||||
|
<input id="trainFileInput" type="file" class="form-control" multiple />
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<h5>选择测试文件</h5>
|
||||||
|
<input id="testFileInput" type="file" class="form-control" multiple />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<div style="padding-right: 50rem;">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">model name:</span>
|
||||||
|
<input id="name" type="text" class="form-control" placeholder="为你的模型命名" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">image filter:</span>
|
||||||
|
<input id="image_filter" type="text" class="form-control" placeholder="_img" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text">masks filter</span>
|
||||||
|
<input id="masks_filter" type="text" class="form-control" placeholder="_masks" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<select id="model" class="form-select"></select>
|
||||||
|
</label>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
<button id="uploadBtn" class="btn btn-success">Upload</button>
|
||||||
|
<progress id="bar" max="100" value="0" style="width:300px;"></progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br><br>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"
|
||||||
|
integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-ep+dxp/oz2RKF89ALMPGc7Z89QFa32C8Uv1A3TcEK8sMzXVysblLA3+eJWTzPJzT"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script>
|
||||||
|
const API = "http://10.147.18.141:5000/";
|
||||||
|
const APT_UPLOAD = API + "train_upload";
|
||||||
|
const API_MODEL = API + "models";
|
||||||
|
|
||||||
|
async function loadModels() {
|
||||||
|
const select = document.getElementById('model');
|
||||||
|
// 占位 & 禁用,避免用户在加载中点选
|
||||||
|
select.disabled = true;
|
||||||
|
select.innerHTML = '<option value="">加载中…</option>';
|
||||||
|
|
||||||
|
// 可选:超时控制(5 秒)
|
||||||
|
const controller = new AbortController();
|
||||||
|
const t = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(API_MODEL, {
|
||||||
|
headers: { 'Accept': 'application/json' },
|
||||||
|
signal: controller.signal
|
||||||
|
});
|
||||||
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||||
|
|
||||||
|
const data = await resp.json();
|
||||||
|
const list = Array.isArray(data.models) ? data.models : [];
|
||||||
|
|
||||||
|
// 记住之前的选择(如果有)
|
||||||
|
const prev = select.value;
|
||||||
|
|
||||||
|
// 重新渲染选项
|
||||||
|
select.innerHTML = '<option value="" disabled selected>请选择基础模型</option>';
|
||||||
|
const seen = new Set();
|
||||||
|
for (const name of list) {
|
||||||
|
if (!name || seen.has(name)) continue;
|
||||||
|
seen.add(name);
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = String(name);
|
||||||
|
opt.textContent = String(name);
|
||||||
|
select.appendChild(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果原选择仍然存在,则恢复它
|
||||||
|
if (prev && seen.has(prev)) {
|
||||||
|
select.value = prev;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('加载模型列表失败:', err);
|
||||||
|
select.innerHTML = '<option value="">加载失败(点击重试)</option>';
|
||||||
|
// 点击下拉框时重试
|
||||||
|
select.addEventListener('click', loadModels, { once: true });
|
||||||
|
} finally {
|
||||||
|
clearTimeout(t);
|
||||||
|
select.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', loadModels);
|
||||||
|
|
||||||
|
function buildUrl() {
|
||||||
|
// 获取参数
|
||||||
|
const model = document.getElementById('model')?.value;
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 用 URLSearchParams 组装查询串
|
||||||
|
const qs = new URLSearchParams({
|
||||||
|
model: model,
|
||||||
|
model_name: model_name,
|
||||||
|
image_filter: image_filter,
|
||||||
|
masks_filter: masks_filter
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${API_UPLOAD}?${qs.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("uploadBtn").addEventListener("click", async () => {
|
||||||
|
const input_train = document.getElementById("trainFileInput");
|
||||||
|
const input_test = document.getElementById("testFileInput");
|
||||||
|
if (!input.files.length) return alert("请选择文件");
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
for (const f of input_train.files) fd.append("train_files", f);
|
||||||
|
for (const f of input_test.files) fd.append("test_files", f);
|
||||||
|
|
||||||
|
const bar = document.getElementById("bar");
|
||||||
|
try {
|
||||||
|
const URL = buildUrl();
|
||||||
|
const res = await axios.post(URL, fd, {
|
||||||
|
onUploadProgress: (e) => {
|
||||||
|
if (e.total) bar.value = Math.round((e.loaded * 100) / e.total);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// alert("上传成功:" + JSON.stringify(res.data));
|
||||||
|
|
||||||
|
// 创建一个提示元素
|
||||||
|
const notice = document.createElement("div");
|
||||||
|
notice.style.position = "fixed";
|
||||||
|
notice.style.top = "20px";
|
||||||
|
notice.style.left = "50%";
|
||||||
|
notice.style.transform = "translateX(-50%)";
|
||||||
|
notice.style.padding = "10px 20px";
|
||||||
|
notice.style.background = "#4caf50";
|
||||||
|
notice.style.color = "white";
|
||||||
|
notice.style.borderRadius = "8px";
|
||||||
|
notice.style.fontSize = "16px";
|
||||||
|
notice.style.zIndex = "9999";
|
||||||
|
|
||||||
|
let seconds = 3;
|
||||||
|
notice.textContent = `上传成功!${seconds} 秒后跳转预览页面…`;
|
||||||
|
document.body.appendChild(notice);
|
||||||
|
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
seconds--;
|
||||||
|
if (seconds > 0) {
|
||||||
|
notice.textContent = `上传成功!${seconds} 秒后跳转预览页面…`;
|
||||||
|
} else {
|
||||||
|
clearInterval(timer);
|
||||||
|
document.body.removeChild(notice);
|
||||||
|
window.location.href = `preview.html?id=${encodeURIComponent(res.data['id'])}`;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} catch (e) {
|
||||||
|
alert("上传失败:" + (e.response?.data?.message || e.message));
|
||||||
|
} finally {
|
||||||
|
bar.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
Reference in a new issue