Spaces:
Runtime error
Runtime error
File size: 6,508 Bytes
45cef88 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
"""
image_analyzer.py - LOGOS Image Analysis Pipeline
Batch-process architectural diagrams and UI screenshots.
"""
import os
import cv2
import numpy as np
from PIL import Image
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass
from collections import Counter
@dataclass
class ImageAnalysis:
"""Result of analyzing a single image."""
filename: str
path: str
width: int
height: int
aspect_ratio: float
classification: str # "diagram", "ui", "photo", "other"
dominant_colors: List[Tuple[int, int, int]]
edge_density: float
text_region_ratio: float
thumbnail: Optional[np.ndarray] = None
def classify_image(edge_density: float, color_variance: float, aspect: float) -> str:
"""
Simple heuristic classification.
- Diagrams: High edge density, low color variance, often wide aspect.
- UI: Medium edge density, structured colors, standard aspect.
- Photos: Low edge density, high color variance.
"""
if edge_density > 0.15 and color_variance < 50:
return "diagram"
elif 0.05 < edge_density < 0.20 and 0.5 < aspect < 2.0:
return "ui"
elif color_variance > 80:
return "photo"
else:
return "other"
def get_dominant_colors(image: np.ndarray, k: int = 3) -> List[Tuple[int, int, int]]:
"""Extract k dominant colors using k-means clustering."""
pixels = image.reshape(-1, 3).astype(np.float32)
# Subsample for speed
if len(pixels) > 10000:
indices = np.random.choice(len(pixels), 10000, replace=False)
pixels = pixels[indices]
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
_, labels, centers = cv2.kmeans(pixels, k, None, criteria, 3, cv2.KMEANS_PP_CENTERS)
# Sort by frequency
counts = Counter(labels.flatten())
sorted_centers = [centers[i] for i, _ in counts.most_common(k)]
return [(int(c[2]), int(c[1]), int(c[0])) for c in sorted_centers] # BGR -> RGB
def calculate_edge_density(gray: np.ndarray) -> float:
"""Calculate edge density using Canny edge detection."""
edges = cv2.Canny(gray, 50, 150)
return np.count_nonzero(edges) / edges.size
def estimate_text_regions(gray: np.ndarray) -> float:
"""
Estimate ratio of image containing text-like regions.
Uses morphological operations to find text blocks.
"""
# Threshold
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# Dilate to connect text characters
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 3))
dilated = cv2.dilate(binary, kernel, iterations=2)
# Find contours
contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Filter by aspect ratio (text regions are usually wide)
text_area = 0
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
if w > h * 2 and w > 20: # Wide and reasonably sized
text_area += w * h
return text_area / (gray.shape[0] * gray.shape[1])
def analyze_image(path: str, thumbnail_size: int = 128) -> ImageAnalysis:
"""
Analyze a single image and return structured metadata.
"""
img = cv2.imread(path)
if img is None:
raise ValueError(f"Could not load image: {path}")
height, width = img.shape[:2]
aspect = width / height
# Convert to RGB for color analysis
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Metrics
dominant_colors = get_dominant_colors(rgb)
edge_density = calculate_edge_density(gray)
text_ratio = estimate_text_regions(gray)
color_variance = np.std(rgb)
# Classification
classification = classify_image(edge_density, color_variance, aspect)
# Thumbnail
scale = thumbnail_size / max(width, height)
thumb = cv2.resize(rgb, (int(width * scale), int(height * scale)))
return ImageAnalysis(
filename=os.path.basename(path),
path=path,
width=width,
height=height,
aspect_ratio=round(aspect, 2),
classification=classification,
dominant_colors=dominant_colors,
edge_density=round(edge_density, 4),
text_region_ratio=round(text_ratio, 4),
thumbnail=thumb
)
def batch_analyze(folder: str, extensions: List[str] = None) -> List[ImageAnalysis]:
"""
Analyze all images in a folder.
Args:
folder: Path to folder containing images.
extensions: List of valid extensions (default: ['.png', '.jpg', '.jpeg']).
Returns:
List of ImageAnalysis results.
"""
if extensions is None:
extensions = ['.png', '.jpg', '.jpeg', '.bmp', '.webp']
results = []
for filename in os.listdir(folder):
ext = os.path.splitext(filename)[1].lower()
if ext in extensions:
path = os.path.join(folder, filename)
try:
analysis = analyze_image(path)
results.append(analysis)
except Exception as e:
print(f"[ANALYZER] Error processing {filename}: {e}")
return results
def summarize_analysis(results: List[ImageAnalysis]) -> Dict:
"""Generate summary statistics from batch analysis."""
if not results:
return {"count": 0}
classifications = Counter(r.classification for r in results)
avg_edge = sum(r.edge_density for r in results) / len(results)
avg_text = sum(r.text_region_ratio for r in results) / len(results)
return {
"count": len(results),
"classifications": dict(classifications),
"avg_edge_density": round(avg_edge, 4),
"avg_text_ratio": round(avg_text, 4),
"total_size_mb": round(sum(r.width * r.height * 3 for r in results) / (1024 * 1024), 2)
}
# CLI for standalone testing
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: python image_analyzer.py <folder_path>")
sys.exit(1)
folder = sys.argv[1]
print(f"[ANALYZER] Processing folder: {folder}")
results = batch_analyze(folder)
summary = summarize_analysis(results)
print(f"\n[SUMMARY]")
print(f" Total Images: {summary['count']}")
print(f" Classifications: {summary['classifications']}")
print(f" Avg Edge Density: {summary['avg_edge_density']}")
print(f" Avg Text Ratio: {summary['avg_text_ratio']}")
|