label_translator.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. """
  2. Label translator for hazard detection system.
  3. Translates pinyin/English labels to Chinese for display and database queries.
  4. """
  5. from typing import List, Dict, Optional
  6. # Complete mapping: pinyin/English -> Chinese
  7. LABEL_MAPPING: Dict[str, str] = {
  8. # Gas Station (加油站) labels
  9. "fangzhuanglan": "防撞栏",
  10. "jiayouqiang": "加油枪",
  11. "jiayouji": "加油机",
  12. "miehuoqi": "灭火器",
  13. "zhuixingtong": "锥形桶",
  14. "push_extinguisher": "手推灭火器",
  15. "support_pillar": "支撑柱",
  16. "fuel_hose": "加油机输油软管",
  17. "vent_pipe": "通气管",
  18. "oil_discharge_port": "卸油口",
  19. "static_release_post": "静电释放桩",
  20. "static_wire": "静电线",
  21. "static_clip": "静电夹",
  22. "power_distribution_box": "配电箱",
  23. # Alternative English names (aliases)
  24. "crash_barrier": "防撞栏",
  25. "fuel_nozzle": "加油枪",
  26. "fuel_dispenser": "加油机",
  27. "fire_extinguisher": "灭火器",
  28. "traffic_cone": "锥形桶",
  29. "pillar": "支撑柱",
  30. "oil_hose": "加油机输油软管",
  31. "ventilation_pipe": "通气管",
  32. "discharge_port": "卸油口",
  33. "grounding_post": "静电释放桩",
  34. "grounding_wire": "静电线",
  35. "grounding_clip": "静电夹",
  36. "distribution_box": "配电箱",
  37. }
  38. # Reverse mapping: Chinese -> pinyin/English (for reference)
  39. REVERSE_MAPPING: Dict[str, str] = {v: k for k, v in LABEL_MAPPING.items()}
  40. def translate_label(label: str, fallback_to_original: bool = True) -> str:
  41. """
  42. Translate a single label from pinyin/English to Chinese.
  43. Args:
  44. label: The label to translate (pinyin or English)
  45. fallback_to_original: If True, return original label when no translation found
  46. Returns:
  47. Translated Chinese label, or original label if not found and fallback is True
  48. Examples:
  49. >>> translate_label("fangzhuanglan")
  50. "防撞栏"
  51. >>> translate_label("fuel_hose")
  52. "加油机输油软管"
  53. >>> translate_label("防撞栏") # Already Chinese
  54. "防撞栏"
  55. >>> translate_label("unknown_label")
  56. "unknown_label"
  57. """
  58. if not label:
  59. return label
  60. label_stripped = label.strip()
  61. if not label_stripped:
  62. return label
  63. # Check if already Chinese (contains Chinese characters)
  64. if _contains_chinese(label_stripped):
  65. return label_stripped
  66. # Try exact match (case-insensitive)
  67. label_lower = label_stripped.lower()
  68. if label_lower in LABEL_MAPPING:
  69. return LABEL_MAPPING[label_lower]
  70. # Try with underscores replaced by spaces
  71. label_spaced = label_lower.replace("_", " ")
  72. if label_spaced in LABEL_MAPPING:
  73. return LABEL_MAPPING[label_spaced]
  74. # Fallback to original if no translation found
  75. if fallback_to_original:
  76. return label_stripped
  77. return ""
  78. def translate_labels(labels: List[str], fallback_to_original: bool = True) -> List[str]:
  79. """
  80. Translate a list of labels from pinyin/English to Chinese.
  81. Args:
  82. labels: List of labels to translate
  83. fallback_to_original: If True, keep original labels when no translation found
  84. Returns:
  85. List of translated Chinese labels
  86. Examples:
  87. >>> translate_labels(["fangzhuanglan", "jiayouqiang", "防撞栏"])
  88. ["防撞栏", "加油枪", "防撞栏"]
  89. """
  90. if not labels:
  91. return []
  92. return [translate_label(label, fallback_to_original) for label in labels]
  93. def is_translated(label: str) -> bool:
  94. """
  95. Check if a label is already in Chinese (translated).
  96. Args:
  97. label: The label to check
  98. Returns:
  99. True if label contains Chinese characters, False otherwise
  100. """
  101. return _contains_chinese(label)
  102. def get_english_name(chinese_label: str) -> Optional[str]:
  103. """
  104. Get the English/pinyin name for a Chinese label (reverse lookup).
  105. Args:
  106. chinese_label: Chinese label to look up
  107. Returns:
  108. English/pinyin name if found, None otherwise
  109. Examples:
  110. >>> get_english_name("防撞栏")
  111. "fangzhuanglan"
  112. """
  113. return REVERSE_MAPPING.get(chinese_label.strip())
  114. def add_custom_mapping(pinyin_or_english: str, chinese: str) -> None:
  115. """
  116. Add a custom label mapping at runtime.
  117. Args:
  118. pinyin_or_english: The pinyin or English label
  119. chinese: The Chinese translation
  120. Note:
  121. This is useful for dynamic label additions without modifying the source code.
  122. """
  123. key = pinyin_or_english.strip().lower()
  124. value = chinese.strip()
  125. LABEL_MAPPING[key] = value
  126. REVERSE_MAPPING[value] = key
  127. def get_all_mappings() -> Dict[str, str]:
  128. """
  129. Get all label mappings.
  130. Returns:
  131. Dictionary of all pinyin/English -> Chinese mappings
  132. """
  133. return LABEL_MAPPING.copy()
  134. def _contains_chinese(text: str) -> bool:
  135. """
  136. Check if text contains Chinese characters.
  137. Args:
  138. text: Text to check
  139. Returns:
  140. True if text contains at least one Chinese character
  141. """
  142. if not text:
  143. return False
  144. for char in text:
  145. if '\u4e00' <= char <= '\u9fff':
  146. return True
  147. return False
  148. # Sub-secondary to secondary scene mapping
  149. # Used only for database queries - these scenes still appear as-is in UI and storage
  150. SUB_SECONDARY_TO_SECONDARY_MAPPING: Dict[str, str] = {
  151. # 加油设施_附属场地 -> maps to parent secondary scene (note: uses underscore in DB)
  152. "加油机": "加油设施_附属场地",
  153. "加油枪": "加油设施_附属场地",
  154. "加油机输油软管": "加油设施_附属场地",
  155. "防撞栏": "加油设施_附属场地",
  156. "锥形桶": "加油设施_附属场地",
  157. "灭火器": "加油设施_附属场地",
  158. # 罩棚 and 立柱 are separate scenes in DB, map support pillar to 立柱
  159. "支撑立柱": "立柱",
  160. }
  161. def translate_scene_for_query(scene_name: str) -> str:
  162. """
  163. Translate sub-secondary scene to its parent secondary scene for database queries.
  164. This is ONLY used when querying the database, not for display or storage.
  165. Args:
  166. scene_name: The scene name to translate (could be sub-secondary or secondary)
  167. Returns:
  168. Parent secondary scene name if it's a sub-secondary scene, otherwise original name
  169. Examples:
  170. >>> translate_scene_for_query("加油机")
  171. "加油设施及附属场地"
  172. >>> translate_scene_for_query("支撑立柱")
  173. "罩棚及立柱"
  174. >>> translate_scene_for_query("加油设施及附属场地")
  175. "加油设施及附属场地"
  176. """
  177. if not scene_name:
  178. return scene_name
  179. scene_stripped = scene_name.strip()
  180. return SUB_SECONDARY_TO_SECONDARY_MAPPING.get(scene_stripped, scene_stripped)
  181. def translate_scenes_for_query(scene_names: List[str]) -> List[str]:
  182. """
  183. Translate a list of sub-secondary scenes to their parent secondary scenes for database queries.
  184. Args:
  185. scene_names: List of scene names to translate
  186. Returns:
  187. List of translated scene names (parent secondary scenes)
  188. Examples:
  189. >>> translate_scenes_for_query(["加油机", "加油枪", "支撑立柱"])
  190. ["加油设施及附属场地", "加油设施及附属场地", "罩棚及立柱"]
  191. """
  192. if not scene_names:
  193. return []
  194. return [translate_scene_for_query(scene) for scene in scene_names]
  195. # Export main functions
  196. __all__ = [
  197. "translate_label",
  198. "translate_labels",
  199. "is_translated",
  200. "get_english_name",
  201. "add_custom_mapping",
  202. "get_all_mappings",
  203. "translate_scene_for_query",
  204. "translate_scenes_for_query",
  205. "LABEL_MAPPING",
  206. "SUB_SECONDARY_TO_SECONDARY_MAPPING",
  207. ]