CoolFace
Modelpublic

nimo1234/LocateAnything-3B

sourceHugging Faceotherupdated 3mo agoView on Hugging Face
0likes11downloads
processing_locateanything.py678 linesDownload Raw Back to root
1# coding=utf-82# Copyright 2024 The HuggingFace Inc. team.3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8#     http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.15"""16Processor class for LocateAnything.17"""18 19import math20import os21from typing import Iterable, List, Union, Literal22import base6423import sys24import time25import warnings26from functools import lru_cache27from io import BytesIO28import re29import requests30import torch31import torchvision32from packaging import version33from PIL import Image34from torchvision import io35from torchvision import transforms36from torchvision.transforms import InterpolationMode37from typing import Optional, Any38import numpy as np39 40from transformers.feature_extraction_utils import BatchFeature41from transformers.image_utils import ImageInput42try:43    from transformers.image_utils import VideoInput44except ImportError:45    VideoInput = None46from transformers.processing_utils import ProcessingKwargs, ProcessorMixin, Unpack47from transformers.tokenization_utils_base import PreTokenizedInput, TextInput48from transformers.utils import logging49import lmdb50import cv251import pickle52import decord53 54logger = logging.get_logger(__name__)55 56FPS = 2.057MAX_FRAMES = 64 58VIDEO_TOTAL_PIXELS = int(float(os.environ.get('VIDEO_MAX_PIXELS', 32000 * 28 * 28 * 0.9)))59logger.info(f"set VIDEO_TOTAL_PIXELS: {VIDEO_TOTAL_PIXELS}")60 61 62def to_rgb(pil_image: Image.Image) -> Image.Image:63      if pil_image.mode == 'RGBA':64          white_background = Image.new("RGB", pil_image.size, (255, 255, 255))65          white_background.paste(pil_image, mask=pil_image.split()[3])  # Use alpha channel as mask66          return white_background67      else:68          return pil_image.convert("RGB")69 70def read_img_from_lmdb_v2(image_data):71    # special case for AgiBotWorld72    lmdb_file, lmdb_key = image_data['lmdb_file'], image_data['lmdb_key']73    key = lmdb_key.encode('ascii')74    env = lmdb.open(lmdb_file, max_readers=10240, readonly=True, lock=False, readahead=False, meminit=False)75    txn = env.begin()76    value = txn.get(key)77    if value is None:78        print(f"Warning: Key {key} not found.")79        return None80    record = pickle.loads(value)81    image_bgr = cv2.imdecode(np.frombuffer(record['image'], dtype=np.uint8), cv2.IMREAD_COLOR)82    image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)83    image = Image.fromarray(image_rgb)84        85    return image86 87def parse_lmdb_image_data(image_data):88    lmdb_file = image_data['lmdb_file']89    if not os.path.exists(lmdb_file):90        if "/home/zhidingy/workspace/libs/eagle/Eagle2/" in lmdb_file:91            image_data['lmdb_file'] = lmdb_file.replace("/home/zhidingy/workspace/libs/eagle/Eagle2/", "")92        else:93            raise ValueError(f"LMDB file {lmdb_file} does not exist")94    # special case for AgiBotWorld95    if 'AgiBotWorld' in image_data['lmdb_file']:96        return read_img_from_lmdb_v2(image_data)97    98    try:99        env = lmdb.open(image_data['lmdb_file'], readonly=True, lock=False, max_readers=10240)100    except Exception as e:101        print(f"Failed to open lmdb file {image_data['lmdb_file']}. Error message: {e}", flush=True)102        raise e103 104    with env.begin(write=False) as txn:105        try:106            image_bin = txn.get(image_data['lmdb_key'].encode('ascii'))107            buf = BytesIO(image_bin)108        except Exception as e:109            print(f"Failed to get image from lmdb file {image_data['lmdb_file']}. Error message: {e}", flush=True)110            raise e111    try:112        image = Image.open(buf)113    except Exception as e:114        image_np = np.frombuffer(image_bin, dtype=np.uint8)115        image_bgr = cv2.imdecode(image_np, cv2.IMREAD_COLOR)116        image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)117        image = Image.fromarray(image_rgb)118    return image119 120def fetch_image(ele: dict[str, str | Image.Image]) -> Image.Image:121    if "image" in ele:122        image = ele["image"]123    else:124        image = ele["image_url"]125    image_obj = None126    if isinstance(image, Image.Image):127        image_obj = image128    elif isinstance(image, dict) and 'lmdb_file' in image:129        image_obj = parse_lmdb_image_data(image)130    elif image.startswith("http://") or image.startswith("https://"):131        response = requests.get(image, stream=True)132        image_obj = Image.open(BytesIO(response.content))133    elif image.startswith("file://"):134        image_obj = Image.open(image[7:])135    elif image.startswith("data:image"):136        if "base64," in image:137            _, base64_data = image.split("base64,", 1)138            data = base64.b64decode(base64_data)139            image_obj = Image.open(BytesIO(data))140    else:141        image_obj = Image.open(image)142    if image_obj is None:143        raise ValueError(f"Unrecognized image input, support local path, http url, base64 and PIL.Image, got {image}")144    image = to_rgb(image_obj)145 146    return image147 148 149def get_video_frame_indices(150    ele: dict,151    total_frames: int,152    video_fps: int | float,153) -> tuple[torch.Tensor, float]:154    target_fps = ele.get("fps", FPS)155    max_frames = ele.get("max_frames", MAX_FRAMES)156    157    nframes = (total_frames / video_fps) * target_fps158    nframes = int(round(nframes))159    nframes = max(1, nframes)160    161    if nframes > max_frames:162        nframes = max_frames163    164    nframes = min(nframes, total_frames)165    166    if nframes == total_frames:167        idx = torch.arange(total_frames).long()168    else:169        idx = torch.linspace(0, total_frames - 1, nframes).round().long()170        171    sample_fps = nframes / max(total_frames, 1e-6) * video_fps172    173    return idx, sample_fps174 175def _read_video_torchvision(176    ele: dict,177) -> (torch.Tensor, float, list):178    """read video using torchvision.io.read_video and return also per-frame timestamps"""179    video_path = ele["video"]180    if version.parse(torchvision.__version__) < version.parse("0.19.0"):181        if "http://" in video_path or "https://" in video_path:182            warnings.warn("torchvision < 0.19.0 does not support http/https video path, please upgrade to 0.19.0.")183        if "file://" in video_path:184            video_path = video_path[7:]185    st = time.time()186    187    video, audio, info = io.read_video(188        video_path,189        start_pts=ele.get("video_start", 0.0),190        end_pts=ele.get("video_end", None),191        pts_unit="sec",192        output_format="TCHW",193    )194    total_frames, video_fps = video.size(0), info["video_fps"]195    logger.info(f"torchvision:  {video_path=}, {total_frames=}, {video_fps=}, time={time.time() - st:.3f}s")196    197    idx, sample_fps = get_video_frame_indices(ele, total_frames, video_fps)198    199    start_time = ele.get("video_start", 0.0)200    timestamps = (start_time + idx.to(torch.float32) / video_fps).tolist()201    202    video = video[idx]203    return video, sample_fps, timestamps204 205 206def is_decord_available() -> bool:207    import importlib.util208    return importlib.util.find_spec("decord") is not None209 210def _read_video_decord(211    ele: dict,212) -> (torch.Tensor, float, list):213    """read video using decord.VideoReader and return also per-frame timestamps"""214    video_path = ele["video"]215    st = time.time()216    vr = decord.VideoReader(video_path)217    218    total_frames, video_fps = len(vr), vr.get_avg_fps()219    logger.info(f"decord:  {video_path=}, {total_frames=}, {video_fps=}, time={time.time() - st:.3f}s")220    221    idx_tensor, sample_fps = get_video_frame_indices(ele, total_frames, video_fps)222    idx = idx_tensor.tolist()223    224    start_time = ele.get("video_start", 0.0)225    timestamps = [start_time + i / video_fps for i in idx]226    227    video = vr.get_batch(idx).asnumpy()228    video = torch.tensor(video).permute(0, 3, 1, 2)  # Convert to TCHW format229    230    return video, sample_fps, timestamps231 232 233VIDEO_READER_BACKENDS = {234    "decord": _read_video_decord,235    "torchvision": _read_video_torchvision,236}237 238 239@lru_cache(maxsize=1)240def get_video_reader_backend() -> str:241    if is_decord_available():242        video_reader_backend = "decord"243    else:244        video_reader_backend = "torchvision"245    return video_reader_backend246 247 248def fetch_video(ele: dict, return_video_sample_fps: bool = False, video_reader_backend: str = "torchvision") -> torch.Tensor | list[Image.Image]:249    """250    Fetches video, samples frames, resizes based on video_total_pixels, and returns as Tensor (TCHW).251    """252    if isinstance(ele["video"], str):253        video_reader_backend = video_reader_backend if video_reader_backend is not None else get_video_reader_backend()254        try:255            video, sample_fps, timestamps = VIDEO_READER_BACKENDS[video_reader_backend](ele)256        except Exception as e:257            logger.warning(f"video_reader_backend {video_reader_backend} error, use torchvision as default, msg: {e}")258            video, sample_fps, timestamps = VIDEO_READER_BACKENDS["torchvision"](ele)259 260        nframes, _, height, width = video.shape261        262        video_total_pixels = ele.get("video_total_pixels", VIDEO_TOTAL_PIXELS)263        current_pixels = nframes * height * width264        265        if current_pixels > video_total_pixels:266            scale_factor = math.sqrt(video_total_pixels / current_pixels)267            new_height = int(height * scale_factor)268            new_width = int(width * scale_factor)269            270            video = transforms.functional.resize(271                video,272                [new_height, new_width],273                interpolation=InterpolationMode.BICUBIC,274                antialias=True,275            ).float()276        else:277            video = video.float()278 279        if return_video_sample_fps:280            return video, sample_fps, timestamps281        return video282 283    else:284        assert isinstance(ele["video"], (list, tuple))285        process_info = ele.copy()286        process_info.pop("type", None)287        process_info.pop("video", None)288        289        images = [290            fetch_image({"image": video_element, **process_info})291            for video_element in ele["video"]292        ]293        294        nframes = len(images)295        timestamps = [-1 for i in range(nframes)] 296        297        # For list of images, we return list of PIL images directly, 298        # the processor will handle conversion to tensor later.299        if return_video_sample_fps:300            return images, process_info.get("fps", 2.0), timestamps301        return images302 303class LocateAnythingProcessorKwargs(ProcessingKwargs, total=False):304    _defaults = {305        "text_kwargs": {306            "padding": False,307        },308        "images_kwargs": {},309        "videos_kwargs": {},310    }311 312 313class LocateAnythingProcessor(ProcessorMixin):314    attributes = ["image_processor", "tokenizer"]315    valid_kwargs = [316        "chat_template",317        "num_image_tokens",318        "image_token",319        "video_token",320        "images_kwargs",321        "videos_kwargs",322        "text_kwargs",323    ]324    image_processor_class = "AutoImageProcessor" 325    tokenizer_class = "AutoTokenizer"326 327    def __init__(328        self,329        image_processor=None,330        tokenizer=None,331        chat_template=None,332        image_token='<IMG_CONTEXT>',333        video_token='<IMG_CONTEXT>',334        merge_kernel_size=[2, 2], # Note: This might need adjustment based on your patch_size (14*14)335        image_placeholder='image',336        video_placeholder='video',337        image_start_token='<img>',338        image_end_token='</img>',339        **kwargs,340    ):  341        self.image_token = tokenizer.image_token if hasattr(tokenizer, "image_token") else image_token342        self.video_token = tokenizer.video_token if hasattr(tokenizer, "video_token") else video_token343        self.image_token_id = (344            tokenizer.image_token_id345            if getattr(tokenizer, "image_token_id", None)346            else tokenizer.convert_tokens_to_ids(self.image_token)347        )348        self.video_token_id = (349            tokenizer.video_token_id350            if getattr(tokenizer, "video_token_id", None)351            else tokenizer.convert_tokens_to_ids(self.video_token)352        )353        self.image_placeholder = image_placeholder354        self.video_placeholder = video_placeholder355        self.merge_kernel_size = merge_kernel_size356        self.image_start_token = image_start_token357        self.image_end_token = image_end_token358        if 'auto_map' in kwargs:359            self.auto_map = kwargs['auto_map']360        super().__init__(image_processor, tokenizer, chat_template=chat_template)361 362    363    def replace_media_placeholder(self, text, image_list, video_list, timestamps_list, fps_list, **output_kwargs):364 365        num_of_images_in_this_sample = 0366        num_of_videos_in_this_sample = 0367        pattern = re.compile(rf"<({self.image_placeholder}|{self.video_placeholder})-(\d+)>")368        unified_frame_list = []369        370        def replace_in_text(text):371            def repl(match):372                nonlocal unified_frame_list373                nonlocal num_of_images_in_this_sample374                nonlocal num_of_videos_in_this_sample375                media_type = match.group(1)376                idx_in_list = int(match.group(2)) - 1377                idx_mapper = {0: "first", 1: "second", 2: "third", 3: "fourth", 4: "fifth", 5: "sixth", 6: "seventh", 7: "eighth", 8: "ninth", 9: "tenth"}  378                379                if media_type == 'image':380                    # Call LocateAnythingImageProcessor with a single image in a list381                    image_inputs = self.image_processor(images=[image_list[idx_in_list]], **output_kwargs["images_kwargs"])382                    383                    num_of_tokens_list = [int(h * w) // (self.image_processor.merge_kernel_size[0] * self.image_processor.merge_kernel_size[1]) for h, w in image_inputs['image_grid_hws']]384                    385                    special_placeholder = f"<image {idx_in_list+1}>{self.image_start_token}{self.image_token * num_of_tokens_list[0]}{self.image_end_token}"386                    unified_frame_list.append(image_inputs)387                    num_of_images_in_this_sample += 1388                    389                elif media_type == 'video':390                    video_obj = video_list[idx_in_list]391                    392                    # Convert Tensor TCHW to list of PIL Images for the ImageProcessor393                    if isinstance(video_obj, torch.Tensor):394                        # video_obj is [T, C, H, W], float, likely 0-255 or standardized395                        # LocateAnythingImageProcessor expects PIL or 0-255 inputs usually.396                        # We need to convert back to PIL or List[Tensor] compatible with make_list_of_images397                        video_frames = []398                        for i in range(video_obj.shape[0]):399                            frame = video_obj[i] # [C, H, W]400                            # Assuming fetch_video returns float tensors.401                            # If they are 0-255, convert to uint8.402                            if frame.dtype.is_floating_point and frame.max() > 1.0:403                                frame = frame.byte()404                            elif frame.dtype.is_floating_point:405                                frame = (frame * 255).byte()406                            407                            img = transforms.ToPILImage()(frame)408                            video_frames.append(img)409                    elif isinstance(video_obj, list):410                        # Already list of PIL images411                        video_frames = video_obj412                    else:413                        raise ValueError("Unsupported video format")414 415                    # Call ImageProcessor with list of frames416                    video_inputs = self.image_processor(images=video_frames, **output_kwargs["videos_kwargs"])417                    418                    # Calculate tokens per frame419                    num_of_tokens_list = [int(h * w) // (self.image_processor.merge_kernel_size[0] * self.image_processor.merge_kernel_size[1]) for h, w in video_inputs['image_grid_hws']]420                    421                    if timestamps_list is not None and -1 not in timestamps_list:422                        frame_timestamps = timestamps_list[idx_in_list]423                    else:424                        frame_timestamps = None425                    sampled_fps = fps_list[idx_in_list] if fps_list is not None else None426                    427                    if frame_timestamps is not None:428                        # Ensure lengths match (sometimes rounding might cause off-by-one if not careful, but usually safe here)429                        if len(frame_timestamps) != len(num_of_tokens_list):430                             logger.warning(f"Timestamp mismatch: {len(frame_timestamps)} vs {len(num_of_tokens_list)}")431                             min_len = min(len(frame_timestamps), len(num_of_tokens_list))432                             frame_timestamps = frame_timestamps[:min_len]433                             num_of_tokens_list = num_of_tokens_list[:min_len]434 435                        special_placeholder = [f"Frame-{i+1}-{frame_timestamps[i]:.2f}s: {self.image_start_token}{self.image_token * num_of_tokens}{self.image_end_token}" for i, num_of_tokens in enumerate(num_of_tokens_list)]436                    else:437                        special_placeholder = [f"Frame-{i+1}: {self.image_start_token}{self.image_token * num_of_tokens}{self.image_end_token}" for i, num_of_tokens in enumerate(num_of_tokens_list)]438                    439                    if sampled_fps is not None:440                        special_placeholder = f"The {idx_mapper[idx_in_list]} video sampled with {sampled_fps:.2f} fps: " + "".join(special_placeholder)441                    else:442                        special_placeholder = f"The {idx_mapper[idx_in_list]} video: " + "".join(special_placeholder)443                    444                    unified_frame_list.append(video_inputs)445                    num_of_videos_in_this_sample += 1446                else:447                    raise ValueError(f'Unknown media type: {media_type}')448                return special_placeholder449            return pattern.sub(repl, text)450        451        text = replace_in_text(text)452        453        if len(unified_frame_list) > 0:454            # Concatenate all pixel values from all images/videos in this sample455            pixel_values = torch.cat([frame['pixel_values'] for frame in unified_frame_list], dim=0)456            # Concatenate grid hws457            image_grid_hws = np.concatenate([frame['image_grid_hws'] for frame in unified_frame_list], axis=0)458        else:459            pixel_values = torch.empty(0)460            image_grid_hws = np.empty(0)461            462        return text, pixel_values, image_grid_hws, num_of_images_in_this_sample, num_of_videos_in_this_sample463    464    def __call__(465        self,466        images: ImageInput = None,467        text: Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]] = None,468        audio=None,469        videos: VideoInput = None,470        **kwargs: Unpack[LocateAnythingProcessorKwargs],471    ) -> BatchFeature:472        output_kwargs = self._merge_kwargs(473            LocateAnythingProcessorKwargs,474            tokenizer_init_kwargs=self.tokenizer.init_kwargs,475            **kwargs,476        )477        478        if isinstance(text, str):479            text_list = [text]480        elif not isinstance(text, list) and not isinstance(text[0], str):481            raise ValueError("Invalid input text. Please provide a string, or a list of strings")482        elif isinstance(text, list) and isinstance(text[0], str):483            text_list = text484        485        if images is None: images = []486        if videos is None: videos = []487        488        pixel_values_list = []489        image_grid_hws_list = []490        new_sample_list = []491        image_start_idx = 0492        video_start_idx = 0493        timestamps_batch = output_kwargs['videos_kwargs'].pop("timestamps", None)494        fps_batch = output_kwargs['videos_kwargs'].pop("fps", None)495        496        for sample in text_list:497            timestamps_list = timestamps_batch[video_start_idx:] if timestamps_batch is not None else None498            fps_list = fps_batch[video_start_idx:] if fps_batch is not None else None499            500            sample, pixel_values, image_grid_hws, num_of_images_in_this_sample, num_of_videos_in_this_sample = self.replace_media_placeholder(501                sample, images[image_start_idx:], videos[video_start_idx:], timestamps_list, fps_list, **output_kwargs502            )503            new_sample_list.append(sample)504            505            if pixel_values.numel() > 0:506                pixel_values_list.append(pixel_values)507                image_grid_hws_list.append(image_grid_hws)508 509            image_start_idx += num_of_images_in_this_sample510            video_start_idx += num_of_videos_in_this_sample511 512        image_inputs = {}513        if len(pixel_values_list) > 0:514            # Concatenate across the batch515            image_inputs['pixel_values'] = torch.cat(pixel_values_list, dim=0)516            image_inputs['image_grid_hws'] = np.concatenate(image_grid_hws_list, axis=0)517        518        video_inputs = {} # Video data is merged into image_inputs now519        text_inputs = self.tokenizer(new_sample_list, **output_kwargs["text_kwargs"])520        521        return BatchFeature(data={**text_inputs, **image_inputs, **video_inputs})522 523    def batch_decode(self, *args, **kwargs):524        return self.tokenizer.batch_decode(*args, **kwargs)525 526    def decode(self, *args, **kwargs):527        return self.tokenizer.decode(*args, **kwargs)528 529    @property530    def model_input_names(self):531        tokenizer_input_names = self.tokenizer.model_input_names532        image_processor_input_names = self.image_processor.model_input_names533        return list(dict.fromkeys(tokenizer_input_names + image_processor_input_names))534 535    def save_pretrained(self, save_directory, **kwargs):536        if os.path.isfile(save_directory):537            raise ValueError(f"Provided path ({save_directory}) should be a directory, not a file")538        os.makedirs(save_directory, exist_ok=True)539        outputs = super().save_pretrained(save_directory, **kwargs)540        return outputs541 542    @classmethod543    def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):544        processor = super().from_pretrained(pretrained_model_name_or_path, **kwargs)545        if isinstance(processor, tuple):546            processor = processor[0]547        return processor548 549    def process_vision_info(550        self,551        conversations: list[dict] | list[list[dict]],552        return_video_kwargs: bool = False,553        video_reader_backend: str = "torchvision",554    ) -> tuple[list[Image.Image] | None, list[torch.Tensor | list[Image.Image]] | None, Optional[dict]]:555 556        vision_infos = self.extract_vision_info(conversations)557        image_inputs = []558        video_inputs = []559        video_sample_fps_list = []560        video_timestamps_list = []561        562        for vision_info in vision_infos:563            if "image" in vision_info or "image_url" in vision_info:564                image_inputs.append(fetch_image(vision_info))565            elif "video" in vision_info:566                video_input, video_sample_fps, video_timestamps = fetch_video(vision_info, return_video_sample_fps=True, video_reader_backend=video_reader_backend)567                video_sample_fps_list.append(video_sample_fps)568                video_inputs.append(video_input)569                video_timestamps_list.append(video_timestamps)570            else:571                raise ValueError("image, image_url or video should in content.")572        573        if len(image_inputs) == 0:574            image_inputs = None575        if len(video_inputs) == 0:576            video_inputs = None577            578        if return_video_kwargs:579            return image_inputs, video_inputs, {'fps': video_sample_fps_list, 'timestamps': video_timestamps_list}580        return image_inputs, video_inputs581 582    def extract_vision_info(self, conversations: list[dict] | list[list[dict]]) -> list[dict]:583        vision_infos = []584        if isinstance(conversations[0], dict):585            conversations = [conversations]586        for conversation in conversations:587            for message in conversation:588                if isinstance(message["content"], list):589                    for ele in message["content"]:590                        if (591                            "image" in ele592                            or "image_url" in ele593                            or "video" in ele594                            or ele["type"] in ("image", "image_url", "video")595                        ):596                            vision_infos.append(ele)597        return vision_infos598    599    def py_apply_chat_template(self, messages, tokenize=False, add_generation_prompt=False):600        assert tokenize == False, "tokenize is not supported yet"601        result = ""602        image_count = 0603        video_count = 0604        605        message_text = ""606        for idx, message in enumerate(messages):607            if message.get('role') != 'user': continue608            content = message.get('content')609            if isinstance(content, str):610                message_text += content611            elif isinstance(content, list):612                for item in content:613                    if isinstance(item, dict) and "text" in item:614                        message_text += item["text"]615                    elif isinstance(item, str):616                        message_text += item617                        618        for idx, message in enumerate(messages):619            if idx == 0 and message.get('role') != 'system':620                result += "<|im_start|>system\n"621                result += "You are a helpful assistant.\n"622                result += "<|im_end|>\n"623 624            result += f"<|im_start|>{message.get('role', '')}\n"625            content = message.get('content')626 627            if isinstance(content, str):628                result += content629                result += "<|im_end|>\n"630            else:631                for item in content:632                    if (isinstance(item, dict) and (item.get('type') == 'image' or 'image' in item or 'image_url' in item)):633                        image_count += 1634                        candidate_token = f"<image-{image_count}>"635                        if candidate_token not in message_text:636                            result += candidate_token637                    elif (isinstance(item, dict) and (item.get('type') == 'video' or 'video' in item)):638                        video_count += 1639                        candidate_token = f"<video-{video_count}>"640                        if candidate_token not in message_text:641                            result += candidate_token642                    elif isinstance(item, dict) and 'text' in item:643                        result += item['text']644                    elif isinstance(item, str):645                        result += item646                result += "<|im_end|>\n"647 648        if add_generation_prompt:649            result += "<|im_start|>assistant\n"650 651        return result652 653 654    @classmethod655    def from_args_and_dict(cls, args, processor_dict: dict[str, Any], **kwargs):656        processor_dict = processor_dict.copy()657        return_unused_kwargs = kwargs.pop("return_unused_kwargs", False)658 659        if "processor_class" in processor_dict:660            del processor_dict["processor_class"]661 662        unused_kwargs = cls.validate_init_kwargs(processor_config=processor_dict, valid_kwargs=cls.valid_kwargs)663        processor = cls(*args, **processor_dict)664 665        for key in set(kwargs.keys()):666            if hasattr(processor, key):667                setattr(processor, key, kwargs.pop(key))668 669        if isinstance(unused_kwargs, dict):670            kwargs.update(unused_kwargs)671        logger.info(f"Processor {processor}")672        if return_unused_kwargs:673            return processor, kwargs674        else:675            return processor676        677        678__all__ = ["LocateAnythingProcessor"]