CoolFace
Modelpublic

MiniMaxAI/MiniMax-VL-01

sourceHugging Faceupdated 1y agoView on Hugging Face
286likes33kdownloads
image_processor.py616 linesDownload Raw Back to root
1from transformers.image_processing_utils import BaseImageProcessor, BatchFeature2from typing import Optional, Union, Tuple, Dict, List, Iterable3from transformers.image_transforms import to_channel_dimension_format, PaddingMode4from transformers.image_utils import ChannelDimension, to_numpy_array, make_list_of_images, get_image_size, infer_channel_dimension_format5from transformers.utils import TensorType6from PIL import Image7import numpy as np8try:9    from torchvision.transforms import InterpolationMode10    BICUBIC = InterpolationMode.BICUBIC11except ImportError:12    BICUBIC = Image.BICUBIC13 14import torch15from transformers.utils import (16    TensorType,17    is_torch_device,18    is_torch_dtype,19    requires_backends,20)21 22from torchvision.transforms import Compose, ToTensor, Normalize, ToPILImage, RandomResizedCrop, Resize23 24try:25    from torchvision.transforms import InterpolationMode26    BICUBIC = InterpolationMode.BICUBIC27except ImportError:28    BICUBIC = Image.BICUBIC29 30from PIL import Image31import torch32import numpy as np33import os34processor_for_vllm = int(os.getenv("PROCESSOR_FOR_VLLM", 0))35 36def select_best_resolution(original_size, possible_resolutions):37    """38    Selects the best resolution from a list of possible resolutions based on the original size.39 40    Args:41        original_size (tuple): The original size of the image in the format (width, height).42        possible_resolutions (list): A list of possible resolutions in the format [(width1, height1), (width2, height2), ...].43 44    Returns:45        tuple: The best fit resolution in the format (width, height).46    """47    original_width, original_height = original_size48    best_fit = None49    max_effective_resolution = 050    min_wasted_resolution = float("inf")51 52    for width, height in possible_resolutions:53        # Calculate the downscaled size to keep the aspect ratio54        scale = min(width / original_width, height / original_height)55        downscaled_width, downscaled_height = int(original_width * scale), int(original_height * scale)56 57        # Calculate effective and wasted resolutions58        effective_resolution = min(downscaled_width * downscaled_height, original_width * original_height)59        wasted_resolution = (width * height) - effective_resolution60 61        if effective_resolution > max_effective_resolution or (effective_resolution == max_effective_resolution and wasted_resolution < min_wasted_resolution):62            max_effective_resolution = effective_resolution63            min_wasted_resolution = wasted_resolution64            best_fit = (width, height)65 66    return best_fit 67 68def divide_to_patches(image, patch_size):69    """70    Divides an image into patches of a specified size.71 72    Args:73        image (PIL.Image.Image): The input image.74        patch_size (int): The size of each patch.75 76    Returns:77        list: A list of PIL.Image.Image objects representing the patches.78    """79    patches = []80    width, height = image.size81    for i in range(0, height, patch_size):82        for j in range(0, width, patch_size):83            box = (j, i, j + patch_size, i + patch_size)84            patch = image.crop(box)85            patches.append(patch)86 87    return patches88 89def image_size_to_num_patches(image_size, grid_pinpoints, patch_size):90    if not isinstance(grid_pinpoints, list):91        raise TypeError("grid_pinpoints should be a list of tuples or lists")92 93    # ! VERY IMPORTANT if image_size is tensor, must convert to into tuple, otherwise it will cause wrong calculate94    if not isinstance(image_size, (list, tuple)):95        if not isinstance(image_size, (torch.Tensor, np.ndarray)):96            raise TypeError(f"image_size invalid type {type(image_size)} with value {image_size}")97        image_size = image_size.tolist()98 99    best_resolution = select_best_resolution(image_size, grid_pinpoints)100    width, height = best_resolution101    num_patches = 0102    # consider change to ceil(height/patch_size)*ceil(width/patch_size) + 1103    for i in range(0, height, patch_size):104        for j in range(0, width, patch_size):105            num_patches += 1106    # add the base patch107    num_patches += 1108    return num_patches109 110def get_anyres_image_grid_shape(image_size, grid_pinpoints, patch_size):111    """112    Calculate the shape of the image patch grid after the preprocessing for images of any resolution.113 114    Args:115        image_size (`tuple`):116            The size of the input image in the format (width, height).117        grid_pinpoints (`List`):118            A list containing possible resolutions. Each item in the list should be a tuple or list119            of the form `(height, width)`.120        patch_size (`int`):121            The size of each image patch.122 123    Returns:124        tuple: The shape of the image patch grid in the format (width, height).125    """126    if not isinstance(grid_pinpoints, list):127        raise TypeError("grid_pinpoints should be a list of tuples or lists")128 129    # ! VERY IMPORTANT if image_size is tensor, must convert to into tuple, otherwise it will cause wrong calculate130    if not isinstance(image_size, (list, tuple)):131        if not isinstance(image_size, (torch.Tensor, np.ndarray)):132            raise TypeError(133                f"image_size invalid type: {type(image_size)} not valid, should be either list, tuple, np.ndarray or tensor"134            )135        image_size = image_size.tolist()136 137    width, height = select_best_resolution(image_size, grid_pinpoints)138    return width // patch_size, height // patch_size139 140 141# custom transform142class KeeyRatioResize(object):143    def __init__(self, size):144        self.size = size145 146    def __call__(self, image):147        return keepratio_resize(image, self.size)148 149def keepratio_resize(image, size, return_scale=False):150    # Resize the image to keep the ratio151    w, h = image.size152    resized_w, resized_h = size153    if w / h > resized_w / resized_h:154        # resize and pad to the right and left155        new_h = int(resized_w*h/w)156        resized_image = image.resize((resized_w, new_h), Image.BICUBIC)157 158        image = Image.new('RGB', (resized_w, resized_h), (0, 0, 0))159        pad_h = (resized_h - new_h) // 2160        image.paste(resized_image, (0, pad_h))161        scale = resized_w / w162        #image.paste(resized_image, (0, 0))163    else:164        # resize and pad to the top and bottom165        new_w = int(resized_h*w/h)166        resized_image = image.resize((new_w, resized_h), Image.BICUBIC)167        image = Image.new('RGB', (resized_w, resized_h), (0, 0, 0))168        #image.paste(resized_image, (0, 0))169        pad_w = (resized_w - new_w) // 2170        image.paste(resized_image, (pad_w, 0))171        scale = resized_h / h172    if return_scale:173        return image, scale174    return image175 176def _convert_image_to_rgb(image):177    return image.convert("RGB")178 179def _transform(img_h, img_w, image_mean=(0.48145466, 0.4578275, 0.40821073), image_std=(0.26862954, 0.26130258, 0.27577711)):180    return Compose([181        # ToPILImage(),182        #RandomResizedCrop((img_h, img_w), scale=(0.5, 1.0), interpolation=BICUBIC),183        #Resize((img_h, img_w), interpolation=BICUBIC),184        _convert_image_to_rgb,185        ToTensor(),186        Normalize(image_mean, image_std),187    ])188 189 190def get_hw_multiple_of(image_size, multiple, max_size=None):191    w, h = image_size192    new_w = w if w % multiple == 0 else w + (multiple - w % multiple)193    new_h = h if h % multiple == 0 else h + (multiple - h % multiple)194    if max_size is not None:195        assert isinstance(max_size, (list, tuple)) and len(max_size) == 2196        max_w, max_h = max_size197        assert max_w % multiple == 0 and max_h % multiple == 0198        if new_w > max_w or new_h > max_h:199            # ratio = min(max_w / new_w, max_h / new_h)200            # new_w = int(new_w * ratio)201            # new_h = int(new_h * ratio)202            new_w = min((new_w * max_w) // new_w, (new_w * max_h) // new_h)203            new_h = min((new_h * max_w) // new_w, (new_h * max_h) // new_h)204 205            new_w = new_w if new_w % multiple == 0 else new_w + (multiple - new_w % multiple)206            new_h = new_h if new_h % multiple == 0 else new_h + (multiple - new_h % multiple)207        assert new_w % multiple == 0 and new_h % multiple == 0208        assert new_w <= max_w and new_h <= max_h209    return new_w, new_h210 211def resize_multiple_of(image, multiple, max_size=None):212    """213    Resize the image to the multiple of a number.214 215    Args:216        image (PIL.Image.Image): The input image.217        multiple (int): The number to which the image should be resized.218 219    Returns:220        PIL.Image.Image: The resized image.221    """222    width, height = image.size223    new_width, new_height = get_hw_multiple_of((width, height), multiple, max_size)224    return image.resize((new_width, new_height), Image.BICUBIC)225 226 227 228class CustomBatchFeature(BatchFeature):229    def convert_to_tensors(self, tensor_type: Optional[Union[str, TensorType]] = None):230        """231        Convert the inner content to tensors.232 233        Args:234            tensor_type (`str` or [`~utils.TensorType`], *optional*):235                The type of tensors to use. If `str`, should be one of the values of the enum [`~utils.TensorType`]. If236                `None`, no modification is done.237        """238        if tensor_type is None:239            return self240 241        is_tensor, as_tensor = self._get_is_as_tensor_fns(tensor_type)242 243        # Do the tensor conversion in batch244        for key, value in self.items():245            if key == "pixel_values":246                for i, image in enumerate(value):247                    if not is_tensor(image):248                        tensor = as_tensor(image)249                        self[key][i] = tensor250                continue251            try:252                if not is_tensor(value):253                    tensor = as_tensor(value)254 255                    self[key] = tensor256            except:  # noqa E722257                if key == "overflowing_values":258                    raise ValueError("Unable to create tensor returning overflowing values of different lengths. ")259                raise ValueError(260                    "Unable to create tensor, you should probably activate padding "261                    "with 'padding=True' to have batched tensors with the same length."262                )263 264        return self265 266    def to(self, *args, **kwargs) -> "BatchFeature":267        """268        Send all values to device by calling `v.to(*args, **kwargs)` (PyTorch only). This should support casting in269        different `dtypes` and sending the `BatchFeature` to a different `device`.270 271        Args:272            args (`Tuple`):273                Will be passed to the `to(...)` function of the tensors.274            kwargs (`Dict`, *optional*):275                Will be passed to the `to(...)` function of the tensors.276 277        Returns:278            [`BatchFeature`]: The same instance after modification.279        """280        requires_backends(self, ["torch"])281        import torch  # noqa282 283        new_data = {}284        device = kwargs.get("device")285        # Check if the args are a device or a dtype286        if device is None and len(args) > 0:287            # device should be always the first argument288            arg = args[0]289            if is_torch_dtype(arg):290                # The first argument is a dtype291                pass292            elif isinstance(arg, str) or is_torch_device(arg) or isinstance(arg, int):293                device = arg294            else:295                # it's something else296                raise ValueError(f"Attempting to cast a BatchFeature to type {str(arg)}. This is not supported.")297        # We cast only floating point tensors to avoid issues with tokenizers casting `LongTensor` to `FloatTensor`298        for k, v in self.items():299            if k == "pixel_values":300                new_data[k] = [v[i].to(*args, **kwargs) for i in range(len(v))]301                continue302            # check if v is a floating point303            if torch.is_floating_point(v):304                # cast and send to device305                new_data[k] = v.to(*args, **kwargs)306            elif device is not None:307                new_data[k] = v.to(device=device)308            else:309                new_data[k] = v310        self.data = new_data311        return self312 313 314def as_tensor(value):315    if isinstance(value, (list, tuple)) and len(value) > 0:316        if isinstance(value[0], np.ndarray):317            value = np.array(value)318        elif (319            isinstance(value[0], (list, tuple))320            and len(value[0]) > 0321            and isinstance(value[0][0], np.ndarray)322        ):323            value = np.array(value)324    if isinstance(value, np.ndarray):325        return torch.from_numpy(value)326    else:327        return torch.tensor(value)328 329class ImageProcessor(BaseImageProcessor):330    model_input_names = ["pixel_values"]331 332    def __init__(333        self,334        size: Optional[Union[int, Tuple[int, int], Dict[str, int]]] = None,335        image_mean: Optional[Union[float, List[float]]] = None,336        image_std: Optional[Union[float, List[float]]] = None,337        process_image_mode: Optional[str] = 'resize',338        patch_size: Optional[int] = 14,339        image_grid_pinpoints: List = None,340        **kwargs,341    ) -> None:342        super().__init__(**kwargs)343        self.size = size # (width, height)344        self.image_mean = image_mean345        self.image_std = image_std346        self.process_image_mode = process_image_mode347        image_grid_pinpoints = (348            image_grid_pinpoints349            if image_grid_pinpoints is not None350            else [[336, 672], [672, 336], [672, 672], [1008, 336], [336, 1008]]351        )352        self.image_grid_pinpoints = image_grid_pinpoints353        self.patch_size = patch_size354 355    def preprocess(self,356                    images,357                    return_tensors: Optional[Union[str, TensorType]] = None,358                    data_format: Optional[ChannelDimension] = ChannelDimension.FIRST,359                    input_data_format: Optional[Union[str, ChannelDimension]] = None,360                    **kwargs,361                    ):362        if self.process_image_mode == 'resize':363            return self.resize_preprocess(images, return_tensors, data_format, input_data_format, **kwargs)364        elif self.process_image_mode == 'anyres':365            if processor_for_vllm == 1:366                return self.anyres_for_vllm_preprocess(images, return_tensors, data_format, input_data_format, **kwargs)367            return self.anyres_preprocess(images, return_tensors, data_format, input_data_format, **kwargs)368        elif self.process_image_mode == 'keepratio_resize':369            return self.keepratio_resize_preprocess(images, return_tensors, data_format, input_data_format, **kwargs)370        elif self.process_image_mode == 'dynamic_res':371            return self.dynamic_res_preprocess(images, return_tensors, data_format, input_data_format, **kwargs)372        else:373            raise ValueError(f"Invalid process_image_mode: {self.process_image_mode}")374    375    def resize_preprocess(self, images, return_tensors: Optional[Union[str, TensorType]] = None, data_format: Optional[ChannelDimension] = ChannelDimension.FIRST, input_data_format: Optional[Union[str, ChannelDimension]] = None, **kwargs):376        images = make_list_of_images(images)377        all_images = []378        for image in images:379            resized_image = image.resize(self.size, Image.BICUBIC)380            transform_img = _transform(self.size[1], self.size[0], self.image_mean, self.image_std)(resized_image)381            all_images.append(to_numpy_array(transform_img))382 383        images = [384            to_channel_dimension_format(image, data_format, input_channel_dim=input_data_format)385            for image in all_images386        ]387 388        data = {"pixel_values": images}389        return CustomBatchFeature(data=data, tensor_type=return_tensors)390 391    def keepratio_resize_preprocess(self, images, return_tensors: Optional[Union[str, TensorType]] = None, data_format: Optional[ChannelDimension] = ChannelDimension.FIRST, input_data_format: Optional[Union[str, ChannelDimension]] = None, **kwargs):392        images = make_list_of_images(images)393        all_images = []394        for image in images:395            resized_image = keepratio_resize(image, self.size)396            transform_img = _transform(self.size[1], self.size[0], self.image_mean, self.image_std)(resized_image)397            all_images.append(to_numpy_array(transform_img))398 399        images = [400            to_channel_dimension_format(image, data_format, input_channel_dim=input_data_format)401            for image in all_images402        ]403 404        data = {"pixel_values": images}405        return CustomBatchFeature(data=data, tensor_type=return_tensors)406 407    def dynamic_res_preprocess(self, images, return_tensors: Optional[Union[str, TensorType]] = None, data_format: Optional[ChannelDimension] = ChannelDimension.FIRST, input_data_format: Optional[Union[str, ChannelDimension]] = None, **kwargs):408        images = make_list_of_images(images)409        all_images = []410        image_sizes = []411        for image in images:412            ori_w, ori_h = image.size413            image_sizes.append([ori_h, ori_w])414            resized_image = resize_multiple_of(image, self.patch_size, max_size=self.size)415            resized_w, resized_h = resized_image.size416            transform_img = _transform(resized_h, resized_w, self.image_mean, self.image_std)(resized_image)417            all_images.append(to_numpy_array(transform_img))418 419        images = [420            as_tensor(to_channel_dimension_format(image, data_format, input_channel_dim=input_data_format))421            for image in all_images422        ]423 424        # data = {"pixel_values": images, "image_sizes": as_tensor(image_sizes)}425        # return data426        data = {"pixel_values": images, "image_sizes": image_sizes}427        #return BatchFeature(data=data, data_format=data_format, tensor_type=return_tensors)428        429        return CustomBatchFeature(data=data, tensor_type=return_tensors)430 431    def get_image_patches(432        self,433        data: Image,434        image_grid_pinpoints,435    ):436        if not isinstance(image_grid_pinpoints, list):437            raise TypeError("grid_pinpoints must be a list of possible resolutions.")438 439 440        best_resolution = select_best_resolution(data.size, image_grid_pinpoints)441 442        resized_data, scale = keepratio_resize(data, best_resolution, return_scale=True)443        resized_data = divide_to_patches(resized_data, self.size[0])444        ori_data = data.resize(self.size, Image.BICUBIC)445        data = [ori_data] + resized_data446        return data447    448    def pad(449        self,450        image: np.ndarray,451        padding: Union[int, Tuple[int, int], Iterable[Tuple[int, int]]],452        mode: PaddingMode = PaddingMode.CONSTANT,453        constant_values: Union[float, Iterable[float]] = 0.0,454        data_format: Optional[Union[str, ChannelDimension]] = None,455        input_data_format: Optional[Union[str, ChannelDimension]] = None,456    ) -> np.ndarray:457        """458        Pads the `image` with the specified `padding` and `mode`. Padding can be in the (`height`, `width`)459        dimension of in the (`num_patches`) dimension. In the second case an iterable if tuples is expected460        as input.461 462        Args:463            image (`np.ndarray`):464                The image to pad.465            padding (`int` or `Tuple[int, int]` or `Iterable[Tuple[int, int]]`):466                Padding to apply to the edges of the height, width axes. Can be one of three formats:467                - `((before_height, after_height), (before_width, after_width))` unique pad widths for each axis.468                - `((before, after),)` yields same before and after pad for height and width.469                - `(pad,)` or int is a shortcut for before = after = pad width for all axes.470            mode (`PaddingMode`):471                The padding mode to use. Can be one of:472                    - `"constant"`: pads with a constant value.473                    - `"reflect"`: pads with the reflection of the vector mirrored on the first and last values of the474                    vector along each axis.475                    - `"replicate"`: pads with the replication of the last value on the edge of the array along each axis.476                    - `"symmetric"`: pads with the reflection of the vector mirrored along the edge of the array.477            constant_values (`float` or `Iterable[float]`, *optional*):478                The value to use for the padding if `mode` is `"constant"`.479            data_format (`str` or `ChannelDimension`, *optional*):480                The channel dimension format for the output image. Can be one of:481                    - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.482                    - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.483                If unset, will use same as the input image.484            input_data_format (`str` or `ChannelDimension`, *optional*):485                The channel dimension format for the input image. Can be one of:486                    - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.487                    - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.488                If unset, will use the inferred format of the input image.489 490        Returns:491            `np.ndarray`: The padded image.492 493        """494 495        # call the general `pad` if padding on `height/width`, otherwise it's the `num_patched` dim496        if isinstance(padding, int) or len(padding) != 4:497            return pad(image, padding, mode, constant_values, data_format, input_data_format)498 499        if input_data_format is None:500            input_data_format = infer_channel_dimension_format(image)501        if mode == PaddingMode.CONSTANT:502            image = np.pad(image, padding, mode="constant", constant_values=constant_values)503        elif mode == PaddingMode.REFLECT:504            image = np.pad(image, padding, mode="reflect")505        elif mode == PaddingMode.REPLICATE:506            image = np.pad(image, padding, mode="edge")507        elif mode == PaddingMode.SYMMETRIC:508            image = np.pad(image, padding, mode="symmetric")509        else:510            raise ValueError(f"Invalid padding mode: {mode}")511        image = (512            to_channel_dimension_format(image, data_format, input_data_format) if data_format is not None else image513        )514        return image515 516    def _pad_for_batching(517        self,518        pixel_values: List[np.ndarray],519        data_format: Optional[Union[str, ChannelDimension]] = None,520        input_data_format: Optional[Union[str, ChannelDimension]] = None,521    ):522        """523        Pads images on the `num_of_patches` dimension with zeros to form a batch of same number of patches.524 525        Args:526            pixel_values (`List[np.ndarray]`):527                An array of pixel values of each images of shape (`batch_size`, `num_patches`, `image_in_3D`)528            data_format (`str` or `ChannelDimension`, *optional*):529                The channel dimension format for the output image. Can be one of:530                    - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.531                    - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.532                If unset, will use same as the input image.533            input_data_format (`str` or `ChannelDimension`, *optional*):534                The channel dimension format for the input image. Can be one of:535                    - `"channels_first"` or `ChannelDimension.FIRST`: image in (num_channels, height, width) format.536                    - `"channels_last"` or `ChannelDimension.LAST`: image in (height, width, num_channels) format.537                If unset, will use the inferred format of the input image.538 539        Returns:540            List[`np.ndarray`]: The padded images.541        """542        max_patch = max(len(x) for x in pixel_values)543        pixel_values = [544            self.pad(545                image,546                padding=((0, max_patch - image.shape[0]), (0, 0), (0, 0), (0, 0)),547                data_format=data_format,548                input_data_format=input_data_format,549            )550            for image in pixel_values551        ]552 553        return pixel_values554 555    def anyres_for_vllm_preprocess(self, images, return_tensors: Optional[Union[str, TensorType]] = None, data_format: Optional[ChannelDimension] = ChannelDimension.FIRST, input_data_format: Optional[Union[str, ChannelDimension]] = None, do_pad: Optional[bool] = None, **kwargs):556        557        images = make_list_of_images(images)558        new_images = []559        image_sizes = []560 561        for image in images:562            ori_w, ori_h = image.size563            image_sizes.append([ori_h, ori_w])564            image_patches = self.get_image_patches(565                image,566                self.image_grid_pinpoints567            )568            all_images = []569            for image in image_patches:570                transform_img = _transform(self.size[0], self.size[1], self.image_mean, self.image_std)(image)571                img_array = to_numpy_array(transform_img)572                img_array = to_channel_dimension_format(img_array, data_format, input_channel_dim=input_data_format)573                all_images.append(img_array)574                #new_images.append(img_array)575            pixel_values = np.array(all_images)576            new_images.append(pixel_values)577        578 579        new_images = self._pad_for_batching(new_images)580 581        data = {"pixel_values": new_images, "image_sizes": image_sizes}582        return BatchFeature(data=data, tensor_type=return_tensors)583 584    585    def anyres_preprocess(self, images, return_tensors: Optional[Union[str, TensorType]] = None, data_format: Optional[ChannelDimension] = ChannelDimension.FIRST, input_data_format: Optional[Union[str, ChannelDimension]] = None, do_pad: Optional[bool] = None, **kwargs):586        587        images = make_list_of_images(images)588        new_images = []589        image_sizes = []590 591        for image in images:592            ori_w, ori_h = image.size593            image_sizes.append([ori_h, ori_w])594            image_patches = self.get_image_patches(595                image,596                self.image_grid_pinpoints597            )598            #all_images = []599            for image in image_patches:600                transform_img = _transform(self.size[0], self.size[1], self.image_mean, self.image_std)(image)601                img_array = to_numpy_array(transform_img)602                img_array = to_channel_dimension_format(img_array, data_format, input_channel_dim=input_data_format)603                #all_images.append(img_array)604                new_images.append(img_array)605            #pixel_values = np.array(all_images)606            #new_images.append(pixel_values)607        608        # if do_pad:609        #     new_images = self._pad_for_batching(new_images)610 611        data = {"pixel_values": new_images, "image_sizes": image_sizes}612        return CustomBatchFeature(data=data, tensor_type=return_tensors)613 614 615 616