template.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. """
  2. Template API router.
  3. Provides endpoints for template management and XML configuration validation.
  4. """
  5. from typing import List, Optional
  6. from fastapi import APIRouter, HTTPException, status, Query, Request
  7. from schemas.template import (
  8. TemplateResponse,
  9. TemplateListResponse,
  10. TemplateCategory,
  11. TemplateCategoryListResponse,
  12. ConfigValidationRequest,
  13. ConfigValidationResponse,
  14. ValidationError
  15. )
  16. import xml.etree.ElementTree as ET
  17. import re
  18. router = APIRouter(
  19. prefix="/api/templates",
  20. tags=["templates"]
  21. )
  22. # 预设模板类别
  23. TEMPLATE_CATEGORIES = [
  24. TemplateCategory(
  25. id="image_classification",
  26. name="图像分类",
  27. description="对图像进行分类标注",
  28. icon="image"
  29. ),
  30. TemplateCategory(
  31. id="object_detection",
  32. name="目标检测",
  33. description="在图像中标注目标边界框",
  34. icon="box"
  35. ),
  36. TemplateCategory(
  37. id="image_segmentation",
  38. name="图像分割",
  39. description="对图像进行像素级分割标注",
  40. icon="layers"
  41. ),
  42. TemplateCategory(
  43. id="text_classification",
  44. name="文本分类",
  45. description="对文本进行分类标注",
  46. icon="file-text"
  47. ),
  48. TemplateCategory(
  49. id="ner",
  50. name="命名实体识别",
  51. description="标注文本中的命名实体",
  52. icon="tag"
  53. ),
  54. TemplateCategory(
  55. id="text_labeling",
  56. name="文本标注",
  57. description="对文本进行通用标注",
  58. icon="edit"
  59. ),
  60. TemplateCategory(
  61. id="audio_transcription",
  62. name="音频转写",
  63. description="将音频转写为文本",
  64. icon="mic"
  65. ),
  66. TemplateCategory(
  67. id="video_annotation",
  68. name="视频标注",
  69. description="对视频进行标注",
  70. icon="video"
  71. ),
  72. ]
  73. # 预设模板
  74. PREDEFINED_TEMPLATES = [
  75. # 图像分类模板
  76. TemplateResponse(
  77. id="image_classification_basic",
  78. name="图像分类 - 基础",
  79. category="image_classification",
  80. description="简单的图像分类任务,支持单选分类",
  81. config="""<View>
  82. <Image name="image" value="$image"/>
  83. <Choices name="choice" toName="image">
  84. <Choice value="类别1"/>
  85. <Choice value="类别2"/>
  86. <Choice value="类别3"/>
  87. </Choices>
  88. </View>""",
  89. tags=["图像", "分类", "单选"]
  90. ),
  91. TemplateResponse(
  92. id="image_classification_multi",
  93. name="图像分类 - 多标签",
  94. category="image_classification",
  95. description="支持多标签的图像分类任务",
  96. config="""<View>
  97. <Image name="image" value="$image"/>
  98. <Choices name="choice" toName="image" choice="multiple">
  99. <Choice value="标签1"/>
  100. <Choice value="标签2"/>
  101. <Choice value="标签3"/>
  102. <Choice value="标签4"/>
  103. </Choices>
  104. </View>""",
  105. tags=["图像", "分类", "多选"]
  106. ),
  107. # 目标检测模板
  108. TemplateResponse(
  109. id="object_detection_bbox",
  110. name="目标检测 - 边界框",
  111. category="object_detection",
  112. description="使用矩形框标注图像中的目标",
  113. config="""<View>
  114. <Image name="image" value="$image"/>
  115. <RectangleLabels name="label" toName="image">
  116. <Label value="目标1" background="red"/>
  117. <Label value="目标2" background="blue"/>
  118. <Label value="目标3" background="green"/>
  119. </RectangleLabels>
  120. </View>""",
  121. tags=["图像", "检测", "边界框"]
  122. ),
  123. TemplateResponse(
  124. id="object_detection_keypoint",
  125. name="目标检测 - 关键点",
  126. category="object_detection",
  127. description="使用关键点标注图像中的目标",
  128. config="""<View>
  129. <Image name="image" value="$image"/>
  130. <KeyPointLabels name="kp" toName="image">
  131. <Label value="关键点1" background="red"/>
  132. <Label value="关键点2" background="blue"/>
  133. </KeyPointLabels>
  134. </View>""",
  135. tags=["图像", "检测", "关键点"]
  136. ),
  137. # 图像分割模板
  138. TemplateResponse(
  139. id="image_segmentation_polygon",
  140. name="图像分割 - 多边形",
  141. category="image_segmentation",
  142. description="使用多边形进行图像分割标注",
  143. config="""<View>
  144. <Image name="image" value="$image"/>
  145. <PolygonLabels name="label" toName="image">
  146. <Label value="区域1" background="red"/>
  147. <Label value="区域2" background="blue"/>
  148. <Label value="区域3" background="green"/>
  149. </PolygonLabels>
  150. </View>""",
  151. tags=["图像", "分割", "多边形"]
  152. ),
  153. TemplateResponse(
  154. id="image_segmentation_brush",
  155. name="图像分割 - 画笔",
  156. category="image_segmentation",
  157. description="使用画笔工具进行图像分割标注",
  158. config="""<View>
  159. <Image name="image" value="$image"/>
  160. <BrushLabels name="label" toName="image">
  161. <Label value="前景" background="red"/>
  162. <Label value="背景" background="blue"/>
  163. </BrushLabels>
  164. </View>""",
  165. tags=["图像", "分割", "画笔"]
  166. ),
  167. # 文本分类模板
  168. TemplateResponse(
  169. id="text_classification_sentiment",
  170. name="文本分类 - 情感分析",
  171. category="text_classification",
  172. description="对文本进行情感分类",
  173. config="""<View>
  174. <Text name="text" value="$text"/>
  175. <Choices name="sentiment" toName="text">
  176. <Choice value="正面"/>
  177. <Choice value="负面"/>
  178. <Choice value="中性"/>
  179. </Choices>
  180. </View>""",
  181. tags=["文本", "分类", "情感"]
  182. ),
  183. TemplateResponse(
  184. id="text_classification_topic",
  185. name="文本分类 - 主题分类",
  186. category="text_classification",
  187. description="对文本进行主题分类",
  188. config="""<View>
  189. <Text name="text" value="$text"/>
  190. <Choices name="topic" toName="text" choice="multiple">
  191. <Choice value="科技"/>
  192. <Choice value="体育"/>
  193. <Choice value="娱乐"/>
  194. <Choice value="财经"/>
  195. <Choice value="教育"/>
  196. </Choices>
  197. </View>""",
  198. tags=["文本", "分类", "主题"]
  199. ),
  200. # 命名实体识别模板
  201. TemplateResponse(
  202. id="ner_basic",
  203. name="命名实体识别 - 基础",
  204. category="ner",
  205. description="标注文本中的命名实体",
  206. config="""<View>
  207. <Labels name="label" toName="text">
  208. <Label value="人名" background="red"/>
  209. <Label value="地名" background="blue"/>
  210. <Label value="组织" background="green"/>
  211. <Label value="时间" background="orange"/>
  212. </Labels>
  213. <Text name="text" value="$text"/>
  214. </View>""",
  215. tags=["文本", "NER", "实体"]
  216. ),
  217. TemplateResponse(
  218. id="ner_relation",
  219. name="命名实体识别 - 关系抽取",
  220. category="ner",
  221. description="标注实体及其关系",
  222. config="""<View>
  223. <Labels name="label" toName="text">
  224. <Label value="主体" background="red"/>
  225. <Label value="客体" background="blue"/>
  226. </Labels>
  227. <Text name="text" value="$text"/>
  228. <Relations>
  229. <Relation value="属于"/>
  230. <Relation value="位于"/>
  231. <Relation value="创建"/>
  232. </Relations>
  233. </View>""",
  234. tags=["文本", "NER", "关系"]
  235. ),
  236. # 文本标注模板
  237. TemplateResponse(
  238. id="text_labeling_qa",
  239. name="文本标注 - 问答对",
  240. category="text_labeling",
  241. description="标注问答对数据",
  242. config="""<View>
  243. <Text name="text" value="$text"/>
  244. <TextArea name="question" toName="text" placeholder="输入问题..." rows="2"/>
  245. <TextArea name="answer" toName="text" placeholder="输入答案..." rows="4"/>
  246. </View>""",
  247. tags=["文本", "问答", "标注"]
  248. ),
  249. TemplateResponse(
  250. id="text_labeling_summary",
  251. name="文本标注 - 摘要生成",
  252. category="text_labeling",
  253. description="为文本生成摘要",
  254. config="""<View>
  255. <Text name="text" value="$text"/>
  256. <TextArea name="summary" toName="text" placeholder="输入摘要..." rows="4"/>
  257. </View>""",
  258. tags=["文本", "摘要", "标注"]
  259. ),
  260. # 音频转写模板
  261. TemplateResponse(
  262. id="audio_transcription_basic",
  263. name="音频转写 - 基础",
  264. category="audio_transcription",
  265. description="将音频转写为文本",
  266. config="""<View>
  267. <Audio name="audio" value="$audio"/>
  268. <TextArea name="transcription" toName="audio" placeholder="输入转写文本..." rows="4"/>
  269. </View>""",
  270. tags=["音频", "转写", "ASR"]
  271. ),
  272. TemplateResponse(
  273. id="audio_transcription_segment",
  274. name="音频转写 - 分段标注",
  275. category="audio_transcription",
  276. description="对音频进行分段转写",
  277. config="""<View>
  278. <Audio name="audio" value="$audio"/>
  279. <Labels name="label" toName="audio">
  280. <Label value="说话人1" background="red"/>
  281. <Label value="说话人2" background="blue"/>
  282. </Labels>
  283. <TextArea name="transcription" toName="audio" placeholder="输入转写文本..." rows="4"/>
  284. </View>""",
  285. tags=["音频", "转写", "分段"]
  286. ),
  287. # 视频标注模板
  288. TemplateResponse(
  289. id="video_annotation_basic",
  290. name="视频标注 - 基础",
  291. category="video_annotation",
  292. description="对视频进行基础标注",
  293. config="""<View>
  294. <Video name="video" value="$video"/>
  295. <Choices name="action" toName="video">
  296. <Choice value="动作1"/>
  297. <Choice value="动作2"/>
  298. <Choice value="动作3"/>
  299. </Choices>
  300. </View>""",
  301. tags=["视频", "标注", "分类"]
  302. ),
  303. TemplateResponse(
  304. id="video_annotation_bbox",
  305. name="视频标注 - 目标跟踪",
  306. category="video_annotation",
  307. description="在视频中跟踪目标",
  308. config="""<View>
  309. <Video name="video" value="$video"/>
  310. <VideoRectangle name="box" toName="video"/>
  311. <Labels name="label" toName="video">
  312. <Label value="目标1" background="red"/>
  313. <Label value="目标2" background="blue"/>
  314. </Labels>
  315. </View>""",
  316. tags=["视频", "跟踪", "检测"]
  317. ),
  318. ]
  319. def validate_xml_config(config: str) -> ConfigValidationResponse:
  320. """
  321. 验证 XML 配置的有效性。
  322. Args:
  323. config: XML 配置字符串
  324. Returns:
  325. ConfigValidationResponse 包含验证结果
  326. """
  327. errors = []
  328. # 检查是否为空
  329. if not config or not config.strip():
  330. errors.append(ValidationError(
  331. line=1,
  332. column=1,
  333. message="配置不能为空"
  334. ))
  335. return ConfigValidationResponse(valid=False, errors=errors)
  336. try:
  337. # 尝试解析 XML
  338. ET.fromstring(config)
  339. return ConfigValidationResponse(valid=True, errors=[])
  340. except ET.ParseError as e:
  341. # 解析错误信息
  342. error_msg = str(e)
  343. line = 1
  344. column = 1
  345. # 尝试从错误信息中提取行号和列号
  346. match = re.search(r'line (\d+), column (\d+)', error_msg)
  347. if match:
  348. line = int(match.group(1))
  349. column = int(match.group(2))
  350. errors.append(ValidationError(
  351. line=line,
  352. column=column,
  353. message=f"XML 解析错误: {error_msg}"
  354. ))
  355. return ConfigValidationResponse(valid=False, errors=errors)
  356. @router.get("", response_model=TemplateListResponse)
  357. async def list_templates(
  358. request: Request,
  359. category: Optional[str] = Query(None, description="按类别筛选"),
  360. search: Optional[str] = Query(None, description="搜索关键词")
  361. ):
  362. """
  363. 获取模板列表。
  364. 支持按类别筛选和关键词搜索。
  365. """
  366. templates = PREDEFINED_TEMPLATES.copy()
  367. # 按类别筛选
  368. if category:
  369. templates = [t for t in templates if t.category == category]
  370. # 按关键词搜索
  371. if search:
  372. search_lower = search.lower()
  373. templates = [
  374. t for t in templates
  375. if search_lower in t.name.lower()
  376. or search_lower in t.description.lower()
  377. or any(search_lower in tag.lower() for tag in t.tags)
  378. ]
  379. return TemplateListResponse(
  380. templates=templates,
  381. total=len(templates)
  382. )
  383. @router.get("/categories", response_model=TemplateCategoryListResponse)
  384. async def list_categories(request: Request):
  385. """
  386. 获取模板类别列表。
  387. """
  388. return TemplateCategoryListResponse(categories=TEMPLATE_CATEGORIES)
  389. @router.get("/{template_id}", response_model=TemplateResponse)
  390. async def get_template(request: Request, template_id: str):
  391. """
  392. 获取指定模板详情。
  393. """
  394. for template in PREDEFINED_TEMPLATES:
  395. if template.id == template_id:
  396. return template
  397. raise HTTPException(
  398. status_code=status.HTTP_404_NOT_FOUND,
  399. detail=f"模板 '{template_id}' 不存在"
  400. )
  401. @router.post("/validate", response_model=ConfigValidationResponse)
  402. async def validate_config(request: Request, validation_request: ConfigValidationRequest):
  403. """
  404. 验证 XML 配置的有效性。
  405. 检查 XML 语法是否正确,返回验证结果和错误信息。
  406. """
  407. return validate_xml_config(validation_request.config)