Spaces:
Running
on
Zero
Running
on
Zero
import logging | |
import traceback | |
import re | |
from typing import Dict, List, Optional, Union, Any | |
class TemplateProcessor: | |
""" | |
模板處理器 - 負責模板填充、後處理和結構化模板渲染 | |
此類別專門處理模板的最終填充過程、文本格式化、 | |
語法修復以及結構化模板的渲染邏輯。 | |
""" | |
def __init__(self): | |
"""初始化模板處理器""" | |
self.logger = logging.getLogger(self.__class__.__name__) | |
self.logger.debug("TemplateProcessor initialized successfully") | |
def preprocess_template(self, template: str) -> str: | |
""" | |
預處理模板,修復常見問題 | |
Args: | |
template: 原始模板字符串 | |
Returns: | |
str: 預處理後的模板 | |
""" | |
try: | |
# 移除可能導致問題的模式 | |
template = re.sub(r'\{[^}]*\}\s*,\s*\{[^}]*\}', '{combined_elements}', template) | |
# 確保模板不以逗號開始 | |
template = re.sub(r'^[,\s]*', '', template) | |
return template.strip() | |
except Exception as e: | |
self.logger.warning(f"Error preprocessing template: {str(e)}") | |
return template | |
def postprocess_filled_template(self, filled_template: str) -> str: | |
""" | |
後處理填充完成的模板,修復語法問題 | |
Args: | |
filled_template: 填充後的模板字符串 | |
Returns: | |
str: 修復後的模板字符串 | |
""" | |
try: | |
# 原有的語法修復邏輯 | |
# 修復 "In , " 模式 | |
filled_template = re.sub(r'\bIn\s*,\s*', 'In this scene, ', filled_template) | |
filled_template = re.sub(r'\bAt\s*,\s*', 'At this location, ', filled_template) | |
filled_template = re.sub(r'\bWithin\s*,\s*', 'Within this area, ', filled_template) | |
# 修復連續逗號 | |
filled_template = re.sub(r',\s*,', ',', filled_template) | |
# 修復開頭的逗號 | |
filled_template = re.sub(r'^[,\s]*', '', filled_template) | |
# 1. 修復不完整的 "and." 結尾問題 | |
# 處理 "物件列表, and." 的模式,將其修正為完整的句子 | |
filled_template = re.sub(r',\s*and\s*\.\s*', '. ', filled_template) | |
filled_template = re.sub(r'\s+and\s*\.\s*', '. ', filled_template) | |
# 2. 處理重複的物件列表模式 | |
# 識別並移除重複的完整物件描述片段 | |
# 針對 "數字 + 物件名稱" 的重複模式 | |
object_pattern = r'(\b\d+\s+\w+(?:\s+\w+)*(?:,\s*\d+\s+\w+(?:\s+\w+)*)*(?:,\s*(?:a|an)\s+\w+(?:\s+\w+)*)*)' | |
# 找到所有物件列表片段 | |
object_matches = re.findall(object_pattern, filled_template) | |
if object_matches: | |
# 移除重複的物件列表 | |
seen_objects = set() | |
for obj_desc in object_matches: | |
# 標準化物件描述用於比較(移除多餘空格) | |
normalized_desc = re.sub(r'\s+', ' ', obj_desc.strip().lower()) | |
if normalized_desc in seen_objects: | |
# 找到重複的物件描述,移除後續出現的實例 | |
escaped_desc = re.escape(obj_desc) | |
pattern = r'\.\s*' + escaped_desc + r'(?=\s*\.|\s*$)' | |
filled_template = re.sub(pattern, '', filled_template, count=1) | |
else: | |
seen_objects.add(normalized_desc) | |
# 3. 處理重複的句子片段 | |
# 將文本分割為句子,檢查是否有完整句子的重複 | |
sentences = re.split(r'(?<=[.!?])\s+', filled_template) | |
unique_sentences = [] | |
seen_sentences = set() | |
for sentence in sentences: | |
if sentence.strip(): # 忽略空句子 | |
# 標準化句子用於比較(移除標點符號和多餘空格) | |
normalized_sentence = re.sub(r'[^\w\s]', '', sentence.lower().strip()) | |
normalized_sentence = re.sub(r'\s+', ' ', normalized_sentence) | |
# 只有當句子足夠長且確實重複時才移除 | |
if len(normalized_sentence) > 10 and normalized_sentence not in seen_sentences: | |
unique_sentences.append(sentence.strip()) | |
seen_sentences.add(normalized_sentence) | |
elif len(normalized_sentence) <= 10: | |
# 短句子直接保留,避免過度清理 | |
unique_sentences.append(sentence.strip()) | |
# 重新組合句子 | |
if unique_sentences: | |
filled_template = ' '.join(unique_sentences) | |
# 4. 清理可能產生的多餘空格和標點符號 | |
filled_template = re.sub(r'\s+', ' ', filled_template) | |
filled_template = re.sub(r'\s*\.\s*\.\s*', '. ', filled_template) # 移除連續句號 | |
filled_template = re.sub(r'\s*,\s*\.\s*', '. ', filled_template) # 修正 ", ." | |
# 確保首字母大寫 | |
if filled_template and not filled_template[0].isupper(): | |
filled_template = filled_template[0].upper() + filled_template[1:] | |
# 確保以句號結尾 | |
if filled_template and not filled_template.endswith(('.', '!', '?')): | |
filled_template += '.' | |
return filled_template.strip() | |
except Exception as e: | |
self.logger.warning(f"Error postprocessing filled template: {str(e)}") | |
return filled_template | |
def get_template_by_scene_type(self, scene_type: str, detected_objects: List[Dict], | |
functional_zones: Dict, template_repository) -> str: | |
""" | |
根據場景類型選擇合適的模板並進行標準化處理 | |
Args: | |
scene_type: 場景類型 | |
detected_objects: 檢測到的物件列表 | |
functional_zones: 功能區域字典 | |
template_repository: 模板庫實例 | |
Returns: | |
str: 標準化後的模板字符串 | |
""" | |
try: | |
# 獲取場景的物件統計信息 | |
object_stats = self._analyze_scene_composition(detected_objects) | |
zone_count = len(functional_zones) if functional_zones else 0 | |
# 根據場景複雜度和類型選擇模板 | |
templates = template_repository.templates | |
if scene_type in templates: | |
scene_templates = templates[scene_type] | |
# 根據複雜度選擇合適的模板變體 | |
if zone_count >= 3 and object_stats.get("total_objects", 0) >= 10: | |
template_key = "complex" | |
elif zone_count >= 2 or object_stats.get("total_objects", 0) >= 5: | |
template_key = "moderate" | |
else: | |
template_key = "simple" | |
if template_key in scene_templates: | |
raw_template = scene_templates[template_key] | |
else: | |
raw_template = scene_templates.get("default", scene_templates[list(scene_templates.keys())[0]]) | |
else: | |
# 如果沒有特定場景的模板,使用通用模板 | |
raw_template = self._get_generic_template(object_stats, zone_count) | |
# 標準化模板中的佔位符和格式 | |
standardized_template = self._standardize_template_format(raw_template) | |
return standardized_template | |
except Exception as e: | |
self.logger.error(f"Error selecting template for scene type '{scene_type}': {str(e)}") | |
return self._get_fallback_template() | |
def _analyze_scene_composition(self, detected_objects: List[Dict]) -> Dict: | |
""" | |
分析場景組成以確定模板複雜度 | |
Args: | |
detected_objects: 檢測到的物件列表 | |
Returns: | |
Dict: 場景組成統計信息 | |
""" | |
try: | |
total_objects = len(detected_objects) | |
# 統計不同類型的物件 | |
object_categories = {} | |
for obj in detected_objects: | |
class_name = obj.get("class_name", "unknown") | |
object_categories[class_name] = object_categories.get(class_name, 0) + 1 | |
# 計算場景多樣性 | |
unique_categories = len(object_categories) | |
return { | |
"total_objects": total_objects, | |
"unique_categories": unique_categories, | |
"category_distribution": object_categories, | |
"complexity_score": min(total_objects * 0.3 + unique_categories * 0.7, 10) | |
} | |
except Exception as e: | |
self.logger.warning(f"Error analyzing scene composition: {str(e)}") | |
return {"total_objects": 0, "unique_categories": 0, "complexity_score": 0} | |
def _get_generic_template(self, object_stats: Dict, zone_count: int) -> str: | |
""" | |
獲取通用模板 | |
Args: | |
object_stats: 物件統計信息 | |
zone_count: 功能區域數量 | |
Returns: | |
str: 通用模板字符串 | |
""" | |
try: | |
complexity_score = object_stats.get("complexity_score", 0) | |
if complexity_score >= 7 or zone_count >= 3: | |
return "This scene presents a comprehensive view featuring {functional_area} with {primary_objects}. The spatial organization demonstrates {spatial_arrangement} across multiple {activity_areas}, creating a dynamic environment with diverse elements and clear functional zones." | |
elif complexity_score >= 4 or zone_count >= 2: | |
return "The scene displays {functional_area} containing {primary_objects}. The arrangement shows {spatial_organization} with distinct areas serving different purposes within the overall space." | |
else: | |
return "A {scene_description} featuring {primary_objects} arranged in {basic_layout} within the visible area." | |
except Exception as e: | |
self.logger.warning(f"Error getting generic template: {str(e)}") | |
return self._get_fallback_template() | |
def _get_fallback_template(self) -> str: | |
""" | |
獲取備用模板 | |
Returns: | |
str: 備用模板字符串 | |
""" | |
return "A scene featuring various elements and organized areas of activity within the visible space." | |
def _standardize_template_format(self, template: str) -> str: | |
""" | |
標準化模板格式,確保佔位符和表達方式符合自然語言要求 | |
Args: | |
template: 原始模板字符串 | |
Returns: | |
str: 標準化後的模板字符串 | |
""" | |
try: | |
if not template: | |
return self._get_fallback_template() | |
standardized = template | |
# 標準化佔位符格式,移除技術性標記 | |
placeholder_mapping = { | |
r'\{zone_\d+\}': '{functional_area}', | |
r'\{object_group_\d+\}': '{primary_objects}', | |
r'\{region_\d+\}': '{spatial_area}', | |
r'\{category_\d+\}': '{object_category}', | |
r'\{area_\d+\}': '{activity_area}', | |
r'\{section_\d+\}': '{scene_section}' | |
} | |
for pattern, replacement in placeholder_mapping.items(): | |
standardized = re.sub(pattern, replacement, standardized) | |
# 標準化常見的技術性術語 | |
term_replacements = { | |
'functional_zones': 'areas of activity', | |
'object_detection': 'visible elements', | |
'category_regions': 'organized sections', | |
'spatial_distribution': 'arrangement throughout the space', | |
'viewpoint_analysis': 'perspective view' | |
} | |
for tech_term, natural_term in term_replacements.items(): | |
standardized = standardized.replace(tech_term, natural_term) | |
# 確保模板語法的自然性 | |
standardized = self._improve_template_readability(standardized) | |
return standardized | |
except Exception as e: | |
self.logger.warning(f"Error standardizing template format: {str(e)}") | |
return template if template else self._get_fallback_template() | |
def _improve_template_readability(self, template: str) -> str: | |
""" | |
改善模板的可讀性和自然性 | |
Args: | |
template: 模板字符串 | |
Returns: | |
str: 改善後的模板字符串 | |
""" | |
try: | |
# 移除多餘的空格和換行 | |
improved = re.sub(r'\s+', ' ', template).strip() | |
# 改善句子連接 | |
improved = improved.replace(' . ', '. ') | |
improved = improved.replace(' , ', ', ') | |
improved = improved.replace(' ; ', '; ') | |
# 確保適當的句號結尾 | |
if improved and not improved.endswith(('.', '!', '?')): | |
improved += '.' | |
# 改善常見的表達問題 | |
readability_fixes = [ | |
(r'\bthe the\b', 'the'), | |
(r'\ba a\b', 'a'), | |
(r'\ban an\b', 'an'), | |
(r'\bwith with\b', 'with'), | |
(r'\bin in\b', 'in'), | |
(r'\bof of\b', 'of'), | |
(r'\band and\b', 'and') | |
] | |
for pattern, replacement in readability_fixes: | |
improved = re.sub(pattern, replacement, improved, flags=re.IGNORECASE) | |
return improved | |
except Exception as e: | |
self.logger.warning(f"Error improving template readability: {str(e)}") | |
return template | |
def process_structured_template(self, template: Dict[str, Any], scene_data: Dict[str, Any], | |
statistics_processor) -> str: | |
""" | |
處理結構化模板字典 | |
Args: | |
template: 結構化模板字典 | |
scene_data: 場景分析資料 | |
statistics_processor: 統計處理器實例 | |
Returns: | |
str: 生成的場景描述 | |
""" | |
try: | |
# 提取 scene_data 中各區塊資料 | |
zone_data = scene_data.get("functional_zones", scene_data.get("zones", {})) | |
object_data = scene_data.get("detected_objects", []) | |
scene_context = scene_data.get("scene_context", "") | |
# 獲取模板結構 | |
structure = template.get("structure", []) | |
if not structure: | |
self.logger.warning("Template has no structure defined") | |
return self._generate_fallback_scene_description(scene_data) | |
description_parts = [] | |
# 按照模板結構生成描述 | |
for section in structure: | |
section_type = section.get("type", "") | |
content = section.get("content", "") | |
if section_type == "opening": | |
description_parts.append(content) | |
elif section_type == "zone_analysis": | |
zone_descriptions = statistics_processor.generate_zone_descriptions(zone_data, section) | |
if zone_descriptions: | |
description_parts.extend(zone_descriptions) | |
elif section_type == "object_summary": | |
object_summary = statistics_processor.generate_object_summary(object_data, section) | |
if object_summary: | |
description_parts.append(object_summary) | |
elif section_type == "conclusion": | |
conclusion = statistics_processor.generate_conclusion(template, zone_data, object_data) | |
if conclusion: | |
description_parts.append(conclusion) | |
# 合併並標準化輸出 | |
final_description = self._standardize_final_description(" ".join(description_parts)) | |
self.logger.info("Successfully applied structured template") | |
return final_description | |
except Exception as e: | |
self.logger.error(f"Error processing structured template: {str(e)}") | |
return self._generate_fallback_scene_description(scene_data) | |
def _generate_fallback_scene_description(self, scene_data: Dict[str, Any]) -> str: | |
""" | |
生成備用場景描述 | |
Args: | |
scene_data: 場景分析資料 | |
Returns: | |
str: 備用場景描述 | |
""" | |
try: | |
detected_objects = scene_data.get("detected_objects", []) | |
zones = scene_data.get("functional_zones", scene_data.get("zones", {})) | |
scene_type = scene_data.get("scene_type", "general") | |
object_count = len(detected_objects) | |
zone_count = len(zones) | |
if zone_count > 0 and object_count > 0: | |
return f"Scene analysis completed with {zone_count} functional areas containing {object_count} identified objects." | |
elif object_count > 0: | |
return f"Scene analysis identified {object_count} objects in this {scene_type.replace('_', ' ')} environment." | |
else: | |
return f"Scene analysis completed for this {scene_type.replace('_', ' ')} environment." | |
except Exception as e: | |
self.logger.warning(f"Error generating fallback description: {str(e)}") | |
return "Scene analysis completed with detected objects and functional areas." | |
def _standardize_final_description(self, description: str) -> str: | |
""" | |
對最終描述進行標準化處理 | |
Args: | |
description: 原始描述文本 | |
Returns: | |
str: 標準化後的描述文本 | |
""" | |
try: | |
# 移除多餘空格 | |
description = " ".join(description.split()) | |
# 確保句子間有適當間距 | |
description = description.replace(". ", ". ") | |
# 移除任何殘留的技術性標識符 | |
technical_patterns = [ | |
r'zone_\d+', r'area_\d+', r'region_\d+', | |
r'_zone', r'_area', r'_region' | |
] | |
for pattern in technical_patterns: | |
description = re.sub(pattern, '', description, flags=re.IGNORECASE) | |
return description.strip() | |
except Exception as e: | |
self.logger.error(f"Error standardizing final description: {str(e)}") | |
return description | |
def generate_fallback_description(self, scene_type: str, detected_objects: List[Dict]) -> str: | |
""" | |
生成備用描述,當模板填充完全失敗時使用 | |
Args: | |
scene_type: 場景類型 | |
detected_objects: 檢測到的物體列表 | |
Returns: | |
str: 備用描述 | |
""" | |
try: | |
object_count = len(detected_objects) | |
if object_count == 0: | |
return f"A {scene_type.replace('_', ' ')} scene." | |
elif object_count == 1: | |
return f"A {scene_type.replace('_', ' ')} scene with one visible element." | |
else: | |
return f"A {scene_type.replace('_', ' ')} scene with {object_count} visible elements." | |
except Exception as e: | |
self.logger.warning(f"Error generating fallback description: {str(e)}") | |
return "A scene with various elements." | |