File size: 25,006 Bytes
49b7391 | 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 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 | import torch
import torch.nn.functional as F
import math
import numpy as np
from collections import OrderedDict
# =======================================================================
# vUltimate - REAL Deep Code Audit
# Based on v13 (THE_last_version) with CRITICAL FIXES
# =======================================================================
# =======================================================================
# 1. SMART CACHING (Speed optimization ~15-20%)
# =======================================================================
class SmartMaskCache:
"""LRU cache for blend masks to avoid regeneration"""
def __init__(self, max_size=50):
self.cache = OrderedDict()
self.max_size = max_size
def get(self, key):
if key in self.cache:
self.cache.move_to_end(key)
return self.cache[key]
return None
def set(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.max_size:
self.cache.popitem(last=False)
# Global cache instance
_MASK_CACHE = SmartMaskCache()
# =======================================================================
# 2. SAFE EPSILON (π΄ CRITICAL FIX: Infinite recursion bug in v13!)
# =======================================================================
def get_safe_epsilon(tensor_or_dtype):
"""
Float16-safe epsilon - CRITICAL for half precision!
π΄ vUltimate Fix: v13 had INFINITE RECURSION bug:
Line 51: return get_safe_epsilon(torch.float16) # INFINITE LOOP!
Args:
tensor_or_dtype: torch.Tensor or torch.dtype
Returns:
float: safe epsilon for given dtype
"""
if isinstance(tensor_or_dtype, torch.Tensor):
dtype = tensor_or_dtype.dtype
else:
dtype = tensor_or_dtype
# Float16 minimum value ~6e-5, so 1e-6 causes underflow
if dtype in (torch.float16, torch.bfloat16):
return 1e-3 # Safe for half precision
elif dtype == torch.float32:
return 1e-6 # π΄ FIX: Changed from recursive call to direct value
else:
return 1e-12 # High precision for float64
# =======================================================================
# 3. LATENT COLOR FIX (Variance-preserving blend)
# =======================================================================
def blend_with_variance_fix(a, b, mask):
"""
Mathematically correct blending of latent noise.
β
FLOAT16 FIX: Safe sqrt with adaptive epsilon
Args:
a: Primary layer (active where mask=1) -> Advanced/Circular
b: Background layer (active where mask=0) -> Simple/Replicate
mask: Blend mask [0,1]
π΄ IMPORTANT: From v11+ the mask semantics are:
mask=1.0 on EDGES (where Advanced padding is needed)
mask=0.0 in CENTER (where content or Simple padding is)
"""
# 1. Linear blend
blended = a * mask + b * (1 - mask)
# 2. Variance correction with adaptive epsilon
eps_val = get_safe_epsilon(mask.dtype)
variance_fix = torch.sqrt(mask**2 + (1 - mask)**2 + eps_val)
return blended / variance_fix
# =======================================================================
# 4. LEGACY FADE TO BLACK (For ZOOM effect) β
# =======================================================================
def compute_blend_fade_to_black(padded, pad_h, pad_w, fade_strength=0.1):
"""
β‘ LEGACY MODE for Zoom effect (V3.5 logic from v11-v13) β‘
Gradient now covers ENTIRE padding + part of content.
Result: Beautiful vignette from 0 (edge) to 1 (center).
π΄ vUltimate Note: This is v13 logic (NOT v7 logic).
v7 applied fade only to content inside padding zones.
v13 applies fade to ENTIRE image including padding.
Args:
padded: Already padded tensor [B, C, H, W]
pad_h: Vertical padding size
pad_w: Horizontal padding size
fade_strength: Fade depth into content (0.0-1.0), typically 0.05-0.2
Returns:
Tensor with darkened edges
"""
b, c, H, W = padded.shape
# Calculate content size (without padding)
h_content = max(H - 2 * pad_h, 0)
w_content = max(W - 2 * pad_w, 0)
# Fade depth inside content
blend_in_h = int(h_content * fade_strength)
blend_in_w = int(w_content * fade_strength)
# Total fade zone = Padding + Entry into content
total_fade_h = pad_h + blend_in_h
total_fade_w = pad_w + blend_in_w
result = padded.clone()
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# VERTICAL EDGES
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if total_fade_h > 0:
# Create gradient 0 -> 1
fade = torch.linspace(0, 1, steps=total_fade_h,
device=padded.device, dtype=padded.dtype)
fade = fade.view(1, 1, -1, 1) # Shape: (1,1,H,1)
# Top (from 0 to total_fade_h)
safe_h = min(total_fade_h, H)
result[:, :, :safe_h, :] *= fade[:, :, :safe_h, :]
# Bottom (from H-total_fade_h to H) - use flipped gradient
result[:, :, -safe_h:, :] *= fade[:, :, :safe_h, :].flip(2)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# HORIZONTAL EDGES
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if total_fade_w > 0:
# Create gradient 0 -> 1
fade = torch.linspace(0, 1, steps=total_fade_w,
device=padded.device, dtype=padded.dtype)
fade = fade.view(1, 1, 1, -1) # Shape: (1,1,1,W)
# Left
safe_w = min(total_fade_w, W)
result[:, :, :, :safe_w] *= fade[:, :, :, :safe_w]
# Right
result[:, :, :, -safe_w:] *= fade[:, :, :, :safe_w].flip(3)
return result
# =======================================================================
# 5. ADVANCED BLEND MASK (For modern tiling mode)
# =======================================================================
def create_advanced_blend_mask(h, w, blend_width, device, dtype=torch.float32,
falloff_curve="smoothstep", edge_sharpness=1.0):
"""
Creates cached edge blend mask.
π΄ MASK SEMANTICS (v11+ convention):
1.0 = on the very EDGE (where Advanced Padding is needed)
0.0 = in CENTER (where content or Simple Padding is)
Args:
h, w: Mask dimensions
blend_width: Transition zone width (pixels)
device: Torch device
dtype: Data type
falloff_curve: Curve type ('linear', 'smoothstep', 'cosine')
edge_sharpness: Edge sharpness (1.0 = normal, >1 = sharper, <1 = softer)
Returns:
Mask of size [1, 1, h, w]
"""
if blend_width <= 0:
return torch.ones(1, 1, h, w, device=device, dtype=dtype)
# BUG FIX 6a: normalise falloff_curve to a known value; warn loudly if
# the UI has sent something the backend doesn't actually implement.
_KNOWN_FALLOFFS = {'linear', 'smoothstep', 'cosine'}
if falloff_curve not in _KNOWN_FALLOFFS:
print(f"[AdvancedBlend] Warning: unsupported falloff_curve '{falloff_curve}' "
f"β falling back to 'smoothstep'. Supported: {sorted(_KNOWN_FALLOFFS)}")
falloff_curve = 'smoothstep'
blend_w = min(blend_width, w // 2)
blend_h = min(blend_width, h // 2)
mask = torch.zeros((1, 1, h, w), device=device, dtype=dtype)
def get_ramp(size):
"""Generate gradient with configurable curve.
BUG FIX: size==1 ΡΠ΅ΡΠ΅Π· linspace(0,1,1) Π΄Π°Π²Π°Π» [0], ΡΠΎ Π΅ΡΡΡ Π½ΡΠ»Π΅Π²ΡΡ ΠΌΠ°ΡΠΊΡ.
Π’Π΅ΠΏΠ΅ΡΡ Π΄Π»Ρ size<=1 Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌ ones β Π³ΡΠ°Π½ΠΈΡΠ½ΡΠΉ ΠΏΠΈΠΊΡΠ΅Π»Ρ ΠΏΠΎΠ»ΡΡΠ°Π΅Ρ ΠΏΠΎΠ»Π½ΡΠΉ Π²Π΅Ρ.
"""
if size <= 1:
return torch.ones(max(size, 1), device=device, dtype=dtype)
t = torch.linspace(0, 1, steps=size, device=device, dtype=dtype)
if edge_sharpness != 1.0:
t = torch.pow(t, edge_sharpness)
if falloff_curve == 'smoothstep':
return t * t * (3 - 2 * t)
elif falloff_curve == 'cosine':
return (1 - torch.cos(t * math.pi)) / 2
elif falloff_curve == 'linear':
return t
return t
# Fill edges
if blend_w > 0:
ramp = get_ramp(blend_w)
# Left edge
mask[:, :, :, :blend_w] = torch.maximum(mask[:, :, :, :blend_w],
ramp.flip(0).view(1,1,1,-1))
# Right edge
mask[:, :, :, -blend_w:] = torch.maximum(mask[:, :, :, -blend_w:],
ramp.view(1,1,1,-1))
if blend_h > 0:
ramp = get_ramp(blend_h)
# Top edge
mask[:, :, :blend_h, :] = torch.maximum(mask[:, :, :blend_h, :],
ramp.flip(0).view(1,1,-1,1))
# Bottom edge
mask[:, :, -blend_h:, :] = torch.maximum(mask[:, :, -blend_h:, :],
ramp.view(1,1,-1,1))
return mask
# =======================================================================
# 6. IMPROVED BLEND PADDING (Main tiling function)
# =======================================================================
def compute_advanced_blend_padding(input_tensor, pad_h, pad_w,
mode_simple='replicate',
mode_advanced='circular',
blend_strength=0.5,
blend_width=None,
falloff_curve='smoothstep',
edge_sharpness=1.0,
fade_to_black=False,
fade_strength=0.1):
"""
IMPROVED PADDING MODE
Two operation modes:
1. FADE TO BLACK (fade_to_black=True) - for Zoom effect:
- Applies one padding (mode_advanced)
- DARKENS edges, creating zoom out effect
- Uses legacy compute_blend_fade_to_black function
2. BLEND TWO PADDINGS (fade_to_black=False) - for quality edges:
- Creates two different paddings (simple and advanced)
- Blends them via mask
- Applies variance fix for color correction
- Does NOT create zoom effect
Args:
input_tensor: Original tensor WITHOUT padding [B, C, H, W]
pad_h, pad_w: Padding sizes
mode_simple: Mode for "simple" padding ('replicate', 'constant')
mode_advanced: Mode for "advanced" padding ('circular', 'reflect')
blend_strength: Blend strength (0.0-1.0)
blend_width: Transition width (None = auto)
falloff_curve: Gradient curve type
edge_sharpness: Edge sharpness
fade_to_black: If True, uses legacy darkening mode
fade_strength: Darkening strength for fade_to_black mode
Returns:
Padded tensor [B, C, H+2*pad_h, W+2*pad_w]
"""
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# MODE 1: FADE TO BLACK (for Zoom)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if fade_to_black:
# Apply ONE padding first
if isinstance(mode_advanced, str):
if mode_advanced == 'reflect':
b, c, h, w = input_tensor.shape
if pad_w < w and pad_h < h:
padded = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='reflect')
else:
padded = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='replicate')
else: # circular
padded = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode=mode_advanced)
else:
padded = mode_advanced # Pre-computed tensor
# Darken edges (now works correctly!)
return compute_blend_fade_to_black(padded, pad_h, pad_w, fade_strength)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# MODE 2: BLEND TWO PADDINGS (Tiling)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# If disabled
if blend_strength <= 0.001:
if mode_simple == 'constant':
return F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='constant', value=0)
return F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode=mode_simple)
# If 100% strength
if blend_strength >= 0.999:
if isinstance(mode_advanced, str):
if mode_advanced == 'reflect':
b, c, h, w = input_tensor.shape
if pad_w < w and pad_h < h:
return F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='reflect')
else:
return F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='replicate')
return F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode=mode_advanced)
return mode_advanced # Pre-computed tensor
# 1. Prepare layers
if mode_simple == 'constant':
simple = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='constant', value=0)
else:
simple = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode=mode_simple)
if isinstance(mode_advanced, str):
if mode_advanced == 'reflect':
b, c, h, w = input_tensor.shape
if pad_w < w and pad_h < h:
advanced = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='reflect')
else:
advanced = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='replicate')
else: # circular
advanced = F.pad(input_tensor, (pad_w, pad_w, pad_h, pad_h), mode='circular')
else:
advanced = mode_advanced # Pre-computed
# 2. Get mask from cache
if blend_width is None:
blend_width = max(pad_h, pad_w)
b, c, h, w = input_tensor.shape
device = input_tensor.device
dtype = input_tensor.dtype
# π΄ vUltimate Fix: Enhanced cache key WITH dtype (v11+ feature)
cache_key = (h, w, pad_h, pad_w, blend_width, falloff_curve, edge_sharpness,
str(device), str(dtype))
mask = _MASK_CACHE.get(cache_key)
if mask is None:
H_pad, W_pad = simple.shape[2:]
mask = create_advanced_blend_mask(H_pad, W_pad, blend_width, device,
dtype, falloff_curve, edge_sharpness)
_MASK_CACHE.set(cache_key, mask)
# 3. Blend with variance fix
final_mask = mask * blend_strength
# π΄ vUltimate: v11+ semantics: advanced (mask=1 on edges) / simple (mask=0 in center)
return blend_with_variance_fix(advanced, simple, final_mask)
# =======================================================================
# 7. MULTI-RESOLUTION (Temporal strategy)
# =======================================================================
class BlendStrategy:
"""Interpolation strategies for multi-resolution transitions"""
LINEAR = "linear"
COSINE = "cosine"
EXPONENTIAL = "exponential"
SIGMOID = "sigmoid"
class MultiResStrategy:
"""Handles temporal blending curves for progressive detail addition"""
def __init__(self, strategy_type=BlendStrategy.COSINE):
self.strategy_type = strategy_type
def get_factor(self, progress, sharpness=1.0):
"""Calculate blend factor based on progress (0.0 to 1.0)"""
t = max(0.0, min(1.0, progress))
if sharpness != 1.0:
t = math.pow(t, sharpness)
if self.strategy_type == BlendStrategy.LINEAR:
return t
elif self.strategy_type == BlendStrategy.COSINE:
return (1.0 - math.cos(t * math.pi)) / 2.0
elif self.strategy_type == BlendStrategy.EXPONENTIAL:
return math.pow(t, 2)
elif self.strategy_type == BlendStrategy.SIGMOID:
if t <= 0: return 0.0
if t >= 1: return 1.0
return 1.0 / (1.0 + math.exp(-12.0 * (t - 0.5)))
return t
def apply_multires_blend(tensor_simple, tensor_advanced, current_step,
start_step, end_step,
strategy="cosine",
transition_start=0.0,
transition_end=0.3,
sharpness=1.0,
enabled=False):
"""
Progressive blending from simple to advanced over denoising steps.
Args:
tensor_simple: Low-detail padding result
tensor_advanced: High-detail padding result
current_step: Current denoising step
start_step, end_step: Denoising range
strategy: Interpolation curve type
transition_start, transition_end: Transition window (0.0-1.0)
sharpness: Curve adjustment
enabled: Master switch
Returns:
Blended tensor
"""
if not enabled:
return tensor_advanced
# BUG FIX 6b: normalise strategy to a supported value before use.
_KNOWN_STRATEGIES = {BlendStrategy.LINEAR, BlendStrategy.COSINE,
BlendStrategy.EXPONENTIAL, BlendStrategy.SIGMOID}
_STRATEGY_ALIASES = {
'linear': BlendStrategy.LINEAR,
'cosine': BlendStrategy.COSINE,
'exponential': BlendStrategy.EXPONENTIAL,
'sigmoid': BlendStrategy.SIGMOID,
}
if isinstance(strategy, str):
strategy_key = strategy.lower()
if strategy_key not in _STRATEGY_ALIASES:
print(f"[MultiRes] Warning: unsupported strategy '{strategy}' "
f"β falling back to 'cosine'. "
f"Supported: {sorted(_STRATEGY_ALIASES.keys())}")
strategy = BlendStrategy.COSINE
else:
strategy = _STRATEGY_ALIASES[strategy_key]
elif strategy not in _KNOWN_STRATEGIES:
print(f"[MultiRes] Warning: unknown strategy {strategy!r} β falling back to cosine")
strategy = BlendStrategy.COSINE
total_steps = end_step - start_step
if total_steps <= 0:
return tensor_advanced
step_frac = (current_step - start_step) / total_steps
step_frac = max(0.0, min(1.0, step_frac))
if step_frac < transition_start:
local_progress = 0.0
elif step_frac > transition_end:
local_progress = 1.0
else:
duration = transition_end - transition_start
if duration <= 0:
local_progress = 1.0
else:
local_progress = (step_frac - transition_start) / duration
strat = MultiResStrategy(strategy)
alpha = strat.get_factor(local_progress, sharpness)
if alpha <= 0.001:
return tensor_simple
if alpha >= 0.999:
return tensor_advanced
# Standard lerp (temporal blend, not spatial)
return tensor_simple * (1.0 - alpha) + tensor_advanced * alpha
# =======================================================================
# 8. HELPER FUNCTIONS (From v13 for compatibility)
# =======================================================================
def create_circular_mask(h, w, center_x=0.5, center_y=0.5, radius=0.5,
device='cpu', dtype=torch.float32):
"""
Creates circular mask (white circle on black background).
β
FLOAT16 FIX: Safe sqrt
NOTE: This is v13 version (radial distance mask).
Different from v1/exp which don't have this function.
"""
eps_val = get_safe_epsilon(dtype)
# Create coordinate grid
y, x = torch.meshgrid(
torch.linspace(-1, 1, h, device=device, dtype=dtype),
torch.linspace(-1, 1, w, device=device, dtype=dtype),
indexing='ij'
)
# Shift center
x = x - (center_x - 0.5) * 2
y = y - (center_y - 0.5) * 2
# Calculate distance from center with protection
dist = torch.sqrt(x*x + y*y + eps_val)
# Create soft mask (smooth edges 0.1)
mask = 1.0 - torch.clamp((dist - (radius - 0.1)) / 0.2, 0, 1)
# Add channel and batch dimensions
if len(mask.shape) == 2:
mask = mask.unsqueeze(0).unsqueeze(0)
return mask
def create_fade_to_black_mask(h, w, strength=0.1, device='cpu', dtype=torch.float32):
"""
Creates vignette (darkening towards edges).
β
FLOAT16 FIX: Safe sqrt
NOTE: This is v13 version (radial vignette).
Different from v1/exp which don't have this function.
"""
eps_val = get_safe_epsilon(dtype)
y, x = torch.meshgrid(
torch.linspace(-1, 1, h, device=device, dtype=dtype),
torch.linspace(-1, 1, w, device=device, dtype=dtype),
indexing='ij'
)
# sqrt with protection
dist = torch.sqrt(x*x + y*y + eps_val)
# Normalize so corners are 1.0 (max distance ~1.41)
dist = dist / 1.4142
# Invert: center white (1), edges black (0)
threshold = 1.0 - strength
mask = 1.0 - torch.clamp((dist - threshold) / strength, 0, 1)
if len(mask.shape) == 2:
mask = mask.unsqueeze(0).unsqueeze(0)
return mask
# =======================================================================
# 9. PARAMETER VALIDATION HELPERS
# =======================================================================
def validate_blend_params(params):
"""Extract and validate blend parameters from dict.
Unsupported falloff values are normalised here with a warning so callers
never silently receive a mode the backend cannot honour.
Supported: 'linear', 'smoothstep', 'cosine'
"""
falloff = params.get('blend_falloff', 'smoothstep')
_SUPPORTED_FALLOFFS = {'linear', 'smoothstep', 'cosine'}
if falloff not in _SUPPORTED_FALLOFFS:
print(f"[validate_blend_params] Warning: unsupported blend_falloff '{falloff}' "
f"β falling back to 'smoothstep'. Supported: {sorted(_SUPPORTED_FALLOFFS)}")
falloff = 'smoothstep'
return {
'strength': float(params.get('blend_strength', 0.5)),
'width': int(params.get('blend_width', 0)) if params.get('blend_width') else None,
'falloff': falloff,
'sharpness': float(params.get('blend_sharpness', 1.0)),
'fade_to_black': bool(params.get('blend_fade_to_black', False)),
'fade_strength': float(params.get('blend_fade_strength', 0.1))
}
def validate_multires_params(params):
"""Extract and validate multi-resolution parameters from dict.
Unsupported strategy values are normalised here with a warning.
Supported: 'linear', 'cosine', 'exponential', 'sigmoid'
"""
strategy = params.get('multires_strategy', 'cosine')
_SUPPORTED_STRATEGIES = {'linear', 'cosine', 'exponential', 'sigmoid'}
if strategy not in _SUPPORTED_STRATEGIES:
print(f"[validate_multires_params] Warning: unsupported multires_strategy '{strategy}' "
f"β falling back to 'cosine'. Supported: {sorted(_SUPPORTED_STRATEGIES)}")
strategy = 'cosine'
return {
'strategy': strategy,
'transition_start': float(params.get('multires_start', 0.0)),
'transition_end': float(params.get('multires_end', 0.3)),
'sharpness': float(params.get('multires_sharpness', 1.0))
}
# =======================================================================
# vUltimate - End of File
# CRITICAL FIXES APPLIED:
# β
Fix 1: Infinite recursion in get_safe_epsilon (v13 bug at line 51/65)
# β
Fix 2: Correct v11+ mask semantics (advanced first, mask=1.0 on edges)
# β
Fix 3: Cache key includes dtype (v11+ improvement)
# β
Fix 4: Safe reflect with size validation
# β
Fix 5: v13 fade_to_black logic (not v7 logic)
# β
Fix 6: v13 circular/vignette masks (not v1/exp)
# =======================================================================
|