mirror of
https://github.com/modelscope/modelscope.git
synced 2026-09-03 12:42:03 +02:00
811 lines
30 KiB
Python
811 lines
30 KiB
Python
# Copyright (c) Alibaba, Inc. and its affiliates.
|
|
|
|
import os.path as osp
|
|
import re
|
|
from io import BytesIO
|
|
from typing import Any, Dict, List, Tuple, Union
|
|
|
|
import decord
|
|
import json
|
|
import numpy as np
|
|
import torch
|
|
from PIL import Image
|
|
from timm.data import create_transform
|
|
from torchvision import transforms
|
|
from torchvision.datasets import ImageFolder
|
|
from torchvision.transforms import Compose, Normalize, Resize, ToTensor
|
|
|
|
from modelscope.hub.snapshot_download import snapshot_download
|
|
from modelscope.metainfo import Preprocessors
|
|
from modelscope.pipelines.base import Input
|
|
from modelscope.pipelines.cv.cmdssl_video_embedding_pipeline import (
|
|
VCenterCrop, VCompose, VNormalize, VRescale, VToTensor)
|
|
from modelscope.preprocessors import load_image
|
|
from modelscope.utils.config import Config
|
|
from modelscope.utils.constant import (Fields, Invoke, ModeKeys, ModelFile,
|
|
Tasks)
|
|
from .base import Preprocessor
|
|
from .builder import PREPROCESSORS
|
|
from .ofa import * # noqa
|
|
from .ofa.utils.collate import collate_fn
|
|
from .ofa.utils.constant import OFA_TASK_KEY_MAPPING
|
|
|
|
__all__ = [
|
|
'DiffusionImageGenerationPreprocessor', 'OfaPreprocessor',
|
|
'MPlugPreprocessor', 'HiTeAPreprocessor', 'MplugOwlPreprocessor'
|
|
]
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal,
|
|
module_name=Preprocessors.diffusion_image_generation_preprocessor)
|
|
class DiffusionImageGenerationPreprocessor(Preprocessor):
|
|
""" Preprocessor the data with the combination of image and text.
|
|
Args:
|
|
data: process the value as an image for keys ending with 'FILE'
|
|
or existing in preprocessor_image_keys and pass-through the values of other keys.
|
|
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.preprocessor_resolution = kwargs.pop('resolution', 512)
|
|
self.preprocessor_mean = kwargs.pop('mean', [0.5])
|
|
self.preprocessor_std = kwargs.pop('std', [0.5])
|
|
self.preprocessor_image_keys = set(kwargs.pop('image_keys', []))
|
|
self.center_crop = kwargs.pop('center_crop', True)
|
|
|
|
self.transform_input = transforms.Compose([
|
|
transforms.Resize(
|
|
self.preprocessor_resolution,
|
|
interpolation=transforms.InterpolationMode.BILINEAR),
|
|
transforms.CenterCrop(self.preprocessor_resolution)
|
|
if self.center_crop else transforms.RandomCrop(
|
|
self.preprocessor_resolution),
|
|
transforms.ToTensor(),
|
|
transforms.Normalize(self.preprocessor_mean,
|
|
self.preprocessor_std),
|
|
])
|
|
|
|
def __call__(self, data) -> Dict[str, Any]:
|
|
results = {}
|
|
for key, value in data.items():
|
|
if key.endswith(':FILE') or key in self.preprocessor_image_keys:
|
|
image = load_image(value)
|
|
img = self.transform_input(image)
|
|
results[key.replace(':FILE', '').lower()] = img
|
|
else:
|
|
results[key.lower()] = value if value else ''
|
|
return results
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal, module_name=Preprocessors.ofa_tasks_preprocessor)
|
|
class OfaPreprocessor(Preprocessor):
|
|
|
|
def __init__(self,
|
|
model_dir: str,
|
|
mode=ModeKeys.INFERENCE,
|
|
*args,
|
|
**kwargs):
|
|
"""preprocess the data
|
|
|
|
Args:
|
|
model_dir (str): model path
|
|
mode: preprocessor mode (model mode)
|
|
"""
|
|
super().__init__(*args, **kwargs)
|
|
preprocess_mapping = {
|
|
Tasks.ocr_recognition: OfaOcrRecognitionPreprocessor,
|
|
Tasks.image_captioning: OfaImageCaptioningPreprocessor,
|
|
Tasks.visual_grounding: OfaVisualGroundingPreprocessor,
|
|
Tasks.visual_question_answering:
|
|
OfaVisualQuestionAnsweringPreprocessor,
|
|
Tasks.visual_entailment: OfaVisualEntailmentPreprocessor,
|
|
Tasks.image_classification: OfaImageClassificationPreprocessor,
|
|
Tasks.text_classification: OfaTextClassificationPreprocessor,
|
|
Tasks.text_summarization: OfaSummarizationPreprocessor,
|
|
Tasks.text_to_image_synthesis: OfaTextToImageSynthesisPreprocessor,
|
|
Tasks.auto_speech_recognition: OfaASRPreprocessor,
|
|
Tasks.sudoku: OfaSudokuPreprocessor,
|
|
Tasks.text2sql: OfaTextToSqlPreprocessor
|
|
}
|
|
model_dir = model_dir if osp.exists(model_dir) else snapshot_download(
|
|
model_dir, user_agent={Invoke.KEY: Invoke.PREPROCESSOR})
|
|
self.cfg = Config.from_file(
|
|
osp.join(model_dir, ModelFile.CONFIGURATION))
|
|
self.preprocess = preprocess_mapping[self.cfg.task](
|
|
cfg=self.cfg, model_dir=model_dir, mode=mode)
|
|
self.keys = OFA_TASK_KEY_MAPPING[self.cfg.task]
|
|
self.tokenizer = self.preprocess.tokenizer
|
|
if kwargs.get('no_collate', None):
|
|
self.no_collate = True
|
|
else:
|
|
self.no_collate = False
|
|
|
|
# just for modelscope demo
|
|
def _build_dict(self, input: Union[Input, List[Input]]) -> Dict[str, Any]:
|
|
data = dict()
|
|
if not isinstance(input, tuple) and not isinstance(input, list):
|
|
input = (input, )
|
|
for key, item in zip(self.keys, input):
|
|
data[key] = item
|
|
return data
|
|
|
|
def _ofa_input_compatibility_conversion(self, data): # fake
|
|
if 'image' in data and self.cfg.model.get('type', None) == 'ofa':
|
|
if isinstance(data['image'], str):
|
|
image = load_image(data['image'])
|
|
else:
|
|
image = data['image']
|
|
if image.mode != 'RGB':
|
|
image = image.convert('RGB')
|
|
img_buffer = BytesIO()
|
|
image.save(img_buffer, format='JPEG')
|
|
data['image'] = Image.open(img_buffer)
|
|
return data
|
|
|
|
def __call__(self, input: Union[str, tuple, Dict[str, Any]], *args,
|
|
**kwargs) -> Dict[str, Any]:
|
|
if isinstance(input, dict):
|
|
data = input
|
|
else:
|
|
data = self._build_dict(input)
|
|
sample = self.preprocess(data)
|
|
str_data = dict()
|
|
for k, v in data.items():
|
|
str_data[k] = str(v)
|
|
sample['sample'] = str_data
|
|
if self.no_collate:
|
|
return sample
|
|
else:
|
|
return collate_fn([sample],
|
|
pad_idx=self.tokenizer.pad_token_id,
|
|
eos_idx=self.tokenizer.eos_token_id)
|
|
|
|
|
|
def _convert_to_rgb(image):
|
|
return image.convert('RGB')
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal, module_name=Preprocessors.clip_preprocessor)
|
|
class CLIPPreprocessor(Preprocessor):
|
|
|
|
def __init__(self,
|
|
model_dir: str,
|
|
mode=ModeKeys.INFERENCE,
|
|
*args,
|
|
**kwargs):
|
|
"""preprocess the data
|
|
|
|
Args:
|
|
model_dir (str): model path
|
|
mode: preprocessor mode (model mode)
|
|
"""
|
|
super().__init__(*args, **kwargs)
|
|
model_dir = model_dir if osp.exists(model_dir) else snapshot_download(
|
|
model_dir, user_agent={Invoke.KEY: Invoke.PREPROCESSOR})
|
|
self.mode = mode
|
|
# text tokenizer
|
|
from modelscope.models.multi_modal.clip.bert_tokenizer import FullTokenizer
|
|
if 'tokenizer' in kwargs and isinstance(kwargs['tokenizer'],
|
|
FullTokenizer):
|
|
self.tokenizer = kwargs['tokenizer']
|
|
else:
|
|
vocab_file = f'{model_dir}/{ModelFile.VOCAB_FILE}'
|
|
self.tokenizer = FullTokenizer(vocab_file=vocab_file)
|
|
# image preprocessor
|
|
if 'resolution' in kwargs and isinstance(kwargs['resolution'], int):
|
|
self.image_resolution = kwargs['resolution']
|
|
else:
|
|
self.image_resolution = json.load(
|
|
open(
|
|
'{}/vision_model_config.json'.format(model_dir),
|
|
encoding='utf-8'))['image_resolution']
|
|
self.img_preprocess = self._build_image_transform()
|
|
# key mapping
|
|
# specify the input keys, compatible with training and inference whose key names may be different
|
|
self.input_keys = {'img': 'img', 'text': 'text'}
|
|
|
|
def _build_image_transform(self):
|
|
|
|
if self.mode == ModeKeys.TRAIN:
|
|
transform = create_transform(
|
|
input_size=self.image_resolution,
|
|
scale=(0.9, 1.0),
|
|
is_training=True,
|
|
color_jitter=None,
|
|
auto_augment='original',
|
|
interpolation='bicubic',
|
|
mean=(0.48145466, 0.4578275, 0.40821073),
|
|
std=(0.26862954, 0.26130258, 0.27577711),
|
|
)
|
|
transform = Compose(transform.transforms[:-3] + [_convert_to_rgb]
|
|
+ transform.transforms[-3:])
|
|
else:
|
|
transform = Compose([
|
|
Resize((self.image_resolution, self.image_resolution),
|
|
interpolation=Image.BICUBIC),
|
|
_convert_to_rgb,
|
|
ToTensor(),
|
|
Normalize((0.48145466, 0.4578275, 0.40821073),
|
|
(0.26862954, 0.26130258, 0.27577711)),
|
|
])
|
|
return transform
|
|
|
|
def tokenize(self,
|
|
texts: Union[str, List[str]],
|
|
context_length: int = 52) -> torch.LongTensor:
|
|
"""
|
|
Returns the tokenized representation of given input string(s)
|
|
Parameters
|
|
----------
|
|
texts : Union[str, List[str]]
|
|
An input string or a list of input strings to tokenize
|
|
context_length : int
|
|
The context length to use; all baseline models use 24 as the context length
|
|
Returns
|
|
-------
|
|
A two-dimensional tensor containing the resulting tokens, shape = [number of input strings, context_length]
|
|
"""
|
|
if isinstance(texts, str):
|
|
texts = [texts]
|
|
|
|
all_tokens = []
|
|
for text in texts:
|
|
all_tokens.append(
|
|
[self.tokenizer.vocab['[CLS]']]
|
|
+ self.tokenizer.convert_tokens_to_ids(
|
|
self.tokenizer.tokenize(text))[:context_length - 2]
|
|
+ [self.tokenizer.vocab['[SEP]']])
|
|
|
|
result = torch.zeros(len(all_tokens), context_length, dtype=torch.long)
|
|
|
|
for i, tokens in enumerate(all_tokens):
|
|
assert len(tokens) <= context_length
|
|
result[i, :len(tokens)] = torch.tensor(tokens)
|
|
|
|
return result
|
|
|
|
def set_input_img_key(self, new_key: str):
|
|
self.input_keys['img'] = new_key
|
|
|
|
def set_input_text_key(self, new_key: str):
|
|
self.input_keys['text'] = new_key
|
|
|
|
def __call__(self, input: Union[str, tuple, Dict[str, Any]], *args,
|
|
**kwargs) -> Dict[str, Any]:
|
|
output = {}
|
|
# preprocess the image input
|
|
input_img_key = self.input_keys['img']
|
|
if input_img_key in input and input[input_img_key] is not None:
|
|
image_input = input[input_img_key]
|
|
|
|
# single image input
|
|
if isinstance(image_input, Image.Image):
|
|
image_tensor = self.img_preprocess(image_input).unsqueeze(0)
|
|
# multi images input
|
|
elif isinstance(image_input, list):
|
|
if all([isinstance(elem, Image.Image)
|
|
for elem in image_input]):
|
|
image_tensor = torch.stack(
|
|
[self.img_preprocess(elem)
|
|
for elem in image_input], # noqa
|
|
dim=0) # noqa
|
|
else:
|
|
unsupported_elem_type = [
|
|
type(elem) for elem in image_input
|
|
if not isinstance(elem, Image.Image)
|
|
][0]
|
|
raise TypeError(
|
|
f'img should be PIL.Image or List[PIL.Image], \
|
|
but got a List containing one {unsupported_elem_type}'
|
|
)
|
|
# others
|
|
else:
|
|
raise TypeError(
|
|
f'img should be PIL.Image or List[PIL.Image], but got {type(image_input)}'
|
|
)
|
|
output['img'] = image_tensor
|
|
|
|
# preprocess the text input
|
|
input_text_key = self.input_keys['text']
|
|
if input_text_key in input and input[input_text_key] is not None:
|
|
text_input = input[input_text_key]
|
|
|
|
# single text input
|
|
if isinstance(text_input, str):
|
|
text_tensor = self.tokenize(text_input)
|
|
# multi texts input
|
|
elif isinstance(text_input, list):
|
|
if all([isinstance(elem, str) for elem in text_input]):
|
|
text_tensor = self.tokenize(text_input)
|
|
else:
|
|
unsupported_elem_type = [
|
|
type(elem) for elem in text_input
|
|
if not isinstance(elem, str)
|
|
][0]
|
|
raise TypeError(
|
|
f'text should be str or List[str], but got a List containing one {unsupported_elem_type}'
|
|
)
|
|
# others
|
|
else:
|
|
raise TypeError(
|
|
f'text should be str or List[str], but got {type(text_input)}'
|
|
)
|
|
output['text'] = text_tensor
|
|
|
|
return output
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal, module_name=Preprocessors.mplug_tasks_preprocessor)
|
|
class MPlugPreprocessor(Preprocessor):
|
|
|
|
def __init__(self,
|
|
model_dir: str,
|
|
mode: str = ModeKeys.INFERENCE,
|
|
tokenizer_max_length: int = 25,
|
|
*args,
|
|
**kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.model_dir = model_dir
|
|
self.mode = mode
|
|
self.tokenizer_max_length = tokenizer_max_length
|
|
|
|
self._tokenizer = None
|
|
self._patch_resize_transform = None
|
|
self._image_map = {}
|
|
|
|
@property
|
|
def tokenizer(self):
|
|
from transformers import BertTokenizer
|
|
|
|
if self._tokenizer is None:
|
|
self._tokenizer = BertTokenizer.from_pretrained(self.model_dir)
|
|
return self._tokenizer
|
|
|
|
@property
|
|
def patch_resize_transform(self):
|
|
if self._patch_resize_transform is None:
|
|
from torchvision import transforms
|
|
from modelscope.models.multi_modal.mplug import CONFIG_NAME, MPlugConfig
|
|
|
|
config = MPlugConfig.from_yaml_file(
|
|
osp.join(self.model_dir, CONFIG_NAME))
|
|
|
|
mean = (0.48145466, 0.4578275, 0.40821073)
|
|
std = (0.26862954, 0.26130258, 0.27577711)
|
|
|
|
self._patch_resize_transform = transforms.Compose([
|
|
transforms.Resize((config.image_res, config.image_res),
|
|
interpolation=Image.BICUBIC),
|
|
transforms.ToTensor(),
|
|
transforms.Normalize(mean=mean, std=std),
|
|
])
|
|
return self._patch_resize_transform
|
|
|
|
def image_open(self, path: str) -> Tuple[Image.Image, int]:
|
|
if path not in self._image_map:
|
|
index = len(self._image_map)
|
|
self._image_map[path] = (load_image(path), index)
|
|
return self._image_map[path]
|
|
|
|
def __call__(
|
|
self, data: Union[Image.Image, tuple,
|
|
Dict[str, Any]]) -> Dict[str, Any]:
|
|
self.cfg = Config.from_file(
|
|
osp.join(self.model_dir, ModelFile.CONFIGURATION))
|
|
|
|
if isinstance(data, (Image.Image, str)):
|
|
image = data
|
|
elif isinstance(data, tuple):
|
|
image = data[0]
|
|
else:
|
|
image = data['image']
|
|
index = 0
|
|
if isinstance(image, str):
|
|
image, index = self.image_open(image)
|
|
image = image.convert('RGB')
|
|
image = self.patch_resize_transform(image)
|
|
question = '' if self.cfg.task == Tasks.image_captioning \
|
|
else data[1 if isinstance(data, tuple)
|
|
else ('text' if 'text' in data else 'question')]
|
|
question = self.tokenizer(
|
|
question.lower(),
|
|
padding='max_length',
|
|
truncation=True,
|
|
max_length=self.tokenizer_max_length,
|
|
return_tensors='pt')
|
|
|
|
if self.mode == ModeKeys.INFERENCE:
|
|
image = torch.stack([image], dim=0)
|
|
return {'image': image, 'question': question}
|
|
else:
|
|
answer = data['answer']
|
|
answer = self.tokenizer(
|
|
answer,
|
|
padding='max_length',
|
|
truncation=True,
|
|
max_length=self.tokenizer_max_length,
|
|
return_tensors='pt')
|
|
output = {
|
|
'image': image,
|
|
'question_input_ids': question.input_ids.squeeze(),
|
|
'question_attention_mask': question.attention_mask.squeeze(),
|
|
'answer_input_ids': answer.input_ids.squeeze(),
|
|
'answer_attention_mask': answer.attention_mask.squeeze(),
|
|
}
|
|
if self.cfg.task == Tasks.image_text_retrieval:
|
|
output['index'] = index
|
|
return output
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal, module_name=Preprocessors.vldoc_preprocessor)
|
|
class VLDocPreprocessor(Preprocessor):
|
|
|
|
def __init__(self,
|
|
model_dir: str,
|
|
mode: str = ModeKeys.INFERENCE,
|
|
*args,
|
|
**kwargs):
|
|
"""Preprocess data for the model `VLDocForDocVLEmbedding`.
|
|
|
|
Args:
|
|
model_dir (str): model path in model hub.
|
|
mode (str): model mode, in ('train', 'eval', 'inference').
|
|
"""
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.model_dir = model_dir
|
|
self.mode = mode
|
|
|
|
model_cfg_path = osp.join(model_dir, 'config.json')
|
|
with open(model_cfg_path, 'r', encoding='utf-8') as f:
|
|
model_cfg = json.load(f)
|
|
|
|
from modelscope.models.multi_modal.vldoc.tokenization import VLDocXLMTokenizer
|
|
tokenizer_path = osp.join(model_dir, ModelFile.TOKENIZER_FOLDER)
|
|
self.tokenizer = VLDocXLMTokenizer.from_pretrained(tokenizer_path)
|
|
|
|
from modelscope.models.multi_modal.vldoc.processing import Processor, ImageProcessor
|
|
self.img_proc = ImageProcessor(
|
|
do_preprocess=True,
|
|
do_resize=True,
|
|
image_size={
|
|
'height': model_cfg['image_size'][0],
|
|
'width': model_cfg['image_size'][1],
|
|
},
|
|
do_normalize=True,
|
|
apply_ocr=False)
|
|
self.proc = Processor(
|
|
max_seq_length=model_cfg['max_seq_length'],
|
|
max_block_num=model_cfg['max_block_num'],
|
|
img_processor=self.img_proc,
|
|
tokenizer=self.tokenizer,
|
|
width=model_cfg['image_size'][1],
|
|
height=model_cfg['image_size'][0],
|
|
)
|
|
|
|
def __call__(self, input: Dict[str, Any], *args,
|
|
**kwargs) -> Dict[str, Any]:
|
|
"""
|
|
Args:
|
|
input: {
|
|
'images': ['img_path1', 'img_path2', ...],
|
|
'ocr_info_paths': ['json_path1', 'json_path2', ...]
|
|
}
|
|
Return:
|
|
encodings: Dict[str, Tensor]
|
|
"""
|
|
|
|
ocr_infos = []
|
|
for one_ocr_info_path in input['ocr_info_paths']:
|
|
with open(one_ocr_info_path, 'r') as f:
|
|
ocr_info = json.load(f)
|
|
ocr_info = ocr_info['form']
|
|
ocr_infos.append(ocr_info)
|
|
|
|
proc_input = {'images': input['images'], 'ocr_infos': ocr_infos}
|
|
encodings = self.proc(**proc_input)
|
|
|
|
return encodings
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal, module_name=Preprocessors.hitea_tasks_preprocessor)
|
|
class HiTeAPreprocessor(Preprocessor):
|
|
|
|
def __init__(self,
|
|
model_dir: str,
|
|
mode: str = ModeKeys.INFERENCE,
|
|
tokenizer_max_length: int = 25,
|
|
*args,
|
|
**kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.model_dir = model_dir
|
|
self.mode = mode
|
|
self.tokenizer_max_length = tokenizer_max_length
|
|
|
|
self._tokenizer = None
|
|
self._patch_resize_transform = None
|
|
self._num_frames = None
|
|
self._video_map = {}
|
|
|
|
@property
|
|
def tokenizer(self):
|
|
from transformers import BertTokenizer
|
|
|
|
if self._tokenizer is None:
|
|
self._tokenizer = BertTokenizer.from_pretrained(self.model_dir)
|
|
return self._tokenizer
|
|
|
|
@property
|
|
def patch_resize_transform(self):
|
|
if self._patch_resize_transform is None:
|
|
from torchvision import transforms
|
|
from modelscope.models.multi_modal.mplug import CONFIG_NAME, HiTeAConfig
|
|
|
|
config = HiTeAConfig.from_yaml_file(
|
|
osp.join(self.model_dir, CONFIG_NAME))
|
|
|
|
mean = (0.48145466, 0.4578275, 0.40821073)
|
|
std = (0.26862954, 0.26130258, 0.27577711)
|
|
|
|
self._patch_resize_transform = transforms.Compose([
|
|
transforms.Resize((config.image_res, config.image_res),
|
|
interpolation=Image.BICUBIC),
|
|
transforms.ToTensor(),
|
|
transforms.Normalize(mean=mean, std=std),
|
|
])
|
|
return self._patch_resize_transform
|
|
|
|
@property
|
|
def num_frames(self):
|
|
if self._num_frames is None:
|
|
from torchvision import transforms
|
|
from modelscope.models.multi_modal.mplug import CONFIG_NAME, HiTeAConfig
|
|
|
|
config = HiTeAConfig.from_yaml_file(
|
|
osp.join(self.model_dir, CONFIG_NAME))
|
|
|
|
self._num_frames = config.num_frames
|
|
return self._num_frames
|
|
|
|
def video_open(self, path: str) -> Tuple[decord.VideoReader, int]:
|
|
if path not in self._video_map:
|
|
index = len(self._video_map)
|
|
vr = decord.VideoReader(path, ctx=decord.cpu(0))
|
|
self._video_map[path] = (vr, index)
|
|
return self._video_map[path]
|
|
|
|
def sample_frames(self, num_frames: int, vlen: int) -> List[int]:
|
|
acc_samples = min(num_frames, vlen)
|
|
# split the video into `acc_samples` intervals, and sample from each interval.
|
|
intervals = np.linspace(
|
|
start=0, stop=vlen, num=acc_samples + 1).astype(int)
|
|
ranges = []
|
|
for idx, interv in enumerate(intervals[:-1]):
|
|
ranges.append((interv, intervals[idx + 1] - 1))
|
|
|
|
frame_indices = [(x[0] + x[1]) // 2 for x in ranges]
|
|
|
|
if len(frame_indices) < num_frames: # padded with last frame
|
|
padded_frame_indices = [frame_indices[-1]] * num_frames
|
|
padded_frame_indices[:len(frame_indices)] = frame_indices
|
|
frame_indices = padded_frame_indices
|
|
return frame_indices
|
|
|
|
def __call__(
|
|
self, data: Union[decord.VideoReader, tuple,
|
|
Dict[str, Any]]) -> Dict[str, Any]:
|
|
self.cfg = Config.from_file(
|
|
osp.join(self.model_dir, ModelFile.CONFIGURATION))
|
|
|
|
if isinstance(data, (decord.VideoReader, str)):
|
|
video = data
|
|
elif isinstance(data, tuple):
|
|
video = data[0]
|
|
else:
|
|
video = data['video']
|
|
index = 0
|
|
if isinstance(video, str):
|
|
video, index = self.video_open(video)
|
|
frame_indices = self.sample_frames(self.num_frames, len(video))
|
|
video.seek(0)
|
|
video = torch.from_numpy(video.get_batch(frame_indices).asnumpy())
|
|
video = [
|
|
self.patch_resize_transform(Image.fromarray(f))
|
|
for f in video.numpy()
|
|
]
|
|
video = torch.stack(video, dim=0)
|
|
question = '' if self.cfg.task == Tasks.video_captioning \
|
|
else data[1 if isinstance(data, tuple)
|
|
else ('text' if 'text' in data else 'question')]
|
|
question = self.tokenizer(
|
|
question.lower(),
|
|
padding='max_length',
|
|
truncation=True,
|
|
max_length=self.tokenizer_max_length,
|
|
return_tensors='pt')
|
|
|
|
if self.mode == ModeKeys.INFERENCE:
|
|
video = torch.stack([video], dim=0)
|
|
return {'video': video, 'question': question}
|
|
else:
|
|
answer = data['answer']
|
|
answer = self.tokenizer(
|
|
answer,
|
|
padding='max_length',
|
|
truncation=True,
|
|
max_length=self.tokenizer_max_length,
|
|
return_tensors='pt')
|
|
output = {
|
|
'video': video,
|
|
'question_input_ids': question.input_ids.squeeze(),
|
|
'question_attention_mask': question.attention_mask.squeeze(),
|
|
'answer_input_ids': answer.input_ids.squeeze(),
|
|
'answer_attention_mask': answer.attention_mask.squeeze(),
|
|
}
|
|
return output
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal, module_name=Preprocessors.mplug_owl_preprocessor)
|
|
class MplugOwlPreprocessor(Preprocessor):
|
|
|
|
def __init__(self,
|
|
model_dir: str,
|
|
mode: str = ModeKeys.INFERENCE,
|
|
*args,
|
|
**kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.model_dir = model_dir
|
|
self.mode = mode
|
|
|
|
self._tokenizer = None
|
|
self._patch_resize_transform = None
|
|
self.media_token = {'<|image|>': 65}
|
|
self._image_map = {}
|
|
|
|
@property
|
|
def tokenizer(self):
|
|
from modelscope.models.nlp.llama import LlamaTokenizer
|
|
|
|
if self._tokenizer is None:
|
|
self._tokenizer = LlamaTokenizer.from_pretrained(self.model_dir)
|
|
return self._tokenizer
|
|
|
|
@property
|
|
def patch_resize_transform(self):
|
|
if self._patch_resize_transform is None:
|
|
from torchvision import transforms
|
|
|
|
mean = (0.48145466, 0.4578275, 0.40821073)
|
|
std = (0.26862954, 0.26130258, 0.27577711)
|
|
|
|
self._patch_resize_transform = transforms.Compose([
|
|
transforms.Resize((224, 224), interpolation=Image.BICUBIC),
|
|
transforms.ToTensor(),
|
|
transforms.Normalize(mean=mean, std=std),
|
|
])
|
|
return self._patch_resize_transform
|
|
|
|
def image_open(self, path: str) -> Tuple[Image.Image, int]:
|
|
if path not in self._image_map:
|
|
index = len(self._image_map)
|
|
self._image_map[path] = (load_image(path), index)
|
|
return self._image_map[path]
|
|
|
|
def tokenize_text(self, text: str) -> List[int]:
|
|
media_tokens = {
|
|
k: -int(i + 1)
|
|
for i, k in enumerate(self.media_token.keys())
|
|
}
|
|
media_lengths = self.media_token.copy()
|
|
|
|
prompt_chunk = [self.tokenizer.bos_token_id]
|
|
|
|
# Pure Text
|
|
condition = [
|
|
media_token not in text for media_token in media_tokens.keys()
|
|
]
|
|
if all(condition):
|
|
enc_chunk = prompt_chunk + \
|
|
self.tokenizer(text, add_special_tokens=False)['input_ids']
|
|
|
|
# Multi-Modal Text
|
|
else:
|
|
enc_chunk = prompt_chunk
|
|
pattern = '|'.join(map(re.escape, list(media_tokens.keys())))
|
|
chunk_strs = re.split(f'({pattern})', text)
|
|
chunk_strs = [x for x in chunk_strs if len(x) > 0]
|
|
for idx, chunk_str in enumerate(chunk_strs):
|
|
if chunk_str in media_tokens:
|
|
enc_chunk += [media_tokens[chunk_str]] * \
|
|
media_lengths[chunk_str]
|
|
else:
|
|
tmp_chunk = self.tokenizer(
|
|
chunk_str, add_special_tokens=False)['input_ids']
|
|
enc_chunk += tmp_chunk
|
|
return enc_chunk
|
|
|
|
def convert(self, messages: Dict[str, List[Dict]]) -> str:
|
|
texts = []
|
|
image = []
|
|
messages = messages['messages']
|
|
for turn in messages:
|
|
if turn['role'] == 'system':
|
|
role = ''
|
|
elif turn['role'] == 'user':
|
|
role = 'Human: '
|
|
else:
|
|
role = 'AI: '
|
|
if isinstance(turn['content'], str):
|
|
text = f"{role}{turn['content']}"
|
|
texts.append(text)
|
|
else:
|
|
for t in turn['content']:
|
|
if isinstance(t, str):
|
|
text = f'{role}{t}'
|
|
else:
|
|
text = f'{role}<|image|>'
|
|
image.append(t['image'])
|
|
texts.append(text)
|
|
texts = '\n'.join(texts)
|
|
texts += '\nAI: '
|
|
return image, texts
|
|
|
|
def __call__(self, messages: Dict[str, Any],
|
|
**forward_params) -> Dict[str, Any]:
|
|
"""
|
|
Args:
|
|
messages: {[
|
|
{'role': 'system', 'content': 'message1'},
|
|
{'role': 'user', 'content': 'message2'},
|
|
{'role': 'user', 'content': ['message2', {"image": 'image_path'}, 'message3', ...]},
|
|
]}
|
|
The 'role' should be choose from ['system', 'user', 'assistant'].
|
|
The 'content' can be either str or List[Union[str, Dict]]
|
|
Return:
|
|
output: Dict[str, Tensor]
|
|
"""
|
|
output = {}
|
|
images, text = self.convert(messages)
|
|
|
|
if len(images) > 0:
|
|
pixel_values = []
|
|
for image in images:
|
|
pixel_values.append(
|
|
self.patch_resize_transform(self.image_open(image)[0]))
|
|
pixel_values = torch.stack(pixel_values, dim=0)
|
|
else:
|
|
pixel_values = None
|
|
|
|
input_ids = self.tokenize_text(text)
|
|
input_ids = torch.LongTensor([input_ids])
|
|
|
|
output = {
|
|
'pixel_values': pixel_values,
|
|
'input_ids': input_ids,
|
|
**forward_params
|
|
}
|
|
|
|
return output
|
|
|
|
|
|
@PREPROCESSORS.register_module(
|
|
Fields.multi_modal,
|
|
module_name=Preprocessors.image_captioning_clip_interrogator_preprocessor)
|
|
class ImageCaptioningClipInterrogatorPreprocessor(Preprocessor):
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
def __call__(self, data) -> Dict[str, Any]:
|
|
image = load_image(data)
|
|
data = np.array(image).transpose(2, 0, 1)
|
|
return data
|