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."