diff --git a/backend/cp_run.py b/backend/cp_run.py index c69c95b..1b8bd72 100644 --- a/backend/cp_run.py +++ b/backend/cp_run.py @@ -3,15 +3,10 @@ from cellpose.io import imread, save_masks from PIL import Image import numpy as np import os, datetime -from typing import Literal - -from sympy import false +import time class Cprun: - # def __init__(self, model: str | Literal["cpsam"], images: list[str] | str): - # self.model = model - # self.images = images @classmethod def run_test(cls): @@ -26,7 +21,7 @@ class Cprun: f"[{i}] mask max={int(getattr(m, 'max', lambda: 0)()) if hasattr(m, 'max') else int(np.max(m))}, unique={np.unique(m)[:5]} ..." ) - ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + f"-{int(time.time()*1000)%1000:03d}" outdir = os.path.join(os.path.dirname(__file__), "test_output", ts) os.makedirs(outdir, exist_ok=True) # 自动创建目录 for img, mask, flow, name in zip(imgs, masks, flows, files): @@ -43,8 +38,8 @@ class Cprun: @classmethod async def run(cls, images: list[str] | str | None = None, - time: datetime.datetime | None = None, - model: str | str = "cpsam", + time: str | None = None, + model: str = "cpsam", diameter: float | None = None, flow_threshold: float | float = 0.4, cellprob_threshold: float | float = 0.0, ): @@ -67,8 +62,8 @@ class Cprun: diameter=diameter ) - ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") - outdir = os.path.join(os.path.dirname(__file__), "run_output", ts) + ts = time + outdir = os.path.join(os.path.dirname(__file__), "output", ts) os.makedirs(outdir, exist_ok=True) # 自动创建目录 for img, mask, flow, name in zip(imgs, masks, flows, files): base = os.path.join(outdir, os.path.splitext(os.path.basename(name))[0]) @@ -82,4 +77,5 @@ class Cprun: Image.fromarray(over).save(base + "_overlay.png") message.append(f"Output saved to: {outdir}") - return [True, message] \ No newline at end of file + message.append(outdir) + return [True, message] diff --git a/backend/flaskApp.py b/backend/flaskApp.py index 5f1dc7e..73c5856 100644 --- a/backend/flaskApp.py +++ b/backend/flaskApp.py @@ -1,39 +1,74 @@ +import asyncio +from concurrent.futures import ThreadPoolExecutor from flask import Flask, send_from_directory, request, jsonify -import os, shutil, time, threading, datetime +import os, shutil, time, threading, datetime, json, redis from werkzeug.utils import secure_filename from flask_cors import CORS from pathlib import Path +from cp_run import Cprun app = Flask(__name__) CORS(app) BASE_DIR = Path(__file__).resolve().parent UPLOAD_DIR = BASE_DIR / "uploads" os.makedirs(UPLOAD_DIR, exist_ok=True) +executor = ThreadPoolExecutor(max_workers=4) +TASKS = {} +r = redis.Redis(host="127.0.0.1", port=6379, db=0) -def run_flask(): +# 启动测试服务器 +def run_dev(): app.run(host="10.147.18.141", port=5000) +def set_status(task_id, status, **extra): + payload = {"status": status, "updated_at": datetime.datetime.utcnow().isoformat(), **extra} + r.set(f"task:{task_id}", json.dumps(payload), ex=86400) # 1 天过期 + +def get_status(task_id): + raw = r.get(f"task:{task_id}") + return json.loads(raw) if raw else None + @app.route("/") def index(): return "

Hello

This is the backend of our cellpose server, please visit our website.

" -@app.route("/testdl") +@app.get("/testdl") def test_download(): return send_from_directory("test_output/2025-09-16-20-03-51", "img_overlay.png", as_attachment=True) -@app.route("/dl/") +@app.get("/dl/") def download(timestamp): - input_dir = os.path.join("output", timestamp) - output_dir = os.path.join("output/tmp", timestamp) # 不要加 .zip,make_archive 会自动加 - os.makedirs("output/tmp", exist_ok=True) # 确保 tmp 存在 + input_dir = os.path.join(BASE_DIR, "output", timestamp) + output_dir = os.path.join(BASE_DIR, "output/tmp", timestamp) # 不要加 .zip,make_archive 会自动加 + os.makedirs(BASE_DIR / "output/tmp", exist_ok=True) # 确保 tmp 存在 shutil.make_archive(output_dir, 'zip', input_dir) print(f"压缩完成: {output_dir}.zip") return send_from_directory("output/tmp", f"{timestamp}.zip", as_attachment=True) - @app.post("/upload") def upload(): - ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + """ + 接收上传的文件,并将其发送给cellpose。 + :return: + """ + + # 从请求中获取参数,若没有则设定为默认值 + model = request.args.get("model") or request.form.get("model") or "cpsam" + + def _to_float(x, default): + try: + return float(x) + except (TypeError, ValueError): + return default + + 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"), + 0.0) + 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 + + # 将文件保存在本地目录中 + ts = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + f"-{int(time.time()*1000)%1000:03d}" os.makedirs(UPLOAD_DIR / ts, exist_ok=True) files = request.files.getlist("files") saved = [] @@ -42,5 +77,41 @@ def upload(): continue name = secure_filename(f.filename) f.save(os.path.join(UPLOAD_DIR / ts, name)) - saved.append(name) - return jsonify({"ok": True, "count": len(saved), "files": saved, "id": ts}) \ No newline at end of file + saved.append(os.path.join(UPLOAD_DIR, ts, name)) + + # 新建一个线程,防止返回被阻塞 + def job(): + return asyncio.run(Cprun.run( + images=saved, model=model, + cellprob_threshold=cellprob_threshold, + flow_threshold=flow_threshold, + diameter=diameter, time=ts + )) + + # 将线程状态存入redis + set_status(ts, "running") + fut = executor.submit(job) + + def done_cb(f): + try: + f.result() + set_status(ts, "success") + except Exception as e: + set_status(ts, "failed", error=str(e)) + + fut.add_done_callback(done_cb) + + return jsonify({"ok": True, "count": len(saved), "id": ts}) + +@app.get("/status/") +def status(): + """ + 检查某一cellpose任务是否完成 + + :return: + """ + task_id = request.args.get('id') + st = get_status(task_id) + if not st: + return jsonify({"ok": True, "exists": False, "status": "not_found"}), 200 + return jsonify({"ok": True, "exists": True, **st}), 200 diff --git a/backend/main.py b/backend/main.py index f6ff9fa..be2bcba 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,10 +1,10 @@ from cp_run import Cprun -from flaskApp import run_flask +from flaskApp import run_dev from multiprocessing import Process if __name__ == "__main__": # Cprun.run_test() - p = Process(target=run_flask) + p = Process(target=run_dev) p.start() print(f"Flask running in PID {p.pid}") \ No newline at end of file diff --git a/frontend/preview.html b/frontend/preview.html index d60ced9..6278803 100644 --- a/frontend/preview.html +++ b/frontend/preview.html @@ -1,3 +1,37 @@ - -

This is preview

\ No newline at end of file + +

This is preview

+ + + + diff --git a/backend/requirements.txt b/requirements.txt similarity index 60% rename from backend/requirements.txt rename to requirements.txt index 3ddb55f..f94249a 100644 --- a/backend/requirements.txt +++ b/requirements.txt @@ -2,5 +2,7 @@ numpy~=2.1.2 cellpose~=4.0.6 pillow~=11.0.0 sympy~=1.13.3 +redis~=6.4.0 flask~=3.1.2 -werkzeug~=3.1.3 \ No newline at end of file +werkzeug~=3.1.3 +flask-cors~=6.0.1 \ No newline at end of file