feat: Add support for ADAS project in labeling and export processes

- Implemented class name loading for ADAS campaigns in `_class_names_for_campaign`.
- Enhanced annotation parsing to support cuboid and 2D rectangle labels in `_parse_ls_annotations`.
- Updated catalog building to include ADAS packs and batches in `build_catalog_signature`.
- Added ADAS cuboid promotion logic in `adas_cuboid.py`, supporting both quaternion_json and YOLO HBB formats.
- Introduced validation for ADAS batches to check for YOLO HBB labels in `adas_cuboid.py`.
- Modified delivery scanning to include ADAS project deliveries in `scan.py`.
- Extended job execution to handle ADAS exports for YOLO format in `runner.py`.
- Updated labeling export logic to accommodate ADAS project in `export_cuboid_batch.py`.
- Enhanced format conversion to support rectangle labels in `format_converter.py`.
- Improved CVAT task handling in `service.py` to ensure task ID updates on each labeling job.
- Updated web API endpoints to reflect ADAS project changes in `hsap-api.ts`.
- Added ADAS catalog types and UI components for displaying ADAS packs and batches in `dmsCatalog.ts`, `CatalogPage.tsx`, and `ExportPage.tsx`.
- Adjusted workflow registry to align with new ADAS project structure.
This commit is contained in:
2026-07-16 16:03:04 +08:00
parent be8a885b07
commit 1bde0fe430
23 changed files with 472 additions and 83 deletions

View File

@@ -186,9 +186,9 @@ def open_campaign(
else:
camp.status = "in_progress"
camp.updated_at = now
if cvat_task_id and not camp.cvat_task_id:
camp.cvat_task_id = cvat_task_id
camp.cvat_job_url = cvat_job_url
# 每次开标都创建新 CVAT Task始终更新 task_id
camp.cvat_task_id = cvat_task_id
camp.cvat_job_url = cvat_job_url
if ann_types and not camp.annotation_types:
camp.annotation_types = ann_types
db.flush()
@@ -531,6 +531,7 @@ def _cvat_upload_thread(cvat_task_id: int, image_paths: list, campaign_id: str |
with session_scope() as db:
camp = db.get(LabelingCampaign, campaign_id)
if camp and camp.cvat_job_url is None:
camp.cvat_task_id = cvat_task_id
camp.cvat_job_url = f"_UPLOAD_FAILED_: {e}"
camp.status = "upload_failed"
updated = True
@@ -542,12 +543,13 @@ def _cvat_upload_thread(cvat_task_id: int, image_paths: list, campaign_id: str |
try:
with session_scope() as db:
camp = db.get(LabelingCampaign, campaign_id)
# 上传成功后,尝试回填 job_url
# 上传成功后,回填 job_url 和 task_id
if camp and camp.cvat_job_url is None:
from as_platform.labeling.cvat_client import get_cvat_client
cvat = get_cvat_client()
task = cvat.get_task(cvat_task_id)
if task.job_url:
camp.cvat_task_id = cvat_task_id
camp.cvat_job_url = task.job_url
camp.status = "in_progress"
updated = True
@@ -657,7 +659,11 @@ def _build_class_map(camp, reg: dict | None) -> dict[str, int]:
def get_cvat_status(campaign_id: str) -> dict[str, Any]:
"""查询 CVAT 侧 Task 状态,包含上传进度信息。"""
"""查询 CVAT 侧 Task 状态,包含上传进度信息。
自动恢复:当检测到 cvat_job_url 为 None 且 CVAT Task 无数据时,
说明上传线程已丢失(如容器重启),自动重新触发上传。
"""
with session_scope() as db:
camp = db.get(LabelingCampaign, campaign_id)
if not camp:
@@ -690,12 +696,31 @@ def get_cvat_status(campaign_id: str) -> dict[str, Any]:
images = _iter_batch_images(batch_dir)
image_count = len(images)
except Exception:
images = []
pass
# 自动恢复cvat_job_url 为空且 CVAT Task 无 Job → 上传线程已丢失
resolved_url = task.job_url or camp.cvat_job_url
if not resolved_url and image_count > 0:
# CVAT Task 存在但无数据,重新触发上传
import threading
_needs_recovery = True
try:
r = cvat._session.get(f"{cvat._api}/tasks/{camp.cvat_task_id}/data/meta")
_needs_recovery = r.status_code >= 400 # 400 = 未上传数据
except Exception:
pass
if _needs_recovery:
print(f"[CVAT] 检测到断线任务,自动恢复上传 task={camp.cvat_task_id} campaign={campaign_id}")
uploader = _cvat_upload_thread(camp.cvat_task_id, images, campaign_id=campaign_id)
threading.Thread(target=uploader, daemon=True).start()
camp.cvat_job_url = None # 重置,等待线程回填
return {
"cvat_available": True,
"campaign_id": campaign_id,
"cvat_task_id": camp.cvat_task_id,
"cvat_job_url": task.job_url or camp.cvat_job_url,
"cvat_job_url": resolved_url,
"cvat_status": task.status,
"image_count": image_count,
}