fuse_proj: Initial operational package for 3 researchers (Pavlenko/Blizno/Moroz)

Multimodal fusion research on StripNet+GTA-UAV proxy:
- 3 independent fusion tracks: condition-aware (A), token/bottleneck (B), role-aware (C)
- Shared interfaces, protocol, dataset audit, baseline benchmarks
- Canonical version-chain references to vault (SPEC, ANALYSIS, TRIAGE)
- Personalized task plans and decision tables for each researcher
- 3 generated DOCX task assignment files with milestones and DoD checklist
- Full modality dropout diagnostics and missing-modality robustness requirements
- Data contract, benchmark registry, experiment tracking infrastructure

Operational documents:
- docs/00_project/: MERIDIAN context, protocol, repository reuse guide, experiment specification
- docs/01_tasks/: Master assignment + 3 individual researcher tracks + joint integration
- docs/02_references/: Core literature, version-chain bases, code maps
- docs/03_codebase_guides/: Existing code snapshots from vault
- scripts/: gen_task_plans.js (DOCX generation), placeholder infrastructure
- vendor_reference/: Snapshots of caption_test, depth_edges_annotate, existing SOFIA/SegModel code
- reports/, results/, experiments/: Shared output structure for all 3 researchers

3 DOCX files generated from gen_task_plans.js (Times New Roman 14pt, GOST format):
- План_заданий_Павленко_БВ.docx (Condition-Aware track, fusion API owner)
- План_заданий_Близно_МВ.docx (Token/Bottleneck track, benchmark owner)
- План_заданий_Мороз_ЕС.docx (Role-Aware track, data contract owner)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Pikaliov
2026-06-11 17:16:57 +03:00
commit 2c6a00a4ca
155 changed files with 39765 additions and 0 deletions

View File

@@ -0,0 +1 @@
"""Model components for multimodal fusion experiments."""

View File

@@ -0,0 +1,6 @@
"""Fusion interfaces and registry shared by all architecture families."""
from fuse_proj.models.fusion.base import FusionModelBase
from fuse_proj.models.fusion.registry import build_fusion_model, register_fusion_model
__all__ = ["FusionModelBase", "build_fusion_model", "register_fusion_model"]

View File

@@ -0,0 +1,139 @@
from __future__ import annotations
"""Common interface for independently encoded satellite and UAV fusion branches.
Architecture overview:
satellite ViewBatch -> encode_view("satellite") -> normalized descriptor [B, D]
UAV ViewBatch -> encode_view("uav") -> normalized descriptor [B, D]
The base class intentionally does not define a fusion operator. Condition-aware,
token-bottleneck, and role-aware implementations must expose the same contract.
References:
- Hou et al. "Strip R-CNN: Large Strip Convolution for Remote Sensing Object
Detection," CVPR 2025. StripNet provides the shared RGB feature hierarchy.
"""
from abc import ABC, abstractmethod
import torch
import torch.nn as nn
import torch.nn.functional as F
from fuse_proj.data.types import FusionPairOutput, FusionViewOutput, ViewBatch, ViewName
class FusionModelBase(nn.Module, ABC):
"""Base class for multimodal cross-view retrieval encoders.
Architecture:
satellite batch -- encode_view --> satellite descriptor [B, D]
UAV batch -- encode_view --> UAV descriptor [B, D]
|
+--> retrieval objective outside model
Args:
descriptor_dim: Retrieval descriptor width D. Project default is 1024.
Shape contract:
Inputs: Two independent ``ViewBatch`` objects with RGB shape [B, 3, H, W].
Outputs: Two ``FusionViewOutput`` dictionaries with descriptors [B, D].
Cost:
This abstract wrapper adds no trainable parameters and negligible compute.
References:
- Strip R-CNN (CVPR 2025) for the shared StripNet RGB backbone contract.
"""
def __init__(self, descriptor_dim: int = 1024) -> None:
super().__init__()
if descriptor_dim <= 0:
raise ValueError(f"descriptor_dim must be positive, got {descriptor_dim}")
self.descriptor_dim = descriptor_dim
@abstractmethod
def encode_view(self, batch: ViewBatch, view: ViewName) -> FusionViewOutput:
"""Encode one view without access to features from the paired view.
Args:
batch: Multimodal inputs for one view.
view: ``"satellite"`` for CHM geometry or ``"uav"`` for depth geometry.
Returns:
Fusion output with L2-normalized descriptor [B, descriptor_dim].
"""
def forward(self, satellite: ViewBatch, uav: ViewBatch) -> FusionPairOutput:
"""Encode satellite and UAV batches independently.
Args:
satellite: Satellite batch with CHM geometry.
uav: UAV batch with relative-depth geometry.
Returns:
Independent satellite and UAV outputs. The retrieval loss is computed
outside this module to prevent paired-view feature leakage.
"""
self.validate_view_batch(satellite, view="satellite")
self.validate_view_batch(uav, view="uav")
return {
"satellite": self.encode_view(satellite, view="satellite"),
"uav": self.encode_view(uav, view="uav"),
}
@staticmethod
def normalize_descriptor(descriptor: torch.Tensor) -> torch.Tensor:
"""L2-normalize descriptors [B, D] along the feature dimension."""
if descriptor.ndim != 2:
raise ValueError(f"descriptor must have shape [B, D], got {tuple(descriptor.shape)}")
if not torch.isfinite(descriptor).all():
raise ValueError("descriptor contains NaN or Inf values")
return F.normalize(descriptor, dim=-1)
@staticmethod
def validate_view_batch(batch: ViewBatch, view: ViewName) -> None:
"""Validate required keys, batch dimensions, and view-independent shapes.
Args:
batch: Candidate multimodal batch.
view: View name used only to improve validation error messages.
Raises:
KeyError: If a required field is missing.
ValueError: If tensor or list batch dimensions are inconsistent.
"""
required = {
"rgb",
"geometry",
"segmentation",
"geometry_valid",
"segmentation_valid",
"text_valid",
"text",
"sample_id",
}
missing = required.difference(batch.keys())
if missing:
raise KeyError(f"{view} batch is missing keys: {sorted(missing)}")
rgb = batch["rgb"]
geometry = batch["geometry"]
segmentation = batch["segmentation"]
if rgb.ndim != 4 or rgb.shape[1] != 3:
raise ValueError(f"{view} rgb must be [B, 3, H, W], got {tuple(rgb.shape)}")
if geometry.ndim != 4 or geometry.shape[1] != 1:
raise ValueError(f"{view} geometry must be [B, 1, H, W], got {tuple(geometry.shape)}")
if segmentation.ndim != 4 or segmentation.shape[1] not in {1, 17}:
raise ValueError(
f"{view} segmentation must be [B, 1, H, W] or [B, 17, H, W], "
f"got {tuple(segmentation.shape)}"
)
batch_size = rgb.shape[0]
tensor_fields = ("geometry", "segmentation", "geometry_valid", "segmentation_valid", "text_valid")
for field in tensor_fields:
if batch[field].shape[0] != batch_size:
raise ValueError(f"{view} field {field} has inconsistent batch size")
if len(batch["text"]) != batch_size or len(batch["sample_id"]) != batch_size:
raise ValueError(f"{view} text/sample_id length must equal batch size {batch_size}")

View File

@@ -0,0 +1,56 @@
from __future__ import annotations
"""Registry for selecting a fusion implementation from a gin configuration."""
from collections.abc import Callable
from fuse_proj.models.fusion.base import FusionModelBase
FusionBuilder = Callable[..., FusionModelBase]
_FUSION_REGISTRY: dict[str, FusionBuilder] = {}
def register_fusion_model(name: str) -> Callable[[FusionBuilder], FusionBuilder]:
"""Register a fusion model builder under a stable configuration name.
Args:
name: Unique lowercase identifier used by experiment configuration.
Returns:
Decorator that stores and returns the original builder.
Raises:
ValueError: If the name is empty or already registered.
"""
normalized = name.strip().lower()
if not normalized:
raise ValueError("Fusion model name must not be empty")
def decorator(builder: FusionBuilder) -> FusionBuilder:
if normalized in _FUSION_REGISTRY:
raise ValueError(f"Fusion model already registered: {normalized}")
_FUSION_REGISTRY[normalized] = builder
return builder
return decorator
def build_fusion_model(name: str, **kwargs: object) -> FusionModelBase:
"""Build a registered fusion model.
Args:
name: Registered fusion model identifier.
**kwargs: Constructor arguments forwarded to the registered builder.
Returns:
Constructed fusion model.
Raises:
KeyError: If no model is registered under ``name``.
"""
normalized = name.strip().lower()
if normalized not in _FUSION_REGISTRY:
available = ", ".join(sorted(_FUSION_REGISTRY)) or "<none>"
raise KeyError(f"Unknown fusion model '{normalized}'. Available: {available}")
return _FUSION_REGISTRY[normalized](**kwargs)