250 lines
8.7 KiB
Python
250 lines
8.7 KiB
Python
from typing import *
|
|
import torch
|
|
import torch.nn.functional as F
|
|
from torchvision import transforms
|
|
from transformers import DINOv3ViTModel
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
from ....utils import dist_utils
|
|
|
|
|
|
class DinoV2FeatureExtractor:
|
|
"""
|
|
Feature extractor for DINOv2 models.
|
|
"""
|
|
def __init__(self, model_name: str):
|
|
self.model_name = model_name
|
|
self.model = torch.hub.load('facebookresearch/dinov2', model_name, pretrained=True)
|
|
self.model.eval()
|
|
self.transform = transforms.Compose([
|
|
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
|
])
|
|
|
|
def to(self, device):
|
|
self.model.to(device)
|
|
|
|
def cuda(self):
|
|
self.model.cuda()
|
|
|
|
def cpu(self):
|
|
self.model.cpu()
|
|
|
|
@torch.no_grad()
|
|
def __call__(self, image: Union[torch.Tensor, List[Image.Image]]) -> torch.Tensor:
|
|
"""
|
|
Extract features from the image.
|
|
|
|
Args:
|
|
image: A batch of images as a tensor of shape (B, C, H, W) or a list of PIL images.
|
|
|
|
Returns:
|
|
A tensor of shape (B, N, D) where N is the number of patches and D is the feature dimension.
|
|
"""
|
|
if isinstance(image, torch.Tensor):
|
|
assert image.ndim == 4, "Image tensor should be batched (B, C, H, W)"
|
|
elif isinstance(image, list):
|
|
assert all(isinstance(i, Image.Image) for i in image), "Image list should be list of PIL images"
|
|
image = [i.resize((518, 518), Image.LANCZOS) for i in image]
|
|
image = [np.array(i.convert('RGB')).astype(np.float32) / 255 for i in image]
|
|
image = [torch.from_numpy(i).permute(2, 0, 1).float() for i in image]
|
|
image = torch.stack(image).cuda()
|
|
else:
|
|
raise ValueError(f"Unsupported type of image: {type(image)}")
|
|
|
|
image = self.transform(image).cuda()
|
|
features = self.model(image, is_training=True)['x_prenorm']
|
|
patchtokens = F.layer_norm(features, features.shape[-1:])
|
|
return patchtokens
|
|
|
|
|
|
class DinoV3FeatureExtractor:
|
|
"""
|
|
Feature extractor for DINOv3 models.
|
|
"""
|
|
def __init__(self, model_name: str, image_size=512):
|
|
self.model_name = model_name
|
|
self.model = DINOv3ViTModel.from_pretrained(model_name)
|
|
self.model.eval()
|
|
self.image_size = image_size
|
|
self.transform = transforms.Compose([
|
|
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
|
])
|
|
|
|
def to(self, device):
|
|
self.model.to(device)
|
|
|
|
def cuda(self):
|
|
self.model.cuda()
|
|
|
|
def cpu(self):
|
|
self.model.cpu()
|
|
|
|
def extract_features(self, image: torch.Tensor) -> torch.Tensor:
|
|
image = image.to(self.model.embeddings.patch_embeddings.weight.dtype)
|
|
hidden_states = self.model.embeddings(image, bool_masked_pos=None)
|
|
position_embeddings = self.model.rope_embeddings(image)
|
|
|
|
for i, layer_module in enumerate(self.model.layer):
|
|
hidden_states = layer_module(
|
|
hidden_states,
|
|
position_embeddings=position_embeddings,
|
|
)
|
|
|
|
return F.layer_norm(hidden_states, hidden_states.shape[-1:])
|
|
|
|
@torch.no_grad()
|
|
def __call__(self, image: Union[torch.Tensor, List[Image.Image]]) -> torch.Tensor:
|
|
"""
|
|
Extract features from the image.
|
|
|
|
Args:
|
|
image: A batch of images as a tensor of shape (B, C, H, W) or a list of PIL images.
|
|
|
|
Returns:
|
|
A tensor of shape (B, N, D) where N is the number of patches and D is the feature dimension.
|
|
"""
|
|
if isinstance(image, torch.Tensor):
|
|
assert image.ndim == 4, "Image tensor should be batched (B, C, H, W)"
|
|
elif isinstance(image, list):
|
|
assert all(isinstance(i, Image.Image) for i in image), "Image list should be list of PIL images"
|
|
image = [i.resize((self.image_size, self.image_size), Image.LANCZOS) for i in image]
|
|
image = [np.array(i.convert('RGB')).astype(np.float32) / 255 for i in image]
|
|
image = [torch.from_numpy(i).permute(2, 0, 1).float() for i in image]
|
|
image = torch.stack(image).cuda()
|
|
else:
|
|
raise ValueError(f"Unsupported type of image: {type(image)}")
|
|
|
|
image = self.transform(image).cuda()
|
|
features = self.extract_features(image)
|
|
return features
|
|
|
|
|
|
class ImageConditionedMixin:
|
|
"""
|
|
Mixin for image-conditioned models.
|
|
|
|
Args:
|
|
image_cond_model: The image conditioning model.
|
|
"""
|
|
def __init__(self, *args, image_cond_model: dict, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.image_cond_model_config = image_cond_model
|
|
self.image_cond_model = None # the model is init lazily
|
|
|
|
def _init_image_cond_model(self):
|
|
"""
|
|
Initialize the image conditioning model.
|
|
"""
|
|
with dist_utils.local_master_first():
|
|
self.image_cond_model = globals()[self.image_cond_model_config['name']](**self.image_cond_model_config.get('args', {}))
|
|
self.image_cond_model.cuda()
|
|
|
|
@torch.no_grad()
|
|
def encode_image(self, image: Union[torch.Tensor, List[Image.Image]]) -> torch.Tensor:
|
|
"""
|
|
Encode the image.
|
|
"""
|
|
if self.image_cond_model is None:
|
|
self._init_image_cond_model()
|
|
features = self.image_cond_model(image)
|
|
return features
|
|
|
|
def get_cond(self, cond, **kwargs):
|
|
"""
|
|
Get the conditioning data.
|
|
"""
|
|
cond = self.encode_image(cond)
|
|
kwargs['neg_cond'] = torch.zeros_like(cond)
|
|
cond = super().get_cond(cond, **kwargs)
|
|
return cond
|
|
|
|
def get_inference_cond(self, cond, **kwargs):
|
|
"""
|
|
Get the conditioning data for inference.
|
|
"""
|
|
cond = self.encode_image(cond)
|
|
kwargs['neg_cond'] = torch.zeros_like(cond)
|
|
cond = super().get_inference_cond(cond, **kwargs)
|
|
return cond
|
|
|
|
def vis_cond(self, cond, **kwargs):
|
|
"""
|
|
Visualize the conditioning data.
|
|
"""
|
|
return {'image': {'value': cond, 'type': 'image'}}
|
|
|
|
|
|
class MultiImageConditionedMixin:
|
|
"""
|
|
Mixin for multiple-image-conditioned models.
|
|
|
|
Args:
|
|
image_cond_model: The image conditioning model.
|
|
"""
|
|
def __init__(self, *args, image_cond_model: dict, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.image_cond_model_config = image_cond_model
|
|
self.image_cond_model = None # the model is init lazily
|
|
|
|
def _init_image_cond_model(self):
|
|
"""
|
|
Initialize the image conditioning model.
|
|
"""
|
|
with dist_utils.local_master_first():
|
|
self.image_cond_model = globals()[self.image_cond_model_config['name']](**self.image_cond_model_config.get('args', {}))
|
|
|
|
@torch.no_grad()
|
|
def encode_images(self, images: Union[List[torch.Tensor], List[List[Image.Image]]]) -> List[torch.Tensor]:
|
|
"""
|
|
Encode the image.
|
|
"""
|
|
if self.image_cond_model is None:
|
|
self._init_image_cond_model()
|
|
seqlen = [len(i) for i in images]
|
|
images = torch.cat(images, dim=0) if isinstance(images[0], torch.Tensor) else sum(images, [])
|
|
features = self.image_cond_model(images)
|
|
features = torch.split(features, seqlen)
|
|
features = [feature.reshape(-1, feature.shape[-1]) for feature in features]
|
|
return features
|
|
|
|
def get_cond(self, cond, **kwargs):
|
|
"""
|
|
Get the conditioning data.
|
|
"""
|
|
cond = self.encode_images(cond)
|
|
kwargs['neg_cond'] = [
|
|
torch.zeros_like(cond[0][:1, :]) for _ in range(len(cond))
|
|
]
|
|
cond = super().get_cond(cond, **kwargs)
|
|
return cond
|
|
|
|
def get_inference_cond(self, cond, **kwargs):
|
|
"""
|
|
Get the conditioning data for inference.
|
|
"""
|
|
cond = self.encode_images(cond)
|
|
kwargs['neg_cond'] = [
|
|
torch.zeros_like(cond[0][:1, :]) for _ in range(len(cond))
|
|
]
|
|
cond = super().get_inference_cond(cond, **kwargs)
|
|
return cond
|
|
|
|
def vis_cond(self, cond, **kwargs):
|
|
"""
|
|
Visualize the conditioning data.
|
|
"""
|
|
H, W = cond[0].shape[-2:]
|
|
vis = []
|
|
for images in cond:
|
|
canvas = torch.zeros(3, H * 2, W * 2, device=images.device, dtype=images.dtype)
|
|
for i, image in enumerate(images):
|
|
if i == 4:
|
|
break
|
|
kh = i // 2
|
|
kw = i % 2
|
|
canvas[:, kh*H:(kh+1)*H, kw*W:(kw+1)*W] = image
|
|
vis.append(canvas)
|
|
vis = torch.stack(vis)
|
|
return {'image': {'value': vis, 'type': 'image'}}
|