PeizhaoEdinburgh's picture
Upload standalone NLOS model, inference code, and verified package
97fd768 verified
Raw History Blame Contribute Delete
5.02 kB
"""Standalone architecture extracted from the original lidarx implementation."""
import torch
from torch import nn
def coarse_projection_features(x: torch.Tensor) -> torch.Tensor:
"""Return `projection_coarse_32x8x8` features for `[B, 1, D, H, W]` tensors."""
if x.ndim != 5:
msg = f"expected input with shape [batch, channel, depth, height, width], got {x.shape}"
raise ValueError(msg)
if x.shape[1] != 1:
msg = f"expected one histogram channel, got {x.shape[1]}"
raise ValueError(msg)
x = x.squeeze(1)
depth = x.shape[1]
height = x.shape[2]
width = x.shape[3]
if depth % 32 != 0 or height % 8 != 0 or width % 8 != 0:
msg = (
"hist coarse projection requires depth divisible by 32 and "
f"height/width divisible by 8, got {(depth, height, width)}"
)
raise ValueError(msg)
global_features = torch.cat(
[
x.mean(dim=(1, 2, 3), keepdim=False).unsqueeze(1),
x.std(dim=(1, 2, 3), unbiased=False, keepdim=False).unsqueeze(1),
x.amin(dim=(1, 2, 3), keepdim=False).unsqueeze(1),
x.amax(dim=(1, 2, 3), keepdim=False).unsqueeze(1),
],
dim=1,
)
depth_features = torch.cat(
[
x.mean(dim=(2, 3)),
x.std(dim=(2, 3), unbiased=False),
],
dim=1,
)
row_features = x.mean(dim=(1, 3))
col_features = x.mean(dim=(1, 2))
coarse = (
x.reshape(
x.shape[0],
32,
depth // 32,
8,
height // 8,
8,
width // 8,
)
.mean(dim=(2, 4, 6))
.reshape(x.shape[0], -1)
)
return torch.cat(
[global_features, depth_features, row_features, col_features, coarse],
dim=1,
)
def _coarse_grid_features(
x: torch.Tensor,
*,
depth_bins: int,
row_bins: int,
col_bins: int,
) -> torch.Tensor:
depth = x.shape[1]
height = x.shape[2]
width = x.shape[3]
if depth % depth_bins != 0 or height % row_bins != 0 or width % col_bins != 0:
msg = (
"hist coarse grid requires dimensions divisible by requested bins, "
f"got volume {(depth, height, width)} and bins {(depth_bins, row_bins, col_bins)}"
)
raise ValueError(msg)
return (
x.reshape(
x.shape[0],
depth_bins,
depth // depth_bins,
row_bins,
height // row_bins,
col_bins,
width // col_bins,
)
.mean(dim=(2, 4, 6))
.reshape(x.shape[0], -1)
)
def multiscale_coarse_projection_features(x: torch.Tensor) -> torch.Tensor:
"""Return coarse histogram features with added low-resolution context views."""
base = coarse_projection_features(x)
volume = x.squeeze(1)
depth_context = _coarse_grid_features(volume, depth_bins=16, row_bins=8, col_bins=8)
spatial_context = _coarse_grid_features(volume, depth_bins=32, row_bins=4, col_bins=4)
return torch.cat([base, depth_context, spatial_context], dim=1)
class HistCoarseMultiScaleMLP(nn.Module):
"""Small supervised MLP over multi-scale coarse histogram projections.
This keeps the strong non-backbone inductive bias of the coarse-only model,
but gives the classifier both the best 32x8x8 block view and two coarser
context views before a lightweight gated residual head.
"""
def __init__(self, hidden_features: int = 128, dropout: float = 0.2) -> None:
super().__init__()
bottleneck_features = max(1, hidden_features // 2)
self.input_norm = nn.LazyBatchNorm1d()
self.input_projection = nn.Sequential(
nn.LazyLinear(hidden_features),
nn.SiLU(inplace=True),
nn.Dropout(dropout),
)
self.residual = nn.Sequential(
nn.Linear(hidden_features, hidden_features),
nn.SiLU(inplace=True),
nn.Dropout(dropout),
nn.Linear(hidden_features, hidden_features),
)
self.gate = nn.Sequential(
nn.Linear(hidden_features, hidden_features),
nn.Sigmoid(),
)
self.head = nn.Sequential(
nn.LayerNorm(hidden_features),
nn.SiLU(inplace=True),
nn.Dropout(dropout),
nn.Linear(hidden_features, bottleneck_features),
nn.SiLU(inplace=True),
nn.Dropout(dropout),
nn.Linear(bottleneck_features, 1),
)
def forward_features(self, x: torch.Tensor) -> torch.Tensor:
"""Return the learned 128-D representation immediately before the classifier."""
features = self.input_norm(multiscale_coarse_projection_features(x))
embedding = self.input_projection(features)
return embedding + self.gate(embedding) * self.residual(embedding)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.head(self.forward_features(x))