6e1a22ba8b
assets/conveyors (274 МБ) - ленты и угловая секция NVIDIA, на которые ссылается сцена относительным путём. Раньше исключались как перекачиваемые, но без них сцена не композится из коробки. cv/ - код стереодвижков, которые вызывает control_test, без весов: * defom-stereo - рабочий бейзлайн (DEFOM vitl, вход 480, iters 24) * crestereo - второй движок, точнее по габаритам (MAE 23.5 против 32.8 мм) * fast-foundationstereo - проверялся, в бейзлайн не вошёл * circular_section.py - показатель кругового сечения, перенесён в measure_plane.py: выравнивает облако по СОБСТВЕННЫМ главным осям и режет на пяти высотах вдоль каждой. Три самодельные версии (мировые оси, одно сечение) давали хуже; результаты проверки на эталонной геометрии - в circular_section_results.json Веса по-прежнему не в репозитории - источники в MODELS.md. Наборы кадров прежних прогонов (cv/flow_*, 1.26 ГБ) исключены: это выход, а не исходники. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
123 lines
4.8 KiB
Python
Executable File
123 lines
4.8 KiB
Python
Executable File
import torch,os,sys
|
|
code_dir = os.path.dirname(os.path.realpath(__file__))
|
|
sys.path.append(f'{code_dir}/../../')
|
|
import torch.nn.functional as F
|
|
import numpy as np
|
|
|
|
|
|
class InputPadder:
|
|
""" Pads images such that dimensions are divisible by 8 """
|
|
def __init__(self, dims, mode='sintel', divis_by=8, force_square=False):
|
|
self.ht, self.wd = dims[-2:]
|
|
if force_square:
|
|
max_side = max(self.ht, self.wd)
|
|
pad_ht = ((max_side // divis_by) + 1) * divis_by - self.ht
|
|
pad_wd = ((max_side // divis_by) + 1) * divis_by - self.wd
|
|
else:
|
|
pad_ht = (((self.ht // divis_by) + 1) * divis_by - self.ht) % divis_by
|
|
pad_wd = (((self.wd // divis_by) + 1) * divis_by - self.wd) % divis_by
|
|
if mode == 'sintel':
|
|
self._pad = [pad_wd//2, pad_wd - pad_wd//2, pad_ht//2, pad_ht - pad_ht//2]
|
|
else:
|
|
self._pad = [pad_wd//2, pad_wd - pad_wd//2, 0, pad_ht]
|
|
|
|
def pad(self, *inputs):
|
|
assert all((x.ndim == 4) for x in inputs)
|
|
return [F.pad(x, self._pad, mode='replicate') for x in inputs]
|
|
|
|
def unpad(self, x):
|
|
assert x.ndim == 4
|
|
ht, wd = x.shape[-2:]
|
|
c = [self._pad[2], ht-self._pad[3], self._pad[0], wd-self._pad[1]]
|
|
return x[..., c[0]:c[1], c[2]:c[3]]
|
|
|
|
|
|
@torch.compile
|
|
def bilinear_sampler1d(img, x_coords, mode='bilinear', align_corners=True):
|
|
"""
|
|
1D bilinear sampling along width dimension only (for stereo applications)
|
|
Much faster than grid_sample for stereo where y is constant
|
|
|
|
Args:
|
|
img: (B, C, 1, W) input tensor
|
|
x_coords: (B, 1, W_out, 1) x coordinates in pixel space [0, W-1]
|
|
mode: interpolation mode ('bilinear' or 'nearest')
|
|
align_corners: if True, corner pixels are aligned (like grid_sample)
|
|
|
|
Returns:
|
|
sampled: (B, C, 1, W_coords) sampled tensor
|
|
mask: (B, 1, H, W) validity mask (if mask=True)
|
|
"""
|
|
B, C, H_img, W = img.shape
|
|
x = x_coords.reshape(B,-1) # (B, W_out)
|
|
|
|
if align_corners:
|
|
# align_corners=True: coordinate range [0, W-1] maps to pixel centers
|
|
# This matches grid_sample with align_corners=True behavior
|
|
x_normalized = x
|
|
else:
|
|
# align_corners=False: coordinate range [0, W-1] maps to pixel edges
|
|
# Need to adjust coordinates to match grid_sample with align_corners=False
|
|
# grid_sample maps [-1, 1] to [0, W-1] when align_corners=False
|
|
# So our [0, W-1] input should be treated as [0.5, W-0.5] in pixel space
|
|
x_normalized = x + 0.5
|
|
|
|
if mode == 'nearest':
|
|
# Nearest neighbor sampling with zero padding outside [0, W-1]
|
|
if align_corners:
|
|
x_nearest = torch.round(x_normalized).long()
|
|
else:
|
|
x_nearest = torch.floor(x_normalized).long()
|
|
valid = (x_nearest >= 0) & (x_nearest < W) # (B, W_out)
|
|
x_index = torch.clamp(x_nearest, 0, W-1)
|
|
sampled = torch.gather(img, 3, x_index.view(B,1,1,-1).expand(B,C,1,-1))
|
|
sampled = sampled * valid.view(B,1,1,-1).to(img.dtype)
|
|
|
|
else: # bilinear
|
|
# Get integer and fractional parts
|
|
x_floor = torch.floor(x_normalized)
|
|
x_ceil = x_floor + 1
|
|
x_frac = x_normalized - x_floor # (B, W_out)
|
|
|
|
# Zero padding behavior: mark validity and zero-out invalid contributions
|
|
valid_floor = (x_floor >= 0) & (x_floor < W)
|
|
valid_ceil = (x_ceil >= 0) & (x_ceil < W)
|
|
x_floor_clamped = torch.clamp(x_floor, 0, W-1)
|
|
x_ceil_clamped = torch.clamp(x_ceil, 0, W-1)
|
|
|
|
# Create index tensors
|
|
batch_idx = torch.arange(B, device=img.device).view(B, 1)
|
|
img_floor = torch.gather(img, 3, x_floor_clamped.view(B,1,1,-1).expand(B,C,1,-1).long())
|
|
img_ceil = torch.gather(img, 3, x_ceil_clamped.view(B,1,1,-1).expand(B,C,1,-1).long())
|
|
|
|
# Apply validity masks (zero out-of-bounds samples)
|
|
img_floor = img_floor * valid_floor.view(B,1,1,-1).to(img.dtype)
|
|
img_ceil = img_ceil * valid_ceil.view(B,1,1,-1).to(img.dtype)
|
|
|
|
# Linear interpolation
|
|
x_frac = x_frac.view(B,1,1,-1)
|
|
sampled = img_floor * (1 - x_frac) + img_ceil * x_frac
|
|
|
|
return sampled
|
|
|
|
|
|
def bilinear_sampler(img, coords, mode='bilinear', mask=False, low_memory=False, use1d=False):
|
|
""" Wrapper for grid_sample, uses pixel coordinates """
|
|
H, W = img.shape[-2:]
|
|
coords[...,0] = 2*coords[...,0]/(W-1) - 1
|
|
if low_memory:
|
|
B = img.shape[0]
|
|
out = []
|
|
bs = 102400
|
|
for b in np.arange(0,B,bs):
|
|
tmp = F.grid_sample(img[b:b+bs], coords[b:b+bs], align_corners=True)
|
|
out.append(tmp)
|
|
img = torch.cat(out, dim=0)
|
|
else:
|
|
img = F.grid_sample(img, coords, align_corners=True)
|
|
if mask:
|
|
mask = (xgrid > -1) & (ygrid > -1) & (xgrid < 1) & (ygrid < 1)
|
|
return img, mask.float()
|
|
return img
|
|
|