Files
2026-07-13 13:24:13 +08:00

386 lines
11 KiB
Python

import json
import glob
import argparse
import re
import os
import ast
import numpy as np
import tiktoken
import torch
from tqdm import tqdm
from omegaconf import OmegaConf
from fairseq import checkpoint_utils, distributed_utils, options, tasks, utils
from kosmos2_5 import GenerationTask
from PIL import Image
from transformers import AutoProcessor
def parse_list(arg):
try:
parsed_list = ast.literal_eval(arg)
if isinstance(parsed_list, list):
return parsed_list
else:
raise ValueError
except (ValueError, SyntaxError):
raise argparse.ArgumentTypeError("Argument must be a list formatted as '[value1, value2, ...]'")
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument("--image", "-i", type=str, default=None, help="Input image")
parser.add_argument("--out_dir", "-o", type=str, default="./", help="Output directory.")
parser.add_argument("--do_ocr", action='store_true', default=False)
parser.add_argument("--do_md", action='store_true', default=False)
parser.add_argument("--ckpt", "-c", type=str, default="")
parser.add_argument("--use_preprocess", action='store_true', default=False, help="")
parser.add_argument("--hw_ratio_adj_upper_span", type=parse_list, default=[1.5, 5])
parser.add_argument("--hw_ratio_adj_lower_span", type=parse_list, default=[0.5, 1.0])
args = parser.parse_args()
if args.image != None:
assert os.path.exists(args.image), "Image does not exist."
if os.path.exists(args.out_dir) == False:
os.makedirs(args.out_dir)
assert os.path.exists(args.image), "Ckpt does not exist."
assert (args.do_ocr and not args.do_md) or (
args.do_md and not args.do_ocr), "A task must be selected, with the options being either '--do_ocr' or '--do_md'."
return args
def init(args):
cfg = {
'_name': None,
'common': {
'fp16': True,
},
'common_eval': {
'_name': None,
'path': None,
'post_process': 'sentencepiece',
'quiet': False,
'model_overrides': '{}',
'results_path': None,
'is_moe': False
},
'generation': {
'_name': None,
'beam': 1,
'nbest': 1,
'max_len_a': 0.0,
'max_len_b': 4000,
'min_len': 1,
'match_source_len': False,
'unnormalized': False,
'no_early_stop': False,
'no_beamable_mm': False,
'lenpen': 1.0,
'unkpen': 0.0,
'replace_unk': None,
'sacrebleu': False,
'score_reference': False,
'prefix_size': 0,
'no_repeat_ngram_size': 0,
'sampling': False,
'sampling_topk': -1,
'sampling_topp': -1.0,
'constraints': None,
'temperature': 1.0,
'diverse_beam_groups': -1,
'diverse_beam_strength': 0.5,
'diversity_rate': -1.0,
'print_alignment': None,
'print_step': False,
'lm_path': None,
'lm_weight': 0.0,
'iter_decode_eos_penalty': 0.0,
'iter_decode_max_iter': 10,
'iter_decode_force_max_iter': False,
'iter_decode_with_beam': 1,
'iter_decode_with_external_reranker': False,
'retain_iter_history': False,
'retain_dropout': False,
'retain_dropout_modules': None,
'decoding_format': None,
'no_seed_provided': False
},
'task': {
'_name': 'generation',
'data': '',
'required_batch_size_multiple': 1,
'dict_path': './dict.txt',
},
}
cfg['common_eval']['path'] = args.ckpt
cfg = OmegaConf.create(cfg)
utils.import_user_module(cfg.common)
if cfg.common.seed is not None and not cfg.generation.no_seed_provided:
np.random.seed(cfg.common.seed)
utils.set_torch_seed(cfg.common.seed)
use_cuda = True
task = tasks.setup_task(cfg.task)
overrides = ast.literal_eval(cfg.common_eval.model_overrides)
models, _model_args = checkpoint_utils.load_model_ensemble(
utils.split_paths(cfg.common_eval.path),
arg_overrides=overrides,
task=task,
suffix='',
strict=True,
num_shards=1,
)
dictionary = task.source_dictionary
for model in models:
if model is None:
continue
if cfg.common.fp16:
model.half()
if use_cuda:
model.cuda()
model.prepare_for_inference_(cfg)
generator = task.build_generator(models, cfg.generation)
generator.max_len_a = 1.0
tokenizer = tiktoken.get_encoding("cl100k_base")
image_processor = AutoProcessor.from_pretrained("google/pix2struct-large", is_vqa=False)
return task, models, generator, image_processor, dictionary, tokenizer
def build_data(args, image_path, image_processor, dictionary):
bos_id = dictionary.bos()
eos_id = dictionary.eos()
boi_id = dictionary.index("<image>")
eoi_id = dictionary.index("</image>")
image_feature_length = 2048
token, img_gpt_input_mask, segment_token = [], [], []
token.append(bos_id)
img_gpt_input_mask.append(0)
segment_token.append(0)
image = Image.open(image_path).convert("RGB")
raw_width, raw_height = image.width, image.height
if args.use_preprocess:
ratio = raw_height / raw_width
if args.hw_ratio_adj_upper_span[1] > ratio > args.hw_ratio_adj_upper_span[0]:
new_width = int(raw_height / args.hw_ratio_adj_upper_span[0])
image = image.resize((new_width, raw_height))
elif args.hw_ratio_adj_lower_span[1] > ratio > args.hw_ratio_adj_lower_span[0]:
new_height = (int(raw_width * args.hw_ratio_adj_lower_span[1]))
image = image.resize((raw_width, new_height))
img_res = image_processor(images=image, return_tensors="pt", max_patches=4096)
width = img_res['width'][0]
height = img_res['height'][0]
img_src_token = img_res['flattened_patches'][0]
img_attn_mask = img_res['attention_mask'][0]
token.extend([boi_id] + list(range(4, image_feature_length + 4)) + [eoi_id])
img_gpt_input_mask.extend([0] + [1] * image_feature_length + [0])
segment_token.extend([1] + [1] * image_feature_length + [1])
if args.do_ocr == True:
text_token = [dictionary.index("<ocr>"), dictionary.index('<bbox>')]
else:
text_token = [dictionary.index("<md>")]
token += text_token
img_gpt_input_mask += [0] * len(text_token)
segment_token += [0] * len(text_token)
# token.append(eos_id)
assert len(token) == len(img_gpt_input_mask) == len(segment_token)
token = torch.LongTensor(token)
img_gpt_input_mask = torch.LongTensor(img_gpt_input_mask)
segment_token = torch.LongTensor(segment_token)
lengths = torch.LongTensor([t.numel() for t in token])
return token.unsqueeze(0), lengths, img_src_token.unsqueeze(0), img_attn_mask.unsqueeze(0), img_gpt_input_mask.unsqueeze(0), segment_token.unsqueeze(0), width, height, raw_width, raw_height
def get_markdown_res(tokenizer, tokens, raw_width, raw_height):
def md_pre_process(tokens):
return tokens
def md_post_process(md):
md = md.replace('<br>', '\n')
lines = md.split('\n')
new_lines = []
for i in range(len(lines)):
text = lines[i].strip()
new_lines.append(text)
md = '\n'.join(new_lines)
md = re.sub('\n{2,}', '\n\n', md).strip()
return md
def get_json_format(md, raw_width, raw_height):
json_res = {
'model': "kosmos 2.5",
'task': "markdown",
'width': raw_width,
'height': raw_height,
"results": md,
}
return json_res
tokens = md_pre_process(tokens)
tokens = tokens[tokens.index('</image>') + 2:tokens.index('</s>')]
md = tokenizer.decode([int(t) for t in tokens])
md = md_post_process(md)
json_data = get_json_format(md, raw_width, raw_height)
return json_data
def get_ocr_res(tokenizer, tokens, p2s_resized_width, p2s_resized_height, raw_width, raw_height):
def ocr_pre_process(tokens):
return tokens
def ocr_post_process(lines, p2s_resized_width, p2s_resized_height, raw_width, raw_height):
def clip(min_num, num, max_num):
return min(max(num, min_num), max_num)
new_lines = []
for i in range(len(lines)):
text, [x0, y0, x1, y1], _ = lines[i]
text = text.strip()
if len(text) == 0: continue
x0 = clip(0, int(clip(0, x0 / p2s_resized_width, 1) * raw_width), raw_width)
y0 = clip(0, int(clip(0, y0 / p2s_resized_height, 1) * raw_height), raw_height)
x1 = clip(0, int(clip(0, x1 / p2s_resized_width, 1) * raw_width), raw_width)
y1 = clip(0, int(clip(0, y1 / p2s_resized_height, 1) * raw_height), raw_height)
new_lines.append([text, [x0, y0, x1, y1]])
return new_lines
def get_json_format(lines, raw_width, raw_height):
json_res = {
'model': "kosmos 2.5",
'task': "ocr",
'width': raw_width,
'height': raw_height,
"results": []
}
for i in range(len(lines)):
cur_item = {
'text': lines[i][0],
'bounding box': {
'x0': lines[i][1][0],
'y0': lines[i][1][1],
'x1': lines[i][1][2],
'y1': lines[i][1][3],
}
}
json_res['results'].append(cur_item)
return json_res
tokens = ocr_pre_process(tokens)
tokens = tokens[tokens.index('</image>') + 2:tokens.index('</s>')]
cur_token = []
lines = []
index = 0
while index < len(tokens):
cur_line = []
cur_bbox = []
while index < len(tokens) and tokens[index].startswith('<') == True:
cur_bbox.append(tokens[index])
index += 1
while index < len(tokens) and tokens[index].startswith('<') == False:
cur_line.append(int(tokens[index]))
index += 1
try:
assert len(cur_line) != 0
assert len(cur_bbox) == 6
assert cur_bbox[0] == '<bbox>'
assert cur_bbox[-1] == '</bbox>'
cur_bbox = cur_bbox[1:-1]
x0 = int(cur_bbox[0][1:-1].split('_')[-1])
y0 = int(cur_bbox[1][1:-1].split('_')[-1])
x1 = int(cur_bbox[2][1:-1].split('_')[-1])
y1 = int(cur_bbox[3][1:-1].split('_')[-1])
pass
except:
print('w')
continue
cur_token.append(cur_line)
lines.append([tokenizer.decode(cur_line).strip(), [x0, y0, x1, y1], cur_bbox])
lines = ocr_post_process(lines, p2s_resized_width, p2s_resized_height, raw_width, raw_height)
json_data = get_json_format(lines, raw_width, raw_height)
return json_data
def main():
args = get_args()
task, models, generator, image_processor, dictionary, tokenizer = init(args)
if args.image != None:
image_list = [args.image]
else:
image_list = glob.glob(os.path.join(args.image_dir, '*'))
for i in tqdm(range(len(image_list)), desc='inference'):
save_path = os.path.join(args.out_dir, os.path.basename(image_list[i]) + '.json')
src_tokens, src_lengths, img_src_token, img_attn_mask, img_gpt_input_mask, segment_token, p2s_resized_width, p2s_resized_height, raw_width, raw_height = build_data(args, image_list[i], image_processor, dictionary)
src_tokens = src_tokens.cuda()
src_lengths = src_lengths.cuda().half()
img_src_token = img_src_token.cuda().half()
img_attn_mask = img_attn_mask.cuda().half()
img_gpt_input_mask = img_gpt_input_mask.cuda().half()
segment_token = segment_token.cuda()
sample = {
"net_input": {
"src_tokens": src_tokens,
"src_lengths": src_lengths,
"image": img_src_token,
'image_attention_masks': img_attn_mask,
"segment_tokens": segment_token,
"img_gpt_input_mask": img_gpt_input_mask,
},
}
translations = task.inference_step(
generator, models, sample, constraints=None
)
tokens = []
for tid in translations[0][0]["tokens"].int().cpu().tolist():
cur_id = dictionary[tid]
tokens.append(cur_id)
if args.do_ocr:
result = get_ocr_res(tokenizer, tokens, p2s_resized_width, p2s_resized_height, raw_width, raw_height)
else:
result = get_markdown_res(tokenizer, tokens, raw_width, raw_height)
print(f'\n{result}')
json.dump(result, open(save_path, 'w', encoding='utf-8'), indent=4)
print('done')
if __name__ == '__main__':
main()