Files

833 lines
34 KiB
Python

"""
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()