102 lines
3.2 KiB
Python
102 lines
3.2 KiB
Python
import math
|
|
|
|
import torch
|
|
import torch.nn.functional as F
|
|
|
|
|
|
def chunked_interpolate(x, scale_factor, mode="nearest"):
|
|
"""
|
|
Interpolate large tensors by chunking along the channel dimension. https://discuss.pytorch.org/t/error-using-f-interpolate-for-large-3d-input/207859
|
|
Only supports 'nearest' interpolation mode.
|
|
|
|
Args:
|
|
x (torch.Tensor): Input tensor (B, C, D, H, W)
|
|
scale_factor: Tuple of scaling factors (d, h, w)
|
|
|
|
Returns:
|
|
torch.Tensor: Interpolated tensor
|
|
"""
|
|
assert (
|
|
mode == "nearest"
|
|
), "Only the nearest mode is supported" # actually other modes are theoretically supported but not tested
|
|
if len(x.shape) != 5:
|
|
raise ValueError("Expected 5D input tensor (B, C, D, H, W)")
|
|
|
|
# Calculate max chunk size to avoid int32 overflow. num_elements < max_int32
|
|
# Max int32 is 2^31 - 1
|
|
max_elements_per_chunk = 2**31 - 1
|
|
|
|
# Calculate output spatial dimensions
|
|
out_d = math.ceil(x.shape[2] * scale_factor[0])
|
|
out_h = math.ceil(x.shape[3] * scale_factor[1])
|
|
out_w = math.ceil(x.shape[4] * scale_factor[2])
|
|
|
|
# Calculate max channels per chunk to stay under limit
|
|
elements_per_channel = out_d * out_h * out_w
|
|
max_channels = max_elements_per_chunk // (x.shape[0] * elements_per_channel)
|
|
|
|
# Use smaller of max channels or input channels
|
|
chunk_size = min(max_channels, x.shape[1])
|
|
|
|
# Ensure at least 1 channel per chunk
|
|
chunk_size = max(1, chunk_size)
|
|
|
|
chunks = []
|
|
for i in range(0, x.shape[1], chunk_size):
|
|
start_idx = i
|
|
end_idx = min(i + chunk_size, x.shape[1])
|
|
|
|
chunk = x[:, start_idx:end_idx, :, :, :]
|
|
|
|
interpolated_chunk = F.interpolate(chunk, scale_factor=scale_factor, mode="nearest")
|
|
|
|
chunks.append(interpolated_chunk)
|
|
|
|
if not chunks:
|
|
raise ValueError(f"No chunks were generated. Input shape: {x.shape}")
|
|
|
|
# Concatenate chunks along channel dimension
|
|
return torch.cat(chunks, dim=1)
|
|
|
|
|
|
def pixel_shuffle_3d(x, upscale_factor):
|
|
"""
|
|
3D pixelshuffle operation.
|
|
"""
|
|
B, C, T, H, W = x.shape
|
|
r = upscale_factor
|
|
assert C % (r * r * r) == 0, "channel number must be a multiple of the cube of the upsampling factor"
|
|
|
|
C_new = C // (r * r * r)
|
|
x = x.view(B, C_new, r, r, r, T, H, W)
|
|
|
|
x = x.permute(0, 1, 5, 2, 6, 3, 7, 4)
|
|
|
|
y = x.reshape(B, C_new, T * r, H * r, W * r)
|
|
return y
|
|
|
|
|
|
def pixel_unshuffle_3d(x, downsample_factor):
|
|
"""
|
|
3D pixel unshuffle operation.
|
|
"""
|
|
B, C, T, H, W = x.shape
|
|
|
|
r = downsample_factor
|
|
assert T % r == 0, f"time dimension must be a multiple of the downsampling factor, got shape {x.shape}"
|
|
assert H % r == 0, f"height dimension must be a multiple of the downsampling factor, got shape {x.shape}"
|
|
assert W % r == 0, f"width dimension must be a multiple of the downsampling factor, got shape {x.shape}"
|
|
T_new = T // r
|
|
H_new = H // r
|
|
W_new = W // r
|
|
C_new = C * (r * r * r)
|
|
|
|
x = x.view(B, C, T_new, r, H_new, r, W_new, r)
|
|
x = x.permute(0, 1, 3, 5, 7, 2, 4, 6)
|
|
y = x.reshape(B, C_new, T_new, H_new, W_new)
|
|
return y
|
|
|
|
|
|
def ceil_to_divisible(n: int, dividend: int) -> int:
|
|
return math.ceil(dividend / (dividend // n))
|