KevinHuSh commited on
Commit
cdba7f7
·
1 Parent(s): 8c4ec99

use onnx models, new deepdoc (#68)

Browse files
api/apps/conversation_app.py CHANGED
@@ -198,7 +198,7 @@ def chat(dialog, messages, **kwargs):
198
  return {"answer": prompt_config["empty_response"], "retrieval": kbinfos}
199
 
200
  kwargs["knowledge"] = "\n".join(knowledges)
201
- gen_conf = dialog.llm_setting[dialog.llm_setting_type]
202
  msg = [{"role": m["role"], "content": m["content"]} for m in messages if m["role"] != "system"]
203
  used_token_count, msg = message_fit_in(msg, int(llm.max_tokens * 0.97))
204
  if "max_tokens" in gen_conf:
 
198
  return {"answer": prompt_config["empty_response"], "retrieval": kbinfos}
199
 
200
  kwargs["knowledge"] = "\n".join(knowledges)
201
+ gen_conf = dialog.llm_setting
202
  msg = [{"role": m["role"], "content": m["content"]} for m in messages if m["role"] != "system"]
203
  used_token_count, msg = message_fit_in(msg, int(llm.max_tokens * 0.97))
204
  if "max_tokens" in gen_conf:
api/apps/dialog_app.py CHANGED
@@ -33,38 +33,17 @@ def set_dialog():
33
  name = req.get("name", "New Dialog")
34
  description = req.get("description", "A helpful Dialog")
35
  language = req.get("language", "Chinese")
36
- llm_setting_type = req.get("llm_setting_type", "Precise")
 
 
37
  llm_setting = req.get("llm_setting", {
38
- "Creative": {
39
- "temperature": 0.9,
40
- "top_p": 0.9,
41
- "frequency_penalty": 0.2,
42
- "presence_penalty": 0.4,
43
- "max_tokens": 512
44
- },
45
- "Precise": {
46
- "temperature": 0.1,
47
- "top_p": 0.3,
48
- "frequency_penalty": 0.7,
49
- "presence_penalty": 0.4,
50
- "max_tokens": 215
51
- },
52
- "Evenly": {
53
- "temperature": 0.5,
54
- "top_p": 0.5,
55
- "frequency_penalty": 0.7,
56
- "presence_penalty": 0.4,
57
- "max_tokens": 215
58
- },
59
- "Custom": {
60
- "temperature": 0.2,
61
- "top_p": 0.3,
62
- "frequency_penalty": 0.6,
63
- "presence_penalty": 0.3,
64
- "max_tokens": 215
65
- },
66
  })
67
- prompt_config = req.get("prompt_config", {
68
  "system": """你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
69
  以下是知识库:
70
  {knowledge}
@@ -74,30 +53,40 @@ def set_dialog():
74
  {"key": "knowledge", "optional": False}
75
  ],
76
  "empty_response": "Sorry! 知识库中未找到相关内容!"
77
- })
 
78
 
79
- if len(prompt_config["parameters"]) < 1:
80
- return get_data_error_result(retmsg="'knowledge' should be in parameters")
 
 
 
 
81
 
82
  for p in prompt_config["parameters"]:
83
- if prompt_config["system"].find("{%s}"%p["key"]) < 0:
 
84
  return get_data_error_result(retmsg="Parameter '{}' is not used".format(p["key"]))
85
 
86
  try:
87
  e, tenant = TenantService.get_by_id(current_user.id)
88
- if not e:return get_data_error_result(retmsg="Tenant not found!")
89
  llm_id = req.get("llm_id", tenant.llm_id)
90
  if not dialog_id:
 
91
  dia = {
92
  "id": get_uuid(),
93
  "tenant_id": current_user.id,
94
  "name": name,
 
95
  "description": description,
96
  "language": language,
97
  "llm_id": llm_id,
98
- "llm_setting_type": llm_setting_type,
99
  "llm_setting": llm_setting,
100
- "prompt_config": prompt_config
 
 
 
101
  }
102
  if not DialogService.save(**dia): return get_data_error_result(retmsg="Fail to new a dialog!")
103
  e, dia = DialogService.get_by_id(dia["id"])
@@ -122,7 +111,7 @@ def set_dialog():
122
  def get():
123
  dialog_id = request.args["dialog_id"]
124
  try:
125
- e,dia = DialogService.get_by_id(dialog_id)
126
  if not e: return get_data_error_result(retmsg="Dialog not found!")
127
  dia = dia.to_dict()
128
  dia["kb_ids"], dia["kb_names"] = get_kb_names(dia["kb_ids"])
@@ -130,20 +119,22 @@ def get():
130
  except Exception as e:
131
  return server_error_response(e)
132
 
 
133
  def get_kb_names(kb_ids):
134
  ids, nms = [], []
135
  for kid in kb_ids:
136
  e, kb = KnowledgebaseService.get_by_id(kid)
137
- if not e or kb.status != StatusEnum.VALID.value:continue
138
  ids.append(kid)
139
  nms.append(kb.name)
140
  return ids, nms
141
 
 
142
  @manager.route('/list', methods=['GET'])
143
  @login_required
144
  def list():
145
  try:
146
- diags = DialogService.query(tenant_id=current_user.id, status=StatusEnum.VALID.value)
147
  diags = [d.to_dict() for d in diags]
148
  for d in diags:
149
  d["kb_ids"], d["kb_names"] = get_kb_names(d["kb_ids"])
@@ -154,12 +145,11 @@ def list():
154
 
155
  @manager.route('/rm', methods=['POST'])
156
  @login_required
157
- @validate_request("dialog_id")
158
  def rm():
159
  req = request.json
160
  try:
161
- if not DialogService.update_by_id(req["dialog_id"], {"status": StatusEnum.INVALID.value}):
162
- return get_data_error_result(retmsg="Dialog not found!")
163
  return get_json_result(data=True)
164
  except Exception as e:
165
- return server_error_response(e)
 
33
  name = req.get("name", "New Dialog")
34
  description = req.get("description", "A helpful Dialog")
35
  language = req.get("language", "Chinese")
36
+ top_n = req.get("top_n", 6)
37
+ similarity_threshold = req.get("similarity_threshold", 0.1)
38
+ vector_similarity_weight = req.get("vector_similarity_weight", 0.3)
39
  llm_setting = req.get("llm_setting", {
40
+ "temperature": 0.1,
41
+ "top_p": 0.3,
42
+ "frequency_penalty": 0.7,
43
+ "presence_penalty": 0.4,
44
+ "max_tokens": 215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  })
46
+ default_prompt = {
47
  "system": """你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。
48
  以下是知识库:
49
  {knowledge}
 
53
  {"key": "knowledge", "optional": False}
54
  ],
55
  "empty_response": "Sorry! 知识库中未找到相关内容!"
56
+ }
57
+ prompt_config = req.get("prompt_config", default_prompt)
58
 
59
+ if not prompt_config["system"]: prompt_config["system"] = default_prompt["system"]
60
+ # if len(prompt_config["parameters"]) < 1:
61
+ # prompt_config["parameters"] = default_prompt["parameters"]
62
+ # for p in prompt_config["parameters"]:
63
+ # if p["key"] == "knowledge":break
64
+ # else: prompt_config["parameters"].append(default_prompt["parameters"][0])
65
 
66
  for p in prompt_config["parameters"]:
67
+ if p["optional"]: continue
68
+ if prompt_config["system"].find("{%s}" % p["key"]) < 0:
69
  return get_data_error_result(retmsg="Parameter '{}' is not used".format(p["key"]))
70
 
71
  try:
72
  e, tenant = TenantService.get_by_id(current_user.id)
73
+ if not e: return get_data_error_result(retmsg="Tenant not found!")
74
  llm_id = req.get("llm_id", tenant.llm_id)
75
  if not dialog_id:
76
+ if not req.get("kb_ids"):return get_data_error_result(retmsg="Fail! Please select knowledgebase!")
77
  dia = {
78
  "id": get_uuid(),
79
  "tenant_id": current_user.id,
80
  "name": name,
81
+ "kb_ids": req["kb_ids"],
82
  "description": description,
83
  "language": language,
84
  "llm_id": llm_id,
 
85
  "llm_setting": llm_setting,
86
+ "prompt_config": prompt_config,
87
+ "top_n": top_n,
88
+ "similarity_threshold": similarity_threshold,
89
+ "vector_similarity_weight": vector_similarity_weight
90
  }
91
  if not DialogService.save(**dia): return get_data_error_result(retmsg="Fail to new a dialog!")
92
  e, dia = DialogService.get_by_id(dia["id"])
 
111
  def get():
112
  dialog_id = request.args["dialog_id"]
113
  try:
114
+ e, dia = DialogService.get_by_id(dialog_id)
115
  if not e: return get_data_error_result(retmsg="Dialog not found!")
116
  dia = dia.to_dict()
117
  dia["kb_ids"], dia["kb_names"] = get_kb_names(dia["kb_ids"])
 
119
  except Exception as e:
120
  return server_error_response(e)
121
 
122
+
123
  def get_kb_names(kb_ids):
124
  ids, nms = [], []
125
  for kid in kb_ids:
126
  e, kb = KnowledgebaseService.get_by_id(kid)
127
+ if not e or kb.status != StatusEnum.VALID.value: continue
128
  ids.append(kid)
129
  nms.append(kb.name)
130
  return ids, nms
131
 
132
+
133
  @manager.route('/list', methods=['GET'])
134
  @login_required
135
  def list():
136
  try:
137
+ diags = DialogService.query(tenant_id=current_user.id, status=StatusEnum.VALID.value, reverse=True, order_by=DialogService.model.create_time)
138
  diags = [d.to_dict() for d in diags]
139
  for d in diags:
140
  d["kb_ids"], d["kb_names"] = get_kb_names(d["kb_ids"])
 
145
 
146
  @manager.route('/rm', methods=['POST'])
147
  @login_required
148
+ @validate_request("dialog_ids")
149
  def rm():
150
  req = request.json
151
  try:
152
+ DialogService.update_many_by_id([{"id": id, "status": StatusEnum.INVALID.value} for id in req["dialog_ids"]])
 
153
  return get_json_result(data=True)
154
  except Exception as e:
155
+ return server_error_response(e)
api/db/db_models.py CHANGED
@@ -529,8 +529,6 @@ class Dialog(DataBaseModel):
529
  icon = CharField(max_length=16, null=False, help_text="dialog icon")
530
  language = CharField(max_length=32, null=True, default="Chinese", help_text="English|Chinese")
531
  llm_id = CharField(max_length=32, null=False, help_text="default llm ID")
532
- llm_setting_type = CharField(max_length=8, null=False, help_text="Creative|Precise|Evenly|Custom",
533
- default="Creative")
534
  llm_setting = JSONField(null=False, default={"temperature": 0.1, "top_p": 0.3, "frequency_penalty": 0.7,
535
  "presence_penalty": 0.4, "max_tokens": 215})
536
  prompt_type = CharField(max_length=16, null=False, default="simple", help_text="simple|advanced")
 
529
  icon = CharField(max_length=16, null=False, help_text="dialog icon")
530
  language = CharField(max_length=32, null=True, default="Chinese", help_text="English|Chinese")
531
  llm_id = CharField(max_length=32, null=False, help_text="default llm ID")
 
 
532
  llm_setting = JSONField(null=False, default={"temperature": 0.1, "top_p": 0.3, "frequency_penalty": 0.7,
533
  "presence_penalty": 0.4, "max_tokens": 215})
534
  prompt_type = CharField(max_length=16, null=False, default="simple", help_text="simple|advanced")
deepdoc/__init__.py ADDED
File without changes
{rag → deepdoc}/parser/__init__.py RENAMED
@@ -1,4 +1,3 @@
1
- import copy
2
  import random
3
 
4
  from .pdf_parser import HuParser as PdfParser
@@ -10,7 +9,7 @@ import re
10
  from nltk import word_tokenize
11
 
12
  from rag.nlp import stemmer, huqie
13
- from ..utils import num_tokens_from_string
14
 
15
  BULLET_PATTERN = [[
16
  r"第[零一二三四五六七八九十百0-9]+(分?编|部分)",
 
 
1
  import random
2
 
3
  from .pdf_parser import HuParser as PdfParser
 
9
  from nltk import word_tokenize
10
 
11
  from rag.nlp import stemmer, huqie
12
+ from rag.utils import num_tokens_from_string
13
 
14
  BULLET_PATTERN = [[
15
  r"第[零一二三四五六七八九十百0-9]+(分?编|部分)",
{rag → deepdoc}/parser/docx_parser.py RENAMED
File without changes
{rag → deepdoc}/parser/excel_parser.py RENAMED
File without changes
{rag → deepdoc}/parser/pdf_parser.py RENAMED
@@ -1,7 +1,6 @@
1
  # -*- coding: utf-8 -*-
2
  import os
3
  import random
4
- from functools import partial
5
 
6
  import fitz
7
  import requests
@@ -15,6 +14,7 @@ from PIL import Image
15
  import numpy as np
16
 
17
  from api.db import ParserType
 
18
  from rag.nlp import huqie
19
  from collections import Counter
20
  from copy import deepcopy
@@ -26,13 +26,32 @@ logging.getLogger("pdfminer").setLevel(logging.WARNING)
26
 
27
  class HuParser:
28
  def __init__(self):
29
- from paddleocr import PaddleOCR
30
- logging.getLogger("ppocr").setLevel(logging.ERROR)
31
- self.ocr = PaddleOCR(use_angle_cls=False, lang="ch")
32
  if not hasattr(self, "model_speciess"):
33
  self.model_speciess = ParserType.GENERAL.value
34
- self.layouter = partial(self.__remote_call, self.model_speciess)
35
- self.tbl_det = partial(self.__remote_call, "table_component")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  self.updown_cnt_mdl = xgb.Booster()
38
  if torch.cuda.is_available():
@@ -56,7 +75,7 @@ class HuParser:
56
  token = os.environ.get("INFINIFLOW_TOKEN")
57
  if not url or not token:
58
  logging.warning("INFINIFLOW_SERVER is not specified. To maximize the effectiveness, please visit https://github.com/infiniflow/ragflow, and sign in the our demo web site to get token. It's FREE! Using 'export' to set both environment variables: INFINIFLOW_SERVER and INFINIFLOW_TOKEN.")
59
- return []
60
 
61
  def convert_image_to_bytes(PILimage):
62
  image = BytesIO()
@@ -382,7 +401,7 @@ class HuParser:
382
 
383
  return layouts
384
 
385
- def __table_paddle(self, images):
386
  tbls = self.tbl_det(images, thr=0.5)
387
  res = []
388
  # align left&right for rows, align top&bottom for columns
@@ -452,7 +471,7 @@ class HuParser:
452
  assert len(self.page_images) == len(tbcnt) - 1
453
  if not imgs:
454
  return
455
- recos = self.__table_paddle(imgs)
456
  tbcnt = np.cumsum(tbcnt)
457
  for i in range(len(tbcnt) - 1): # for page
458
  pg = []
@@ -517,8 +536,8 @@ class HuParser:
517
  b["H_right"] = spans[ii]["x1"]
518
  b["SP"] = ii
519
 
520
- def __ocr_paddle(self, pagenum, img, chars, ZM=3):
521
- bxs = self.ocr.ocr(np.array(img), cls=True)[0]
522
  if not bxs:
523
  self.boxes.append([])
524
  return
@@ -557,11 +576,12 @@ class HuParser:
557
 
558
  self.boxes.append(bxs)
559
 
560
- def _layouts_paddle(self, ZM):
561
  assert len(self.page_images) == len(self.boxes)
562
  # Tag layout type
563
  boxes = []
564
  layouts = self.layouter(self.page_images)
 
565
  assert len(self.page_images) == len(layouts)
566
  for pn, lts in enumerate(layouts):
567
  bxs = self.boxes[pn]
@@ -1741,7 +1761,7 @@ class HuParser:
1741
  # else:
1742
  # self.page_cum_height.append(
1743
  # np.max([c["bottom"] for c in chars]))
1744
- self.__ocr_paddle(i + 1, img, chars, zoomin)
1745
 
1746
  if not self.is_english and not any([c for c in self.page_chars]) and self.boxes:
1747
  bxes = [b for bxs in self.boxes for b in bxs]
@@ -1754,7 +1774,7 @@ class HuParser:
1754
 
1755
  def __call__(self, fnm, need_image=True, zoomin=3, return_html=False):
1756
  self.__images__(fnm, zoomin)
1757
- self._layouts_paddle(zoomin)
1758
  self._table_transformer_job(zoomin)
1759
  self._text_merge()
1760
  self._concat_downward()
 
1
  # -*- coding: utf-8 -*-
2
  import os
3
  import random
 
4
 
5
  import fitz
6
  import requests
 
14
  import numpy as np
15
 
16
  from api.db import ParserType
17
+ from deepdoc.visual import OCR, Recognizer
18
  from rag.nlp import huqie
19
  from collections import Counter
20
  from copy import deepcopy
 
26
 
27
  class HuParser:
28
  def __init__(self):
29
+ self.ocr = OCR()
 
 
30
  if not hasattr(self, "model_speciess"):
31
  self.model_speciess = ParserType.GENERAL.value
32
+ self.layout_labels = [
33
+ "_background_",
34
+ "Text",
35
+ "Title",
36
+ "Figure",
37
+ "Figure caption",
38
+ "Table",
39
+ "Table caption",
40
+ "Header",
41
+ "Footer",
42
+ "Reference",
43
+ "Equation",
44
+ ]
45
+ self.tsr_labels = [
46
+ "table",
47
+ "table column",
48
+ "table row",
49
+ "table column header",
50
+ "table projected row header",
51
+ "table spanning cell",
52
+ ]
53
+ self.layouter = Recognizer(self.layout_labels, "layout", "/data/newpeak/medical-gpt/res/ppdet/")
54
+ self.tbl_det = Recognizer(self.tsr_labels, "tsr", "/data/newpeak/medical-gpt/res/ppdet.tbl/")
55
 
56
  self.updown_cnt_mdl = xgb.Booster()
57
  if torch.cuda.is_available():
 
75
  token = os.environ.get("INFINIFLOW_TOKEN")
76
  if not url or not token:
77
  logging.warning("INFINIFLOW_SERVER is not specified. To maximize the effectiveness, please visit https://github.com/infiniflow/ragflow, and sign in the our demo web site to get token. It's FREE! Using 'export' to set both environment variables: INFINIFLOW_SERVER and INFINIFLOW_TOKEN.")
78
+ return [[] for _ in range(len(images))]
79
 
80
  def convert_image_to_bytes(PILimage):
81
  image = BytesIO()
 
401
 
402
  return layouts
403
 
404
+ def __table_tsr(self, images):
405
  tbls = self.tbl_det(images, thr=0.5)
406
  res = []
407
  # align left&right for rows, align top&bottom for columns
 
471
  assert len(self.page_images) == len(tbcnt) - 1
472
  if not imgs:
473
  return
474
+ recos = self.__table_tsr(imgs)
475
  tbcnt = np.cumsum(tbcnt)
476
  for i in range(len(tbcnt) - 1): # for page
477
  pg = []
 
536
  b["H_right"] = spans[ii]["x1"]
537
  b["SP"] = ii
538
 
539
+ def __ocr(self, pagenum, img, chars, ZM=3):
540
+ bxs = self.ocr(np.array(img))
541
  if not bxs:
542
  self.boxes.append([])
543
  return
 
576
 
577
  self.boxes.append(bxs)
578
 
579
+ def _layouts_rec(self, ZM):
580
  assert len(self.page_images) == len(self.boxes)
581
  # Tag layout type
582
  boxes = []
583
  layouts = self.layouter(self.page_images)
584
+ #save_results(self.page_images, layouts, self.layout_labels, output_dir='output/', threshold=0.7)
585
  assert len(self.page_images) == len(layouts)
586
  for pn, lts in enumerate(layouts):
587
  bxs = self.boxes[pn]
 
1761
  # else:
1762
  # self.page_cum_height.append(
1763
  # np.max([c["bottom"] for c in chars]))
1764
+ self.__ocr(i + 1, img, chars, zoomin)
1765
 
1766
  if not self.is_english and not any([c for c in self.page_chars]) and self.boxes:
1767
  bxes = [b for bxs in self.boxes for b in bxs]
 
1774
 
1775
  def __call__(self, fnm, need_image=True, zoomin=3, return_html=False):
1776
  self.__images__(fnm, zoomin)
1777
+ self._layouts_rec(zoomin)
1778
  self._table_transformer_job(zoomin)
1779
  self._text_merge()
1780
  self._concat_downward()
deepdoc/visual/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .ocr import OCR
2
+ from .recognizer import Recognizer
deepdoc/visual/ocr.py ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
+
14
+ import copy
15
+ import time
16
+ import os
17
+
18
+ from huggingface_hub import snapshot_download
19
+
20
+ from .operators import *
21
+ import numpy as np
22
+ import onnxruntime as ort
23
+
24
+ from api.utils.file_utils import get_project_base_directory
25
+ from .postprocess import build_post_process
26
+ from rag.settings import cron_logger
27
+
28
+
29
+ def transform(data, ops=None):
30
+ """ transform """
31
+ if ops is None:
32
+ ops = []
33
+ for op in ops:
34
+ data = op(data)
35
+ if data is None:
36
+ return None
37
+ return data
38
+
39
+
40
+ def create_operators(op_param_list, global_config=None):
41
+ """
42
+ create operators based on the config
43
+
44
+ Args:
45
+ params(list): a dict list, used to create some operators
46
+ """
47
+ assert isinstance(
48
+ op_param_list, list), ('operator config should be a list')
49
+ ops = []
50
+ for operator in op_param_list:
51
+ assert isinstance(operator,
52
+ dict) and len(operator) == 1, "yaml format error"
53
+ op_name = list(operator)[0]
54
+ param = {} if operator[op_name] is None else operator[op_name]
55
+ if global_config is not None:
56
+ param.update(global_config)
57
+ op = eval(op_name)(**param)
58
+ ops.append(op)
59
+ return ops
60
+
61
+
62
+ def load_model(model_dir, nm):
63
+ model_file_path = os.path.join(model_dir, nm + ".onnx")
64
+ if not os.path.exists(model_file_path):
65
+ raise ValueError("not find model file path {}".format(
66
+ model_file_path))
67
+ sess = ort.InferenceSession(model_file_path)
68
+ return sess, sess.get_inputs()[0]
69
+
70
+
71
+ class TextRecognizer(object):
72
+ def __init__(self, model_dir):
73
+ self.rec_image_shape = [int(v) for v in "3, 48, 320".split(",")]
74
+ self.rec_batch_num = 16
75
+ postprocess_params = {
76
+ 'name': 'CTCLabelDecode',
77
+ "character_dict_path": os.path.join(get_project_base_directory(), "rag/res", "ocr.res"),
78
+ "use_space_char": True
79
+ }
80
+ self.postprocess_op = build_post_process(postprocess_params)
81
+ self.predictor, self.input_tensor = load_model(model_dir, 'rec')
82
+
83
+ def resize_norm_img(self, img, max_wh_ratio):
84
+ imgC, imgH, imgW = self.rec_image_shape
85
+
86
+ assert imgC == img.shape[2]
87
+ imgW = int((imgH * max_wh_ratio))
88
+ w = self.input_tensor.shape[3:][0]
89
+ if isinstance(w, str):
90
+ pass
91
+ elif w is not None and w > 0:
92
+ imgW = w
93
+ h, w = img.shape[:2]
94
+ ratio = w / float(h)
95
+ if math.ceil(imgH * ratio) > imgW:
96
+ resized_w = imgW
97
+ else:
98
+ resized_w = int(math.ceil(imgH * ratio))
99
+
100
+ resized_image = cv2.resize(img, (resized_w, imgH))
101
+ resized_image = resized_image.astype('float32')
102
+ resized_image = resized_image.transpose((2, 0, 1)) / 255
103
+ resized_image -= 0.5
104
+ resized_image /= 0.5
105
+ padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)
106
+ padding_im[:, :, 0:resized_w] = resized_image
107
+ return padding_im
108
+
109
+ def resize_norm_img_vl(self, img, image_shape):
110
+
111
+ imgC, imgH, imgW = image_shape
112
+ img = img[:, :, ::-1] # bgr2rgb
113
+ resized_image = cv2.resize(
114
+ img, (imgW, imgH), interpolation=cv2.INTER_LINEAR)
115
+ resized_image = resized_image.astype('float32')
116
+ resized_image = resized_image.transpose((2, 0, 1)) / 255
117
+ return resized_image
118
+
119
+ def resize_norm_img_srn(self, img, image_shape):
120
+ imgC, imgH, imgW = image_shape
121
+
122
+ img_black = np.zeros((imgH, imgW))
123
+ im_hei = img.shape[0]
124
+ im_wid = img.shape[1]
125
+
126
+ if im_wid <= im_hei * 1:
127
+ img_new = cv2.resize(img, (imgH * 1, imgH))
128
+ elif im_wid <= im_hei * 2:
129
+ img_new = cv2.resize(img, (imgH * 2, imgH))
130
+ elif im_wid <= im_hei * 3:
131
+ img_new = cv2.resize(img, (imgH * 3, imgH))
132
+ else:
133
+ img_new = cv2.resize(img, (imgW, imgH))
134
+
135
+ img_np = np.asarray(img_new)
136
+ img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2GRAY)
137
+ img_black[:, 0:img_np.shape[1]] = img_np
138
+ img_black = img_black[:, :, np.newaxis]
139
+
140
+ row, col, c = img_black.shape
141
+ c = 1
142
+
143
+ return np.reshape(img_black, (c, row, col)).astype(np.float32)
144
+
145
+ def srn_other_inputs(self, image_shape, num_heads, max_text_length):
146
+
147
+ imgC, imgH, imgW = image_shape
148
+ feature_dim = int((imgH / 8) * (imgW / 8))
149
+
150
+ encoder_word_pos = np.array(range(0, feature_dim)).reshape(
151
+ (feature_dim, 1)).astype('int64')
152
+ gsrm_word_pos = np.array(range(0, max_text_length)).reshape(
153
+ (max_text_length, 1)).astype('int64')
154
+
155
+ gsrm_attn_bias_data = np.ones((1, max_text_length, max_text_length))
156
+ gsrm_slf_attn_bias1 = np.triu(gsrm_attn_bias_data, 1).reshape(
157
+ [-1, 1, max_text_length, max_text_length])
158
+ gsrm_slf_attn_bias1 = np.tile(
159
+ gsrm_slf_attn_bias1,
160
+ [1, num_heads, 1, 1]).astype('float32') * [-1e9]
161
+
162
+ gsrm_slf_attn_bias2 = np.tril(gsrm_attn_bias_data, -1).reshape(
163
+ [-1, 1, max_text_length, max_text_length])
164
+ gsrm_slf_attn_bias2 = np.tile(
165
+ gsrm_slf_attn_bias2,
166
+ [1, num_heads, 1, 1]).astype('float32') * [-1e9]
167
+
168
+ encoder_word_pos = encoder_word_pos[np.newaxis, :]
169
+ gsrm_word_pos = gsrm_word_pos[np.newaxis, :]
170
+
171
+ return [
172
+ encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1,
173
+ gsrm_slf_attn_bias2
174
+ ]
175
+
176
+ def process_image_srn(self, img, image_shape, num_heads, max_text_length):
177
+ norm_img = self.resize_norm_img_srn(img, image_shape)
178
+ norm_img = norm_img[np.newaxis, :]
179
+
180
+ [encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2] = \
181
+ self.srn_other_inputs(image_shape, num_heads, max_text_length)
182
+
183
+ gsrm_slf_attn_bias1 = gsrm_slf_attn_bias1.astype(np.float32)
184
+ gsrm_slf_attn_bias2 = gsrm_slf_attn_bias2.astype(np.float32)
185
+ encoder_word_pos = encoder_word_pos.astype(np.int64)
186
+ gsrm_word_pos = gsrm_word_pos.astype(np.int64)
187
+
188
+ return (norm_img, encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1,
189
+ gsrm_slf_attn_bias2)
190
+
191
+ def resize_norm_img_sar(self, img, image_shape,
192
+ width_downsample_ratio=0.25):
193
+ imgC, imgH, imgW_min, imgW_max = image_shape
194
+ h = img.shape[0]
195
+ w = img.shape[1]
196
+ valid_ratio = 1.0
197
+ # make sure new_width is an integral multiple of width_divisor.
198
+ width_divisor = int(1 / width_downsample_ratio)
199
+ # resize
200
+ ratio = w / float(h)
201
+ resize_w = math.ceil(imgH * ratio)
202
+ if resize_w % width_divisor != 0:
203
+ resize_w = round(resize_w / width_divisor) * width_divisor
204
+ if imgW_min is not None:
205
+ resize_w = max(imgW_min, resize_w)
206
+ if imgW_max is not None:
207
+ valid_ratio = min(1.0, 1.0 * resize_w / imgW_max)
208
+ resize_w = min(imgW_max, resize_w)
209
+ resized_image = cv2.resize(img, (resize_w, imgH))
210
+ resized_image = resized_image.astype('float32')
211
+ # norm
212
+ if image_shape[0] == 1:
213
+ resized_image = resized_image / 255
214
+ resized_image = resized_image[np.newaxis, :]
215
+ else:
216
+ resized_image = resized_image.transpose((2, 0, 1)) / 255
217
+ resized_image -= 0.5
218
+ resized_image /= 0.5
219
+ resize_shape = resized_image.shape
220
+ padding_im = -1.0 * np.ones((imgC, imgH, imgW_max), dtype=np.float32)
221
+ padding_im[:, :, 0:resize_w] = resized_image
222
+ pad_shape = padding_im.shape
223
+
224
+ return padding_im, resize_shape, pad_shape, valid_ratio
225
+
226
+ def resize_norm_img_spin(self, img):
227
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
228
+ # return padding_im
229
+ img = cv2.resize(img, tuple([100, 32]), cv2.INTER_CUBIC)
230
+ img = np.array(img, np.float32)
231
+ img = np.expand_dims(img, -1)
232
+ img = img.transpose((2, 0, 1))
233
+ mean = [127.5]
234
+ std = [127.5]
235
+ mean = np.array(mean, dtype=np.float32)
236
+ std = np.array(std, dtype=np.float32)
237
+ mean = np.float32(mean.reshape(1, -1))
238
+ stdinv = 1 / np.float32(std.reshape(1, -1))
239
+ img -= mean
240
+ img *= stdinv
241
+ return img
242
+
243
+ def resize_norm_img_svtr(self, img, image_shape):
244
+
245
+ imgC, imgH, imgW = image_shape
246
+ resized_image = cv2.resize(
247
+ img, (imgW, imgH), interpolation=cv2.INTER_LINEAR)
248
+ resized_image = resized_image.astype('float32')
249
+ resized_image = resized_image.transpose((2, 0, 1)) / 255
250
+ resized_image -= 0.5
251
+ resized_image /= 0.5
252
+ return resized_image
253
+
254
+ def resize_norm_img_abinet(self, img, image_shape):
255
+
256
+ imgC, imgH, imgW = image_shape
257
+
258
+ resized_image = cv2.resize(
259
+ img, (imgW, imgH), interpolation=cv2.INTER_LINEAR)
260
+ resized_image = resized_image.astype('float32')
261
+ resized_image = resized_image / 255.
262
+
263
+ mean = np.array([0.485, 0.456, 0.406])
264
+ std = np.array([0.229, 0.224, 0.225])
265
+ resized_image = (
266
+ resized_image - mean[None, None, ...]) / std[None, None, ...]
267
+ resized_image = resized_image.transpose((2, 0, 1))
268
+ resized_image = resized_image.astype('float32')
269
+
270
+ return resized_image
271
+
272
+ def norm_img_can(self, img, image_shape):
273
+
274
+ img = cv2.cvtColor(
275
+ img, cv2.COLOR_BGR2GRAY) # CAN only predict gray scale image
276
+
277
+ if self.rec_image_shape[0] == 1:
278
+ h, w = img.shape
279
+ _, imgH, imgW = self.rec_image_shape
280
+ if h < imgH or w < imgW:
281
+ padding_h = max(imgH - h, 0)
282
+ padding_w = max(imgW - w, 0)
283
+ img_padded = np.pad(img, ((0, padding_h), (0, padding_w)),
284
+ 'constant',
285
+ constant_values=(255))
286
+ img = img_padded
287
+
288
+ img = np.expand_dims(img, 0) / 255.0 # h,w,c -> c,h,w
289
+ img = img.astype('float32')
290
+
291
+ return img
292
+
293
+ def __call__(self, img_list):
294
+ img_num = len(img_list)
295
+ # Calculate the aspect ratio of all text bars
296
+ width_list = []
297
+ for img in img_list:
298
+ width_list.append(img.shape[1] / float(img.shape[0]))
299
+ # Sorting can speed up the recognition process
300
+ indices = np.argsort(np.array(width_list))
301
+ rec_res = [['', 0.0]] * img_num
302
+ batch_num = self.rec_batch_num
303
+ st = time.time()
304
+
305
+ for beg_img_no in range(0, img_num, batch_num):
306
+ end_img_no = min(img_num, beg_img_no + batch_num)
307
+ norm_img_batch = []
308
+ imgC, imgH, imgW = self.rec_image_shape[:3]
309
+ max_wh_ratio = imgW / imgH
310
+ # max_wh_ratio = 0
311
+ for ino in range(beg_img_no, end_img_no):
312
+ h, w = img_list[indices[ino]].shape[0:2]
313
+ wh_ratio = w * 1.0 / h
314
+ max_wh_ratio = max(max_wh_ratio, wh_ratio)
315
+ for ino in range(beg_img_no, end_img_no):
316
+ norm_img = self.resize_norm_img(img_list[indices[ino]],
317
+ max_wh_ratio)
318
+ norm_img = norm_img[np.newaxis, :]
319
+ norm_img_batch.append(norm_img)
320
+ norm_img_batch = np.concatenate(norm_img_batch)
321
+ norm_img_batch = norm_img_batch.copy()
322
+
323
+ input_dict = {}
324
+ input_dict[self.input_tensor.name] = norm_img_batch
325
+ outputs = self.predictor.run(None, input_dict)
326
+ preds = outputs[0]
327
+ rec_result = self.postprocess_op(preds)
328
+ for rno in range(len(rec_result)):
329
+ rec_res[indices[beg_img_no + rno]] = rec_result[rno]
330
+
331
+ return rec_res, time.time() - st
332
+
333
+
334
+ class TextDetector(object):
335
+ def __init__(self, model_dir):
336
+ pre_process_list = [{
337
+ 'DetResizeForTest': {
338
+ 'limit_side_len': 960,
339
+ 'limit_type': "max",
340
+ }
341
+ }, {
342
+ 'NormalizeImage': {
343
+ 'std': [0.229, 0.224, 0.225],
344
+ 'mean': [0.485, 0.456, 0.406],
345
+ 'scale': '1./255.',
346
+ 'order': 'hwc'
347
+ }
348
+ }, {
349
+ 'ToCHWImage': None
350
+ }, {
351
+ 'KeepKeys': {
352
+ 'keep_keys': ['image', 'shape']
353
+ }
354
+ }]
355
+ postprocess_params = {"name": "DBPostProcess", "thresh": 0.3, "box_thresh": 0.6, "max_candidates": 1000,
356
+ "unclip_ratio": 1.5, "use_dilation": False, "score_mode": "fast", "box_type": "quad"}
357
+
358
+ self.postprocess_op = build_post_process(postprocess_params)
359
+ self.predictor, self.input_tensor = load_model(model_dir, 'det')
360
+
361
+ img_h, img_w = self.input_tensor.shape[2:]
362
+ if isinstance(img_h, str) or isinstance(img_w, str):
363
+ pass
364
+ elif img_h is not None and img_w is not None and img_h > 0 and img_w > 0:
365
+ pre_process_list[0] = {
366
+ 'DetResizeForTest': {
367
+ 'image_shape': [img_h, img_w]
368
+ }
369
+ }
370
+ self.preprocess_op = create_operators(pre_process_list)
371
+
372
+ def order_points_clockwise(self, pts):
373
+ rect = np.zeros((4, 2), dtype="float32")
374
+ s = pts.sum(axis=1)
375
+ rect[0] = pts[np.argmin(s)]
376
+ rect[2] = pts[np.argmax(s)]
377
+ tmp = np.delete(pts, (np.argmin(s), np.argmax(s)), axis=0)
378
+ diff = np.diff(np.array(tmp), axis=1)
379
+ rect[1] = tmp[np.argmin(diff)]
380
+ rect[3] = tmp[np.argmax(diff)]
381
+ return rect
382
+
383
+ def clip_det_res(self, points, img_height, img_width):
384
+ for pno in range(points.shape[0]):
385
+ points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1))
386
+ points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1))
387
+ return points
388
+
389
+ def filter_tag_det_res(self, dt_boxes, image_shape):
390
+ img_height, img_width = image_shape[0:2]
391
+ dt_boxes_new = []
392
+ for box in dt_boxes:
393
+ if isinstance(box, list):
394
+ box = np.array(box)
395
+ box = self.order_points_clockwise(box)
396
+ box = self.clip_det_res(box, img_height, img_width)
397
+ rect_width = int(np.linalg.norm(box[0] - box[1]))
398
+ rect_height = int(np.linalg.norm(box[0] - box[3]))
399
+ if rect_width <= 3 or rect_height <= 3:
400
+ continue
401
+ dt_boxes_new.append(box)
402
+ dt_boxes = np.array(dt_boxes_new)
403
+ return dt_boxes
404
+
405
+ def filter_tag_det_res_only_clip(self, dt_boxes, image_shape):
406
+ img_height, img_width = image_shape[0:2]
407
+ dt_boxes_new = []
408
+ for box in dt_boxes:
409
+ if isinstance(box, list):
410
+ box = np.array(box)
411
+ box = self.clip_det_res(box, img_height, img_width)
412
+ dt_boxes_new.append(box)
413
+ dt_boxes = np.array(dt_boxes_new)
414
+ return dt_boxes
415
+
416
+ def __call__(self, img):
417
+ ori_im = img.copy()
418
+ data = {'image': img}
419
+
420
+ st = time.time()
421
+ data = transform(data, self.preprocess_op)
422
+ img, shape_list = data
423
+ if img is None:
424
+ return None, 0
425
+ img = np.expand_dims(img, axis=0)
426
+ shape_list = np.expand_dims(shape_list, axis=0)
427
+ img = img.copy()
428
+ input_dict = {}
429
+ input_dict[self.input_tensor.name] = img
430
+ outputs = self.predictor.run(None, input_dict)
431
+
432
+ post_result = self.postprocess_op({"maps": outputs[0]}, shape_list)
433
+ dt_boxes = post_result[0]['points']
434
+ dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im.shape)
435
+
436
+ return dt_boxes, time.time() - st
437
+
438
+
439
+ class OCR(object):
440
+ def __init__(self, model_dir=None):
441
+ """
442
+ If you have trouble downloading HuggingFace models, -_^ this might help!!
443
+
444
+ For Linux:
445
+ export HF_ENDPOINT=https://hf-mirror.com
446
+
447
+ For Windows:
448
+ Good luck
449
+ ^_-
450
+
451
+ """
452
+ if not model_dir:
453
+ model_dir = snapshot_download(repo_id="InfiniFlow/ocr")
454
+
455
+ self.text_detector = TextDetector(model_dir)
456
+ self.text_recognizer = TextRecognizer(model_dir)
457
+ self.drop_score = 0.5
458
+ self.crop_image_res_index = 0
459
+
460
+ def get_rotate_crop_image(self, img, points):
461
+ '''
462
+ img_height, img_width = img.shape[0:2]
463
+ left = int(np.min(points[:, 0]))
464
+ right = int(np.max(points[:, 0]))
465
+ top = int(np.min(points[:, 1]))
466
+ bottom = int(np.max(points[:, 1]))
467
+ img_crop = img[top:bottom, left:right, :].copy()
468
+ points[:, 0] = points[:, 0] - left
469
+ points[:, 1] = points[:, 1] - top
470
+ '''
471
+ assert len(points) == 4, "shape of points must be 4*2"
472
+ img_crop_width = int(
473
+ max(
474
+ np.linalg.norm(points[0] - points[1]),
475
+ np.linalg.norm(points[2] - points[3])))
476
+ img_crop_height = int(
477
+ max(
478
+ np.linalg.norm(points[0] - points[3]),
479
+ np.linalg.norm(points[1] - points[2])))
480
+ pts_std = np.float32([[0, 0], [img_crop_width, 0],
481
+ [img_crop_width, img_crop_height],
482
+ [0, img_crop_height]])
483
+ M = cv2.getPerspectiveTransform(points, pts_std)
484
+ dst_img = cv2.warpPerspective(
485
+ img,
486
+ M, (img_crop_width, img_crop_height),
487
+ borderMode=cv2.BORDER_REPLICATE,
488
+ flags=cv2.INTER_CUBIC)
489
+ dst_img_height, dst_img_width = dst_img.shape[0:2]
490
+ if dst_img_height * 1.0 / dst_img_width >= 1.5:
491
+ dst_img = np.rot90(dst_img)
492
+ return dst_img
493
+
494
+ def sorted_boxes(self, dt_boxes):
495
+ """
496
+ Sort text boxes in order from top to bottom, left to right
497
+ args:
498
+ dt_boxes(array):detected text boxes with shape [4, 2]
499
+ return:
500
+ sorted boxes(array) with shape [4, 2]
501
+ """
502
+ num_boxes = dt_boxes.shape[0]
503
+ sorted_boxes = sorted(dt_boxes, key=lambda x: (x[0][1], x[0][0]))
504
+ _boxes = list(sorted_boxes)
505
+
506
+ for i in range(num_boxes - 1):
507
+ for j in range(i, -1, -1):
508
+ if abs(_boxes[j + 1][0][1] - _boxes[j][0][1]) < 10 and \
509
+ (_boxes[j + 1][0][0] < _boxes[j][0][0]):
510
+ tmp = _boxes[j]
511
+ _boxes[j] = _boxes[j + 1]
512
+ _boxes[j + 1] = tmp
513
+ else:
514
+ break
515
+ return _boxes
516
+
517
+ def __call__(self, img, cls=True):
518
+ time_dict = {'det': 0, 'rec': 0, 'cls': 0, 'all': 0}
519
+
520
+ if img is None:
521
+ return None, None, time_dict
522
+
523
+ start = time.time()
524
+ ori_im = img.copy()
525
+ dt_boxes, elapse = self.text_detector(img)
526
+ time_dict['det'] = elapse
527
+
528
+ if dt_boxes is None:
529
+ end = time.time()
530
+ time_dict['all'] = end - start
531
+ return None, None, time_dict
532
+ else:
533
+ cron_logger.debug("dt_boxes num : {}, elapsed : {}".format(
534
+ len(dt_boxes), elapse))
535
+ img_crop_list = []
536
+
537
+ dt_boxes = self.sorted_boxes(dt_boxes)
538
+
539
+ for bno in range(len(dt_boxes)):
540
+ tmp_box = copy.deepcopy(dt_boxes[bno])
541
+ img_crop = self.get_rotate_crop_image(ori_im, tmp_box)
542
+ img_crop_list.append(img_crop)
543
+
544
+ rec_res, elapse = self.text_recognizer(img_crop_list)
545
+ time_dict['rec'] = elapse
546
+ cron_logger.debug("rec_res num : {}, elapsed : {}".format(
547
+ len(rec_res), elapse))
548
+
549
+ filter_boxes, filter_rec_res = [], []
550
+ for box, rec_result in zip(dt_boxes, rec_res):
551
+ text, score = rec_result
552
+ if score >= self.drop_score:
553
+ filter_boxes.append(box)
554
+ filter_rec_res.append(rec_result)
555
+ end = time.time()
556
+ time_dict['all'] = end - start
557
+
558
+ #for bno in range(len(img_crop_list)):
559
+ # print(f"{bno}, {rec_res[bno]}")
560
+
561
+ return list(zip([a.tolist() for a in filter_boxes], filter_rec_res))
deepdoc/visual/ocr.res ADDED
@@ -0,0 +1,6623 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '
2
+
3
+
4
+
5
+
6
+
7
+
8
+ 贿
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+ 2
26
+ 0
27
+ 8
28
+ -
29
+ 7
30
+
31
+ >
32
+ :
33
+ ]
34
+ ,
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+ 蹿
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+ 1
94
+ 3
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+ !
167
+
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+
245
+
246
+
247
+
248
+
249
+
250
+
251
+
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+
303
+
304
+ 诿
305
+
306
+
307
+
308
+
309
+
310
+
311
+
312
+
313
+
314
+
315
+
316
+ 线
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
382
+
383
+
384
+
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+ 尿
393
+
394
+
395
+
396
+
397
+
398
+
399
+
400
+
401
+ |
402
+ ;
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+ H
426
+
427
+
428
+
429
+
430
+
431
+
432
+
433
+
434
+
435
+
436
+
437
+
438
+
439
+
440
+
441
+
442
+
443
+
444
+
445
+
446
+
447
+
448
+
449
+
450
+
451
+
452
+
453
+
454
+
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+
465
+
466
+ .
467
+
468
+
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+
478
+
479
+
480
+
481
+
482
+
483
+
484
+
485
+
486
+
487
+ /
488
+ *
489
+
490
+ 忿
491
+
492
+
493
+
494
+
495
+
496
+
497
+
498
+
499
+
500
+
501
+
502
+
503
+
504
+ 齿
505
+
506
+
507
+
508
+
509
+
510
+
511
+
512
+
513
+
514
+
515
+
516
+
517
+
518
+
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+
535
+
536
+
537
+
538
+
539
+
540
+
541
+
542
+
543
+
544
+
545
+
546
+
547
+
548
+
549
+
550
+
551
+
552
+
553
+
554
+
555
+
556
+
557
+
558
+
559
+
560
+
561
+
562
+
563
+
564
+
565
+
566
+
567
+
568
+
569
+
570
+
571
+ 西
572
+
573
+
574
+
575
+
576
+
577
+
578
+
579
+
580
+
581
+
582
+
583
+
584
+
585
+
586
+
587
+
588
+
589
+
590
+
591
+
592
+
593
+
594
+
595
+
596
+
597
+
598
+
599
+
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+
608
+
609
+
610
+
611
+
612
+
613
+
614
+
615
+
616
+
617
+
618
+
619
+
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+
628
+
629
+
630
+
631
+ 5
632
+ 4
633
+
634
+
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
653
+
654
+
655
+
656
+ 亿
657
+
658
+
659
+
660
+
661
+
662
+
663
+
664
+
665
+
666
+
667
+
668
+
669
+
670
+
671
+
672
+
673
+
674
+
675
+
676
+
677
+
678
+
679
+
680
+
681
+
682
+
683
+
684
+
685
+
686
+
687
+
688
+
689
+
690
+
691
+
692
+
693
+
694
+
695
+
696
+
697
+
698
+
699
+
700
+
701
+
702
+
703
+
704
+
705
+
706
+
707
+
708
+
709
+
710
+
711
+
712
+
713
+
714
+
715
+
716
+
717
+
718
+
719
+
720
+
721
+ (
722
+
723
+
724
+
725
+
726
+
727
+
728
+
729
+
730
+
731
+
732
+
733
+
734
+
735
+
736
+
737
+
738
+
739
+
740
+
741
+
742
+
743
+
744
+
745
+
746
+
747
+
748
+
749
+
750
+
751
+
752
+
753
+
754
+ 访
755
+
756
+
757
+
758
+
759
+
760
+
761
+
762
+
763
+
764
+
765
+
766
+
767
+
768
+
769
+
770
+
771
+
772
+
773
+
774
+
775
+
776
+
777
+
778
+
779
+
780
+
781
+
782
+
783
+
784
+
785
+
786
+
787
+
788
+
789
+
790
+
791
+
792
+
793
+
794
+
795
+
796
+
797
+
798
+
799
+
800
+
801
+
802
+
803
+
804
+
805
+
806
+
807
+
808
+
809
+
810
+
811
+
812
+
813
+
814
+
815
+
816
+
817
+
818
+
819
+
820
+
821
+
822
+
823
+
824
+
825
+
826
+
827
+
828
+
829
+
830
+
831
+
832
+
833
+
834
+
835
+
836
+
837
+
838
+
839
+
840
+
841
+
842
+
843
+
844
+
845
+
846
+
847
+
848
+
849
+
850
+
851
+
852
+
853
+
854
+
855
+
856
+
857
+
858
+ ��
859
+
860
+
861
+
862
+
863
+
864
+
865
+
866
+
867
+
868
+
869
+
870
+
871
+
872
+
873
+
874
+
875
+
876
+
877
+
878
+
879
+
880
+
881
+
882
+
883
+
884
+
885
+
886
+
887
+
888
+
889
+
890
+
891
+
892
+
893
+
894
+
895
+
896
+
897
+
898
+
899
+
900
+
901
+
902
+
903
+
904
+
905
+
906
+
907
+
908
+
909
+
910
+
911
+
912
+
913
+
914
+
915
+
916
+
917
+
918
+
919
+
920
+
921
+
922
+
923
+
924
+
925
+
926
+
927
+
928
+
929
+
930
+
931
+
932
+
933
+ 6
934
+
935
+
936
+
937
+
938
+
939
+
940
+
941
+
942
+
943
+
944
+
945
+
946
+
947
+
948
+
949
+
950
+
951
+
952
+
953
+
954
+
955
+
956
+
957
+
958
+
959
+
960
+
961
+
962
+
963
+
964
+
965
+ )
966
+
967
+
968
+
969
+
970
+
971
+
972
+
973
+
974
+
975
+
976
+
977
+
978
+
979
+
980
+
981
+
982
+
983
+
984
+
985
+
986
+
987
+
988
+
989
+ 稿
990
+
991
+
992
+
993
+
994
+
995
+
996
+
997
+
998
+
999
+
1000
+
1001
+
1002
+
1003
+
1004
+
1005
+
1006
+
1007
+
1008
+
1009
+
1010
+
1011
+
1012
+
1013
+
1014
+
1015
+
1016
+
1017
+
1018
+
1019
+
1020
+
1021
+
1022
+
1023
+
1024
+
1025
+
1026
+
1027
+
1028
+
1029
+
1030
+
1031
+
1032
+
1033
+ s
1034
+ u
1035
+
1036
+
1037
+
1038
+
1039
+
1040
+
1041
+
1042
+
1043
+
1044
+
1045
+
1046
+
1047
+
1048
+
1049
+
1050
+
1051
+
1052
+
1053
+
1054
+
1055
+
1056
+
1057
+
1058
+
1059
+
1060
+
1061
+
1062
+
1063
+
1064
+
1065
+
1066
+
1067
+
1068
+
1069
+
1070
+
1071
+
1072
+
1073
+
1074
+
1075
+
1076
+
1077
+
1078
+
1079
+
1080
+
1081
+
1082
+
1083
+
1084
+
1085
+
1086
+
1087
+
1088
+
1089
+
1090
+
1091
+
1092
+
1093
+
1094
+
1095
+
1096
+
1097
+
1098
+
1099
+
1100
+
1101
+
1102
+
1103
+
1104
+
1105
+ [
1106
+
1107
+
1108
+
1109
+ 9
1110
+
1111
+
1112
+
1113
+
1114
+
1115
+
1116
+
1117
+
1118
+
1119
+
1120
+
1121
+
1122
+
1123
+
1124
+
1125
+
1126
+
1127
+
1128
+
1129
+
1130
+
1131
+
1132
+
1133
+
1134
+
1135
+
1136
+
1137
+
1138
+
1139
+
1140
+
1141
+
1142
+
1143
+
1144
+
1145
+
1146
+
1147
+
1148
+
1149
+
1150
+
1151
+
1152
+
1153
+
1154
+
1155
+
1156
+
1157
+
1158
+
1159
+
1160
+
1161
+
1162
+
1163
+
1164
+
1165
+
1166
+
1167
+
1168
+
1169
+
1170
+
1171
+
1172
+
1173
+
1174
+
1175
+
1176
+
1177
+
1178
+
1179
+
1180
+
1181
+
1182
+ 岿
1183
+
1184
+
1185
+
1186
+
1187
+
1188
+
1189
+
1190
+
1191
+
1192
+
1193
+
1194
+
1195
+
1196
+
1197
+
1198
+
1199
+
1200
+ 广
1201
+
1202
+
1203
+
1204
+
1205
+
1206
+
1207
+
1208
+
1209
+
1210
+
1211
+
1212
+
1213
+
1214
+
1215
+
1216
+
1217
+ S
1218
+ Y
1219
+ F
1220
+ D
1221
+ A
1222
+
1223
+
1224
+
1225
+
1226
+
1227
+
1228
+
1229
+
1230
+
1231
+
1232
+
1233
+
1234
+
1235
+
1236
+
1237
+
1238
+
1239
+
1240
+
1241
+
1242
+
1243
+
1244
+
1245
+
1246
+
1247
+
1248
+
1249
+
1250
+
1251
+
1252
+
1253
+
1254
+
1255
+
1256
+
1257
+
1258
+
1259
+
1260
+
1261
+
1262
+
1263
+
1264
+
1265
+
1266
+
1267
+
1268
+
1269
+
1270
+
1271
+
1272
+
1273
+
1274
+
1275
+
1276
+
1277
+
1278
+
1279
+
1280
+
1281
+
1282
+
1283
+
1284
+
1285
+
1286
+
1287
+
1288
+
1289
+
1290
+
1291
+
1292
+
1293
+
1294
+
1295
+
1296
+
1297
+
1298
+
1299
+
1300
+
1301
+
1302
+
1303
+
1304
+
1305
+
1306
+
1307
+
1308
+
1309
+
1310
+ P
1311
+
1312
+
1313
+
1314
+
1315
+
1316
+
1317
+
1318
+
1319
+
1320
+
1321
+
1322
+
1323
+
1324
+
1325
+
1326
+
1327
+
1328
+
1329
+
1330
+
1331
+
1332
+
1333
+
1334
+
1335
+
1336
+
1337
+
1338
+
1339
+
1340
+
1341
+
1342
+
1343
+
1344
+
1345
+
1346
+
1347
+
1348
+
1349
+
1350
+
1351
+
1352
+
1353
+
1354
+
1355
+
1356
+
1357
+
1358
+
1359
+
1360
+
1361
+
1362
+
1363
+
1364
+
1365
+
1366
+
1367
+
1368
+
1369
+
1370
+
1371
+
1372
+
1373
+
1374
+
1375
+
1376
+
1377
+
1378
+
1379
+
1380
+
1381
+ T
1382
+
1383
+
1384
+
1385
+
1386
+ 湿
1387
+
1388
+
1389
+
1390
+
1391
+
1392
+
1393
+
1394
+
1395
+
1396
+
1397
+ 窿
1398
+
1399
+
1400
+
1401
+
1402
+
1403
+
1404
+
1405
+
1406
+
1407
+
1408
+
1409
+
1410
+
1411
+
1412
+
1413
+
1414
+
1415
+
1416
+
1417
+
1418
+
1419
+
1420
+
1421
+
1422
+
1423
+
1424
+
1425
+
1426
+
1427
+
1428
+
1429
+
1430
+
1431
+
1432
+
1433
+
1434
+
1435
+
1436
+
1437
+
1438
+
1439
+
1440
+
1441
+
1442
+
1443
+
1444
+
1445
+
1446
+
1447
+
1448
+
1449
+
1450
+
1451
+
1452
+
1453
+
1454
+
1455
+
1456
+
1457
+
1458
+
1459
+
1460
+
1461
+
1462
+
1463
+
1464
+
1465
+
1466
+
1467
+
1468
+
1469
+
1470
+
1471
+
1472
+
1473
+
1474
+
1475
+
1476
+
1477
+
1478
+
1479
+
1480
+
1481
+
1482
+
1483
+
1484
+
1485
+
1486
+
1487
+
1488
+
1489
+
1490
+
1491
+
1492
+
1493
+
1494
+
1495
+
1496
+
1497
+
1498
+
1499
+
1500
+
1501
+
1502
+
1503
+
1504
+
1505
+
1506
+
1507
+
1508
+
1509
+
1510
+
1511
+
1512
+
1513
+
1514
+
1515
+
1516
+
1517
+
1518
+
1519
+
1520
+
1521
+
1522
+
1523
+
1524
+
1525
+
1526
+
1527
+
1528
+
1529
+ @
1530
+
1531
+
1532
+
1533
+
1534
+
1535
+
1536
+
1537
+
1538
+
1539
+
1540
+
1541
+
1542
+
1543
+
1544
+
1545
+
1546
+
1547
+
1548
+
1549
+
1550
+
1551
+
1552
+
1553
+
1554
+
1555
+
1556
+
1557
+
1558
+
1559
+
1560
+
1561
+
1562
+
1563
+ 丿
1564
+
1565
+
1566
+
1567
+
1568
+
1569
+
1570
+
1571
+
1572
+
1573
+
1574
+
1575
+
1576
+
1577
+
1578
+
1579
+
1580
+
1581
+
1582
+
1583
+
1584
+
1585
+
1586
+
1587
+
1588
+
1589
+
1590
+
1591
+
1592
+
1593
+
1594
+
1595
+
1596
+
1597
+
1598
+
1599
+
1600
+
1601
+
1602
+
1603
+
1604
+
1605
+
1606
+
1607
+
1608
+
1609
+
1610
+
1611
+
1612
+
1613
+
1614
+
1615
+
1616
+
1617
+
1618
+
1619
+
1620
+
1621
+
1622
+
1623
+
1624
+
1625
+
1626
+
1627
+
1628
+
1629
+
1630
+
1631
+
1632
+
1633
+
1634
+
1635
+
1636
+
1637
+
1638
+
1639
+
1640
+
1641
+
1642
+
1643
+ 沿
1644
+
1645
+
1646
+
1647
+
1648
+
1649
+
1650
+
1651
+
1652
+
1653
+
1654
+
1655
+
1656
+
1657
+
1658
+
1659
+
1660
+
1661
+
1662
+
1663
+
1664
+
1665
+
1666
+
1667
+
1668
+
1669
+
1670
+
1671
+
1672
+
1673
+
1674
+
1675
+
1676
+
1677
+
1678
+
1679
+
1680
+
1681
+ 使
1682
+
1683
+
1684
+
1685
+
1686
+
1687
+
1688
+
1689
+
1690
+
1691
+
1692
+
1693
+
1694
+
1695
+ 绿
1696
+
1697
+
1698
+
1699
+
1700
+
1701
+
1702
+
1703
+
1704
+
1705
+
1706
+
1707
+
1708
+
1709
+
1710
+
1711
+
1712
+
1713
+
1714
+
1715
+
1716
+ ��
1717
+
1718
+
1719
+
1720
+
1721
+
1722
+
1723
+
1724
+
1725
+
1726
+
1727
+
1728
+
1729
+
1730
+
1731
+
1732
+
1733
+
1734
+
1735
+
1736
+
1737
+
1738
+
1739
+
1740
+
1741
+
1742
+
1743
+
1744
+
1745
+
1746
+
1747
+
1748
+
1749
+
1750
+
1751
+
1752
+
1753
+
1754
+
1755
+
1756
+
1757
+
1758
+
1759
+
1760
+
1761
+
1762
+
1763
+
1764
+
1765
+
1766
+
1767
+
1768
+
1769
+
1770
+
1771
+
1772
+
1773
+
1774
+
1775
+
1776
+
1777
+
1778
+
1779
+
1780
+
1781
+
1782
+
1783
+
1784
+
1785
+
1786
+
1787
+
1788
+
1789
+
1790
+
1791
+
1792
+
1793
+
1794
+
1795
+
1796
+
1797
+
1798
+
1799
+
1800
+
1801
+
1802
+
1803
+
1804
+
1805
+
1806
+
1807
+
1808
+
1809
+
1810
+
1811
+ %
1812
+
1813
+
1814
+
1815
+
1816
+
1817
+
1818
+
1819
+
1820
+
1821
+
1822
+ "
1823
+
1824
+
1825
+
1826
+
1827
+
1828
+
1829
+
1830
+
1831
+
1832
+
1833
+
1834
+
1835
+
1836
+
1837
+ 婿
1838
+
1839
+
1840
+
1841
+
1842
+
1843
+
1844
+
1845
+
1846
+
1847
+
1848
+
1849
+
1850
+
1851
+
1852
+
1853
+
1854
+
1855
+
1856
+
1857
+
1858
+
1859
+
1860
+
1861
+
1862
+
1863
+
1864
+
1865
+
1866
+
1867
+
1868
+
1869
+
1870
+
1871
+
1872
+
1873
+
1874
+
1875
+
1876
+
1877
+
1878
+
1879
+
1880
+
1881
+
1882
+
1883
+
1884
+
1885
+
1886
+
1887
+
1888
+
1889
+
1890
+
1891
+
1892
+
1893
+
1894
+
1895
+
1896
+
1897
+
1898
+
1899
+
1900
+
1901
+
1902
+
1903
+
1904
+
1905
+
1906
+
1907
+
1908
+
1909
+
1910
+
1911
+
1912
+
1913
+
1914
+
1915
+
1916
+
1917
+
1918
+
1919
+
1920
+
1921
+
1922
+
1923
+
1924
+
1925
+
1926
+
1927
+
1928
+
1929
+
1930
+
1931
+
1932
+
1933
+
1934
+
1935
+
1936
+
1937
+
1938
+
1939
+
1940
+
1941
+
1942
+
1943
+
1944
+
1945
+
1946
+
1947
+
1948
+
1949
+
1950
+
1951
+
1952
+
1953
+
1954
+
1955
+
1956
+
1957
+
1958
+ r
1959
+
1960
+
1961
+
1962
+
1963
+
1964
+
1965
+
1966
+
1967
+
1968
+
1969
+
1970
+
1971
+
1972
+
1973
+
1974
+
1975
+
1976
+
1977
+
1978
+
1979
+
1980
+
1981
+
1982
+
1983
+
1984
+
1985
+
1986
+
1987
+
1988
+
1989
+ =
1990
+
1991
+
1992
+
1993
+
1994
+
1995
+
1996
+
1997
+
1998
+
1999
+
2000
+ 饿
2001
+
2002
+
2003
+
2004
+
2005
+
2006
+
2007
+
2008
+
2009
+
2010
+
2011
+
2012
+
2013
+
2014
+
2015
+
2016
+
2017
+
2018
+
2019
+
2020
+
2021
+
2022
+
2023
+
2024
+
2025
+
2026
+
2027
+
2028
+
2029
+
2030
+
2031
+
2032
+
2033
+
2034
+
2035
+
2036
+
2037
+
2038
+
2039
+
2040
+
2041
+
2042
+
2043
+
2044
+
2045
+
2046
+
2047
+
2048
+
2049
+
2050
+
2051
+
2052
+
2053
+
2054
+
2055
+
2056
+
2057
+
2058
+
2059
+
2060
+
2061
+
2062
+
2063
+
2064
+
2065
+
2066
+
2067
+
2068
+
2069
+
2070
+
2071
+
2072
+
2073
+
2074
+
2075
+
2076
+
2077
+
2078
+
2079
+
2080
+
2081
+
2082
+
2083
+
2084
+
2085
+
2086
+
2087
+
2088
+
2089
+
2090
+
2091
+
2092
+
2093
+
2094
+
2095
+
2096
+
2097
+
2098
+
2099
+
2100
+
2101
+
2102
+
2103
+
2104
+
2105
+
2106
+
2107
+
2108
+
2109
+
2110
+
2111
+
2112
+
2113
+
2114
+
2115
+ ˇ
2116
+
2117
+
2118
+
2119
+
2120
+
2121
+
2122
+
2123
+
2124
+
2125
+
2126
+
2127
+
2128
+
2129
+
2130
+
2131
+
2132
+
2133
+
2134
+
2135
+
2136
+
2137
+
2138
+
2139
+
2140
+
2141
+
2142
+
2143
+
2144
+
2145
+
2146
+
2147
+
2148
+
2149
+
2150
+
2151
+
2152
+
2153
+
2154
+
2155
+
2156
+ q
2157
+
2158
+
2159
+
2160
+
2161
+
2162
+
2163
+
2164
+
2165
+
2166
+
2167
+
2168
+
2169
+
2170
+
2171
+
2172
+
2173
+
2174
+
2175
+
2176
+
2177
+
2178
+
2179
+
2180
+
2181
+
2182
+
2183
+
2184
+
2185
+
2186
+
2187
+
2188
+
2189
+
2190
+
2191
+
2192
+
2193
+
2194
+
2195
+
2196
+
2197
+
2198
+
2199
+
2200
+
2201
+
2202
+
2203
+
2204
+
2205
+
2206
+
2207
+
2208
+
2209
+
2210
+
2211
+
2212
+
2213
+
2214
+
2215
+
2216
+
2217
+
2218
+
2219
+
2220
+
2221
+
2222
+
2223
+
2224
+
2225
+
2226
+
2227
+
2228
+
2229
+
2230
+
2231
+
2232
+
2233
+
2234
+
2235
+
2236
+
2237
+
2238
+
2239
+
2240
+
2241
+
2242
+
2243
+
2244
+
2245
+
2246
+
2247
+
2248
+
2249
+
2250
+
2251
+
2252
+
2253
+
2254
+
2255
+
2256
+
2257
+
2258
+
2259
+
2260
+
2261
+
2262
+
2263
+
2264
+
2265
+
2266
+
2267
+
2268
+
2269
+ ÷
2270
+
2271
+
2272
+
2273
+
2274
+
2275
+
2276
+
2277
+
2278
+
2279
+
2280
+
2281
+
2282
+
2283
+
2284
+
2285
+
2286
+
2287
+
2288
+
2289
+
2290
+
2291
+
2292
+
2293
+
2294
+
2295
+
2296
+
2297
+
2298
+
2299
+
2300
+
2301
+
2302
+
2303
+
2304
+
2305
+
2306
+
2307
+
2308
+
2309
+
2310
+
2311
+
2312
+
2313
+
2314
+
2315
+
2316
+
2317
+
2318
+
2319
+
2320
+
2321
+
2322
+
2323
+
2324
+
2325
+
2326
+
2327
+
2328
+
2329
+
2330
+
2331
+
2332
+
2333
+
2334
+
2335
+
2336
+
2337
+
2338
+
2339
+
2340
+
2341
+
2342
+
2343
+
2344
+
2345
+
2346
+
2347
+
2348
+
2349
+
2350
+
2351
+
2352
+
2353
+
2354
+
2355
+
2356
+
2357
+
2358
+
2359
+
2360
+
2361
+
2362
+
2363
+
2364
+
2365
+
2366
+
2367
+
2368
+
2369
+
2370
+
2371
+
2372
+
2373
+
2374
+
2375
+
2376
+
2377
+
2378
+
2379
+
2380
+
2381
+ 椿
2382
+
2383
+
2384
+
2385
+ 寿
2386
+
2387
+
2388
+
2389
+
2390
+
2391
+
2392
+
2393
+
2394
+
2395
+
2396
+
2397
+
2398
+
2399
+
2400
+
2401
+
2402
+
2403
+
2404
+
2405
+
2406
+
2407
+
2408
+
2409
+
2410
+
2411
+
2412
+
2413
+
2414
+
2415
+
2416
+
2417
+
2418
+
2419
+
2420
+
2421
+
2422
+
2423
+
2424
+
2425
+
2426
+
2427
+
2428
+
2429
+
2430
+
2431
+
2432
+
2433
+
2434
+
2435
+
2436
+
2437
+
2438
+
2439
+
2440
+
2441
+
2442
+
2443
+
2444
+
2445
+
2446
+
2447
+
2448
+
2449
+
2450
+
2451
+
2452
+
2453
+
2454
+
2455
+
2456
+ ?
2457
+
2458
+
2459
+
2460
+
2461
+
2462
+
2463
+
2464
+
2465
+
2466
+
2467
+
2468
+
2469
+
2470
+
2471
+
2472
+
2473
+
2474
+
2475
+
2476
+
2477
+
2478
+
2479
+
2480
+
2481
+
2482
+
2483
+
2484
+
2485
+
2486
+
2487
+
2488
+
2489
+
2490
+
2491
+
2492
+
2493
+
2494
+
2495
+
2496
+
2497
+
2498
+
2499
+
2500
+
2501
+
2502
+
2503
+
2504
+
2505
+
2506
+
2507
+
2508
+
2509
+
2510
+
2511
+
2512
+
2513
+
2514
+
2515
+
2516
+
2517
+
2518
+
2519
+
2520
+
2521
+
2522
+
2523
+
2524
+
2525
+
2526
+
2527
+
2528
+
2529
+
2530
+
2531
+
2532
+
2533
+
2534
+
2535
+
2536
+
2537
+
2538
+
2539
+
2540
+
2541
+
2542
+
2543
+
2544
+
2545
+
2546
+
2547
+
2548
+
2549
+
2550
+
2551
+
2552
+
2553
+
2554
+
2555
+
2556
+
2557
+
2558
+
2559
+
2560
+
2561
+
2562
+
2563
+
2564
+
2565
+
2566
+
2567
+
2568
+
2569
+
2570
+
2571
+
2572
+
2573
+
2574
+
2575
+
2576
+
2577
+
2578
+
2579
+
2580
+
2581
+
2582
+
2583
+
2584
+
2585
+
2586
+
2587
+
2588
+
2589
+
2590
+
2591
+
2592
+
2593
+
2594
+
2595
+
2596
+
2597
+
2598
+ 便
2599
+
2600
+
2601
+
2602
+
2603
+
2604
+
2605
+
2606
+
2607
+
2608
+
2609
+
2610
+
2611
+
2612
+
2613
+
2614
+
2615
+
2616
+
2617
+
2618
+
2619
+
2620
+
2621
+
2622
+
2623
+
2624
+
2625
+
2626
+
2627
+
2628
+
2629
+
2630
+
2631
+
2632
+
2633
+
2634
+
2635
+
2636
+
2637
+
2638
+
2639
+
2640
+
2641
+
2642
+
2643
+
2644
+
2645
+
2646
+
2647
+
2648
+
2649
+
2650
+
2651
+
2652
+
2653
+
2654
+
2655
+
2656
+
2657
+
2658
+
2659
+
2660
+
2661
+
2662
+
2663
+
2664
+
2665
+
2666
+
2667
+ 殿
2668
+
2669
+
2670
+
2671
+
2672
+
2673
+
2674
+
2675
+
2676
+
2677
+
2678
+
2679
+
2680
+
2681
+
2682
+
2683
+
2684
+
2685
+
2686
+
2687
+
2688
+
2689
+
2690
+
2691
+
2692
+
2693
+
2694
+
2695
+
2696
+
2697
+ J
2698
+
2699
+
2700
+
2701
+
2702
+
2703
+
2704
+
2705
+
2706
+
2707
+
2708
+
2709
+
2710
+ l
2711
+
2712
+
2713
+
2714
+
2715
+
2716
+
2717
+
2718
+
2719
+
2720
+
2721
+
2722
+
2723
+
2724
+
2725
+
2726
+
2727
+
2728
+
2729
+
2730
+
2731
+
2732
+
2733
+
2734
+
2735
+
2736
+
2737
+
2738
+
2739
+
2740
+
2741
+
2742
+
2743
+
2744
+
2745
+
2746
+
2747
+
2748
+
2749
+
2750
+
2751
+
2752
+
2753
+
2754
+
2755
+
2756
+
2757
+
2758
+
2759
+
2760
+
2761
+
2762
+
2763
+
2764
+
2765
+
2766
+
2767
+
2768
+
2769
+
2770
+
2771
+
2772
+
2773
+
2774
+
2775
+
2776
+
2777
+
2778
+
2779
+
2780
+
2781
+
2782
+
2783
+
2784
+
2785
+
2786
+
2787
+
2788
+
2789
+
2790
+
2791
+
2792
+
2793
+
2794
+
2795
+
2796
+
2797
+
2798
+
2799
+
2800
+
2801
+
2802
+
2803
+
2804
+
2805
+
2806
+
2807
+
2808
+
2809
+
2810
+
2811
+
2812
+
2813
+
2814
+
2815
+
2816
+
2817
+
2818
+
2819
+
2820
+
2821
+
2822
+
2823
+
2824
+
2825
+
2826
+
2827
+
2828
+
2829
+
2830
+
2831
+
2832
+
2833
+
2834
+
2835
+
2836
+
2837
+
2838
+
2839
+
2840
+
2841
+
2842
+
2843
+
2844
+
2845
+
2846
+
2847
+
2848
+
2849
+
2850
+
2851
+
2852
+
2853
+
2854
+ &
2855
+
2856
+
2857
+
2858
+
2859
+
2860
+
2861
+
2862
+
2863
+
2864
+
2865
+
2866
+
2867
+
2868
+
2869
+
2870
+
2871
+
2872
+
2873
+
2874
+
2875
+
2876
+
2877
+
2878
+
2879
+
2880
+
2881
+
2882
+
2883
+
2884
+
2885
+
2886
+
2887
+
2888
+
2889
+
2890
+
2891
+
2892
+
2893
+
2894
+
2895
+
2896
+
2897
+
2898
+
2899
+
2900
+
2901
+
2902
+
2903
+
2904
+
2905
+
2906
+
2907
+
2908
+
2909
+
2910
+
2911
+
2912
+
2913
+
2914
+
2915
+
2916
+
2917
+
2918
+
2919
+
2920
+
2921
+
2922
+
2923
+
2924
+
2925
+
2926
+
2927
+
2928
+
2929
+
2930
+
2931
+
2932
+
2933
+
2934
+
2935
+
2936
+
2937
+
2938
+
2939
+
2940
+
2941
+
2942
+
2943
+ 驿
2944
+
2945
+
2946
+
2947
+
2948
+
2949
+
2950
+
2951
+
2952
+
2953
+
2954
+
2955
+
2956
+
2957
+
2958
+
2959
+
2960
+
2961
+
2962
+
2963
+
2964
+
2965
+
2966
+
2967
+
2968
+
2969
+
2970
+
2971
+
2972
+
2973
+
2974
+
2975
+
2976
+
2977
+
2978
+
2979
+
2980
+
2981
+
2982
+
2983
+
2984
+
2985
+
2986
+
2987
+
2988
+
2989
+
2990
+
2991
+
2992
+
2993
+ x
2994
+
2995
+
2996
+
2997
+
2998
+
2999
+
3000
+
3001
+
3002
+
3003
+
3004
+
3005
+
3006
+
3007
+
3008
+
3009
+
3010
+
3011
+
3012
+
3013
+
3014
+
3015
+
3016
+
3017
+
3018
+
3019
+
3020
+
3021
+
3022
+ 耀
3023
+
3024
+
3025
+
3026
+
3027
+
3028
+
3029
+
3030
+
3031
+
3032
+
3033
+
3034
+
3035
+
3036
+
3037
+
3038
+
3039
+
3040
+
3041
+
3042
+
3043
+
3044
+
3045
+
3046
+
3047
+
3048
+
3049
+
3050
+
3051
+
3052
+
3053
+
3054
+
3055
+
3056
+
3057
+
3058
+
3059
+
3060
+
3061
+
3062
+
3063
+
3064
+
3065
+
3066
+
3067
+
3068
+
3069
+
3070
+
3071
+
3072
+ 仿
3073
+
3074
+
3075
+
3076
+
3077
+
3078
+
3079
+
3080
+
3081
+
3082
+
3083
+
3084
+
3085
+
3086
+
3087
+
3088
+
3089
+
3090
+
3091
+
3092
+
3093
+
3094
+
3095
+
3096
+
3097
+
3098
+
3099
+
3100
+
3101
+
3102
+
3103
+
3104
+
3105
+
3106
+
3107
+
3108
+
3109
+
3110
+
3111
+
3112
+
3113
+
3114
+
3115
+
3116
+
3117
+
3118
+
3119
+
3120
+
3121
+
3122
+
3123
+ 鸿
3124
+
3125
+
3126
+
3127
+
3128
+
3129
+
3130
+
3131
+
3132
+
3133
+
3134
+
3135
+
3136
+
3137
+
3138
+
3139
+
3140
+
3141
+
3142
+
3143
+
3144
+
3145
+
3146
+
3147
+
3148
+
3149
+
3150
+
3151
+
3152
+
3153
+
3154
+
3155
+
3156
+
3157
+
3158
+
3159
+
3160
+
3161
+
3162
+
3163
+
3164
+
3165
+
3166
+
3167
+
3168
+
3169
+
3170
+
3171
+
3172
+
3173
+
3174
+
3175
+
3176
+
3177
+
3178
+
3179
+
3180
+
3181
+
3182
+
3183
+
3184
+
3185
+
3186
+
3187
+
3188
+
3189
+
3190
+
3191
+
3192
+
3193
+
3194
+
3195
+
3196
+
3197
+
3198
+
3199
+
3200
+
3201
+
3202
+
3203
+
3204
+
3205
+
3206
+
3207
+
3208
+
3209
+
3210
+
3211
+
3212
+
3213
+
3214
+
3215
+
3216
+
3217
+
3218
+
3219
+
3220
+
3221
+
3222
+
3223
+
3224
+
3225
+
3226
+
3227
+
3228
+
3229
+
3230
+
3231
+
3232
+
3233
+
3234
+
3235
+
3236
+
3237
+
3238
+
3239
+ 廿
3240
+
3241
+
3242
+
3243
+
3244
+
3245
+
3246
+
3247
+
3248
+
3249
+
3250
+
3251
+
3252
+
3253
+
3254
+
3255
+
3256
+
3257
+
3258
+
3259
+
3260
+
3261
+
3262
+
3263
+
3264
+
3265
+
3266
+
3267
+
3268
+
3269
+
3270
+
3271
+
3272
+
3273
+
3274
+
3275
+
3276
+
3277
+
3278
+
3279
+
3280
+
3281
+
3282
+
3283
+
3284
+
3285
+
3286
+
3287
+
3288
+
3289
+
3290
+
3291
+
3292
+
3293
+
3294
+
3295
+
3296
+
3297
+
3298
+
3299
+
3300
+
3301
+
3302
+
3303
+
3304
+
3305
+
3306
+
3307
+
3308
+
3309
+
3310
+
3311
+
3312
+
3313
+
3314
+
3315
+
3316
+ z
3317
+
3318
+
3319
+ ±
3320
+
3321
+
3322
+
3323
+
3324
+
3325
+
3326
+
3327
+
3328
+
3329
+
3330
+
3331
+
3332
+ e
3333
+ t
3334
+
3335
+
3336
+
3337
+
3338
+
3339
+
3340
+
3341
+
3342
+
3343
+
3344
+
3345
+
3346
+
3347
+
3348
+
3349
+
3350
+
3351
+
3352
+
3353
+
3354
+
3355
+
3356
+
3357
+
3358
+
3359
+
3360
+
3361
+
3362
+
3363
+
3364
+
3365
+
3366
+
3367
+
3368
+
3369
+
3370
+
3371
+
3372
+
3373
+
3374
+
3375
+
3376
+
3377
+
3378
+
3379
+
3380
+ §
3381
+
3382
+
3383
+
3384
+
3385
+
3386
+
3387
+
3388
+
3389
+
3390
+
3391
+
3392
+
3393
+
3394
+
3395
+
3396
+
3397
+
3398
+
3399
+
3400
+ 姿
3401
+
3402
+
3403
+
3404
+
3405
+
3406
+
3407
+
3408
+
3409
+
3410
+
3411
+
3412
+
3413
+
3414
+
3415
+
3416
+
3417
+
3418
+
3419
+
3420
+
3421
+
3422
+
3423
+
3424
+
3425
+
3426
+
3427
+
3428
+
3429
+
3430
+
3431
+
3432
+
3433
+
3434
+
3435
+
3436
+
3437
+
3438
+
3439
+
3440
+
3441
+
3442
+
3443
+
3444
+
3445
+
3446
+
3447
+
3448
+
3449
+
3450
+
3451
+
3452
+
3453
+
3454
+
3455
+
3456
+
3457
+
3458
+
3459
+
3460
+
3461
+
3462
+
3463
+ b
3464
+
3465
+
3466
+
3467
+
3468
+
3469
+
3470
+
3471
+
3472
+
3473
+
3474
+
3475
+
3476
+
3477
+
3478
+
3479
+
3480
+
3481
+
3482
+
3483
+
3484
+
3485
+
3486
+
3487
+
3488
+
3489
+
3490
+ <
3491
+
3492
+
3493
+
3494
+
3495
+
3496
+
3497
+
3498
+
3499
+
3500
+
3501
+
3502
+
3503
+
3504
+
3505
+ 退
3506
+ L
3507
+
3508
+
3509
+
3510
+
3511
+
3512
+
3513
+
3514
+
3515
+
3516
+
3517
+ 鹿
3518
+
3519
+
3520
+
3521
+
3522
+
3523
+
3524
+
3525
+
3526
+
3527
+
3528
+
3529
+
3530
+
3531
+
3532
+
3533
+
3534
+
3535
+
3536
+
3537
+ w
3538
+ i
3539
+ h
3540
+
3541
+
3542
+
3543
+
3544
+
3545
+
3546
+
3547
+
3548
+
3549
+
3550
+
3551
+
3552
+
3553
+
3554
+
3555
+
3556
+
3557
+
3558
+
3559
+
3560
+
3561
+
3562
+
3563
+
3564
+
3565
+
3566
+
3567
+
3568
+
3569
+
3570
+
3571
+
3572
+
3573
+ +
3574
+
3575
+
3576
+
3577
+
3578
+
3579
+
3580
+
3581
+
3582
+
3583
+
3584
+
3585
+
3586
+
3587
+ I
3588
+ B
3589
+ N
3590
+
3591
+
3592
+
3593
+
3594
+
3595
+
3596
+
3597
+
3598
+
3599
+
3600
+
3601
+
3602
+
3603
+
3604
+
3605
+
3606
+
3607
+
3608
+
3609
+
3610
+
3611
+
3612
+
3613
+
3614
+
3615
+
3616
+
3617
+
3618
+
3619
+
3620
+
3621
+
3622
+
3623
+
3624
+
3625
+
3626
+ ^
3627
+ _
3628
+
3629
+
3630
+
3631
+
3632
+
3633
+
3634
+
3635
+
3636
+
3637
+
3638
+
3639
+ M
3640
+
3641
+
3642
+
3643
+
3644
+
3645
+
3646
+
3647
+
3648
+
3649
+
3650
+
3651
+
3652
+
3653
+
3654
+
3655
+
3656
+
3657
+
3658
+
3659
+
3660
+
3661
+
3662
+
3663
+
3664
+
3665
+
3666
+
3667
+
3668
+
3669
+
3670
+
3671
+
3672
+
3673
+ 鱿
3674
+
3675
+
3676
+
3677
+
3678
+
3679
+
3680
+
3681
+
3682
+
3683
+
3684
+
3685
+
3686
+
3687
+
3688
+
3689
+
3690
+
3691
+
3692
+
3693
+
3694
+
3695
+
3696
+
3697
+
3698
+
3699
+
3700
+
3701
+
3702
+
3703
+
3704
+
3705
+
3706
+
3707
+
3708
+
3709
+
3710
+
3711
+
3712
+
3713
+
3714
+
3715
+
3716
+
3717
+
3718
+
3719
+
3720
+
3721
+
3722
+
3723
+
3724
+
3725
+
3726
+
3727
+
3728
+
3729
+
3730
+
3731
+
3732
+
3733
+
3734
+
3735
+
3736
+
3737
+
3738
+
3739
+
3740
+
3741
+
3742
+
3743
+
3744
+
3745
+
3746
+
3747
+
3748
+
3749
+
3750
+
3751
+
3752
+
3753
+
3754
+
3755
+
3756
+
3757
+
3758
+
3759
+
3760
+
3761
+
3762
+
3763
+
3764
+
3765
+
3766
+
3767
+
3768
+
3769
+
3770
+
3771
+
3772
+
3773
+
3774
+
3775
+
3776
+
3777
+
3778
+
3779
+
3780
+
3781
+
3782
+
3783
+
3784
+
3785
+
3786
+
3787
+
3788
+
3789
+
3790
+
3791
+
3792
+
3793
+
3794
+
3795
+
3796
+
3797
+
3798
+
3799
+
3800
+
3801
+
3802
+
3803
+
3804
+
3805
+
3806
+
3807
+
3808
+
3809
+
3810
+
3811
+
3812
+
3813
+
3814
+
3815
+
3816
+
3817
+
3818
+
3819
+
3820
+
3821
+
3822
+
3823
+
3824
+
3825
+
3826
+
3827
+
3828
+
3829
+
3830
+
3831
+
3832
+
3833
+
3834
+
3835
+
3836
+
3837
+
3838
+
3839
+
3840
+
3841
+
3842
+
3843
+
3844
+
3845
+
3846
+
3847
+
3848
+
3849
+
3850
+
3851
+
3852
+
3853
+
3854
+
3855
+
3856
+
3857
+
3858
+
3859
+
3860
+
3861
+
3862
+
3863
+
3864
+
3865
+
3866
+
3867
+
3868
+
3869
+
3870
+
3871
+
3872
+
3873
+
3874
+
3875
+
3876
+
3877
+ 怀
3878
+
3879
+
3880
+
3881
+
3882
+
3883
+
3884
+
3885
+
3886
+
3887
+
3888
+
3889
+
3890
+
3891
+
3892
+
3893
+
3894
+
3895
+
3896
+
3897
+
3898
+
3899
+
3900
+
3901
+
3902
+
3903
+
3904
+
3905
+
3906
+
3907
+
3908
+
3909
+
3910
+
3911
+
3912
+
3913
+
3914
+
3915
+
3916
+
3917
+
3918
+
3919
+
3920
+
3921
+
3922
+
3923
+
3924
+
3925
+
3926
+
3927
+
3928
+
3929
+
3930
+
3931
+
3932
+
3933
+
3934
+
3935
+
3936
+
3937
+
3938
+
3939
+
3940
+
3941
+
3942
+
3943
+
3944
+
3945
+
3946
+
3947
+
3948
+
3949
+
3950
+
3951
+
3952
+
3953
+
3954
+
3955
+
3956
+
3957
+
3958
+
3959
+
3960
+
3961
+
3962
+
3963
+
3964
+
3965
+
3966
+
3967
+
3968
+
3969
+
3970
+
3971
+
3972
+
3973
+
3974
+
3975
+
3976
+
3977
+
3978
+
3979
+
3980
+
3981
+
3982
+
3983
+
3984
+
3985
+
3986
+
3987
+
3988
+
3989
+
3990
+
3991
+
3992
+
3993
+
3994
+
3995
+
3996
+
3997
+
3998
+
3999
+
4000
+
4001
+
4002
+
4003
+
4004
+
4005
+
4006
+
4007
+
4008
+
4009
+
4010
+
4011
+
4012
+
4013
+
4014
+
4015
+
4016
+
4017
+
4018
+
4019
+
4020
+
4021
+
4022
+
4023
+
4024
+
4025
+
4026
+
4027
+
4028
+
4029
+
4030
+
4031
+
4032
+
4033
+
4034
+
4035
+
4036
+
4037
+
4038
+
4039
+
4040
+
4041
+
4042
+
4043
+
4044
+
4045
+
4046
+
4047
+
4048
+
4049
+
4050
+
4051
+
4052
+
4053
+
4054
+
4055
+
4056
+
4057
+
4058
+
4059
+
4060
+
4061
+
4062
+
4063
+
4064
+
4065
+
4066
+
4067
+
4068
+
4069
+
4070
+
4071
+
4072
+
4073
+
4074
+
4075
+
4076
+
4077
+
4078
+ }
4079
+
4080
+
4081
+
4082
+
4083
+
4084
+
4085
+
4086
+
4087
+
4088
+
4089
+
4090
+
4091
+
4092
+
4093
+
4094
+
4095
+
4096
+
4097
+
4098
+
4099
+
4100
+
4101
+
4102
+
4103
+
4104
+
4105
+
4106
+
4107
+
4108
+
4109
+
4110
+
4111
+
4112
+
4113
+
4114
+
4115
+
4116
+
4117
+
4118
+
4119
+
4120
+
4121
+
4122
+
4123
+
4124
+
4125
+
4126
+
4127
+
4128
+ ~
4129
+
4130
+
4131
+
4132
+
4133
+
4134
+
4135
+
4136
+ Z
4137
+
4138
+
4139
+
4140
+
4141
+
4142
+
4143
+
4144
+
4145
+
4146
+
4147
+
4148
+
4149
+
4150
+
4151
+
4152
+
4153
+
4154
+
4155
+
4156
+
4157
+
4158
+
4159
+
4160
+
4161
+
4162
+
4163
+
4164
+
4165
+
4166
+
4167
+
4168
+
4169
+
4170
+
4171
+
4172
+
4173
+
4174
+
4175
+
4176
+
4177
+
4178
+
4179
+
4180
+
4181
+
4182
+
4183
+
4184
+
4185
+
4186
+
4187
+
4188
+
4189
+
4190
+
4191
+
4192
+
4193
+
4194
+
4195
+
4196
+
4197
+
4198
+
4199
+
4200
+
4201
+
4202
+
4203
+
4204
+
4205
+
4206
+
4207
+
4208
+
4209
+
4210
+
4211
+
4212
+
4213
+
4214
+
4215
+
4216
+
4217
+
4218
+
4219
+
4220
+
4221
+
4222
+
4223
+
4224
+
4225
+
4226
+
4227
+
4228
+
4229
+
4230
+
4231
+
4232
+
4233
+
4234
+
4235
+
4236
+ 槿
4237
+
4238
+
4239
+
4240
+
4241
+
4242
+
4243
+
4244
+ C
4245
+ o
4246
+
4247
+
4248
+
4249
+
4250
+
4251
+
4252
+
4253
+
4254
+
4255
+
4256
+
4257
+
4258
+
4259
+
4260
+
4261
+
4262
+
4263
+
4264
+
4265
+
4266
+
4267
+
4268
+
4269
+
4270
+
4271
+
4272
+
4273
+
4274
+
4275
+
4276
+
4277
+
4278
+
4279
+
4280
+
4281
+
4282
+
4283
+
4284
+
4285
+
4286
+
4287
+ ��
4288
+
4289
+
4290
+
4291
+
4292
+
4293
+
4294
+
4295
+
4296
+
4297
+
4298
+
4299
+
4300
+
4301
+
4302
+
4303
+
4304
+
4305
+
4306
+
4307
+
4308
+
4309
+
4310
+
4311
+
4312
+
4313
+
4314
+
4315
+
4316
+
4317
+
4318
+
4319
+
4320
+
4321
+
4322
+
4323
+
4324
+
4325
+
4326
+
4327
+
4328
+
4329
+
4330
+
4331
+
4332
+
4333
+
4334
+
4335
+
4336
+
4337
+
4338
+
4339
+
4340
+
4341
+
4342
+
4343
+
4344
+
4345
+
4346
+
4347
+
4348
+
4349
+
4350
+
4351
+
4352
+
4353
+
4354
+
4355
+
4356
+
4357
+
4358
+
4359
+
4360
+
4361
+
4362
+
4363
+
4364
+
4365
+
4366
+
4367
+
4368
+
4369
+
4370
+
4371
+
4372
+
4373
+
4374
+
4375
+
4376
+
4377
+
4378
+
4379
+
4380
+
4381
+ E
4382
+
4383
+
4384
+
4385
+
4386
+
4387
+
4388
+
4389
+ f
4390
+
4391
+
4392
+
4393
+
4394
+
4395
+
4396
+
4397
+
4398
+
4399
+
4400
+
4401
+
4402
+
4403
+
4404
+
4405
+
4406
+
4407
+
4408
+
4409
+
4410
+
4411
+
4412
+
4413
+
4414
+
4415
+
4416
+
4417
+
4418
+
4419
+
4420
+
4421
+
4422
+
4423
+
4424
+
4425
+
4426
+
4427
+
4428
+ \
4429
+
4430
+
4431
+
4432
+
4433
+
4434
+
4435
+
4436
+
4437
+
4438
+
4439
+
4440
+
4441
+
4442
+
4443
+
4444
+
4445
+
4446
+
4447
+
4448
+
4449
+
4450
+
4451
+
4452
+
4453
+
4454
+
4455
+
4456
+
4457
+
4458
+
4459
+
4460
+
4461
+
4462
+
4463
+
4464
+
4465
+
4466
+
4467
+
4468
+
4469
+
4470
+
4471
+
4472
+
4473
+ 屿
4474
+
4475
+
4476
+
4477
+
4478
+
4479
+
4480
+
4481
+
4482
+
4483
+
4484
+
4485
+
4486
+
4487
+
4488
+
4489
+
4490
+
4491
+
4492
+
4493
+
4494
+
4495
+
4496
+
4497
+ U
4498
+
4499
+
4500
+
4501
+
4502
+
4503
+
4504
+
4505
+
4506
+
4507
+
4508
+
4509
+
4510
+
4511
+
4512
+
4513
+
4514
+
4515
+
4516
+
4517
+
4518
+
4519
+
4520
+
4521
+
4522
+
4523
+
4524
+
4525
+
4526
+
4527
+
4528
+
4529
+
4530
+
4531
+
4532
+
4533
+
4534
+
4535
+
4536
+
4537
+
4538
+
4539
+
4540
+
4541
+
4542
+
4543
+
4544
+ a
4545
+ p
4546
+ y
4547
+ n
4548
+ g
4549
+
4550
+
4551
+
4552
+
4553
+
4554
+
4555
+
4556
+
4557
+
4558
+
4559
+
4560
+
4561
+
4562
+
4563
+
4564
+
4565
+
4566
+
4567
+
4568
+
4569
+
4570
+
4571
+
4572
+
4573
+
4574
+
4575
+
4576
+
4577
+
4578
+
4579
+
4580
+
4581
+
4582
+
4583
+
4584
+
4585
+
4586
+
4587
+
4588
+
4589
+
4590
+
4591
+
4592
+
4593
+
4594
+
4595
+
4596
+
4597
+
4598
+
4599
+
4600
+
4601
+
4602
+
4603
+
4604
+
4605
+
4606
+
4607
+
4608
+
4609
+
4610
+
4611
+
4612
+
4613
+
4614
+
4615
+
4616
+
4617
+
4618
+
4619
+
4620
+
4621
+
4622
+
4623
+
4624
+
4625
+
4626
+
4627
+
4628
+
4629
+
4630
+
4631
+
4632
+
4633
+
4634
+
4635
+
4636
+
4637
+
4638
+
4639
+
4640
+
4641
+
4642
+
4643
+
4644
+
4645
+
4646
+
4647
+
4648
+
4649
+
4650
+
4651
+
4652
+
4653
+
4654
+
4655
+
4656
+
4657
+
4658
+
4659
+
4660
+
4661
+
4662
+
4663
+
4664
+
4665
+
4666
+
4667
+
4668
+
4669
+
4670
+
4671
+
4672
+
4673
+
4674
+
4675
+
4676
+
4677
+
4678
+
4679
+
4680
+
4681
+
4682
+
4683
+
4684
+
4685
+
4686
+
4687
+
4688
+
4689
+
4690
+
4691
+
4692
+
4693
+
4694
+
4695
+
4696
+
4697
+
4698
+
4699
+
4700
+
4701
+
4702
+
4703
+
4704
+
4705
+
4706
+
4707
+ 竿
4708
+
4709
+
4710
+
4711
+
4712
+
4713
+
4714
+
4715
+
4716
+
4717
+
4718
+
4719
+
4720
+
4721
+
4722
+
4723
+
4724
+
4725
+
4726
+
4727
+
4728
+
4729
+
4730
+
4731
+ Q
4732
+
4733
+
4734
+
4735
+
4736
+
4737
+
4738
+
4739
+ 羿
4740
+
4741
+ O
4742
+
4743
+
4744
+
4745
+
4746
+
4747
+
4748
+
4749
+
4750
+
4751
+
4752
+
4753
+
4754
+
4755
+
4756
+
4757
+
4758
+
4759
+ 宿
4760
+
4761
+
4762
+
4763
+
4764
+
4765
+
4766
+
4767
+
4768
+
4769
+
4770
+
4771
+
4772
+
4773
+
4774
+
4775
+
4776
+
4777
+
4778
+
4779
+
4780
+
4781
+
4782
+
4783
+
4784
+
4785
+
4786
+
4787
+
4788
+
4789
+
4790
+
4791
+
4792
+
4793
+
4794
+
4795
+
4796
+
4797
+
4798
+
4799
+
4800
+
4801
+
4802
+
4803
+
4804
+
4805
+
4806
+
4807
+
4808
+
4809
+
4810
+
4811
+
4812
+
4813
+
4814
+
4815
+
4816
+
4817
+
4818
+
4819
+
4820
+
4821
+
4822
+
4823
+
4824
+
4825
+
4826
+
4827
+
4828
+
4829
+
4830
+
4831
+
4832
+
4833
+
4834
+
4835
+
4836
+
4837
+
4838
+
4839
+
4840
+
4841
+
4842
+
4843
+
4844
+
4845
+
4846
+
4847
+
4848
+
4849
+ k
4850
+
4851
+
4852
+
4853
+
4854
+
4855
+
4856
+
4857
+
4858
+
4859
+
4860
+
4861
+
4862
+
4863
+
4864
+
4865
+
4866
+
4867
+
4868
+
4869
+
4870
+
4871
+
4872
+
4873
+
4874
+
4875
+
4876
+
4877
+
4878
+
4879
+
4880
+
4881
+
4882
+
4883
+
4884
+
4885
+ $
4886
+
4887
+
4888
+
4889
+
4890
+
4891
+
4892
+
4893
+
4894
+
4895
+
4896
+
4897
+
4898
+
4899
+
4900
+
4901
+
4902
+ c
4903
+
4904
+
4905
+
4906
+
4907
+
4908
+
4909
+
4910
+
4911
+
4912
+
4913
+
4914
+
4915
+
4916
+
4917
+
4918
+
4919
+
4920
+
4921
+
4922
+ v
4923
+
4924
+
4925
+
4926
+
4927
+
4928
+
4929
+
4930
+
4931
+
4932
+
4933
+
4934
+
4935
+
4936
+
4937
+
4938
+
4939
+
4940
+
4941
+
4942
+
4943
+
4944
+
4945
+
4946
+
4947
+
4948
+
4949
+
4950
+
4951
+
4952
+
4953
+
4954
+
4955
+
4956
+
4957
+
4958
+
4959
+
4960
+
4961
+
4962
+
4963
+
4964
+
4965
+
4966
+
4967
+
4968
+
4969
+
4970
+
4971
+
4972
+
4973
+
4974
+
4975
+
4976
+
4977
+
4978
+
4979
+
4980
+
4981
+
4982
+
4983
+
4984
+
4985
+
4986
+
4987
+
4988
+
4989
+
4990
+
4991
+
4992
+
4993
+
4994
+
4995
+
4996
+
4997
+
4998
+
4999
+
5000
+
5001
+
5002
+
5003
+
5004
+
5005
+
5006
+
5007
+
5008
+
5009
+
5010
+
5011
+
5012
+
5013
+
5014
+
5015
+
5016
+
5017
+
5018
+
5019
+
5020
+
5021
+
5022
+
5023
+
5024
+
5025
+
5026
+
5027
+
5028
+
5029
+
5030
+
5031
+
5032
+
5033
+ W
5034
+
5035
+
5036
+
5037
+
5038
+
5039
+
5040
+
5041
+
5042
+
5043
+
5044
+
5045
+ 穿
5046
+
5047
+
5048
+
5049
+
5050
+
5051
+
5052
+
5053
+
5054
+
5055
+
5056
+
5057
+
5058
+
5059
+
5060
+
5061
+
5062
+
5063
+
5064
+
5065
+
5066
+
5067
+
5068
+
5069
+
5070
+
5071
+
5072
+
5073
+
5074
+
5075
+
5076
+
5077
+
5078
+
5079
+
5080
+
5081
+
5082
+
5083
+
5084
+
5085
+
5086
+ ×
5087
+
5088
+
5089
+
5090
+
5091
+
5092
+
5093
+
5094
+
5095
+
5096
+
5097
+
5098
+
5099
+ 轿
5100
+
5101
+
5102
+
5103
+
5104
+
5105
+
5106
+
5107
+
5108
+
5109
+
5110
+
5111
+
5112
+
5113
+
5114
+
5115
+
5116
+
5117
+
5118
+
5119
+
5120
+
5121
+
5122
+
5123
+
5124
+
5125
+
5126
+
5127
+ R
5128
+ G
5129
+
5130
+
5131
+
5132
+
5133
+
5134
+
5135
+
5136
+
5137
+
5138
+
5139
+
5140
+
5141
+
5142
+
5143
+
5144
+
5145
+
5146
+
5147
+
5148
+
5149
+
5150
+
5151
+
5152
+
5153
+
5154
+
5155
+
5156
+
5157
+
5158
+
5159
+
5160
+
5161
+
5162
+
5163
+
5164
+
5165
+
5166
+
5167
+
5168
+
5169
+ ˉ
5170
+
5171
+ d
5172
+ °
5173
+
5174
+
5175
+
5176
+
5177
+
5178
+
5179
+
5180
+
5181
+
5182
+
5183
+
5184
+
5185
+
5186
+
5187
+
5188
+
5189
+
5190
+
5191
+
5192
+
5193
+ K
5194
+
5195
+
5196
+
5197
+
5198
+
5199
+
5200
+ X
5201
+
5202
+
5203
+
5204
+
5205
+
5206
+
5207
+
5208
+
5209
+
5210
+
5211
+
5212
+
5213
+
5214
+
5215
+
5216
+
5217
+
5218
+
5219
+
5220
+
5221
+
5222
+
5223
+
5224
+
5225
+
5226
+
5227
+
5228
+
5229
+
5230
+
5231
+
5232
+
5233
+ m
5234
+
5235
+
5236
+
5237
+
5238
+
5239
+
5240
+
5241
+
5242
+
5243
+
5244
+ 涿
5245
+
5246
+
5247
+
5248
+
5249
+
5250
+
5251
+
5252
+
5253
+
5254
+
5255
+
5256
+
5257
+
5258
+
5259
+
5260
+
5261
+
5262
+
5263
+
5264
+
5265
+
5266
+
5267
+
5268
+
5269
+
5270
+
5271
+
5272
+
5273
+
5274
+
5275
+
5276
+
5277
+
5278
+
5279
+
5280
+
5281
+
5282
+
5283
+
5284
+
5285
+
5286
+
5287
+
5288
+
5289
+
5290
+
5291
+
5292
+
5293
+
5294
+
5295
+
5296
+
5297
+
5298
+
5299
+
5300
+
5301
+
5302
+
5303
+
5304
+
5305
+
5306
+
5307
+
5308
+
5309
+
5310
+
5311
+
5312
+
5313
+
5314
+
5315
+
5316
+
5317
+
5318
+
5319
+
5320
+
5321
+
5322
+
5323
+
5324
+
5325
+
5326
+
5327
+
5328
+
5329
+
5330
+
5331
+
5332
+
5333
+
5334
+
5335
+ `
5336
+
5337
+
5338
+
5339
+
5340
+
5341
+
5342
+
5343
+
5344
+
5345
+
5346
+
5347
+
5348
+
5349
+
5350
+
5351
+
5352
+
5353
+
5354
+
5355
+
5356
+
5357
+
5358
+
5359
+
5360
+
5361
+
5362
+
5363
+
5364
+
5365
+
5366
+
5367
+
5368
+
5369
+
5370
+
5371
+
5372
+
5373
+
5374
+
5375
+
5376
+
5377
+
5378
+
5379
+
5380
+
5381
+
5382
+
5383
+
5384
+
5385
+
5386
+
5387
+
5388
+
5389
+
5390
+
5391
+
5392
+
5393
+
5394
+
5395
+
5396
+
5397
+
5398
+
5399
+
5400
+
5401
+
5402
+
5403
+
5404
+
5405
+ V
5406
+
5407
+
5408
+
5409
+
5410
+
5411
+
5412
+
5413
+
5414
+
5415
+
5416
+
5417
+
5418
+
5419
+
5420
+
5421
+
5422
+
5423
+
5424
+
5425
+
5426
+
5427
+
5428
+
5429
+
5430
+
5431
+
5432
+
5433
+
5434
+
5435
+
5436
+
5437
+
5438
+
5439
+
5440
+
5441
+
5442
+
5443
+
5444
+
5445
+
5446
+
5447
+
5448
+
5449
+
5450
+
5451
+
5452
+
5453
+
5454
+
5455
+
5456
+
5457
+
5458
+
5459
+
5460
+
5461
+ #
5462
+
5463
+
5464
+
5465
+
5466
+
5467
+
5468
+
5469
+
5470
+
5471
+
5472
+
5473
+
5474
+
5475
+
5476
+
5477
+
5478
+
5479
+
5480
+
5481
+
5482
+
5483
+ 簿
5484
+
5485
+
5486
+
5487
+
5488
+
5489
+ {
5490
+
5491
+
5492
+
5493
+ j
5494
+
5495
+
5496
+
5497
+
5498
+
5499
+
5500
+
5501
+
5502
+
5503
+
5504
+
5505
+
5506
+
5507
+
5508
+
5509
+
5510
+
5511
+
5512
+
5513
+
5514
+
5515
+
5516
+
5517
+
5518
+
5519
+
5520
+
5521
+
5522
+
5523
+
5524
+
5525
+
5526
+
5527
+
5528
+
5529
+ ·
5530
+
5531
+
5532
+
5533
+ Ë
5534
+
5535
+
5536
+
5537
+
5538
+
5539
+
5540
+
5541
+
5542
+
5543
+
5544
+
5545
+
5546
+ ¥
5547
+
5548
+
5549
+
5550
+
5551
+
5552
+
5553
+
5554
+
5555
+
5556
+
5557
+
5558
+
5559
+
5560
+ π
5561
+
5562
+
5563
+
5564
+ é
5565
+
5566
+
5567
+ Λ
5568
+
5569
+
5570
+
5571
+
5572
+
5573
+
5574
+
5575
+
5576
+
5577
+
5578
+
5579
+
5580
+
5581
+
5582
+
5583
+
5584
+
5585
+
5586
+
5587
+
5588
+
5589
+
5590
+
5591
+
5592
+
5593
+
5594
+
5595
+
5596
+
5597
+
5598
+
5599
+
5600
+
5601
+
5602
+
5603
+
5604
+
5605
+ Ο
5606
+
5607
+
5608
+
5609
+
5610
+
5611
+
5612
+
5613
+
5614
+
5615
+
5616
+
5617
+
5618
+
5619
+
5620
+
5621
+
5622
+
5623
+
5624
+
5625
+
5626
+
5627
+
5628
+
5629
+
5630
+
5631
+
5632
+
5633
+
5634
+
5635
+
5636
+
5637
+
5638
+
5639
+
5640
+
5641
+
5642
+
5643
+
5644
+
5645
+
5646
+
5647
+
5648
+
5649
+
5650
+
5651
+
5652
+
5653
+
5654
+
5655
+
5656
+
5657
+
5658
+
5659
+
5660
+
5661
+
5662
+
5663
+
5664
+
5665
+
5666
+
5667
+
5668
+
5669
+
5670
+
5671
+
5672
+
5673
+
5674
+ α
5675
+
5676
+
5677
+
5678
+
5679
+
5680
+
5681
+
5682
+
5683
+
5684
+
5685
+
5686
+
5687
+
5688
+
5689
+
5690
+
5691
+
5692
+
5693
+
5694
+
5695
+
5696
+
5697
+
5698
+
5699
+
5700
+
5701
+
5702
+
5703
+
5704
+
5705
+
5706
+
5707
+
5708
+
5709
+
5710
+  
5711
+
5712
+
5713
+
5714
+
5715
+
5716
+
5717
+
5718
+
5719
+
5720
+
5721
+
5722
+
5723
+
5724
+
5725
+
5726
+
5727
+
5728
+ 鴿
5729
+
5730
+
5731
+
5732
+
5733
+
5734
+
5735
+
5736
+
5737
+
5738
+
5739
+
5740
+
5741
+
5742
+
5743
+
5744
+
5745
+
5746
+
5747
+
5748
+
5749
+
5750
+
5751
+
5752
+
5753
+
5754
+
5755
+
5756
+
5757
+
5758
+
5759
+
5760
+
5761
+
5762
+
5763
+
5764
+
5765
+
5766
+
5767
+
5768
+
5769
+
5770
+
5771
+
5772
+
5773
+
5774
+
5775
+
5776
+
5777
+
5778
+
5779
+
5780
+
5781
+
5782
+
5783
+
5784
+
5785
+
5786
+
5787
+
5788
+
5789
+
5790
+
5791
+
5792
+
5793
+
5794
+
5795
+
5796
+
5797
+
5798
+
5799
+
5800
+ è
5801
+
5802
+
5803
+
5804
+
5805
+
5806
+ Ü
5807
+
5808
+
5809
+
5810
+
5811
+
5812
+
5813
+
5814
+
5815
+
5816
+
5817
+ И
5818
+
5819
+
5820
+
5821
+
5822
+
5823
+
5824
+
5825
+
5826
+
5827
+
5828
+
5829
+
5830
+
5831
+
5832
+
5833
+
5834
+
5835
+
5836
+
5837
+
5838
+ »
5839
+
5840
+
5841
+ ä
5842
+
5843
+
5844
+
5845
+
5846
+
5847
+
5848
+
5849
+
5850
+
5851
+
5852
+
5853
+
5854
+
5855
+
5856
+
5857
+
5858
+
5859
+
5860
+
5861
+
5862
+
5863
+
5864
+
5865
+
5866
+
5867
+
5868
+
5869
+
5870
+
5871
+
5872
+
5873
+
5874
+
5875
+
5876
+ ɔ
5877
+
5878
+
5879
+
5880
+
5881
+
5882
+
5883
+ ´
5884
+
5885
+
5886
+
5887
+
5888
+ í
5889
+
5890
+
5891
+
5892
+
5893
+
5894
+
5895
+
5896
+
5897
+
5898
+
5899
+
5900
+
5901
+
5902
+
5903
+
5904
+
5905
+
5906
+
5907
+
5908
+
5909
+ É
5910
+
5911
+
5912
+
5913
+
5914
+ ʌ
5915
+
5916
+
5917
+
5918
+
5919
+
5920
+
5921
+
5922
+
5923
+
5924
+
5925
+ Я
5926
+ Й
5927
+
5928
+
5929
+
5930
+
5931
+
5932
+
5933
+
5934
+
5935
+
5936
+
5937
+
5938
+
5939
+
5940
+
5941
+
5942
+
5943
+
5944
+
5945
+
5946
+
5947
+
5948
+ 粿
5949
+
5950
+
5951
+
5952
+
5953
+ ®
5954
+
5955
+
5956
+
5957
+
5958
+
5959
+
5960
+
5961
+
5962
+
5963
+
5964
+
5965
+
5966
+ З
5967
+
5968
+
5969
+
5970
+
5971
+
5972
+
5973
+
5974
+
5975
+
5976
+ β
5977
+
5978
+ á
5979
+
5980
+
5981
+
5982
+
5983
+
5984
+
5985
+
5986
+
5987
+
5988
+
5989
+
5990
+
5991
+
5992
+
5993
+
5994
+
5995
+
5996
+
5997
+
5998
+
5999
+
6000
+
6001
+
6002
+
6003
+
6004
+
6005
+
6006
+
6007
+
6008
+
6009
+
6010
+
6011
+
6012
+
6013
+
6014
+
6015
+
6016
+
6017
+
6018
+
6019
+
6020
+
6021
+
6022
+
6023
+
6024
+
6025
+
6026
+
6027
+
6028
+
6029
+
6030
+
6031
+
6032
+
6033
+
6034
+
6035
+
6036
+
6037
+
6038
+
6039
+
6040
+
6041
+
6042
+
6043
+
6044
+
6045
+
6046
+
6047
+
6048
+
6049
+
6050
+
6051
+
6052
+
6053
+
6054
+
6055
+
6056
+
6057
+
6058
+
6059
+
6060
+
6061
+
6062
+
6063
+
6064
+
6065
+
6066
+ Ó
6067
+
6068
+
6069
+
6070
+
6071
+
6072
+
6073
+
6074
+
6075
+
6076
+
6077
+
6078
+
6079
+
6080
+
6081
+
6082
+
6083
+
6084
+
6085
+
6086
+
6087
+
6088
+
6089
+
6090
+
6091
+
6092
+
6093
+
6094
+
6095
+
6096
+ ò
6097
+
6098
+
6099
+
6100
+
6101
+
6102
+
6103
+
6104
+
6105
+
6106
+
6107
+
6108
+
6109
+
6110
+
6111
+
6112
+
6113
+
6114
+
6115
+
6116
+
6117
+
6118
+
6119
+
6120
+
6121
+
6122
+
6123
+
6124
+
6125
+ 貿
6126
+
6127
+
6128
+
6129
+
6130
+
6131
+
6132
+
6133
+
6134
+
6135
+
6136
+
6137
+
6138
+ 𣇉
6139
+
6140
+
6141
+
6142
+
6143
+
6144
+
6145
+
6146
+
6147
+
6148
+
6149
+
6150
+
6151
+
6152
+
6153
+
6154
+
6155
+
6156
+
6157
+
6158
+
6159
+
6160
+
6161
+
6162
+
6163
+
6164
+
6165
+
6166
+
6167
+ г
6168
+
6169
+
6170
+
6171
+
6172
+
6173
+
6174
+
6175
+
6176
+
6177
+
6178
+
6179
+
6180
+
6181
+
6182
+
6183
+
6184
+
6185
+
6186
+
6187
+
6188
+
6189
+
6190
+
6191
+ 楿
6192
+
6193
+
6194
+
6195
+
6196
+
6197
+
6198
+ 滿
6199
+
6200
+
6201
+
6202
+
6203
+
6204
+
6205
+
6206
+
6207
+
6208
+
6209
+
6210
+
6211
+
6212
+
6213
+
6214
+
6215
+
6216
+
6217
+
6218
+
6219
+
6220
+
6221
+
6222
+
6223
+
6224
+
6225
+
6226
+
6227
+
6228
+
6229
+
6230
+
6231
+
6232
+
6233
+
6234
+
6235
+
6236
+
6237
+
6238
+
6239
+
6240
+
6241
+
6242
+
6243
+
6244
+
6245
+
6246
+
6247
+
6248
+
6249
+
6250
+
6251
+
6252
+
6253
+
6254
+ Φ
6255
+
6256
+
6257
+
6258
+
6259
+
6260
+
6261
+ ε
6262
+
6263
+
6264
+
6265
+
6266
+
6267
+
6268
+
6269
+
6270
+
6271
+
6272
+
6273
+
6274
+ ü
6275
+
6276
+
6277
+
6278
+
6279
+ 調
6280
+
6281
+
6282
+
6283
+
6284
+
6285
+
6286
+
6287
+
6288
+
6289
+
6290
+
6291
+
6292
+
6293
+
6294
+
6295
+
6296
+
6297
+
6298
+
6299
+
6300
+
6301
+
6302
+
6303
+
6304
+
6305
+
6306
+
6307
+
6308
+
6309
+
6310
+
6311
+
6312
+
6313
+
6314
+
6315
+
6316
+
6317
+
6318
+
6319
+
6320
+
6321
+
6322
+
6323
+
6324
+
6325
+
6326
+ ˋ
6327
+
6328
+
6329
+ ā
6330
+
6331
+
6332
+
6333
+
6334
+
6335
+
6336
+
6337
+
6338
+
6339
+
6340
+
6341
+
6342
+
6343
+
6344
+
6345
+
6346
+
6347
+
6348
+
6349
+
6350
+
6351
+
6352
+
6353
+
6354
+
6355
+
6356
+
6357
+
6358
+
6359
+
6360
+
6361
+
6362
+
6363
+
6364
+
6365
+
6366
+
6367
+
6368
+
6369
+ ú
6370
+ ó
6371
+
6372
+
6373
+
6374
+
6375
+
6376
+
6377
+
6378
+
6379
+
6380
+
6381
+
6382
+
6383
+
6384
+
6385
+
6386
+
6387
+
6388
+
6389
+
6390
+ ē
6391
+
6392
+
6393
+
6394
+
6395
+
6396
+
6397
+
6398
+
6399
+
6400
+
6401
+
6402
+
6403
+
6404
+
6405
+
6406
+
6407
+
6408
+
6409
+
6410
+
6411
+
6412
+ Ω
6413
+
6414
+
6415
+
6416
+
6417
+
6418
+
6419
+
6420
+
6421
+
6422
+
6423
+
6424
+
6425
+
6426
+
6427
+
6428
+
6429
+
6430
+
6431
+
6432
+
6433
+
6434
+
6435
+
6436
+
6437
+ П
6438
+
6439
+
6440
+
6441
+
6442
+
6443
+
6444
+
6445
+
6446
+
6447
+
6448
+
6449
+
6450
+
6451
+
6452
+
6453
+
6454
+
6455
+
6456
+
6457
+
6458
+
6459
+
6460
+ ǐ
6461
+ ō
6462
+ ǒ
6463
+
6464
+
6465
+
6466
+ μ
6467
+
6468
+
6469
+
6470
+
6471
+
6472
+
6473
+
6474
+
6475
+ à
6476
+ ɡ
6477
+
6478
+
6479
+
6480
+
6481
+
6482
+
6483
+
6484
+
6485
+ ī
6486
+
6487
+
6488
+
6489
+
6490
+
6491
+
6492
+
6493
+
6494
+
6495
+
6496
+
6497
+
6498
+
6499
+
6500
+
6501
+
6502
+
6503
+
6504
+
6505
+
6506
+
6507
+
6508
+
6509
+
6510
+
6511
+
6512
+
6513
+
6514
+
6515
+
6516
+
6517
+
6518
+
6519
+
6520
+
6521
+
6522
+
6523
+
6524
+
6525
+
6526
+
6527
+
6528
+
6529
+
6530
+
6531
+
6532
+
6533
+
6534
+
6535
+
6536
+
6537
+
6538
+
6539
+
6540
+
6541
+ ²
6542
+
6543
+
6544
+
6545
+
6546
+
6547
+
6548
+
6549
+
6550
+
6551
+
6552
+
6553
+
6554
+
6555
+
6556
+
6557
+
6558
+
6559
+
6560
+
6561
+
6562
+
6563
+
6564
+
6565
+
6566
+
6567
+
6568
+
6569
+
6570
+
6571
+
6572
+
6573
+
6574
+
6575
+
6576
+
6577
+
6578
+
6579
+
6580
+
6581
+
6582
+ 駿
6583
+
6584
+
6585
+
6586
+
6587
+
6588
+
6589
+
6590
+
6591
+
6592
+
6593
+
6594
+
6595
+
6596
+
6597
+
6598
+
6599
+
6600
+
6601
+
6602
+
6603
+
6604
+
6605
+
6606
+
6607
+
6608
+
6609
+ θ
6610
+
6611
+
6612
+
6613
+ ū
6614
+ ì
6615
+
6616
+
6617
+
6618
+
6619
+
6620
+
6621
+
6622
+
6623
+
deepdoc/visual/operators.py ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #
2
+ # Copyright 2024 The InfiniFlow Authors. All Rights Reserved.
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 at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # 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 and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import sys
18
+ import six
19
+ import cv2
20
+ import numpy as np
21
+ import math
22
+ from PIL import Image
23
+
24
+
25
+ class DecodeImage(object):
26
+ """ decode image """
27
+
28
+ def __init__(self,
29
+ img_mode='RGB',
30
+ channel_first=False,
31
+ ignore_orientation=False,
32
+ **kwargs):
33
+ self.img_mode = img_mode
34
+ self.channel_first = channel_first
35
+ self.ignore_orientation = ignore_orientation
36
+
37
+ def __call__(self, data):
38
+ img = data['image']
39
+ if six.PY2:
40
+ assert isinstance(img, str) and len(
41
+ img) > 0, "invalid input 'img' in DecodeImage"
42
+ else:
43
+ assert isinstance(img, bytes) and len(
44
+ img) > 0, "invalid input 'img' in DecodeImage"
45
+ img = np.frombuffer(img, dtype='uint8')
46
+ if self.ignore_orientation:
47
+ img = cv2.imdecode(img, cv2.IMREAD_IGNORE_ORIENTATION |
48
+ cv2.IMREAD_COLOR)
49
+ else:
50
+ img = cv2.imdecode(img, 1)
51
+ if img is None:
52
+ return None
53
+ if self.img_mode == 'GRAY':
54
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
55
+ elif self.img_mode == 'RGB':
56
+ assert img.shape[2] == 3, 'invalid shape of image[%s]' % (
57
+ img.shape)
58
+ img = img[:, :, ::-1]
59
+
60
+ if self.channel_first:
61
+ img = img.transpose((2, 0, 1))
62
+
63
+ data['image'] = img
64
+ return data
65
+
66
+ class StandardizeImage(object):
67
+ """normalize image
68
+ Args:
69
+ mean (list): im - mean
70
+ std (list): im / std
71
+ is_scale (bool): whether need im / 255
72
+ norm_type (str): type in ['mean_std', 'none']
73
+ """
74
+
75
+ def __init__(self, mean, std, is_scale=True, norm_type='mean_std'):
76
+ self.mean = mean
77
+ self.std = std
78
+ self.is_scale = is_scale
79
+ self.norm_type = norm_type
80
+
81
+ def __call__(self, im, im_info):
82
+ """
83
+ Args:
84
+ im (np.ndarray): image (np.ndarray)
85
+ im_info (dict): info of image
86
+ Returns:
87
+ im (np.ndarray): processed image (np.ndarray)
88
+ im_info (dict): info of processed image
89
+ """
90
+ im = im.astype(np.float32, copy=False)
91
+ if self.is_scale:
92
+ scale = 1.0 / 255.0
93
+ im *= scale
94
+
95
+ if self.norm_type == 'mean_std':
96
+ mean = np.array(self.mean)[np.newaxis, np.newaxis, :]
97
+ std = np.array(self.std)[np.newaxis, np.newaxis, :]
98
+ im -= mean
99
+ im /= std
100
+ return im, im_info
101
+
102
+
103
+ class NormalizeImage(object):
104
+ """ normalize image such as substract mean, divide std
105
+ """
106
+
107
+ def __init__(self, scale=None, mean=None, std=None, order='chw', **kwargs):
108
+ if isinstance(scale, str):
109
+ scale = eval(scale)
110
+ self.scale = np.float32(scale if scale is not None else 1.0 / 255.0)
111
+ mean = mean if mean is not None else [0.485, 0.456, 0.406]
112
+ std = std if std is not None else [0.229, 0.224, 0.225]
113
+
114
+ shape = (3, 1, 1) if order == 'chw' else (1, 1, 3)
115
+ self.mean = np.array(mean).reshape(shape).astype('float32')
116
+ self.std = np.array(std).reshape(shape).astype('float32')
117
+
118
+ def __call__(self, data):
119
+ img = data['image']
120
+ from PIL import Image
121
+ if isinstance(img, Image.Image):
122
+ img = np.array(img)
123
+ assert isinstance(img,
124
+ np.ndarray), "invalid input 'img' in NormalizeImage"
125
+ data['image'] = (
126
+ img.astype('float32') * self.scale - self.mean) / self.std
127
+ return data
128
+
129
+
130
+ class ToCHWImage(object):
131
+ """ convert hwc image to chw image
132
+ """
133
+
134
+ def __init__(self, **kwargs):
135
+ pass
136
+
137
+ def __call__(self, data):
138
+ img = data['image']
139
+ from PIL import Image
140
+ if isinstance(img, Image.Image):
141
+ img = np.array(img)
142
+ data['image'] = img.transpose((2, 0, 1))
143
+ return data
144
+
145
+
146
+ class Fasttext(object):
147
+ def __init__(self, path="None", **kwargs):
148
+ import fasttext
149
+ self.fast_model = fasttext.load_model(path)
150
+
151
+ def __call__(self, data):
152
+ label = data['label']
153
+ fast_label = self.fast_model[label]
154
+ data['fast_label'] = fast_label
155
+ return data
156
+
157
+
158
+ class KeepKeys(object):
159
+ def __init__(self, keep_keys, **kwargs):
160
+ self.keep_keys = keep_keys
161
+
162
+ def __call__(self, data):
163
+ data_list = []
164
+ for key in self.keep_keys:
165
+ data_list.append(data[key])
166
+ return data_list
167
+
168
+
169
+ class Pad(object):
170
+ def __init__(self, size=None, size_div=32, **kwargs):
171
+ if size is not None and not isinstance(size, (int, list, tuple)):
172
+ raise TypeError("Type of target_size is invalid. Now is {}".format(
173
+ type(size)))
174
+ if isinstance(size, int):
175
+ size = [size, size]
176
+ self.size = size
177
+ self.size_div = size_div
178
+
179
+ def __call__(self, data):
180
+
181
+ img = data['image']
182
+ img_h, img_w = img.shape[0], img.shape[1]
183
+ if self.size:
184
+ resize_h2, resize_w2 = self.size
185
+ assert (
186
+ img_h < resize_h2 and img_w < resize_w2
187
+ ), '(h, w) of target size should be greater than (img_h, img_w)'
188
+ else:
189
+ resize_h2 = max(
190
+ int(math.ceil(img.shape[0] / self.size_div) * self.size_div),
191
+ self.size_div)
192
+ resize_w2 = max(
193
+ int(math.ceil(img.shape[1] / self.size_div) * self.size_div),
194
+ self.size_div)
195
+ img = cv2.copyMakeBorder(
196
+ img,
197
+ 0,
198
+ resize_h2 - img_h,
199
+ 0,
200
+ resize_w2 - img_w,
201
+ cv2.BORDER_CONSTANT,
202
+ value=0)
203
+ data['image'] = img
204
+ return data
205
+
206
+
207
+ class LinearResize(object):
208
+ """resize image by target_size and max_size
209
+ Args:
210
+ target_size (int): the target size of image
211
+ keep_ratio (bool): whether keep_ratio or not, default true
212
+ interp (int): method of resize
213
+ """
214
+
215
+ def __init__(self, target_size, keep_ratio=True, interp=cv2.INTER_LINEAR):
216
+ if isinstance(target_size, int):
217
+ target_size = [target_size, target_size]
218
+ self.target_size = target_size
219
+ self.keep_ratio = keep_ratio
220
+ self.interp = interp
221
+
222
+ def __call__(self, im, im_info):
223
+ """
224
+ Args:
225
+ im (np.ndarray): image (np.ndarray)
226
+ im_info (dict): info of image
227
+ Returns:
228
+ im (np.ndarray): processed image (np.ndarray)
229
+ im_info (dict): info of processed image
230
+ """
231
+ assert len(self.target_size) == 2
232
+ assert self.target_size[0] > 0 and self.target_size[1] > 0
233
+ im_channel = im.shape[2]
234
+ im_scale_y, im_scale_x = self.generate_scale(im)
235
+ im = cv2.resize(
236
+ im,
237
+ None,
238
+ None,
239
+ fx=im_scale_x,
240
+ fy=im_scale_y,
241
+ interpolation=self.interp)
242
+ im_info['im_shape'] = np.array(im.shape[:2]).astype('float32')
243
+ im_info['scale_factor'] = np.array(
244
+ [im_scale_y, im_scale_x]).astype('float32')
245
+ return im, im_info
246
+
247
+ def generate_scale(self, im):
248
+ """
249
+ Args:
250
+ im (np.ndarray): image (np.ndarray)
251
+ Returns:
252
+ im_scale_x: the resize ratio of X
253
+ im_scale_y: the resize ratio of Y
254
+ """
255
+ origin_shape = im.shape[:2]
256
+ im_c = im.shape[2]
257
+ if self.keep_ratio:
258
+ im_size_min = np.min(origin_shape)
259
+ im_size_max = np.max(origin_shape)
260
+ target_size_min = np.min(self.target_size)
261
+ target_size_max = np.max(self.target_size)
262
+ im_scale = float(target_size_min) / float(im_size_min)
263
+ if np.round(im_scale * im_size_max) > target_size_max:
264
+ im_scale = float(target_size_max) / float(im_size_max)
265
+ im_scale_x = im_scale
266
+ im_scale_y = im_scale
267
+ else:
268
+ resize_h, resize_w = self.target_size
269
+ im_scale_y = resize_h / float(origin_shape[0])
270
+ im_scale_x = resize_w / float(origin_shape[1])
271
+ return im_scale_y, im_scale_x
272
+
273
+
274
+ class Resize(object):
275
+ def __init__(self, size=(640, 640), **kwargs):
276
+ self.size = size
277
+
278
+ def resize_image(self, img):
279
+ resize_h, resize_w = self.size
280
+ ori_h, ori_w = img.shape[:2] # (h, w, c)
281
+ ratio_h = float(resize_h) / ori_h
282
+ ratio_w = float(resize_w) / ori_w
283
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
284
+ return img, [ratio_h, ratio_w]
285
+
286
+ def __call__(self, data):
287
+ img = data['image']
288
+ if 'polys' in data:
289
+ text_polys = data['polys']
290
+
291
+ img_resize, [ratio_h, ratio_w] = self.resize_image(img)
292
+ if 'polys' in data:
293
+ new_boxes = []
294
+ for box in text_polys:
295
+ new_box = []
296
+ for cord in box:
297
+ new_box.append([cord[0] * ratio_w, cord[1] * ratio_h])
298
+ new_boxes.append(new_box)
299
+ data['polys'] = np.array(new_boxes, dtype=np.float32)
300
+ data['image'] = img_resize
301
+ return data
302
+
303
+
304
+ class DetResizeForTest(object):
305
+ def __init__(self, **kwargs):
306
+ super(DetResizeForTest, self).__init__()
307
+ self.resize_type = 0
308
+ self.keep_ratio = False
309
+ if 'image_shape' in kwargs:
310
+ self.image_shape = kwargs['image_shape']
311
+ self.resize_type = 1
312
+ if 'keep_ratio' in kwargs:
313
+ self.keep_ratio = kwargs['keep_ratio']
314
+ elif 'limit_side_len' in kwargs:
315
+ self.limit_side_len = kwargs['limit_side_len']
316
+ self.limit_type = kwargs.get('limit_type', 'min')
317
+ elif 'resize_long' in kwargs:
318
+ self.resize_type = 2
319
+ self.resize_long = kwargs.get('resize_long', 960)
320
+ else:
321
+ self.limit_side_len = 736
322
+ self.limit_type = 'min'
323
+
324
+ def __call__(self, data):
325
+ img = data['image']
326
+ src_h, src_w, _ = img.shape
327
+ if sum([src_h, src_w]) < 64:
328
+ img = self.image_padding(img)
329
+
330
+ if self.resize_type == 0:
331
+ # img, shape = self.resize_image_type0(img)
332
+ img, [ratio_h, ratio_w] = self.resize_image_type0(img)
333
+ elif self.resize_type == 2:
334
+ img, [ratio_h, ratio_w] = self.resize_image_type2(img)
335
+ else:
336
+ # img, shape = self.resize_image_type1(img)
337
+ img, [ratio_h, ratio_w] = self.resize_image_type1(img)
338
+ data['image'] = img
339
+ data['shape'] = np.array([src_h, src_w, ratio_h, ratio_w])
340
+ return data
341
+
342
+ def image_padding(self, im, value=0):
343
+ h, w, c = im.shape
344
+ im_pad = np.zeros((max(32, h), max(32, w), c), np.uint8) + value
345
+ im_pad[:h, :w, :] = im
346
+ return im_pad
347
+
348
+ def resize_image_type1(self, img):
349
+ resize_h, resize_w = self.image_shape
350
+ ori_h, ori_w = img.shape[:2] # (h, w, c)
351
+ if self.keep_ratio is True:
352
+ resize_w = ori_w * resize_h / ori_h
353
+ N = math.ceil(resize_w / 32)
354
+ resize_w = N * 32
355
+ ratio_h = float(resize_h) / ori_h
356
+ ratio_w = float(resize_w) / ori_w
357
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
358
+ # return img, np.array([ori_h, ori_w])
359
+ return img, [ratio_h, ratio_w]
360
+
361
+ def resize_image_type0(self, img):
362
+ """
363
+ resize image to a size multiple of 32 which is required by the network
364
+ args:
365
+ img(array): array with shape [h, w, c]
366
+ return(tuple):
367
+ img, (ratio_h, ratio_w)
368
+ """
369
+ limit_side_len = self.limit_side_len
370
+ h, w, c = img.shape
371
+
372
+ # limit the max side
373
+ if self.limit_type == 'max':
374
+ if max(h, w) > limit_side_len:
375
+ if h > w:
376
+ ratio = float(limit_side_len) / h
377
+ else:
378
+ ratio = float(limit_side_len) / w
379
+ else:
380
+ ratio = 1.
381
+ elif self.limit_type == 'min':
382
+ if min(h, w) < limit_side_len:
383
+ if h < w:
384
+ ratio = float(limit_side_len) / h
385
+ else:
386
+ ratio = float(limit_side_len) / w
387
+ else:
388
+ ratio = 1.
389
+ elif self.limit_type == 'resize_long':
390
+ ratio = float(limit_side_len) / max(h, w)
391
+ else:
392
+ raise Exception('not support limit type, image ')
393
+ resize_h = int(h * ratio)
394
+ resize_w = int(w * ratio)
395
+
396
+ resize_h = max(int(round(resize_h / 32) * 32), 32)
397
+ resize_w = max(int(round(resize_w / 32) * 32), 32)
398
+
399
+ try:
400
+ if int(resize_w) <= 0 or int(resize_h) <= 0:
401
+ return None, (None, None)
402
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
403
+ except BaseException:
404
+ print(img.shape, resize_w, resize_h)
405
+ sys.exit(0)
406
+ ratio_h = resize_h / float(h)
407
+ ratio_w = resize_w / float(w)
408
+ return img, [ratio_h, ratio_w]
409
+
410
+ def resize_image_type2(self, img):
411
+ h, w, _ = img.shape
412
+
413
+ resize_w = w
414
+ resize_h = h
415
+
416
+ if resize_h > resize_w:
417
+ ratio = float(self.resize_long) / resize_h
418
+ else:
419
+ ratio = float(self.resize_long) / resize_w
420
+
421
+ resize_h = int(resize_h * ratio)
422
+ resize_w = int(resize_w * ratio)
423
+
424
+ max_stride = 128
425
+ resize_h = (resize_h + max_stride - 1) // max_stride * max_stride
426
+ resize_w = (resize_w + max_stride - 1) // max_stride * max_stride
427
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
428
+ ratio_h = resize_h / float(h)
429
+ ratio_w = resize_w / float(w)
430
+
431
+ return img, [ratio_h, ratio_w]
432
+
433
+
434
+ class E2EResizeForTest(object):
435
+ def __init__(self, **kwargs):
436
+ super(E2EResizeForTest, self).__init__()
437
+ self.max_side_len = kwargs['max_side_len']
438
+ self.valid_set = kwargs['valid_set']
439
+
440
+ def __call__(self, data):
441
+ img = data['image']
442
+ src_h, src_w, _ = img.shape
443
+ if self.valid_set == 'totaltext':
444
+ im_resized, [ratio_h, ratio_w] = self.resize_image_for_totaltext(
445
+ img, max_side_len=self.max_side_len)
446
+ else:
447
+ im_resized, (ratio_h, ratio_w) = self.resize_image(
448
+ img, max_side_len=self.max_side_len)
449
+ data['image'] = im_resized
450
+ data['shape'] = np.array([src_h, src_w, ratio_h, ratio_w])
451
+ return data
452
+
453
+ def resize_image_for_totaltext(self, im, max_side_len=512):
454
+
455
+ h, w, _ = im.shape
456
+ resize_w = w
457
+ resize_h = h
458
+ ratio = 1.25
459
+ if h * ratio > max_side_len:
460
+ ratio = float(max_side_len) / resize_h
461
+ resize_h = int(resize_h * ratio)
462
+ resize_w = int(resize_w * ratio)
463
+
464
+ max_stride = 128
465
+ resize_h = (resize_h + max_stride - 1) // max_stride * max_stride
466
+ resize_w = (resize_w + max_stride - 1) // max_stride * max_stride
467
+ im = cv2.resize(im, (int(resize_w), int(resize_h)))
468
+ ratio_h = resize_h / float(h)
469
+ ratio_w = resize_w / float(w)
470
+ return im, (ratio_h, ratio_w)
471
+
472
+ def resize_image(self, im, max_side_len=512):
473
+ """
474
+ resize image to a size multiple of max_stride which is required by the network
475
+ :param im: the resized image
476
+ :param max_side_len: limit of max image size to avoid out of memory in gpu
477
+ :return: the resized image and the resize ratio
478
+ """
479
+ h, w, _ = im.shape
480
+
481
+ resize_w = w
482
+ resize_h = h
483
+
484
+ # Fix the longer side
485
+ if resize_h > resize_w:
486
+ ratio = float(max_side_len) / resize_h
487
+ else:
488
+ ratio = float(max_side_len) / resize_w
489
+
490
+ resize_h = int(resize_h * ratio)
491
+ resize_w = int(resize_w * ratio)
492
+
493
+ max_stride = 128
494
+ resize_h = (resize_h + max_stride - 1) // max_stride * max_stride
495
+ resize_w = (resize_w + max_stride - 1) // max_stride * max_stride
496
+ im = cv2.resize(im, (int(resize_w), int(resize_h)))
497
+ ratio_h = resize_h / float(h)
498
+ ratio_w = resize_w / float(w)
499
+
500
+ return im, (ratio_h, ratio_w)
501
+
502
+
503
+ class KieResize(object):
504
+ def __init__(self, **kwargs):
505
+ super(KieResize, self).__init__()
506
+ self.max_side, self.min_side = kwargs['img_scale'][0], kwargs[
507
+ 'img_scale'][1]
508
+
509
+ def __call__(self, data):
510
+ img = data['image']
511
+ points = data['points']
512
+ src_h, src_w, _ = img.shape
513
+ im_resized, scale_factor, [ratio_h, ratio_w
514
+ ], [new_h, new_w] = self.resize_image(img)
515
+ resize_points = self.resize_boxes(img, points, scale_factor)
516
+ data['ori_image'] = img
517
+ data['ori_boxes'] = points
518
+ data['points'] = resize_points
519
+ data['image'] = im_resized
520
+ data['shape'] = np.array([new_h, new_w])
521
+ return data
522
+
523
+ def resize_image(self, img):
524
+ norm_img = np.zeros([1024, 1024, 3], dtype='float32')
525
+ scale = [512, 1024]
526
+ h, w = img.shape[:2]
527
+ max_long_edge = max(scale)
528
+ max_short_edge = min(scale)
529
+ scale_factor = min(max_long_edge / max(h, w),
530
+ max_short_edge / min(h, w))
531
+ resize_w, resize_h = int(w * float(scale_factor) + 0.5), int(h * float(
532
+ scale_factor) + 0.5)
533
+ max_stride = 32
534
+ resize_h = (resize_h + max_stride - 1) // max_stride * max_stride
535
+ resize_w = (resize_w + max_stride - 1) // max_stride * max_stride
536
+ im = cv2.resize(img, (resize_w, resize_h))
537
+ new_h, new_w = im.shape[:2]
538
+ w_scale = new_w / w
539
+ h_scale = new_h / h
540
+ scale_factor = np.array(
541
+ [w_scale, h_scale, w_scale, h_scale], dtype=np.float32)
542
+ norm_img[:new_h, :new_w, :] = im
543
+ return norm_img, scale_factor, [h_scale, w_scale], [new_h, new_w]
544
+
545
+ def resize_boxes(self, im, points, scale_factor):
546
+ points = points * scale_factor
547
+ img_shape = im.shape[:2]
548
+ points[:, 0::2] = np.clip(points[:, 0::2], 0, img_shape[1])
549
+ points[:, 1::2] = np.clip(points[:, 1::2], 0, img_shape[0])
550
+ return points
551
+
552
+
553
+ class SRResize(object):
554
+ def __init__(self,
555
+ imgH=32,
556
+ imgW=128,
557
+ down_sample_scale=4,
558
+ keep_ratio=False,
559
+ min_ratio=1,
560
+ mask=False,
561
+ infer_mode=False,
562
+ **kwargs):
563
+ self.imgH = imgH
564
+ self.imgW = imgW
565
+ self.keep_ratio = keep_ratio
566
+ self.min_ratio = min_ratio
567
+ self.down_sample_scale = down_sample_scale
568
+ self.mask = mask
569
+ self.infer_mode = infer_mode
570
+
571
+ def __call__(self, data):
572
+ imgH = self.imgH
573
+ imgW = self.imgW
574
+ images_lr = data["image_lr"]
575
+ transform2 = ResizeNormalize(
576
+ (imgW // self.down_sample_scale, imgH // self.down_sample_scale))
577
+ images_lr = transform2(images_lr)
578
+ data["img_lr"] = images_lr
579
+ if self.infer_mode:
580
+ return data
581
+
582
+ images_HR = data["image_hr"]
583
+ label_strs = data["label"]
584
+ transform = ResizeNormalize((imgW, imgH))
585
+ images_HR = transform(images_HR)
586
+ data["img_hr"] = images_HR
587
+ return data
588
+
589
+
590
+ class ResizeNormalize(object):
591
+ def __init__(self, size, interpolation=Image.BICUBIC):
592
+ self.size = size
593
+ self.interpolation = interpolation
594
+
595
+ def __call__(self, img):
596
+ img = img.resize(self.size, self.interpolation)
597
+ img_numpy = np.array(img).astype("float32")
598
+ img_numpy = img_numpy.transpose((2, 0, 1)) / 255
599
+ return img_numpy
600
+
601
+
602
+ class GrayImageChannelFormat(object):
603
+ """
604
+ format gray scale image's channel: (3,h,w) -> (1,h,w)
605
+ Args:
606
+ inverse: inverse gray image
607
+ """
608
+
609
+ def __init__(self, inverse=False, **kwargs):
610
+ self.inverse = inverse
611
+
612
+ def __call__(self, data):
613
+ img = data['image']
614
+ img_single_channel = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
615
+ img_expanded = np.expand_dims(img_single_channel, 0)
616
+
617
+ if self.inverse:
618
+ data['image'] = np.abs(img_expanded - 1)
619
+ else:
620
+ data['image'] = img_expanded
621
+
622
+ data['src_image'] = img
623
+ return data
624
+
625
+
626
+ class Permute(object):
627
+ """permute image
628
+ Args:
629
+ to_bgr (bool): whether convert RGB to BGR
630
+ channel_first (bool): whether convert HWC to CHW
631
+ """
632
+
633
+ def __init__(self, ):
634
+ super(Permute, self).__init__()
635
+
636
+ def __call__(self, im, im_info):
637
+ """
638
+ Args:
639
+ im (np.ndarray): image (np.ndarray)
640
+ im_info (dict): info of image
641
+ Returns:
642
+ im (np.ndarray): processed image (np.ndarray)
643
+ im_info (dict): info of processed image
644
+ """
645
+ im = im.transpose((2, 0, 1)).copy()
646
+ return im, im_info
647
+
648
+
649
+ class PadStride(object):
650
+ """ padding image for model with FPN, instead PadBatch(pad_to_stride) in original config
651
+ Args:
652
+ stride (bool): model with FPN need image shape % stride == 0
653
+ """
654
+
655
+ def __init__(self, stride=0):
656
+ self.coarsest_stride = stride
657
+
658
+ def __call__(self, im, im_info):
659
+ """
660
+ Args:
661
+ im (np.ndarray): image (np.ndarray)
662
+ im_info (dict): info of image
663
+ Returns:
664
+ im (np.ndarray): processed image (np.ndarray)
665
+ im_info (dict): info of processed image
666
+ """
667
+ coarsest_stride = self.coarsest_stride
668
+ if coarsest_stride <= 0:
669
+ return im, im_info
670
+ im_c, im_h, im_w = im.shape
671
+ pad_h = int(np.ceil(float(im_h) / coarsest_stride) * coarsest_stride)
672
+ pad_w = int(np.ceil(float(im_w) / coarsest_stride) * coarsest_stride)
673
+ padding_im = np.zeros((im_c, pad_h, pad_w), dtype=np.float32)
674
+ padding_im[:, :im_h, :im_w] = im
675
+ return padding_im, im_info
676
+
677
+
678
+ def decode_image(im_file, im_info):
679
+ """read rgb image
680
+ Args:
681
+ im_file (str|np.ndarray): input can be image path or np.ndarray
682
+ im_info (dict): info of image
683
+ Returns:
684
+ im (np.ndarray): processed image (np.ndarray)
685
+ im_info (dict): info of processed image
686
+ """
687
+ if isinstance(im_file, str):
688
+ with open(im_file, 'rb') as f:
689
+ im_read = f.read()
690
+ data = np.frombuffer(im_read, dtype='uint8')
691
+ im = cv2.imdecode(data, 1) # BGR mode, but need RGB mode
692
+ im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
693
+ else:
694
+ im = im_file
695
+ im_info['im_shape'] = np.array(im.shape[:2], dtype=np.float32)
696
+ im_info['scale_factor'] = np.array([1., 1.], dtype=np.float32)
697
+ return im, im_info
698
+
699
+
700
+ def preprocess(im, preprocess_ops):
701
+ # process image by preprocess_ops
702
+ im_info = {
703
+ 'scale_factor': np.array(
704
+ [1., 1.], dtype=np.float32),
705
+ 'im_shape': None,
706
+ }
707
+ im, im_info = decode_image(im, im_info)
708
+ for operator in preprocess_ops:
709
+ im, im_info = operator(im, im_info)
710
+ return im, im_info
deepdoc/visual/postprocess.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+
3
+ import numpy as np
4
+ import cv2
5
+ import paddle
6
+ from shapely.geometry import Polygon
7
+ import pyclipper
8
+
9
+
10
+ def build_post_process(config, global_config=None):
11
+ support_dict = ['DBPostProcess', 'CTCLabelDecode']
12
+
13
+ config = copy.deepcopy(config)
14
+ module_name = config.pop('name')
15
+ if module_name == "None":
16
+ return
17
+ if global_config is not None:
18
+ config.update(global_config)
19
+ assert module_name in support_dict, Exception(
20
+ 'post process only support {}'.format(support_dict))
21
+ module_class = eval(module_name)(**config)
22
+ return module_class
23
+
24
+
25
+ class DBPostProcess(object):
26
+ """
27
+ The post process for Differentiable Binarization (DB).
28
+ """
29
+
30
+ def __init__(self,
31
+ thresh=0.3,
32
+ box_thresh=0.7,
33
+ max_candidates=1000,
34
+ unclip_ratio=2.0,
35
+ use_dilation=False,
36
+ score_mode="fast",
37
+ box_type='quad',
38
+ **kwargs):
39
+ self.thresh = thresh
40
+ self.box_thresh = box_thresh
41
+ self.max_candidates = max_candidates
42
+ self.unclip_ratio = unclip_ratio
43
+ self.min_size = 3
44
+ self.score_mode = score_mode
45
+ self.box_type = box_type
46
+ assert score_mode in [
47
+ "slow", "fast"
48
+ ], "Score mode must be in [slow, fast] but got: {}".format(score_mode)
49
+
50
+ self.dilation_kernel = None if not use_dilation else np.array(
51
+ [[1, 1], [1, 1]])
52
+
53
+ def polygons_from_bitmap(self, pred, _bitmap, dest_width, dest_height):
54
+ '''
55
+ _bitmap: single map with shape (1, H, W),
56
+ whose values are binarized as {0, 1}
57
+ '''
58
+
59
+ bitmap = _bitmap
60
+ height, width = bitmap.shape
61
+
62
+ boxes = []
63
+ scores = []
64
+
65
+ contours, _ = cv2.findContours((bitmap * 255).astype(np.uint8),
66
+ cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
67
+
68
+ for contour in contours[:self.max_candidates]:
69
+ epsilon = 0.002 * cv2.arcLength(contour, True)
70
+ approx = cv2.approxPolyDP(contour, epsilon, True)
71
+ points = approx.reshape((-1, 2))
72
+ if points.shape[0] < 4:
73
+ continue
74
+
75
+ score = self.box_score_fast(pred, points.reshape(-1, 2))
76
+ if self.box_thresh > score:
77
+ continue
78
+
79
+ if points.shape[0] > 2:
80
+ box = self.unclip(points, self.unclip_ratio)
81
+ if len(box) > 1:
82
+ continue
83
+ else:
84
+ continue
85
+ box = box.reshape(-1, 2)
86
+
87
+ _, sside = self.get_mini_boxes(box.reshape((-1, 1, 2)))
88
+ if sside < self.min_size + 2:
89
+ continue
90
+
91
+ box = np.array(box)
92
+ box[:, 0] = np.clip(
93
+ np.round(box[:, 0] / width * dest_width), 0, dest_width)
94
+ box[:, 1] = np.clip(
95
+ np.round(box[:, 1] / height * dest_height), 0, dest_height)
96
+ boxes.append(box.tolist())
97
+ scores.append(score)
98
+ return boxes, scores
99
+
100
+ def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height):
101
+ '''
102
+ _bitmap: single map with shape (1, H, W),
103
+ whose values are binarized as {0, 1}
104
+ '''
105
+
106
+ bitmap = _bitmap
107
+ height, width = bitmap.shape
108
+
109
+ outs = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST,
110
+ cv2.CHAIN_APPROX_SIMPLE)
111
+ if len(outs) == 3:
112
+ img, contours, _ = outs[0], outs[1], outs[2]
113
+ elif len(outs) == 2:
114
+ contours, _ = outs[0], outs[1]
115
+
116
+ num_contours = min(len(contours), self.max_candidates)
117
+
118
+ boxes = []
119
+ scores = []
120
+ for index in range(num_contours):
121
+ contour = contours[index]
122
+ points, sside = self.get_mini_boxes(contour)
123
+ if sside < self.min_size:
124
+ continue
125
+ points = np.array(points)
126
+ if self.score_mode == "fast":
127
+ score = self.box_score_fast(pred, points.reshape(-1, 2))
128
+ else:
129
+ score = self.box_score_slow(pred, contour)
130
+ if self.box_thresh > score:
131
+ continue
132
+
133
+ box = self.unclip(points, self.unclip_ratio).reshape(-1, 1, 2)
134
+ box, sside = self.get_mini_boxes(box)
135
+ if sside < self.min_size + 2:
136
+ continue
137
+ box = np.array(box)
138
+
139
+ box[:, 0] = np.clip(
140
+ np.round(box[:, 0] / width * dest_width), 0, dest_width)
141
+ box[:, 1] = np.clip(
142
+ np.round(box[:, 1] / height * dest_height), 0, dest_height)
143
+ boxes.append(box.astype("int32"))
144
+ scores.append(score)
145
+ return np.array(boxes, dtype="int32"), scores
146
+
147
+ def unclip(self, box, unclip_ratio):
148
+ poly = Polygon(box)
149
+ distance = poly.area * unclip_ratio / poly.length
150
+ offset = pyclipper.PyclipperOffset()
151
+ offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
152
+ expanded = np.array(offset.Execute(distance))
153
+ return expanded
154
+
155
+ def get_mini_boxes(self, contour):
156
+ bounding_box = cv2.minAreaRect(contour)
157
+ points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])
158
+
159
+ index_1, index_2, index_3, index_4 = 0, 1, 2, 3
160
+ if points[1][1] > points[0][1]:
161
+ index_1 = 0
162
+ index_4 = 1
163
+ else:
164
+ index_1 = 1
165
+ index_4 = 0
166
+ if points[3][1] > points[2][1]:
167
+ index_2 = 2
168
+ index_3 = 3
169
+ else:
170
+ index_2 = 3
171
+ index_3 = 2
172
+
173
+ box = [
174
+ points[index_1], points[index_2], points[index_3], points[index_4]
175
+ ]
176
+ return box, min(bounding_box[1])
177
+
178
+ def box_score_fast(self, bitmap, _box):
179
+ '''
180
+ box_score_fast: use bbox mean score as the mean score
181
+ '''
182
+ h, w = bitmap.shape[:2]
183
+ box = _box.copy()
184
+ xmin = np.clip(np.floor(box[:, 0].min()).astype("int32"), 0, w - 1)
185
+ xmax = np.clip(np.ceil(box[:, 0].max()).astype("int32"), 0, w - 1)
186
+ ymin = np.clip(np.floor(box[:, 1].min()).astype("int32"), 0, h - 1)
187
+ ymax = np.clip(np.ceil(box[:, 1].max()).astype("int32"), 0, h - 1)
188
+
189
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
190
+ box[:, 0] = box[:, 0] - xmin
191
+ box[:, 1] = box[:, 1] - ymin
192
+ cv2.fillPoly(mask, box.reshape(1, -1, 2).astype("int32"), 1)
193
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0]
194
+
195
+ def box_score_slow(self, bitmap, contour):
196
+ '''
197
+ box_score_slow: use polyon mean score as the mean score
198
+ '''
199
+ h, w = bitmap.shape[:2]
200
+ contour = contour.copy()
201
+ contour = np.reshape(contour, (-1, 2))
202
+
203
+ xmin = np.clip(np.min(contour[:, 0]), 0, w - 1)
204
+ xmax = np.clip(np.max(contour[:, 0]), 0, w - 1)
205
+ ymin = np.clip(np.min(contour[:, 1]), 0, h - 1)
206
+ ymax = np.clip(np.max(contour[:, 1]), 0, h - 1)
207
+
208
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
209
+
210
+ contour[:, 0] = contour[:, 0] - xmin
211
+ contour[:, 1] = contour[:, 1] - ymin
212
+
213
+ cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype("int32"), 1)
214
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0]
215
+
216
+ def __call__(self, outs_dict, shape_list):
217
+ pred = outs_dict['maps']
218
+ if isinstance(pred, paddle.Tensor):
219
+ pred = pred.numpy()
220
+ pred = pred[:, 0, :, :]
221
+ segmentation = pred > self.thresh
222
+
223
+ boxes_batch = []
224
+ for batch_index in range(pred.shape[0]):
225
+ src_h, src_w, ratio_h, ratio_w = shape_list[batch_index]
226
+ if self.dilation_kernel is not None:
227
+ mask = cv2.dilate(
228
+ np.array(segmentation[batch_index]).astype(np.uint8),
229
+ self.dilation_kernel)
230
+ else:
231
+ mask = segmentation[batch_index]
232
+ if self.box_type == 'poly':
233
+ boxes, scores = self.polygons_from_bitmap(pred[batch_index],
234
+ mask, src_w, src_h)
235
+ elif self.box_type == 'quad':
236
+ boxes, scores = self.boxes_from_bitmap(pred[batch_index], mask,
237
+ src_w, src_h)
238
+ else:
239
+ raise ValueError(
240
+ "box_type can only be one of ['quad', 'poly']")
241
+
242
+ boxes_batch.append({'points': boxes})
243
+ return boxes_batch
244
+
245
+
246
+ class BaseRecLabelDecode(object):
247
+ """ Convert between text-label and text-index """
248
+
249
+ def __init__(self, character_dict_path=None, use_space_char=False):
250
+ self.beg_str = "sos"
251
+ self.end_str = "eos"
252
+ self.reverse = False
253
+ self.character_str = []
254
+
255
+ if character_dict_path is None:
256
+ self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz"
257
+ dict_character = list(self.character_str)
258
+ else:
259
+ with open(character_dict_path, "rb") as fin:
260
+ lines = fin.readlines()
261
+ for line in lines:
262
+ line = line.decode('utf-8').strip("\n").strip("\r\n")
263
+ self.character_str.append(line)
264
+ if use_space_char:
265
+ self.character_str.append(" ")
266
+ dict_character = list(self.character_str)
267
+ if 'arabic' in character_dict_path:
268
+ self.reverse = True
269
+
270
+ dict_character = self.add_special_char(dict_character)
271
+ self.dict = {}
272
+ for i, char in enumerate(dict_character):
273
+ self.dict[char] = i
274
+ self.character = dict_character
275
+
276
+ def pred_reverse(self, pred):
277
+ pred_re = []
278
+ c_current = ''
279
+ for c in pred:
280
+ if not bool(re.search('[a-zA-Z0-9 :*./%+-]', c)):
281
+ if c_current != '':
282
+ pred_re.append(c_current)
283
+ pred_re.append(c)
284
+ c_current = ''
285
+ else:
286
+ c_current += c
287
+ if c_current != '':
288
+ pred_re.append(c_current)
289
+
290
+ return ''.join(pred_re[::-1])
291
+
292
+ def add_special_char(self, dict_character):
293
+ return dict_character
294
+
295
+ def decode(self, text_index, text_prob=None, is_remove_duplicate=False):
296
+ """ convert text-index into text-label. """
297
+ result_list = []
298
+ ignored_tokens = self.get_ignored_tokens()
299
+ batch_size = len(text_index)
300
+ for batch_idx in range(batch_size):
301
+ selection = np.ones(len(text_index[batch_idx]), dtype=bool)
302
+ if is_remove_duplicate:
303
+ selection[1:] = text_index[batch_idx][1:] != text_index[
304
+ batch_idx][:-1]
305
+ for ignored_token in ignored_tokens:
306
+ selection &= text_index[batch_idx] != ignored_token
307
+
308
+ char_list = [
309
+ self.character[text_id]
310
+ for text_id in text_index[batch_idx][selection]
311
+ ]
312
+ if text_prob is not None:
313
+ conf_list = text_prob[batch_idx][selection]
314
+ else:
315
+ conf_list = [1] * len(selection)
316
+ if len(conf_list) == 0:
317
+ conf_list = [0]
318
+
319
+ text = ''.join(char_list)
320
+
321
+ if self.reverse: # for arabic rec
322
+ text = self.pred_reverse(text)
323
+
324
+ result_list.append((text, np.mean(conf_list).tolist()))
325
+ return result_list
326
+
327
+ def get_ignored_tokens(self):
328
+ return [0] # for ctc blank
329
+
330
+
331
+ class CTCLabelDecode(BaseRecLabelDecode):
332
+ """ Convert between text-label and text-index """
333
+
334
+ def __init__(self, character_dict_path=None, use_space_char=False,
335
+ **kwargs):
336
+ super(CTCLabelDecode, self).__init__(character_dict_path,
337
+ use_space_char)
338
+
339
+ def __call__(self, preds, label=None, *args, **kwargs):
340
+ if isinstance(preds, tuple) or isinstance(preds, list):
341
+ preds = preds[-1]
342
+ if isinstance(preds, paddle.Tensor):
343
+ preds = preds.numpy()
344
+ preds_idx = preds.argmax(axis=2)
345
+ preds_prob = preds.max(axis=2)
346
+ text = self.decode(preds_idx, preds_prob, is_remove_duplicate=True)
347
+ if label is None:
348
+ return text
349
+ label = self.decode(label)
350
+ return text, label
351
+
352
+ def add_special_char(self, dict_character):
353
+ dict_character = ['blank'] + dict_character
354
+ return dict_character
deepdoc/visual/recognizer.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
+
14
+ import os
15
+ import onnxruntime as ort
16
+ from huggingface_hub import snapshot_download
17
+
18
+ from .operators import *
19
+ from rag.settings import cron_logger
20
+
21
+
22
+ class Recognizer(object):
23
+ def __init__(self, label_list, task_name, model_dir=None):
24
+ """
25
+ If you have trouble downloading HuggingFace models, -_^ this might help!!
26
+
27
+ For Linux:
28
+ export HF_ENDPOINT=https://hf-mirror.com
29
+
30
+ For Windows:
31
+ Good luck
32
+ ^_-
33
+
34
+ """
35
+ if not model_dir:
36
+ model_dir = snapshot_download(repo_id="InfiniFlow/ocr")
37
+
38
+ model_file_path = os.path.join(model_dir, task_name + ".onnx")
39
+ if not os.path.exists(model_file_path):
40
+ raise ValueError("not find model file path {}".format(
41
+ model_file_path))
42
+ if ort.get_device() == "GPU":
43
+ self.ort_sess = ort.InferenceSession(model_file_path, providers=['CUDAExecutionProvider'])
44
+ else:
45
+ self.ort_sess = ort.InferenceSession(model_file_path, providers=['CPUExecutionProvider'])
46
+ self.label_list = label_list
47
+
48
+ def create_inputs(self, imgs, im_info):
49
+ """generate input for different model type
50
+ Args:
51
+ imgs (list(numpy)): list of images (np.ndarray)
52
+ im_info (list(dict)): list of image info
53
+ Returns:
54
+ inputs (dict): input of model
55
+ """
56
+ inputs = {}
57
+
58
+ im_shape = []
59
+ scale_factor = []
60
+ if len(imgs) == 1:
61
+ inputs['image'] = np.array((imgs[0],)).astype('float32')
62
+ inputs['im_shape'] = np.array(
63
+ (im_info[0]['im_shape'],)).astype('float32')
64
+ inputs['scale_factor'] = np.array(
65
+ (im_info[0]['scale_factor'],)).astype('float32')
66
+ return inputs
67
+
68
+ for e in im_info:
69
+ im_shape.append(np.array((e['im_shape'],)).astype('float32'))
70
+ scale_factor.append(np.array((e['scale_factor'],)).astype('float32'))
71
+
72
+ inputs['im_shape'] = np.concatenate(im_shape, axis=0)
73
+ inputs['scale_factor'] = np.concatenate(scale_factor, axis=0)
74
+
75
+ imgs_shape = [[e.shape[1], e.shape[2]] for e in imgs]
76
+ max_shape_h = max([e[0] for e in imgs_shape])
77
+ max_shape_w = max([e[1] for e in imgs_shape])
78
+ padding_imgs = []
79
+ for img in imgs:
80
+ im_c, im_h, im_w = img.shape[:]
81
+ padding_im = np.zeros(
82
+ (im_c, max_shape_h, max_shape_w), dtype=np.float32)
83
+ padding_im[:, :im_h, :im_w] = img
84
+ padding_imgs.append(padding_im)
85
+ inputs['image'] = np.stack(padding_imgs, axis=0)
86
+ return inputs
87
+
88
+ def preprocess(self, image_list):
89
+ preprocess_ops = []
90
+ for op_info in [
91
+ {'interp': 2, 'keep_ratio': False, 'target_size': [800, 608], 'type': 'LinearResize'},
92
+ {'is_scale': True, 'mean': [0.485, 0.456, 0.406], 'std': [0.229, 0.224, 0.225], 'type': 'StandardizeImage'},
93
+ {'type': 'Permute'},
94
+ {'stride': 32, 'type': 'PadStride'}
95
+ ]:
96
+ new_op_info = op_info.copy()
97
+ op_type = new_op_info.pop('type')
98
+ preprocess_ops.append(eval(op_type)(**new_op_info))
99
+
100
+ inputs = []
101
+ for im_path in image_list:
102
+ im, im_info = preprocess(im_path, preprocess_ops)
103
+ inputs.append({"image": np.array((im,)).astype('float32'), "scale_factor": np.array((im_info["scale_factor"],)).astype('float32')})
104
+ return inputs
105
+
106
+
107
+ def __call__(self, image_list, thr=0.7, batch_size=16):
108
+ res = []
109
+ imgs = []
110
+ for i in range(len(image_list)):
111
+ if not isinstance(image_list[i], np.ndarray):
112
+ imgs.append(np.array(image_list[i]))
113
+ else: imgs.append(image_list[i])
114
+
115
+ batch_loop_cnt = math.ceil(float(len(imgs)) / batch_size)
116
+ for i in range(batch_loop_cnt):
117
+ start_index = i * batch_size
118
+ end_index = min((i + 1) * batch_size, len(imgs))
119
+ batch_image_list = imgs[start_index:end_index]
120
+ inputs = self.preprocess(batch_image_list)
121
+ for ins in inputs:
122
+ bb = []
123
+ for b in self.ort_sess.run(None, ins)[0]:
124
+ clsid, bbox, score = int(b[0]), b[2:], b[1]
125
+ if score < thr:
126
+ continue
127
+ if clsid >= len(self.label_list):
128
+ cron_logger.warning(f"bad category id")
129
+ continue
130
+ bb.append({
131
+ "type": self.label_list[clsid].lower(),
132
+ "bbox": [float(t) for t in bbox.tolist()],
133
+ "score": float(score)
134
+ })
135
+ res.append(bb)
136
+
137
+ #seeit.save_results(image_list, res, self.label_list, threshold=thr)
138
+
139
+ return res
deepdoc/visual/seeit.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
+
14
+ import os
15
+ import PIL
16
+ from PIL import ImageDraw
17
+
18
+
19
+ def save_results(image_list, results, labels, output_dir='output/', threshold=0.5):
20
+ if not os.path.exists(output_dir):
21
+ os.makedirs(output_dir)
22
+ for idx, im in enumerate(image_list):
23
+ im = draw_box(im, results[idx], labels, threshold=threshold)
24
+
25
+ out_path = os.path.join(output_dir, f"{idx}.jpg")
26
+ im.save(out_path, quality=95)
27
+ print("save result to: " + out_path)
28
+
29
+
30
+ def draw_box(im, result, lables, threshold=0.5):
31
+ draw_thickness = min(im.size) // 320
32
+ draw = ImageDraw.Draw(im)
33
+ color_list = get_color_map_list(len(lables))
34
+ clsid2color = {n.lower():color_list[i] for i,n in enumerate(lables)}
35
+ result = [r for r in result if r["score"] >= threshold]
36
+
37
+ for dt in result:
38
+ color = tuple(clsid2color[dt["type"]])
39
+ xmin, ymin, xmax, ymax = dt["bbox"]
40
+ draw.line(
41
+ [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin),
42
+ (xmin, ymin)],
43
+ width=draw_thickness,
44
+ fill=color)
45
+
46
+ # draw label
47
+ text = "{} {:.4f}".format(dt["type"], dt["score"])
48
+ tw, th = imagedraw_textsize_c(draw, text)
49
+ draw.rectangle(
50
+ [(xmin + 1, ymin - th), (xmin + tw + 1, ymin)], fill=color)
51
+ draw.text((xmin + 1, ymin - th), text, fill=(255, 255, 255))
52
+ return im
53
+
54
+
55
+ def get_color_map_list(num_classes):
56
+ """
57
+ Args:
58
+ num_classes (int): number of class
59
+ Returns:
60
+ color_map (list): RGB color list
61
+ """
62
+ color_map = num_classes * [0, 0, 0]
63
+ for i in range(0, num_classes):
64
+ j = 0
65
+ lab = i
66
+ while lab:
67
+ color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j))
68
+ color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j))
69
+ color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j))
70
+ j += 1
71
+ lab >>= 3
72
+ color_map = [color_map[i:i + 3] for i in range(0, len(color_map), 3)]
73
+ return color_map
74
+
75
+
76
+ def imagedraw_textsize_c(draw, text):
77
+ if int(PIL.__version__.split('.')[0]) < 10:
78
+ tw, th = draw.textsize(text)
79
+ else:
80
+ left, top, right, bottom = draw.textbbox((0, 0), text)
81
+ tw, th = right - left, bottom - top
82
+
83
+ return tw, th
rag/app/book.py CHANGED
@@ -1,15 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import copy
2
- import random
3
  import re
4
- import numpy as np
5
- from rag.parser import bullets_category, BULLET_PATTERN, is_english, tokenize, remove_contents_table, \
6
  hierarchical_merge, make_colon_as_title, naive_merge, random_choices
7
  from rag.nlp import huqie
8
- from rag.parser.docx_parser import HuDocxParser
9
- from rag.parser.pdf_parser import HuParser
10
 
11
 
12
- class Pdf(HuParser):
13
  def __call__(self, filename, binary=None, from_page=0,
14
  to_page=100000, zoomin=3, callback=None):
15
  self.__images__(
@@ -21,7 +30,7 @@ class Pdf(HuParser):
21
 
22
  from timeit import default_timer as timer
23
  start = timer()
24
- self._layouts_paddle(zoomin)
25
  callback(0.47, "Layout analysis finished")
26
  print("paddle layouts:", timer() - start)
27
  self._table_transformer_job(zoomin)
@@ -53,7 +62,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000, callback=None, **k
53
  sections,tbls = [], []
54
  if re.search(r"\.docx?$", filename, re.IGNORECASE):
55
  callback(0.1, "Start to parse.")
56
- doc_parser = HuDocxParser()
57
  # TODO: table of contents need to be removed
58
  sections, tbls = doc_parser(binary if binary else filename, from_page=from_page, to_page=to_page)
59
  remove_contents_table(sections, eng=is_english(random_choices([t for t,_ in sections], k=200)))
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import copy
 
14
  import re
15
+ from deepdoc.parser import bullets_category, is_english, tokenize, remove_contents_table, \
 
16
  hierarchical_merge, make_colon_as_title, naive_merge, random_choices
17
  from rag.nlp import huqie
18
+ from deepdoc.parser import PdfParser, DocxParser
 
19
 
20
 
21
+ class Pdf(PdfParser):
22
  def __call__(self, filename, binary=None, from_page=0,
23
  to_page=100000, zoomin=3, callback=None):
24
  self.__images__(
 
30
 
31
  from timeit import default_timer as timer
32
  start = timer()
33
+ self._layouts_rec(zoomin)
34
  callback(0.47, "Layout analysis finished")
35
  print("paddle layouts:", timer() - start)
36
  self._table_transformer_job(zoomin)
 
62
  sections,tbls = [], []
63
  if re.search(r"\.docx?$", filename, re.IGNORECASE):
64
  callback(0.1, "Start to parse.")
65
+ doc_parser = DocxParser()
66
  # TODO: table of contents need to be removed
67
  sections, tbls = doc_parser(binary if binary else filename, from_page=from_page, to_page=to_page)
68
  remove_contents_table(sections, eng=is_english(random_choices([t for t,_ in sections], k=200)))
rag/app/laws.py CHANGED
@@ -1,16 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import copy
2
  import re
3
  from io import BytesIO
4
  from docx import Document
5
- from rag.parser import bullets_category, is_english, tokenize, remove_contents_table, hierarchical_merge, \
6
  make_colon_as_title
7
  from rag.nlp import huqie
8
- from rag.parser.docx_parser import HuDocxParser
9
- from rag.parser.pdf_parser import HuParser
10
  from rag.settings import cron_logger
11
 
12
 
13
- class Docx(HuDocxParser):
14
  def __init__(self):
15
  pass
16
 
@@ -35,7 +46,7 @@ class Docx(HuDocxParser):
35
  return [l for l in lines if l]
36
 
37
 
38
- class Pdf(HuParser):
39
  def __call__(self, filename, binary=None, from_page=0,
40
  to_page=100000, zoomin=3, callback=None):
41
  self.__images__(
@@ -47,7 +58,7 @@ class Pdf(HuParser):
47
 
48
  from timeit import default_timer as timer
49
  start = timer()
50
- self._layouts_paddle(zoomin)
51
  callback(0.77, "Layout analysis finished")
52
  cron_logger.info("paddle layouts:".format((timer()-start)/(self.total_page+0.1)))
53
  self._naive_vertical_merge()
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import copy
14
  import re
15
  from io import BytesIO
16
  from docx import Document
17
+ from deepdoc.parser import bullets_category, is_english, tokenize, remove_contents_table, hierarchical_merge, \
18
  make_colon_as_title
19
  from rag.nlp import huqie
20
+ from deepdoc.parser import PdfParser, DocxParser
 
21
  from rag.settings import cron_logger
22
 
23
 
24
+ class Docx(DocxParser):
25
  def __init__(self):
26
  pass
27
 
 
46
  return [l for l in lines if l]
47
 
48
 
49
+ class Pdf(PdfParser):
50
  def __call__(self, filename, binary=None, from_page=0,
51
  to_page=100000, zoomin=3, callback=None):
52
  self.__images__(
 
58
 
59
  from timeit import default_timer as timer
60
  start = timer()
61
+ self._layouts_rec(zoomin)
62
  callback(0.77, "Layout analysis finished")
63
  cron_logger.info("paddle layouts:".format((timer()-start)/(self.total_page+0.1)))
64
  self._naive_vertical_merge()
rag/app/manual.py CHANGED
@@ -1,12 +1,12 @@
1
  import copy
2
  import re
3
- from rag.parser import tokenize
4
  from rag.nlp import huqie
5
- from rag.parser.pdf_parser import HuParser
6
  from rag.utils import num_tokens_from_string
7
 
8
 
9
- class Pdf(HuParser):
10
  def __call__(self, filename, binary=None, from_page=0,
11
  to_page=100000, zoomin=3, callback=None):
12
  self.__images__(
@@ -18,7 +18,7 @@ class Pdf(HuParser):
18
 
19
  from timeit import default_timer as timer
20
  start = timer()
21
- self._layouts_paddle(zoomin)
22
  callback(0.5, "Layout analysis finished.")
23
  print("paddle layouts:", timer() - start)
24
  self._table_transformer_job(zoomin)
 
1
  import copy
2
  import re
3
+ from deepdoc.parser import tokenize
4
  from rag.nlp import huqie
5
+ from deepdoc.parser import PdfParser
6
  from rag.utils import num_tokens_from_string
7
 
8
 
9
+ class Pdf(PdfParser):
10
  def __call__(self, filename, binary=None, from_page=0,
11
  to_page=100000, zoomin=3, callback=None):
12
  self.__images__(
 
18
 
19
  from timeit import default_timer as timer
20
  start = timer()
21
+ self._layouts_rec(zoomin)
22
  callback(0.5, "Layout analysis finished.")
23
  print("paddle layouts:", timer() - start)
24
  self._table_transformer_job(zoomin)
rag/app/naive.py CHANGED
@@ -1,13 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import copy
2
  import re
3
  from rag.app import laws
4
- from rag.parser import is_english, tokenize, naive_merge
5
  from rag.nlp import huqie
6
- from rag.parser.pdf_parser import HuParser
7
  from rag.settings import cron_logger
8
 
9
 
10
- class Pdf(HuParser):
11
  def __call__(self, filename, binary=None, from_page=0,
12
  to_page=100000, zoomin=3, callback=None):
13
  self.__images__(
@@ -19,7 +31,7 @@ class Pdf(HuParser):
19
 
20
  from timeit import default_timer as timer
21
  start = timer()
22
- self._layouts_paddle(zoomin)
23
  callback(0.77, "Layout analysis finished")
24
  cron_logger.info("paddle layouts:".format((timer() - start) / (self.total_page + 0.1)))
25
  self._naive_vertical_merge()
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import copy
14
  import re
15
  from rag.app import laws
16
+ from deepdoc.parser import is_english, tokenize, naive_merge
17
  from rag.nlp import huqie
18
+ from deepdoc.parser import PdfParser
19
  from rag.settings import cron_logger
20
 
21
 
22
+ class Pdf(PdfParser):
23
  def __call__(self, filename, binary=None, from_page=0,
24
  to_page=100000, zoomin=3, callback=None):
25
  self.__images__(
 
31
 
32
  from timeit import default_timer as timer
33
  start = timer()
34
+ self._layouts_rec(zoomin)
35
  callback(0.77, "Layout analysis finished")
36
  cron_logger.info("paddle layouts:".format((timer() - start) / (self.total_page + 0.1)))
37
  self._naive_vertical_merge()
rag/app/paper.py CHANGED
@@ -1,16 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import copy
2
  import re
3
  from collections import Counter
4
 
5
  from api.db import ParserType
6
- from rag.parser import tokenize
7
  from rag.nlp import huqie
8
- from rag.parser.pdf_parser import HuParser
9
  import numpy as np
10
  from rag.utils import num_tokens_from_string
11
 
12
 
13
- class Pdf(HuParser):
14
  def __init__(self):
15
  self.model_speciess = ParserType.PAPER.value
16
  super().__init__()
@@ -26,7 +38,7 @@ class Pdf(HuParser):
26
 
27
  from timeit import default_timer as timer
28
  start = timer()
29
- self._layouts_paddle(zoomin)
30
  callback(0.47, "Layout analysis finished")
31
  print("paddle layouts:", timer() - start)
32
  self._table_transformer_job(zoomin)
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import copy
14
  import re
15
  from collections import Counter
16
 
17
  from api.db import ParserType
18
+ from deepdoc.parser import tokenize
19
  from rag.nlp import huqie
20
+ from deepdoc.parser import PdfParser
21
  import numpy as np
22
  from rag.utils import num_tokens_from_string
23
 
24
 
25
+ class Pdf(PdfParser):
26
  def __init__(self):
27
  self.model_speciess = ParserType.PAPER.value
28
  super().__init__()
 
38
 
39
  from timeit import default_timer as timer
40
  start = timer()
41
+ self._layouts_rec(zoomin)
42
  callback(0.47, "Layout analysis finished")
43
  print("paddle layouts:", timer() - start)
44
  self._table_transformer_job(zoomin)
rag/app/presentation.py CHANGED
@@ -1,11 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import copy
2
  import re
3
  from io import BytesIO
4
  from pptx import Presentation
5
-
6
- from rag.parser import tokenize, is_english
7
  from rag.nlp import huqie
8
- from rag.parser.pdf_parser import HuParser
9
 
10
 
11
  class Ppt(object):
@@ -58,7 +69,7 @@ class Ppt(object):
58
  return [(txts[i], imgs[i]) for i in range(len(txts))]
59
 
60
 
61
- class Pdf(HuParser):
62
  def __init__(self):
63
  super().__init__()
64
 
@@ -74,7 +85,7 @@ class Pdf(HuParser):
74
  assert len(self.boxes) == len(self.page_images), "{} vs. {}".format(len(self.boxes), len(self.page_images))
75
  res = []
76
  #################### More precisely ###################
77
- # self._layouts_paddle(zoomin)
78
  # self._text_merge()
79
  # pages = {}
80
  # for b in self.boxes:
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import copy
14
  import re
15
  from io import BytesIO
16
  from pptx import Presentation
17
+ from deepdoc.parser import tokenize, is_english
 
18
  from rag.nlp import huqie
19
+ from deepdoc.parser import PdfParser
20
 
21
 
22
  class Ppt(object):
 
69
  return [(txts[i], imgs[i]) for i in range(len(txts))]
70
 
71
 
72
+ class Pdf(PdfParser):
73
  def __init__(self):
74
  super().__init__()
75
 
 
85
  assert len(self.boxes) == len(self.page_images), "{} vs. {}".format(len(self.boxes), len(self.page_images))
86
  res = []
87
  #################### More precisely ###################
88
+ # self._layouts_rec(zoomin)
89
  # self._text_merge()
90
  # pages = {}
91
  # for b in self.boxes:
rag/app/qa.py CHANGED
@@ -1,13 +1,25 @@
1
- import random
 
 
 
 
 
 
 
 
 
 
 
2
  import re
3
  from io import BytesIO
4
  from nltk import word_tokenize
5
  from openpyxl import load_workbook
6
- from rag.parser import is_english, random_choices
7
  from rag.nlp import huqie, stemmer
 
8
 
9
 
10
- class Excel(object):
11
  def __call__(self, fnm, binary=None, callback=None):
12
  if not binary:
13
  wb = load_workbook(fnm)
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import re
14
  from io import BytesIO
15
  from nltk import word_tokenize
16
  from openpyxl import load_workbook
17
+ from deepdoc.parser import is_english, random_choices
18
  from rag.nlp import huqie, stemmer
19
+ from deepdoc.parser import ExcelParser
20
 
21
 
22
+ class Excel(ExcelParser):
23
  def __call__(self, fnm, binary=None, callback=None):
24
  if not binary:
25
  wb = load_workbook(fnm)
rag/app/resume.py CHANGED
@@ -1,59 +1,82 @@
1
- import copy
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import json
3
- import os
4
  import re
 
 
5
  import requests
6
  from api.db.services.knowledgebase_service import KnowledgebaseService
7
- from api.settings import stat_logger
8
  from rag.nlp import huqie
9
-
 
10
  from rag.settings import cron_logger
11
  from rag.utils import rmSpace
12
 
13
  forbidden_select_fields4resume = [
14
  "name_pinyin_kwd", "edu_first_fea_kwd", "degree_kwd", "sch_rank_kwd", "edu_fea_kwd"
15
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  def chunk(filename, binary=None, callback=None, **kwargs):
18
  """
19
  The supported file formats are pdf, docx and txt.
20
- To maximize the effectiveness, parse the resume correctly,
21
- please visit https://github.com/infiniflow/ragflow, and sign in the our demo web-site
22
- to get token. It's FREE!
23
- Set INFINIFLOW_SERVER and INFINIFLOW_TOKEN in '.env' file or
24
- using 'export' to set both environment variables: INFINIFLOW_SERVER and INFINIFLOW_TOKEN in docker container.
25
  """
26
  if not re.search(r"\.(pdf|doc|docx|txt)$", filename, flags=re.IGNORECASE):
27
  raise NotImplementedError("file type not supported yet(pdf supported)")
28
 
29
- url = os.environ.get("INFINIFLOW_SERVER")
30
- token = os.environ.get("INFINIFLOW_TOKEN")
31
- if not url or not token:
32
- stat_logger.warning(
33
- "INFINIFLOW_SERVER is not specified. To maximize the effectiveness, please visit https://github.com/infiniflow/ragflow, and sign in the our demo web site to get token. It's FREE! Using 'export' to set both environment variables: INFINIFLOW_SERVER and INFINIFLOW_TOKEN.")
34
- return []
35
-
36
  if not binary:
37
  with open(filename, "rb") as f:
38
  binary = f.read()
39
 
40
- def remote_call():
41
- nonlocal filename, binary
42
- for _ in range(3):
43
- try:
44
- res = requests.post(url + "/v1/layout/resume/", files=[(filename, binary)],
45
- headers={"Authorization": token}, timeout=180)
46
- res = res.json()
47
- if res["retcode"] != 0:
48
- raise RuntimeError(res["retmsg"])
49
- return res["data"]
50
- except RuntimeError as e:
51
- raise e
52
- except Exception as e:
53
- cron_logger.error("resume parsing:" + str(e))
54
-
55
  callback(0.2, "Resume parsing is going on...")
56
- resume = remote_call()
57
  if len(resume.keys()) < 7:
58
  callback(-1, "Resume is not successfully parsed.")
59
  return []
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
+ import base64
14
+ import datetime
15
  import json
 
16
  import re
17
+
18
+ import pandas as pd
19
  import requests
20
  from api.db.services.knowledgebase_service import KnowledgebaseService
 
21
  from rag.nlp import huqie
22
+ from deepdoc.parser.resume import refactor
23
+ from deepdoc.parser.resume import step_one, step_two
24
  from rag.settings import cron_logger
25
  from rag.utils import rmSpace
26
 
27
  forbidden_select_fields4resume = [
28
  "name_pinyin_kwd", "edu_first_fea_kwd", "degree_kwd", "sch_rank_kwd", "edu_fea_kwd"
29
  ]
30
+ def remote_call(filename, binary):
31
+ q = {
32
+ "header": {
33
+ "uid": 1,
34
+ "user": "kevinhu",
35
+ "log_id": filename
36
+ },
37
+ "request": {
38
+ "p": {
39
+ "request_id": "1",
40
+ "encrypt_type": "base64",
41
+ "filename": filename,
42
+ "langtype": '',
43
+ "fileori": base64.b64encode(binary.stream.read()).decode('utf-8')
44
+ },
45
+ "c": "resume_parse_module",
46
+ "m": "resume_parse"
47
+ }
48
+ }
49
+ for _ in range(3):
50
+ try:
51
+ resume = requests.post("http://127.0.0.1:61670/tog", data=json.dumps(q))
52
+ resume = resume.json()["response"]["results"]
53
+ resume = refactor(resume)
54
+ for k in ["education", "work", "project", "training", "skill", "certificate", "language"]:
55
+ if not resume.get(k) and k in resume: del resume[k]
56
+
57
+ resume = step_one.refactor(pd.DataFrame([{"resume_content": json.dumps(resume), "tob_resume_id": "x",
58
+ "updated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}]))
59
+ resume = step_two.parse(resume)
60
+ return resume
61
+ except Exception as e:
62
+ cron_logger.error("Resume parser error: "+str(e))
63
+ return {}
64
+
65
 
66
  def chunk(filename, binary=None, callback=None, **kwargs):
67
  """
68
  The supported file formats are pdf, docx and txt.
69
+ To maximize the effectiveness, parse the resume correctly, please concat us: https://github.com/infiniflow/ragflow
 
 
 
 
70
  """
71
  if not re.search(r"\.(pdf|doc|docx|txt)$", filename, flags=re.IGNORECASE):
72
  raise NotImplementedError("file type not supported yet(pdf supported)")
73
 
 
 
 
 
 
 
 
74
  if not binary:
75
  with open(filename, "rb") as f:
76
  binary = f.read()
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  callback(0.2, "Resume parsing is going on...")
79
+ resume = remote_call(filename, binary)
80
  if len(resume.keys()) < 7:
81
  callback(-1, "Resume is not successfully parsed.")
82
  return []
rag/app/table.py CHANGED
@@ -1,3 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import copy
2
  import re
3
  from io import BytesIO
@@ -8,11 +20,12 @@ from openpyxl import load_workbook
8
  from dateutil.parser import parse as datetime_parse
9
 
10
  from api.db.services.knowledgebase_service import KnowledgebaseService
11
- from rag.parser import is_english, tokenize
12
- from rag.nlp import huqie, stemmer
 
13
 
14
 
15
- class Excel(object):
16
  def __call__(self, fnm, binary=None, callback=None):
17
  if not binary:
18
  wb = load_workbook(fnm)
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import copy
14
  import re
15
  from io import BytesIO
 
20
  from dateutil.parser import parse as datetime_parse
21
 
22
  from api.db.services.knowledgebase_service import KnowledgebaseService
23
+ from deepdoc.parser import is_english, tokenize
24
+ from rag.nlp import huqie
25
+ from deepdoc.parser import ExcelParser
26
 
27
 
28
+ class Excel(ExcelParser):
29
  def __call__(self, fnm, binary=None, callback=None):
30
  if not binary:
31
  wb = load_workbook(fnm)
rag/nlp/huchunk.py CHANGED
@@ -1,3 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import re
2
  import os
3
  import copy
@@ -443,13 +455,13 @@ if __name__ == "__main__":
443
  import sys
444
  sys.path.append(os.path.dirname(__file__) + "/../")
445
  if sys.argv[1].split(".")[-1].lower() == "pdf":
446
- from parser import PdfParser
447
  ckr = PdfChunker(PdfParser())
448
  if sys.argv[1].split(".")[-1].lower().find("doc") >= 0:
449
- from parser import DocxParser
450
  ckr = DocxChunker(DocxParser())
451
  if sys.argv[1].split(".")[-1].lower().find("xlsx") >= 0:
452
- from parser import ExcelParser
453
  ckr = ExcelChunker(ExcelParser())
454
 
455
  # ckr.html(sys.argv[1])
 
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+ #
13
  import re
14
  import os
15
  import copy
 
455
  import sys
456
  sys.path.append(os.path.dirname(__file__) + "/../")
457
  if sys.argv[1].split(".")[-1].lower() == "pdf":
458
+ from deepdoc.parser import PdfParser
459
  ckr = PdfChunker(PdfParser())
460
  if sys.argv[1].split(".")[-1].lower().find("doc") >= 0:
461
+ from deepdoc.parser import DocxParser
462
  ckr = DocxChunker(DocxParser())
463
  if sys.argv[1].split(".")[-1].lower().find("xlsx") >= 0:
464
+ from deepdoc.parser import ExcelParser
465
  ckr = ExcelChunker(ExcelParser())
466
 
467
  # ckr.html(sys.argv[1])
rag/svr/task_broker.py CHANGED
@@ -21,7 +21,7 @@ from datetime import datetime
21
  from api.db.db_models import Task
22
  from api.db.db_utils import bulk_insert_into_db
23
  from api.db.services.task_service import TaskService
24
- from rag.parser.pdf_parser import HuParser
25
  from rag.settings import cron_logger
26
  from rag.utils import MINIO
27
  from rag.utils import findMaxTm
 
21
  from api.db.db_models import Task
22
  from api.db.db_utils import bulk_insert_into_db
23
  from api.db.services.task_service import TaskService
24
+ from deepdoc.parser import HuParser
25
  from rag.settings import cron_logger
26
  from rag.utils import MINIO
27
  from rag.utils import findMaxTm