Jingqiao-ucsc's picture
Upload WiSER private archive chunk: code_snapshots
13a1c10 verified
Raw
History Blame Contribute Delete
28.2 kB
"""Visibility-aware radiomap decoder operating on gathered voxel context."""
from __future__ import annotations
import torch
import torch.nn as nn
import torch.nn.functional as F
def _resolve_group_count(channels: int, max_groups: int = 8) -> int:
groups = min(max(int(max_groups), 1), max(int(channels), 1))
while groups > 1 and channels % groups != 0:
groups -= 1
return max(groups, 1)
class ResidualMLPBlock(nn.Module):
"""Residual MLP block used to deepen the decoder head stack."""
def __init__(self, dim: int, hidden_dim: int, dropout: float = 0.0) -> None:
super().__init__()
self.norm = nn.LayerNorm(dim)
self.fc1 = nn.Linear(dim, hidden_dim)
self.act = nn.GELU()
self.drop1 = nn.Dropout(dropout)
self.fc2 = nn.Linear(hidden_dim, dim)
self.drop2 = nn.Dropout(dropout)
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = x
x = self.norm(x)
x = self.fc1(x)
x = self.act(x)
x = self.drop1(x)
x = self.fc2(x)
x = self.drop2(x)
return residual + x
class QueryHeadStem(nn.Module):
"""Shared token-space stem before scattering query features back to the RX plane."""
def __init__(self, input_dim: int, hidden_dim: int, depth: int, dropout: float = 0.0) -> None:
super().__init__()
self.input_proj = nn.Sequential(
nn.LayerNorm(input_dim),
nn.Linear(input_dim, hidden_dim),
nn.GELU(),
nn.Dropout(dropout),
)
self.blocks = nn.ModuleList(
[ResidualMLPBlock(hidden_dim, hidden_dim * 2, dropout=dropout) for _ in range(max(int(depth), 0))]
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.input_proj(x)
for block in self.blocks:
x = block(x)
return x
class DeepOutputHead(nn.Module):
"""Legacy deeper per-task MLP head kept for compatibility/debugging."""
def __init__(self, input_dim: int, hidden_dim: int, depth: int, out_dim: int = 1, dropout: float = 0.0) -> None:
super().__init__()
self.blocks = nn.ModuleList(
[ResidualMLPBlock(input_dim, hidden_dim * 2, dropout=dropout) for _ in range(max(int(depth), 0))]
)
self.out = nn.Sequential(
nn.LayerNorm(input_dim),
nn.Linear(input_dim, hidden_dim),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(hidden_dim, out_dim),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
for block in self.blocks:
x = block(x)
return self.out(x)
class DenseSelfAttentionBlock(nn.Module):
"""Pre-norm dense transformer block for query refinement."""
def __init__(self, dim: int, num_heads: int, mlp_ratio: float = 4.0, dropout: float = 0.0) -> None:
super().__init__()
self.norm1 = nn.LayerNorm(dim)
self.attn = nn.MultiheadAttention(dim, num_heads, dropout=dropout, batch_first=True)
self.norm2 = nn.LayerNorm(dim)
self.mlp = nn.Sequential(
nn.Linear(dim, int(dim * mlp_ratio)),
nn.GELU(),
nn.Linear(int(dim * mlp_ratio), dim),
)
def forward(self, x: torch.Tensor, key_padding_mask: torch.Tensor | None = None) -> torch.Tensor:
attn_out, _ = self.attn(self.norm1(x), self.norm1(x), self.norm1(x), key_padding_mask=key_padding_mask, need_weights=False)
x = x + attn_out
return x + self.mlp(self.norm2(x))
class DenseCrossAttentionBlock(nn.Module):
"""Pre-norm cross-attention block for query-to-memory fusion."""
def __init__(self, dim: int, context_dim: int, num_heads: int, mlp_ratio: float = 4.0, dropout: float = 0.0) -> None:
super().__init__()
self.norm_q = nn.LayerNorm(dim)
self.norm_ctx = nn.LayerNorm(context_dim)
self.attn = nn.MultiheadAttention(
dim,
num_heads,
dropout=dropout,
batch_first=True,
kdim=context_dim,
vdim=context_dim,
)
self.norm2 = nn.LayerNorm(dim)
self.mlp = nn.Sequential(
nn.Linear(dim, int(dim * mlp_ratio)),
nn.GELU(),
nn.Linear(int(dim * mlp_ratio), dim),
)
def forward(self, x: torch.Tensor, context: torch.Tensor, key_padding_mask: torch.Tensor | None = None) -> torch.Tensor:
attn_out, _ = self.attn(
self.norm_q(x),
self.norm_ctx(context),
self.norm_ctx(context),
key_padding_mask=key_padding_mask,
need_weights=False,
)
x = x + attn_out
return x + self.mlp(self.norm2(x))
class SpatialResidualBlock(nn.Module):
"""Conv residual block for spatial radiomap decoding."""
def __init__(self, channels: int, hidden_channels: int, dropout: float = 0.0) -> None:
super().__init__()
groups = _resolve_group_count(channels)
hidden_groups = _resolve_group_count(hidden_channels)
self.norm1 = nn.GroupNorm(groups, channels)
self.conv1 = nn.Conv2d(channels, hidden_channels, kernel_size=3, padding=1)
self.act = nn.GELU()
self.drop1 = nn.Dropout2d(dropout)
self.norm2 = nn.GroupNorm(hidden_groups, hidden_channels)
self.conv2 = nn.Conv2d(hidden_channels, channels, kernel_size=3, padding=1)
self.drop2 = nn.Dropout2d(dropout)
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = x
x = self.norm1(x)
x = self.conv1(x)
x = self.act(x)
x = self.drop1(x)
x = self.norm2(x)
x = self.conv2(x)
x = self.drop2(x)
return residual + x
class SpatialHeadStem(nn.Module):
"""Shared spatial trunk inspired by the older conv-based radiomap head."""
def __init__(self, channels: int, depth: int, dropout: float = 0.0, use_coord_channels: bool = True) -> None:
super().__init__()
self.channels = int(channels)
self.use_coord_channels = bool(use_coord_channels)
extra_channels = 1 + (2 if self.use_coord_channels else 0)
self.input_proj = nn.Sequential(
nn.Conv2d(self.channels + extra_channels, self.channels, kernel_size=3, padding=1),
nn.GroupNorm(_resolve_group_count(self.channels), self.channels),
nn.GELU(),
nn.Dropout2d(dropout),
)
self.blocks = nn.ModuleList(
[
SpatialResidualBlock(
channels=self.channels,
hidden_channels=max(self.channels * 2, self.channels + 32),
dropout=dropout,
)
for _ in range(max(int(depth), 1))
]
)
def forward(self, x: torch.Tensor, observed_mask: torch.Tensor, extent_mask: torch.Tensor) -> torch.Tensor:
features = [x, observed_mask]
if self.use_coord_channels:
batch_size, _, height, width = x.shape
yy = torch.linspace(-1.0, 1.0, steps=height, device=x.device, dtype=x.dtype)
xx = torch.linspace(-1.0, 1.0, steps=width, device=x.device, dtype=x.dtype)
grid_y, grid_x = torch.meshgrid(yy, xx, indexing="ij")
coord = torch.stack([grid_x, grid_y], dim=0).unsqueeze(0).expand(batch_size, -1, -1, -1)
features.append(coord)
x = self.input_proj(torch.cat(features, dim=1))
x = x * extent_mask
for block in self.blocks:
x = block(x) * extent_mask
return x
class SpatialOutputHead(nn.Module):
"""Small U-Net-like conv head that predicts dense plane outputs."""
def __init__(self, channels: int, hidden_channels: int, depth: int, out_channels: int = 1, dropout: float = 0.0) -> None:
super().__init__()
self.pre_blocks = nn.ModuleList(
[
SpatialResidualBlock(
channels=channels,
hidden_channels=max(channels * 2, channels + 32),
dropout=dropout,
)
for _ in range(max(int(depth), 1))
]
)
self.down = nn.Sequential(
nn.GroupNorm(_resolve_group_count(channels), channels),
nn.GELU(),
nn.Conv2d(channels, hidden_channels, kernel_size=3, stride=2, padding=1),
)
self.bottleneck_blocks = nn.ModuleList(
[
SpatialResidualBlock(
channels=hidden_channels,
hidden_channels=max(hidden_channels * 2, hidden_channels + 32),
dropout=dropout,
)
for _ in range(max(int(depth), 1))
]
)
self.up_proj = nn.Sequential(
nn.GroupNorm(_resolve_group_count(hidden_channels), hidden_channels),
nn.GELU(),
nn.Conv2d(hidden_channels, channels, kernel_size=3, padding=1),
)
self.fuse = nn.Sequential(
nn.Conv2d(channels * 2, channels, kernel_size=3, padding=1),
nn.GroupNorm(_resolve_group_count(channels), channels),
nn.GELU(),
)
self.refine_blocks = nn.ModuleList(
[
SpatialResidualBlock(
channels=channels,
hidden_channels=max(channels * 2, channels + 32),
dropout=dropout,
)
for _ in range(max(int(depth) - 1, 0))
]
)
self.out = nn.Conv2d(channels, out_channels, kernel_size=1)
def forward(self, x: torch.Tensor, extent_mask: torch.Tensor) -> torch.Tensor:
skip = x * extent_mask
for block in self.pre_blocks:
skip = block(skip) * extent_mask
down = self.down(skip)
down_mask = F.interpolate(extent_mask, size=down.shape[-2:], mode="nearest")
down = down * down_mask
for block in self.bottleneck_blocks:
down = block(down) * down_mask
up = self.up_proj(down)
up = F.interpolate(up, size=skip.shape[-2:], mode="bilinear", align_corners=False)
fused = self.fuse(torch.cat([skip, up], dim=1)) * extent_mask
for block in self.refine_blocks:
fused = block(fused) * extent_mask
return self.out(fused) * extent_mask
class VisibilityAwareQueryDecoder(nn.Module):
"""Fuse per-query voxel memory and predict radiomap outputs."""
def __init__(
self,
*,
query_dim: int,
memory_dim: int,
decoder_dim: int,
num_heads: int,
cross_depth: int,
self_depth: int,
output_head_hidden_dim: int = 0,
output_head_shared_depth: int = 2,
output_head_branch_depth: int = 2,
output_head_dropout: float = 0.0,
mlp_ratio: float = 4.0,
dropout: float = 0.0,
predict_valid: bool = False,
predict_visibility: bool = False,
predict_boundary: bool = False,
predict_uncertainty: bool = False,
use_dual_gain_heads: bool = False,
use_los_residual_heads: bool = False,
) -> None:
super().__init__()
self.predict_valid = bool(predict_valid)
self.predict_visibility = bool(predict_visibility)
self.predict_boundary = bool(predict_boundary)
self.predict_uncertainty = bool(predict_uncertainty)
self.use_los_residual_heads = bool(use_los_residual_heads)
requested_dual_gain = bool(use_dual_gain_heads)
if self.use_los_residual_heads and requested_dual_gain:
raise ValueError("use_los_residual_heads and use_dual_gain_heads are mutually exclusive")
if self.use_los_residual_heads and not self.predict_visibility:
self.predict_visibility = True
self.use_dual_gain_heads = bool(requested_dual_gain and self.predict_visibility)
head_hidden_dim = int(output_head_hidden_dim) if int(output_head_hidden_dim) > 0 else int(decoder_dim * 2)
spatial_head_dim = min(max(head_hidden_dim // 2, 96), 256)
aux_hidden_dim = min(max(spatial_head_dim, 96), 256)
self.query_proj = nn.Sequential(nn.Linear(query_dim, decoder_dim), nn.LayerNorm(decoder_dim))
self.memory_proj = nn.Sequential(nn.Linear(memory_dim, decoder_dim), nn.LayerNorm(decoder_dim))
self.cross_blocks = nn.ModuleList(
[
DenseCrossAttentionBlock(
dim=decoder_dim,
context_dim=decoder_dim,
num_heads=num_heads,
mlp_ratio=mlp_ratio,
dropout=dropout,
)
for _ in range(max(int(cross_depth), 1))
]
)
self.self_blocks = nn.ModuleList(
[
DenseSelfAttentionBlock(
dim=decoder_dim,
num_heads=num_heads,
mlp_ratio=mlp_ratio,
dropout=dropout,
)
for _ in range(max(int(self_depth), 0))
]
)
self.head_stem = QueryHeadStem(
input_dim=int(decoder_dim),
hidden_dim=head_hidden_dim,
depth=int(output_head_shared_depth),
dropout=float(output_head_dropout),
)
self.spatial_token_proj = nn.Sequential(
nn.LayerNorm(head_hidden_dim),
nn.Linear(head_hidden_dim, spatial_head_dim),
nn.GELU(),
nn.Dropout(output_head_dropout),
)
self.spatial_stem = SpatialHeadStem(
channels=spatial_head_dim,
depth=int(output_head_shared_depth),
dropout=float(output_head_dropout),
use_coord_channels=True,
)
self.path_gain_head = (
None
if (self.use_dual_gain_heads or self.use_los_residual_heads)
else SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=aux_hidden_dim,
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
)
self.valid_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=max(aux_hidden_dim // 2, 64),
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.predict_valid
else None
)
self.visibility_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=max(aux_hidden_dim // 2, 64),
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.predict_visibility
else None
)
self.boundary_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=max(aux_hidden_dim // 2, 64),
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.predict_boundary
else None
)
self.clear_gain_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=aux_hidden_dim,
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.use_dual_gain_heads
else None
)
self.blocked_gain_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=aux_hidden_dim,
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.use_dual_gain_heads
else None
)
self.los_gain_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=aux_hidden_dim,
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.use_los_residual_heads
else None
)
self.residual_gain_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=aux_hidden_dim,
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.use_los_residual_heads
else None
)
self.uncertainty_head = (
SpatialOutputHead(
channels=spatial_head_dim,
hidden_channels=max(aux_hidden_dim // 2, 64),
depth=int(output_head_branch_depth),
out_channels=1,
dropout=float(output_head_dropout),
)
if self.predict_uncertainty
else None
)
@staticmethod
def _db_to_power(db: torch.Tensor) -> torch.Tensor:
return torch.pow(10.0, db.clamp(min=-200.0, max=80.0) / 10.0)
@staticmethod
def _power_to_db(power: torch.Tensor) -> torch.Tensor:
return 10.0 * torch.log10(power.clamp_min(1e-20))
@staticmethod
def _build_extent_mask(grid_shape: torch.Tensor, max_h: int, max_w: int, dtype: torch.dtype) -> torch.Tensor:
batch_size = int(grid_shape.shape[0])
mask = torch.zeros((batch_size, 1, max_h, max_w), dtype=dtype, device=grid_shape.device)
for batch_idx in range(batch_size):
grid_h = int(grid_shape[batch_idx, 0].item())
grid_w = int(grid_shape[batch_idx, 1].item())
mask[batch_idx, :, :grid_h, :grid_w] = 1.0
return mask
@staticmethod
def _scatter_query_tokens_to_dense(
token_features: torch.Tensor,
grid_shape: torch.Tensor,
rx_flat_index: torch.Tensor,
query_padding_mask: torch.Tensor | None,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
batch_size, num_queries, channels = token_features.shape
max_h = int(grid_shape[:, 0].max().item())
max_w = int(grid_shape[:, 1].max().item())
dense_flat = token_features.new_zeros((batch_size, channels, max_h * max_w))
count_flat = token_features.new_zeros((batch_size, 1, max_h * max_w))
if query_padding_mask is None:
valid_queries = torch.ones((batch_size, num_queries), dtype=torch.bool, device=token_features.device)
else:
valid_queries = query_padding_mask > 0.5
valid_queries = valid_queries & (rx_flat_index >= 0)
for batch_idx in range(batch_size):
sample_valid = valid_queries[batch_idx]
if not bool(sample_valid.any()):
continue
sample_index = rx_flat_index[batch_idx, sample_valid].long()
sample_tokens = token_features[batch_idx, sample_valid]
grid_w = int(grid_shape[batch_idx, 1].item())
rows = torch.div(sample_index, grid_w, rounding_mode="floor")
cols = sample_index.remainder(grid_w)
dense_index = rows * max_w + cols
dense_flat[batch_idx].scatter_add_(
1,
dense_index.unsqueeze(0).expand(channels, -1),
sample_tokens.transpose(0, 1),
)
count_flat[batch_idx, 0].scatter_add_(
0,
dense_index,
torch.ones_like(dense_index, dtype=token_features.dtype),
)
observed_mask = (count_flat > 0).view(batch_size, 1, max_h, max_w).to(dtype=token_features.dtype)
dense = (dense_flat / count_flat.clamp_min(1.0)).view(batch_size, channels, max_h, max_w)
extent_mask = VisibilityAwareQueryDecoder._build_extent_mask(grid_shape, max_h, max_w, token_features.dtype)
return dense * extent_mask, observed_mask, extent_mask
@staticmethod
def _gather_dense_predictions(
dense_map: torch.Tensor,
grid_shape: torch.Tensor,
rx_flat_index: torch.Tensor,
) -> torch.Tensor:
batch_size, channels, _, max_w = dense_map.shape
flat_map = dense_map.flatten(start_dim=2)
outputs = []
for batch_idx in range(batch_size):
sample_index = rx_flat_index[batch_idx].long()
sample_out = dense_map.new_zeros((channels, sample_index.numel()))
valid = sample_index >= 0
if bool(valid.any()):
grid_w = int(grid_shape[batch_idx, 1].item())
rows = torch.div(sample_index[valid], grid_w, rounding_mode="floor")
cols = sample_index[valid].remainder(grid_w)
dense_index = rows * max_w + cols
sample_out[:, valid] = flat_map[batch_idx].index_select(1, dense_index)
outputs.append(sample_out.transpose(0, 1))
return torch.stack(outputs, dim=0)
def _apply_spatial_head(
self,
head: SpatialOutputHead | None,
dense_hidden: torch.Tensor,
extent_mask: torch.Tensor,
grid_shape: torch.Tensor,
rx_flat_index: torch.Tensor,
) -> torch.Tensor | None:
if head is None:
return None
dense_map = head(dense_hidden, extent_mask)
gathered = self._gather_dense_predictions(dense_map, grid_shape=grid_shape, rx_flat_index=rx_flat_index)
return gathered.squeeze(-1)
def forward(
self,
*,
query_tokens: torch.Tensor,
memory_tokens: torch.Tensor,
memory_valid_mask: torch.Tensor,
grid_shape: torch.Tensor,
rx_flat_index: torch.Tensor,
query_padding_mask: torch.Tensor | None = None,
) -> dict[str, torch.Tensor]:
x = self.query_proj(query_tokens)
memory = self.memory_proj(memory_tokens)
if memory.dim() == 4:
batch_size, num_queries, memory_len, dim = memory.shape
flat_query = x.view(batch_size * num_queries, 1, dim)
flat_memory = memory.view(batch_size * num_queries, memory_len, dim)
flat_memory_invalid = (~memory_valid_mask.bool()).view(batch_size * num_queries, memory_len)
for block in self.cross_blocks:
flat_query = block(flat_query, flat_memory, key_padding_mask=flat_memory_invalid)
x = flat_query.view(batch_size, num_queries, dim)
elif memory.dim() == 3:
memory_invalid = ~memory_valid_mask.bool()
for block in self.cross_blocks:
x = block(x, memory, key_padding_mask=memory_invalid)
else:
raise ValueError(f"Unsupported memory token rank: {memory.dim()}")
query_invalid = None
if query_padding_mask is not None:
query_invalid = ~(query_padding_mask > 0.5)
for block in self.self_blocks:
x = block(x, key_padding_mask=query_invalid)
head_hidden = self.head_stem(x)
spatial_tokens = self.spatial_token_proj(head_hidden)
dense_hidden, observed_mask, extent_mask = self._scatter_query_tokens_to_dense(
spatial_tokens,
grid_shape=grid_shape,
rx_flat_index=rx_flat_index,
query_padding_mask=query_padding_mask,
)
dense_hidden = self.spatial_stem(dense_hidden, observed_mask=observed_mask, extent_mask=extent_mask)
if self.use_los_residual_heads:
los_gain_db = self._apply_spatial_head(self.los_gain_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
residual_gain_db = self._apply_spatial_head(self.residual_gain_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
los_logits = self._apply_spatial_head(self.visibility_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
assert los_gain_db is not None and residual_gain_db is not None and los_logits is not None
los_prob = torch.sigmoid(los_logits)
total_power = los_prob * self._db_to_power(los_gain_db) + self._db_to_power(residual_gain_db)
path_gain_db = self._power_to_db(total_power)
clear_path_gain_db = los_gain_db
blocked_path_gain_db = residual_gain_db
visibility_logits = los_logits
elif self.use_dual_gain_heads:
clear_path_gain_db = self._apply_spatial_head(self.clear_gain_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
blocked_path_gain_db = self._apply_spatial_head(self.blocked_gain_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
visibility_logits = self._apply_spatial_head(self.visibility_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
assert clear_path_gain_db is not None and blocked_path_gain_db is not None and visibility_logits is not None
visibility_prob = torch.sigmoid(visibility_logits)
path_gain_db = visibility_prob * clear_path_gain_db + (1.0 - visibility_prob) * blocked_path_gain_db
else:
path_gain_db = self._apply_spatial_head(self.path_gain_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
assert path_gain_db is not None
clear_path_gain_db = path_gain_db
blocked_path_gain_db = path_gain_db
visibility_logits = self._apply_spatial_head(self.visibility_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
outputs = {
"query_tokens": x,
"path_gain_db": path_gain_db,
"base_path_gain_db": clear_path_gain_db,
"coarse_path_gain_db": clear_path_gain_db,
}
if self.predict_valid and self.valid_head is not None:
valid_logits = self._apply_spatial_head(self.valid_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
if valid_logits is not None:
outputs["valid_logits"] = valid_logits
if self.use_dual_gain_heads or self.use_los_residual_heads:
outputs["clear_path_gain_db"] = clear_path_gain_db
outputs["blocked_path_gain_db"] = blocked_path_gain_db
if self.predict_visibility and visibility_logits is not None:
outputs["visibility_logits"] = visibility_logits
outputs["occlusion_logits"] = -visibility_logits
if self.use_dual_gain_heads or self.use_los_residual_heads:
outputs["residual_path_gain_db"] = (
blocked_path_gain_db if self.use_los_residual_heads else blocked_path_gain_db - clear_path_gain_db
)
if self.use_los_residual_heads and visibility_logits is not None:
outputs["los_logits"] = visibility_logits
outputs["los_gain_db"] = clear_path_gain_db
outputs["residual_multipath_gain_db"] = blocked_path_gain_db
outputs["los_prob"] = torch.sigmoid(visibility_logits)
if self.predict_boundary and self.boundary_head is not None:
boundary_logits = self._apply_spatial_head(self.boundary_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
if boundary_logits is not None:
outputs["boundary_logits"] = boundary_logits
outputs["boundary_prob"] = torch.sigmoid(boundary_logits)
if self.predict_uncertainty and self.uncertainty_head is not None:
uncertainty_logits = self._apply_spatial_head(self.uncertainty_head, dense_hidden, extent_mask, grid_shape, rx_flat_index)
if uncertainty_logits is not None:
outputs["uncertainty_logits"] = uncertainty_logits
return outputs
__all__ = [
"DeepOutputHead",
"DenseCrossAttentionBlock",
"DenseSelfAttentionBlock",
"QueryHeadStem",
"ResidualMLPBlock",
"SpatialHeadStem",
"SpatialOutputHead",
"SpatialResidualBlock",
"VisibilityAwareQueryDecoder",
]