| """Defines and samples the render-spec search space.""" |
|
|
| from __future__ import annotations |
|
|
| import random |
| from dataclasses import replace |
|
|
| from veil_pgd.config import OptimizerConfig |
| from veil_pgd.types import Position, RenderSpec |
|
|
| _POSITIONS: list[Position] = [ |
| "bottom_middle", "top_middle", "top_right", "bottom_right", |
| "top_left", "bottom_left", "center", |
| ] |
| _COLOR_STRATEGIES = ["bg_mean_offset", "near_bg_dark", "near_bg_light"] |
|
|
|
|
| def grid_specs( |
| decoys: list[str], positions: list[Position], cfg: OptimizerConfig |
| ) -> list[RenderSpec]: |
| """Coarse grid over structural knobs at a fixed mid alpha. |
| |
| Caps positions/colors to `cfg.grid_positions`/`cfg.grid_colors` so grid size |
| stays bounded; the evolutionary phase explores the rest of the space. |
| """ |
| positions = positions[: cfg.grid_positions] |
| colors = _COLOR_STRATEGIES[: cfg.grid_colors] |
| specs: list[RenderSpec] = [] |
| for text in decoys: |
| for pos in positions: |
| for color in colors: |
| for px in cfg.grid_font_px: |
| specs.append( |
| RenderSpec( |
| text=text, |
| font_px=px, |
| alpha=cfg.grid_alpha, |
| color_strategy=color, |
| position=pos, |
| ) |
| ) |
| return specs |
|
|
|
|
| def mutate(spec: RenderSpec, cfg: OptimizerConfig, rng: random.Random) -> RenderSpec: |
| """Perturb the continuous/fine knobs for the evolutionary phase.""" |
| lo, hi = cfg.alpha_range |
| flo, fhi = cfg.font_px_range |
| choice = rng.choice(["alpha", "font", "offset", "rep", "color"]) |
| if choice == "alpha": |
| return replace(spec, alpha=min(hi, max(lo, spec.alpha + rng.uniform(-0.05, 0.05)))) |
| if choice == "font": |
| return replace(spec, font_px=min(fhi, max(flo, spec.font_px + rng.choice([-2, -1, 1, 2])))) |
| if choice == "offset": |
| return replace(spec, brightness_offset=min(30, max(5, spec.brightness_offset + rng.choice([-5, 5])))) |
| if choice == "rep": |
| return replace(spec, repetition=min(4, max(1, spec.repetition + rng.choice([-1, 1])))) |
| return replace(spec, color_strategy=rng.choice(_COLOR_STRATEGIES)) |
|
|
|
|
| def default_positions() -> list[Position]: |
| return list(_POSITIONS) |
|
|