sift-vton / utils.py
multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
e997469 verified
Raw
History Blame Contribute Delete
26.7 kB
import json
import argparse
import random
import numpy as np
import torch.nn.functional as F
import torch
from torch import nn
import cv2
from scipy.spatial import KDTree
from functools import partial
def save_args(args, to_path):
with open(to_path, "w") as f:
json.dump(args.__dict__, f, indent=2)
def load_args(from_path, is_test=True):
parser = argparse.ArgumentParser()
args = parser.parse_args()
with open(from_path, "r") as f:
args.__dict__ = json.load(f)
args.is_test = is_test
if "E_name" not in args.__dict__.keys():
args.E_name = "basic"
return args
def tensor2img(x, round=False):
'''
x : [BS x c x H x W] or [c x H x W]
'''
if x.ndim == 3:
x = x.unsqueeze(0)
BS, C, H, W = x.shape
x = x.permute(0,2,3,1).reshape(-1, W, C).detach().cpu().numpy()
# x = (x+1)/2
# x = np.clip(x, 0, 1)
x = np.clip(x, -1, 1)
x = (x+1)/2
adjust = 0.5 if round else 0
x = np.uint8(x*255.0 + adjust)
if x.shape[-1] == 1: # gray sclae
x = np.concatenate([x,x,x], axis=-1)
return x
def resize_mask(m, shape):
m = F.interpolate(m, shape)
m[m > 0.5] = 1
m[m < 0.5] = 0
return m
def remove_overlap(seg_out, warped_cm, inference=False):
assert len(warped_cm.shape) == 4
overlapped_region = (torch.cat([seg_out[:, 1:3, :, :], seg_out[:, 5:, :, :]], dim=1)).sum(dim=1, keepdim=True)
if inference:
overlapped_region = (overlapped_region > 0.5).float()
warped_cm = warped_cm - overlapped_region * warped_cm
return warped_cm
def bilateral_filter(image, kernel_size, sigma_d, sigma_r):
"""Bilateral filter implementation.
Args:
image: input float tensor with shape [bsz, ch, height, width]
kernel_size: int. we assume it is odd.
sigma_d: float. standard deviation for distance.
sigma_r: float or tensor. standard deviation for range.
"""
padding = (kernel_size - 1) // 2
# distance
bsz, ch, height, width = image.shape
if isinstance(sigma_r, float):
sigma_r = torch.tensor([sigma_r]).expand(bsz)
sigma_r = sigma_r.to(image.device)
height_pad = height + 2 * padding
width_pad = width + 2 * padding
# gaussian on spacial distance
grid_x, grid_y = torch.meshgrid(torch.arange(width_pad), torch.arange(height_pad),
indexing='xy')
grid_x = grid_x.float().to(image.device)
grid_y = grid_y.float().to(image.device)
unfold_grid = nn.Unfold(kernel_size=kernel_size)
grid_x_unfolded = unfold_grid(grid_x[None, None])
grid_y_unfolded = unfold_grid(grid_y[None, None])
grid_x_unfolded = grid_x_unfolded.transpose(1, 2).reshape(height * width, 1, kernel_size ** 2)
grid_y_unfolded = grid_y_unfolded.transpose(1, 2).reshape(height * width, 1, kernel_size ** 2)
center_index = kernel_size ** 2 // 2
diff_x_unfolded = grid_x_unfolded - grid_x_unfolded[:, :, center_index][:, :, None]
diff_y_unfolded = grid_y_unfolded - grid_y_unfolded[:, :, center_index][:, :, None]
dist_unfolded = diff_x_unfolded ** 2 + diff_y_unfolded ** 2
gaussian_dist = torch.exp(-dist_unfolded / (2 * sigma_d ** 2))
# gaussian on range
unfold = nn.Unfold(kernel_size=kernel_size, padding=padding)
image_unfolded = unfold(image)
image_unfolded = image_unfolded.transpose(1, 2).reshape(bsz, height * width, ch, kernel_size ** 2)
center_value = image_unfolded[:, :, :, center_index]
diff_value = image_unfolded - center_value[:, :, :, None]
dist_value = diff_value ** 2
gaussian_value = torch.exp(-dist_value / (2 * sigma_r[:, None, None, None] ** 2))
# bilateral filter
bilateral_weight = gaussian_dist[None] * gaussian_value
result_unfolded = torch.sum(bilateral_weight * image_unfolded, dim=-1)
z_constant = bilateral_weight.sum(dim=-1)
result_unfolded = result_unfolded / z_constant
result = result_unfolded.transpose(1, 2).reshape(bsz, ch, height, width)
return result
def ensure_tensor(x):
if isinstance(x, torch.Tensor):
return x
# numpy
if isinstance(x, np.ndarray):
return torch.from_numpy(x)
return torch.tensor(x)
def set_seed(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
# SIFT-VTON SIFT matching and filtering
ANGLE_THRES = 45
def filter_angle(keypoints1, keypoints2, match):
idx1 = match.queryIdx
idx2 = match.trainIdx
angle1 = keypoints1[idx1].angle
angle2 = keypoints2[idx2].angle
diff = abs(angle1 - angle2)
if diff > 180:
diff = 360 - diff
return diff < ANGLE_THRES
def round_tuple(t):
return tuple(map(lambda x: round(x), t))
def transpose_pt(pt):
return (pt[1], pt[0])
SCALE_RATIO_THRES = 2.25
def filter_scale(keypoints1, keypoints2, match):
idx1 = match.queryIdx
idx2 = match.trainIdx
size1 = keypoints1[idx1].size
size2 = keypoints2[idx2].size
scale_ratio = size1 / size2
if 1/SCALE_RATIO_THRES <= scale_ratio <= SCALE_RATIO_THRES:
return True
return False
# unused
PIXEL_DIST_THRES = 200
def filter_value(keypoints1, keypoints2, img1, img2, match):
idx1 = match.queryIdx
idx2 = match.trainIdx
loc1 = transpose_pt(round_tuple(keypoints1[idx1].pt))
loc2 = transpose_pt(round_tuple(keypoints2[idx2].pt))
value1 = img1[loc1].astype(np.int16)
value2 = img2[loc2].astype(np.int16)
dist = np.linalg.norm(value1 - value2, ord=1)
if dist < PIXEL_DIST_THRES:
return True
return False
PIXEL_HUE_DIFF_THRES = 15
PIXEL_SAT_EFFECTIVE_RATIO = 0.1
PIXEL_VAL_DIFF_RATIO = 0.4
PIXEL_VAL_EFFECTIVE_RATIO = 0.2
def filter_hsv(keypoints1, keypoints2, img1, img2, match):
idx1 = match.queryIdx
idx2 = match.trainIdx
loc1 = transpose_pt(round_tuple(keypoints1[idx1].pt))
loc2 = transpose_pt(round_tuple(keypoints2[idx2].pt))
value1 = img1[loc1]
value2 = img2[loc2]
h1, s1, v1 = cv2.cvtColor(value1[None, None], cv2.COLOR_RGB2HSV)[0, 0]
h2, s2, v2 = cv2.cvtColor(value2[None, None], cv2.COLOR_RGB2HSV)[0, 0]
v1 = v1.astype(np.int16)
v2 = v2.astype(np.int16)
diff = abs(v1 - v2)
if diff > PIXEL_VAL_DIFF_RATIO * 255:
return False
if s1 < 255 * PIXEL_SAT_EFFECTIVE_RATIO and s2 < 255 * PIXEL_SAT_EFFECTIVE_RATIO:
return True
if v1 < 255 * PIXEL_VAL_EFFECTIVE_RATIO and v2 < 255 * PIXEL_VAL_EFFECTIVE_RATIO:
return True
h1 = h1.astype(np.int16)
h2 = h2.astype(np.int16)
diff = abs(h1 - h2)
if diff > 90:
diff = 180 - diff
if diff < PIXEL_HUE_DIFF_THRES:
return True
return False
PIXEL_DIST_MULTIPLIER2 = 1.4
NUM_FILTER_NEAREST_ITTER = 1
def filter_nearest_dist_from2(keypoints1, keypoints2, tree, matches, sift_match,
num_neighbors=1):
idx1 = sift_match.queryIdx
idx2 = sift_match.trainIdx
pt1 = keypoints1[idx1].pt
pt2 = keypoints2[idx2].pt
dist2, ii = tree.query(pt2, k=num_neighbors + 1)
for i in range(num_neighbors):
if dist2[i+1] == np.inf:
continue
pt1_2 = keypoints1[matches[ii[i+1]].queryIdx].pt
dist1 = np.linalg.norm(np.array(pt1) - np.array(pt1_2))
if dist1 > PIXEL_DIST_MULTIPLIER2 * dist2[i+1]:
return False
return True
PIXEL_DIST_MULTIPLIER1 = 1.8
def filter_nearest_dist_from1(keypoints1, keypoints2, tree, matches, sift_match, num_neighbors=1):
idx1 = sift_match.queryIdx
idx2 = sift_match.trainIdx
pt1 = keypoints1[idx1].pt
pt2 = keypoints2[idx2].pt
dist1, ii = tree.query(pt1, k=num_neighbors + 1)
for i in range(num_neighbors):
if dist1[i+1] == np.inf:
continue
pt2_2 = keypoints2[matches[ii[i+1]].trainIdx].pt
dist2 = np.linalg.norm(np.array(pt2) - np.array(pt2_2))
if dist2 > PIXEL_DIST_MULTIPLIER1 * dist1[i+1]:
return False
return True
import math
LOCATION_EPS = 1 * 2**0.5
def remove_duplicate_match(keypoints1, keypoints2, matches):
matches_filtered = []
def match_in_filtered(match):
match_point1 = keypoints1[match.queryIdx].pt
match_point2 = keypoints2[match.trainIdx].pt
for filtered_match in matches_filtered:
cur_point1 = keypoints1[filtered_match.queryIdx].pt
cur_point2 = keypoints2[filtered_match.trainIdx].pt
dist1 = np.linalg.norm(np.array(match_point1) - np.array(cur_point1))
dist2 = np.linalg.norm(np.array(match_point2) - np.array(cur_point2))
if dist1 == 0.0 or dist2 == 0.0:
return True
if dist1 < LOCATION_EPS and dist2 < LOCATION_EPS:
return True
return False
for match in matches:
if not match_in_filtered(match):
matches_filtered.append(match)
return matches_filtered
# SLOPE_STD_MULTIPLIER = 2.58
# SLOPE_STD_MULTIPLIER = 1.96
SLOPE_STD_MULTIPLIER = 1.65
def remove_slope_outliers(keypoints1, keypoints2, matches):
slopes = []
for match in matches:
idx1 = match.queryIdx
idx2 = match.trainIdx
pt1 = keypoints1[idx1].pt
pt2 = keypoints2[idx2].pt
slope = (pt2[1] - pt1[1]) / (pt2[0] - pt1[0])
slopes.append(slope)
slope_mean = np.mean(slopes)
slope_std = np.std(slopes)
def check_slope(slope):
return abs(slope - slope_mean) < SLOPE_STD_MULTIPLIER * slope_std
matches_filtered = [matches[idx] for idx, slope in enumerate(slopes) if check_slope(slope)]
return matches_filtered
SLOPE_ANGLE_THRES = np.pi / 12
def remove_angle_outliers(keypoints1, keypoints2, matches):
if len(matches) < 2:
return matches, None
slopes = []
for match in matches:
idx1 = match.queryIdx
idx2 = match.trainIdx
pt1 = keypoints1[idx1].pt
pt2 = keypoints2[idx2].pt
sl = - (pt2[1] - pt1[1]) / (pt2[0] - pt1[0] + 384)
slopes.append(sl)
def check_slope(idx, slope):
slopes_tmp = slopes.copy()
slopes_tmp.pop(idx)
slope_mean_tmp = np.mean(slopes_tmp)
base_angle_tmp = math.atan(slope_mean_tmp)
return abs(base_angle_tmp - math.atan(slope)) < SLOPE_ANGLE_THRES
matches_filtered = [matches[idx] for idx, slope in enumerate(slopes) if check_slope(idx, slope)]
return matches_filtered, None
class HomographyRANSAC:
def __init__(self, threshold=2.0, confidence=0.99, max_iterations=1000):
"""
RANSAC specifically optimized for homography transformations
Args:
threshold: Reprojection error threshold in pixels
confidence: Desired confidence level (0.99 = 99%)
max_iterations: Maximum number of iterations
"""
self.threshold = threshold
self.confidence = confidence
self.max_iterations = max_iterations
self.min_samples = 4 # Homography needs minimum 4 point correspondences
def estimate_homography_transform(self, src_pts, dst_pts):
"""
Estimate homography transformation from point correspondences
Homography transformation: [x'] = H [x]
[y'] [y]
[w'] [1]
where x'/w', y'/w' are the final 2D coordinates
Args:
src_pts: Source points (Nx2)
dst_pts: Destination points (Nx2)
Returns:
3x3 homography matrix or None if failed
"""
if src_pts.shape[0] < 4:
return None
n_points = src_pts.shape[0]
# Build the coefficient matrix A
# For each point correspondence, we get 2 equations
A = []
for i in range(n_points):
x, y = src_pts[i]
x_prime, y_prime = dst_pts[i]
# First equation: x' = (h11*x + h12*y + h13) / (h31*x + h32*y + h33)
# Rearranged: h11*x + h12*y + h13 - h31*x*x' - h32*y*x' - h33*x' = 0
A.append([-x, -y, -1, 0, 0, 0, x*x_prime, y*x_prime, x_prime])
# Second equation: y' = (h21*x + h22*y + h23) / (h31*x + h32*y + h33)
# Rearranged: h21*x + h22*y + h23 - h31*x*y' - h32*y*y' - h33*y' = 0
A.append([0, 0, 0, -x, -y, -1, x*y_prime, y*y_prime, y_prime])
A = np.array(A)
try:
# Solve using SVD (homogeneous least squares)
# The solution is the last column of V (corresponding to smallest singular value)
U, S, Vt = np.linalg.svd(A)
H = Vt[-1].reshape(3, 3)
# Normalize so that H[2,2] = 1
H = H / H[2, 2]
return H
except np.linalg.LinAlgError:
return None
def apply_homography_transform(self, homography_matrix, points):
"""
Apply homography transformation to points
Args:
homography_matrix: 3x3 homography matrix
points: Nx2 array of points
Returns:
Transformed points (Nx2)
"""
# Convert to homogeneous coordinates
homogeneous_pts = np.hstack([points, np.ones((points.shape[0], 1))])
# Apply transformation
transformed_homogeneous = (homography_matrix @ homogeneous_pts.T).T
# Convert back from homogeneous coordinates (divide by w)
# Handle potential division by zero
w = transformed_homogeneous[:, 2]
w = np.where(np.abs(w) < 1e-8, 1e-8, w) # Avoid division by zero
transformed = transformed_homogeneous[:, :2] / w[:, np.newaxis]
return transformed
def compute_reprojection_error(self, homography_matrix, src_pts, dst_pts):
"""
Compute reprojection errors for homography transformation
Args:
homography_matrix: 3x3 homography matrix
src_pts: Source points
dst_pts: Destination points
Returns:
Array of reprojection errors (distances)
"""
# Transform source points
transformed_pts = self.apply_homography_transform(homography_matrix, src_pts)
# Compute Euclidean distances
errors = np.linalg.norm(transformed_pts - dst_pts, axis=1)
return errors
def adaptive_iterations(self, n_points, current_best_inliers=0):
"""
Compute adaptive number of iterations based on current best result
"""
if n_points < self.min_samples:
return 0
# Estimate outlier ratio
if current_best_inliers > 0:
inlier_ratio = current_best_inliers / n_points
outlier_ratio = 1 - inlier_ratio
else:
outlier_ratio = 0.5 # Conservative initial estimate
outlier_ratio = max(0.01, min(0.99, outlier_ratio)) # Clamp to reasonable range
# Probability that a random sample of 4 points are all inliers
prob_all_inliers = (1 - outlier_ratio) ** self.min_samples
if prob_all_inliers > 1e-10:
# Number of iterations to have confidence% chance of success
num_iter = int(np.log(1 - self.confidence) / np.log(1 - prob_all_inliers))
return min(num_iter, self.max_iterations)
else:
return self.max_iterations
def eliminate_outliers(self, src_pts, dst_pts, verbose=False):
"""
Main RANSAC function for eliminating outliers using homography model
Args:
src_pts: Source points (Nx2)
dst_pts: Destination points (Nx2)
verbose: Print progress information
Returns:
inlier_mask: Boolean mask for inlier correspondences
inlier_indices: Indices of inlier correspondences
best_homography: Best homography transformation found
stats: Dictionary with statistics
"""
n_points = src_pts.shape[0]
if n_points < self.min_samples:
empty_mask = np.zeros(n_points, dtype=bool)
return empty_mask, [], None, {"iterations": 0, "inliers": 0}
best_inliers = []
best_homography = None
max_inliers = 0
iteration = 0
if verbose:
print(f"Starting RANSAC with {n_points} correspondences")
print(f"Threshold: {self.threshold} pixels")
while iteration < self.max_iterations:
# Adaptive termination
if iteration > 100 and iteration % 100 == 0:
required_iterations = self.adaptive_iterations(n_points, max_inliers)
if iteration >= required_iterations:
if verbose:
print(f"Early termination at iteration {iteration}")
break
# Randomly sample 4 correspondences
try:
sample_indices = random.sample(range(n_points), self.min_samples)
except ValueError:
break
sample_src = src_pts[sample_indices]
sample_dst = dst_pts[sample_indices]
# Estimate homography transformation
homography_matrix = self.estimate_homography_transform(sample_src, sample_dst)
if homography_matrix is None:
iteration += 1
continue
# Compute errors for all points
try:
errors = self.compute_reprojection_error(homography_matrix, src_pts, dst_pts)
except:
iteration += 1
continue
# Find inliers
inlier_indices = np.where(errors < self.threshold)[0]
n_inliers = len(inlier_indices)
# Update best model if current is better
if n_inliers > max_inliers:
max_inliers = n_inliers
best_inliers = inlier_indices
best_homography = homography_matrix.copy()
if verbose and iteration % 500 == 0:
print(f"Iteration {iteration}: Found {n_inliers} inliers ({n_inliers/n_points*100:.1f}%)")
iteration += 1
# Create inlier mask
inlier_mask = np.zeros(n_points, dtype=bool)
if len(best_inliers) > 0:
inlier_mask[best_inliers] = True
# Refine the transformation using all inliers
if len(best_inliers) >= 4:
refined_homography = self.estimate_homography_transform(
src_pts[best_inliers],
dst_pts[best_inliers]
)
if refined_homography is not None:
best_homography = refined_homography
stats = {
"iterations": iteration,
"inliers": len(best_inliers),
"inlier_ratio": len(best_inliers) / n_points if n_points > 0 else 0,
"outlier_elimination_rate": 1 - (len(best_inliers) / n_points) if n_points > 0 else 0
}
if verbose:
print(f"Final results after {iteration} iterations:")
print(f" Inliers: {len(best_inliers)}/{n_points} ({stats['inlier_ratio']*100:.1f}%)")
print(f" Outliers eliminated: {stats['outlier_elimination_rate']*100:.1f}%")
return inlier_mask, best_inliers, best_homography, stats
def filter_opencv_matches(self, matches, kp1, kp2, verbose=False):
"""
Filter OpenCV matches using homography RANSAC
Args:
matches: List of cv2.DMatch objects
kp1: Keypoints from first image
kp2: Keypoints from second image
verbose: Print statistics
Returns:
good_matches: Filtered matches
homography_matrix: Estimated homography transformation
stats: Statistics dictionary
"""
if len(matches) < self.min_samples:
return [], None, {"error": "Not enough matches"}
# Extract point coordinates
src_pts = np.float32([kp1[m.queryIdx].pt for m in matches])
dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches])
# Apply RANSAC
inlier_mask, inlier_indices, homography_matrix, stats = self.eliminate_outliers(
src_pts, dst_pts, verbose=verbose
)
# Filter matches based on inliers
good_matches = [matches[i] for i in inlier_indices]
return good_matches, homography_matrix, stats
def filter_homography_ransac(src_pts, dst_pts, threshold, confidence=0.99, max_iterations=1000):
"""
Convenience function to filter correspondences using homography RANSAC
Args:
src_pts: Source points (Nx2)
dst_pts: Destination points (Nx2)
threshold: Reprojection error threshold
Returns:
detected_inliers: Set of inlier indices
"""
# Initialize homography RANSAC
homography_ransac = HomographyRANSAC(
threshold=threshold,
confidence=confidence,
max_iterations=max_iterations
)
# Eliminate outliers
inlier_mask, inlier_indices, estimated_homography, stats = homography_ransac.eliminate_outliers(
src_pts, dst_pts, verbose=False
)
# Evaluate results
detected_inliers = set(inlier_indices)
return detected_inliers
RANSAC_THRESHOLD = 12.0
def sift_match(img1, img2, lowe_ratio=0.75, cross_check=True, verbose=False, legacy_filtering=False):
"""
Match two images using SIFT and return the matched keypoints.
:param img1: First image (numpy array).
:param img2: Second image (numpy array).
:param ratio: Lowe's ratio test threshold.
:param cross_check: Whether to use cross-checking. not used if ratio < 1
:param legacy_filtering: Reproduce the pre-fix behavior where filter_scale was
silently skipped (filter_angle applied twice). For comparison only.
:return: list of match objects, and two list of keypoints.
"""
# Initialize SIFT detector
sift = cv2.SIFT_create()
# Find the keypoints and descriptors with SIFT
keypoints1, descriptors1 = sift.detectAndCompute(img1, None)
keypoints2, descriptors2 = sift.detectAndCompute(img2, None)
if len(keypoints1) < 2 or len(keypoints2) < 2:
if verbose:
print("Not enough keypoints found in one of the images.(< 2)")
return [], keypoints1, keypoints2
if lowe_ratio < 1:
# filter matches using lowe's ratio test
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=False)
matches = bf.knnMatch(descriptors1, descriptors2, k=2)
good_matches = []
for m, n in matches:
if m.distance < lowe_ratio * n.distance:
good_matches.append(m)
matches = good_matches
if verbose:
print(f"Found {len(matches)} matches after Lowe's ratio test")
else:
# Create a Brute Force Matcher object
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=cross_check)
# Match descriptors
matches = bf.match(descriptors1, descriptors2)
if verbose:
print(f"Found {len(matches)} matches after brute-force matching with cross_check={cross_check}")
matches = sorted(matches, key=lambda x: x.distance)
# filtering matches
filtering_functions_1st = [filter_angle, filter_scale]
for filter_func in filtering_functions_1st:
applied_func = filter_angle if legacy_filtering else filter_func
matches = list(filter(partial(applied_func, keypoints1, keypoints2), matches))
if verbose:
print(f"Found {len(matches)} matches after {filter_func.__name__}")
# filtering hue, saturation, value
matches = list(filter(partial(filter_hsv, keypoints1, keypoints2, img1, img2), matches))
if verbose:
print(f"Found {len(matches)} matches after filter_hsv")
matches = remove_duplicate_match(keypoints1, keypoints2, matches)
# detecting outliers
if len(matches) >= 15:
src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches])
dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches])
detected_inliers = filter_homography_ransac(src_pts, dst_pts, threshold=RANSAC_THRESHOLD)
matches = [matches[i] for i in detected_inliers]
if verbose:
print(f"Found {len(matches)} matches after filter_homography_ransac")
elif len(matches) > 1:
matches, _ = remove_angle_outliers(keypoints1, keypoints2, matches)
if verbose:
print(f"Found {len(matches)} matches after remove_angle_outliers")
filtering_functions_2nd = [filter_nearest_dist_from2, filter_nearest_dist_from1]
for filter_func in filtering_functions_2nd:
for i in range(NUM_FILTER_NEAREST_ITTER):
if len(matches) < 2:
break
if filter_func == filter_nearest_dist_from2:
locations = np.array([keypoints2[m.trainIdx].pt for m in matches])
else:
locations = np.array([keypoints1[m.queryIdx].pt for m in matches])
tree = KDTree(locations, copy_data=True)
matches = list(filter(partial(filter_func, keypoints1, keypoints2, tree, matches.copy()),
matches))
if verbose:
print(f"Found {len(matches)} matches after {filter_func.__name__}, itter {i}")
if len(matches) < 2:
if verbose:
print("Not enough matches after filtering.(< 2)")
return [], keypoints1, keypoints2
return matches, keypoints1, keypoints2
### Ported from diffusers script 2025.10.28 Takemoto###
def compute_snr(timesteps, alphas_cumprod):
"""
Computes SNR as per https://github.com/TiankaiHang/Min-SNR-Diffusion-Training/blob/521b624bd70c67cee4bdf49225915f5945a872e3/guided_diffusion/gaussian_diffusion.py#L847-L849
"""
# Get alphas and sigmas.
alpha = get_alphas(timesteps, alphas_cumprod)
sigma = get_sigmas(timesteps, alphas_cumprod)
# Compute SNR.
snr = (alpha / sigma) ** 2
return snr
def get_alphas(timesteps, alphas_cumprod):
sqrt_alphas_cumprod = alphas_cumprod ** 0.5
# Expand the tensors.
# Adapted from https://github.com/TiankaiHang/Min-SNR-Diffusion-Training/blob/521b624bd70c67cee4bdf49225915f5945a872e3/guided_diffusion/gaussian_diffusion.py#L1026
sqrt_alphas_cumprod = sqrt_alphas_cumprod.to(device=timesteps.device)[timesteps].float()
while len(sqrt_alphas_cumprod.shape) < len(timesteps.shape):
sqrt_alphas_cumprod = sqrt_alphas_cumprod[..., None]
alpha = sqrt_alphas_cumprod.expand(timesteps.shape)
return alpha
def get_sigmas(timesteps, alphas_cumprod):
sqrt_one_minus_alphas_cumprod = (1.0 - alphas_cumprod) ** 0.5
sqrt_one_minus_alphas_cumprod = sqrt_one_minus_alphas_cumprod.to(device=timesteps.device)[timesteps].float()
while len(sqrt_one_minus_alphas_cumprod.shape) < len(timesteps.shape):
sqrt_one_minus_alphas_cumprod = sqrt_one_minus_alphas_cumprod[..., None]
sigma = sqrt_one_minus_alphas_cumprod.expand(timesteps.shape)
return sigma