diff --git a/README.md b/README.md index 89ec028..6892f3b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - ⛵️ 训练、分割结果随时下载 - 📚 权重可直接作为后续分割模型 - 🛠️ 一键安装部署脚本 -- 🚧 [TODO] 前端样式美化 +- 🎨 前端样式美化
diff --git a/backend/cp_train.py b/backend/cp_train.py index d6d9aa4..58594ac 100644 --- a/backend/cp_train.py +++ b/backend/cp_train.py @@ -5,6 +5,8 @@ import redis import datetime import json +from sympy import false + CONFIG_PATH = Path(__file__).parent / "config.yaml" cfg = OmegaConf.load(CONFIG_PATH) cfg.data.root_dir = str((CONFIG_PATH.parent / cfg.data.root_dir).resolve()) @@ -34,11 +36,25 @@ class Cptrain: @classmethod async def start_train(cls, - time: str | None = None, - model_name: str | None = None, - image_filter: str = "_img", - mask_filter: str = "_masks", - base_model: str = "cpsam"): + time: str | None = None, + model_name: str | None = None, + image_filter: str = "_img", + mask_filter: str = "_masks", + 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, + ): train_dir = Path(TRAIN_DIR) / time test_dir = Path(TEST_DIR) / time @@ -56,9 +72,13 @@ class Cptrain: model_path, train_losses, test_losses = train.train_seg(model.net, train_data=images, train_labels=labels, test_data=test_images, test_labels=test_labels, - weight_decay=0.1, learning_rate=1e-5, - n_epochs=100, model_name=model_name, - save_path=BASE_DIR) + train_probs=train_probs, test_probs=test_probs, + weight_decay=weight_decay, learning_rate=learning_rate, + 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) print("模型已保存到:", model_path) diff --git a/backend/flaskApp.py b/backend/flaskApp.py index bcf20d0..40109f5 100644 --- a/backend/flaskApp.py +++ b/backend/flaskApp.py @@ -140,11 +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 = _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") @@ -172,7 +208,18 @@ def train_upload(): model_name=model_name, image_filter=image_filter, mask_filter=mask_filter, - base_model=base_model + base_model=base_model, + batch_size=batch_size, + 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, + rescale=rescale, + scale_range=scale_range, + channel_axis=channel_axis, )) fut = executor.submit(job) @@ -197,6 +244,7 @@ def status(): """ task_id = request.args.get('id') st = get_status(task_id) + print(st) if not st: return jsonify({"ok": True, "exists": False, "status": "not_found"}), 200 return jsonify({"ok": True, "exists": True, **st}), 200 diff --git a/frontend/index.html b/frontend/index.html index 7a0a62d..fd98f6d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,18 +1,139 @@ - - + - - - Bootstrap demo - + + + 任务面板 + + + + + + + +
+
+
+
+ + Cell Processing with CELLPOSE + +
- 运行 - 训练 +

欢迎使用任务面板

+

+ 选择你要进行的操作。可随时返回此页切换任务。 +

+ + + +
+ +
+ + Github + + + + 设置 + + + 快捷键:R 运行,T 训练 +
+
+
+
+ + + + + + - - \ No newline at end of file + diff --git a/frontend/preview.html b/frontend/preview.html index c2e61a8..c504e69 100644 --- a/frontend/preview.html +++ b/frontend/preview.html @@ -1,10 +1,149 @@ - -

运行结果预览

- - - + + + + 运行结果预览 + + + + + + + + +
+
+ +
+

+ 运行结果预览 +

+
+ 生成的图像/掩膜将显示在下方网格中 +
+
+ + + + + + + + +
+ +
+
+
+ + + + + \ No newline at end of file + + + diff --git a/frontend/run.html b/frontend/run.html index 0d88074..839ebd2 100644 --- a/frontend/run.html +++ b/frontend/run.html @@ -4,13 +4,14 @@ - Bootstrap demo + 运行分割 + -
+ + +
+
+ + +
+
+

选择文件

+ 可多选 +
+
+ + +
+
+ +
+ + +
+
+
+
+
+ flow threshold + +
+
+ +
+
+ cellprob threshold + +
+
+ +
+
+ diameter + +
+
+ +
+ +
+
+
+ + +
+
+
+ + 提示 +
+
+ • 参数留空将使用默认值。
+ • 数值可使用小数(如 0.4)。
+ • 选择合适的模型以获得更稳健的分割效果。 +
+
+
+
+ + +
+ + +
+
+ 进度 + +
+ +
+
+ +
+
@@ -184,6 +272,82 @@ } }); + + + \ No newline at end of file diff --git a/frontend/train.html b/frontend/train.html index c155218..f1b386b 100644 --- a/frontend/train.html +++ b/frontend/train.html @@ -1,58 +1,290 @@ - - + - - - Bootstrap demo - + + + 训练任务 + + + + + + -
-
-
-
选择训练文件
- -
-
-
选择测试文件
- -
+
+
+ +
+

+ 训练配置 +

+
选择训练/测试数据并设置模型参数
+
-
-
-
-
- model name: - -
- -
- image filter: - -
- -
- masks filter - -
- - - -
+ +
+
+
+ +
+ +
-
-
- - +
可多选;支持拖拽到输入框。
+
+ +
+ +
+ +
+
可选;用于评估训练效果。
+
+
+ +
+ + +
+
+ +
+
+ 基础设置 +
+ +
+
+
+ model name + +
+
+ +
+
+ image filter + +
+
*_img.png
+
+ +
+
+ masks filter + +
+
*_masks.png
+
+ +
+ +
+
+
+ + +
+
+ 训练超参数 +
+ +
+
+
+ learning rate + +
+
+ +
+
+ n epochs + +
+
+ +
+
+ weight decay + +
+
+ +
+
+ normalize + +
+
+ +
+
+ compute flows + + +
+
与光流/流场相关,通常仅在特定任务启用。
+
+ +
+
+ min train masks + +
+
+ +
+
+ nimg_per_epoch + +
+
+ +
+
+ channel axis + +
+
+
+
+
+ + +
+
+
+ 提示 +
+
+ • 名称用于区分训练产物(日志/权重)。
+ • filter 用于匹配文件名,如 *_img.png*_masks.png
+ • 选择合适的预训练模型可加速收敛。
+ • 建议仅微调必要超参,保持可复现性。 +
+
+
+
+ + +
+ +
+
+ 进度 + +
+ +
+
-


+
+ + + @@ -126,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()}`; diff --git a/frontend/train_result.html b/frontend/train_result.html index 409952a..58a3752 100644 --- a/frontend/train_result.html +++ b/frontend/train_result.html @@ -1,96 +1,214 @@ - - -

训练结果预览

- - + + - - - - \ No newline at end of file + @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; + } + + + + +
+
+
+

+ 训练结果预览 +

+
Loss / Metric 曲线将根据你的训练日志自动绘制
+
+ + + + + +
+ +
+ + +
+ 提示:如果你使用 Chart.js,建议设置 responsive: truemaintainAspectRatio: false, + 本页样式已自动保证画布自适应容器尺寸。 +
+
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/install.sh b/install.sh index ca76cea..22ccbf0 100755 --- a/install.sh +++ b/install.sh @@ -434,6 +434,6 @@ else fi 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 fi