| |
| """ |
| Segment-Aware Model Extension Modules |
| ====================================== |
| Drop-in components for AniFlowFormer-T to enable segment-aware processing: |
| |
| 1. SegmentAwareCostModulation - Modulates cost volume based on segment affinity |
| 2. SegmentAwareAttentionMask - Creates attention bias from segment structure |
| 3. SegmentAwareRefinementHead - Refines flow using segment boundaries |
| |
| All modules are independently toggleable for ablation studies. |
| """ |
| from __future__ import annotations |
| from typing import Dict, Optional, Tuple, List |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from einops import rearrange |
|
|
|
|
| class SegmentAwareCostModulation(nn.Module): |
| """ |
| Segment-Aware Cost Modulation |
| ============================== |
| Modulates the cost volume based on segment affinity between frames. |
| |
| Pixels within the same segment across frames have their matching costs |
| boosted, while cross-segment matches are suppressed. |
| |
| This helps flow estimation by leveraging segment structure as a prior. |
| """ |
| |
| def __init__( |
| self, |
| cost_dim: int = 1, |
| segment_dim: int = 64, |
| modulation_strength: float = 0.5, |
| learnable: bool = True, |
| ): |
| """ |
| Args: |
| cost_dim: Number of cost volume channels |
| segment_dim: Dimension for segment embedding projection |
| modulation_strength: Strength of modulation (0 = no effect, 1 = full) |
| learnable: Whether to use learnable projection layers |
| """ |
| super().__init__() |
| self.modulation_strength = modulation_strength |
| self.learnable = learnable |
| |
| if learnable: |
| |
| self.segment_proj = nn.Sequential( |
| nn.Conv2d(segment_dim, segment_dim // 2, 1), |
| nn.ReLU(inplace=True), |
| nn.Conv2d(segment_dim // 2, 1, 1), |
| nn.Sigmoid(), |
| ) |
| |
| def forward( |
| self, |
| cost_volume: torch.Tensor, |
| segment_masks_1: torch.Tensor, |
| segment_masks_2: torch.Tensor, |
| ) -> torch.Tensor: |
| """ |
| Modulate cost volume using segment affinity. |
| |
| Args: |
| cost_volume: Cost volume to modulate |
| segment_masks_1: Segment masks for frame 1 |
| segment_masks_2: Segment masks for frame 2 |
| |
| Returns: |
| Modulated cost volume (same shape as input) |
| """ |
| B = cost_volume.shape[0] |
| |
| |
| |
| affinity = self._compute_segment_affinity(segment_masks_1, segment_masks_2) |
| |
| |
| if cost_volume.dim() == 4: |
| |
| H, W = cost_volume.shape[-2:] |
| affinity = F.interpolate( |
| affinity.unsqueeze(1), size=(H, W), |
| mode='bilinear', align_corners=False |
| ).squeeze(1) |
| |
| |
| modulation = 1.0 + self.modulation_strength * (affinity.unsqueeze(1) - 0.5) |
| cost_volume = cost_volume * modulation |
| |
| return cost_volume |
| |
| def _compute_segment_affinity( |
| self, |
| masks_1: torch.Tensor, |
| masks_2: torch.Tensor, |
| ) -> torch.Tensor: |
| """ |
| Compute per-pixel segment affinity between two frames. |
| |
| Returns affinity map where high values indicate pixels likely |
| belong to the same semantic segment. |
| """ |
| B, S, H, W = masks_1.shape |
| |
| |
| if masks_2.shape[-2:] != (H, W): |
| masks_2 = F.interpolate( |
| masks_2, size=(H, W), mode='bilinear', align_corners=False |
| ) |
| |
| |
| |
| |
| |
| |
| masks_1_norm = masks_1 / (masks_1.sum(dim=1, keepdim=True).clamp(min=1e-6)) |
| masks_2_norm = masks_2 / (masks_2.sum(dim=1, keepdim=True).clamp(min=1e-6)) |
| |
| |
| affinity = (masks_1_norm * masks_2_norm).sum(dim=1) |
| |
| return affinity |
|
|
|
|
| class SegmentAwareAttentionMask(nn.Module): |
| """ |
| Segment-Aware Attention Mask Generator |
| ======================================== |
| Creates attention bias from segment structure for transformer layers. |
| |
| Tokens from the same segment attend more strongly to each other, |
| while cross-segment attention is relatively suppressed. |
| """ |
| |
| def __init__( |
| self, |
| temperature: float = 1.0, |
| bias_scale: float = 2.0, |
| ): |
| """ |
| Args: |
| temperature: Temperature for softmax (higher = softer attention) |
| bias_scale: Scale factor for attention bias |
| """ |
| super().__init__() |
| self.temperature = temperature |
| self.bias_scale = bias_scale |
| |
| def forward( |
| self, |
| segment_masks: torch.Tensor, |
| query_shape: Tuple[int, int], |
| key_shape: Optional[Tuple[int, int]] = None, |
| ) -> torch.Tensor: |
| """ |
| Generate attention bias from segment masks. |
| |
| Args: |
| segment_masks: Segment masks (B, S, H, W) |
| query_shape: Spatial shape of query tokens |
| key_shape: Spatial shape of key tokens (default: same as query) |
| |
| Returns: |
| attention_bias: (B, N_q, N_k) bias to add to attention logits |
| """ |
| B, S, H, W = segment_masks.shape |
| h_q, w_q = query_shape |
| h_k, w_k = key_shape if key_shape else query_shape |
| |
| |
| masks_q = F.interpolate( |
| segment_masks, size=(h_q, w_q), mode='bilinear', align_corners=False |
| ) |
| masks_k = F.interpolate( |
| segment_masks, size=(h_k, w_k), mode='bilinear', align_corners=False |
| ) |
| |
| |
| masks_q = rearrange(masks_q, 'b s h w -> b (h w) s') |
| masks_k = rearrange(masks_k, 'b s h w -> b s (h w)') |
| |
| |
| masks_q = masks_q / (masks_q.sum(dim=-1, keepdim=True).clamp(min=1e-6)) |
| masks_k = masks_k / (masks_k.sum(dim=-1, keepdim=True).clamp(min=1e-6)) |
| |
| |
| |
| |
| affinity = torch.bmm(masks_q, masks_k) |
| |
| |
| attention_bias = self.bias_scale * (affinity - 0.5) / self.temperature |
| |
| return attention_bias |
|
|
|
|
| class SegmentAwareRefinementHead(nn.Module): |
| """ |
| Segment-Aware Flow Refinement Head |
| ==================================== |
| Post-processing refinement that uses segment boundaries to improve |
| flow predictions, especially at object boundaries. |
| |
| The refinement encourages: |
| - Smooth flow within segments |
| - Accurate flow discontinuities at segment boundaries |
| """ |
| |
| def __init__( |
| self, |
| flow_dim: int = 2, |
| hidden_dim: int = 64, |
| num_layers: int = 2, |
| use_boundary_features: bool = True, |
| ): |
| """ |
| Args: |
| flow_dim: Flow channels (typically 2) |
| hidden_dim: Hidden dimension for refinement network |
| num_layers: Number of refinement layers |
| use_boundary_features: Whether to use explicit boundary features |
| """ |
| super().__init__() |
| self.use_boundary_features = use_boundary_features |
| |
| |
| in_dim = flow_dim + (1 if use_boundary_features else 0) |
| |
| layers = [] |
| for i in range(num_layers): |
| dim_in = in_dim if i == 0 else hidden_dim |
| dim_out = hidden_dim if i < num_layers - 1 else flow_dim |
| layers.extend([ |
| nn.Conv2d(dim_in, dim_out, 3, padding=1), |
| nn.ReLU(inplace=True) if i < num_layers - 1 else nn.Identity(), |
| ]) |
| |
| self.refine_net = nn.Sequential(*layers) |
| |
| |
| self.residual_weight = nn.Parameter(torch.tensor(0.1)) |
| |
| def forward( |
| self, |
| flow: torch.Tensor, |
| boundary_map: torch.Tensor, |
| ) -> torch.Tensor: |
| """ |
| Refine flow using segment boundary information. |
| |
| Args: |
| flow: Initial flow prediction |
| boundary_map: Segment boundary map (1 at boundaries) |
| |
| Returns: |
| Refined flow (same shape as input) |
| """ |
| |
| if boundary_map.shape[-2:] != flow.shape[-2:]: |
| boundary_map = F.interpolate( |
| boundary_map, size=flow.shape[-2:], |
| mode='bilinear', align_corners=False |
| ) |
| |
| if self.use_boundary_features: |
| x = torch.cat([flow, boundary_map], dim=1) |
| else: |
| x = flow |
| |
| |
| residual = self.refine_net(x) |
| |
| |
| refined_flow = flow + self.residual_weight * residual |
| |
| return refined_flow |
|
|
|
|
| class SegmentGuidedCorrelation(nn.Module): |
| """ |
| Segment-Guided Correlation Layer |
| ================================== |
| Computes correlation/cost volume with segment-aware weighting. |
| |
| This replaces standard correlation with one that considers |
| segment structure when computing matching costs. |
| """ |
| |
| def __init__( |
| self, |
| feature_dim: int, |
| segment_weight: float = 0.3, |
| radius: int = 4, |
| ): |
| """ |
| Args: |
| feature_dim: Feature dimension |
| segment_weight: Weight for segment-based modulation |
| radius: Correlation search radius |
| """ |
| super().__init__() |
| self.segment_weight = segment_weight |
| self.radius = radius |
| |
| |
| self.seg_proj = nn.Conv2d(feature_dim, feature_dim // 4, 1) |
| |
| def forward( |
| self, |
| fmap1: torch.Tensor, |
| fmap2: torch.Tensor, |
| segment_masks_1: torch.Tensor, |
| segment_masks_2: torch.Tensor, |
| ) -> torch.Tensor: |
| """ |
| Compute segment-guided correlation volume. |
| |
| Args: |
| fmap1, fmap2: Feature maps from two frames |
| segment_masks_1, segment_masks_2: Corresponding segment masks |
| |
| Returns: |
| corr: Correlation volume with segment modulation |
| """ |
| B, C, H, W = fmap1.shape |
| |
| |
| |
| fmap1_flat = fmap1.view(B, C, -1) |
| fmap2_flat = fmap2.view(B, C, -1) |
| |
| corr = torch.bmm(fmap1_flat.transpose(1, 2), fmap2_flat) |
| corr = corr / (C ** 0.5) |
| |
| |
| |
| masks_1 = F.interpolate(segment_masks_1, size=(H, W), mode='bilinear', align_corners=False) |
| masks_2 = F.interpolate(segment_masks_2, size=(H, W), mode='bilinear', align_corners=False) |
| |
| |
| masks_1_flat = masks_1.view(B, -1, H * W) |
| masks_2_flat = masks_2.view(B, -1, H * W) |
| |
| |
| masks_1_norm = masks_1_flat / (masks_1_flat.sum(dim=1, keepdim=True).clamp(min=1e-6)) |
| masks_2_norm = masks_2_flat / (masks_2_flat.sum(dim=1, keepdim=True).clamp(min=1e-6)) |
| |
| |
| seg_affinity = torch.bmm(masks_1_norm.transpose(1, 2), masks_2_norm) |
| |
| |
| corr = corr + self.segment_weight * seg_affinity |
| |
| |
| corr = corr.view(B, H, W, H, W) |
| |
| return corr |
|
|
|
|
| class SegmentAwareModuleBundle(nn.Module): |
| """ |
| Bundle of all segment-aware modules for easy integration. |
| |
| This provides a unified interface to all segment-aware extensions, |
| with each component independently toggleable. |
| """ |
| |
| def __init__( |
| self, |
| feature_dim: int = 128, |
| use_cost_modulation: bool = True, |
| use_attention_mask: bool = True, |
| use_refinement: bool = False, |
| ): |
| super().__init__() |
| |
| self.use_cost_modulation = use_cost_modulation |
| self.use_attention_mask = use_attention_mask |
| self.use_refinement = use_refinement |
| |
| if use_cost_modulation: |
| self.cost_modulation = SegmentAwareCostModulation( |
| segment_dim=feature_dim |
| ) |
| |
| if use_attention_mask: |
| self.attention_mask = SegmentAwareAttentionMask() |
| |
| if use_refinement: |
| self.refinement = SegmentAwareRefinementHead( |
| hidden_dim=feature_dim |
| ) |
| |
| def modulate_cost( |
| self, |
| cost_volume: torch.Tensor, |
| masks_1: torch.Tensor, |
| masks_2: torch.Tensor, |
| ) -> torch.Tensor: |
| """Apply cost modulation if enabled.""" |
| if self.use_cost_modulation: |
| return self.cost_modulation(cost_volume, masks_1, masks_2) |
| return cost_volume |
| |
| def get_attention_bias( |
| self, |
| segment_masks: torch.Tensor, |
| query_shape: Tuple[int, int], |
| key_shape: Optional[Tuple[int, int]] = None, |
| ) -> Optional[torch.Tensor]: |
| """Get attention bias if enabled.""" |
| if self.use_attention_mask: |
| return self.attention_mask(segment_masks, query_shape, key_shape) |
| return None |
| |
| def refine_flow( |
| self, |
| flow: torch.Tensor, |
| boundary_map: torch.Tensor, |
| ) -> torch.Tensor: |
| """Apply flow refinement if enabled.""" |
| if self.use_refinement: |
| return self.refinement(flow, boundary_map) |
| return flow |
|
|
|
|
| |
| def build_segment_modules(cfg: Dict) -> SegmentAwareModuleBundle: |
| """Build segment-aware module bundle from config.""" |
| model_cfg = cfg.get('model', {}).get('args', {}) |
| return SegmentAwareModuleBundle( |
| feature_dim=model_cfg.get('enc_channels', 64) * 2, |
| use_cost_modulation=model_cfg.get('use_segment_cost_modulation', False), |
| use_attention_mask=model_cfg.get('use_segment_attention_mask', False), |
| use_refinement=model_cfg.get('use_segment_refinement', False), |
| ) |
|
|