CoolFace
Modelpublic

naver-hyperclovax/HyperCLOVAX-SEED-Vision-Instruct-3B

sourceHugging Faceotherupdated 1y agoView on Hugging Face
221likes5kdownloads
image_processing_hyperclovax.py790 linesDownload Raw Back to root
1import copy2import math3import os4from typing import Dict, List, Optional, Union5 6import numpy as np7import torch8from PIL import Image9from transformers.feature_extraction_utils import BatchFeature10from transformers.image_processing_utils import (11    BaseImageProcessor,12    get_size_dict,13)14from transformers.image_transforms import (15    convert_to_rgb,16    get_resize_output_image_size,17    resize,18    to_channel_dimension_format,19)20from transformers.image_utils import (21    OPENAI_CLIP_MEAN,22    OPENAI_CLIP_STD,23    ChannelDimension,24    ImageInput,25    PILImageResampling,26    get_image_size,27    infer_channel_dimension_format,28    is_scaled_image,29    make_list_of_images,30    to_numpy_array,31    valid_images,32)33from transformers.utils import TensorType, logging34 35logger = logging.get_logger(__name__)36 37 38class HCXImageProcessor(BaseImageProcessor):39    r"""40    Constructs a VLM image processor. Based on [`CLIPImageProcessor`] with incorporation of additional techniques for processing high resolution images.41    Args:42        anyres: (bool) anyres 기능을 사용할지 안할지43        unpad: (bool) anyres 사용시, unpad 기능 (순수 pad 영역에 해당하는 visual tokens 은 LLM input 에서 제거) 을 사용할지 안할지44        num_queries_vis_abstractor: (int) 각 grid 에 대해서 resampler 를 사용하는 경우, visual query 수45        possible_resolutions: (List) anyres 기능 사용시, 가능한 resolution 조합, 예: [[336, 336], [336, 672], [672, 336]]46        patch_size: (int) ViT patch size47        pad_to_square: (bool) 정사각형으로 padding 을 수행할지, 안할지를 결정. False 이면 정사각형이 아니기 때문에 center crop 을 거쳐 ViT 의 입력으로 들어감48    """49 50    model_input_names = ["pixel_values"]51 52    def __init__(53        self,54        do_resize: bool = True,55        size: Dict[str, int] = None,56        anyres: bool = False,57        unpad: bool = False,58        num_queries_vis_abstractor_image: int = 81,59        num_queries_vis_abstractor_video_slow: int = 81,60        num_queries_vis_abstractor_video_fast: int = 9,61        first_last_frames_slow_video: bool = False,62        possible_resolutions: List = [],63        patch_size: int = 14,64        pad_to_square: bool = True,65        resample: PILImageResampling = PILImageResampling.BICUBIC,66        do_center_crop: bool = True,67        crop_size: Dict[str, int] = None,68        do_rescale: bool = True,69        rescale_factor: Union[int, float] = 1 / 255,70        do_normalize: bool = True,71        image_mean: Optional[Union[float, List[float]]] = None,72        image_std: Optional[Union[float, List[float]]] = None,73        do_convert_rgb: bool = True,74        **kwargs,75    ) -> None:76        super().__init__(**kwargs)77        size = size if size is not None else {"shortest_edge": 336}78        size = get_size_dict(size, default_to_square=False)79        crop_size = crop_size if crop_size is not None else {"height": 336, "width": 336}80        crop_size = get_size_dict(crop_size, default_to_square=True, param_name="crop_size")81 82        self.do_resize = do_resize83        self.size = size84        self.anyres = anyres85        self.unpad = unpad86        self.num_queries_vis_abstractor_image = num_queries_vis_abstractor_image87        self.num_queries_vis_abstractor_video_slow = num_queries_vis_abstractor_video_slow88        self.num_queries_vis_abstractor_video_fast = num_queries_vis_abstractor_video_fast89        self.first_last_frames_slow_video = first_last_frames_slow_video90        self.possible_resolutions = [_resolution for _resolution in possible_resolutions]91        self.patch_size = patch_size92        self.pad_to_square = pad_to_square93        self.resample = resample94        self.do_center_crop = do_center_crop95        self.crop_size = crop_size96        self.do_rescale = do_rescale97        self.rescale_factor = rescale_factor98        self.do_normalize = do_normalize99        self.image_mean = image_mean if image_mean is not None else OPENAI_CLIP_MEAN100        self.image_std = image_std if image_std is not None else OPENAI_CLIP_STD101        self.do_convert_rgb = do_convert_rgb102 103    def resize(104        self,105        image: np.ndarray,106        size: Dict[str, int],107        resample: PILImageResampling = PILImageResampling.BICUBIC,108        data_format: Optional[Union[str, ChannelDimension]] = None,109        input_data_format: Optional[Union[str, ChannelDimension]] = None,110        **kwargs,111    ) -> np.ndarray:112        default_to_square = True113        if "shortest_edge" in size:114            size = size["shortest_edge"]115            default_to_square = False116        elif "height" in size and "width" in size:117            size = (size["height"], size["width"])118        else:119            raise ValueError("Size must contain either 'shortest_edge' or 'height' and 'width'.")120 121        output_size = get_resize_output_image_size(122            image,123            size=size,124            default_to_square=default_to_square,125            input_data_format=input_data_format,126        )127 128        return resize(129            image,130            size=output_size,131            resample=resample,132            data_format=data_format,133            input_data_format=input_data_format,134            **kwargs,135        )136 137    def _preprocess(138        self,139        images: ImageInput,140        do_resize: bool = None,141        size: Dict[str, int] = None,142        resample: PILImageResampling = None,143        do_center_crop: bool = None,144        crop_size: int = None,145        do_rescale: bool = None,146        rescale_factor: float = None,147        do_normalize: bool = None,148        image_mean: Optional[Union[float, List[float]]] = None,149        image_std: Optional[Union[float, List[float]]] = None,150        data_format: Optional[ChannelDimension] = ChannelDimension.FIRST,151        input_data_format: Optional[Union[str, ChannelDimension]] = None,152    ) -> Image.Image:153        images = make_list_of_images(images)154 155        if do_resize:156            images = [157                self.resize(image=image, size=size, resample=resample, input_data_format=input_data_format)158                for image in images159            ]160 161        if do_center_crop:162            images = [163                self.center_crop(image=image, size=crop_size, input_data_format=input_data_format) for image in images164            ]165 166        if do_rescale:167            images = [168                self.rescale(image=image, scale=rescale_factor, input_data_format=input_data_format) for image in images169            ]170 171        if do_normalize:172            images = [173                self.normalize(image=image, mean=image_mean, std=image_std, input_data_format=input_data_format)174                for image in images175            ]176 177        images = [178            to_channel_dimension_format(image, data_format, input_channel_dim=input_data_format) for image in images179        ]180 181        return images182 183    def _resize_for_local_grids(184        self, image: np.array, target_resolution: tuple, resample, input_data_format: ChannelDimension185    ) -> np.array:186        new_height, new_width = _get_local_grids_output_size(image, target_resolution, input_data_format)187 188        # Resize the image189        resized_image = resize(image, (new_height, new_width), resample=resample, input_data_format=input_data_format)190 191        return resized_image192 193    def _pad_for_patching(194        self, image: np.array, target_resolution: tuple, input_data_format: ChannelDimension195    ) -> np.array:196        """197        Pad an image to a target resolution while maintaining aspect ratio.198        """199        target_height, target_width = target_resolution200 201        background_color = tuple(int(x * 255) for x in self.image_mean)202        padded_image = pad(203            image,204            target_size=(target_height, target_width),205            background_color=background_color,206            input_data_format=input_data_format,207        )208 209        return padded_image210 211    def get_image_grids(212        self,213        image: np.array,214        possible_resolutions,215        grid_size: int,216        resample: PILImageResampling,217        data_format: ChannelDimension,218        input_data_format: ChannelDimension,219    ) -> List[np.array]:220        if not isinstance(possible_resolutions, list):221            raise ValueError("possible_resolutions must be a list of possible resolutions.")222 223        image_size = get_image_size(image, channel_dim=input_data_format)224        best_resolution = select_best_resolution(image_size, possible_resolutions)225        resized_image = self._resize_for_local_grids(226            image, best_resolution, resample=resample, input_data_format=input_data_format227        )228        padded_image = self._pad_for_patching(resized_image, best_resolution, input_data_format=input_data_format)229        local_grids = divide_to_grids(padded_image, grid_size=grid_size, input_data_format=input_data_format)230 231        # make sure that all patches are in the input data format232        local_grids = [233            to_channel_dimension_format(grid, channel_dim=data_format, input_channel_dim=input_data_format)234            for grid in local_grids235        ]236 237        return local_grids238 239    def preprocess(240        self,241        images: ImageInput,242        do_resize: bool = None,243        size: Dict[str, int] = None,244        anyres: bool = None,245        unpad: bool = None,246        is_video: bool = False,247        num_queries_vis_abstractor_image: int = None,248        num_queries_vis_abstractor_video_slow: int = None,249        num_queries_vis_abstractor_video_fast: int = None,250        first_last_frames_slow_video: bool = None,251        possible_resolutions: List = None,252        patch_size: int = None,253        pad_to_square: bool = None,254        resample: PILImageResampling = None,255        do_center_crop: bool = None,256        crop_size: int = None,257        do_rescale: bool = None,258        rescale_factor: float = None,259        do_normalize: bool = None,260        image_mean: Optional[Union[float, List[float]]] = None,261        image_std: Optional[Union[float, List[float]]] = None,262        do_convert_rgb: bool = None,263        return_tensors: Optional[Union[str, TensorType]] = None,264        data_format: Optional[ChannelDimension] = ChannelDimension.FIRST,265        input_data_format: Optional[Union[str, ChannelDimension]] = None,266        return_dummy_image: bool = False,267        first_last_frames_slow: bool = False,268        is_first_or_last_frames: bool = False,269        **kwargs,270    ):271        """272        HCXVisionImageProcessor 로 image tensor, original image size (width, height), visual tokens273        :return pixel_values: List of 4D tensor 로 image tensor274        :return image_sizes: List of Dict 로 image width, height [{"width": image 1 의 width, "height": image 1 의 height}, {"width": image 2 의 width, "height": image 2 의 height}, ...]275        :return vision_query_lengths: List of int 로 각 image 가 LLM 입력으로 전달될때 변환되는 visual token 수276        """277 278        do_resize = do_resize if do_resize is not None else self.do_resize279        size = size if size is not None else self.size280        size = get_size_dict(size, param_name="size", default_to_square=False)281        anyres = anyres if anyres is not None else self.anyres282        unpad = unpad if unpad is not None else self.unpad283        num_queries_vis_abstractor_image = (284            num_queries_vis_abstractor_image285            if num_queries_vis_abstractor_image is not None286            else self.num_queries_vis_abstractor_image287        )288        num_queries_vis_abstractor_video_slow = (289            num_queries_vis_abstractor_video_slow290            if num_queries_vis_abstractor_video_slow is not None291            else self.num_queries_vis_abstractor_video_slow292        )293        num_queries_vis_abstractor_video_fast = (294            num_queries_vis_abstractor_video_fast295            if num_queries_vis_abstractor_video_fast is not None296            else self.num_queries_vis_abstractor_video_fast297        )298        first_last_frames_slow_video = (299            first_last_frames_slow_video300            if first_last_frames_slow_video is not None301            else self.first_last_frames_slow_video302        )303        possible_resolutions = possible_resolutions if possible_resolutions is not None else self.possible_resolutions304        patch_size = patch_size if patch_size is not None else self.patch_size305        pad_to_square = pad_to_square if pad_to_square is not None else self.pad_to_square306        resample = resample if resample is not None else self.resample307        do_center_crop = do_center_crop if do_center_crop is not None else self.do_center_crop308        crop_size = crop_size if crop_size is not None else self.crop_size309        crop_size = get_size_dict(crop_size, param_name="crop_size", default_to_square=True)310        do_rescale = do_rescale if do_rescale is not None else self.do_rescale311        rescale_factor = rescale_factor if rescale_factor is not None else self.rescale_factor312        do_normalize = do_normalize if do_normalize is not None else self.do_normalize313        image_mean = image_mean if image_mean is not None else self.image_mean314        image_std = image_std if image_std is not None else self.image_std315        do_convert_rgb = do_convert_rgb if do_convert_rgb is not None else self.do_convert_rgb316 317        if is_video:318            num_queries_vis_abstractor = num_queries_vis_abstractor_video_fast319            num_queries_vis_abstractor_slow = num_queries_vis_abstractor_video_slow320            unpad = False321        else:322            num_queries_vis_abstractor = num_queries_vis_abstractor_image323            num_queries_vis_abstractor_slow = 0324 325        if return_dummy_image:326            images = Image.new("RGB", (224, 224), (0, 0, 0))327 328        images = make_list_of_images(images)329 330        if not valid_images(images):331            raise ValueError(332                "Invalid image type. Must be of type PIL.Image.Image, numpy.ndarray, "333                "torch.Tensor, tf.Tensor or jax.ndarray."334            )335 336        if do_convert_rgb:337            images = [convert_to_rgb(image) for image in images]338 339        # All transformations expect numpy arrays.340        images = [to_numpy_array(image) for image in images]341 342        if is_scaled_image(images[0]) and do_rescale:343            logger.warning_once(344                "It looks like you are trying to rescale already rescaled images. If the input"345                " images have pixel values between 0 and 1, set `do_rescale=False` to avoid rescaling them again."346            )347 348        if input_data_format is None:349            # We assume that all images have the same channel dimension format.350            input_data_format = infer_channel_dimension_format(images[0])351 352        new_images = []353        image_sizes = [get_image_size(image, channel_dim=input_data_format) for image in images]354        vision_query_lengths = []355 356        assert crop_size["height"] == crop_size["width"]357 358        # global image 의 padding 연산은, image original width, height 가 클 때 bottleneck 이 될 수 있음359        # 장축의 길이를 size["shortest_edge"] 로 resize 를 먼저 한 뒤에, padding360        if anyres:361            anyres_global_images = copy.deepcopy(images)362            if pad_to_square:363                background_color = tuple(int(x * 255) for x in self.image_mean)364                anyres_global_images = [365                    resize_longside(copy.deepcopy(image), size["shortest_edge"], resample, input_data_format)366                    for image in anyres_global_images367                ]368                anyres_global_images = [369                    expand2square(image, background_color=background_color, input_data_format=input_data_format)[0]370                    for image in anyres_global_images371                ]372            else:373                anyres_global_images = [374                    self.resize(375                        image=image,376                        size={"height": size["shortest_edge"], "width": size["shortest_edge"]},377                        resample=resample,378                        input_data_format=input_data_format,379                    )380                    for image in anyres_global_images381                ]382        else:383            anyres_global_images = [None for _ in range(len(images))]384            if pad_to_square:385                background_color = tuple(int(x * 255) for x in self.image_mean)386                images = [387                    resize_longside(image, size["shortest_edge"], resample, input_data_format) for image in images388                ]389                images = [390                    expand2square(image, background_color=background_color, input_data_format=input_data_format)[0]391                    for image in images392                ]393 394        for image, anyres_global_image, image_size in zip(images, anyres_global_images, image_sizes):395            if anyres:396                # convert image into a list of grids397                # we intentially use the same data format as the input data format398                image_grids = self.get_image_grids(399                    image,400                    possible_resolutions,401                    grid_size=crop_size["height"],402                    resample=resample,403                    data_format=input_data_format,404                    input_data_format=input_data_format,405                )406                # video 에 대해서는 global image (thumbnail) 를 사용하지 않음407                if not is_video:408                    image_grids = [anyres_global_image] + image_grids409            else:410                image_grids = [image]411 412            pixel_values = self._preprocess(413                image_grids,414                do_resize=do_resize,415                size=size,416                resample=resample,417                do_center_crop=do_center_crop,418                crop_size=crop_size,419                do_rescale=do_rescale,420                rescale_factor=rescale_factor,421                do_normalize=do_normalize,422                image_mean=image_mean,423                image_std=image_std,424                data_format=data_format,425                input_data_format=input_data_format,426            )427 428            pixel_values = np.array(pixel_values)429            new_images.append(pixel_values)430 431            vision_query_length = determine_anyres_num_vision_patches(432                image_size=image_size,433                grid_size=crop_size["height"],434                patch_size=patch_size,435                possible_resolutions=possible_resolutions,436                anyres=anyres,437                unpad=unpad,438                num_queries_vis_abstractor=num_queries_vis_abstractor,439                num_queries_vis_abstractor_slow=num_queries_vis_abstractor_slow,440                is_video=is_video,441                first_last_frames_slow=first_last_frames_slow,442                is_first_or_last_frames=is_first_or_last_frames,443            )444 445            vision_query_lengths.append(vision_query_length)446 447        if return_dummy_image:448            vision_query_lengths = []449 450        data = {451            "pixel_values": [torch.tensor(new_image) for new_image in new_images],452            "image_sizes": [{"width": image_size[1], "height": image_size[0]} for image_size in image_sizes],453            "vision_query_lengths": vision_query_lengths,454        }455 456        return BatchFeature(data=data, tensor_type=return_tensors)457 458    def save_pretrained(459        self,460        save_directory: Union[str, os.PathLike],461        *args,462        **kwargs,463    ):464        self.register_for_auto_class()465        super().save_pretrained(save_directory, *args, **kwargs)466 467 468def determine_anyres_num_vision_patches(469    image_size,470    grid_size,471    patch_size,472    possible_resolutions,473    anyres=False,474    unpad=True,475    num_queries_vis_abstractor=0,476    num_queries_vis_abstractor_slow=0,477    is_video=False,478    first_last_frames_slow=False,  # sample-wise option479    is_first_or_last_frames=False,  # grid-wise option480):481    """482    Computes the number of visual tokens (patches) based on image resolution, grid configuration, and patch size.483 484    This function supports both fixed-size and any-resolution settings, as well as video-specific configurations485    such as handling slow frames and frame position flags.486 487    Args:488        num_grids (int): Number of grids per image (e.g., 1 for 1x1, 4 for 2x2, etc.).489        image_size (tuple): The original image size as (height, width).490        grid_size (int): Size of each grid in pixels (e.g., 336).491        patch_size (int): Size of each vision patch (e.g., 14 for ViT models).492        possible_resolutions (list): List of possible resolution tuples [(h1, w1), (h2, w2), ...].493        anyres (bool, optional): Whether to use any-resolution mode. Defaults to False.494        unpad (bool, optional): Whether to unpad the image before computing patches. Defaults to True.495        num_queries_vis_abstractor (int, optional): Number of query tokens for vision abstractor (fast path).496        num_queries_vis_abstractor_slow (int, optional): Number of query tokens for vision abstractor (slow path).497        is_video (bool, optional): Whether the input is a video. Defaults to False.498        first_last_frames_slow (bool, optional): Whether to treat first/last video frames as "slow". Defaults to False.499        is_first_or_last_frames (bool, optional): Whether current grid corresponds to first/last frame. Defaults to False.500 501    Returns:502        int: Total number of visual tokens (patches) after processing.503    """504 505    if not anyres:506        return num_queries_vis_abstractor if num_queries_vis_abstractor > 0 else (grid_size // patch_size) ** 2507 508    if num_queries_vis_abstractor > 0:509        num_patch_per_grid = int(num_queries_vis_abstractor**0.5)510    else:511        num_patch_per_grid = grid_size // patch_size512 513    num_global_per_grid = num_patch_per_grid514 515    # In anyres mode, a global image is included, so there are always at least 2 grids.516    # However, for video inputs, there is no global image, so it's possible to have only 1 grid.517    # Therefore, the assertion below is commented out:518    # assert num_grids > 1519 520    # Compute the number of vision patches.521    height, width = select_best_resolution(image_size, possible_resolutions)522 523    num_patch_height = (height // grid_size) * num_patch_per_grid524    num_patch_width = (width // grid_size) * num_patch_per_grid525 526    # local images527    if unpad:528        original_height, original_width = image_size529 530        original_aspect_ratio = original_width / original_height531        current_aspect_ratio = num_patch_width / num_patch_height532 533        if original_aspect_ratio > current_aspect_ratio:534            scale_factor = num_patch_width / original_width535            new_height = int(original_height * scale_factor)536            padding = (num_patch_height - new_height) // 2537            num_patch_height = num_patch_height - padding * 2538        else:539            scale_factor = num_patch_height / original_height540            new_width = int(original_width * scale_factor)541            padding = (num_patch_width - new_width) // 2542            num_patch_width = num_patch_width - padding * 2543 544        num_patches = num_patch_width * num_patch_height + num_patch_height545    else:546        num_patches = num_patch_width * num_patch_height547 548    # In the "slow" strategy, when applying to first and last frames only, it is applied exclusively to those two frames.549    if num_queries_vis_abstractor_slow > 0:550        if first_last_frames_slow:551            if is_first_or_last_frames:552                num_patches += num_queries_vis_abstractor_slow - num_queries_vis_abstractor553        else:554            num_patches += num_queries_vis_abstractor_slow - num_queries_vis_abstractor555        # The slowfast feature is only applicable when unpad is set to False.556        assert unpad is False557 558    # Global image is not included for video inputs.559    if not is_video:560        num_patches += num_global_per_grid**2561 562    return num_patches563 564 565def divide_to_grids(image: np.array, grid_size: int, input_data_format=None) -> List[np.array]:566    """567    Divides a local image into grids of size (grid_size x grid_size).568 569    Args:570        image (np.array): Input image as a NumPy array.571        grid_size (int): The size (in pixels) of each square grid.572        input_data_format (optional): Optional format specifier (e.g., "channels_first" or "channels_last").573 574    Returns:575        List[np.array]: A list of image patches, each of size (grid_size x grid_size).576    """577    grids = []578    height, width = get_image_size(image, channel_dim=input_data_format)579    for i in range(0, height, grid_size):580        for j in range(0, width, grid_size):581            if input_data_format == ChannelDimension.LAST:582                grid = image[i : i + grid_size, j : j + grid_size]583            else:584                grid = image[:, i : i + grid_size, j : j + grid_size]585            grids.append(grid)586 587    return grids588 589 590def pad(591    image: np.array,592    target_size: tuple,593    background_color=(127, 127, 127),594    input_data_format=None,595) -> np.array:596    """597    Pads the input image on the sides (top/bottom and left/right) to match the target height and width.598 599    Args:600        image (np.array): Input image as a NumPy array.601        target_size (tuple): Target size as (target_height, target_width).602        background_color (tuple, optional): RGB color value used for padding. Defaults to (127, 127, 127).603        input_data_format (optional): Optional format specifier (e.g., "channels_first" or "channels_last").604 605    Returns:606        np.array: The padded image with the specified target size.607    """608    target_height, target_width = target_size609    height, width = get_image_size(image, channel_dim=input_data_format)610 611    # result = np.ones((target_height, target_width, image.shape[2]), dtype=image.dtype) * background_color612    result = np.empty((target_height, target_width, image.shape[2]), dtype=image.dtype)613    for i in range(image.shape[2]):614        result[..., i].fill(background_color[i])615 616    paste_x = (target_width - width) // 2617    paste_y = (target_height - height) // 2618 619    result[paste_y : paste_y + height, paste_x : paste_x + width, :] = image620 621    return result622 623 624def expand2square(625    image: np.array,626    bboxes_dict=None,627    background_color=(127, 127, 127),628    input_data_format=None,629) -> np.array:630    """631    Expands the input image to a square shape by placing it at the center of a new square canvas,632    with padding added to the shorter side (either top/bottom or left/right).633 634    The image is always centered on the new canvas, and padding is applied symmetrically.635 636    Args:637        image (np.array): Input image as a NumPy array.638        bboxes_dict (dict, optional): A dictionary of bounding boxes, where each value is an NDArray of shape (N, 4, 2)639            with box coordinates in the format [[xtl, ytl], [xtr, ytr], [xbr, ybr], [xbl, ybl]].640            Supports multiple categories (e.g., "ocr", "html") simultaneously.641        background_color (tuple, optional): RGB color to fill the padding area. Defaults to (127, 127, 127).642        input_data_format (optional): Optional format specifier for image data (e.g., "channels_first" or "channels_last").643 644    Returns:645        np.array: A square-shaped image with the original image centered and padded as needed.646 647    Example:648        >>> _img = np.ones((80, 100), dtype=np.uint8) * 100649        >>> _bboxes_dict = {"words": np.array([[[10, 10], [20, 10], [20, 20], [10, 20]],650        ...                                    [[30, 30], [40, 30], [40, 40], [30, 40]]])}651        >>> _img, _bboxes_dict = expand2square(_img, _bboxes_dict, (255, 255, 255))652        >>> _img.shape653        (100, 100)654        >>> guessed_ocr_bboxes = np.array([[[20, 10], [30, 10], [30, 20], [20, 20]],655        ...                                [[40, 30], [50, 30], [50, 40], [40, 40]]])656        >>> np.testing.assert_array_almost_equal(_bboxes_dict["words"], guessed_ocr_bboxes) is None657        True658    """659    height, width = get_image_size(image, channel_dim=input_data_format)660    if width == height:661        return image, bboxes_dict662    elif width > height:663        # result = np.ones((width, width, image.shape[2]), dtype=image.dtype) * background_color664        result = np.empty((width, width, image.shape[2]), dtype=image.dtype)665        for i in range(image.shape[2]):666            result[..., i].fill(background_color[i])667 668        result[(width - height) // 2 : (width - height) // 2 + height, :] = image669        if bboxes_dict is not None:670            for key in bboxes_dict:671                bboxes_dict[key][:, :, 1] += (width - height) // 2672        return result, bboxes_dict673    else:674        # result = np.ones((height, height, image.shape[2]), dtype=image.dtype) * background_color675        result = np.empty((height, height, image.shape[2]), dtype=image.dtype)676        for i in range(image.shape[2]):677            result[..., i].fill(background_color[i])678 679        result[:, (height - width) // 2 : (height - width) // 2 + width] = image680        if bboxes_dict is not None:681            for key in bboxes_dict:682                bboxes_dict[key][:, :, 0] += (height - width) // 2683        return result, bboxes_dict684 685 686def resize_longside(687    image: np.array,688    size: int,689    resample: PILImageResampling = PILImageResampling.BICUBIC,  # type: ignore690    data_format: Optional[Union[str, ChannelDimension]] = None,691    input_data_format: Optional[Union[str, ChannelDimension]] = None,692):693    """694    Resizes the image so that its longer side matches the specified size, maintaining the original aspect ratio.695 696    Args:697        image (np.array): Input image as a NumPy array.698        size (int): Target size for the longer side of the image.699        resample (PILImageResampling, optional): Resampling method to use during resizing. Defaults to BICUBIC.700        data_format (str or ChannelDimension, optional): Output data format (e.g., "channels_first" or "channels_last").701        input_data_format (str or ChannelDimension, optional): Input data format of the image.702 703    Returns:704        np.array: The resized image with its aspect ratio preserved.705    """706    height, width = get_image_size(image, channel_dim=input_data_format)707 708    if width == height:709        target_height, target_width = size, size710    elif width > height:711        target_width = size712        target_height = math.ceil(height / width * size)713    else:714        target_width = math.ceil(width / height * size)715        target_height = size716 717    return resize(718        image,719        size=(target_height, target_width),720        resample=resample,721        data_format=data_format,722        input_data_format=input_data_format,723    )724 725 726def _get_local_grids_output_size(image: np.array, target_resolution: tuple, input_data_format=None):727    """728    Computes the number of local grids (patches) along the height and width when resizing an image729    to the target resolution.730 731    Args:732        image (np.array): Input image as a NumPy array.733        target_resolution (tuple): Target resolution in the format (target_height, target_width).734        input_data_format (optional): Optional format specifier (e.g., "channels_first" or "channels_last").735 736    Returns:737        tuple: A tuple (grid_h, grid_w) representing the number of grids along the height and width.738    """739    original_height, original_width = get_image_size(image, channel_dim=input_data_format)740    target_height, target_width = target_resolution741 742    scale_w = target_width / original_width743    scale_h = target_height / original_height744 745    if scale_w < scale_h:746        new_width = target_width747        new_height = min(math.ceil(original_height * scale_w), target_height)748    else:749        new_height = target_height750        new_width = min(math.ceil(original_width * scale_h), target_width)751 752    return new_height, new_width753 754 755def select_best_resolution(original_size: tuple, possible_resolutions: list) -> tuple:756    """757    Selects the best-fit resolution from a list of possible resolutions based on the original image size.758 759    This function, adapted from LLaVA-Next760    (https://github.com/huggingface/transformers/blob/v4.40.2/src/transformers/models/llava_next/image_processing_llava_next.py),761    evaluates each resolution by computing its effective and wasted area compared to the original size.762    The optimal resolution is the one that maximizes the effective area while minimizing unused (wasted) space.763 764    Args:765        original_size (tuple): The original image size in the format (height, width).766        possible_resolutions (list): A list of candidate resolutions in the format [(height1, width1), (height2, width2), ...].767 768    Returns:769        tuple: The best-fit resolution in the format (height, width).770    """771    original_height, original_width = original_size772    best_fit = None773    max_effective_resolution = 0774    min_wasted_resolution = float("inf")775 776    for height, width in possible_resolutions:777        scale = min(width / original_width, height / original_height)778        downscaled_width, downscaled_height = int(original_width * scale), int(original_height * scale)779        effective_resolution = min(downscaled_width * downscaled_height, original_width * original_height)780        wasted_resolution = (width * height) - effective_resolution781 782        if effective_resolution > max_effective_resolution or (783            effective_resolution == max_effective_resolution and wasted_resolution < min_wasted_resolution784        ):785            max_effective_resolution = effective_resolution786            min_wasted_resolution = wasted_resolution787            best_fit = (height, width)788 789    return best_fit790