Deforum_Soonr / dev /utils2.py
AlekseyCalvin's picture
Rename utils2.py to dev/utils2.py
2121f0c verified
import numpy as np
import cv2
import numexpr
import re
from PIL import Image, ImageOps
# --- Math & Schedule Parsing ---
def get_inbetweens(key_frames, max_frames, integer=False):
"""Interpolates values between keyframes (simple linear for now, but robust)."""
key_frames = dict(sorted(key_frames.items()))
keys = list(key_frames.keys())
vals = list(key_frames.values())
# Fill array
series = np.linspace(vals[0], vals[0], max_frames)
for i in range(len(keys)-1):
idx_start, idx_end = keys[i], keys[i+1]
val_start, val_end = vals[i], vals[i+1]
if idx_end > max_frames: idx_end = max_frames
# Linear interpolation
range_len = idx_end - idx_start
if range_len > 0:
segment = np.linspace(val_start, val_end, range_len, endpoint=False)
series[idx_start:idx_end] = segment
# Fill tail
if keys[-1] < max_frames:
series[keys[-1]:] = vals[-1]
return series.astype(int) if integer else series
def parse_weight_string(string, max_frames):
"""
Parses '0:(0.5), 10:(1.0)' including Math like '0:(sin(t/10))'.
Returns a numpy array of float values for every frame.
"""
# Clean string
string = re.sub(r'\s+', '', string)
keyframes = {}
# Split by comma, respecting parentheses might be needed in complex regex,
# but simple split usually works for Deforum format
parts = string.split(',')
for part in parts:
try:
frame_str, val_str = part.split(':')
frame = int(frame_str)
# Remove parentheses
val_str = val_str.strip('()')
keyframes[frame] = val_str
except:
continue
if 0 not in keyframes:
keyframes[0] = "0"
sorted_frames = sorted(keyframes.keys())
series = np.zeros(max_frames)
# Evaluate math for every frame
for i in range(len(sorted_frames)):
f_start = sorted_frames[i]
f_end = sorted_frames[i+1] if i < len(sorted_frames)-1 else max_frames
formula = keyframes[f_start]
for f in range(f_start, f_end):
t = f # Deforum standard variable
try:
# Safe evaluation environment
val = numexpr.evaluate(formula, local_dict={'t': t, 's': f_start, 'pi': np.pi, 'sin': np.sin, 'cos': np.cos, 'tan': np.tan})
series[f] = float(val)
except Exception as e:
# If static value or fail
try:
series[f] = float(formula)
except:
series[f] = series[f-1] if f > 0 else 0.0
return series
# --- Image Processing ---
def maintain_colors(prev_img, color_match_sample):
"""
Matches the coloring of the previous frame (or frame 0) to prevent color drift.
Uses LAB color space transfer.
"""
prev_img_np = np.array(prev_img).astype(np.uint8)
sample_np = np.array(color_match_sample).astype(np.uint8)
prev_lab = cv2.cvtColor(prev_img_np, cv2.COLOR_RGB2LAB)
sample_lab = cv2.cvtColor(sample_np, cv2.COLOR_RGB2LAB)
l_avg_p, a_avg_p, b_avg_p = np.mean(prev_lab[:,:,0]), np.mean(prev_lab[:,:,1]), np.mean(prev_lab[:,:,2])
l_avg_s, a_avg_s, b_avg_s = np.mean(sample_lab[:,:,0]), np.mean(sample_lab[:,:,1]), np.mean(sample_lab[:,:,2])
l, a, b = cv2.split(prev_lab)
# Shift current image logic towards sample mean
# Note: Deforum usually matches the NEW generation to the OLD image.
# Here we are adjusting the image we just warped (prev) to match the original anchor?
# Actually, standard Deforum 'Match Frame 0' means we force the init image to look like Frame 0 colors.
l = l - l_avg_p + l_avg_s
a = a - a_avg_p + a_avg_s
b = b - b_avg_p + b_avg_s
l = np.clip(l, 0, 255)
a = np.clip(a, 0, 255)
b = np.clip(b, 0, 255)
matched_lab = cv2.merge([l.astype(np.uint8), a.astype(np.uint8), b.astype(np.uint8)])
return Image.fromarray(cv2.cvtColor(matched_lab, cv2.COLOR_LAB2RGB))
def add_noise(img, noise_amt):
"""Adds uniform noise to the image to give the diffusion model texture to latch onto."""
if noise_amt <= 0: return img
img_np = np.array(img).astype(np.float32)
noise = np.random.normal(0, noise_amt * 255, img_np.shape).astype(np.float32)
noisy_img = np.clip(img_np + noise, 0, 255).astype(np.uint8)
return Image.fromarray(noisy_img)
def anim_frame_warp_2d(prev_img_pil, args_dict):
"""
Standard Deforum 2D Warping.
args_dict must contain: angle, zoom, translation_x, translation_y
"""
cv2_img = np.array(prev_img_pil)
height, width = cv2_img.shape[:2]
center = (width // 2, height // 2)
# Rotation & Zoom
angle = args_dict.get('angle', 0)
zoom = args_dict.get('zoom', 1.0)
trans_mat = cv2.getRotationMatrix2D(center, angle, zoom)
# Translation
tx = args_dict.get('translation_x', 0)
ty = args_dict.get('translation_y', 0)
trans_mat[0, 2] += tx
trans_mat[1, 2] += ty
# Warp with Reflection to handle edges naturally
warped = cv2.warpAffine(
cv2_img,
trans_mat,
(width, height),
borderMode=cv2.BORDER_REFLECT_101
)
return Image.fromarray(warped)