Spaces:
Sleeping
Sleeping
File size: 11,847 Bytes
2cf467c | 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 | import numpy as np
from typing import Tuple
from .mask_utils import calculate_mask, expand_mask
import os
from PIL import Image
def find_best_size_and_position(main_mask: np.ndarray, image_content: str, padding: int, mode: str = "side", chart_bbox: dict = None, avoid_mask: np.ndarray = None) -> Tuple[int, int, int]:
"""
通过降采样加速查找最佳图片尺寸和位置
Args:
main_mask: 主要内容的mask
image_content: base64图片内容
padding: 边界padding
mode: 放置模式,可选"side"、"background"或"overlay"
chart_bbox: 图表边界框,格式为{"x": x, "y": y, "width": width, "height": height}
avoid_mask: 需要避免重叠的区域mask
Returns:
Tuple[int, int, int]: (image_size, best_x, best_y)
"""
# Save the main_mask to PNG for debugging
os.makedirs('tmp', exist_ok=True)
mask_image = Image.fromarray((main_mask * 255).astype(np.uint8))
mask_image.save('tmp/main_mask.png')
grid_size = 5
# 将main_mask降采样到1/grid_size大小
h, w = main_mask.shape
downsampled_h = h // grid_size
downsampled_w = w // grid_size
downsampled_main = np.zeros((downsampled_h, downsampled_w), dtype=np.uint8)
# 对每个grid进行降采样,只要原grid中有内容(1)就标记为1
for i in range(downsampled_h):
for j in range(downsampled_w):
y_start = max(0, (i - 1) * (grid_size))
x_start = max(0, (j - 1) * (grid_size))
y_end = min((i + 2) * (grid_size), h)
x_end = min((j + 2) * (grid_size), w)
grid = main_mask[y_start:y_end, x_start:x_end]
downsampled_main[i, j] = 1 if np.any(grid == 1) else 0
# 如果有avoid_mask,也进行降采样
downsampled_avoid = None
if avoid_mask is not None:
downsampled_avoid = np.zeros((downsampled_h, downsampled_w), dtype=np.uint8)
for i in range(downsampled_h):
for j in range(downsampled_w):
y_start = max(0, (i - 1) * (grid_size))
x_start = max(0, (j - 1) * (grid_size))
y_end = min((i + 2) * (grid_size), h)
x_end = min((j + 2) * (grid_size), w)
grid = avoid_mask[y_start:y_end, x_start:x_end]
downsampled_avoid[i, j] = 1 if np.any(grid == 1) else 0
# 调整padding到降采样尺度
downsampled_padding = max(1, padding // grid_size)
# 二分查找最佳尺寸
min_size = max(1, 64 // grid_size) # 最小尺寸也要降采样
max_size = int(min(downsampled_main.shape) * 1)
best_size = min_size
best_x = downsampled_padding
best_y = downsampled_padding
if mode == "side":
best_overlap_ratio = float('inf')
elif mode == "background":
best_overlap_ratio = float('inf')
else:
best_overlap_ratio = 0
overlap_threshold = 0.01
if mode == "side":
overlap_threshold = 0.01
elif mode == "background":
overlap_threshold = 0.05
elif mode == "overlay":
overlap_threshold = 0.97
while max_size - min_size >= 2: # 由于降采样,可以用更小的阈值
mid_size = (min_size + max_size) // 2
# 生成当前尺寸的图片mask并降采样
original_size = mid_size * grid_size
temp_svg = f"""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{original_size}" height="{original_size}">
<image width="{original_size}" height="{original_size}" href="{image_content}"/>
</svg>"""
image_mask = calculate_mask(temp_svg, original_size, original_size, 0, grid_size=grid_size, bg_threshold=240)
if mode == "background":
image_mask = expand_mask(image_mask, 10)
# Save the original image mask to PNG for debugging
os.makedirs('tmp', exist_ok=True)
mask_image = Image.fromarray((image_mask * 255).astype(np.uint8))
mask_image.save('tmp/image_mask.png')
# 将image_mask降采样
downsampled_image = np.zeros((mid_size, mid_size), dtype=np.uint8)
for i in range(mid_size):
for j in range(mid_size):
y_start = max(0, (i - 1) * (grid_size))
x_start = max(0, (j - 1) * (grid_size))
y_end = min((i + 2) * (grid_size), original_size)
x_end = min((j + 2) * (grid_size), original_size)
grid = image_mask[y_start:y_end, x_start:x_end]
downsampled_image[i, j] = 1 if np.any(grid == 1) else 0
# 计算有效的搜索范围
if mode == "background" and chart_bbox is not None:
# 将chart_bbox转换到降采样尺度
chart_x = max(0, chart_bbox["x"] // grid_size)
chart_y = max(0, chart_bbox["y"] // grid_size)
chart_width = min(chart_bbox["width"] // grid_size, downsampled_w - chart_x)
chart_height = min(chart_bbox["height"] // grid_size, downsampled_h - chart_y)
# 确保搜索范围在chart_bbox内
y_range = chart_height - mid_size - downsampled_padding * 2
x_range = chart_width - mid_size - downsampled_padding * 2
if y_range <= 0 or x_range <= 0:
max_size = mid_size - 1
continue
else:
y_range = downsampled_h - mid_size - downsampled_padding * 2
x_range = downsampled_w - mid_size - downsampled_padding * 2
if y_range <= 0 or x_range <= 0:
max_size = mid_size - 1
continue
# 在降采样空间中寻找最佳位置
min_overlap = float('inf')
if mode == "side" or mode == "background":
min_overlap = float('inf')
elif mode == "overlay":
min_overlap = 0
current_x = downsampled_padding
current_y = downsampled_padding
min_distance = float('inf')
mask_center_x = np.mean(np.where(downsampled_main == 1)[1]) if np.any(downsampled_main == 1) else downsampled_w // 2
mask_center_y = np.mean(np.where(downsampled_main == 1)[0]) if np.any(downsampled_main == 1) else downsampled_h // 2
if mode == "background" and chart_bbox is not None:
y_start = chart_y + downsampled_padding
y_end = chart_y + chart_height - mid_size - downsampled_padding + 1
x_start = chart_x + downsampled_padding
x_end = chart_x + chart_width - mid_size - downsampled_padding + 1
else:
y_start = downsampled_padding
y_end = downsampled_h - mid_size - downsampled_padding + 1
x_start = downsampled_padding
x_end = downsampled_w - mid_size - downsampled_padding + 1
for y in range(y_start, y_end):
for x in range(x_start, x_end):
region = downsampled_main[y:y + mid_size, x:x + mid_size]
overlap = np.sum((region == 1) & (downsampled_image == 1))
total = np.sum(downsampled_image == 1)
overlap_ratio = overlap / total if total > 0 else 1.0
# 检查与avoid_mask的重叠
avoid_overlap = 0
if downsampled_avoid is not None:
avoid_region = downsampled_avoid[y:y + mid_size, x:x + mid_size]
avoid_overlap = np.sum((avoid_region == 1) & (downsampled_image == 1))
if mode == "side" or mode == "background":
if mode == "background" and chart_bbox is not None:
distance_to_left = x - (chart_x + downsampled_padding)
distance_to_right = (chart_x + chart_width - mid_size - downsampled_padding) - x
distance_to_top = y - (chart_y + downsampled_padding)
distance_to_bottom = (chart_y + chart_height - mid_size - downsampled_padding) - y
else:
distance_to_left = x - downsampled_padding
distance_to_right = downsampled_w - mid_size - downsampled_padding - x
distance_to_top = y - downsampled_padding
distance_to_bottom = downsampled_h - mid_size - downsampled_padding - y
distance_to_border = min(distance_to_left, distance_to_right, distance_to_top, distance_to_bottom)
if overlap_ratio < min_overlap or (overlap_ratio < overlap_threshold and distance_to_border < min_distance):
min_overlap = overlap_ratio
current_x = x
current_y = y
min_distance = distance_to_border
elif mode == "overlay":
# 对于overlay模式,需要同时满足与main_mask的重叠足够大,且与avoid_mask没有重叠
if avoid_overlap > 0:
continue # 跳过与avoid_mask有重叠的位置
distance_to_center = np.sqrt(((x + mid_size / 2 - mask_center_x) ** 2 + (y + mid_size / 2 - mask_center_y) ** 2))
if overlap_ratio > min_overlap or (overlap_ratio > overlap_threshold and distance_to_center < min_distance):
min_overlap = overlap_ratio
current_x = x
current_y = y
min_distance = distance_to_center
# print(f"Trying size {mid_size * grid_size}x{mid_size * grid_size}, minimum overlap ratio: {min_overlap:.3f}")
if mode == "side" or mode == "background":
if min_overlap < overlap_threshold:
best_size = mid_size
best_overlap_ratio = min_overlap
best_x = current_x
best_y = current_y
min_size = mid_size + 1
else:
max_size = mid_size - 1
elif mode == "overlay":
if min_overlap > overlap_threshold:
best_size = mid_size
best_overlap_ratio = min_overlap
best_x = current_x
best_y = current_y
min_size = mid_size + 1
else:
max_size = mid_size - 1
if best_overlap_ratio > overlap_threshold and (mode == "side" or mode == "background"):
return 0, 0, 0
if best_overlap_ratio < overlap_threshold and mode == "overlay":
return 0, 0, 0
final_size = best_size * grid_size
final_x = best_x * grid_size
final_y = best_y * grid_size
'''
# 生成最终尺寸的图片mask
temp_svg = f"""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{final_size}" height="{final_size}">
<image width="{final_size}" height="{final_size}" href="{image_content}"/>
</svg>"""
final_image_mask = calculate_mask(temp_svg, final_size, final_size, 0)
# 创建合并的mask,将image_mask放在正确的位置
combined_mask = np.zeros_like(main_mask)
combined_mask[main_mask == 1] = 1
# 将image_mask放在正确的位置
combined_mask[final_y:final_y + final_size, final_x:final_x + final_size] = np.where(final_image_mask == 1, 2, combined_mask[final_y:final_y + final_size, final_x:final_x + final_size])
# 保存合并的mask
combined_image = Image.fromarray((combined_mask * 127).astype(np.uint8))
combined_image.save('tmp/all_mask.png')
print(f"Final result: size={final_size}x{final_size}, position=({final_x}, {final_y}), overlap ratio={best_overlap_ratio:.3f}")
'''
return final_size, final_x, final_y |