diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f4b54ce --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.pytest_cache +__pycache__ +*.pyc +*.pyo +*.pyd +.DS_Store +dist +release_packages +packaging_build +storage/jobs +storage/uploads +storage/**/*.raw.mp4 +*.tar.gz +*.zip diff --git a/.gitignore b/.gitignore index 1e99008..a4ec699 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,11 @@ __pycache__/ .ruff_cache/ .venv/ venv/ + +data/ storage/jobs/ storage/uploads/ storage/demos/ +packaging_build/ +release_packages/ *.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..193a30d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_COMPILE=1 \ + PORT=8001 + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ffmpeg \ + libgl1 \ + libglib2.0-0 \ + libgomp1 \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements-docker.txt ./ +RUN pip install --no-cache-dir --no-compile -r requirements-docker.txt + +COPY backend ./backend +COPY frontend ./frontend +COPY scripts ./scripts +COPY storage/samples ./storage/samples + +RUN mkdir -p /app/storage/uploads /app/storage/jobs + +EXPOSE 8001 + +HEALTHCHECK --interval=10s --timeout=5s --retries=6 --start-period=20s \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8001/api/health', timeout=3).read()" + +CMD ["sh", "-c", "python -m uvicorn backend.main:app --host 0.0.0.0 --port ${PORT:-8001}"] diff --git a/deploy/README_CLEAN_INSTALL.md b/deploy/README_CLEAN_INSTALL.md new file mode 100644 index 0000000..da04584 --- /dev/null +++ b/deploy/README_CLEAN_INSTALL.md @@ -0,0 +1,38 @@ +# ISISeg 纯净安装包 + +本包用于新环境部署。网络相关信息都在 `docker_compose.yaml` 中以 `TODO` 注释和环境变量形式保留,请先按目标环境填写。 + +## 启动应用 + +```bash +docker compose -f docker_compose.yaml up -d --build +``` + +默认本机端口为 `10004`,可通过环境变量调整: + +```bash +ISISEG_HOST_PORT=8001 docker compose -f docker_compose.yaml up -d --build +``` + +## 启用 frpc + +先填写 `docker_compose.yaml` 中: + +- `FRP_SERVER_ADDR` +- `FRP_SERVER_PORT` +- `FRP_AUTH_TOKEN` +- `FRP_PROXY_NAME` +- `FRP_REMOTE_PORT` + +然后运行: + +```bash +docker compose -f docker_compose.yaml --profile frpc up -d --build +``` + +## 数据目录 + +- `data/uploads` +- `data/jobs` + +内置样例视频已包含在镜像构建上下文中。 diff --git a/deploy/README_DOCKER.md b/deploy/README_DOCKER.md new file mode 100644 index 0000000..af0e86b --- /dev/null +++ b/deploy/README_DOCKER.md @@ -0,0 +1,48 @@ +# ISISeg Docker 部署说明 + +## 纯净模板 + +使用仓库根目录 `docker_compose.yaml`: + +```bash +docker compose -f docker_compose.yaml up -d --build +``` + +如需启用 frpc,先填写 compose 中 `FRP_SERVER_ADDR`、`FRP_AUTH_TOKEN`、`FRP_REMOTE_PORT` 等待填写项,然后运行: + +```bash +docker compose -f docker_compose.yaml --profile frpc up -d --build +``` + +## huijutec 直接运行版 + +使用 `docker_compose_huijutec.yaml`: + +```bash +docker compose -f docker_compose_huijutec.yaml up -d --build +``` + +默认本机端口和 frpc 远端端口均为 `10004`,公网域名为 `https://isiseg.huijutec.cn`。 + +## QNAP NAS 版 + +将安装包解压到: + +```text +/share/Container/ISISeg +``` + +在 QTS Container Station 中选择 `docker_compose_Nas.yaml` 运行。该文件已使用绝对路径、`192.168.31.7:7893` 代理和 frpc `10004` 远端端口。 + +## 数据目录 + +运行数据默认挂载到: + +- `data/uploads` +- `data/jobs` + +内置样例视频保留在镜像内的 `storage/samples`。 + +## 依赖说明 + +Docker 镜像使用 `requirements-docker.txt`,其中 OpenCV 使用 headless 版本,避免在 NAS 或精简 Linux 镜像中额外安装 GUI 依赖。源码开发环境仍可使用 `requirements.txt`。 diff --git a/deploy/README_HUIJUTEC_INSTALL.md b/deploy/README_HUIJUTEC_INSTALL.md new file mode 100644 index 0000000..2df8fb8 --- /dev/null +++ b/deploy/README_HUIJUTEC_INSTALL.md @@ -0,0 +1,27 @@ +# ISISeg huijutec 直接运行安装包 + +本包已预置: + +- 公网访问域名:`https://isiseg.huijutec.cn` +- 本机端口:`10004` +- frpc 远端端口:`10004` +- frpc 服务端:`82.157.255.195:7000` + +## 启动 + +```bash +docker compose -f docker_compose.yaml up -d --build +``` + +启动后,frpc 会将容器内 `isiseg_app:8001` 映射到公网 frp 服务器的 `10004` 端口。按你给的 Nginx Proxy Manager 配置,`isiseg.huijutec.cn` 转发到 `172.17.0.1:10004` 后即可访问。 + +## 本机检查 + +```bash +curl http://127.0.0.1:10004/api/health +``` + +## 数据目录 + +- `data/uploads` +- `data/jobs` diff --git a/deploy/README_QNAP_INSTALL.md b/deploy/README_QNAP_INSTALL.md new file mode 100644 index 0000000..0aad073 --- /dev/null +++ b/deploy/README_QNAP_INSTALL.md @@ -0,0 +1,36 @@ +# ISISeg QNAP NAS 安装包 + +本包适用于 QNAP QTS / Container Station,已预置: + +- 项目路径:`/share/Container/ISISeg` +- 公网访问域名:`https://isiseg.huijutec.cn` +- 本机端口:`10004` +- frpc 远端端口:`10004` +- 构建代理:`http://192.168.31.7:7893` + +## 安装步骤 + +1. 将安装包解压到: + + ```text + /share/Container/ISISeg + ``` + +2. 在 Container Station 中使用 `docker_compose_Nas.yaml` 创建应用。 + +3. 启动后访问: + + ```text + https://isiseg.huijutec.cn + ``` + +## 数据目录 + +- `/share/Container/ISISeg/data/uploads` +- `/share/Container/ISISeg/data/jobs` + +## 本地检查 + +```bash +curl http://127.0.0.1:10004/api/health +``` diff --git a/docker_compose.yaml b/docker_compose.yaml new file mode 100644 index 0000000..85d0f82 --- /dev/null +++ b/docker_compose.yaml @@ -0,0 +1,76 @@ +# ISISeg clean deployment template. +# Copy this file as docker_compose.yaml, then fill the TODO values below. +# Start app only: +# docker compose -f docker_compose.yaml up -d --build +# Start app + frpc: +# docker compose -f docker_compose.yaml --profile frpc up -d --build + +name: isiseg + +services: + isiseg_app: + image: isiseg/app:latest + build: + context: . + dockerfile: Dockerfile + # TODO: Uncomment args only if this host needs a proxy during docker build. + # args: + # HTTP_PROXY: http://: + # HTTPS_PROXY: http://: + # NO_PROXY: localhost,127.0.0.1,,isiseg_app + restart: unless-stopped + environment: + TZ: Asia/Shanghai + PORT: 8001 + # TODO: Fill with your public URL, for example https://isiseg.example.com + PUBLIC_URL: "" + ports: + # TODO: Change host port if 10004 is occupied. + - "${ISISEG_HOST_PORT:-10004}:8001" + volumes: + - ./data/uploads:/app/storage/uploads + - ./data/jobs:/app/storage/jobs + healthcheck: + test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8001/api/health', timeout=3).read()\""] + interval: 10s + timeout: 5s + retries: 12 + start_period: 20s + + isiseg_frpc: + # TODO: Remove profiles if you want frpc to start by default. + profiles: ["frpc"] + image: snowdreamtech/frpc:latest + restart: unless-stopped + environment: + # TODO: Fill these values before enabling frpc. + FRP_SERVER_ADDR: "" + FRP_SERVER_PORT: "7000" + FRP_AUTH_TOKEN: "" + FRP_PROXY_NAME: "ISISeg" + FRP_REMOTE_PORT: "10004" + entrypoint: ["/bin/sh"] + command: + - -c + - | + cat > /tmp/frpc.toml < /tmp/frpc.toml <<'EOF' + serverAddr = "82.157.255.195" + serverPort = 7000 + + auth.method = "token" + auth.token = "en.xjtu.edu.cn" + + transport.poolCount = 5 + transport.heartbeatTimeout = -1 + + [[proxies]] + name = "ISISeg_QNAP" + type = "tcp" + localIP = "isiseg_app" + localPort = 8001 + remotePort = 10004 + EOF + exec frpc -c /tmp/frpc.toml + depends_on: + isiseg_app: + condition: service_healthy diff --git a/docker_compose_huijutec.yaml b/docker_compose_huijutec.yaml new file mode 100644 index 0000000..0ccc2a1 --- /dev/null +++ b/docker_compose_huijutec.yaml @@ -0,0 +1,58 @@ +# ISISeg huijutec.cn direct deployment. +# This file is prefilled for https://isiseg.huijutec.cn and frpc remotePort 10004. +# Run: +# docker compose -f docker_compose_huijutec.yaml up -d --build + +name: isiseg_huijutec + +services: + isiseg_app: + image: isiseg/app:huijutec-20260518 + build: + context: . + dockerfile: Dockerfile + restart: unless-stopped + environment: + TZ: Asia/Shanghai + PORT: 8001 + PUBLIC_URL: https://isiseg.huijutec.cn + ports: + - "10004:8001" + volumes: + - ./data/uploads:/app/storage/uploads + - ./data/jobs:/app/storage/jobs + healthcheck: + test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8001/api/health', timeout=3).read()\""] + interval: 10s + timeout: 5s + retries: 12 + start_period: 20s + + isiseg_frpc: + image: snowdreamtech/frpc:latest + restart: unless-stopped + entrypoint: ["/bin/sh"] + command: + - -c + - | + cat > /tmp/frpc.toml <<'EOF' + serverAddr = "82.157.255.195" + serverPort = 7000 + + auth.method = "token" + auth.token = "en.xjtu.edu.cn" + + transport.poolCount = 5 + transport.heartbeatTimeout = -1 + + [[proxies]] + name = "ISISeg" + type = "tcp" + localIP = "isiseg_app" + localPort = 8001 + remotePort = 10004 + EOF + exec frpc -c /tmp/frpc.toml + depends_on: + isiseg_app: + condition: service_healthy diff --git a/requirements-docker.txt b/requirements-docker.txt new file mode 100644 index 0000000..0d60626 --- /dev/null +++ b/requirements-docker.txt @@ -0,0 +1,7 @@ +fastapi==0.116.1 +uvicorn[standard]==0.35.0 +python-multipart==0.0.20 +opencv-python-headless==4.13.0.92 +numpy==2.4.4 +scikit-image==0.26.0 +pillow==12.2.0 diff --git a/工程分析/实现方案-2026-05-18-22-23-07.md b/工程分析/实现方案-2026-05-18-22-23-07.md new file mode 100644 index 0000000..468722b --- /dev/null +++ b/工程分析/实现方案-2026-05-18-22-23-07.md @@ -0,0 +1,55 @@ +# 实现方案 + +开始时间:2026-05-18-22-23-07 + +## 容器化 + +1. 新增 `Dockerfile` + - 基于 `python:3.12-slim`。 + - 安装 `ffmpeg`、`libgl1`、`libglib2.0-0`、`libgomp1`。 + - 安装 Docker 专用 `requirements-docker.txt`,OpenCV 使用 headless 版本。 + - 复制 `backend`、`frontend`、`storage/samples`、`scripts`。 + - 默认监听 `8001`。 + +2. 新增 `requirements-docker.txt` + - 保留 FastAPI、Uvicorn、NumPy、scikit-image、Pillow 等运行依赖。 + - 使用 `opencv-python-headless==4.13.0.92`,避免容器中额外 GUI 依赖和构建不稳定。 + +3. 新增 `.dockerignore` 和扩展 `.gitignore` + - 排除 `.git`、缓存、测试产物、`storage/jobs`、`storage/uploads`、打包目录。 + +## Compose 文件 + +1. `docker_compose.yaml` + - 纯净模板。 + - app 服务可直接构建启动。 + - frpc 服务放在 `frpc` profile 中。 + - 域名、FRP 地址、token、远端端口等以注释和环境变量形式标记为待填写。 + +2. `docker_compose_huijutec.yaml` + - 直接运行版。 + - app 端口映射 `10004:8001`。 + - frpc 连接用户参考配置中的服务器和 token,`remotePort = 10004`。 + - 预置 `PUBLIC_URL=https://isiseg.huijutec.cn`。 + +3. `docker_compose_Nas.yaml` + - QNAP 版。 + - build context 使用 `/share/Container/ISISeg`。 + - 数据目录使用 `/share/Container/ISISeg/data/...`。 + - 代理使用 `192.168.31.7:7893`。 + - frpc 远端端口 `10004`。 + +## 安装包 + +- `isiseg-clean-install-2026-05-18-22-23-07.tar.gz` +- `isiseg-huijutec-direct-2026-05-18-22-23-07.tar.gz` +- `isiseg-qnap-nas-2026-05-18-22-23-07.tar.gz` + +每个包包含运行所需源码、Dockerfile、compose 文件和安装说明。 + +## Gitea 发布 + +- 提交代码到 main。 +- 创建 tag:`v2026-05-18-22-23-07-docker-packages`。 +- 调用 Gitea API 创建 release。 +- 上传三个 tar.gz 作为 release 附件。 diff --git a/工程分析/测试方案-2026-05-18-22-23-07.md b/工程分析/测试方案-2026-05-18-22-23-07.md new file mode 100644 index 0000000..424b895 --- /dev/null +++ b/工程分析/测试方案-2026-05-18-22-23-07.md @@ -0,0 +1,50 @@ +# 测试方案 + +开始时间:2026-05-18-22-23-07 + +## 自动化测试 + +- `python3 -m compileall backend tests` +- `node --check frontend/app.js` +- `pytest -q` + +执行结果:全部通过,`pytest -q` 为 `5 passed`。 + +## Docker/Compose 校验 + +- `docker compose -f docker_compose.yaml config` +- `docker compose -f docker_compose.yaml --profile frpc config` +- `docker compose -f docker_compose_huijutec.yaml config` +- `docker compose -f docker_compose_Nas.yaml config` +- `docker build -t isiseg:docker-package-test .` +- 运行容器并请求 `/api/health`。 + +执行结果: + +- 三个 compose 文件及纯净模板 frpc profile 均可解析。 +- Docker 镜像构建通过。 +- 测试容器 `/api/health` 返回 `{"status":"ok","service":"ISISeg","version":"0.1.0"}`。 +- 首次使用完整 `requirements.txt` 构建时,`opencv-python` 安装阶段触发段错误;已改为 Docker 专用 `requirements-docker.txt`,使用 `opencv-python-headless` 并保留运行时所需依赖。 + +## 安装包校验 + +- 检查三个 tar.gz 都存在。 +- 分别列出包内文件,确认包含 compose、Dockerfile、backend、frontend、样例视频和 README。 +- 解包后分别执行 compose config 校验。 +- 确认安装包内不包含 `__pycache__` 或 `.pyc`。 + +执行结果: + +| 安装包 | SHA256 | +| --- | --- | +| `isiseg-clean-install-2026-05-18-22-23-07.tar.gz` | `dc62b411877271375b91833a48c933ff1201f87f9710626f4ba637c04eb039d1` | +| `isiseg-huijutec-direct-2026-05-18-22-23-07.tar.gz` | `84df7476152dd98a44b5b6479bd713f639bd12485ee4385559c5571dfba8850e` | +| `isiseg-qnap-nas-2026-05-18-22-23-07.tar.gz` | `f31bb0bc290b0d3e8f68721c16c638964dc80f6ac1a0b4d11063e4dcf80a71dd` | + +## Gitea 发布校验 + +- push main。 +- push tag。 +- 创建 release。 +- 上传三个附件。 +- 查询 release 附件列表确认三个包均存在。 diff --git a/工程分析/经验记录.md b/工程分析/经验记录.md index 8de6a02..01870eb 100644 --- a/工程分析/经验记录.md +++ b/工程分析/经验记录.md @@ -175,3 +175,35 @@ B. 产生问题原因:上一版使用“最近一次分割掩膜持续叠加 C. 解决问题方案:后端生成右侧完整结果视频时改为每一帧都运行当前方法分割;`max_frames` 只控制下方结果卡片保存数量,不再控制主结果视频是否分割。关键帧卡片与视频对应帧用像素差测试校验一致性。 D. 后续如何避免问题:用于播放的结果视频必须逐帧使用当前帧结果;抽帧限制只能用于结果列表、调参预览或摘要,不应用来偷换主视频的逐帧分割语义。 + +## 2026-05-18-22-23-07 Docker 安装包与发布 + +### 1. Docker 构建阶段 OpenCV 安装段错误 + +A. 具体问题:用完整 `requirements.txt` 构建 Docker 镜像时,`pip install` 在安装 `opencv-python` 相关依赖后发生段错误,镜像无法稳定构建。 + +B. 产生问题原因:服务端容器只需要无界面 OpenCV 运行时,完整 `opencv-python` 轮子会携带 GUI 相关依赖,构建阶段资源和二进制兼容风险更高。 + +C. 解决问题方案:新增 `requirements-docker.txt`,将容器依赖收敛为运行时所需库,并使用 `opencv-python-headless==4.13.0.92`;Dockerfile 使用 `--no-cache-dir --no-compile` 安装,最终构建通过。 + +D. 后续如何避免问题:服务端 Docker 镜像优先使用 headless 图像处理依赖,并将开发依赖与容器运行依赖拆分,避免把本地开发包完整带入生产镜像。 + +### 2. Compose 模板注释块导致配置解析失败 + +A. 具体问题:纯净模板最初只注释了 build args 的值,保留了空的 `args:`,`docker compose config` 解析失败。 + +B. 产生问题原因:YAML 中空配置节点不是注释说明,Compose 会按实际配置校验类型。 + +C. 解决问题方案:将整个可选 `args:` 块都注释掉,只保留待填写说明;需要代理时再整体取消注释。 + +D. 后续如何避免问题:带 TODO 的 Compose 模板必须用 `docker compose config` 同时校验默认路径和可选 profile,确保注释示例不会变成无效配置。 + +### 3. 安装包夹带 Python 缓存 + +A. 具体问题:首次打包后安装包内包含 `__pycache__` 和 `.pyc`,不符合纯净安装包预期。 + +B. 产生问题原因:自动化测试和 Docker 构建前已运行 Python 代码,工作目录中生成了缓存文件,打包清单没有排除。 + +C. 解决问题方案:重新打包时排除 `__pycache__`、`.pyc`、临时目录和发布目录,并新增 `.gitignore` 避免后续误提交。 + +D. 后续如何避免问题:发布包生成后必须执行包内文件检查,至少确认无缓存、无临时目录、无本地数据和无敏感配置外泄。 diff --git a/工程分析/需求分析-2026-05-18-22-23-07.md b/工程分析/需求分析-2026-05-18-22-23-07.md new file mode 100644 index 0000000..09f763d --- /dev/null +++ b/工程分析/需求分析-2026-05-18-22-23-07.md @@ -0,0 +1,25 @@ +# 需求分析 + +开始时间:2026-05-18-22-23-07 + +## 用户需求 + +1. 系统可以通过 `docker_compose.yaml` 安装。 +2. 打包一个纯净安装包: + - 网络相关信息保留注释,作为待填写项。 + - 适合迁移到任意环境后再配置域名、端口、FRP。 +3. 打包一个可直接运行安装包: + - 预置 `isiseg.huijutec.cn`。 + - 参考用户提供的 frpc 配置。 + - frpc 远端映射端口使用 `10004`。 +4. 打包一个威联通 NAS 版安装包: + - 提供 `docker_compose_Nas.yaml`。 + - 参考 QNAP QTS / Container Station 的绝对路径和代理写法。 + - 端口和 frpc 映射与直接运行版保持一致。 +5. 三个安装包都需要在 Gitea 发布。 + +## 约束 + +- 当前项目是 FastAPI 后端直接挂载静态前端,不需要单独 nginx。 +- OpenCV 和视频转码依赖 `ffmpeg`、系统图形/运行库,需要 Docker 镜像补齐。 +- 应保留内置样例视频,但不要把运行产生的 jobs/uploads 打进安装包。