mirror of
https://github.com/ClovertaTheTrilobita/cellpose-web.git
synced 2026-04-01 23:14:50 +00:00
Compare commits
15 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
334a3ee47f | ||
| 09d0756184 | |||
|
|
7ba82c4369 | ||
| a4ca19b61a | |||
| 185b01c467 | |||
| 8c24892ac4 | |||
| 20473453c8 | |||
| 98c2114f12 | |||
| c5728df2ce | |||
| 2089efb1cb | |||
| 723bfdf2d9 | |||
|
|
4887441d3c | ||
|
|
35f07b6dbc | ||
|
|
0c269e7597 | ||
|
|
38ca1532b7 |
13 changed files with 1138 additions and 185 deletions
11
README.md
11
README.md
|
|
@ -6,8 +6,13 @@
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/Redis-6.4.0-red">
|
<img alt="Static Badge" src="https://img.shields.io/badge/Redis-6.4.0-red">
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/JSDelivr-in_use-brown">
|
<img alt="Static Badge" src="https://img.shields.io/badge/JSDelivr-in_use-brown">
|
||||||
<img alt="Static Badge" src="https://img.shields.io/badge/Flask-3.1.2-8ecae6">
|
<img alt="Static Badge" src="https://img.shields.io/badge/Flask-3.1.2-8ecae6">
|
||||||
|
<br><br>
|
||||||
|
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/da5b891e-b4ac-484a-885c-0856f18e04fc" style="height: 70%; width: 70%"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
🌈 实现功能:
|
🌈 实现功能:
|
||||||
|
|
@ -16,16 +21,18 @@
|
||||||
- ⛵️ 训练、分割结果随时下载
|
- ⛵️ 训练、分割结果随时下载
|
||||||
- 📚 权重可直接作为后续分割模型
|
- 📚 权重可直接作为后续分割模型
|
||||||
- 🛠️ 一键安装部署脚本
|
- 🛠️ 一键安装部署脚本
|
||||||
- 🚧 [TODO] 前端样式美化
|
- 🎨 前端样式美化
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
## 🚀一键安装
|
## 🚀一键安装
|
||||||
|
|
||||||
在<b>[Release页面](https://github.com/ClovertaTheTrilobita/cellpose-web/releases)</b>中下载最新的 <b>install.sh</b> 到你的Linux/macOS机器上。
|
在<b>[最新Release页面](https://github.com/ClovertaTheTrilobita/cellpose-web/releases/latest)</b>中下载最新的 <b>install.sh</b> 到你的Linux/macOS机器上。
|
||||||
|
|
||||||
将它放到你希望项目存在的位置,并执行它,安装脚本会将项目自动拉取到同一目录下。
|
将它放到你希望项目存在的位置,并执行它,安装脚本会将项目自动拉取到同一目录下。
|
||||||
|
|
||||||
|
**NOTE:** 安装脚本设计上支持Debian系、Arch系、RHEL系Linux,但目前仅测试过Arch Linux。其他发行版暂未经过测试,若出现意外错误,推荐手动安装。
|
||||||
|
|
||||||
Windows暂时不支持通过脚本一键安装。
|
Windows暂时不支持通过脚本一键安装。
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
backend:
|
backend:
|
||||||
ip: 10.10.25.240
|
ip: 192.168.193.141
|
||||||
port: 5000
|
port: 5000
|
||||||
|
|
||||||
model:
|
model:
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,20 @@ class Cprun:
|
||||||
diameter: float | None = None,
|
diameter: float | None = None,
|
||||||
flow_threshold: float = 0.4,
|
flow_threshold: float = 0.4,
|
||||||
cellprob_threshold: float = 0.0, ):
|
cellprob_threshold: float = 0.0, ):
|
||||||
|
"""
|
||||||
|
运行 cellpose 分割
|
||||||
|
|
||||||
|
Args:
|
||||||
|
images: [list] 图片存储路径
|
||||||
|
time: [str] 开始运行的时间,相当于本次运行的ID,用于存储运行结果
|
||||||
|
model: [str] 图像分割所使用的模型
|
||||||
|
diameter: [float] diameters are used to rescale the image to 30 pix cell diameter.
|
||||||
|
flow_threshold: [float] flow error threshold (all cells with errors below threshold are kept) (not used for 3D). Defaults to 0.4.
|
||||||
|
cellprob_threshold: [float] all pixels with value above threshold kept for masks, decrease to find more and larger masks. Defaults to 0.0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
if time is None:
|
if time is None:
|
||||||
return [False, "No time received"]
|
return [False, "No time received"]
|
||||||
|
|
@ -71,9 +85,10 @@ class Cprun:
|
||||||
|
|
||||||
message = [f"Using {model} model"]
|
message = [f"Using {model} model"]
|
||||||
|
|
||||||
|
# 设定模型参数
|
||||||
model = models.CellposeModel(gpu=True, model_type=model)
|
model = models.CellposeModel(gpu=True, model_type=model)
|
||||||
files = images
|
files = images
|
||||||
imgs = [imread(f) for f in files]
|
imgs = [imread(f) for f in files] # 获取目录中的每一个文件
|
||||||
masks, flows, styles = model.eval(
|
masks, flows, styles = model.eval(
|
||||||
imgs,
|
imgs,
|
||||||
flow_threshold=flow_threshold,
|
flow_threshold=flow_threshold,
|
||||||
|
|
@ -90,7 +105,7 @@ class Cprun:
|
||||||
out = base + "_output"
|
out = base + "_output"
|
||||||
save_masks(imgs, mask, flow, out, tif=True)
|
save_masks(imgs, mask, flow, out, tif=True)
|
||||||
|
|
||||||
# 用 plot 生成彩色叠加图(不依赖 skimage)
|
# 用 plot 生成彩色叠加图
|
||||||
rgb = plot.image_to_rgb(img, channels=[0, 0]) # 原图转 RGB
|
rgb = plot.image_to_rgb(img, channels=[0, 0]) # 原图转 RGB
|
||||||
over = plot.mask_overlay(rgb, masks=mask, colors=None) # 叠加彩色实例
|
over = plot.mask_overlay(rgb, masks=mask, colors=None) # 叠加彩色实例
|
||||||
Image.fromarray(over).save(base + "_overlay.png")
|
Image.fromarray(over).save(base + "_overlay.png")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import redis
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from sympy import false
|
||||||
|
|
||||||
CONFIG_PATH = Path(__file__).parent / "config.yaml"
|
CONFIG_PATH = Path(__file__).parent / "config.yaml"
|
||||||
cfg = OmegaConf.load(CONFIG_PATH)
|
cfg = OmegaConf.load(CONFIG_PATH)
|
||||||
cfg.data.root_dir = str((CONFIG_PATH.parent / cfg.data.root_dir).resolve())
|
cfg.data.root_dir = str((CONFIG_PATH.parent / cfg.data.root_dir).resolve())
|
||||||
|
|
@ -17,6 +19,19 @@ os.environ["CELLPOSE_LOCAL_MODELS_PATH"] = MODELS_DIR
|
||||||
r = redis.Redis(host="127.0.0.1", port=6379, db=0)
|
r = redis.Redis(host="127.0.0.1", port=6379, db=0)
|
||||||
|
|
||||||
def set_status(task_id, status, train_losses, test_losses, **extra):
|
def set_status(task_id, status, train_losses, test_losses, **extra):
|
||||||
|
"""
|
||||||
|
修改redis数据库中某一任务的运行状态
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_id: 这一任务的时间戳
|
||||||
|
status: 任务状态
|
||||||
|
train_losses: 此次任务的训练loss
|
||||||
|
test_losses: 此次任务的测试loss
|
||||||
|
**extra:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
payload = {"status": status,
|
payload = {"status": status,
|
||||||
"updated_at": datetime.datetime.utcnow().isoformat(),
|
"updated_at": datetime.datetime.utcnow().isoformat(),
|
||||||
"train_losses": train_losses.tolist() if hasattr(train_losses, "tolist") else train_losses,
|
"train_losses": train_losses.tolist() if hasattr(train_losses, "tolist") else train_losses,
|
||||||
|
|
@ -38,7 +53,47 @@ class Cptrain:
|
||||||
model_name: str | None = None,
|
model_name: str | None = None,
|
||||||
image_filter: str = "_img",
|
image_filter: str = "_img",
|
||||||
mask_filter: str = "_masks",
|
mask_filter: str = "_masks",
|
||||||
base_model: str = "cpsam"):
|
base_model: str = "cpsam",
|
||||||
|
train_probs: list[float] = None,
|
||||||
|
test_probs: list[float] = None,
|
||||||
|
batch_size: int = 8,
|
||||||
|
learning_rate = 5e-5,
|
||||||
|
n_epochs: int = 100,
|
||||||
|
weight_decay=0.1,
|
||||||
|
normalize: bool =True,
|
||||||
|
compute_flows: bool = False,
|
||||||
|
min_train_masks: int = 5,
|
||||||
|
nimg_per_epoch: int =None,
|
||||||
|
rescale: bool= False,
|
||||||
|
scale_range=None,
|
||||||
|
channel_axis: int = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
开始训练
|
||||||
|
|
||||||
|
Args:
|
||||||
|
time: 此次任务的时间戳(即任务ID)
|
||||||
|
model_name: 训练结果命名
|
||||||
|
image_filter:
|
||||||
|
mask_filter:
|
||||||
|
base_model:
|
||||||
|
train_probs:
|
||||||
|
test_probs:
|
||||||
|
batch_size:
|
||||||
|
learning_rate:
|
||||||
|
n_epochs:
|
||||||
|
weight_decay:
|
||||||
|
normalize:
|
||||||
|
compute_flows:
|
||||||
|
min_train_masks:
|
||||||
|
nimg_per_epoch:
|
||||||
|
rescale:
|
||||||
|
scale_range:
|
||||||
|
channel_axis:
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
train_dir = Path(TRAIN_DIR) / time
|
train_dir = Path(TRAIN_DIR) / time
|
||||||
test_dir = Path(TEST_DIR) / time
|
test_dir = Path(TEST_DIR) / time
|
||||||
|
|
@ -56,9 +111,13 @@ class Cptrain:
|
||||||
model_path, train_losses, test_losses = train.train_seg(model.net,
|
model_path, train_losses, test_losses = train.train_seg(model.net,
|
||||||
train_data=images, train_labels=labels,
|
train_data=images, train_labels=labels,
|
||||||
test_data=test_images, test_labels=test_labels,
|
test_data=test_images, test_labels=test_labels,
|
||||||
weight_decay=0.1, learning_rate=1e-5,
|
train_probs=train_probs, test_probs=test_probs,
|
||||||
n_epochs=100, model_name=model_name,
|
weight_decay=weight_decay, learning_rate=learning_rate,
|
||||||
save_path=BASE_DIR)
|
n_epochs=n_epochs, model_name=model_name,
|
||||||
|
save_path=BASE_DIR, batch_size=batch_size,
|
||||||
|
normalize=normalize, compute_flows=compute_flows, min_train_masks=min_train_masks,
|
||||||
|
nimg_per_epoch=nimg_per_epoch, rescale=rescale, scale_range=scale_range, channel_axis=channel_axis
|
||||||
|
)
|
||||||
|
|
||||||
set_status(time, "done", train_losses, test_losses)
|
set_status(time, "done", train_losses, test_losses)
|
||||||
print("模型已保存到:", model_path)
|
print("模型已保存到:", model_path)
|
||||||
|
|
|
||||||
|
|
@ -93,15 +93,10 @@ def run_upload():
|
||||||
return default
|
return default
|
||||||
|
|
||||||
flow_threshold = _to_float(request.args.get("flow_threshold") or request.form.get("flow_threshold"), 0.4)
|
flow_threshold = _to_float(request.args.get("flow_threshold") or request.form.get("flow_threshold"), 0.4)
|
||||||
cellprob_threshold = _to_float(request.args.get("cellprob_threshold") or request.form.get("cellprob_threshold"),
|
cellprob_threshold = _to_float(request.args.get("cellprob_threshold") or request.form.get("cellprob_threshold"),0.0)
|
||||||
0.0)
|
|
||||||
diameter_raw = request.args.get("diameter") or request.form.get("diameter")
|
diameter_raw = request.args.get("diameter") or request.form.get("diameter")
|
||||||
diameter = _to_float(diameter_raw, None) if diameter_raw not in (None, "") else None
|
diameter = _to_float(diameter_raw, None) if diameter_raw not in (None, "") else None
|
||||||
|
|
||||||
print("cpt:" + str(cellprob_threshold))
|
|
||||||
print("flow:" + str(flow_threshold))
|
|
||||||
print("diameter:" + str(diameter))
|
|
||||||
|
|
||||||
# 将文件保存在本地目录中
|
# 将文件保存在本地目录中
|
||||||
ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + f"-{int(time.time()*1000)%1000:03d}"
|
ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + f"-{int(time.time()*1000)%1000:03d}"
|
||||||
os.makedirs(Path(UPLOAD_DIR) / ts, exist_ok=True)
|
os.makedirs(Path(UPLOAD_DIR) / ts, exist_ok=True)
|
||||||
|
|
@ -140,12 +135,69 @@ def run_upload():
|
||||||
|
|
||||||
@app.post("/train_upload")
|
@app.post("/train_upload")
|
||||||
def train_upload():
|
def train_upload():
|
||||||
|
"""
|
||||||
|
从前端获取训练数据和测试数据,并开始训练
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _to_float(x, default):
|
||||||
|
"""
|
||||||
|
将变量转为float类型
|
||||||
|
Args:
|
||||||
|
x: 变量
|
||||||
|
default: 默认值
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return float(x)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
def _to_int(x, default):
|
||||||
|
"""
|
||||||
|
将变量转为int类型
|
||||||
|
Args:
|
||||||
|
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}"
|
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}"
|
model_name = request.args.get("model_name") or f"custom_model-{ts}"
|
||||||
image_filter = request.args.get("image_filter") or "_img"
|
image_filter = request.args.get("image_filter") or "_img"
|
||||||
mask_filter = request.args.get("mask_filter") or "_masks"
|
mask_filter = request.args.get("mask_filter") or "_masks"
|
||||||
base_model = request.args.get("base_model") or "cpsam"
|
base_model = request.args.get("base_model") or "cpsam"
|
||||||
|
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")
|
train_files = request.files.getlist("train_files")
|
||||||
test_files = request.files.getlist("test_files")
|
test_files = request.files.getlist("test_files")
|
||||||
os.makedirs(Path(TRAIN_DIR) / ts, exist_ok=True)
|
os.makedirs(Path(TRAIN_DIR) / ts, exist_ok=True)
|
||||||
|
|
@ -167,23 +219,30 @@ def train_upload():
|
||||||
saved.append(os.path.join(TEST_DIR, ts, name))
|
saved.append(os.path.join(TEST_DIR, ts, name))
|
||||||
|
|
||||||
def job():
|
def job():
|
||||||
|
"""
|
||||||
|
子线程方法
|
||||||
|
"""
|
||||||
return asyncio.run(Cptrain.start_train(
|
return asyncio.run(Cptrain.start_train(
|
||||||
time=ts,
|
time=ts, model_name=model_name, image_filter=image_filter, mask_filter=mask_filter, base_model=base_model,
|
||||||
model_name=model_name,
|
batch_size=batch_size, learning_rate=learning_rate, n_epochs=n_epochs, weight_decay=weight_decay,
|
||||||
image_filter=image_filter,
|
normalize=normalize, compute_flows=compute_flows, min_train_masks=min_train_masks, nimg_per_epoch=nimg_per_epoch,
|
||||||
mask_filter=mask_filter,
|
rescale=rescale, scale_range=scale_range, channel_axis=channel_axis,
|
||||||
base_model=base_model
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# 创建一个子线程,防止阻塞主线程
|
||||||
fut = executor.submit(job)
|
fut = executor.submit(job)
|
||||||
|
|
||||||
def done_cb(f):
|
def done_cb(f):
|
||||||
|
"""
|
||||||
|
获取训练结果,并存入redis数据库
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
train_losses, test_losses = f.result()
|
train_losses, test_losses = f.result()
|
||||||
set_train_status(ts, "success", train_losses, test_losses)
|
set_train_status(ts, "success", train_losses, test_losses)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
set_status(ts, "failed", error=str(e))
|
set_status(ts, "failed", error=str(e))
|
||||||
|
|
||||||
|
# 添加回调,在子线程执行完后更新redis中任务状态
|
||||||
fut.add_done_callback(done_cb)
|
fut.add_done_callback(done_cb)
|
||||||
|
|
||||||
return jsonify({"ok": True, "count": len(saved), "id": ts})
|
return jsonify({"ok": True, "count": len(saved), "id": ts})
|
||||||
|
|
@ -197,12 +256,20 @@ def status():
|
||||||
"""
|
"""
|
||||||
task_id = request.args.get('id')
|
task_id = request.args.get('id')
|
||||||
st = get_status(task_id)
|
st = get_status(task_id)
|
||||||
|
print(st)
|
||||||
if not st:
|
if not st:
|
||||||
return jsonify({"ok": True, "exists": False, "status": "not_found"}), 200
|
return jsonify({"ok": True, "exists": False, "status": "not_found"}), 200
|
||||||
return jsonify({"ok": True, "exists": True, **st}), 200
|
return jsonify({"ok": True, "exists": True, **st}), 200
|
||||||
|
|
||||||
@app.get("/preview")
|
@app.get("/preview")
|
||||||
def preview():
|
def preview():
|
||||||
|
"""
|
||||||
|
获取本次分割结果的预览
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
task_id = request.args.get('id')
|
task_id = request.args.get('id')
|
||||||
task_dir = Path(OUTPUT_DIR) / task_id
|
task_dir = Path(OUTPUT_DIR) / task_id
|
||||||
if not task_dir.exists():
|
if not task_dir.exists():
|
||||||
|
|
@ -227,11 +294,24 @@ def preview():
|
||||||
|
|
||||||
@app.get("/models")
|
@app.get("/models")
|
||||||
def list_models():
|
def list_models():
|
||||||
models_list = os.listdir(MODELS_DIR)
|
"""
|
||||||
|
获取现有模型列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
models_list = os.listdir(MODELS_DIR) # 查询模型列表中有哪些文件
|
||||||
return jsonify({"ok": True, "models": models_list})
|
return jsonify({"ok": True, "models": models_list})
|
||||||
|
|
||||||
@app.get("/result")
|
@app.get("/result")
|
||||||
def list_results():
|
def list_results():
|
||||||
|
"""
|
||||||
|
获取运行结果
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
task_id = request.args.get('id')
|
task_id = request.args.get('id')
|
||||||
st = get_status(task_id)
|
st = get_status(task_id)
|
||||||
if not st:
|
if not st:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from multiprocessing import Process
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Cprun.run_test()
|
# 启动测试服务器
|
||||||
p = Process(target=run_dev)
|
p = Process(target=run_dev)
|
||||||
p.start()
|
p.start()
|
||||||
print(f"Flask running in PID {p.pid}")
|
print(f"Flask running in PID {p.pid}")
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const config = {
|
const config = {
|
||||||
server: {
|
server: {
|
||||||
protocol: 'http',
|
protocol: 'http',
|
||||||
host: '10.10.25.240',
|
host: '192.168.193.141',
|
||||||
port: 5000
|
port: 5000
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,139 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="zh-CN" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Bootstrap demo</title>
|
<title>任务面板</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">
|
<!-- Bootstrap CSS -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous" />
|
||||||
|
<!-- Bootstrap Icons -->
|
||||||
|
<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, 0.65);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] .glass-card {
|
||||||
|
background: rgba(17, 20, 34, 0.6);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
transition: transform .15s ease, box-shadow .15s ease;
|
||||||
|
padding: 1.1rem 1.4rem;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
.action-btn:hover, .action-btn:focus-visible {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 0.75rem 1.25rem rgba(0,0,0,.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-badge {
|
||||||
|
letter-spacing: .08em;
|
||||||
|
background: rgba(13,110,253,.1);
|
||||||
|
color: #0d6efd;
|
||||||
|
border: 1px solid rgba(13,110,253,.15);
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] .brand-badge {
|
||||||
|
background: rgba(13,110,253,.15);
|
||||||
|
color: #9ec5fe;
|
||||||
|
border-color: rgba(13,110,253,.25);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<main class="container min-vh-100 d-flex align-items-center justify-content-center py-5">
|
||||||
|
<div class="col-12 col-md-10 col-lg-8">
|
||||||
|
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5 text-center">
|
||||||
|
<div class="d-flex justify-content-center mb-3">
|
||||||
|
<span class="badge brand-badge rounded-pill px-3 py-2">
|
||||||
|
<i class="bi bi-rocket-takeoff me-1"></i> Cell Processing with <a href="https://github.com/MouseLand/cellpose">CELLPOSE</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="run.html">运行</a>
|
<h1 class="display-6 fw-semibold mb-2">欢迎使用任务面板</h1>
|
||||||
<a href="train.html">训练</a>
|
<p class="text-secondary mb-4">
|
||||||
|
选择你要进行的操作。可随时返回此页切换任务。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="row g-3 g-md-4 justify-content-center">
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<a href="run.html" class="btn btn-primary w-100 action-btn btn-icon" role="button" aria-label="进入运行页面">
|
||||||
|
<i class="bi bi-play-circle-fill fs-4"></i>
|
||||||
|
<span>
|
||||||
|
运行<br>
|
||||||
|
<small class="opacity-75">快速执行已有模型/流程</small>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6">
|
||||||
|
<a href="train.html" class="btn btn-outline-primary w-100 action-btn btn-icon" role="button" aria-label="进入训练页面">
|
||||||
|
<i class="bi bi-cpu-fill fs-4"></i>
|
||||||
|
<span>
|
||||||
|
训练<br>
|
||||||
|
<small class="opacity-75">启动或继续训练任务</small>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4 my-md-5">
|
||||||
|
|
||||||
|
<div class="d-flex flex-column flex-md-row gap-2 justify-content-center">
|
||||||
|
<a href="https://github.com/ClovertaTheTrilobita/cellpose-web" class="link-secondary text-decoration-none">
|
||||||
|
<i class="bi bi-github"></i> Github
|
||||||
|
</a>
|
||||||
|
<span class="text-secondary d-none d-md-inline">•</span>
|
||||||
|
<a href="#" class="link-secondary text-decoration-none">
|
||||||
|
<i class="bi bi-gear"></i> 设置
|
||||||
|
</a>
|
||||||
|
<span class="text-secondary d-none d-md-inline">•</span>
|
||||||
|
<span class="text-secondary"><i class="bi bi-keyboard"></i> 快捷键:<kbd>R</kbd> 运行,<kbd>T</kbd> 训练</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 键盘快捷键(可选) -->
|
||||||
|
<script>
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
if (['INPUT','TEXTAREA'].includes((e.target.tagName || '').toUpperCase())) return;
|
||||||
|
if (e.key.toLowerCase() === 'r') location.href = 'run.html';
|
||||||
|
if (e.key.toLowerCase() === 't') location.href = 'train.html';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- 只需引入 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>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,9 +1,148 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" data-bs-theme="auto">
|
||||||
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<h1>运行结果预览</h1>
|
<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, 0.65);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] .glass-card {
|
||||||
|
background: rgba(17, 20, 34, 0.6);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标题区 */
|
||||||
|
.page-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .6rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.sub-hint {
|
||||||
|
font-size: .9rem;
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 预览网格:仅美化 #gallery,保持 id 不变 */
|
||||||
|
#gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
/* 预览卡片 */
|
||||||
|
.thumb {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: rgba(0,0,0,.05);
|
||||||
|
border: 1px solid rgba(0,0,0,.06);
|
||||||
|
transition: transform .12s ease, box-shadow .12s ease;
|
||||||
|
}
|
||||||
|
.thumb:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 .9rem 1.2rem rgba(0,0,0,.12);
|
||||||
|
}
|
||||||
|
.thumb img, .thumb canvas, .thumb video {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.thumb .badge-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: .5rem;
|
||||||
|
left: .5rem;
|
||||||
|
padding: .25rem .5rem;
|
||||||
|
border-radius: .5rem;
|
||||||
|
background: rgba(13,110,253,.85);
|
||||||
|
color: #fff;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空态提示:仅美化 #none-exist */
|
||||||
|
#none-exist {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
border-radius: .75rem;
|
||||||
|
background: rgba(108,117,125,.08);
|
||||||
|
border: 1px dashed rgba(108,117,125,.35);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下载按钮 */
|
||||||
|
.download-wrap {
|
||||||
|
gap: .75rem;
|
||||||
|
}
|
||||||
|
.download-btn {
|
||||||
|
padding: .65rem 1.1rem;
|
||||||
|
border-radius: .8rem;
|
||||||
|
transition: transform .12s ease, box-shadow .12s ease;
|
||||||
|
}
|
||||||
|
.download-btn:hover, .download-btn:focus-visible {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 .75rem 1.25rem rgba(0,0,0,.12);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5">
|
||||||
|
<!-- 标题区(保持语义,未改 id) -->
|
||||||
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
|
||||||
|
<h1 class="page-title h4">
|
||||||
|
<i class="bi bi-images"></i> 运行结果预览
|
||||||
|
</h1>
|
||||||
|
<div class="sub-hint">
|
||||||
|
生成的图像/掩膜将显示在下方网格中
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空态提示:仅样式增强,不改 id/hidden 机制 -->
|
||||||
<p id="none-exist" hidden></p>
|
<p id="none-exist" hidden></p>
|
||||||
|
|
||||||
|
<!-- 预览网格:只美化 #gallery -->
|
||||||
<div id="gallery"></div>
|
<div id="gallery"></div>
|
||||||
<button onclick="downloadTif()">下载tif</button>
|
|
||||||
|
<!-- 下载按钮:保留 onclick,不动逻辑,只加样式 -->
|
||||||
|
<div class="mt-4 d-flex download-wrap">
|
||||||
|
<button class="btn btn-primary d-inline-flex align-items-center download-btn"
|
||||||
|
onclick="downloadTif()">
|
||||||
|
<i class="bi bi-download me-2"></i> 下载 TIF
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 仅需引入 bundle(如你已有可省略) -->
|
||||||
|
<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/axios/dist/axios.min.js"></script>
|
||||||
<script src="api.js"></script>
|
<script src="api.js"></script>
|
||||||
|
|
@ -67,3 +206,5 @@
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Bootstrap demo</title>
|
<title>运行分割</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"
|
<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">
|
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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div style="padding: 1rem 1rem;">
|
<!-- <div style="padding: 1rem 1rem;">
|
||||||
<div class="mb-3 border" style="padding: 1rem 1rem;">
|
<div class="mb-3 border" style="padding: 1rem 1rem;">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input id="fileInput" type="file" class="form-control" multiple />
|
<input id="fileInput" type="file" class="form-control" multiple />
|
||||||
|
|
@ -47,7 +48,94 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br><br><br>
|
<br><br><br> -->
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5">
|
||||||
|
|
||||||
|
<!-- 上传区 -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||||
|
<h2 class="h5 section-title mb-0"><i class="bi bi-folder2-open me-2"></i>选择文件</h2>
|
||||||
|
<span class="hint">可多选</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="bi bi-cloud-arrow-up"></i></span>
|
||||||
|
<input id="fileInput" type="file" class="form-control" multiple />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<!-- 参数区 -->
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-lg-8">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="bi bi-speedometer2 me-1"></i>flow threshold</span>
|
||||||
|
<input id="flow" 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-bezier2 me-1"></i>cellprob threshold</span>
|
||||||
|
<input id="cellprob" 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-bounding-box-circles me-1"></i>diameter</span>
|
||||||
|
<input id="diameter" type="text" class="form-control" placeholder="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<label class="w-100">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="bi bi-cpu-fill me-1"></i>model</span>
|
||||||
|
<select id="model" class="form-select"></select>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</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">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
<strong>提示</strong>
|
||||||
|
</div>
|
||||||
|
<div class="hint">
|
||||||
|
• 参数留空将使用默认值。<br>
|
||||||
|
• 数值可使用小数(如 0.4)。<br>
|
||||||
|
• 选择合适的模型以获得更稳健的分割效果。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 行动区 -->
|
||||||
|
<div class="mt-4 d-flex flex-column flex-md-row align-items-stretch gap-3">
|
||||||
|
<button id="uploadBtn" class="btn btn-success d-inline-flex align-items-center">
|
||||||
|
<i class="bi bi-upload me-2"></i> Upload
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<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 可写入文本 -->
|
||||||
|
</div>
|
||||||
|
<progress id="bar" max="100" value="0"></progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
|
@ -184,6 +272,82 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<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, 0.65);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
[data-bs-theme="dark"] .glass-card {
|
||||||
|
background: rgba(17, 20, 34, 0.6);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 区块与控件 */
|
||||||
|
.section-title {
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: .02em;
|
||||||
|
}
|
||||||
|
.input-group-text i {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
.hint {
|
||||||
|
font-size: .875rem;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
/* Chrome / Edge / Safari */
|
||||||
|
#bar::-webkit-progress-bar {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
#bar::-webkit-progress-value {
|
||||||
|
background: linear-gradient(90deg, #0d6efd, #6ea8fe);
|
||||||
|
border-radius: .75rem;
|
||||||
|
}
|
||||||
|
/* Firefox */
|
||||||
|
#bar::-moz-progress-bar {
|
||||||
|
background: linear-gradient(90deg, #0d6efd, #6ea8fe);
|
||||||
|
border-radius: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上传按钮动效 */
|
||||||
|
#uploadBtn {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,58 +1,290 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="zh-CN" data-bs-theme="auto">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Bootstrap demo</title>
|
<title>训练任务</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">
|
<!-- Bootstrap 5.3 与 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-bottom: 0;
|
||||||
|
}
|
||||||
|
.hint { font-size: .9rem; opacity: .75 }
|
||||||
|
|
||||||
|
/* 小卡片(参数分组) */
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div style="padding: 1rem 1rem;">
|
<div class="container py-4">
|
||||||
<div class="mb-3 border" style="padding: 1rem 1rem;">
|
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5">
|
||||||
<div class="input-group mb-3">
|
<!-- 标题 -->
|
||||||
<h5>选择训练文件</h5>
|
<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>
|
||||||
|
<div class="hint">选择训练/测试数据并设置模型参数</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 选择文件 -->
|
||||||
|
<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>
|
||||||
|
<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 />
|
<input id="trainFileInput" type="file" class="form-control" multiple />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group mb-3">
|
<div class="form-text">可多选;支持拖拽到输入框。</div>
|
||||||
<h5>选择测试文件</h5>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-lg-6">
|
||||||
|
<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 />
|
<input id="testFileInput" type="file" class="form-control" multiple />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-text">可选;用于评估训练效果。</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr class="my-4">
|
||||||
<div>
|
|
||||||
<div style="padding-right: 50rem;">
|
<!-- 参数区 -->
|
||||||
<div class="input-group mb-3">
|
<div class="row g-3">
|
||||||
<span class="input-group-text">model name:</span>
|
<div class="col-12 col-lg-8">
|
||||||
|
<!-- 基础设置 -->
|
||||||
|
<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>
|
||||||
<input id="name" type="text" class="form-control" placeholder="为你的模型命名" />
|
<input id="name" type="text" class="form-control" placeholder="为你的模型命名" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="input-group mb-3">
|
<div class="col-12 col-md-6">
|
||||||
<span class="input-group-text">image filter:</span>
|
<div class="input-group">
|
||||||
|
<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" />
|
<input id="image_filter" type="text" class="form-control" placeholder="_img" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-text">如 <code>*_img.png</code></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="input-group mb-3">
|
<div class="col-12 col-md-6">
|
||||||
<span class="input-group-text">masks filter</span>
|
<div class="input-group">
|
||||||
|
<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" />
|
<input id="masks_filter" type="text" class="form-control" placeholder="_masks" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-text">如 <code>*_masks.png</code></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label>
|
<div class="col-12">
|
||||||
|
<label class="w-100">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="bi bi-cpu-fill me-1"></i>model</span>
|
||||||
<select id="model" class="form-select"></select>
|
<select id="model" class="form-select"></select>
|
||||||
</label>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
</div>
|
||||||
<div>
|
|
||||||
<button id="uploadBtn" class="btn btn-success">Upload</button>
|
<!-- 训练超参 -->
|
||||||
<progress id="bar" max="100" value="0" style="width:300px;"></progress>
|
<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>
|
||||||
<br><br><br>
|
</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">
|
||||||
|
<i class="bi bi-info-circle me-2"></i><strong>提示</strong>
|
||||||
|
</div>
|
||||||
|
<div class="hint">
|
||||||
|
• 名称用于区分训练产物(日志/权重)。<br>
|
||||||
|
• filter 用于匹配文件名,如 <code>*_img.png</code>、<code>*_masks.png</code>。<br>
|
||||||
|
• 选择合适的预训练模型可加速收敛。<br>
|
||||||
|
• 建议仅微调必要超参,保持可复现性。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 行动与进度(保持 id 与机制不变) -->
|
||||||
|
<div class="mt-4 d-flex flex-column flex-md-row align-items-stretch gap-3">
|
||||||
|
<button id="uploadBtn" class="btn btn-success d-inline-flex align-items-center">
|
||||||
|
<i class="bi bi-play-circle me-2"></i> Upload
|
||||||
|
</button>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<progress id="bar" max="100" value="0"></progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 只需引入 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/axios/dist/axios.min.js"></script>
|
||||||
|
|
@ -126,13 +358,29 @@
|
||||||
const model_name = (document.getElementById('model_name')?.value || '').trim();
|
const model_name = (document.getElementById('model_name')?.value || '').trim();
|
||||||
const image_filter = (document.getElementById('image_filter')?.value || '').trim();
|
const image_filter = (document.getElementById('image_filter')?.value || '').trim();
|
||||||
const masks_filter = (document.getElementById('masks_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 组装查询串
|
// 用 URLSearchParams 组装查询串
|
||||||
const qs = new URLSearchParams({
|
const qs = new URLSearchParams({
|
||||||
model: model,
|
model: model,
|
||||||
model_name: model_name,
|
model_name: model_name,
|
||||||
image_filter: image_filter,
|
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()}`;
|
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" />
|
<meta charset="UTF-8" />
|
||||||
<h1>训练结果预览</h1>
|
<title>训练结果预览</title>
|
||||||
<p id="none-exist" hidden></p>
|
|
||||||
|
<!-- 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>
|
<canvas id="lossChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部小提示 -->
|
||||||
|
<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/axios/dist/axios.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
@ -28,12 +138,17 @@
|
||||||
const data = res.data;
|
const data = res.data;
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
msg.textContent = `id "${ID}" 不存在`;
|
msg.textContent = `任务 "${ID}" 不存在`;
|
||||||
msg.hidden = false;
|
msg.hidden = false;
|
||||||
cava.hidden = true;
|
cava.hidden = true;
|
||||||
}
|
}
|
||||||
else if (status == "running") {
|
else if (status == "running") {
|
||||||
msg.textContent = `id "${ID}" 仍在运行中,请耐心等待片刻后刷新。`;
|
msg.textContent = `任务 "${ID}" 仍在运行中,请耐心等待片刻后刷新。`;
|
||||||
|
msg.hidden = false;
|
||||||
|
cava.hidden = true;
|
||||||
|
}
|
||||||
|
else if (status == "failed") {
|
||||||
|
msg.textContent = `任务 "${ID}" 运行失败,由于:"Error: ${data.error}",请检查上传数据集是否存在问题`;
|
||||||
msg.hidden = false;
|
msg.hidden = false;
|
||||||
cava.hidden = true;
|
cava.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
@ -94,3 +209,6 @@
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -434,6 +434,6 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}==>${RESET} Deployment successfull"
|
echo -e "${GREEN}==>${RESET} Deployment successfull"
|
||||||
if ask_yn "Do you wish to start cellpose server now?" y; then
|
if ask_yn "Do you wish to start cellpose developmental server now?" y; then
|
||||||
python ${root}/backend/main.py
|
python ${root}/backend/main.py
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue