# Copyright (c) Meta Platforms, Inc. and affiliates. # # This software may be used and distributed in accordance with # the terms of the DINOv3 License Agreement. import logging from typing import Sequence import torch from torchvision.transforms import v2 logger = logging.getLogger("dinov3") def make_interpolation_mode(mode_str: str) -> v2.InterpolationMode: return {mode.value: mode for mode in v2.InterpolationMode}[mode_str] class GaussianBlur(v2.RandomApply): """ Apply Gaussian Blur to the PIL image. """ def __init__(self, *, p: float = 0.5, radius_min: float = 0.1, radius_max: float = 2.0): # NOTE: torchvision is applying 1 - probability to return the original image keep_p = 1 - p transform = v2.GaussianBlur(kernel_size=9, sigma=(radius_min, radius_max)) super().__init__(transforms=[transform], p=keep_p) # Use timm's names IMAGENET_DEFAULT_MEAN = (0.485, 0.456, 0.406) IMAGENET_DEFAULT_STD = (0.229, 0.224, 0.225) CROP_DEFAULT_SIZE = 224 RESIZE_DEFAULT_SIZE = int(256 * CROP_DEFAULT_SIZE / 224) def make_normalize_transform( mean: Sequence[float] = IMAGENET_DEFAULT_MEAN, std: Sequence[float] = IMAGENET_DEFAULT_STD, ) -> v2.Normalize: return v2.Normalize(mean=mean, std=std) def make_base_transform( mean: Sequence[float] = IMAGENET_DEFAULT_MEAN, std: Sequence[float] = IMAGENET_DEFAULT_STD, ) -> v2.Normalize: return v2.Compose( [ v2.ToDtype(torch.float32, scale=True), make_normalize_transform(mean=mean, std=std), ] ) # This roughly matches torchvision's preset for classification training: # https://github.com/pytorch/vision/blob/main/references/classification/presets.py#L6-L44 def make_classification_train_transform( *, crop_size: int = CROP_DEFAULT_SIZE, interpolation=v2.InterpolationMode.BICUBIC, hflip_prob: float = 0.5, mean: Sequence[float] = IMAGENET_DEFAULT_MEAN, std: Sequence[float] = IMAGENET_DEFAULT_STD, ): transforms_list = [v2.ToImage(), v2.RandomResizedCrop(crop_size, interpolation=interpolation)] if hflip_prob > 0.0: transforms_list.append(v2.RandomHorizontalFlip(hflip_prob)) transforms_list.append(make_base_transform(mean, std)) transform = v2.Compose(transforms_list) logger.info(f"Built classification train transform\n{transform}") return transform def make_resize_transform( *, resize_size: int, resize_square: bool = False, resize_large_side: bool = False, # Set the larger side to resize_size instead of the smaller interpolation: v2.InterpolationMode = v2.InterpolationMode.BICUBIC, ): assert not (resize_square and resize_large_side), "These two options can not be set together" if resize_square: logger.info("resizing image as a square") size = (resize_size, resize_size) transform = v2.Resize(size=size, interpolation=interpolation) return transform elif resize_large_side: logger.info("resizing based on large side") transform = v2.Resize(size=None, max_size=resize_size, interpolation=interpolation) return transform else: transform = v2.Resize(resize_size, interpolation=interpolation) return transform # Derived from make_classification_eval_transform() with more control over resize and crop def make_eval_transform( *, resize_size: int = RESIZE_DEFAULT_SIZE, crop_size: int = CROP_DEFAULT_SIZE, resize_square: bool = False, resize_large_side: bool = False, # Set the larger side to resize_size instead of the smaller interpolation: v2.InterpolationMode = v2.InterpolationMode.BICUBIC, mean: Sequence[float] = IMAGENET_DEFAULT_MEAN, std: Sequence[float] = IMAGENET_DEFAULT_STD, ) -> v2.Compose: transforms_list = [v2.ToImage()] resize_transform = make_resize_transform( resize_size=resize_size, resize_square=resize_square, resize_large_side=resize_large_side, interpolation=interpolation, ) transforms_list.append(resize_transform) if crop_size: transforms_list.append(v2.CenterCrop(crop_size)) transforms_list.append(make_base_transform(mean, std)) transform = v2.Compose(transforms_list) logger.info(f"Built eval transform\n{transform}") return transform # This matches (roughly) torchvision's preset for classification evaluation: # https://github.com/pytorch/vision/blob/main/references/classification/presets.py#L47-L69 def make_classification_eval_transform( *, resize_size: int = RESIZE_DEFAULT_SIZE, crop_size: int = CROP_DEFAULT_SIZE, interpolation=v2.InterpolationMode.BICUBIC, mean: Sequence[float] = IMAGENET_DEFAULT_MEAN, std: Sequence[float] = IMAGENET_DEFAULT_STD, ) -> v2.Compose: return make_eval_transform( resize_size=resize_size, crop_size=crop_size, interpolation=interpolation, mean=mean, std=std, resize_square=False, resize_large_side=False, ) def voc2007_classification_target_transform(label, n_categories=20): one_hot = torch.zeros(n_categories, dtype=int) for instance in label.instances: one_hot[instance.category_id] = True return one_hot def imaterialist_classification_target_transform(label, n_categories=294): one_hot = torch.zeros(n_categories, dtype=int) one_hot[label.attributes] = True return one_hot def get_target_transform(dataset_str): if "VOC2007" in dataset_str: return voc2007_classification_target_transform elif "IMaterialist" in dataset_str: return imaterialist_classification_target_transform return None