Spaces:
Sleeping
Sleeping
File size: 10,433 Bytes
725cb98 5ae52ca 6f52b91 725cb98 3435684 5ae52ca 3435684 5ae52ca 3435684 5ae52ca 3435684 5ae52ca 3435684 5ae52ca 3435684 725cb98 5ae52ca 3435684 2c1aa80 3435684 5ae52ca 3435684 5ae52ca 725cb98 3435684 725cb98 2c1aa80 3435684 5ae52ca 3435684 725cb98 3435684 725cb98 3435684 5ae52ca 725cb98 5ae52ca 3435684 725cb98 6f52b91 725cb98 3435684 725cb98 3435684 6f52b91 3435684 6f52b91 3435684 |
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 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
import os
import numpy as np
import cv2
from PIL import Image
import base64
from io import BytesIO
# ---------------------------------------------------------------------
# Find solid strips (low complexity horizontal regions)
# ---------------------------------------------------------------------
def analyze_horizontal_complexity(gray, window_size=5):
"""
Analyze complexity of each horizontal strip in the image.
Returns array of complexity scores (lower = more suitable for splitting).
Args:
gray: Grayscale image
window_size: Height of strip to analyze
Returns:
Array of complexity scores for each row
"""
h, w = gray.shape
# Detect edges
edges = cv2.Canny(gray, 80, 160)
# Calculate variance (texture complexity) and edge density for each row
complexity_scores = []
for y in range(h):
# Define window around this row
y_start = max(0, y - window_size // 2)
y_end = min(h, y + window_size // 2)
window = gray[y_start:y_end, :]
edge_window = edges[y_start:y_end, :]
# Edge density
edge_score = np.sum(edge_window) / (w * (y_end - y_start))
# Variance (texture)
variance_score = np.var(window)
# Combined score (normalized)
combined = edge_score + variance_score / 255.0
complexity_scores.append(combined)
return np.array(complexity_scores)
def find_solid_strips(gray, min_strip_height=10, complexity_threshold=0.1):
"""
Find all solid/low-complexity horizontal strips suitable for splitting.
Args:
gray: Grayscale image
min_strip_height: Minimum consecutive rows with low complexity
complexity_threshold: Maximum complexity score (lower = stricter)
Returns:
List of (start_y, end_y, score) tuples for solid strips
"""
h = gray.shape[0]
complexity = analyze_horizontal_complexity(gray)
# Normalize complexity scores
if complexity.max() > 0:
complexity = complexity / complexity.max()
# Find runs of low complexity
is_simple = complexity < complexity_threshold
strips = []
start = None
for i in range(h):
if is_simple[i]:
if start is None:
start = i
else:
if start is not None:
# End of strip
if i - start >= min_strip_height:
avg_score = np.mean(complexity[start:i])
strips.append((start, i, avg_score))
start = None
# Handle strip at end of image
if start is not None and h - start >= min_strip_height:
avg_score = np.mean(complexity[start:h])
strips.append((start, h, avg_score))
# Sort by score (best strips first)
strips.sort(key=lambda x: x[2])
return strips
def find_best_split_location(gray, target_row, search_pct=0.2, prefer_solid_strips=True):
"""
Find the best row near target_row for splitting.
Args:
gray: Grayscale image
target_row: Desired split location
search_pct: Search radius as percentage of image height
prefer_solid_strips: If True, strongly prefer solid strips
Returns:
Best row index for splitting
"""
h, w = gray.shape
search_radius = int(h * search_pct)
start = max(0, target_row - search_radius)
end = min(h - 1, target_row + search_radius)
if prefer_solid_strips:
# Find all solid strips in the search region
search_region = gray[start:end, :]
strips = find_solid_strips(search_region, min_strip_height=5, complexity_threshold=0.15)
if strips:
# Choose strip closest to target
best_strip = min(strips, key=lambda s: abs((s[0] + s[1]) // 2 - (target_row - start)))
# Return center of strip
return start + (best_strip[0] + best_strip[1]) // 2
# Fallback: use edge density
edges = cv2.Canny(gray, 80, 160)
row_scores = edges[start:end].sum(axis=1)
best_local_idx = np.argmin(row_scores)
return start + best_local_idx
def find_optimal_splits(gray, desired_chunks, min_chunk_height=200):
"""
Find optimal split locations, potentially returning fewer chunks if
good split points don't exist.
Args:
gray: Grayscale image
desired_chunks: Target number of chunks
min_chunk_height: Minimum height for each chunk
Returns:
List of split points (y-coordinates)
"""
h = gray.shape[0]
# If image too small for desired chunks, reduce
max_possible_chunks = max(1, h // min_chunk_height)
actual_chunks = min(desired_chunks, max_possible_chunks)
if actual_chunks <= 1:
print(f"⚠️ Image too small for multiple chunks ({h}px height)")
return [0, h]
# Find all solid strips
solid_strips = find_solid_strips(gray, min_strip_height=10, complexity_threshold=0.12)
if not solid_strips:
print("⚠️ No solid strips found, using uniform splits")
# Fallback to uniform splits
splits = [int(i * h / actual_chunks) for i in range(actual_chunks + 1)]
return splits
print(f"✓ Found {len(solid_strips)} solid strips")
# Calculate ideal split locations
ideal_splits = [int(i * h / actual_chunks) for i in range(1, actual_chunks)]
# Match each ideal split to nearest solid strip
actual_splits = [0] # Start
for target in ideal_splits:
# Find closest solid strip center
best_strip = min(solid_strips, key=lambda s: abs((s[0] + s[1]) // 2 - target))
split_y = (best_strip[0] + best_strip[1]) // 2
# Ensure minimum spacing from previous split
if split_y - actual_splits[-1] >= min_chunk_height:
actual_splits.append(split_y)
else:
print(f"⚠️ Skipping split at {split_y} (too close to previous)")
actual_splits.append(h) # End
num_resulting_chunks = len(actual_splits) - 1
if num_resulting_chunks < desired_chunks:
print(f"ℹ️ Returning {num_resulting_chunks} chunks (requested {desired_chunks}, but not enough good split points)")
return actual_splits
# ---------------------------------------------------------------------
# Load & Split Image (Enhanced)
# ---------------------------------------------------------------------
def load_and_split_image(file_obj, num_chunks, min_chunk_height=200, allow_fewer_chunks=True):
"""
Loads an image and splits it intelligently across solid strips.
Can return fewer chunks than requested if good split points don't exist.
Args:
file_obj: File object or path
num_chunks: Desired number of chunks
min_chunk_height: Minimum height per chunk (pixels)
allow_fewer_chunks: If True, can return < num_chunks
Returns:
(filename, original_image, list_of_chunks)
"""
if file_obj is not None:
image_path = file_obj.name if hasattr(file_obj, "name") else file_obj
filename = os.path.basename(image_path)
else:
image_path = "00_sample.jpg"
filename = "00_sample.jpg"
# Load original image
image = Image.open(image_path).convert("RGB")
width, height = image.size
print(f"📏 Image size: {width}x{height}")
# Convert to OpenCV for analysis
img_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)
# If only 1 chunk requested, no split needed
if num_chunks <= 1:
return filename, image, [image]
# Find optimal split locations
if allow_fewer_chunks:
split_points = find_optimal_splits(gray, num_chunks, min_chunk_height)
else:
# Old behavior: always return exact number of chunks
approx_points = [int(i * height / num_chunks) for i in range(1, num_chunks)]
split_points = [0]
for pt in approx_points:
best = find_best_split_location(gray, target_row=pt, prefer_solid_strips=True)
split_points.append(best)
split_points.append(height)
# Produce final chunks
chunks = []
num_actual_chunks = len(split_points) - 1
for i in range(num_actual_chunks):
top = split_points[i]
bottom = split_points[i + 1]
chunk = image.crop((0, top, width, bottom))
chunks.append(chunk)
print(f" Chunk {i+1}: rows {top}-{bottom} (height: {bottom-top}px)")
print(f"✅ Split into {len(chunks)} chunks")
return filename, image, chunks
# ---------------------------------------------------------------------
# Visualization Helper
# ---------------------------------------------------------------------
def visualize_split_analysis(gray, split_points):
"""
Create a visualization showing complexity analysis and split points.
Useful for debugging split decisions.
"""
h, w = gray.shape
# Analyze complexity
complexity = analyze_horizontal_complexity(gray)
# Create visualization
vis = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
# Draw complexity heatmap on the side
heatmap_width = 50
heatmap = np.zeros((h, heatmap_width, 3), dtype=np.uint8)
normalized_complexity = (complexity / complexity.max() * 255).astype(np.uint8)
for y in range(h):
color_val = normalized_complexity[y]
heatmap[y, :] = [0, 255 - color_val, color_val] # Green=low, Red=high
# Draw split lines
for split_y in split_points[1:-1]: # Skip first and last
cv2.line(vis, (0, split_y), (w, split_y), (0, 255, 0), 2)
# Combine
result = np.hstack([vis, heatmap])
return result
# ---------------------------------------------------------------------
# Encode Image to HTML
# ---------------------------------------------------------------------
def encode_image_to_html(image: Image.Image) -> str:
buffered = BytesIO()
image.save(buffered, format="PNG")
encoded = base64.b64encode(buffered.getvalue()).decode()
return f"""
<div style="height:500px; overflow-y:auto; border:1px solid #ccc;">
<img src="data:image/png;base64,{encoded}" style="width:100%;" />
</div>
""" |