""" Analysis Engine Description: ComfyUI custom node for PDF tools and media processing Author: Eric Hiss (GitHub: EricRollei) Contact: eric@historic.camera, eric@rollei.us License: Dual License (Non-Commercial and Commercial Use) Copyright (c) 2025 Eric Hiss. All rights reserved. Dual License: 1. Non-Commercial Use: This software is licensed under the terms of the Creative Commons Attribution-NonCommercial 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/ 2. Commercial Use: For commercial use, a separate license is required. Please contact Eric Hiss at eric@historic.camera or eric@rollei.us for licensing options. Dependencies: This code depends on several third-party libraries, each with its own license. See CREDITS.md for a comprehensive list of dependencies and their licenses. Third-party code: - See CREDITS.md for complete list of dependencies """ """ Shared Analysis Engine v1.0 Multi-modal content analysis combining Surya Layout + Florence2 + OCR Used by: Enhanced Layout Parser, PDF Extractor, Rectangle Cropper, etc. """ import os import sys import numpy as np from PIL import Image from typing import Dict, List, Any, Optional, Tuple import torch import importlib.util # Add the current directory and parent to path for local imports current_dir = os.path.dirname(os.path.abspath(__file__)) parent_dir = os.path.dirname(current_dir) if current_dir not in sys.path: sys.path.append(current_dir) if parent_dir not in sys.path: sys.path.append(parent_dir) # ComfyUI path detection with fallback try: import folder_paths COMFYUI_BASE_PATH = os.path.dirname(folder_paths.models_dir) print(f"✅ ComfyUI base path detected: {COMFYUI_BASE_PATH}") except ImportError: # Smart fallback for test scripts # Look for ComfyUI directory structure possible_comfy_paths = [ # Try to find ComfyUI from current location os.path.abspath(os.path.join(current_dir, "..", "..", "..")), # Go up from PDF_tools/florence2_scripts os.path.abspath(os.path.join(current_dir, "..", "..", "..", "..")), # Go up one more level "A:\\Comfy_Dec\\ComfyUI", # Your specific path "C:\\ComfyUI", # Common location "D:\\ComfyUI", # Another common location ] COMFYUI_BASE_PATH = "." # Default fallback for path in possible_comfy_paths: models_dir = os.path.join(path, "models", "LLM") if os.path.exists(models_dir): COMFYUI_BASE_PATH = path print(f"✅ ComfyUI base path auto-detected: {COMFYUI_BASE_PATH}") break if COMFYUI_BASE_PATH == ".": print("⚠️ ComfyUI folder_paths not available, using current directory") print(f" Checked paths: {possible_comfy_paths}") # Surya imports try: from surya.layout import LayoutPredictor SURYA_LAYOUT_AVAILABLE = True print("✅ Surya layout import successful") except ImportError as e: SURYA_LAYOUT_AVAILABLE = False print(f"❌ Surya layout import failed: {e}") # Florence2 imports with enhanced import strategy print("🔍 Checking import availability...") FLORENCE2_AVAILABLE = False Florence2RectangleDetector = None BoundingBox = None def import_florence2_modules(): """Import Florence2 modules with multiple fallback strategies""" global Florence2RectangleDetector, BoundingBox try: # Strategy 1: Direct import (works when everything is in sys.modules) try: from florence2_detector import Florence2RectangleDetector, BoundingBox print("✅ Florence2 import from local florence2_detector successful") return True except ImportError: pass # Strategy 2: Load florence2_detector with proper module dependencies import importlib.util import sys # First, ensure the required modules are loaded current_dir = os.path.dirname(os.path.abspath(__file__)) # Load configuration_florence2 if not already loaded if 'configuration_florence2' not in sys.modules: config_path = os.path.join(current_dir, "configuration_florence2.py") if os.path.exists(config_path): spec = importlib.util.spec_from_file_location("configuration_florence2", config_path) config_module = importlib.util.module_from_spec(spec) sys.modules['configuration_florence2'] = config_module spec.loader.exec_module(config_module) print("✅ Pre-loaded configuration_florence2") # Load modeling_florence2 if not already loaded if 'modeling_florence2' not in sys.modules: modeling_path = os.path.join(current_dir, "modeling_florence2.py") if os.path.exists(modeling_path): spec = importlib.util.spec_from_file_location("modeling_florence2", modeling_path) modeling_module = importlib.util.module_from_spec(spec) sys.modules['modeling_florence2'] = modeling_module spec.loader.exec_module(modeling_module) print("✅ Pre-loaded modeling_florence2") # Now load florence2_detector detector_path = os.path.join(current_dir, "florence2_detector.py") if os.path.exists(detector_path): spec = importlib.util.spec_from_file_location("florence2_detector", detector_path) detector_module = importlib.util.module_from_spec(spec) sys.modules['florence2_detector'] = detector_module spec.loader.exec_module(detector_module) Florence2RectangleDetector = detector_module.Florence2RectangleDetector BoundingBox = detector_module.BoundingBox print("✅ Florence2 import via importlib successful") return True else: print(f"❌ florence2_detector.py not found at {detector_path}") return False except Exception as e: print(f"❌ Florence2 import failed: {e}") print(f" Current dir: {current_dir}") if os.path.exists(current_dir): py_files = [f for f in os.listdir(current_dir) if f.endswith('.py')] print(f" Available .py files: {py_files}") return False # Try to import Florence2 try: FLORENCE2_AVAILABLE = import_florence2_modules() except Exception as e: print(f"❌ Florence2 import completely failed: {e}") FLORENCE2_AVAILABLE = False print(f"📊 Import Summary:") print(f" Surya Available: {SURYA_LAYOUT_AVAILABLE}") print(f" Florence2 Available: {FLORENCE2_AVAILABLE}") class ContentAnalysisEngine: """Shared engine for multi-modal content analysis""" def __init__(self, enable_surya=True, enable_florence2=True, enable_ocr=True, debug_mode=False): self.debug_mode = debug_mode if debug_mode: print(f"🏗️ Initializing ContentAnalysisEngine...") print(f" Surya requested: {enable_surya}, available: {SURYA_LAYOUT_AVAILABLE}") print(f" Florence2 requested: {enable_florence2}, available: {FLORENCE2_AVAILABLE}") print(f" OCR requested: {enable_ocr}") self.enable_surya = enable_surya and SURYA_LAYOUT_AVAILABLE self.enable_florence2 = enable_florence2 and FLORENCE2_AVAILABLE self.enable_ocr = enable_ocr # Initialize models self.surya_predictor = None self.florence2_detector = None if self.enable_surya: if debug_mode: print("🔄 Initializing Surya...") success = self._init_surya() if not success and debug_mode: print("❌ Surya initialization failed") if self.enable_florence2: if debug_mode: print("🔄 Initializing Florence2...") success = self._init_florence2() if not success and debug_mode: print("❌ Florence2 initialization failed") self._report_capabilities() def _init_surya(self): """Initialize Surya Layout predictor with new API""" try: # Clear environment interference import os env_vars_to_remove = ['TORCHDYNAMO_DISABLE', 'TORCH_COMPILE_DISABLE', 'PYTORCH_DISABLE_DYNAMO'] original_env = {} for var in env_vars_to_remove: if var in os.environ: original_env[var] = os.environ[var] del os.environ[var] # Initialize Surya with new API (requires FoundationPredictor) from surya.foundation import FoundationPredictor from surya.layout import LayoutPredictor # Determine device device = "cuda" if torch.cuda.is_available() else "cpu" dtype = torch.float16 if device == "cuda" else torch.float32 if self.debug_mode: print(f" 🔧 Initializing FoundationPredictor (device: {device}, dtype: {dtype})") # Create foundation predictor first foundation_predictor = FoundationPredictor( checkpoint=None, # Use default checkpoint device=device, dtype=dtype, attention_implementation="sdpa" # Use scaled dot product attention ) # Now create layout predictor with foundation predictor self.surya_predictor = LayoutPredictor(foundation_predictor=foundation_predictor) if self.debug_mode: print("✅ Surya Layout initialized successfully") # Restore environment variables for var, value in original_env.items(): os.environ[var] = value return True except Exception as e: if self.debug_mode: print(f"❌ Surya Layout initialization failed: {e}") import traceback print(f" Traceback: {traceback.format_exc()}") print(" 📋 Surya layout features will be disabled") self.surya_predictor = None self.enable_surya = False return False def _init_florence2(self): """Initialize Florence2 detector with better error handling""" if not FLORENCE2_AVAILABLE or Florence2RectangleDetector is None: if self.debug_mode: print("❌ Florence2 not available - import failed") return False try: if torch.cuda.is_available(): torch.cuda.empty_cache() if self.debug_mode: print(f"🔍 Attempting Florence2 initialization...") print(f" Base path: {COMFYUI_BASE_PATH}") # Show available models available_models = get_available_florence2_models() if available_models: print(f" Available Florence2 models: {available_models}") else: print(f" No Florence2 models found") # Try models in order of preference model_priorities = [ "CogFlorence-2.2-Large", "Florence-2-base", "microsoft--Florence-2-base", # Sometimes downloaded with -- instead of / ] # Add any available models that contain 'florence' available_models = get_available_florence2_models() for model in available_models: if model not in model_priorities: model_priorities.append(model) for model_name in model_priorities: try: if self.debug_mode: print(f" 🔄 Trying model: {model_name}") # Use the imported class self.florence2_detector = Florence2RectangleDetector( model_name=model_name, comfyui_base_path=COMFYUI_BASE_PATH, min_box_area=1000 ) if self.debug_mode: print(f" ✅ Florence2 {model_name} initialized successfully") return True except Exception as e: if self.debug_mode: print(f" ❌ {model_name} failed: {e}") continue # If we get here, all models failed if self.debug_mode: print(f" ❌ All Florence2 models failed to initialize") models_dir = os.path.join(COMFYUI_BASE_PATH, "models", "LLM") if os.path.exists(models_dir): all_dirs = [d for d in os.listdir(models_dir) if os.path.isdir(os.path.join(models_dir, d))] print(f" 📂 All directories in models/LLM: {all_dirs}") self.florence2_detector = None self.enable_florence2 = False return False except Exception as e: if self.debug_mode: print(f"❌ Florence2 initialization completely failed: {e}") self.florence2_detector = None self.enable_florence2 = False return False def _report_capabilities(self): """Report available capabilities""" capabilities = [] if self.enable_surya: capabilities.append("🎯 Surya Layout (semantic detection)") if self.enable_florence2: capabilities.append("📦 Florence2 (rectangle detection)") if self.enable_ocr: capabilities.append("📝 OCR (text extraction)") if self.debug_mode: print(f"🚀 Content Analysis Engine ready:") for cap in capabilities: print(f" {cap}") def analyze_image_comprehensive(self, image: Image.Image, surya_confidence=0.3, florence2_image_prompt="rectangular images in page", florence2_confidence=0.5) -> Dict[str, Any]: """Comprehensive image analysis using all available methods""" results = { "surya_layout": [], # Initialize as empty list instead of None "florence2_rectangles": [], # Initialize as empty list instead of None "semantic_regions": { "text_regions": [], "image_regions": [], "caption_regions": [], "header_regions": [], "other_regions": [] }, "analysis_summary": {} } # 1. Surya Layout Analysis if self.enable_surya: surya_results = self._run_surya_layout(image, surya_confidence) results["surya_layout"] = surya_results if surya_results else [] results["semantic_regions"] = self._categorize_surya_regions(results["surya_layout"]) # 2. Florence2 Rectangle Detection if self.enable_florence2: florence2_results = self._run_florence2_detection( image, florence2_image_prompt, florence2_confidence ) results["florence2_rectangles"] = florence2_results if florence2_results else [] # 3. Create Analysis Summary results["analysis_summary"] = self._create_analysis_summary(results) return results def _run_surya_layout(self, image: Image.Image, confidence_threshold: float) -> List[Dict]: """Run Surya layout detection (proven minimal approach)""" if not self.surya_predictor: return [] try: # FIX: Resize very large images to avoid embedding size mismatch # Surya has a maximum size it can handle - resize if needed max_dimension = 2048 # Safe maximum for Surya original_size = image.size needs_resize = False if max(image.size) > max_dimension: needs_resize = True # Calculate new size maintaining aspect ratio ratio = max_dimension / max(image.size) new_size = tuple(int(dim * ratio) for dim in image.size) resized_image = image.resize(new_size, Image.Resampling.LANCZOS) layout_predictions = self.surya_predictor([resized_image]) # Scale factor to convert resized coordinates back to original scale_x = original_size[0] / new_size[0] scale_y = original_size[1] / new_size[1] else: layout_predictions = self.surya_predictor([image]) scale_x = scale_y = 1.0 layout_regions = [] for page_result in layout_predictions: if hasattr(page_result, 'bboxes'): for layout_box in page_result.bboxes: if hasattr(layout_box, 'bbox') and hasattr(layout_box, 'label'): bbox = layout_box.bbox label = layout_box.label confidence = getattr(layout_box, 'confidence', 0.9) position = getattr(layout_box, 'position', -1) if confidence >= confidence_threshold: # Scale bbox back to original size if image was resized if needs_resize: bbox = [ bbox[0] * scale_x, bbox[1] * scale_y, bbox[2] * scale_x, bbox[3] * scale_y ] area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) layout_regions.append({ 'bbox': bbox, 'confidence': confidence, 'area': area, 'semantic_label': label, 'reading_position': position, 'source': 'surya_layout' }) # Sort by reading order layout_regions.sort(key=lambda x: x['reading_position'] if x['reading_position'] >= 0 else 999) if self.debug_mode: print(f"📋 Surya Layout: {len(layout_regions)} regions detected") return layout_regions except Exception as e: if self.debug_mode: print(f"❌ Surya layout detection failed: {e}") # Check if it's a cv2 import issue if "cv2" in str(e) or "opencv" in str(e).lower(): print(" 💡 This may be an OpenCV compatibility issue with Surya") print(" Try: pip install opencv-python-headless --upgrade") return [] def _run_florence2_detection(self, image: Image.Image, prompt: str, confidence_threshold: float) -> List[Dict]: """Run Florence2 rectangle detection""" if not self.florence2_detector or not self.enable_florence2: if self.debug_mode: print("📦 Florence2 detection skipped (not available)") return [] try: bounding_boxes, _ = self.florence2_detector.detect_rectangles( image=image, text_input=prompt, return_mask=False, keep_model_loaded=True ) florence2_regions = [] for box in bounding_boxes: # Handle missing confidence values confidence = getattr(box, 'confidence', None) if confidence is None: confidence = 1.0 # Default high confidence if not provided # Apply confidence threshold if confidence >= confidence_threshold: florence2_regions.append({ 'bbox': box.to_tuple(), 'confidence': confidence, 'area': box.area, 'label': getattr(box, 'label', 'rectangular_image'), # Safe label access 'source': 'florence2' }) if self.debug_mode: print(f"📦 Florence2: {len(florence2_regions)} rectangles detected (threshold: {confidence_threshold})") for i, region in enumerate(florence2_regions): print(f" 📦 Region {i+1}: {region['bbox']}, conf: {region['confidence']:.2f}") return florence2_regions except Exception as e: if self.debug_mode: print(f"❌ Florence2 detection failed: {e}") import traceback traceback.print_exc() return [] def _categorize_surya_regions(self, layout_regions: List[Dict]) -> Dict[str, List[Dict]]: """Categorize Surya regions by semantic type""" categorized = { "text_regions": [], "image_regions": [], "caption_regions": [], "header_regions": [], "other_regions": [] } text_labels = ['Text', 'Footnote', 'Formula', 'TextInlineMath'] image_labels = ['Picture', 'Figure', 'Table'] caption_labels = ['Caption'] header_labels = ['SectionHeader', 'PageHeader', 'PageFooter', 'Title'] for region in layout_regions: label = region['semantic_label'] if label in text_labels: categorized["text_regions"].append(region) elif label in image_labels: categorized["image_regions"].append(region) elif label in caption_labels: categorized["caption_regions"].append(region) elif label in header_labels: categorized["header_regions"].append(region) else: categorized["other_regions"].append(region) return categorized def _create_analysis_summary(self, results: Dict) -> Dict[str, Any]: """Create summary of analysis results""" surya_regions = results.get("surya_layout", []) florence2_regions = results.get("florence2_rectangles", []) semantic_regions = results.get("semantic_regions", {}) # Ensure we have lists, not None values if surya_regions is None: surya_regions = [] if florence2_regions is None: florence2_regions = [] return { "total_surya_regions": len(surya_regions), "total_florence2_regions": len(florence2_regions), "text_regions_count": len(semantic_regions.get("text_regions", [])), "image_regions_count": len(semantic_regions.get("image_regions", [])), "caption_regions_count": len(semantic_regions.get("caption_regions", [])), "header_regions_count": len(semantic_regions.get("header_regions", [])), "has_semantic_data": len(surya_regions) > 0, "has_rectangle_data": len(florence2_regions) > 0, "analysis_methods": [ method for method, enabled in [ ("surya_layout", self.enable_surya and len(surya_regions) > 0), ("florence2", self.enable_florence2 and len(florence2_regions) > 0), ("ocr", self.enable_ocr) ] if enabled ] } def extract_text_from_regions(self, image: Image.Image, text_regions: List[Dict], ocr_engine: str = "tesseract") -> List[Dict]: """Extract text from detected regions using OCR""" if not (self.enable_ocr and text_regions): return [] extracted_texts = [] for region in text_regions: bbox = region['bbox'] text_result = self._extract_text_from_bbox(image, bbox, ocr_engine) if text_result['text'].strip(): extracted_texts.append({ **region, 'text': text_result['text'], 'ocr_confidence': text_result['confidence'], 'ocr_method': text_result['method'] }) return extracted_texts def _extract_text_from_bbox(self, image: Image.Image, bbox: List[int], ocr_engine: str) -> Dict[str, Any]: """Extract text from bounding box using specified OCR engine""" try: # Crop with padding padding = 10 x1, y1, x2, y2 = bbox x1 = max(0, x1 - padding) y1 = max(0, y1 - padding) x2 = min(image.width, x2 + padding) y2 = min(image.height, y2 + padding) cropped = image.crop((x1, y1, x2, y2)) if cropped.width < 50 or cropped.height < 20: return {"text": "", "confidence": 0.0, "method": "too_small"} # Enhance for OCR enhanced = self._enhance_for_ocr(cropped) if ocr_engine == "tesseract": return self._tesseract_ocr(enhanced) else: return {"text": "", "confidence": 0.0, "method": f"unsupported_{ocr_engine}"} except Exception as e: return {"text": "", "confidence": 0.0, "method": "extraction_error"} def _enhance_for_ocr(self, image: Image.Image) -> Image.Image: """Enhance image for better OCR results""" if image.mode != 'L': image = image.convert('L') from PIL import ImageEnhance enhancer = ImageEnhance.Contrast(image) image = enhancer.enhance(1.8) enhancer = ImageEnhance.Sharpness(image) image = enhancer.enhance(1.5) if image.width < 300: scale = 400 / image.width new_size = (int(image.width * scale), int(image.height * scale)) image = image.resize(new_size, Image.Resampling.LANCZOS) return image def _tesseract_ocr(self, image: Image.Image) -> Dict[str, Any]: """Extract text using Tesseract""" try: import pytesseract # Try different PSM modes for best results psm_modes = [6, 3, 8, 11, 13] best_text = "" best_length = 0 best_psm = 6 for psm in psm_modes: try: config = f'--oem 3 --psm {psm}' text = pytesseract.image_to_string(image, config=config).strip() if len(text) > best_length and len(text) > 5: best_text = text best_length = len(text) best_psm = psm except Exception: continue if best_text: return { "text": best_text, "confidence": 0.8, "method": f"tesseract_psm_{best_psm}" } else: return {"text": "", "confidence": 0.0, "method": "tesseract_no_results"} except ImportError: return {"text": "", "confidence": 0.0, "method": "tesseract_not_available"} except Exception as e: return {"text": "", "confidence": 0.0, "method": "tesseract_error"} def get_smart_crop_recommendations(self, image: Image.Image, avoid_captions=True, prefer_images=True) -> List[Dict[str, Any]]: """Get smart cropping recommendations based on content analysis""" analysis = self.analyze_image_comprehensive(image) recommendations = [] image_regions = analysis["semantic_regions"]["image_regions"] caption_regions = analysis["semantic_regions"]["caption_regions"] for img_region in image_regions: crop_recommendation = { "bbox": img_region["bbox"], "confidence": img_region["confidence"], "reason": "detected_image_region", "semantic_label": img_region["semantic_label"], "avoid_regions": [] } # Check for nearby captions to avoid if avoid_captions: for caption in caption_regions: if self._regions_nearby(img_region["bbox"], caption["bbox"], threshold=50): crop_recommendation["avoid_regions"].append({ "bbox": caption["bbox"], "type": "caption", "reason": "avoid_cropping_with_image" }) recommendations.append(crop_recommendation) return recommendations def _regions_nearby(self, bbox1: List[int], bbox2: List[int], threshold: int) -> bool: """Check if two regions are nearby (within threshold pixels)""" x1_min, y1_min, x1_max, y1_max = bbox1 x2_min, y2_min, x2_max, y2_max = bbox2 # Calculate minimum distance between boxes if x1_max < x2_min: # bbox1 is to the left horizontal_dist = x2_min - x1_max elif x2_max < x1_min: # bbox1 is to the right horizontal_dist = x1_min - x2_max else: # overlapping horizontally horizontal_dist = 0 if y1_max < y2_min: # bbox1 is above vertical_dist = y2_min - y1_max elif y2_max < y1_min: # bbox1 is below vertical_dist = y1_min - y2_max else: # overlapping vertically vertical_dist = 0 distance = (horizontal_dist**2 + vertical_dist**2)**0.5 return distance <= threshold def set_comfyui_base_path(path: str): """Override ComfyUI base path for testing purposes""" global COMFYUI_BASE_PATH old_path = COMFYUI_BASE_PATH COMFYUI_BASE_PATH = path print(f"🔧 ComfyUI base path changed from {old_path} to {COMFYUI_BASE_PATH}") # Verify the path has models models_dir = os.path.join(COMFYUI_BASE_PATH, "models", "LLM") if os.path.exists(models_dir): available_models = [d for d in os.listdir(models_dir) if os.path.isdir(os.path.join(models_dir, d))] print(f"✅ Found {len(available_models)} models: {available_models[:3]}...") return True else: print(f"❌ Models directory not found: {models_dir}") return False def get_available_florence2_models(): """Get list of available Florence2 models""" models_dir = os.path.join(COMFYUI_BASE_PATH, "models", "LLM") if not os.path.exists(models_dir): return [] available_models = [] for item in os.listdir(models_dir): item_path = os.path.join(models_dir, item) if os.path.isdir(item_path) and "florence" in item.lower(): available_models.append(item) return available_models # Factory function for easy instantiation def create_content_analyzer(enable_surya=True, enable_florence2=True, enable_ocr=True, debug_mode=False): """Factory function to create a content analyzer instance""" return ContentAnalysisEngine( enable_surya=enable_surya, enable_florence2=enable_florence2, enable_ocr=enable_ocr, debug_mode=debug_mode ) # Convenience functions for common use cases def analyze_for_layout_parsing(image: Image.Image, debug_mode=False) -> Dict[str, Any]: """Optimized for layout parsing nodes""" analyzer = create_content_analyzer(enable_surya=True, enable_florence2=False, debug_mode=debug_mode) return analyzer.analyze_image_comprehensive(image) def analyze_for_pdf_extraction(image: Image.Image, debug_mode=False) -> Dict[str, Any]: """Optimized for PDF extraction (needs both Surya + Florence2)""" analyzer = create_content_analyzer(enable_surya=True, enable_florence2=True, debug_mode=debug_mode) return analyzer.analyze_image_comprehensive(image) def analyze_for_smart_cropping(image: Image.Image, debug_mode=False) -> List[Dict[str, Any]]: """Optimized for smart cropping (avoid captions, find images)""" analyzer = create_content_analyzer(enable_surya=True, enable_florence2=True, debug_mode=debug_mode) return analyzer.get_smart_crop_recommendations(image, avoid_captions=True, prefer_images=True) # Add this to your analysis_engine.py for testing: def test_analysis_engine(): """Comprehensive test of the analysis engine""" print("🧪 Testing Analysis Engine...") # Test imports print(f" Surya available: {SURYA_LAYOUT_AVAILABLE}") print(f" Florence2 available: {FLORENCE2_AVAILABLE}") # Create test image test_image = Image.new('RGB', (800, 600), color='white') # Draw some test rectangles from PIL import ImageDraw draw = ImageDraw.Draw(test_image) draw.rectangle([100, 100, 300, 200], outline='red', width=3) draw.rectangle([400, 150, 600, 350], outline='blue', width=3) draw.text((110, 110), "Test text region", fill='black') # Test analysis try: analyzer = create_content_analyzer(debug_mode=True) result = analyzer.analyze_image_comprehensive(test_image) print(f"📊 Test Results:") print(f" Surya regions: {len(result.get('surya_layout', []))}") print(f" Florence2 rectangles: {len(result.get('florence2_rectangles', []))}") print(f" Analysis methods: {result.get('analysis_summary', {}).get('analysis_methods', [])}") return True except Exception as e: print(f"❌ Test failed: {e}") import traceback traceback.print_exc() return False if __name__ == "__main__": test_analysis_engine()