277 lines
8.7 KiB
Python
Executable File
277 lines
8.7 KiB
Python
Executable File
# Copyright 2024 NVIDIA CORPORATION & AFFILIATES
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
# Parts of this file are adapted from https://github.com/hpcaitech/Open-Sora/blob/main/opensora/datasets/video_transforms.py#L161
|
|
|
|
import numbers
|
|
|
|
import numpy as np
|
|
import torch
|
|
import torchvision.transforms as T
|
|
from PIL import Image
|
|
|
|
TRANSFORMS = dict()
|
|
|
|
|
|
def _is_tensor_video_clip(clip):
|
|
if not torch.is_tensor(clip):
|
|
raise TypeError("clip should be Tensor. Got %s" % type(clip))
|
|
|
|
if not clip.ndimension() == 4:
|
|
raise ValueError("clip should be 4D. Got %dD" % clip.dim())
|
|
|
|
return True
|
|
|
|
|
|
def crop(clip, i, j, h, w):
|
|
"""
|
|
Args:
|
|
clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
|
|
"""
|
|
if len(clip.size()) != 4:
|
|
raise ValueError("clip should be a 4D tensor")
|
|
return clip[..., i : i + h, j : j + w]
|
|
|
|
|
|
def resize(clip, target_size, interpolation_mode):
|
|
if len(target_size) != 2:
|
|
raise ValueError(f"target size should be tuple (height, width), instead got {target_size}")
|
|
return torch.nn.functional.interpolate(clip, size=target_size, mode=interpolation_mode, align_corners=False)
|
|
|
|
|
|
def resize_crop_to_fill(clip, target_size):
|
|
if not _is_tensor_video_clip(clip):
|
|
raise ValueError("clip should be a 4D torch.tensor")
|
|
h, w = clip.size(-2), clip.size(-1)
|
|
th, tw = target_size[0], target_size[1]
|
|
rh, rw = th / h, tw / w
|
|
if rh > rw:
|
|
sh, sw = th, round(w * rh)
|
|
clip = resize(clip, (sh, sw), "bilinear")
|
|
i = 0
|
|
j = int(round(sw - tw) / 2.0)
|
|
else:
|
|
sh, sw = round(h * rw), tw
|
|
clip = resize(clip, (sh, sw), "bilinear")
|
|
i = int(round(sh - th) / 2.0)
|
|
j = 0
|
|
assert i + th <= clip.size(-2) and j + tw <= clip.size(-1)
|
|
return crop(clip, i, j, th, tw)
|
|
|
|
|
|
def resize_crop_to_fill_image(pil_image, image_size):
|
|
w, h = pil_image.size # PIL is (W, H)
|
|
th, tw = image_size
|
|
rh, rw = th / h, tw / w
|
|
if rh > rw:
|
|
sh, sw = th, round(w * rh)
|
|
image = pil_image.resize((sw, sh), Image.BICUBIC)
|
|
i = 0
|
|
j = int(round((sw - tw) / 2.0))
|
|
else:
|
|
sh, sw = round(h * rw), tw
|
|
image = pil_image.resize((sw, sh), Image.BICUBIC)
|
|
i = int(round((sh - th) / 2.0))
|
|
j = 0
|
|
arr = np.array(image)
|
|
assert i + th <= arr.shape[0] and j + tw <= arr.shape[1]
|
|
return Image.fromarray(arr[i : i + th, j : j + tw])
|
|
|
|
|
|
def to_tensor(clip):
|
|
"""
|
|
Convert tensor data type from uint8 to float, divide value by 255.0 and
|
|
permute the dimensions of clip tensor
|
|
Args:
|
|
clip (torch.tensor, dtype=torch.uint8): Size is (T, C, H, W)
|
|
Return:
|
|
clip (torch.tensor, dtype=torch.float): Size is (T, C, H, W)
|
|
"""
|
|
_is_tensor_video_clip(clip)
|
|
if not clip.dtype == torch.uint8:
|
|
raise TypeError("clip tensor should have data type uint8. Got %s" % str(clip.dtype))
|
|
# return clip.float().permute(3, 0, 1, 2) / 255.0
|
|
return clip.float() / 255.0
|
|
|
|
|
|
class ToTensorVideo:
|
|
"""
|
|
Convert tensor data type from uint8 to float, divide value by 255.0 and
|
|
permute the dimensions of clip tensor
|
|
"""
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def __call__(self, clip):
|
|
"""
|
|
Args:
|
|
clip (torch.tensor, dtype=torch.uint8): Size is (T, C, H, W)
|
|
Return:
|
|
clip (torch.tensor, dtype=torch.float): Size is (T, C, H, W)
|
|
"""
|
|
return to_tensor(clip)
|
|
|
|
def __repr__(self) -> str:
|
|
return self.__class__.__name__
|
|
|
|
|
|
def resize_scale(clip, target_size, interpolation_mode):
|
|
if len(target_size) != 2:
|
|
raise ValueError(f"target size should be tuple (height, width), instead got {target_size}")
|
|
H, W = clip.size(-2), clip.size(-1)
|
|
scale_ = target_size[0] / min(H, W)
|
|
th, tw = int(round(H * scale_)), int(round(W * scale_))
|
|
return torch.nn.functional.interpolate(clip, size=(th, tw), mode=interpolation_mode, align_corners=False)
|
|
|
|
|
|
def resized_crop(clip, i, j, h, w, size, interpolation_mode="bilinear"):
|
|
"""
|
|
Do spatial cropping and resizing to the video clip
|
|
Args:
|
|
clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
|
|
i (int): i in (i,j) i.e coordinates of the upper left corner.
|
|
j (int): j in (i,j) i.e coordinates of the upper left corner.
|
|
h (int): Height of the cropped region.
|
|
w (int): Width of the cropped region.
|
|
size (tuple(int, int)): height and width of resized clip
|
|
Returns:
|
|
clip (torch.tensor): Resized and cropped clip. Size is (T, C, H, W)
|
|
"""
|
|
if not _is_tensor_video_clip(clip):
|
|
raise ValueError("clip should be a 4D torch.tensor")
|
|
clip = crop(clip, i, j, h, w)
|
|
clip = resize(clip, size, interpolation_mode)
|
|
return clip
|
|
|
|
|
|
def center_crop(clip, crop_size):
|
|
if not _is_tensor_video_clip(clip):
|
|
raise ValueError("clip should be a 4D torch.tensor")
|
|
h, w = clip.size(-2), clip.size(-1)
|
|
th, tw = crop_size
|
|
if h < th or w < tw:
|
|
raise ValueError("height and width must be no smaller than crop_size")
|
|
|
|
i = int(round((h - th) / 2.0))
|
|
j = int(round((w - tw) / 2.0))
|
|
return crop(clip, i, j, th, tw)
|
|
|
|
|
|
def get_closest_ratio(height: float, width: float, ratios: dict):
|
|
aspect_ratio = height / width
|
|
closest_ratio = min(ratios.keys(), key=lambda ratio: abs(float(ratio) - aspect_ratio))
|
|
return ratios[closest_ratio], float(closest_ratio)
|
|
|
|
|
|
class ResizeCrop:
|
|
def __init__(self, size):
|
|
if isinstance(size, numbers.Number):
|
|
self.size = (int(size), int(size))
|
|
else:
|
|
self.size = size
|
|
|
|
def __call__(self, clip):
|
|
clip = resize_crop_to_fill(clip, self.size)
|
|
return clip
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{self.__class__.__name__}(size={self.size})"
|
|
|
|
|
|
class ResizeCenterCropVideo:
|
|
"""
|
|
First scale to the specified size in equal proportion to the short edge,
|
|
then center cropping
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
size,
|
|
interpolation_mode="bilinear",
|
|
):
|
|
if isinstance(size, tuple):
|
|
if len(size) != 2:
|
|
raise ValueError(f"size should be tuple (height, width), instead got {size}")
|
|
self.size = size
|
|
else:
|
|
self.size = (size, size)
|
|
|
|
self.interpolation_mode = interpolation_mode
|
|
|
|
def __call__(self, clip):
|
|
"""
|
|
Args:
|
|
clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
|
|
Returns:
|
|
torch.tensor: scale resized / center cropped video clip.
|
|
size is (T, C, crop_size, crop_size)
|
|
"""
|
|
clip_resize = resize_scale(clip=clip, target_size=self.size, interpolation_mode=self.interpolation_mode)
|
|
clip_center_crop = center_crop(clip_resize, self.size)
|
|
return clip_center_crop
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{self.__class__.__name__}(size={self.size}, interpolation_mode={self.interpolation_mode}"
|
|
|
|
|
|
def register_transform(transform):
|
|
name = transform.__name__
|
|
if name in TRANSFORMS:
|
|
raise RuntimeError(f"Transform {name} has already registered.")
|
|
TRANSFORMS.update({name: transform})
|
|
|
|
|
|
def get_transform(type, resolution):
|
|
transform = TRANSFORMS[type](resolution)
|
|
transform = T.Compose(transform)
|
|
transform.image_size = resolution
|
|
return transform
|
|
|
|
|
|
@register_transform
|
|
def default_train(n_px):
|
|
transform = [
|
|
T.Lambda(lambda img: img.convert("RGB")),
|
|
T.Resize(n_px), # Image.BICUBIC
|
|
T.CenterCrop(n_px),
|
|
# T.RandomHorizontalFlip(),
|
|
T.ToTensor(),
|
|
T.Normalize([0.5], [0.5]),
|
|
]
|
|
return transform
|
|
|
|
|
|
@register_transform
|
|
def default_train_video(image_size=(256, 256)):
|
|
transform = [
|
|
ToTensorVideo(), # TCHW
|
|
ResizeCrop(image_size),
|
|
T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
|
|
]
|
|
return transform
|
|
|
|
|
|
def read_image_from_path(path, image_size):
|
|
image = Image.open(path).convert("RGB")
|
|
transform = T.Compose(
|
|
[
|
|
T.Lambda(lambda pil_image: resize_crop_to_fill_image(pil_image, image_size)),
|
|
T.ToTensor(),
|
|
T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
|
|
]
|
|
)
|
|
return transform(image) # C,H,W, range (-1, 1)
|