224 lines
6.5 KiB
Python
224 lines
6.5 KiB
Python
"""ColorMNet model wrapper with modern error handling."""
|
|
|
|
import torch
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from core.logger import get_logger
|
|
from core.exceptions import ModelNotFoundError
|
|
from core.device import DeviceManager
|
|
from .config import ColorMNetConfig
|
|
|
|
# Import ColorMNet network from embedded source
|
|
# Prefer the vendored package name but keep a fallback for older paths.
|
|
try:
|
|
from colormnet_model.network import ColorMNet
|
|
except ImportError:
|
|
# Older revisions referenced `model.network`; alias is registered in __init__, but double-protect here.
|
|
from model.network import ColorMNet
|
|
|
|
|
|
class ColorMNetModel:
|
|
"""Wrapper around ColorMNet network with modern error handling and device management.
|
|
|
|
This class provides a clean interface to the ColorMNet network with:
|
|
- Automatic device management
|
|
- Model lazy loading
|
|
- Error handling
|
|
- Memory management
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: ColorMNetConfig,
|
|
device_manager: Optional[DeviceManager] = None,
|
|
):
|
|
"""Initialize ColorMNet model wrapper.
|
|
|
|
Args:
|
|
config: ColorMNet configuration
|
|
device_manager: Device manager (creates one if None)
|
|
"""
|
|
self.logger = get_logger()
|
|
self.config = config
|
|
self.device_manager = device_manager or DeviceManager(
|
|
device=config.device,
|
|
use_fp16=config.use_fp16
|
|
)
|
|
|
|
self._model: Optional[ColorMNet] = None
|
|
self._model_loaded = False
|
|
|
|
self.logger.info("ColorMNetModel initialized")
|
|
|
|
def load_model(self, model_path: Optional[Path] = None) -> ColorMNet:
|
|
"""Load the ColorMNet model.
|
|
|
|
Args:
|
|
model_path: Path to model checkpoint (uses config if None)
|
|
|
|
Returns:
|
|
Loaded ColorMNet network
|
|
|
|
Raises:
|
|
ModelNotFoundError: If checkpoint file doesn't exist
|
|
"""
|
|
if self._model_loaded and self._model is not None:
|
|
return self._model
|
|
|
|
# Use provided path or config path
|
|
checkpoint_path = model_path or self.config.model_path
|
|
|
|
if checkpoint_path is None:
|
|
raise ModelNotFoundError("No model path specified")
|
|
|
|
checkpoint_path = Path(checkpoint_path)
|
|
if not checkpoint_path.exists():
|
|
raise ModelNotFoundError(str(checkpoint_path))
|
|
|
|
self.logger.info(f"Loading ColorMNet from: {checkpoint_path}")
|
|
|
|
try:
|
|
# Create model with config
|
|
config_dict = self.config.to_dict()
|
|
self._model = ColorMNet(config_dict, str(checkpoint_path))
|
|
|
|
# Move to device
|
|
self._model = self._model.to(self.device_manager.device)
|
|
self._model.eval()
|
|
|
|
# Load weights
|
|
model_weights = torch.load(
|
|
checkpoint_path,
|
|
map_location=self.device_manager.device
|
|
)
|
|
self._model.load_weights(model_weights, init_as_zero_if_needed=True)
|
|
|
|
self._model_loaded = True
|
|
self.logger.info("ColorMNet loaded successfully")
|
|
|
|
# Log memory usage
|
|
if self.device_manager.device == "cuda":
|
|
mem_mb = self.device_manager.get_used_memory_mb()
|
|
self.logger.info(f"Model memory: {mem_mb}MB")
|
|
|
|
return self._model
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Failed to load ColorMNet: {e}")
|
|
raise
|
|
|
|
@property
|
|
def model(self) -> ColorMNet:
|
|
"""Get the loaded model (loads if not already loaded).
|
|
|
|
Returns:
|
|
ColorMNet network
|
|
|
|
Raises:
|
|
ModelNotFoundError: If model can't be loaded
|
|
"""
|
|
if not self._model_loaded or self._model is None:
|
|
self.load_model()
|
|
return self._model
|
|
|
|
def encode_key(self, frame: torch.Tensor, need_sk: bool = True, need_ek: bool = True):
|
|
"""Encode frame keys using DINOv2 features.
|
|
|
|
Args:
|
|
frame: Input frame tensor
|
|
need_sk: Whether to compute shrinkage
|
|
need_ek: Whether to compute selection
|
|
|
|
Returns:
|
|
Tuple of (key, shrinkage, selection, f16, f8, f4)
|
|
"""
|
|
with torch.no_grad():
|
|
return self.model.encode_key(frame, need_sk, need_ek)
|
|
|
|
def encode_value(
|
|
self,
|
|
frame: torch.Tensor,
|
|
image_feat_f16: torch.Tensor,
|
|
h16: torch.Tensor,
|
|
masks: torch.Tensor,
|
|
is_deep_update: bool = True,
|
|
):
|
|
"""Encode frame values.
|
|
|
|
Args:
|
|
frame: Input frame
|
|
image_feat_f16: Image features at scale 16
|
|
h16: Hidden state at scale 16
|
|
masks: Color masks (ab channels)
|
|
is_deep_update: Whether to perform deep update
|
|
|
|
Returns:
|
|
Tuple of (g16, h16)
|
|
"""
|
|
with torch.no_grad():
|
|
return self.model.encode_value(frame, image_feat_f16, h16, masks, is_deep_update)
|
|
|
|
def segment(
|
|
self,
|
|
multi_scale_features,
|
|
memory_readout,
|
|
hidden_state,
|
|
selector=None,
|
|
h_out=True,
|
|
strip_bg=True,
|
|
):
|
|
"""Segment and decode to color predictions.
|
|
|
|
Args:
|
|
multi_scale_features: Features at multiple scales
|
|
memory_readout: Memory bank readout
|
|
hidden_state: Current hidden state
|
|
selector: Optional selector
|
|
h_out: Whether to output hidden state
|
|
strip_bg: Whether to strip background
|
|
|
|
Returns:
|
|
Decoded color prediction
|
|
"""
|
|
with torch.no_grad():
|
|
return self.model.segment(
|
|
multi_scale_features,
|
|
memory_readout,
|
|
hidden_state,
|
|
selector,
|
|
h_out,
|
|
strip_bg,
|
|
)
|
|
|
|
def to(self, device: str):
|
|
"""Move model to device.
|
|
|
|
Args:
|
|
device: Target device
|
|
|
|
Returns:
|
|
Self for chaining
|
|
"""
|
|
if self._model is not None:
|
|
self._model = self._model.to(device)
|
|
self.logger.info(f"Model moved to {device}")
|
|
return self
|
|
|
|
def eval(self):
|
|
"""Set model to evaluation mode.
|
|
|
|
Returns:
|
|
Self for chaining
|
|
"""
|
|
if self._model is not None:
|
|
self._model.eval()
|
|
return self
|
|
|
|
def __repr__(self) -> str:
|
|
return (
|
|
f"ColorMNetModel(loaded={self._model_loaded}, "
|
|
f"device={self.device_manager.device}, "
|
|
f"fp16={self.device_manager.use_fp16})"
|
|
)
|