| import math
|
| from typing import Tuple
|
|
|
| import torch
|
| import torch.nn as nn
|
|
|
|
|
| def stape_patch_world_coords_physical(
|
| X:int, Y:int, Z:int,
|
| kx:int, ky:int, kz:int,
|
| affine: torch.Tensor,
|
| rho_mm: Tuple[float, float, float],
|
| device=None, dtype=None
|
| ):
|
| if device is None: device = affine.device
|
| if dtype is None: dtype = torch.float32
|
|
|
| A = affine[:3, :3].to(device=device, dtype=dtype)
|
| t = affine[:3, 3].to(device=device, dtype=dtype)
|
|
|
| Lx, Ly, Lz = X//kx, Y//ky, Z//kz
|
| icx = torch.arange(Lx, device=device, dtype=dtype)*kx + (kx-1)*0.5
|
| icy = torch.arange(Ly, device=device, dtype=dtype)*ky + (ky-1)*0.5
|
| icz = torch.arange(Lz, device=device, dtype=dtype)*kz + (kz-1)*0.5
|
|
|
| gx, gy, gz = torch.meshgrid(icx, icy, icz, indexing='ij')
|
| idx = torch.stack([gx, gy, gz], dim=-1).reshape(-1, 3)
|
| coords = idx @ A.T + t
|
| return coords
|
|
|
|
|
|
|
| class FixedSinCos3DPE(nn.Module):
|
| def __init__(self, embed_dim:int, num_freq:int=12,
|
| space_scale:float=1.0, learnable_proj: bool=True):
|
| super().__init__()
|
| self.embed_dim = embed_dim
|
| self.num_freq = num_freq
|
| freq = torch.exp(torch.linspace(0, math.log(10000.0), num_freq)) / 10000.0
|
| self.register_buffer('freq', freq)
|
| self.space_scale = space_scale
|
| in_dim = 3 * 2 * num_freq
|
| self.proj = nn.Linear(in_dim, embed_dim, bias=False) if learnable_proj else nn.Identity()
|
|
|
| def forward(self, xyz: torch.Tensor):
|
| """
|
| xyz: [B, L, 3]
|
| return: [B, L, embed_dim]
|
| """
|
| B, L, _ = xyz.shape
|
| x = xyz[..., 0] * self.space_scale
|
| y = xyz[..., 1] * self.space_scale
|
| z = xyz[..., 2] * self.space_scale
|
|
|
| freq = self.freq.to(device=xyz.device, dtype=xyz.dtype)
|
|
|
| def enc(u):
|
| u = u[..., None] * freq
|
| return torch.cat([torch.sin(u), torch.cos(u)], dim=-1)
|
|
|
| feats = torch.cat([enc(x), enc(y), enc(z)], dim=-1)
|
| return self.proj(feats)
|
|
|