You've already forked DataMate
feat(annotation): 支持音频和视频数据类型的标注任务
- 添加了音频和视频数据类型常量定义 - 实现了音频和视频标注模板的内置配置 - 扩展前端组件以支持按数据类型过滤标注模板 - 重构后端编辑器服务以处理音频和视频任务构建 - 更新数据库初始化脚本包含音频和视频标注模板 - 添加音频和视频数据类型的预览URL映射逻辑
This commit is contained in:
@@ -43,6 +43,8 @@ logger = get_logger(__name__)
|
||||
|
||||
TEXT_DATA_KEY = "text"
|
||||
IMAGE_DATA_KEY = "image"
|
||||
AUDIO_DATA_KEY = "audio"
|
||||
VIDEO_DATA_KEY = "video"
|
||||
DATASET_ID_KEY = "dataset_id"
|
||||
FILE_ID_KEY = "file_id"
|
||||
FILE_NAME_KEY = "file_name"
|
||||
@@ -53,9 +55,19 @@ SEGMENT_INDEX_KEY = "segment_index"
|
||||
SEGMENT_INDEX_CAMEL_KEY = "segmentIndex"
|
||||
JSONL_EXTENSION = ".jsonl"
|
||||
TEXTUAL_OBJECT_CATEGORIES = {"text", "document"}
|
||||
MEDIA_OBJECT_CATEGORIES = {"image"}
|
||||
IMAGE_OBJECT_CATEGORIES = {"image"}
|
||||
MEDIA_OBJECT_CATEGORIES = {"media"}
|
||||
OBJECT_NAME_HEADER_PREFIX = "dm_object_header_"
|
||||
SUPPORTED_EDITOR_DATASET_TYPES = ("TEXT", "IMAGE")
|
||||
DATASET_TYPE_TEXT = "TEXT"
|
||||
DATASET_TYPE_IMAGE = "IMAGE"
|
||||
DATASET_TYPE_AUDIO = "AUDIO"
|
||||
DATASET_TYPE_VIDEO = "VIDEO"
|
||||
SUPPORTED_EDITOR_DATASET_TYPES = (
|
||||
DATASET_TYPE_TEXT,
|
||||
DATASET_TYPE_IMAGE,
|
||||
DATASET_TYPE_AUDIO,
|
||||
DATASET_TYPE_VIDEO,
|
||||
)
|
||||
SEGMENTATION_ENABLED_KEY = "segmentation_enabled"
|
||||
|
||||
|
||||
@@ -174,21 +186,19 @@ class AnnotationEditorService:
|
||||
return keys[0]
|
||||
|
||||
@classmethod
|
||||
def _resolve_primary_media_key(
|
||||
def _resolve_media_value_keys(
|
||||
cls,
|
||||
label_config: Optional[str],
|
||||
default_key: str,
|
||||
categories: Optional[set[str]] = None,
|
||||
) -> str:
|
||||
) -> List[str]:
|
||||
if not label_config:
|
||||
return default_key
|
||||
return [default_key]
|
||||
target_categories = categories or set()
|
||||
keys = cls._extract_object_value_keys_by_category(label_config, target_categories)
|
||||
if not keys:
|
||||
return default_key
|
||||
if default_key in keys:
|
||||
return default_key
|
||||
return keys[0]
|
||||
return [default_key]
|
||||
return keys
|
||||
|
||||
@staticmethod
|
||||
def _try_parse_json_payload(text_content: str) -> Optional[Dict[str, Any]]:
|
||||
@@ -467,7 +477,10 @@ class AnnotationEditorService:
|
||||
|
||||
dataset_type = self._normalize_dataset_type(await self._get_dataset_type(project.dataset_id))
|
||||
if dataset_type not in SUPPORTED_EDITOR_DATASET_TYPES:
|
||||
raise HTTPException(status_code=400, detail="当前仅支持 TEXT/IMAGE 项目的内嵌编辑器")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="当前仅支持 TEXT/IMAGE/AUDIO/VIDEO 项目的内嵌编辑器",
|
||||
)
|
||||
|
||||
file_result = await self.db.execute(
|
||||
select(DatasetFiles).where(
|
||||
@@ -479,9 +492,15 @@ class AnnotationEditorService:
|
||||
if not file_record:
|
||||
raise HTTPException(status_code=404, detail=f"文件不存在或不属于该项目: {file_id}")
|
||||
|
||||
if dataset_type == "IMAGE":
|
||||
if dataset_type == DATASET_TYPE_IMAGE:
|
||||
return await self._build_image_task(project, file_record, file_id)
|
||||
|
||||
if dataset_type == DATASET_TYPE_AUDIO:
|
||||
return await self._build_audio_task(project, file_record, file_id)
|
||||
|
||||
if dataset_type == DATASET_TYPE_VIDEO:
|
||||
return await self._build_video_task(project, file_record, file_id)
|
||||
|
||||
return await self._build_text_task(project, file_record, file_id, segment_index)
|
||||
|
||||
async def _build_text_task(
|
||||
@@ -668,23 +687,20 @@ class AnnotationEditorService:
|
||||
currentSegmentIndex=current_segment_index,
|
||||
)
|
||||
|
||||
async def _build_image_task(
|
||||
async def _build_media_task(
|
||||
self,
|
||||
project: LabelingProject,
|
||||
file_record: DatasetFiles,
|
||||
file_id: str,
|
||||
default_key: str,
|
||||
categories: set[str],
|
||||
) -> EditorTaskResponse:
|
||||
label_config = await self._resolve_project_label_config(project)
|
||||
image_key = self._resolve_primary_media_key(
|
||||
label_config,
|
||||
IMAGE_DATA_KEY,
|
||||
MEDIA_OBJECT_CATEGORIES,
|
||||
)
|
||||
media_keys = self._resolve_media_value_keys(label_config, default_key, categories)
|
||||
preview_url = self._build_file_preview_url(project.dataset_id, file_id)
|
||||
file_name = str(getattr(file_record, "file_name", ""))
|
||||
|
||||
task_data: Dict[str, Any] = {
|
||||
image_key: preview_url,
|
||||
FILE_ID_KEY: file_id,
|
||||
FILE_ID_CAMEL_KEY: file_id,
|
||||
DATASET_ID_KEY: project.dataset_id,
|
||||
@@ -692,6 +708,9 @@ class AnnotationEditorService:
|
||||
FILE_NAME_KEY: file_name,
|
||||
FILE_NAME_CAMEL_KEY: file_name,
|
||||
}
|
||||
for key in media_keys:
|
||||
task_data[key] = preview_url
|
||||
self._apply_text_placeholders(task_data, label_config)
|
||||
|
||||
# 获取现有标注
|
||||
ann_result = await self.db.execute(
|
||||
@@ -738,6 +757,48 @@ class AnnotationEditorService:
|
||||
currentSegmentIndex=0,
|
||||
)
|
||||
|
||||
async def _build_image_task(
|
||||
self,
|
||||
project: LabelingProject,
|
||||
file_record: DatasetFiles,
|
||||
file_id: str,
|
||||
) -> EditorTaskResponse:
|
||||
return await self._build_media_task(
|
||||
project=project,
|
||||
file_record=file_record,
|
||||
file_id=file_id,
|
||||
default_key=IMAGE_DATA_KEY,
|
||||
categories=IMAGE_OBJECT_CATEGORIES,
|
||||
)
|
||||
|
||||
async def _build_audio_task(
|
||||
self,
|
||||
project: LabelingProject,
|
||||
file_record: DatasetFiles,
|
||||
file_id: str,
|
||||
) -> EditorTaskResponse:
|
||||
return await self._build_media_task(
|
||||
project=project,
|
||||
file_record=file_record,
|
||||
file_id=file_id,
|
||||
default_key=AUDIO_DATA_KEY,
|
||||
categories=MEDIA_OBJECT_CATEGORIES,
|
||||
)
|
||||
|
||||
async def _build_video_task(
|
||||
self,
|
||||
project: LabelingProject,
|
||||
file_record: DatasetFiles,
|
||||
file_id: str,
|
||||
) -> EditorTaskResponse:
|
||||
return await self._build_media_task(
|
||||
project=project,
|
||||
file_record=file_record,
|
||||
file_id=file_id,
|
||||
default_key=VIDEO_DATA_KEY,
|
||||
categories=MEDIA_OBJECT_CATEGORIES,
|
||||
)
|
||||
|
||||
async def upsert_annotation(self, project_id: str, file_id: str, request: UpsertAnnotationRequest) -> UpsertAnnotationResponse:
|
||||
project = await self._get_project_or_404(project_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user