CoolFace
Modelpublic

openbmb/MiniCPM-V-4-int4

sourceHugging Faceapache-2.0updated 1mo agoView on Hugging Face
8likes204downloads
image_processing_minicpmv.py419 linesDownload Raw Back to root
1from typing import Optional, Union, Dict, Any, List2 3import torch4import math5import PIL.Image6import PIL.ImageSequence7import numpy as np8import PIL9from PIL import Image10 11from transformers.utils import TensorType, requires_backends, is_torch_dtype, is_torch_device12from transformers.image_processing_utils import BaseImageProcessor, BatchFeature13from transformers import AutoImageProcessor14from transformers.image_transforms import to_channel_dimension_format15from transformers.image_utils import (16    ImageInput, 17    make_list_of_images, 18    valid_images, 19    is_torch_tensor, 20    is_batched,21    to_numpy_array, 22    infer_channel_dimension_format,23    ChannelDimension24)25 26 27def recursive_converter(converter, value):28    if isinstance(value, list):29        new_value = []30        for v in value:31            new_value += [recursive_converter(converter, v)]32        return new_value33    else:34        return converter(value)35 36 37class MiniCPMVBatchFeature(BatchFeature):38    r"""39    Extend from BatchFeature for supporting various image size40    """41    def __init__(self, data: Optional[Dict[str, Any]] = None, tensor_type: Union[None, str, TensorType] = None):42        super().__init__(data)43        self.convert_to_tensors(tensor_type=tensor_type)44 45    def convert_to_tensors(self, tensor_type: Optional[Union[str, TensorType]] = None):46        if tensor_type is None:47            return self48        49        is_tensor, as_tensor = self._get_is_as_tensor_fns(tensor_type)50 51        def converter(value):52            try:53                if not is_tensor(value):54                    tensor = as_tensor(value)55                    return tensor56            except:  # noqa E72257                if key == "overflowing_values":58                    raise ValueError("Unable to create tensor returning overflowing values of different lengths. ")59                raise ValueError(60                    "Unable to create tensor, you should probably activate padding "61                    "with 'padding=True' to have batched tensors with the same length."62                )63 64 65        for key, value in self.items():66            self[key] = recursive_converter(converter, value)67        return self68            69    def to(self, *args, **kwargs) -> "MiniCPMVBatchFeature":70        requires_backends(self, ["torch"])71        import torch72 73        def cast_tensor(v):74            # check if v is a floating point75            if torch.is_floating_point(v):76                # cast and send to device77                return v.to(*args, **kwargs)78            elif device is not None:79                return v.to(device=device)80            else:81                return v82 83        new_data = {}84        device = kwargs.get("device")85        # Check if the args are a device or a dtype86        if device is None and len(args) > 0:87            # device should be always the first argument88            arg = args[0]89            if is_torch_dtype(arg):90                # The first argument is a dtype91                pass92            elif isinstance(arg, str) or is_torch_device(arg) or isinstance(arg, int):93                device = arg94            else:95                # it's something else96                raise ValueError(f"Attempting to cast a BatchFeature to type {str(arg)}. This is not supported.")97        # We cast only floating point tensors to avoid issues with tokenizers casting `LongTensor` to `FloatTensor`98        for k, v in self.items():99            new_data[k] = recursive_converter(cast_tensor, v)100        self.data = new_data101        return self102 103 104class MiniCPMVImageProcessor(BaseImageProcessor):105    model_input_names = ["pixel_values"]106 107    def __init__(108            self, 109            max_slice_nums=9,110            scale_resolution=448,111            patch_size=14,112            **kwargs):113        super().__init__(**kwargs)114        self.max_slice_nums = max_slice_nums115        self.scale_resolution = scale_resolution116        self.patch_size = patch_size117        self.use_image_id = kwargs.pop("use_image_id", False)118        self.image_feature_size = kwargs.pop("image_feature_size", 64)119        self.im_start_token = kwargs.pop("im_start", "<image>")120        self.im_end_token = kwargs.pop("im_end", "</image>")121        self.slice_start_token = kwargs.pop("slice_start", "<slice>")122        self.slice_end_token = kwargs.pop("slice_end", "</slice>")123        self.unk_token = kwargs.pop("unk", "<unk>")124        self.im_id_start = kwargs.pop("im_id_start", "<image_id>")125        self.im_id_end = kwargs.pop("im_id_end", "</image_id>")126        self.slice_mode = kwargs.pop("slice_mode", True)127        self.mean = np.array(kwargs.pop("norm_mean", [0.5, 0.5, 0.5]))128        self.std = np.array(kwargs.pop("norm_std", [0.5, 0.5, 0.5]))129        self.version = kwargs.pop("version", 2.0)130 131    def ensure_divide(self, length, patch_size):132        return max(round(length / patch_size) * patch_size, patch_size)133 134    def find_best_resize(self,135                         original_size,136                         scale_resolution,137                         patch_size,138                         allow_upscale=False):139        width, height = original_size140        if (width * height >141                scale_resolution * scale_resolution) or allow_upscale:142            r = width / height143            height = int(scale_resolution / math.sqrt(r))144            width = int(height * r)145        best_width = self.ensure_divide(width, patch_size)146        best_height = self.ensure_divide(height, patch_size)147        return (best_width, best_height)148 149    def get_refine_size(self,150                        original_size,151                        grid,152                        scale_resolution,153                        patch_size,154                        allow_upscale=False):155        width, height = original_size156        grid_x, grid_y = grid157 158        refine_width = self.ensure_divide(width, grid_x)159        refine_height = self.ensure_divide(height, grid_y)160 161        grid_width = refine_width / grid_x162        grid_height = refine_height / grid_y163 164        best_grid_size = self.find_best_resize((grid_width, grid_height),165                                               scale_resolution,166                                               patch_size,167                                               allow_upscale=allow_upscale)168        refine_size = (best_grid_size[0] * grid_x, best_grid_size[1] * grid_y)169        return refine_size170 171    def split_to_patches(self, image, grid):172        patches = []173        width, height = image.size174        grid_x = int(width / grid[0])175        grid_y = int(height / grid[1])176        for i in range(0, height, grid_y):177            images = []178            for j in range(0, width, grid_x):179                box = (j, i, j + grid_x, i + grid_y)180                patch = image.crop(box)181                images.append(patch)182            patches.append(images)183        return patches184 185    def slice_image(186        self, image, max_slice_nums=9, scale_resolution=448, patch_size=14, never_split=False187    ):188        original_size = image.size189        source_image = None190        best_grid = self.get_sliced_grid(original_size, max_slice_nums, never_split)191        patches = []192 193        if best_grid is None:194            # dont need to slice, upsample195            best_size = self.find_best_resize(196                original_size, scale_resolution, patch_size, allow_upscale=True197            )198            source_image = image.resize(best_size, resample=Image.Resampling.BICUBIC)199        else:200            # source image, down-sampling and ensure divided by patch_size201            best_resize = self.find_best_resize(original_size, scale_resolution, patch_size)202            source_image = image.copy().resize(best_resize, resample=Image.Resampling.BICUBIC)203            refine_size = self.get_refine_size(204                original_size, best_grid, scale_resolution, patch_size, allow_upscale=True205            )206            refine_image = image.resize(refine_size, resample=Image.Resampling.BICUBIC)207            patches = self.split_to_patches(refine_image, best_grid)208 209        return source_image, patches, best_grid210 211    def get_grid_placeholder(self, grid):212        if grid is None:213            return ""214        slice_image_placeholder = (215            self.slice_start_token 216            + self.unk_token * self.image_feature_size217            + self.slice_end_token218        )219 220        cols = grid[0]221        rows = grid[1]222        slices = []223        for i in range(rows):224            lines = []225            for j in range(cols):226                lines.append(slice_image_placeholder)227            slices.append("".join(lines))228            229        slice_placeholder = "\n".join(slices)230        return slice_placeholder231 232    def get_image_id_placeholder(self, idx=0):233        return f"{self.im_id_start}{idx}{self.im_id_end}"234    235    def get_sliced_images(self, image, max_slice_nums=None):236        slice_images = []237 238        if not self.slice_mode:239            return [image]240 241        max_slice_nums = self.max_slice_nums if max_slice_nums is None else int(max_slice_nums)242        assert max_slice_nums > 0 243        source_image, patches, sliced_grid = self.slice_image(244            image,245            max_slice_nums,  # default: 9246            self.scale_resolution,  # default: 448247            self.patch_size  # default: 14248        )249 250        slice_images.append(source_image)251        if len(patches) > 0:252            for i in range(len(patches)):253                for j in range(len(patches[0])):254                    slice_images.append(patches[i][j])255        return slice_images256 257    def get_sliced_grid(self, image_size, max_slice_nums, nerver_split=False):258        original_width, original_height = image_size259        log_ratio = math.log(original_width / original_height)260        ratio = original_width * original_height / (self.scale_resolution * self.scale_resolution)261        multiple = min(math.ceil(ratio), max_slice_nums)262        if multiple <= 1 or nerver_split:263            return None264        candidate_split_grids_nums = []265        for i in [multiple - 1, multiple, multiple + 1]:266            if i == 1 or i > max_slice_nums:267                continue268            candidate_split_grids_nums.append(i)269        270        candidate_grids = []271        for split_grids_nums in candidate_split_grids_nums:272            m = 1273            while m <= split_grids_nums:274                if split_grids_nums % m == 0:275                    candidate_grids.append([m, split_grids_nums // m])276                m += 1277 278        best_grid = [1, 1]279        min_error = float("inf")280        for grid in candidate_grids:281            error = abs(log_ratio - math.log(grid[0] / grid[1]))282            if error < min_error:283                best_grid = grid284                min_error = error285        286        return best_grid287    288    def get_slice_image_placeholder(self, image_size, image_idx=0, max_slice_nums=None, use_image_id=None):289        max_slice_nums = self.max_slice_nums if max_slice_nums is None else int(max_slice_nums)290        assert max_slice_nums > 0        291        grid = self.get_sliced_grid(image_size=image_size, max_slice_nums=max_slice_nums)292 293        image_placeholder = (294            self.im_start_token 295            + self.unk_token * self.image_feature_size 296            + self.im_end_token297        )298        use_image_id = self.use_image_id if use_image_id is None else bool(use_image_id)299        if use_image_id:300            final_placeholder = self.get_image_id_placeholder(image_idx) + image_placeholder301        else:302            final_placeholder = image_placeholder303 304        if self.slice_mode:305            final_placeholder = final_placeholder + self.get_grid_placeholder(grid=grid)306        return final_placeholder307        308    def to_pil_image(self, image, rescale=None) -> PIL.Image.Image:309        """310        Converts `image` to a PIL Image. Optionally rescales it and puts the channel dimension back as the last axis if311        needed.312 313        Args:314            image (`PIL.Image.Image` or `numpy.ndarray` or `torch.Tensor`):315                The image to convert to the PIL Image format.316            rescale (`bool`, *optional*):317                Whether or not to apply the scaling factor (to make pixel values integers between 0 and 255). Will318                default to `True` if the image type is a floating type, `False` otherwise.319        """320        if isinstance(image, PIL.Image.Image):321            return image322        if is_torch_tensor(image):323            image = image.numpy()324 325        if isinstance(image, np.ndarray):326            if rescale is None:327                # rescale default to the array being of floating type.328                rescale = isinstance(image.flat[0], np.floating)329            # If the channel as been moved to first dim, we put it back at the end.330            if image.ndim == 3 and image.shape[0] in [1, 3]:331                image = image.transpose(1, 2, 0)332            if rescale:333                image = image * 255334            image = image.astype(np.uint8)335            return PIL.Image.fromarray(image)336        return image337 338    def reshape_by_patch(self, image):339        """340        :param image: shape [3, H, W]341        :param patch_size:342        :return: [3, patch_size, HW/patch_size]343        """344        image = torch.from_numpy(image)345        patch_size = self.patch_size346        patches = torch.nn.functional.unfold(347            image,348            (patch_size, patch_size),349            stride=(patch_size, patch_size)350        )351 352        patches = patches.reshape(image.size(0), patch_size, patch_size, -1)353        patches = patches.permute(0, 1, 3, 2).reshape(image.size(0), patch_size, -1)354        return patches.numpy()355 356    def preprocess(357            self, 358            images: Union[Image.Image, List[Image.Image], List[List[Image.Image]]],359            do_pad: Optional[bool] = True, # TODO: add pad for MiniCPM-Llama3-V-2_5360            max_slice_nums: int = None,361            return_tensors: Optional[Union[str, TensorType]] = None,362            **kwargs363        ) -> MiniCPMVBatchFeature:364        if isinstance(images, Image.Image):365            images_list = [[images]]366        elif isinstance(images[0], Image.Image):367            images_list = [images]368        else:369            images_list = images370 371        new_images_list = []372        image_sizes_list = []373        tgt_sizes_list = []374        375        for _images in images_list:376            if _images is None or len(_images) == 0:377                new_images_list.append([])378                image_sizes_list.append([])379                tgt_sizes_list.append([])380                continue             381            if not valid_images(_images):382                raise ValueError(383                    "Invalid image type. Must be of type PIL.Image.Image, numpy.ndarray, "384                    "torch.Tensor, tf.Tensor or jax.ndarray."385                )386            387            _images = [self.to_pil_image(image).convert("RGB") for image in _images]388            input_data_format = infer_channel_dimension_format(np.array(_images[0]))389 390            new_images = []391            image_sizes = [image.size for image in _images]392            tgt_sizes = []393            for image in _images:394                image_patches = self.get_sliced_images(image, max_slice_nums)395                image_patches = [to_numpy_array(image).astype(np.float32) / 255 for image in image_patches]396                image_patches = [397                    self.normalize(image=image, mean=self.mean, std=self.std, input_data_format=input_data_format)398                        for image in image_patches399                ]400                image_patches = [401                    to_channel_dimension_format(image, ChannelDimension.FIRST, input_channel_dim=input_data_format) 402                        for image in image_patches403                ]404                for slice_image in image_patches:405                    new_images.append(self.reshape_by_patch(slice_image))406                    tgt_sizes.append(np.array((slice_image.shape[1] // self.patch_size, slice_image.shape[2] // self.patch_size)))407 408            if tgt_sizes:409                tgt_sizes = np.vstack(tgt_sizes)410            411            new_images_list.append(new_images)412            image_sizes_list.append(image_sizes)413            tgt_sizes_list.append(tgt_sizes)414        return MiniCPMVBatchFeature(415            data={"pixel_values": new_images_list, "image_sizes": image_sizes_list, "tgt_sizes": tgt_sizes_list}, tensor_type=return_tensors416        )417 418AutoImageProcessor.register("MiniCPMVImageProcessor", MiniCPMVImageProcessor)419