fix(label-studio): 图片 URL 按实际的 DOCUMENT_ROOT 生成(--document-root)

原先任务里的图片 URL 固定是 /data/local-files/?d=images/<图名>,隐含假设输出目录就是
LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT。服务器上 DOCUMENT_ROOT=/home/deep/labeling,
Label Studio 会去找 /home/deep/labeling/images/<图名>,11211 张图全部 404。

- 新增 --document-root,缺省读 LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT;URL 写成相对它的
  路径,Local files 存储路径随之算好并打印。
- 输出目录在 DOCUMENT_ROOT 之外时,在 DOCUMENT_ROOT 里建同名软链接指过去,产出仍留在
  runs/,不用手动复制;DOCUMENT_ROOT 里已有同名真目录或指向别处的软链接时报错,不覆盖。
- 更正此前"软链接解析后会落到 DOCUMENT_ROOT 外面"的说法:核对 Label Studio 1.23.0
  源码,safe_join 用 abspath、存储路径用 normpath 做字符串前缀比较,都不解析软链接。
- 测试照搬 localfiles_data() 的判定逐个任务验证能否被服务,并加入旧 URL 在
  /home/deep/labeling 下全部 404 的回归用例。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-15 20:04:04 +08:00
co-authored by Claude Opus 5
parent 891f930de2
commit 7dd0f89d18
3 changed files with 233 additions and 10 deletions
+24 -2
View File
@@ -644,6 +644,26 @@ runs/boxes_shap_label_studio/ # 同时也是 Label Studio 的 DOCUMENT_ROOT
### 导入 Label Studio
**图片 URL 必须按 Label Studio 实际的 `DOCUMENT_ROOT` 生成。** 任务里写的是
`/data/local-files/?d=<相对路径>/images/<图名>.png`,Label Studio 把 `d` 拼到
`LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT` 后面找文件,相对路径错了就是整个项目 404。
脚本缺省读这个环境变量,也可以用 `--document-root` 显式指定:
```bash
# DOCUMENT_ROOT 统一放标注项目,产出仍留在 runs/
uv run python scripts/prepare_label_studio.py --source runs/boxes_shap \
--document-root /home/deep/labeling
```
| 情形 | 做法 | 图片 URL |
|---|---|---|
| 没设 DOCUMENT_ROOT | 输出目录自己当 DOCUMENT_ROOT | `?d=images/…` |
| 输出目录在 DOCUMENT_ROOT 之下 | 直接用 | `?d=<相对路径>/images/…` |
| 输出目录在 DOCUMENT_ROOT 之外 | 在 DOCUMENT_ROOT 里建同名软链接指向它 | `?d=boxes_shap_label_studio/images/…` |
DOCUMENT_ROOT 里已有同名的**真目录**(比如手动复制过去的一份)时脚本会报错,不会覆盖它。
Local files 存储的绝对路径填 `<DOCUMENT_ROOT>/<访问路径>/images`,脚本打印的步骤里已经算好。
脚本结束时会打印带实际路径的导入步骤:设置 `LABEL_STUDIO_LOCAL_FILES_*` 环境变量启动、
粘贴 labeling config、添加 Local files 存储(**只保存不要 Sync**,否则每张图多出一个空任务)、
导入 `tasks.json`。
@@ -652,8 +672,10 @@ runs/boxes_shap_label_studio/ # 同时也是 Label Studio 的 DOCUMENT_ROOT
- **为什么是扁平结构**:`label-studio-converter import yolo` 只认 `<input>/{images,labels,classes.txt}`,
且 `os.listdir` 不递归,看不见子目录。
- **硬链接而不是软链接**:Label Studio 读本地文件时会检查路径是否在 `DOCUMENT_ROOT` 之下,
软链接解析后可能落到外面。
- **软链接可以用**:Label Studio(1.23.0 核对过源码)拼文件路径用 `safe_join`(`abspath`),
存储路径做 `normpath` 后按字符串前缀比较,都**不解析软链接**,所以 DOCUMENT_ROOT 里放一个
指向 `runs/` 的软链接就能访问,不必复制。存储路径也必须是 DOCUMENT_ROOT 的**子目录**,
与 DOCUMENT_ROOT 相同会被拒绝,这正是要填到 `…/images` 的原因。
- **框作为 predictions 而不是 annotations**:它们是模型生成的。审核员修改提交后才成为 annotation,
哪些审过一目了然,导出时也只导出 annotation,没审过的框不会混进去。
- **不传 `--image-dims`**:图片尺寸有 3,223 种,传了会把同一个宽高写进每条标注。