sysu111's picture
Add PXDepth demo
87608ea
Raw
History Blame Contribute Delete
5.88 kB
"""Image and prediction resizing shared by evaluation and inference.
The functions parse user-facing sizes, derive patch-compatible equal-area
shapes, resize RGB tensors for a model, and restore depth or mask maps to the
source resolution. Their return values retain the original image dimensions so
camera-normalized geometry remains consistent after restoration.
"""
from typing import Optional, Tuple
import torch
import torch.nn.functional as F
def parse_size(value: Optional[str]) -> Optional[Tuple[int, int]]:
"""Parse a CLI image-size string in ``WIDTHxHEIGHT`` notation.
Args:
value: Size string, or ``None``/empty text when no target is requested.
Returns:
Positive integer tuple ``(width, height)``, or ``None``.
"""
if value is None or not str(value).strip():
return None
parts = str(value).lower().replace(",", "x").split("x")
if len(parts) != 2:
raise ValueError("Image size must be WIDTHxHEIGHT, for example 1022x770.")
width, height = map(int, parts)
if width <= 0 or height <= 0:
raise ValueError("Image width and height must be positive.")
return width, height
def area_size(
height: int,
width: int,
target_width: int,
target_height: int,
patch_size: int,
) -> Tuple[int, int]:
"""Preserve aspect ratio while matching a reference width-height area.
Args:
height: Original image height ``H``.
width: Original image width ``W``.
target_width: Width defining the desired reference area.
target_height: Height defining the desired reference area.
patch_size: Required divisibility of both output dimensions.
Returns:
Integer ``(new_height, new_width)`` with approximately
``target_width*target_height`` pixels and the original aspect ratio.
"""
area = int(target_width) * int(target_height)
return area_size_from_area(height, width, area, patch_size)
def area_size_from_area(
height: int,
width: int,
target_area: int,
patch_size: int,
) -> Tuple[int, int]:
"""Preserve aspect ratio while matching an explicit target pixel area.
Args:
height: Original image height ``H``.
width: Original image width ``W``.
target_area: Desired number of input pixels before patch rounding.
patch_size: Required divisibility of both output dimensions.
Returns:
Integer ``(new_height, new_width)`` rounded to patch multiples.
"""
area = int(target_area)
if height <= 0 or width <= 0 or area <= 0:
raise ValueError("Image dimensions and target area must be positive.")
aspect = width / height
new_width = int(round((area * aspect) ** 0.5))
new_height = int(round(new_width / aspect))
new_width = max(patch_size, int(round(new_width / patch_size)) * patch_size)
new_height = max(patch_size, int(round(new_height / patch_size)) * patch_size)
return new_height, new_width
def patch_size(height: int, width: int, patch: int) -> Tuple[int, int]:
"""Round spatial dimensions down to valid patch multiples.
Args:
height: Original image height.
width: Original image width.
patch: Positive encoder patch side length.
Returns:
Integer ``(new_height, new_width)``. Already divisible dimensions are
unchanged; smaller results are clamped to one patch.
"""
new_height = height if height % patch == 0 else max(patch, height // patch * patch)
new_width = width if width % patch == 0 else max(patch, width // patch * patch)
return new_height, new_width
def resize_image(
image: torch.Tensor,
target: Optional[Tuple[int, int]],
resize_by_area: bool,
patch: int,
) -> Tuple[torch.Tensor, Tuple[int, int]]:
"""Resize a CHW/BCHW image to the model input resolution.
Args:
image: RGB tensor ``[3,H,W]`` or ``[B,3,H,W]``.
target: Optional ``(width,height)`` reference size.
resize_by_area: Preserve aspect ratio and use only ``target`` area when
``True``; otherwise force the exact target dimensions.
patch: Required encoder patch divisibility.
Returns:
resized: Bilinearly resized tensor preserving the input batch layout.
original_size: Original integer tuple ``(H,W)``.
"""
original = tuple(image.shape[-2:])
if target is None:
height, width = patch_size(*original, patch)
elif resize_by_area:
height, width = area_size(*original, target[0], target[1], patch)
else:
width, height = target
if height % patch or width % patch:
raise ValueError(f"Fixed input size {width}x{height} must be divisible by patch size {patch}.")
if (height, width) == original:
return image, original
batched = image.ndim == 4
source = image if batched else image.unsqueeze(0)
resized = F.interpolate(source, (height, width), mode="bilinear", align_corners=False)
return (resized if batched else resized[0]), original
def resize_map(value: torch.Tensor, size: Tuple[int, int], is_mask: bool = False) -> torch.Tensor:
"""Nearest-resize a depth/probability map while preserving batch layout.
Args:
value: Map tensor ``[H,W]`` or batch ``[B,H,W]``.
size: Target ``(height,width)``.
is_mask: Threshold resized values at ``0.5`` and return boolean output.
Returns:
Tensor ``[H_t,W_t]`` or ``[B,H_t,W_t]``. Non-mask output is floating;
mask output is boolean.
"""
if tuple(value.shape[-2:]) == tuple(size):
return value
batched = value.ndim == 3
source = value.float().unsqueeze(1) if batched else value.float()[None, None]
output = F.interpolate(source, size=size, mode="nearest")
output = output[:, 0] if batched else output[0, 0]
return output > 0.5 if is_mask else output