annodesk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. annodesk-0.1.0/.gitignore +27 -0
  2. annodesk-0.1.0/PKG-INFO +321 -0
  3. annodesk-0.1.0/README.md +61 -0
  4. annodesk-0.1.0/docs/specs/annodesk.md +217 -0
  5. annodesk-0.1.0/docs/user-guide.md +303 -0
  6. annodesk-0.1.0/frontend/index.html +13 -0
  7. annodesk-0.1.0/frontend/package-lock.json +2109 -0
  8. annodesk-0.1.0/frontend/package.json +25 -0
  9. annodesk-0.1.0/frontend/public/annodesk.svg +8 -0
  10. annodesk-0.1.0/frontend/src/App.vue +767 -0
  11. annodesk-0.1.0/frontend/src/api.ts +108 -0
  12. annodesk-0.1.0/frontend/src/components/AnnotationCanvas.vue +340 -0
  13. annodesk-0.1.0/frontend/src/components/ClassPalette.vue +64 -0
  14. annodesk-0.1.0/frontend/src/components/ClassPicker.vue +116 -0
  15. annodesk-0.1.0/frontend/src/components/FastTooltip.vue +116 -0
  16. annodesk-0.1.0/frontend/src/components/FilmStrip.vue +71 -0
  17. annodesk-0.1.0/frontend/src/components/Icon.vue +27 -0
  18. annodesk-0.1.0/frontend/src/components/ImageList.vue +110 -0
  19. annodesk-0.1.0/frontend/src/components/ThumbImage.vue +26 -0
  20. annodesk-0.1.0/frontend/src/main.ts +5 -0
  21. annodesk-0.1.0/frontend/src/review.ts +27 -0
  22. annodesk-0.1.0/frontend/src/stores/project.test.ts +202 -0
  23. annodesk-0.1.0/frontend/src/stores/project.ts +288 -0
  24. annodesk-0.1.0/frontend/src/style.css +613 -0
  25. annodesk-0.1.0/frontend/src/thumbnails.ts +73 -0
  26. annodesk-0.1.0/frontend/tsconfig.json +15 -0
  27. annodesk-0.1.0/frontend/vite.config.ts +12 -0
  28. annodesk-0.1.0/models.json +8 -0
  29. annodesk-0.1.0/pyproject.toml +46 -0
  30. annodesk-0.1.0/src/annodesk/__init__.py +3 -0
  31. annodesk-0.1.0/src/annodesk/__main__.py +3 -0
  32. annodesk-0.1.0/src/annodesk/app.py +83 -0
  33. annodesk-0.1.0/src/annodesk/batch.py +39 -0
  34. annodesk-0.1.0/src/annodesk/cli.py +136 -0
  35. annodesk-0.1.0/src/annodesk/colors.py +52 -0
  36. annodesk-0.1.0/src/annodesk/domain.py +52 -0
  37. annodesk-0.1.0/src/annodesk/inference.py +161 -0
  38. annodesk-0.1.0/src/annodesk/jobs.py +76 -0
  39. annodesk-0.1.0/src/annodesk/lifecycle.py +256 -0
  40. annodesk-0.1.0/src/annodesk/masks.py +59 -0
  41. annodesk-0.1.0/src/annodesk/models.py +142 -0
  42. annodesk-0.1.0/src/annodesk/offline.py +46 -0
  43. annodesk-0.1.0/src/annodesk/routes.py +247 -0
  44. annodesk-0.1.0/src/annodesk/storage.py +320 -0
  45. annodesk-0.1.0/src/annodesk/web_dist/annodesk.svg +8 -0
  46. annodesk-0.1.0/src/annodesk/web_dist/assets/index-BtictWGJ.css +1 -0
  47. annodesk-0.1.0/src/annodesk/web_dist/assets/index-DWjHULom.js +662 -0
  48. annodesk-0.1.0/src/annodesk/web_dist/index.html +14 -0
  49. annodesk-0.1.0/tests/test_api.py +49 -0
  50. annodesk-0.1.0/tests/test_batch.py +93 -0
  51. annodesk-0.1.0/tests/test_cli.py +69 -0
  52. annodesk-0.1.0/tests/test_inference.py +49 -0
  53. annodesk-0.1.0/tests/test_jobs.py +38 -0
  54. annodesk-0.1.0/tests/test_lifecycle.py +59 -0
  55. annodesk-0.1.0/tests/test_models.py +112 -0
  56. annodesk-0.1.0/tests/test_storage.py +74 -0
  57. annodesk-0.1.0/uv.lock +2171 -0
@@ -0,0 +1,27 @@
1
+ .venv/
2
+ .hfms-bridge/
3
+ __pycache__/
4
+ *.py[cod]
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ *.egg-info/
9
+ .env
10
+
11
+ # OS
12
+ .DS_Store
13
+
14
+ # IDE
15
+ .vscode
16
+
17
+ # Other
18
+ # Design history, disposable prototypes, and local execution plans
19
+ docs/superpowers/
20
+ docs/design-previews/
21
+ docs/plans/
22
+ .superpowers/
23
+ frontend/node_modules/
24
+ frontend/dist/
25
+ *.safetensors
26
+ *.pt
27
+ *.pth
@@ -0,0 +1,321 @@
1
+ Metadata-Version: 2.5
2
+ Name: annodesk
3
+ Version: 0.1.0
4
+ Summary: A local-first image annotation workbench for boxes and masks
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: fastapi<1,>=0.115
7
+ Requires-Dist: numpy<3,>=2
8
+ Requires-Dist: pillow<13,>=11
9
+ Requires-Dist: platformdirs<5,>=4
10
+ Requires-Dist: pycocotools<3,>=2.0.8
11
+ Requires-Dist: pydantic<3,>=2.10
12
+ Requires-Dist: pyyaml<7,>=6
13
+ Requires-Dist: tyro<2,>=0.9
14
+ Requires-Dist: uvicorn<1,>=0.34
15
+ Provides-Extra: inference
16
+ Requires-Dist: ultralytics<9,>=8.3; extra == 'inference'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # AnnoDesk 用户指南
20
+
21
+ AnnoDesk 用于标注图像中的检测框和实例分割 mask,可导入已有 YOLO 检测标签,再根据框生成分割结果并人工复核。界面为英文、浅色主题,在浏览器中使用;图片、模型和标注数据保存在运行后端的电脑上。
22
+
23
+ 本指南面向安装 wheel 的使用者,不需要源码仓库、Node.js 或前端构建工具。
24
+
25
+ ## 1. 安装
26
+
27
+ 需要 Python 3.11 或更新版本。建议使用独立 Python 环境:
28
+
29
+ ```sh
30
+ python -m venv .venv
31
+ ```
32
+
33
+ macOS / Linux 激活环境:
34
+
35
+ ```sh
36
+ source .venv/bin/activate
37
+ ```
38
+
39
+ Windows PowerShell 激活环境:
40
+
41
+ ```powershell
42
+ .\.venv\Scripts\Activate.ps1
43
+ ```
44
+
45
+ 安装拿到的 wheel;文件名中的版本号以实际发布文件为准:
46
+
47
+ ```sh
48
+ python -m pip install ./annodesk-0.1.0-py3-none-any.whl
49
+ ```
50
+
51
+ 基础安装即可使用框标注、类别、审核状态和数据管理。若需要 SAM 推理,显式安装可选依赖:
52
+
53
+ ```sh
54
+ python -m pip install "./annodesk-0.1.0-py3-none-any.whl[inference]"
55
+ annodesk doctor
56
+ ```
57
+
58
+ `doctor` 只检查 torch / ultralytics 是否安装,不验证权重、显卡驱动或推理结果。PyTorch 的 GPU 环境需根据实际机器自行准备;SAM 3 文本推理还需要对应的本地 tokenizer / runtime 依赖。
59
+
60
+ 安装依赖可能访问 Python 包索引;模型推理不会自动下载权重,也不会自动安装缺失依赖。
61
+
62
+ ## 2. 启动和打开数据集
63
+
64
+ ```sh
65
+ annodesk serve --project /data/my-dataset --open
66
+ ```
67
+
68
+ 默认地址为 `http://127.0.0.1:8765`。`--open` 请求打开浏览器;也可手动访问该地址。终端保持运行,按 Ctrl+C 停止服务。
69
+
70
+ 也可先启动,再在页面点击 **Open project…** 输入路径:
71
+
72
+ ```sh
73
+ annodesk serve
74
+ ```
75
+
76
+ 未打开项目时显示空白工作台属于正常状态。路径始终指向后端电脑,不是浏览器电脑。端口被占用时可换端口:
77
+
78
+ ```sh
79
+ annodesk serve --port 8766
80
+ ```
81
+
82
+ 同一数据集目前请只运行一个后端进程,避免多处同时修改。
83
+
84
+ ## 3. 准备数据集
85
+
86
+ 推荐工作目录结构:
87
+
88
+ ```text
89
+ my-dataset/
90
+ ├── images/
91
+ │ ├── train/camera_a/001.jpg
92
+ │ └── val/camera_b/002.jpg
93
+ ├── labels/ # YOLO detection 标签;可选
94
+ ├── labels_seg/ # YOLO segmentation 标签;可选
95
+ └── data.yaml # 类别名称;可选
96
+ ```
97
+
98
+ `train` / `val` / `test` 不是打开项目的必要条件,允许多级子目录。标签路径与图片在 `images/` 下的相对路径对应,例如:
99
+
100
+ ```text
101
+ images/train/camera_a/001.jpg
102
+ labels/train/camera_a/001.txt
103
+ labels_seg/train/camera_a/001.txt
104
+ ```
105
+
106
+ 图片可以尚无标签;没有 `data.yaml` 时,AnnoDesk 会创建它。已有标签但缺少类别名时初始化为 `class_0` 等占位名称。也允许直接打开图片目录;此时标签和 `.annodesk/` 会写到这个目录下,建议使用独立工作目录。
107
+
108
+ 同一目录下的 `001.jpg` 与 `001.png` 会争用同一个标签文件,需先重命名。导入已有 detection 和 segmentation 标签时,目前会分别创建实例,不自动匹配合并。
109
+
110
+ ### 工作文件与备份
111
+
112
+ - `labels/`:显式画出的检测框;mask-only 标注不会凭空生成框。
113
+ - `labels_seg/`:分割结果的 YOLO polygon 投影。
114
+ - `.annodesk/`:实例、无损 mask RLE、审核状态、提示、任务进度和恢复信息。
115
+ - `data.yaml`:类别名称等数据集配置。
116
+
117
+ 备份时保留整个工作目录,尤其不要遗漏 `.annodesk/`。YOLO polygon 不能完整表达孔洞,不能替代内部无损 mask 数据。项目已打开时不要同时用其他工具修改标签;检测到外部修改后,保存会报冲突,需要先备份并核对文件。
118
+
119
+ ## 4. 配置本地 SAM 模型
120
+
121
+ 创建自己的 `models.json`,例如:
122
+
123
+ ```json
124
+ {
125
+ "default_model": "sam2-small",
126
+ "models": [
127
+ {
128
+ "id": "sam2-small",
129
+ "name": "SAM 2.1 Small",
130
+ "backend": "sam2",
131
+ "source": "facebook/sam2.1-hiera-small"
132
+ },
133
+ {
134
+ "id": "mobile",
135
+ "name": "MobileSAM",
136
+ "backend": "mobile_sam",
137
+ "source": "/srv/models/MobileSAM"
138
+ }
139
+ ]
140
+ }
141
+ ```
142
+
143
+ - `id`:唯一标识,供 `--model` 和默认模型引用。
144
+ - `name`:页面显示名称。
145
+ - `backend`:`sam`、`sam2`、`mobile_sam` 或 `sam3`。
146
+ - `source`:模型的最终一级文件夹,不填写 `.pt` / `.safetensors` 文件,也不填写 `checkpoint` 字段。
147
+
148
+ ### 目录查找规则
149
+
150
+ 展开 `~` 和环境变量后:
151
+
152
+ 1. 绝对路径直接检查该目录,缺失则报错,不添加前缀。
153
+ 2. 相对路径先相对 JSON 所在目录解析,存在目录则使用。
154
+ 3. 相对目录不存在时,再检查 `$HF_MODELS/<source>`;`HF_MODELS` 未设置则不回退。
155
+
156
+ repo ID 与相对目录采用相同规则,不访问 Hugging Face。例如 JSON 位于 `/work/models.json`,`source` 为 `facebook/sam2.1-hiera-small`,先检查 `/work/facebook/sam2.1-hiera-small`,再检查 `$HF_MODELS/facebook/sam2.1-hiera-small`。
157
+
158
+ macOS / Linux 可设置:
159
+
160
+ ```sh
161
+ export HF_MODELS=/srv/models
162
+ ```
163
+
164
+ Windows PowerShell 可设置:
165
+
166
+ ```powershell
167
+ $env:HF_MODELS = "D:\models"
168
+ ```
169
+
170
+ 适配器在所选目录的顶层查找权重,优先匹配该 backend 的已知 `.pt` 文件名;没有已知命名时,只接受唯一的 `.pt` 文件。存在多个候选会报错,请每个目录只放一个模型。
171
+
172
+ **当前适配器使用 Ultralytics 兼容的 `.pt` 权重。** 目录来自 Hugging Face 并不代表格式一定兼容;仅含 safetensors 的目录暂不支持。既有目录中缺少兼容权重时不会偷偷换目录或下载。真实模型和加速设备兼容性需在目标机器验证。
173
+
174
+ ### 使用配置与切换模型
175
+
176
+ ```sh
177
+ annodesk models list --models-config ./models.json
178
+ annodesk serve --project /data/my-dataset --models-config ./models.json
179
+ ```
180
+
181
+ 配置查找优先级:`--models-config` > `ANNODESK_MODELS_CONFIG` 环境变量 > 当前目录 `models.json` > 源码仓库默认(若存在)> 安装包内置默认。
182
+
183
+ 建议始终显式传入自己的配置文件。页面顶部选择模型,修改 JSON 或放好模型后点击 **Reload**;`(missing)` 表示目录或兼容权重未就绪,指向提示可查看原因。模型目录存在不代表推理依赖已经全部安装。
184
+
185
+ ## 5. 在页面中标注
186
+
187
+ ### 浏览图片和设置类别
188
+
189
+ 左上图片列表支持 **Tree / Flat**、搜索和展开目录;底部缩略图可左右滚动,图片按需加载。拖动面板之间的分隔条可调整比例。
190
+
191
+ 左下 **Class palette** 是赋类入口:
192
+
193
+ - 没有选中实例时,点击类别设置新标注的默认类别。
194
+ - 点击 **Default class** 取消实例选择,回到默认类别设置。
195
+ - 选中框或 mask 后,点击类别直接修改该实例。
196
+ - 按 **C** 打开类别搜索,↑↓ 选择、Enter 确认、Esc 取消;选择模式下双击实例也可打开。
197
+
198
+ 顶部 **Manage classes** 用于增加类别、修改名称和颜色。类别 ID 平时保持稳定;**Reindex IDs…** 会修改整个项目的类别映射和标签文件,使用前备份项目。
199
+
200
+ ### 画框和生成 mask
201
+
202
+ 1. 在 Class palette 中选择类别。
203
+ 2. 使用 **Draw box** 拖出检测框,切回 **Select** 后可移动和拖动控制点调整;右侧也可编辑坐标。
204
+ 3. 选中框后按 Enter,或点击 **Refine mask**,使用当前模型生成 mask。
205
+ 4. **Segment current** 处理当前图片中尚无 mask 的未锁定检测框。
206
+
207
+ 仅需要 mask、不希望保存检测框时,使用 **Box prompt: mask only**,或用正负点建立提示,再执行分割。框提示与显式检测框分开保存。正点用于包含区域,负点用于排除区域。文本提示工具只适用于支持文本提示的 SAM 3 模型,需先设置默认类别。
208
+
209
+ 开启 **Auto-segment** 后,切换到含待处理检测框的图片会自动发起分割。**Batch…** 可对整个项目的已有框进行批量处理,在任务抽屉查看进度、错误和取消/继续操作。取消在当前图片完成后生效,继续只处理未处理图片;失败图片可在问题解决后重新发起批次。
210
+
211
+ 选中实例可在右侧删除、锁定或隐藏。锁定后先解锁才能改类;隐藏只影响显示,不等于从导出中移除。画布支持滚轮局部缩放、平移和 **Fit image**。
212
+
213
+ ### 保存、复核与标签
214
+
215
+ 编辑会自动保存,切换图片前也会保存。底部显示保存状态;失败时查看错误并使用 **Retry save**,确认保存成功后再关闭页面。
216
+
217
+ 右侧审核状态为:
218
+
219
+ | 状态 | 含义 |
220
+ | --- | --- |
221
+ | Not reviewed | 尚未人工复核 |
222
+ | Approved | 当前任务已复核通过 |
223
+ | Needs review | 当前任务需要再次检查 |
224
+ | Excluded | 整张图片不参与处理和导出;不直接删除图片 |
225
+
226
+ Detection 与 segmentation 的前三个审核状态分别记录;Excluded 作用于整张图片,可再次点击取消。编辑标注后可能恢复为未审核,需要重新确认。
227
+
228
+ 图片可添加自定义 tags(逗号分隔)和 notes。顶部 **Tags** 抽屉展示只读的四个内置审核状态,下方管理自定义 tag 名称和颜色。当前自定义 tag 改名不会批量迁移旧图片上的 tag 字符串,需自行核对。
229
+
230
+ ### 导出
231
+
232
+ 先选中 detection 或 segmentation 任务并完成复核,再点击 **Export**。当前页面导出当前任务的 YOLO 数据集,不是只导出当前图片。
233
+
234
+ 默认跳过未审核、Needs review、Excluded 和含未分配类别实例的图片;无对象图片需明确勾选 **Confirm no objects for this task** 并复核通过,才能作为空标签样本导出。
235
+
236
+ 结果位于后端项目的 `.annodesk/exports/<id>/`,页面返回路径。导出目录包含复制的图片、标准 `labels/`、类别配置及报告;分割工作目录中的 `labels_seg/` 在导出副本中转换为标准 `labels/`。导出类别 ID 可能连续重排,工作项目不受影响。
237
+
238
+ YOLO polygon 对孔洞等结构可能有损,请保留原始项目及内部 RLE。当前界面还没有 COCO 导出选项和回收站管理界面。
239
+
240
+ ## 6. 不打开页面,直接将检测框批量分割
241
+
242
+ ```sh
243
+ annodesk segment --project /data/my-dataset --models-config ./models.json --model sam2-small
244
+ ```
245
+
246
+ 省略 `--model` 使用 JSON 中的 `default_model`。该命令不启动 HTTP 服务或浏览器。
247
+
248
+ - 保留 detection 框及类别,分割写入 `labels_seg/`,内部保存无损 RLE。
249
+ - 跳过已有 mask、锁定实例和 Excluded 图片。
250
+ - 每张图片独立保存;中断后再次运行可跳过已完成结果。
251
+ - 控制台输出处理进度,错误报告位于 `.annodesk/segment-report.json`。
252
+ - 成功退出码为 0;有失败图片时为 1。解决原因后可重新运行。
253
+
254
+ 生成结果仍需在页面人工复核,命令不会自动标为 Approved。
255
+
256
+ ## 7. 快捷键
257
+
258
+ 输入框、类别搜索和 IME 输入期间不会触发工作台单键快捷键。页面 **Shortcuts ?** 可搜索快捷键;按钮提示约 150ms 后显示。
259
+
260
+ | 操作 | 按键 |
261
+ | --- | --- |
262
+ | 上一张 / 下一张 | A / D or ← / → |
263
+ | 选择 / 画框 | V / R |
264
+ | 正点 / 负点 | Q / E |
265
+ | mask 框提示 / 文本提示 | B / T |
266
+ | 执行当前实例分割 | Enter |
267
+ | 修改选中实例类别 | C or double-click |
268
+ | 删除选中实例 | X or Delete |
269
+ | 平移 | 按住 Space,或选择 Pan |
270
+ | 适应画布 | F |
271
+ | 放大 / 缩小 | + / - |
272
+ | Approved / Needs review / Excluded | 1 / 2 / 3 |
273
+ | 撤销 | Ctrl+Z or ⌘Z |
274
+ | 重做 | Ctrl+Shift+Z or ⌘Shift+Z |
275
+ | 保存 | Ctrl+S or ⌘S |
276
+ | 查看快捷键 | ? |
277
+
278
+ ## 8. 前后端分开部署
279
+
280
+ 在保存图片、标签及模型的电脑上:
281
+
282
+ ```sh
283
+ annodesk api --host 0.0.0.0 --port 8765 --models-config /config/models.json
284
+ ```
285
+
286
+ 在提供前端页面的电脑上(同样安装基础 wheel):
287
+
288
+ ```sh
289
+ annodesk ui --api-url http://gpu-host:8765 --port 8080
290
+ ```
291
+
292
+ 浏览器打开 `http://前端电脑:8080`。如果需要让其他电脑访问前端,请为 `ui` 增加 `--host 0.0.0.0`。`gpu-host` 必须能从浏览器所在电脑访问。
293
+
294
+ 所有数据路径、模型配置和 `HF_MODELS` 以 API 电脑为准,浏览器电脑上的目录不会自动上传。当前版本没有认证,HTTP 部署限于可信网络。
295
+
296
+ ## 9. 常见问题
297
+
298
+ | 现象 | 处理方式 |
299
+ | --- | --- |
300
+ | 升级后样式丢失、请求旧 CSS / JS 404,或只有某个浏览器异常 | 先保存编辑,重启为新版本后强制刷新(Windows / Linux:Ctrl+Shift+R;macOS:⌘Shift+R);必要时只清除此站点的缓存。不同浏览器的缓存相互独立 |
301
+ | 页面提示 Open project | 打开数据集,或启动时传入 `--project`;不表示服务故障 |
302
+ | 端口占用 | 换一个 `--port`,或停止原来的服务 |
303
+ | 模型显示 missing | 检查 JSON 路径、目录解析、backend 和目录顶层权重;点击 Reload |
304
+ | 提示 No compatible .pt checkpoint | 当前适配器不兼容该目录内容;准备兼容权重,不会自动下载或转换 |
305
+ | Multiple checkpoint candidates | 为每个模型使用独立目录,避免多个候选权重 |
306
+ | 缺少 ultralytics / torch / tokenizer | 在后端环境手动安装对应依赖;运行 `annodesk doctor` 辅助检查 |
307
+ | 保存冲突 | 保留页面和备份,核对是否有其他进程或工具修改同一项目;不要强行覆盖 |
308
+ | 推理完成时已有新编辑 | 页面会保留新编辑并提示需要核对;备份后比较结果,再决定如何重新载入 |
309
+ | 导出结果少于图片数 | 检查当前任务的审核状态、Excluded、未分配类别和空标签确认 |
310
+ | 模型不支持或推理异常 | 基础框标注仍可用;记录模型格式、依赖版本和控制台错误进行排查 |
311
+
312
+ ## 10. 查阅已安装的指南
313
+
314
+ ```sh
315
+ annodesk docs
316
+ annodesk docs --output ./AnnoDesk-user-guide.md
317
+ annodesk --help
318
+ annodesk segment --help
319
+ ```
320
+
321
+ `annodesk docs` 输出随当前安装版本发布的本指南;`--output` 导出 Markdown,目标文件已存在时不会覆盖。升级安装包后,可用该命令获取对应版本的说明。
@@ -0,0 +1,61 @@
1
+ # AnnoDesk
2
+
3
+ A local-first workbench for YOLO detection boxes and instance masks.
4
+
5
+ **Using the installed package:** read the [user guide](docs/user-guide.md).
6
+ It ships inside the wheel and is available with `annodesk docs`. The guide covers
7
+ installation, model directories, annotation, review, export and headless conversion.
8
+ This README is for repository development.
9
+
10
+ ## Design and implementation scope
11
+
12
+ The complete target design is in [docs/specs/annodesk.md](docs/specs/annodesk.md).
13
+ The first implementation is usable, but the full specification is not a completion
14
+ claim. Important remaining items include cross-process project write leases, class
15
+ deletion/migration UI, trash/restore UI, export options/report UI, configurable
16
+ shortcuts, class favorites/virtualization and small-screen inspector access.
17
+
18
+ The Ultralytics model integration has deterministic adapter tests; actual checkpoints,
19
+ SAM 3 dependencies and accelerator compatibility still need target-machine validation.
20
+ Weights are never downloaded implicitly, and are not included in distributions.
21
+
22
+ ## Development setup
23
+
24
+ Python 3.11+ is supported; the repository uses Python 3.13. Install uv and Node/npm
25
+ for development, then:
26
+
27
+ ```sh
28
+ uv sync
29
+ uv run annodesk serve --project /absolute/path/to/dataset
30
+ npm ci --prefix frontend
31
+ npm run dev --prefix frontend
32
+ ```
33
+
34
+ The frontend development server proxies `/api` and runtime UI configuration to port
35
+ 8765. Use `annodesk api` for an API-only process. Keep a single backend writer per
36
+ dataset. Optional inference dependencies are installed with `uv sync --extra inference`.
37
+ User paths, local model preparation and deployment commands are documented in the
38
+ user guide rather than duplicated here.
39
+
40
+ ## Checks and builds
41
+
42
+ ```sh
43
+ uv run ruff format src tests
44
+ uv run ruff check src tests
45
+ uv run pytest
46
+ npm test --prefix frontend
47
+ npm run build --prefix frontend
48
+ uv build
49
+ ```
50
+
51
+ `uv run annodesk build-ui` runs npm installation, type checking and frontend bundling.
52
+ Commit matching `src/annodesk/web_dist` resources with frontend changes: an ordinary
53
+ clone plus `uv sync` can then serve the workbench without Node.
54
+
55
+ Hatchling bundles the frontend, default `models.json` and `docs/user-guide.md` into
56
+ the wheel. The user guide is also the package long description. Validate the wheel
57
+ in an isolated environment from outside the checkout, including `annodesk docs`
58
+ and `annodesk models list`, before publishing. Publication is a separate action.
59
+
60
+ Git tracks source, tests, locks and official documentation. Implementation plans,
61
+ previous prototypes, screenshots, environments, model weights and user data are ignored.
@@ -0,0 +1,217 @@
1
+ # AnnoDesk 设计规范
2
+
3
+ 版本:1.0 · 2026-10-22。本文合并此前讨论,是实现与验收的唯一设计依据;旧草案与交互原型仅供历史参考。
4
+
5
+ ## 1. 产品目标
6
+
7
+ 面向图像数据集的本地优先标注工具,支持检测框和实例分割。从纯图片开始标注,或直接打开 Ultralytics YOLO 框数据集,以 SAM 按框预分割,再人工修改与复核。首版是英文、浅色 Web 工作台;未来可复用前端以 Tauri 包装客户端。
8
+
9
+ 支持单机运行及前后端分机部署。首版不做账号、权限、TLS、多人协作、视频、训练。不要把“浏览器与后端分机”与“本机数据、远端独立推理节点”混为一谈:后者不属于首版。
10
+
11
+ ## 2. 技术与仓库
12
+
13
+ - 前端:Vue 3、TypeScript、Vite、Pinia、Naive UI。Konva 管理框、提示点与选择;独立 Canvas 显示 mask,使用同一图像坐标变换。
14
+ - 后端:Python、FastAPI、Pydantic、Uvicorn;CLI 使用 tyro,通过类型化配置生成子命令,不自写 argparse,不使用 Typer。
15
+ - 模型:可替换适配器,首批目标为 SAM 2.1、MobileSAM、SAM 3;按能力启用正负点、框、文本。SAM 3 文本与视觉提示分开调用,不假设所有 SAM 衍生模型接口相同。
16
+ - 项目根是一个 uv 项目、一个 Python distribution,名称/import/命令统一为 annodesk。使用 src/annodesk、frontend、tests、docs/specs。
17
+ - uv sync 安装 Python 环境;uv.lock 固定依赖。Python 基准采用当前已配置的 3.13,基础标注服务验证该版本;推理 extras 如不兼容,明确要求另选受支持 Python,而不悄悄更改用户解释器。
18
+ - 可选模型依赖 lazy import;基础安装即可使用 UI、框标注与数据管理,安装 inference extra 后准备真实推理。模型权重不随 wheel 发布,也不由 uv sync 暗中下载。
19
+ - Python 用 ruff format、ruff check(E/F/I/UP/B)统一格式与检查;每次代码提交前执行。领域/存储/API 用 pytest,前端用 vue-tsc、Vite 构建及关键交互验证。
20
+ - Git 跟踪源码、测试、锁文件、正式设计及必要 README;忽略计划、执行记录、旧草案、截图、交互原型、环境、缓存、模型与用户数据。
21
+
22
+ ## 3. 工作项目与路径
23
+
24
+ 标准布局:
25
+
26
+ ```text
27
+ dataset/
28
+ images/[train|val|test]/任意多级目录/image.jpg
29
+ labels/对应相对目录/image.txt # detection
30
+ labels_seg/对应相对目录/image.txt # segmentation
31
+ data.yaml
32
+ .annodesk/
33
+ project.json
34
+ annotations/对应相对路径/image.jpg.json
35
+ masks/实例UUID/版本.png
36
+ operations/
37
+ trash/
38
+ ```
39
+
40
+ 不要求 train/val/test 齐全,也不要求目录必须取这些名字。图片可以直接位于 images 下;平铺/多级目录均可。标签相对路径必须镜像图片目录。图片支持 jpg/jpeg/png/webp/bmp/tif/tiff。
41
+
42
+ 提供两种入口:打开数据集根(识别 images 和可选标签),或打开任意纯图片目录(项目记录图片根映射,不移动原图)。后端处理本机路径,浏览器不把客户端路径误当作后端路径。
43
+
44
+ 同目录同 stem 不同扩展名会映射到同一个 YOLO txt;导入必须报告冲突,不覆盖。非法标签行、类别越界、尺寸异常应展示具体文件和原因,不能静默丢弃。没有 txt 不等于人工确认背景。
45
+
46
+ data.yaml 可缺失。扫描已有类别数字 ID 时生成 class_N 占位名;新增类别自动维护 names 和项目类别表。未划分数据允许标注,不伪造 val/test;训练导出前再完成划分校验。
47
+
48
+ 内部 JSON/无损 mask 是完整编辑记录;两个 txt 目录是同步的 YOLO 投影。首次从 txt 初始化;之后检测外部修改并明确要求重新导入/冲突处理,不自动覆盖用户外部修改。
49
+
50
+ ## 4. 实例和分类
51
+
52
+ 每个实例有稳定 UUID、类别 UUID、可选显式 box、可选 mask、来源、提示历史和模型标识。box-only、mask-only、box+mask 均合法。数字 class ID 只用于序列化。
53
+
54
+ - Draw box:产生显式检测框,可拖动、四角/边缩放、数值调整、键盘微调、改类、删除、隐藏、锁定、撤销/重做。限制图像边界,禁止零面积或非有限坐标。
55
+ - Box prompt:框只是分割提示,默认只保存 mask。正负点或文本分割也不自动创建 detection box。
56
+ - 从已有检测框生成 mask:关联原实例、继承类别、保留框。调整框后关联 mask 标需复核,提供按新框重算;不静默覆盖人工 mask。
57
+ - 选中已有实例加正负点,接受后替换同一实例 mask;不新增重复对象。重启后可从提示历史重建,不依赖持久化 logits。
58
+ - SAM 3 文本可返回多个实例;先指定类别和提示词,预览后接受。中文类别名称允许;不承诺模型中文提示质量。
59
+ - 删除操作区分仅 box、仅 mask 和整个实例;按当前明确的几何选择执行。无损 mask 保留孔洞和不连通部分,投影多边形不作为唯一数据。
60
+
61
+ 类别面板常驻左下。没有选中实例时,显示 Default for new annotations: <class> 并设置新对象默认类别;选中实例时显示 Class for selected object(s),点击类别改类。Default class 按钮明确取消选中,并以 tooltip 解释它只设置后续标注的类别。多类别支持搜索、最近使用和收藏;未分配类别草稿允许存在,但完成/导出前必须解决。
62
+
63
+ ## 5. 类别、tag、颜色
64
+
65
+ 类别支持新增、重命名、颜色编辑、删除;新增分配递增 ID,不复用已删除 ID。删除仍被引用的类别必须选择改派或删除实例。内部身份始终不变,ID 空缺在工作配置中保留占位。
66
+
67
+ 提供一键连续编号,先预览旧/新 ID 映射和影响数量。获得项目写锁,暂停推理写入,更新当前项目 labels/**/*.txt、labels_seg/**/*.txt、data.yaml 和内部映射。只改标注行首 ID,不改坐标、图片路径清单或历史导出副本。全量预校验、备份、事务日志和启动恢复保证多文件一致;不把多次 rename 称为跨文件原子操作。
68
+
69
+ class 与自由 tag 分别管理调色板,采用同一策略:默认建议颜色,可手动改色;新颜色尽量远离同一调色板已用色。在 OKLab 中从明度/色度适中且落在 sRGB 色域的候选中,最大化到已有色的最小感知距离。不能用简单 RGB 距离替代,也不能保证无限类别仍可区分。
70
+
71
+ 颜色绑定稳定 UUID,存项目字典;重命名、ID 重排和新增项不改变旧颜色。手动颜色过浅/过近时提示并提供替代色,但允许保留。颜色外保留名称、实例编号和选中描边;tag 用淡背景、色边、深色正文。状态色固定,不作为可编辑 tag 色。
72
+
73
+ ## 6. 图片状态与清理
74
+
75
+ Detection 和 Segmentation 分别记录复核状态,图片另有全局使用状态。英文展示:
76
+
77
+ | 展示 | 内部含义 | 颜色 |
78
+ | --- | --- | --- |
79
+ | Not reviewed | pending,尚未检查 | 灰 |
80
+ | Approved | ok,人工确认 | 绿 |
81
+ | Needs review | uncertain,有疑问/发现问题 | 琥珀 |
82
+ | Excluded | usability=discard,排除使用 | 低饱和红 |
83
+
84
+ Excluded 优先显示但保留原复核状态,不等于删除。blurry、night、occluded 等是自由多选 tag,可新增、改色、备注、筛选、批量设置。
85
+
86
+ 自动保存、自动分割不会变 Approved。修改已通过的任务标注使其回 pending;关联框变化使 mask 需复核,改 mask 不使独立检测框失效,共享类别变化影响两任务。Needs review 保持至人工明确处理。
87
+
88
+ 清理 Excluded 前显示具体图片及标签数量,一起移入项目回收站,记录原路径,支持恢复;永久清空单独操作。默认批处理/训练导出排除 Excluded 和 Needs review,pending 是否导出由设置决定并显示计数。
89
+
90
+ ## 7. 工作台布局
91
+
92
+ 顶部:项目与连接、批处理、导出、Classes/Tags 管理、快捷键帮助。
93
+ 左侧上方:图片搜索、状态筛选、Tree/Flat 文件列表;下方:常驻 Class palette。
94
+ 中间:标注工具、模型选择/自动分割、主画布、文件与比例、横向缩略图带。
95
+ 右侧:Current image,当前任务状态/tag、当前实例结果列表、选中实例属性及轻量显示设置。不得在当前结果下方混放全项目类别表。
96
+
97
+ 左右分隔条可拖动,中心取剩余空间;默认左260px、右290px,最低左190px、右220px,保护中央可操作宽度。左侧上下也能拖动,分隔条支持键盘小步调整、双击复位。正式版记忆用户布局,窄窗口折叠侧栏,不强行压缩画布。
98
+
99
+ Tree 保留 images 下多级子目录,支持逐目录展开/收起、Expand all/Collapse all;Flat 展示文件名和相对父目录。折叠只影响树内可见行,不改变过滤序列;模式切换不改变当前图片。搜索匹配文件和路径,暂时展开匹配目录,清空后恢复。
100
+
101
+ 实例列表与画布双向选择同步,包含短 ID、类别、Box/Mask、锁定/可见、推理和复核标记。Classes/Tags 抽屉仅管理项目字典,类别下拉与常驻面板允许快捷新增。
102
+
103
+ ## 8. 画布与快捷键
104
+
105
+ 视图工具使用放大镜 +/-、比例、手形 Pan、四角 Fit。滚轮以指针原图位置为中心缩放;按钮以中心缩放。Space 临时平移,松开恢复原工具。F 适应完整图片,1:1 表示原图像素与 CSS 像素一一对应,不能把 Fit 一律称为100%。缩放范围5%..3200%,保持少量图片可见。标注始终原图坐标,框线/提示点/控制柄保持可操作屏幕尺寸。
106
+
107
+ 快捷键注册表为唯一来源,供命令执行、Tooltip、帮助抽屉共用;可搜索、修改映射、检测冲突和恢复默认。
108
+
109
+ | 操作 | 默认 |
110
+ | --- | --- |
111
+ | 上/下一图,下一待复核 | A / D,Shift+D |
112
+ | 选择,画框,正/负点 | V,R,Q / E |
113
+ | 框提示,文本提示 | B,T |
114
+ | 接受,取消 | Enter,Esc |
115
+ | 平移,Fit,专注 | Space拖动,F,Shift+F |
116
+ | 通过,有疑问,排除 | 1,2,3 |
117
+ | 类别1..9 | Alt+1..9 |
118
+ | mask/box显示 | M / H |
119
+ | 撤销/重做,保存 | Ctrl+Z or ⌘Z / Ctrl+Shift+Z or ⌘Shift+Z,Ctrl+S or ⌘S |
120
+ | 删除,快捷键帮助 | Delete/Backspace,? |
121
+
122
+ Tab 保留焦点导航。输入框、IME、浮层不触发画布单键;按钮焦点下 Enter/Space 保留正常行为。主要按钮 hover/键盘聚焦展示动作+按键;重要错误和不可用原因不只藏在 hover。
123
+
124
+ ## 9. 缩略图和内存预算
125
+
126
+ 横向缩略图可滚轮、触控板、拖动滚动条和左右按钮滚动。滚动不切图;明确选图才保存/切图。切图将当前项滚入可见区域,编辑状态/tag 不重置手动滚动。
127
+
128
+ - 虚拟列表,可见区两侧各预取3张,最多24个挂载节点;不是只给全量 img 加 lazy。
129
+ - 最大4个并发请求,窗口外待发任务丢弃,过期请求取消/响应丢弃。
130
+ - 应用解码 LRU 最多48张或24MiB,任一上限触发淘汰并释放引用/bitmap/Object URL;不声称可限制浏览器总内存。
131
+ - 按显示尺寸/DPR 请求,最长边上限320px;不向预览带传原图。
132
+ - 后端缩略图磁盘缓存默认512MiB,可配置。按内容指纹、尺寸和生成版本失效。
133
+ - 原图仅当前图按需加载,可预取下一张适当预览;超大图后续可扩展瓦片。文件列表虚拟化或分页。
134
+
135
+ ## 10. 保存、并发和任务
136
+
137
+ 编辑操作结束后短延迟自动保存;切图前强制提交并等待成功。失败保留当前编辑、显示重试,不带错切图。关闭页面有未保存内容则提示,不能指望 unload 完成写盘。
138
+
139
+ 使用 revision 乐观锁及单写入会话,阻止多浏览器覆盖。保存内部记录、mask 和两类 txt 投影,以临时文件+原子单文件替换+操作日志恢复多文件一致性;全部完成才显示已保存。删除最后一个标注写空 txt,不残留旧标注。缺少标注与 explicit_empty 背景标记分开。
140
+
141
+ 所有推理绑定 project/image/instance/prompt revision/model/job ID;过时结果不能覆盖人工编辑。后台批任务不依赖浏览器连接,完成逐图持久化,支持取消、失败重试、续跑;只在推理调用边界响应取消。
142
+
143
+ 当前图、全部图、切图自动分割共用任务接口。默认只处理有框且无 mask 的实例,跳过 Excluded,不重算已编辑 mask。无框不自动猜目标/全图分割。交互请求优先,SAM 编码按图像指纹/权重/预处理缓存并有内存预算。
144
+
145
+ API 使用单 Uvicorn worker;模型由受控后台执行单元持有,推理不阻塞 HTTP。本地模型加载失败仍允许框标注。模型适配器提供 capabilities、load/unload、encode、segment_visual、segment_text,结果不直接写 UI 状态。
146
+
147
+ ## 11. YOLO 与训练导出
148
+
149
+ 默认工作投影为 YOLO。labels_seg 是工具约定,Ultralytics 默认从 images 映射 labels;仅在 YAML 添加 labels_seg 不能保证训练器采用。
150
+
151
+ 导出 detection 或 segmentation 的独立 images/labels/data.yaml,分割输出将工作 labels_seg 转为标准 labels,禁止混放 box 与 polygon 行。保持多级路径,首版复制图片。检测默认只导显式 box,可选临时从 mask 推导,仅作用于导出,不回写工作项目。
152
+
153
+ 导出校验类别、路径冲突、划分及未分配实例,可生成连续类别 ID 副本而不改工作项目。未标注不能当负样本,只有 explicit_empty 或完成标注的图像可纳入对应任务。
154
+
155
+ YOLO 多边形对孔洞/不连通实例可能有损,输出告警及报告;COCO RLE 支持无损导出。内部始终保留原始 mask、实例 ID 和提示历史。
156
+
157
+ ## 12. CLI、构建与部署
158
+
159
+ 单一 console script 使用 tyro:serve、api、ui、dev、build-ui、doctor、docs、models list、segment。CLI help 不加载模型;模型配置与项目标签分离,模型只从本地读取,禁止自动下载和推理时自动安装依赖。
160
+
161
+ ```bash
162
+ uv sync
163
+ uv run annodesk serve --project /data/dataset --open
164
+ annodesk serve --host 0.0.0.0 --port 8765
165
+ annodesk api --host 0.0.0.0 --port 8765
166
+ annodesk ui --port 8080 --api-url http://gpu-host:8765
167
+ uv run annodesk build-ui
168
+ uv build
169
+ ```
170
+
171
+ 默认127.0.0.1:8765,仅 --open 打开浏览器,端口冲突报错。serve 同源提供 API+已编译 UI;api 仅后端;ui 只提供已编译资源和运行时 API 配置,不加载推理。数据集始终在后端机器,客户端本地图片需上传/同步/挂载后使用。
172
+
173
+ API /api/v1,info 返回应用/协议版本和能力;运行时 /ui-config.json 指定 API base,不写死 localhost、不重新编译。图片/mask/缩略图使用 ID URL,通过 HTTP 获取;跨源支持 CORS 和无 credentials 请求。SSE/任务轮询恢复按 job ID,不丢持久状态。
174
+
175
+ Hatchling 打包。web_dist 作为 package data 随 wheel/sdist 发布,普通运行者不需 Node。仓库跟踪匹配的预构建资源,干净 clone 后 uv sync 即可打开 UI;前端开发使用 Node/npm lock,build-ui 执行 npm ci、类型检查、构建。uv sync 不隐式安装 Node 或 npm。发布 CI 先重建前端并检查资源版本,再 uv build,并在新环境安装 wheel 验证。安装目录只读或任意 cwd 均正常。
176
+
177
+ ### 12.1 本地模型配置与无界面分割
178
+
179
+ 仓库提供 models.json;命令行 --models-config 优先,其次 ANNODESK_MODELS_CONFIG、当前目录 models.json、仓库默认、wheel 内置默认。JSON 定义 default_model 及 models 数组,每项含 id、name、backend(sam/sam2/mobile_sam/sam3)、source(模型最终一级目录,不配置 checkpoint 文件名)。ID 唯一,默认 ID 必须存在。
180
+
181
+ source 只填写最终一级模型文件夹,不填写 pt/safetensors 文件,也不提供 checkpoint 字段。展开环境变量和 ~ 后统一两步查找,不识别 repo ID 类型:先相对 JSON 目录 resolve 并检查 is_dir;不是目录则检查 $HF_MODELS/source(HF_MODELS 已设置时)。绝对路径直接检查目录,缺失报错,不剥离根路径后回退。repo ID 与普通相对目录的查找规则相同;本地同名文件不视为目录。
182
+
183
+ 适配器在已选目录内自动查找兼容 .pt 权重,先按 backend 的已知命名匹配;无已知命名时仅接受唯一 .pt。没有兼容权重或多个候选均报错,不根据文件缺失再次换目录。safetensors-only 的 HF 目录暂不被当前 Ultralytics 适配器支持,页面明确显示不可用。配置每个 source 只包含一个模型。禁止 URL 和自动下载;推理禁用库联网/自动安装选项,Python 推理线程审计网络连接和下载/安装进程。辅助资源缺失也直接报错。
184
+
185
+ GET /api/v1/models 无需打开项目,返回配置条目、默认 ID、本地可用性、解析路径、错误、文本能力;页面模型选择器从此加载,可 Reload 配置及可用性。选择缺失模型时明确报错。GET /api/v1/project 在未打开项目时返回 200 null,避免把正常初始状态记录成 409;依赖项目的实际操作仍可返回 409。
186
+
187
+ annodesk segment --project /data/dataset --models-config ./models.json --model <id> 同步处理已有 YOLO 框,不启动 UI 或 HTTP 服务。默认模型来自 JSON,跳过已有 mask、锁定对象及 Excluded 图片;逐图保存到 labels_seg 与内部 RLE,保留 detection 框及类别 ID。中断后再次运行跳过已成功结果;失败明细写入 .annodesk/segment-report.json,失败退出码为 1,成功为 0。输出待人工复核,不自动标为 Approved。
188
+
189
+ ## 13. 验收和分模块实施
190
+
191
+ 以可运行模块依次提交:工程/CLI;领域和YOLO存储;API和保存;Vue工作台;推理适配/任务;类别迁移/清理/导出;集成和发布验证。未完成能力必须在 UI 显式不可用或在 README 说明,禁止用模拟推理假装真实模型。
192
+
193
+ 关键验收覆盖:YOLO多级路径和冲突、缺少YAML、类ID稳定与迁移、box/mask-only语义、切图保存失败与过时推理、无损mask往返、Excluded恢复、不同origin图片/保存/任务、wheel静态资源及tyro入口、缩略图预算、画布坐标/缩放、快捷键焦点/IME。提交前运行ruff和相应测试,前端提交还需类型检查/构建。
194
+
195
+ ## 14. 设计自检记录
196
+
197
+ - 已消除多份草案的名称/CLI差异,采用AnnoDesk、tyro、light/English。
198
+ - 已区分类别管理与标注赋类、自由tag与固定状态、框标注与框提示、工作labels_seg与训练labels。
199
+ - 已区分uv依赖、Node构建、权重准备,避免承诺所有模型自动安装或GPU驱动自动配置。
200
+ - 已定义多文件恢复、外部修改、未标注/背景和同stem冲突,不以YOLO txt充当全部编辑数据。
201
+ - Python基准遵循用户现有3.13配置;真实推理兼容性必须实测,基础标注不依赖GPU安装成功。
202
+
203
+ ### 交互细化:赋类、提示和品牌图标
204
+
205
+ Class palette 为日常赋类入口;顶部 Manage classes 为名称、颜色、ID 管理入口。选中框或 mask 后可直接点左侧类别,或按 C 打开带搜索的类别选择器,以 ↑↓ 选择、Enter 确认、Esc 取消。画布选择模式下双击实例也可打开;右侧提供 Change class 按钮。赋类沿用自动保存/撤销,不修改几何,锁定实例不可赋类。1/2/3 仍用于图片审核状态,不与类别快捷键混用。
206
+
207
+ 全局提示约 150ms 后显示,适用于 pointer hover 及键盘 focus,替代浏览器无法控制延迟的 title 提示。提示展示作用及已有快捷键,离开、点击、滚动、Esc 时关闭;窗口边缘自动调整位置。图标采用同一份 SVG:蓝色标注框与白色 mask 轮廓,复用为 favicon 与左上角品牌标识。
208
+
209
+ Tags 抽屉顶部始终展示四个内置审核状态及固定颜色/含义(Not reviewed、Approved、Needs review、Excluded),标注 Read only,不提供改名、改色或删除控件。下方独立 Custom image tags 分组管理自定义标签;内置项不写入可编辑 tags 字典。审核前三项按任务记录,Excluded 仍为整图标记,二者语义不变。
210
+
211
+ 快捷键补充:X 或 Delete 删除当前选中标注;←/→ 与 A/D 均可前后切图。沿用输入框、IME、弹层与锁定实例的快捷键保护,帮助抽屉、删除按钮提示与状态栏保持同步。
212
+
213
+ 快捷键文案约定:同一功能的替代操作使用 or(例如 X or Delete、C or double-click),不同功能依次对应的按键使用 /(例如 Previous / next image 对应 A / D or ← / →)。撤销与重做分行展示,避免组合键歧义。
214
+
215
+ 面向安装包用户的操作说明保存于 docs/user-guide.md,与开发者 README 分离。该指南作为 package long description 并以 annodesk/docs/user-guide.md 资源随 wheel 发布,sdist 同时包含源文档;annodesk docs 输出当前安装版本指南,--output 导出 Markdown 且拒绝覆盖已有文件。
216
+
217
+ 入口 HTML(含 /index.html)与状态 JSON 使用 Cache-Control: no-store;HTML 不使用旧 ETag/Last-Modified 条件请求返回 304,以免升级后仍引用已移除的 hash 静态资源。客户端配置和 API fetch 同样显式 no-store。未打开项目时仍由前端空状态分支停止拉取图片列表,不将 409 改为空数据掩盖错误。