feat(wordcloud): 收口在途开发(布局/存储/前端)+ R4 WCD 生产任务(jobs wcd_file)与生产订单列表
This commit is contained in:
+314
-85
@@ -15,10 +15,13 @@ from .fonts import get_cached_font
|
||||
from .layout import OptimizedEfficientWordCloud
|
||||
from .mask import analyze_mask, apply_safe_padding, calculate_dynamic_dimensions, prepare_mask
|
||||
from .render import (
|
||||
compute_coverage_score,
|
||||
compute_fill_ratio_fast,
|
||||
count_layout_overlap_pixels,
|
||||
largest_empty_square_size,
|
||||
append_layout_with_hd_clearance,
|
||||
refine_layout_with_hd_clearance,
|
||||
render_layout_occupancy,
|
||||
scale_layout_for_hd,
|
||||
)
|
||||
from .weights import (
|
||||
@@ -127,7 +130,7 @@ def run_generation_pass(
|
||||
max_font = min(max_font, hard_max_font)
|
||||
return min_font, max(min_font, max_font)
|
||||
|
||||
def try_place(scale, layout_seed=base_layout_seed):
|
||||
def try_place(scale, layout_seed=base_layout_seed, probe=False):
|
||||
min_font, max_font = scaled_bounds(scale)
|
||||
wc = OptimizedEfficientWordCloud(
|
||||
width=w_small,
|
||||
@@ -138,12 +141,23 @@ def run_generation_pass(
|
||||
min_font_size=min_font,
|
||||
max_font_size=max_font,
|
||||
background_color=config.get_output_background(),
|
||||
prefer_horizontal=0.82,
|
||||
# Each word independently draws horizontal vs vertical, so the mix
|
||||
# is scattered rather than banded. VERTICAL_RATIO is the chance of
|
||||
# a vertical word; mixing orientations is one of the cheapest ways
|
||||
# to break up an over-regular grid-like look.
|
||||
prefer_horizontal=1.0 - float(config.VERTICAL_RATIO),
|
||||
# HD clearance is applied after scaling. Keeping the coarse-grid
|
||||
# margin at zero avoids turning 1 HD pixel into 5-6 output pixels.
|
||||
margin=_collision_margin,
|
||||
)
|
||||
wc.layout_seed = layout_seed
|
||||
# A probe only needs to answer "does every word fit at this scale?", so
|
||||
# it stops at the first word that cannot be placed. The answer is exact
|
||||
# -- a word is only reported unplaced once an exhaustive scan has ruled
|
||||
# out every position -- and it avoids paying for a doomed batch's
|
||||
# remaining failures, each of which is far more expensive than a
|
||||
# successful placement.
|
||||
wc.max_failures = 1 if probe else None
|
||||
wc.generate_from_frequencies(frequencies_data)
|
||||
return wc, len(wc.layout_), min_font, max_font
|
||||
|
||||
@@ -165,55 +179,99 @@ def run_generation_pass(
|
||||
best_wc = None
|
||||
best_count = 0
|
||||
best_scale = 1.0
|
||||
scale = 1.0
|
||||
best_coverage = -1.0 # shape-aware coverage of the current best candidate
|
||||
best_occ = None # occupancy raster of the current best candidate
|
||||
tried_layouts = set()
|
||||
failed_scales = []
|
||||
for attempt in range(1, 4):
|
||||
attempt = 0
|
||||
|
||||
def probe_scale(scale):
|
||||
"""Lay out every word at `scale`; return (wc, placed_count, complete)."""
|
||||
nonlocal attempt, best_wc, best_count, best_scale
|
||||
nonlocal best_coverage, best_occ
|
||||
attempt += 1
|
||||
bounds = scaled_bounds(scale)
|
||||
layout_key = (bounds, base_layout_seed)
|
||||
if layout_key in tried_layouts:
|
||||
break
|
||||
tried_layouts.add(layout_key)
|
||||
wc, placed_count, min_font, max_font = try_place(scale)
|
||||
tried_layouts.add((bounds, base_layout_seed))
|
||||
wc, placed_count, min_font, max_font = try_place(scale, probe=True)
|
||||
complete = placed_count >= total_target
|
||||
print(
|
||||
f" 整批布局 #{attempt}: scale={scale:.3f}, "
|
||||
f"字号=[{min_font}, {max_font}] -> {placed_count}/{total_target}"
|
||||
f"字号=[{min_font}, {max_font}] -> "
|
||||
f"{'完整' if complete else '不足'} ({placed_count}/{total_target})"
|
||||
)
|
||||
log.info(
|
||||
" 整批布局 #%d scale=%.3f 字号=[%d,%d] -> %d/%d",
|
||||
attempt,
|
||||
scale,
|
||||
min_font,
|
||||
max_font,
|
||||
placed_count,
|
||||
total_target,
|
||||
" 整批布局 #%d scale=%.3f 字号=[%d,%d] -> %d/%d complete=%s",
|
||||
attempt, scale, min_font, max_font, placed_count, total_target, complete,
|
||||
)
|
||||
if placed_count > best_count:
|
||||
best_wc = wc
|
||||
best_count = placed_count
|
||||
best_scale = scale
|
||||
if placed_count >= total_target:
|
||||
final_wc = wc
|
||||
final_scale = scale
|
||||
final_layout_seed = base_layout_seed
|
||||
if complete:
|
||||
# Among complete layouts prefer the one whose ink reaches furthest
|
||||
# into the mask shape, not merely the largest font scale. A Fermat
|
||||
# spiral packs words into a disc around the fillable centroid; with
|
||||
# few words or large fonts that disc stays small and never reaches
|
||||
# the mask's protrusions, so the cloud reads as a circle instead of
|
||||
# the intended silhouette. Coverage rewards layouts that spread into
|
||||
# those deep regions, letting a slightly smaller scale win when it
|
||||
# trades font size for a recognisable outline. Scale is the tie-
|
||||
# breaker so an equal-coverage denser picture is still preferred.
|
||||
occ = render_layout_occupancy(wc.layout_, mask_small.shape, config.WC_FONT_PATH)
|
||||
coverage = compute_coverage_score(occ, mask_small)
|
||||
log.info(
|
||||
" 整批布局 #%d coverage=%.4f (best=%.4f)",
|
||||
attempt, coverage, best_coverage,
|
||||
)
|
||||
if coverage > best_coverage or (
|
||||
coverage == best_coverage and scale > best_scale
|
||||
):
|
||||
best_wc, best_count, best_scale = wc, placed_count, scale
|
||||
best_coverage, best_occ = coverage, occ
|
||||
if not complete:
|
||||
failed_scales.append(scale)
|
||||
return wc, placed_count, complete
|
||||
|
||||
# Find the largest scale at which every word still fits. Bigger is strictly
|
||||
# better here: the same names drawn larger leave less blank space. A probe
|
||||
# answers feasibility exactly and stops at the first unplaceable word, so
|
||||
# searching for the best scale costs little more than accepting the first
|
||||
# one that happens to work.
|
||||
lo = None # largest scale known to fit everything
|
||||
hi = None # smallest scale known to be too big
|
||||
scale = 1.0
|
||||
for _ in range(2 if config.FAST_MODE else 4):
|
||||
wc, placed_count, complete = probe_scale(scale)
|
||||
if complete:
|
||||
lo = scale
|
||||
break
|
||||
|
||||
failed_scales.append(scale)
|
||||
|
||||
hi = scale
|
||||
placed_ratio = placed_count / max(1, total_target)
|
||||
# Required box area is roughly proportional to size². The extra
|
||||
# safety margin absorbs fragmentation without wasting a binary search.
|
||||
shrink = 0.62 if placed_ratio <= 0 else min(0.92, max(0.58, math.sqrt(placed_ratio) * 0.92))
|
||||
# Area scales with size², so linear size scales with sqrt(ratio). The
|
||||
# probe stops early, which understates how many words would have fit,
|
||||
# so this deliberately undershoots and the bisection below climbs back.
|
||||
shrink = 0.62 if placed_ratio <= 0 else min(0.92, max(0.55, math.sqrt(placed_ratio) * 0.92))
|
||||
scale *= shrink
|
||||
|
||||
if final_wc is None:
|
||||
# Close the gap between the largest failing scale and the smallest passing
|
||||
# one. Each step recovers font size that the shrink above gave away.
|
||||
if lo is not None and hi is not None:
|
||||
for _ in range(1 if config.FAST_MODE else 3):
|
||||
mid = (lo + hi) / 2.0
|
||||
if hi - lo < 0.02 or scaled_bounds(mid) == scaled_bounds(lo):
|
||||
break
|
||||
_wc, _placed, complete = probe_scale(mid)
|
||||
if complete:
|
||||
lo = mid
|
||||
else:
|
||||
hi = mid
|
||||
|
||||
if best_wc is not None:
|
||||
final_wc = best_wc
|
||||
final_scale = best_scale
|
||||
final_layout_seed = base_layout_seed
|
||||
|
||||
if final_wc is None:
|
||||
return {
|
||||
"wc": None,
|
||||
"fill_ratio": 0.0,
|
||||
"coverage": 0.0,
|
||||
"occ_fast": None,
|
||||
"w_small": w_small,
|
||||
"h_small": h_small,
|
||||
@@ -250,10 +308,16 @@ def run_generation_pass(
|
||||
debug_dir.mkdir(parents=True, exist_ok=True)
|
||||
Image.fromarray((occ_fast * 255).astype(np.uint8)).save(str(debug_dir / "occ_fast.png"))
|
||||
|
||||
# Probe larger whole-cloud layouts and keep the largest complete one. If
|
||||
# an earlier batch was too large, search the discrete interval between the
|
||||
# complete and failed scales instead of accepting an over-aggressive
|
||||
# shrink. Every probe rebuilds the entire cloud with one shared scale.
|
||||
# Probe whole-cloud layouts in BOTH font-size directions and keep the one
|
||||
# whose ink reaches furthest into the mask shape. The original search only
|
||||
# grew the font (chasing a higher pixel fill ratio), but a Fermat spiral
|
||||
# packs words into a disc around the centroid: growing the font shrinks that
|
||||
# disc, so an under-filled silhouette gets *more* circular, not less.
|
||||
# Shrinking the font lets the spiral walk further out and reach the mask's
|
||||
# protrusions, which raises shape coverage even when the raw fill ratio
|
||||
# drops a little. Both directions are probed each round and the higher-
|
||||
# coverage candidate wins; the loop stops when neither improves coverage.
|
||||
# Every probe rebuilds the entire cloud with one shared scale.
|
||||
if (
|
||||
len(final_wc.layout_) >= total_target
|
||||
and fill_ratio > 0
|
||||
@@ -262,11 +326,22 @@ def run_generation_pass(
|
||||
or has_character_sized_hole(final_wc, largest_empty_square)
|
||||
)
|
||||
):
|
||||
current_coverage = compute_coverage_score(occ_fast, mask_small)
|
||||
free_px = max(1, int(np.sum(mask_small == 0)))
|
||||
upper_scale = min(
|
||||
(failed for failed in failed_scales if failed > final_scale),
|
||||
default=None,
|
||||
)
|
||||
for density_attempt in range(1, 5):
|
||||
|
||||
def _fill_and_coverage(wc):
|
||||
occ = render_layout_occupancy(wc.layout_, mask_small.shape, config.WC_FONT_PATH)
|
||||
new_fill = float(np.sum((mask_small == 0) & (occ == 1))) / free_px
|
||||
cov = compute_coverage_score(occ, mask_small)
|
||||
return new_fill, cov, occ
|
||||
|
||||
for density_attempt in range(1, 2 if config.FAST_MODE else 5):
|
||||
# Grow direction (larger font): bisect toward a known-too-big scale,
|
||||
# or nudge up by the fill-ratio deficit, exactly as before.
|
||||
if upper_scale is not None:
|
||||
grow_scale = (final_scale + upper_scale) / 2.0
|
||||
elif equal_size_mode:
|
||||
@@ -277,61 +352,79 @@ def run_generation_pass(
|
||||
1.12,
|
||||
math.sqrt(config.TARGET_FILL_RATIO / fill_ratio) * 0.98,
|
||||
)
|
||||
if desired_growth <= 1.005:
|
||||
break
|
||||
grow_scale = final_scale * desired_growth
|
||||
grow_scale = final_scale * desired_growth if desired_growth > 1.005 else None
|
||||
|
||||
grow_bounds = scaled_bounds(grow_scale)
|
||||
if grow_bounds == scaled_bounds(final_scale):
|
||||
break
|
||||
grow_min, grow_max = grow_bounds
|
||||
layout_key = (grow_bounds, base_layout_seed)
|
||||
attempted_layout = False
|
||||
if layout_key not in tried_layouts:
|
||||
tried_layouts.add(layout_key)
|
||||
wc, placed_count, _, _ = try_place(grow_scale)
|
||||
attempted_layout = True
|
||||
print(
|
||||
f" 密度优化 #{density_attempt}: scale={grow_scale:.3f}, "
|
||||
f"字号=[{grow_min}, {grow_max}] -> {placed_count}/{total_target}"
|
||||
)
|
||||
else:
|
||||
wc, placed_count = None, -1
|
||||
# Shrink direction (smaller font): the inverse nudge. Letting the
|
||||
# spiral walk further out costs font size but can reach protrusions
|
||||
# the grow direction abandons. Cap the shrink so one round cannot
|
||||
# collapse the font to the floor.
|
||||
shrink_scale = None
|
||||
if not equal_size_mode and fill_ratio > 0:
|
||||
shrink_factor = 1.0 / max(1.02, min(1.20, math.sqrt(fill_ratio / max(0.05, config.TARGET_FILL_RATIO)) * 1.02))
|
||||
cand = final_scale * shrink_factor
|
||||
if scaled_bounds(cand) != scaled_bounds(final_scale):
|
||||
shrink_scale = cand
|
||||
elif equal_size_mode:
|
||||
current_size, _ = scaled_bounds(final_scale)
|
||||
if current_size > hard_min_font:
|
||||
shrink_scale = (current_size - 1) / max(1, base_min_font)
|
||||
|
||||
selected_seed = base_layout_seed
|
||||
if placed_count < total_target and base_layout_seed is not None:
|
||||
# The reference library samples a fresh legal position order.
|
||||
# One bounded whole-cloud re-layout recovers dense solutions
|
||||
# without per-word shrinking or an unbounded random search.
|
||||
candidate_seed = (int(base_layout_seed) * 3 + 3) % (2**31 - 1)
|
||||
retry_key = (grow_bounds, candidate_seed)
|
||||
if retry_key not in tried_layouts:
|
||||
tried_layouts.add(retry_key)
|
||||
retry_wc, retry_count, _, _ = try_place(grow_scale, candidate_seed)
|
||||
attempted_layout = True
|
||||
candidates = []
|
||||
for direction, scale in (("grow", grow_scale), ("shrink", shrink_scale)):
|
||||
if scale is None or scaled_bounds(scale) == scaled_bounds(final_scale):
|
||||
continue
|
||||
bounds = scaled_bounds(scale)
|
||||
d_min, d_max = bounds
|
||||
key = (bounds, base_layout_seed)
|
||||
wc, placed_count, selected_seed = None, -1, base_layout_seed
|
||||
if key not in tried_layouts:
|
||||
tried_layouts.add(key)
|
||||
wc, placed_count, _, _ = try_place(scale)
|
||||
# Bounded seed retry to recover a complete layout, as before.
|
||||
if placed_count < total_target and base_layout_seed is not None:
|
||||
candidate_seed = (int(base_layout_seed) * 3 + 3) % (2**31 - 1)
|
||||
retry_key = (bounds, candidate_seed)
|
||||
if retry_key not in tried_layouts:
|
||||
tried_layouts.add(retry_key)
|
||||
retry_wc, retry_count, _, _ = try_place(scale, candidate_seed)
|
||||
print(
|
||||
f" 密度优化 #{density_attempt} {direction} 整批重排: "
|
||||
f"seed={candidate_seed}, 字号=[{d_min}, {d_max}] -> "
|
||||
f"{retry_count}/{total_target}"
|
||||
)
|
||||
if retry_count > placed_count:
|
||||
wc, placed_count = retry_wc, retry_count
|
||||
selected_seed = candidate_seed
|
||||
if placed_count < total_target:
|
||||
if direction == "grow":
|
||||
upper_scale = scale
|
||||
print(
|
||||
f" 密度优化 #{density_attempt} 整批重排: "
|
||||
f"seed={candidate_seed}, 字号=[{grow_min}, {grow_max}] -> "
|
||||
f"{retry_count}/{total_target}"
|
||||
f" 密度优化 #{density_attempt} {direction}: scale={scale:.3f}, "
|
||||
f"字号=[{d_min}, {d_max}] -> {placed_count}/{total_target} (不完整)"
|
||||
)
|
||||
if retry_count > placed_count:
|
||||
wc = retry_wc
|
||||
placed_count = retry_count
|
||||
selected_seed = candidate_seed
|
||||
if not attempted_layout:
|
||||
break
|
||||
if placed_count < total_target:
|
||||
upper_scale = grow_scale
|
||||
continue
|
||||
continue
|
||||
new_fill, cov, occ = _fill_and_coverage(wc)
|
||||
print(
|
||||
f" 密度优化 #{density_attempt} {direction}: scale={scale:.3f}, "
|
||||
f"字号=[{d_min}, {d_max}] -> {placed_count}/{total_target} "
|
||||
f"fill={new_fill:.3f} coverage={cov:.4f}"
|
||||
)
|
||||
candidates.append((direction, scale, selected_seed, wc, placed_count, new_fill, cov, occ))
|
||||
|
||||
new_fill, new_occ = compute_fill_ratio_fast(wc.layout_, mask_small, config.WC_FONT_PATH)
|
||||
if new_fill <= fill_ratio:
|
||||
if not candidates:
|
||||
break
|
||||
# Pick the higher-coverage candidate; tie-break on fill ratio so a
|
||||
# genuinely denser picture still wins when coverage is equal.
|
||||
candidates.sort(key=lambda c: (c[6], c[5]))
|
||||
direction, scale, selected_seed, wc, placed_count, new_fill, cov, occ = candidates[-1]
|
||||
if cov <= current_coverage and new_fill <= fill_ratio:
|
||||
break
|
||||
final_wc = wc
|
||||
final_scale = grow_scale
|
||||
final_scale = scale
|
||||
final_layout_seed = selected_seed
|
||||
fill_ratio = new_fill
|
||||
occ_fast = new_occ
|
||||
occ_fast = occ
|
||||
current_coverage = cov
|
||||
largest_empty_square = largest_empty_square_size(occ_fast, mask_small)
|
||||
complete_candidates.append(
|
||||
(final_wc, final_scale, final_layout_seed, fill_ratio, occ_fast)
|
||||
@@ -339,6 +432,7 @@ def run_generation_pass(
|
||||
if (
|
||||
fill_ratio >= config.TARGET_FILL_RATIO * 0.98
|
||||
and not has_character_sized_hole(final_wc, largest_empty_square)
|
||||
and current_coverage >= 0.98
|
||||
):
|
||||
break
|
||||
|
||||
@@ -350,6 +444,7 @@ def run_generation_pass(
|
||||
and len(final_wc.layout_) >= total_target
|
||||
and has_character_sized_hole(final_wc, largest_empty_square)
|
||||
and base_layout_seed is not None
|
||||
and not config.FAST_MODE
|
||||
):
|
||||
if total_target < 100:
|
||||
hole_attempt_budget = 3
|
||||
@@ -389,6 +484,77 @@ def run_generation_pass(
|
||||
(final_wc, final_scale, final_layout_seed, fill_ratio, occ_fast)
|
||||
)
|
||||
|
||||
# ── 工作网格增量填充:原名字号不变,用更小字号追加副本填轮廓 ──
|
||||
# 已填区域标记为阻挡,新词只能进空白间隙。每一轮只生成一份名单,
|
||||
# 并把新占用合并回工作网格;因此自动填充没有固定的重复数量,
|
||||
# 只在轮廓仍未覆盖且还有合法位置时继续追加。
|
||||
fill_work_layout = []
|
||||
if (
|
||||
config.AUTO_REPEAT_TO_FILL
|
||||
and final_wc is not None
|
||||
and len(final_wc.layout_) >= total_target
|
||||
and occ_fast is not None
|
||||
):
|
||||
current_cov = compute_coverage_score(occ_fast, mask_small)
|
||||
if current_cov < 0.95 and names:
|
||||
# 原名字号范围
|
||||
original_sizes = [size for _, size, *_ in final_wc.layout_]
|
||||
orig_min_font = int(min(original_sizes))
|
||||
orig_max_font = int(max(original_sizes))
|
||||
span = orig_max_font - orig_min_font
|
||||
|
||||
# 追加用缩小字号:原最大字号的 50~60%
|
||||
fill_min_font = max(config.MIN_FONT_FLOOR, int(round(orig_min_font * 0.50)))
|
||||
fill_max_font = max(fill_min_font, int(round(orig_min_font + span * 0.60)))
|
||||
fill_seed = (final_layout_seed ^ 0x9E3779B9) & 0x7FFFFFFF
|
||||
if fill_seed == 0:
|
||||
fill_seed = 1
|
||||
|
||||
# AUTO_REPEAT_MAX 只是防止异常掩膜导致无限循环,不是目标重复次数。
|
||||
max_fill_rounds = max(1, int(config.AUTO_REPEAT_MAX))
|
||||
for fill_round in range(max_fill_rounds):
|
||||
if current_cov >= 0.95:
|
||||
break
|
||||
|
||||
# 融合 mask:原阻挡 + 已填区域都标为 255
|
||||
fill_mask = mask_small.copy()
|
||||
fill_mask[(occ_fast == 1)] = 255
|
||||
fill_wc = OptimizedEfficientWordCloud(
|
||||
width=w_small, height=h_small,
|
||||
mask=fill_mask,
|
||||
font_path=config.WC_FONT_PATH,
|
||||
# 一轮只追加一份名单;需要更多时由下一轮按需追加。
|
||||
max_words=len(names),
|
||||
min_font_size=fill_min_font,
|
||||
max_font_size=fill_max_font,
|
||||
background_color=config.get_output_background(),
|
||||
prefer_horizontal=1.0 - float(config.VERTICAL_RATIO),
|
||||
margin=_collision_margin,
|
||||
)
|
||||
fill_wc.layout_seed = (fill_seed + fill_round) & 0x7FFFFFFF or 1
|
||||
fill_freq = {name: 0.3 for name in names}
|
||||
fill_wc.generate_from_frequencies(fill_freq)
|
||||
|
||||
fill_placed = len(fill_wc.layout_)
|
||||
print(
|
||||
f"[增量填充#{fill_round + 1}] 工作网格追加放置 "
|
||||
f"{fill_placed}/{len(names)} 词,字号=[{fill_min_font}, {fill_max_font}]"
|
||||
)
|
||||
log.info(
|
||||
"[增量填充#%d] 工作网格追加放置 %d/%d 词, 字号=[%d,%d]",
|
||||
fill_round + 1, fill_placed, len(names), fill_min_font, fill_max_font,
|
||||
)
|
||||
if fill_placed <= 0:
|
||||
break
|
||||
|
||||
fill_work_layout.extend(fill_wc.layout_)
|
||||
fill_occ = render_layout_occupancy(
|
||||
fill_wc.layout_, mask_small.shape, config.WC_FONT_PATH
|
||||
)
|
||||
occ_fast = np.maximum(occ_fast, fill_occ)
|
||||
current_cov = compute_coverage_score(occ_fast, mask_small)
|
||||
print(f"[增量填充#{fill_round + 1}] 工作网格覆盖度={current_cov:.4f}")
|
||||
|
||||
hd_layout = None
|
||||
hd_clearance = None
|
||||
raw_hd_layout = []
|
||||
@@ -473,14 +639,73 @@ def run_generation_pass(
|
||||
config.WC_FONT_PATH,
|
||||
)
|
||||
|
||||
# ── 增量填充(工作网格 → HD) ──
|
||||
# 工作网格上的合法位置经过放大后可能因取整发生碰撞,因此填充词必须
|
||||
# 与基础布局一起再次做高清精修。若整批填充无法通过,则二分保留最多
|
||||
# 的追加词;绝不能让自动填充破坏原本已经成功的基础布局。
|
||||
if (
|
||||
fill_work_layout
|
||||
and hd_layout is not None
|
||||
):
|
||||
old_count = len(hd_layout)
|
||||
fill_hd = scale_layout_for_hd(fill_work_layout, config.WORK_SCALE)
|
||||
base_hd_layout = list(hd_layout)
|
||||
|
||||
accepted_additions, clearance_stats = append_layout_with_hd_clearance(
|
||||
base_hd_layout,
|
||||
fill_hd,
|
||||
mask_hd,
|
||||
config.WC_FONT_PATH,
|
||||
clearance=0,
|
||||
allow_global_search=False,
|
||||
)
|
||||
accepted_count = len(accepted_additions)
|
||||
accepted_clearance = 0
|
||||
hd_layout = base_hd_layout + accepted_additions
|
||||
hd_overlap_pixels = count_layout_overlap_pixels(
|
||||
hd_layout, (real_hd_h, real_hd_w), config.WC_FONT_PATH
|
||||
)
|
||||
new_fill, new_occ = compute_fill_ratio_fast(
|
||||
hd_layout, mask_hd, config.WC_FONT_PATH
|
||||
)
|
||||
new_cov = compute_coverage_score(new_occ, mask_hd) if new_occ is not None else 0.0
|
||||
clearance_stats = dict(clearance_stats or {})
|
||||
clearance_stats["clearance_px"] = accepted_clearance
|
||||
hd_clearance = clearance_stats
|
||||
print(
|
||||
f"[增量填充] 高清验收: {old_count}+{accepted_count}="
|
||||
f"{len(hd_layout)} 词, fill={new_fill:.3f}, "
|
||||
f"coverage={new_cov:.4f}, overlap={hd_overlap_pixels}"
|
||||
)
|
||||
print(
|
||||
f"[增量填充] 工作网格候选 {len(fill_work_layout)} 词,"
|
||||
f"高清接受 {len(hd_layout) - old_count} 词"
|
||||
)
|
||||
log.info(
|
||||
"[增量填充] HD 验收: base=%d candidate=%d accepted=%d fill=%.4f coverage=%.4f overlap=%d",
|
||||
old_count, len(fill_hd), len(hd_layout) - old_count, new_fill, new_cov, hd_overlap_pixels,
|
||||
)
|
||||
fill_ratio = new_fill
|
||||
occ_fast = new_occ
|
||||
largest_empty_square = largest_empty_square_size(occ_fast, mask_hd)
|
||||
|
||||
print(
|
||||
f"最终填充率: {fill_ratio:.3f} | 高清重叠像素: {hd_overlap_pixels} | "
|
||||
f"精修位移: {hd_clearance['shifted_words']} 词, 最大 {hd_clearance['max_shift']}px | "
|
||||
f"隔离带: {hd_clearance['clearance_px']}px"
|
||||
)
|
||||
# occ_fast 可能是工作网格或 HD 网格形状(增量填充后),按形状匹配计算覆盖度
|
||||
if occ_fast is not None and occ_fast.shape == mask_small.shape:
|
||||
coverage = compute_coverage_score(occ_fast, mask_small)
|
||||
elif occ_fast is not None and occ_fast.shape == mask_hd.shape:
|
||||
coverage = compute_coverage_score(occ_fast, mask_hd)
|
||||
else:
|
||||
coverage = 0.0
|
||||
print(f"轮廓覆盖度: {coverage:.4f}")
|
||||
return {
|
||||
"wc": final_wc,
|
||||
"fill_ratio": fill_ratio,
|
||||
"coverage": coverage,
|
||||
"occ_fast": occ_fast,
|
||||
"w_small": w_small,
|
||||
"h_small": h_small,
|
||||
@@ -614,6 +839,9 @@ def main():
|
||||
)
|
||||
frequencies_data = name_weights_map if config.REMOVE_DUPLICATES else [(name, name_weights_map.get(name, 10)) for name in names]
|
||||
|
||||
canvas_retry_round = 0
|
||||
generation_result = None
|
||||
t_gen = time.time()
|
||||
canvas_retry_round = 0
|
||||
generation_result = None
|
||||
t_gen = time.time()
|
||||
@@ -649,8 +877,9 @@ def main():
|
||||
canvas_retry_round,
|
||||
)
|
||||
sys.exit(1)
|
||||
log.info(" 生成完成 placed=%d/%d fill=%.4f retry=%d",
|
||||
placed, target, generation_result["fill_ratio"], canvas_retry_round)
|
||||
log.info(" 生成完成 placed=%d/%d fill=%.4f coverage=%.4f retry=%d",
|
||||
placed, target, generation_result["fill_ratio"],
|
||||
generation_result.get("coverage", 0.0), canvas_retry_round)
|
||||
break
|
||||
|
||||
canvas_retry_round += 1
|
||||
|
||||
Reference in New Issue
Block a user