mirror of
https://github.com/hanzoai/jin.git
synced 2026-10-30 18:33:17 +00:00
129 lines
5.0 KiB
Python
129 lines
5.0 KiB
Python
import torch
|
|
import torchvision
|
|
|
|
def rotate_and_translate_batch(images, translation, theta, interp_mode = "nearest"):
|
|
"""Custom rotation operation that works on batches of images with batches of angles.
|
|
Assumes theta has shape (batch_size, 1) and translation has shape (batch_size, 2)."""
|
|
# unsqueeze shapes for generating affines
|
|
translation = translation.unsqueeze(-1)
|
|
theta = theta.unsqueeze(-1)
|
|
|
|
cos_theta = torch.cos(theta)
|
|
sin_theta = torch.sin(theta)
|
|
rot_mat = torch.cat([cos_theta, -sin_theta, sin_theta, cos_theta], dim=-1)
|
|
rot_mat = rot_mat.view(-1, 2, 2)
|
|
|
|
# concat rotation with translation
|
|
rot_mat = torch.cat([rot_mat, translation], dim = -1)
|
|
|
|
grid = torch.nn.functional.affine_grid(rot_mat, images.size(), align_corners = False)
|
|
return torch.nn.functional.grid_sample(images, grid, mode = interp_mode, align_corners = False)
|
|
|
|
class SaccadeCropper(torch.nn.Module):
|
|
def __init__(self,
|
|
input_h,
|
|
input_w,
|
|
target_h = 224,
|
|
target_w = 224,
|
|
max_translation = 40,
|
|
max_rotation = 2,
|
|
interp_mode = "nearest",
|
|
default_device = "cuda" if torch.cuda.is_available() else "cpu",
|
|
embed_affines = True,
|
|
translation_L = 4,
|
|
rotation_L = 10
|
|
):
|
|
super().__init__()
|
|
self.input_h = input_h
|
|
self.input_w = input_w
|
|
self.target_h = target_h
|
|
self.target_w = target_w
|
|
|
|
self.max_translation = max_translation
|
|
self.max_translation_frac = max_translation / input_h
|
|
|
|
self.max_rotation = max_rotation
|
|
self.max_rotation_rad = max_rotation * torch.pi / 180
|
|
self.interp_mode = interp_mode
|
|
|
|
# coefficients for the NeRF-like encoder, see method
|
|
self.coeff = {}
|
|
self.embed_affines = embed_affines
|
|
self.translation_L = translation_L
|
|
self.rotation_L = rotation_L
|
|
# since there are two translation dims and one rotation dim
|
|
self.affine_embed_dim = translation_L * 4 + rotation_L * 2
|
|
|
|
self.default_device = default_device
|
|
self.requires_grad_(False)
|
|
|
|
|
|
def forward(self, x):
|
|
with torch.no_grad():
|
|
b, c, h, w = x.shape
|
|
# generate rotations of shape (b, 1)
|
|
rotations = torch.empty(b, 1, device = self.default_device).uniform_(-self.max_rotation_rad, self.max_rotation_rad)
|
|
translations = torch.empty(b, 2, device = self.default_device).uniform_(-self.max_translation_frac, self.max_translation_frac)
|
|
|
|
# apply the roations
|
|
x_affined = rotate_and_translate_batch(x,
|
|
translations,
|
|
rotations,
|
|
interp_mode = self.interp_mode)
|
|
if self.embed_affines:
|
|
rotations = self.nerf_like_encoder(rotations, L = self.rotation_L)
|
|
translations = self.nerf_like_encoder(translations, L = self.translation_L)
|
|
transform_values = torch.cat([rotations, translations], dim = -1)
|
|
|
|
# generate two views via center crop
|
|
x1 = torchvision.transforms.functional.center_crop(x_affined, (self.target_h, self.target_w))
|
|
x2 = torchvision.transforms.functional.center_crop(x, (self.target_h, self.target_w))
|
|
|
|
return x1, x2, transform_values
|
|
|
|
def nerf_like_encoder(self, in_tensor, L = 10):
|
|
"""This embeds the translation or rotation values into a sinuisoidal space.
|
|
This is based on the NeRF paper, where the translation and rotation values are embedded
|
|
in a higher-dimensional sinuisoidal space, which improves performance.
|
|
https://arxiv.org/pdf/2003.08934.pdf
|
|
|
|
Parameters
|
|
----------
|
|
in_tensor : torch.Tensor
|
|
Tensor of shape (batch_size, n) containing the translation or rotation values.
|
|
|
|
L : int
|
|
Half the number of dimensions to use for the embedding.
|
|
"""
|
|
# array of the coefficients for the sinuisoidal embedding
|
|
batch_size = in_tensor.shape[0]
|
|
|
|
# compute coefficient only once since this gets called multiple times
|
|
if L in self.coeff:
|
|
coeff = self.coeff[L].to(in_tensor.device)
|
|
else:
|
|
coeff = (torch.pi * (2 ** torch.arange(L, dtype = torch.float32))).unsqueeze(0).unsqueeze(0)
|
|
self.coeff[L] = coeff
|
|
|
|
coeff = coeff.to(in_tensor.device)
|
|
|
|
|
|
embedding = torch.concat([torch.sin(in_tensor.unsqueeze(-1) * coeff),
|
|
torch.cos(in_tensor.unsqueeze(-1) * coeff)],
|
|
dim = -1)
|
|
|
|
return embedding.view(batch_size, -1)
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from PIL import Image
|
|
sc = SaccadeCropper(304, 304)
|
|
x = torch.rand(64, 3, 304, 304)
|
|
|
|
y1, y2, affines = sc(x)
|
|
|
|
img_rot = torchvision.transforms.ToPILImage()(y1[0])
|
|
img = torchvision.transforms.ToPILImage()(y2[0])
|
|
|