qianliyx commited on
Commit
645af1f
·
verified ·
1 Parent(s): 30af466

Upload 13 files

Browse files
anyocr/__init__.py ADDED
File without changes
anyocr/cal_rec_boxes.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Optional, Tuple
2
+ import cv2
3
+ import copy
4
+ import math
5
+ import numpy as np
6
+
7
+
8
+ class CalRecBoxes:
9
+ """计算识别文字的汉字单字和英文单词的坐标框。代码借鉴自PaddlePaddle/PaddleOCR和fanqie03/char-detection"""
10
+
11
+ def __init__(self):
12
+ pass
13
+
14
+ def __call__(
15
+ self,
16
+ imgs: Optional[List[np.ndarray]],
17
+ dt_boxes: Optional[List[np.ndarray]],
18
+ rec_res: Optional[List[Any]],
19
+ ):
20
+ res = []
21
+ for img, box, rec_res in zip(imgs, dt_boxes, rec_res):
22
+ direction = self.get_box_direction(box)
23
+
24
+ rec_txt, rec_conf, rec_word_info = rec_res[0], rec_res[1], rec_res[2]
25
+ h, w = img.shape[:2]
26
+ img_box = np.array([[0, 0], [w, 0], [w, h], [0, h]])
27
+ word_box_content_list, word_box_list, conf_list = self.cal_ocr_word_box(
28
+ rec_txt, img_box, rec_word_info
29
+ )
30
+ word_box_list = self.adjust_box_overlap(copy.deepcopy(word_box_list))
31
+ word_box_list = self.reverse_rotate_crop_image(
32
+ copy.deepcopy(box), word_box_list, direction
33
+ )
34
+ res.append(
35
+ [rec_txt, rec_conf, word_box_list, word_box_content_list, conf_list]
36
+ )
37
+ return res
38
+
39
+ @staticmethod
40
+ def get_box_direction(box: np.ndarray) -> str:
41
+ direction = "w"
42
+ img_crop_width = int(
43
+ max(
44
+ np.linalg.norm(box[0] - box[1]),
45
+ np.linalg.norm(box[2] - box[3]),
46
+ )
47
+ )
48
+ img_crop_height = int(
49
+ max(
50
+ np.linalg.norm(box[0] - box[3]),
51
+ np.linalg.norm(box[1] - box[2]),
52
+ )
53
+ )
54
+ if img_crop_height * 1.0 / img_crop_width >= 1.5:
55
+ direction = "h"
56
+ return direction
57
+
58
+ @staticmethod
59
+ def cal_ocr_word_box(
60
+ rec_txt: str, box: np.ndarray, rec_word_info: List[Tuple[str, List[int]]]
61
+ ) -> Tuple[List[str], List[List[int]], List[float]]:
62
+ """Calculate the detection frame for each word based on the results of recognition and detection of ocr
63
+ 汉字坐标是单字的
64
+ 英语坐标是单词级别的
65
+ """
66
+
67
+ col_num, word_list, word_col_list, state_list, conf_list = rec_word_info
68
+ box = box.tolist()
69
+ bbox_x_start = box[0][0]
70
+ bbox_x_end = box[1][0]
71
+ bbox_y_start = box[0][1]
72
+ bbox_y_end = box[2][1]
73
+
74
+ cell_width = (bbox_x_end - bbox_x_start) / col_num
75
+ word_box_list = []
76
+ word_box_content_list = []
77
+ cn_width_list = []
78
+ en_width_list = []
79
+ cn_col_list = []
80
+ en_col_list = []
81
+
82
+ def cal_char_width(width_list, word_col_):
83
+ if len(word_col_) == 1:
84
+ return
85
+ char_total_length = (word_col_[-1] - word_col_[0]) * cell_width
86
+ char_width = char_total_length / (len(word_col_) - 1)
87
+ width_list.append(char_width)
88
+
89
+ def cal_box(col_list, width_list, word_box_list_):
90
+ if len(col_list) == 0:
91
+ return
92
+ if len(width_list) != 0:
93
+ avg_char_width = np.mean(width_list)
94
+ else:
95
+ avg_char_width = (bbox_x_end - bbox_x_start) / len(rec_txt)
96
+
97
+ for center_idx in col_list:
98
+ center_x = (center_idx + 0.5) * cell_width
99
+ cell_x_start = max(int(center_x - avg_char_width / 2), 0) + bbox_x_start
100
+ cell_x_end = (
101
+ min(int(center_x + avg_char_width / 2), bbox_x_end - bbox_x_start)
102
+ + bbox_x_start
103
+ )
104
+ cell = [
105
+ [cell_x_start, bbox_y_start],
106
+ [cell_x_end, bbox_y_start],
107
+ [cell_x_end, bbox_y_end],
108
+ [cell_x_start, bbox_y_end],
109
+ ]
110
+ word_box_list_.append(cell)
111
+
112
+ for word, word_col, state in zip(word_list, word_col_list, state_list):
113
+ if state == "cn":
114
+ cal_char_width(cn_width_list, word_col)
115
+ cn_col_list += word_col
116
+ word_box_content_list += word
117
+ else:
118
+ cal_char_width(en_width_list, word_col)
119
+ en_col_list += word_col
120
+ word_box_content_list += word
121
+
122
+ cal_box(cn_col_list, cn_width_list, word_box_list)
123
+ cal_box(en_col_list, en_width_list, word_box_list)
124
+ sorted_word_box_list = sorted(word_box_list, key=lambda box: box[0][0])
125
+ return word_box_content_list, sorted_word_box_list, conf_list
126
+
127
+ @staticmethod
128
+ def adjust_box_overlap(
129
+ word_box_list: List[List[List[int]]],
130
+ ) -> List[List[List[int]]]:
131
+ # 调整bbox有重叠的地方
132
+ for i in range(len(word_box_list) - 1):
133
+ cur, nxt = word_box_list[i], word_box_list[i + 1]
134
+ if cur[1][0] > nxt[0][0]: # 有交集
135
+ distance = abs(cur[1][0] - nxt[0][0])
136
+ cur[1][0] -= distance / 2
137
+ cur[2][0] -= distance / 2
138
+ nxt[0][0] += distance - distance / 2
139
+ nxt[3][0] += distance - distance / 2
140
+ return word_box_list
141
+
142
+ def reverse_rotate_crop_image(
143
+ self,
144
+ bbox_points: np.ndarray,
145
+ word_points_list: List[List[List[int]]],
146
+ direction: str = "w",
147
+ ) -> List[List[List[int]]]:
148
+ """
149
+ get_rotate_crop_image的逆操作
150
+ img为原图
151
+ part_img为crop后的图
152
+ bbox_points为part_img中对应在原图的bbox, 四个点,左上,右上,右下,左下
153
+ part_points为在part_img中的点[(x, y), (x, y)]
154
+ """
155
+ bbox_points = np.float32(bbox_points)
156
+
157
+ left = int(np.min(bbox_points[:, 0]))
158
+ top = int(np.min(bbox_points[:, 1]))
159
+ bbox_points[:, 0] = bbox_points[:, 0] - left
160
+ bbox_points[:, 1] = bbox_points[:, 1] - top
161
+
162
+ img_crop_width = int(np.linalg.norm(bbox_points[0] - bbox_points[1]))
163
+ img_crop_height = int(np.linalg.norm(bbox_points[0] - bbox_points[3]))
164
+
165
+ pts_std = np.array(
166
+ [
167
+ [0, 0],
168
+ [img_crop_width, 0],
169
+ [img_crop_width, img_crop_height],
170
+ [0, img_crop_height],
171
+ ]
172
+ ).astype(np.float32)
173
+ M = cv2.getPerspectiveTransform(bbox_points, pts_std)
174
+ _, IM = cv2.invert(M)
175
+
176
+ new_word_points_list = []
177
+ for word_points in word_points_list:
178
+ new_word_points = []
179
+ for point in word_points:
180
+ new_point = point
181
+ if direction == "h":
182
+ new_point = self.s_rotate(
183
+ math.radians(-90), new_point[0], new_point[1], 0, 0
184
+ )
185
+ new_point[0] = new_point[0] + img_crop_width
186
+
187
+ p = np.float32(new_point + [1])
188
+ x, y, z = np.dot(IM, p)
189
+ new_point = [x / z, y / z]
190
+
191
+ new_point = [int(new_point[0] + left), int(new_point[1] + top)]
192
+ new_word_points.append(new_point)
193
+ new_word_points = self.order_points(new_word_points)
194
+ new_word_points_list.append(new_word_points)
195
+ return new_word_points_list
196
+
197
+ @staticmethod
198
+ def s_rotate(angle, valuex, valuey, pointx, pointy):
199
+ """绕pointx,pointy顺时针旋转
200
+ https://blog.csdn.net/qq_38826019/article/details/84233397
201
+ """
202
+ valuex = np.array(valuex)
203
+ valuey = np.array(valuey)
204
+ sRotatex = (
205
+ (valuex - pointx) * math.cos(angle)
206
+ + (valuey - pointy) * math.sin(angle)
207
+ + pointx
208
+ )
209
+ sRotatey = (
210
+ (valuey - pointy) * math.cos(angle)
211
+ - (valuex - pointx) * math.sin(angle)
212
+ + pointy
213
+ )
214
+ return [sRotatex, sRotatey]
215
+
216
+ @staticmethod
217
+ def order_points(box: List[List[int]]) -> List[List[int]]:
218
+ """矩形框顺序排列"""
219
+
220
+ def convert_to_1x2(p):
221
+ if p.shape == (2,):
222
+ return p.reshape((1, 2))
223
+ elif p.shape == (1, 2):
224
+ return p
225
+ else:
226
+ return p[:1, :]
227
+
228
+ box = np.array(box).reshape((-1, 2))
229
+ center_x, center_y = np.mean(box[:, 0]), np.mean(box[:, 1])
230
+ if np.any(box[:, 0] == center_x) and np.any(
231
+ box[:, 1] == center_y
232
+ ): # 有两点横坐标相等,有两点纵坐标相等,菱形
233
+ p1 = box[np.where(box[:, 0] == np.min(box[:, 0]))]
234
+ p2 = box[np.where(box[:, 1] == np.min(box[:, 1]))]
235
+ p3 = box[np.where(box[:, 0] == np.max(box[:, 0]))]
236
+ p4 = box[np.where(box[:, 1] == np.max(box[:, 1]))]
237
+ elif np.all(box[:, 0] == center_x): # 四个点的横坐标都相同
238
+ y_sort = np.argsort(box[:, 1])
239
+ p1 = box[y_sort[0]]
240
+ p2 = box[y_sort[1]]
241
+ p3 = box[y_sort[2]]
242
+ p4 = box[y_sort[3]]
243
+ elif np.any(box[:, 0] == center_x) and np.all(
244
+ box[:, 1] != center_y
245
+ ): # 只有两点横坐标相等,先上下再左右
246
+ p12, p34 = (
247
+ box[np.where(box[:, 1] < center_y)],
248
+ box[np.where(box[:, 1] > center_y)],
249
+ )
250
+ p1, p2 = (
251
+ p12[np.where(p12[:, 0] == np.min(p12[:, 0]))],
252
+ p12[np.where(p12[:, 0] == np.max(p12[:, 0]))],
253
+ )
254
+ p3, p4 = (
255
+ p34[np.where(p34[:, 0] == np.max(p34[:, 0]))],
256
+ p34[np.where(p34[:, 0] == np.min(p34[:, 0]))],
257
+ )
258
+ else: # 只有两点纵坐标相等,或者是没有相等的,先左右再上下
259
+ p14, p23 = (
260
+ box[np.where(box[:, 0] < center_x)],
261
+ box[np.where(box[:, 0] > center_x)],
262
+ )
263
+ p1, p4 = (
264
+ p14[np.where(p14[:, 1] == np.min(p14[:, 1]))],
265
+ p14[np.where(p14[:, 1] == np.max(p14[:, 1]))],
266
+ )
267
+ p2, p3 = (
268
+ p23[np.where(p23[:, 1] == np.min(p23[:, 1]))],
269
+ p23[np.where(p23[:, 1] == np.max(p23[:, 1]))],
270
+ )
271
+
272
+ # 解决单字切割后横坐标完全相同的shape错误
273
+ p1 = convert_to_1x2(p1)
274
+ p2 = convert_to_1x2(p2)
275
+ p3 = convert_to_1x2(p3)
276
+ p4 = convert_to_1x2(p4)
277
+ return np.array([p1, p2, p3, p4]).reshape((-1, 2)).tolist()
anyocr/infer_engine.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, List, Tuple, Union
2
+ import os
3
+ import platform
4
+ import traceback
5
+ from enum import Enum
6
+ from pathlib import Path
7
+ import numpy as np
8
+ from onnxruntime import (
9
+ GraphOptimizationLevel,
10
+ InferenceSession,
11
+ SessionOptions,
12
+ get_available_providers,
13
+ get_device,
14
+ )
15
+
16
+
17
+ class EP(Enum):
18
+ CPU_EP = "CPUExecutionProvider"
19
+ CUDA_EP = "CUDAExecutionProvider"
20
+ DIRECTML_EP = "DmlExecutionProvider"
21
+
22
+
23
+ class OrtInferSession:
24
+ def __init__(self, config: Dict[str, Any]):
25
+ self.model_path = config.get("model_path", None)
26
+ self._verify_model(self.model_path)
27
+ self.config = config
28
+ self.cfg_use_cuda = config.get("use_cuda", None)
29
+ self.cfg_use_dml = config.get("use_dml", None)
30
+
31
+ self.had_providers: List[str] = get_available_providers()
32
+ self.EP_list = self._get_ep_list()
33
+
34
+ self.sess_opt = self._init_sess_opts(self.config)
35
+ self.session = InferenceSession(
36
+ self.model_path,
37
+ sess_options=self.sess_opt,
38
+ providers=self.EP_list,
39
+ )
40
+ self._verify_providers()
41
+
42
+ @staticmethod
43
+ def _init_sess_opts(config: Dict[str, Any]) -> SessionOptions:
44
+ sess_opt = SessionOptions()
45
+ sess_opt.log_severity_level = 4
46
+ sess_opt.enable_cpu_mem_arena = False
47
+ sess_opt.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL
48
+
49
+ cpu_nums = os.cpu_count()
50
+ intra_op_num_threads = config.get("intra_op_num_threads", -1)
51
+ if intra_op_num_threads != -1 and 1 <= intra_op_num_threads <= cpu_nums:
52
+ sess_opt.intra_op_num_threads = intra_op_num_threads
53
+
54
+ inter_op_num_threads = config.get("inter_op_num_threads", -1)
55
+ if inter_op_num_threads != -1 and 1 <= inter_op_num_threads <= cpu_nums:
56
+ sess_opt.inter_op_num_threads = inter_op_num_threads
57
+
58
+ return sess_opt
59
+
60
+ def _get_ep_list(self) -> List[Tuple[str, Dict[str, Any]]]:
61
+ cpu_provider_opts = {
62
+ "arena_extend_strategy": "kSameAsRequested",
63
+ }
64
+ EP_list = [(EP.CPU_EP.value, cpu_provider_opts)]
65
+
66
+ cuda_provider_opts = {
67
+ "device_id": 0,
68
+ "arena_extend_strategy": "kNextPowerOfTwo",
69
+ "cudnn_conv_algo_search": "EXHAUSTIVE",
70
+ "do_copy_in_default_stream": True,
71
+ }
72
+ self.use_cuda = self._check_cuda()
73
+ if self.use_cuda:
74
+ EP_list.insert(0, (EP.CUDA_EP.value, cuda_provider_opts))
75
+
76
+ self.use_directml = self._check_dml()
77
+ if self.use_directml:
78
+ directml_options = (
79
+ cuda_provider_opts if self.use_cuda else cpu_provider_opts
80
+ )
81
+ EP_list.insert(0, (EP.DIRECTML_EP.value, directml_options))
82
+ return EP_list
83
+
84
+ def _check_cuda(self) -> bool:
85
+ if not self.cfg_use_cuda:
86
+ return False
87
+
88
+ cur_device = get_device()
89
+ if cur_device == "GPU" and EP.CUDA_EP.value in self.had_providers:
90
+ return True
91
+ return False
92
+
93
+ def _check_dml(self) -> bool:
94
+ if not self.cfg_use_dml:
95
+ return False
96
+
97
+ cur_os = platform.system()
98
+ if cur_os != "Windows":
99
+ return False
100
+
101
+ cur_window_version = int(platform.release().split(".")[0])
102
+ if cur_window_version < 10:
103
+ return False
104
+
105
+ if EP.DIRECTML_EP.value in self.had_providers:
106
+ return True
107
+ return False
108
+
109
+ def _verify_providers(self):
110
+ session_providers = self.session.get_providers()
111
+ first_provider = session_providers[0]
112
+
113
+ def __call__(self, input_content: np.ndarray) -> np.ndarray:
114
+ try:
115
+ if not self.session:
116
+ self.session = InferenceSession(
117
+ self.model_path,
118
+ sess_options=self.sess_opt,
119
+ providers=self.EP_list,
120
+ )
121
+ self._verify_providers()
122
+ input_dict = dict(zip(self.get_input_names(), [input_content]))
123
+ res = self.session.run(self.get_output_names(), input_dict)
124
+ return res
125
+ except Exception as e:
126
+ error_info = traceback.format_exc()
127
+ raise ONNXRuntimeError(error_info) from e
128
+ finally:
129
+ del input_dict
130
+ self.session = None
131
+
132
+ def get_input_names(self) -> List[str]:
133
+ return [v.name for v in self.session.get_inputs()]
134
+
135
+ def get_output_names(self) -> List[str]:
136
+ return [v.name for v in self.session.get_outputs()]
137
+
138
+ def get_character_list(self, key: str = "character") -> List[str]:
139
+ meta_dict = self.session.get_modelmeta().custom_metadata_map
140
+ return meta_dict[key].splitlines()
141
+
142
+ def have_key(self, key: str = "character") -> bool:
143
+ meta_dict = self.session.get_modelmeta().custom_metadata_map
144
+ if key in meta_dict.keys():
145
+ return True
146
+ return False
147
+
148
+ @staticmethod
149
+ def _verify_model(model_path: Union[str, Path, None]):
150
+ if model_path is None:
151
+ raise ValueError("model_path is None!")
152
+
153
+ model_path = Path(model_path)
154
+ if not model_path.exists():
155
+ raise FileNotFoundError(f"{model_path} does not exists.")
156
+
157
+ if not model_path.is_file():
158
+ raise FileExistsError(f"{model_path} is not a file.")
159
+
160
+
161
+ class ONNXRuntimeError(Exception):
162
+ pass
anyocr/models/anyocr_cls_v4.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e263cad54b85e6dd6ce5aed66154785682a221be2216300ce321e58a9d022296
3
+ size 571423
anyocr/models/anyocr_det_ch_v4_lite.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:69ce850fec741a2a4568c7c924bb025c9d4f1129e5f96ab428c799ccc5ef2275
3
+ size 4729474
anyocr/models/anyocr_keys_v4.txt 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
+
anyocr/models/anyocr_rec_v4_server.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3a2d9fc2362736795e44aa3b157bd65994b787decf9a4d0fcdcb14f220a498e
3
+ size 90500640
anyocr/pipeline.py ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, List, Optional, Tuple, Union
2
+ import cv2
3
+ import copy
4
+ import numpy as np
5
+ from pathlib import Path
6
+ from pydantic import BaseModel
7
+ from anyocr.cal_rec_boxes import CalRecBoxes
8
+ from anyocr.text_cls import TextClassifier
9
+ from anyocr.text_det import TextDetector
10
+ from anyocr.text_rec import TextRecognizer
11
+ from anyocr.utils import (
12
+ LoadImage,
13
+ add_round_letterbox,
14
+ increase_min_side,
15
+ reduce_max_side
16
+ )
17
+
18
+
19
+ class PipelineOCR:
20
+ def __init__(self, **kwargs):
21
+ config = kwargs
22
+ global_config = config["Global"]
23
+ self.print_verbose = global_config["print_verbose"]
24
+ self.text_score = global_config["text_score"]
25
+ self.min_height = global_config["min_height"]
26
+ self.width_height_ratio = global_config["width_height_ratio"]
27
+
28
+ self.use_det = global_config["use_det"]
29
+ self.text_det = TextDetector(config["Det"])
30
+
31
+ self.use_cls = global_config["use_cls"]
32
+ self.text_cls = TextClassifier(config["Cls"])
33
+
34
+ self.use_rec = global_config["use_rec"]
35
+ self.text_rec = TextRecognizer(config["Rec"])
36
+
37
+ self.load_img = LoadImage()
38
+ self.max_side_len = global_config["max_side_len"]
39
+ self.min_side_len = global_config["min_side_len"]
40
+
41
+ self.cal_rec_boxes = CalRecBoxes()
42
+
43
+ def __call__(
44
+ self,
45
+ img_content: Union[str, np.ndarray, bytes, Path],
46
+ use_det: Optional[bool] = None,
47
+ use_cls: Optional[bool] = None,
48
+ use_rec: Optional[bool] = None,
49
+ **kwargs,
50
+ ) -> Tuple[Optional[List[List[Union[Any, str]]]], Optional[List[float]]]:
51
+ use_det = self.use_det if use_det is None else use_det
52
+ use_cls = self.use_cls if use_cls is None else use_cls
53
+ use_rec = self.use_rec if use_rec is None else use_rec
54
+ return_word_box = False
55
+ if kwargs:
56
+ box_thresh = kwargs.get("box_thresh", 0.5)
57
+ unclip_ratio = kwargs.get("unclip_ratio", 1.6)
58
+ text_score = kwargs.get("text_score", 0.5)
59
+ return_word_box = kwargs.get("return_word_box", False)
60
+ self.text_det.postprocess_op.box_thresh = box_thresh
61
+ self.text_det.postprocess_op.unclip_ratio = unclip_ratio
62
+ self.text_score = text_score
63
+
64
+ img = self.load_img(img_content)
65
+
66
+ raw_h, raw_w = img.shape[:2]
67
+ op_record = {}
68
+ img, ratio_h, ratio_w = self.preprocess(img)
69
+ op_record["preprocess"] = {"ratio_h": ratio_h, "ratio_w": ratio_w}
70
+
71
+ dt_boxes, cls_res, rec_res = None, None, None
72
+ det_elapse, cls_elapse, rec_elapse = 0.0, 0.0, 0.0
73
+
74
+ if use_det:
75
+ img, op_record = self.maybe_add_letterbox(img, op_record)
76
+ dt_boxes, det_elapse = self.auto_text_det(img)
77
+ if dt_boxes is None:
78
+ return None, None
79
+
80
+ img = self.get_crop_img_list(img, dt_boxes)
81
+ # print(img)
82
+ if use_cls:
83
+ img, cls_res, cls_elapse = self.text_cls(img)
84
+ # print(img)
85
+
86
+ if use_rec:
87
+ rec_res, rec_elapse = self.text_rec(img, return_word_box)
88
+
89
+ if dt_boxes is not None and rec_res is not None and return_word_box:
90
+ rec_res = self.cal_rec_boxes(img, dt_boxes, rec_res)
91
+ for rec_res_i in rec_res:
92
+ if rec_res_i[2]:
93
+ rec_res_i[2] = (
94
+ self._get_origin_points(rec_res_i[2], op_record, raw_h, raw_w)
95
+ .astype(np.int32)
96
+ .tolist()
97
+ )
98
+
99
+ if dt_boxes is not None:
100
+ dt_boxes = self._get_origin_points(dt_boxes, op_record, raw_h, raw_w)
101
+
102
+ ocr_res = self.get_final_res(
103
+ dt_boxes, cls_res, rec_res, det_elapse, cls_elapse, rec_elapse
104
+ )
105
+ del img, dt_boxes, cls_res, rec_res
106
+ return ocr_res
107
+
108
+ def preprocess(self, img: np.ndarray) -> Tuple[np.ndarray, float, float]:
109
+ h, w = img.shape[:2]
110
+ max_value = max(h, w)
111
+ ratio_h = ratio_w = 1.0
112
+ if max_value > self.max_side_len:
113
+ img, ratio_h, ratio_w = reduce_max_side(img, self.max_side_len)
114
+
115
+ h, w = img.shape[:2]
116
+ min_value = min(h, w)
117
+ if min_value < self.min_side_len:
118
+ img, ratio_h, ratio_w = increase_min_side(img, self.min_side_len)
119
+ return img, ratio_h, ratio_w
120
+
121
+ def maybe_add_letterbox(
122
+ self, img: np.ndarray, op_record: Dict[str, Any]
123
+ ) -> Tuple[np.ndarray, Dict[str, Any]]:
124
+ h, w = img.shape[:2]
125
+
126
+ if self.width_height_ratio == -1:
127
+ use_limit_ratio = False
128
+ else:
129
+ use_limit_ratio = w / h > self.width_height_ratio
130
+
131
+ if h <= self.min_height or use_limit_ratio:
132
+ padding_h = self._get_padding_h(h, w)
133
+ block_img = add_round_letterbox(img, (padding_h, padding_h, 0, 0))
134
+ op_record["padding_1"] = {"top": padding_h, "left": 0}
135
+ return block_img, op_record
136
+
137
+ op_record["padding_1"] = {"top": 0, "left": 0}
138
+ return img, op_record
139
+
140
+ def _get_padding_h(self, h: int, w: int) -> int:
141
+ new_h = max(int(w / self.width_height_ratio), self.min_height) * 2
142
+ padding_h = int(abs(new_h - h) / 2)
143
+ return padding_h
144
+
145
+ def auto_text_det(
146
+ self, img: np.ndarray
147
+ ) -> Tuple[Optional[List[np.ndarray]], float]:
148
+ dt_boxes, det_elapse = self.text_det(img)
149
+ if dt_boxes is None or len(dt_boxes) < 1:
150
+ return None, 0.0
151
+
152
+ dt_boxes = self.sorted_boxes(dt_boxes)
153
+ return dt_boxes, det_elapse
154
+
155
+ def get_crop_img_list(
156
+ self, img: np.ndarray, dt_boxes: List[np.ndarray]
157
+ ) -> List[np.ndarray]:
158
+ def get_rotate_crop_image(img: np.ndarray, points: np.ndarray) -> np.ndarray:
159
+ img_crop_width = int(
160
+ max(
161
+ np.linalg.norm(points[0] - points[1]),
162
+ np.linalg.norm(points[2] - points[3]),
163
+ )
164
+ )
165
+ img_crop_height = int(
166
+ max(
167
+ np.linalg.norm(points[0] - points[3]),
168
+ np.linalg.norm(points[1] - points[2]),
169
+ )
170
+ )
171
+ pts_std = np.array(
172
+ [
173
+ [0, 0],
174
+ [img_crop_width, 0],
175
+ [img_crop_width, img_crop_height],
176
+ [0, img_crop_height],
177
+ ]
178
+ ).astype(np.float32)
179
+ M = cv2.getPerspectiveTransform(points, pts_std)
180
+ dst_img = cv2.warpPerspective(
181
+ img,
182
+ M,
183
+ (img_crop_width, img_crop_height),
184
+ borderMode=cv2.BORDER_REPLICATE,
185
+ flags=cv2.INTER_CUBIC,
186
+ )
187
+ dst_img_height, dst_img_width = dst_img.shape[0:2]
188
+ if dst_img_height * 1.0 / dst_img_width >= 1.5:
189
+ dst_img = np.rot90(dst_img)
190
+ return dst_img
191
+
192
+ img_crop_list = []
193
+ for box in dt_boxes:
194
+ tmp_box = copy.deepcopy(box)
195
+ img_crop = get_rotate_crop_image(img, tmp_box)
196
+ img_crop_list.append(img_crop)
197
+ return img_crop_list
198
+
199
+ @staticmethod
200
+ def sorted_boxes(dt_boxes: np.ndarray) -> List[np.ndarray]:
201
+ """
202
+ Sort text boxes in order from top to bottom, left to right
203
+ args:
204
+ dt_boxes(array):detected text boxes with shape [4, 2]
205
+ return:
206
+ sorted boxes(array) with shape [4, 2]
207
+ """
208
+ num_boxes = dt_boxes.shape[0]
209
+ sorted_boxes = sorted(dt_boxes, key=lambda x: (x[0][1], x[0][0]))
210
+ _boxes = list(sorted_boxes)
211
+
212
+ for i in range(num_boxes - 1):
213
+ for j in range(i, -1, -1):
214
+ if (
215
+ abs(_boxes[j + 1][0][1] - _boxes[j][0][1]) < 10
216
+ and _boxes[j + 1][0][0] < _boxes[j][0][0]
217
+ ):
218
+ tmp = _boxes[j]
219
+ _boxes[j] = _boxes[j + 1]
220
+ _boxes[j + 1] = tmp
221
+ else:
222
+ break
223
+ return _boxes
224
+
225
+ def _get_origin_points(
226
+ self,
227
+ dt_boxes: List[np.ndarray],
228
+ op_record: Dict[str, Any],
229
+ raw_h: int,
230
+ raw_w: int,
231
+ ) -> np.ndarray:
232
+ dt_boxes_array = np.array(dt_boxes).astype(np.float32)
233
+ for op in reversed(list(op_record.keys())):
234
+ v = op_record[op]
235
+ if "padding" in op:
236
+ top, left = v.get("top"), v.get("left")
237
+ dt_boxes_array[:, :, 0] -= left
238
+ dt_boxes_array[:, :, 1] -= top
239
+ elif "preprocess" in op:
240
+ ratio_h = v.get("ratio_h")
241
+ ratio_w = v.get("ratio_w")
242
+ dt_boxes_array[:, :, 0] *= ratio_w
243
+ dt_boxes_array[:, :, 1] *= ratio_h
244
+
245
+ dt_boxes_array = np.where(dt_boxes_array < 0, 0, dt_boxes_array)
246
+ dt_boxes_array[..., 0] = np.where(
247
+ dt_boxes_array[..., 0] > raw_w, raw_w, dt_boxes_array[..., 0]
248
+ )
249
+ dt_boxes_array[..., 1] = np.where(
250
+ dt_boxes_array[..., 1] > raw_h, raw_h, dt_boxes_array[..., 1]
251
+ )
252
+ return dt_boxes_array
253
+
254
+ def get_final_res(
255
+ self,
256
+ dt_boxes: Optional[List[np.ndarray]],
257
+ cls_res: Optional[List[List[Union[str, float]]]],
258
+ rec_res: Optional[List[Tuple[str, float, List[Union[str, float]]]]],
259
+ det_elapse: float,
260
+ cls_elapse: float,
261
+ rec_elapse: float,
262
+ ) -> Tuple[Optional[List[List[Union[Any, str]]]], Optional[List[float]]]:
263
+ if dt_boxes is None and rec_res is None and cls_res is not None:
264
+ return cls_res, [cls_elapse]
265
+
266
+ if dt_boxes is None and rec_res is None:
267
+ return None, None
268
+
269
+ if dt_boxes is None and rec_res is not None:
270
+ return [[res[0], res[1]] for res in rec_res], [rec_elapse]
271
+
272
+ if dt_boxes is not None and rec_res is None:
273
+ return [box.tolist() for box in dt_boxes], [det_elapse]
274
+
275
+ dt_boxes, rec_res = self.filter_result(dt_boxes, rec_res)
276
+ if not dt_boxes or not rec_res or len(dt_boxes) <= 0:
277
+ return None, None
278
+
279
+ ocr_res = [[box.tolist(), *res] for box, res in zip(dt_boxes, rec_res)], [
280
+ det_elapse,
281
+ cls_elapse,
282
+ rec_elapse,
283
+ ]
284
+ return ocr_res
285
+
286
+ def filter_result(
287
+ self,
288
+ dt_boxes: Optional[List[np.ndarray]],
289
+ rec_res: Optional[List[Tuple[str, float]]],
290
+ ) -> Tuple[Optional[List[np.ndarray]], Optional[List[Tuple[str, float]]]]:
291
+ if dt_boxes is None or rec_res is None:
292
+ return None, None
293
+
294
+ filter_boxes, filter_rec_res = [], []
295
+ for box, rec_reuslt in zip(dt_boxes, rec_res):
296
+ text, score = rec_reuslt[0], rec_reuslt[1]
297
+ if float(score) >= self.text_score:
298
+ filter_boxes.append(box)
299
+ filter_rec_res.append(rec_reuslt)
300
+
301
+ return filter_boxes, filter_rec_res
302
+
303
+
304
+ class anyocrConfig(BaseModel):
305
+ text_score: float = 0.5
306
+ use_det: bool = True
307
+ use_cls: bool = True
308
+ use_rec: bool = True
309
+ print_verbose: bool = False
310
+ min_height: int = 30
311
+ width_height_ratio: float = 8
312
+ max_side_len: int = 2000
313
+ min_side_len: int = 30
314
+ return_word_box: bool = False
315
+ intra_op_num_threads: int = -1
316
+ inter_op_num_threads: int = -1
317
+
318
+ det_use_cuda: bool = False
319
+ det_use_dml: bool = False
320
+ det_model_path: Optional[str] = None
321
+ det_limit_side_len: float = 736
322
+ det_limit_type: str = "min"
323
+ det_std: list = [ 0.5, 0.5, 0.5 ]
324
+ det_mean: list = [ 0.5, 0.5, 0.5 ]
325
+ det_max_candidates:int = 1000
326
+ det_thresh: float = 0.3
327
+ det_box_thresh: float = 0.5
328
+ det_unclip_ratio: float = 1.6
329
+ det_donot_use_dilation: bool = False
330
+ det_score_mode: str = "slow" #"fast"
331
+ det_intra_op_num_threads: int = -1
332
+ det_inter_op_num_threads: int = -1
333
+
334
+ cls_use_cuda: bool = False
335
+ cls_use_dml: bool = False
336
+ cls_model_path: Optional[str] = None
337
+ cls_image_shape: List[int] = [3, 48, 192]
338
+ cls_label_list: List[str] = ["0", "180"]
339
+ cls_batch_num: int = 6
340
+ cls_thresh: float = 0.9
341
+ cls_intra_op_num_threads: int = -1
342
+ cls_inter_op_num_threads: int = -1
343
+
344
+ rec_use_cuda: bool = False
345
+ rec_use_dml: bool = False
346
+ rec_keys_path: Optional[str] = None
347
+ rec_model_path: Optional[str] = None
348
+ rec_img_shape: List[int] = [3, 48, 320]
349
+ rec_batch_num: int = 6
350
+ rec_intra_op_num_threads: int = -1
351
+ rec_inter_op_num_threads: int = -1
352
+
353
+
354
+ class anyocr(object):
355
+ def __init__(self,config: dict = None):
356
+ ROOT_PATH = Path(__file__).resolve().parent
357
+ if config:
358
+ self.config = config
359
+ else:
360
+ self.config = anyocrConfig().model_dump()
361
+ self.config['det_model_path'] = ROOT_PATH.joinpath("models/anyocr_det_ch_v4_lite.onnx").as_posix()
362
+ self.config['rec_model_path'] = ROOT_PATH.joinpath("models/anyocr_rec_v4_server.onnx").as_posix()
363
+ self.config['cls_model_path'] = ROOT_PATH.joinpath("models/anyocr_cls_v4.onnx").as_posix()
364
+ self.config['rec_keys_path'] = ROOT_PATH.joinpath("models/anyocr_keys_v4.txt").as_posix()
365
+
366
+ ocrconfigraw = anyocrConfig(**self.config).model_dump()
367
+ ocrconfig = {
368
+ "Global": {},
369
+ "Det": {},
370
+ "Cls": {},
371
+ "Rec": {}
372
+ }
373
+ for x,y in ocrconfigraw.items():
374
+ if not x.startswith('det_') and not x.startswith('rec_') and not x.startswith('cls_'):
375
+ ocrconfig["Global"][x] = y
376
+ elif x.startswith('det_'):
377
+ if x in ["det_model_path", "det_use_cuda", "det_use_dml","det_std","det_mean","det_max_candidates"]:
378
+ ocrconfig["Det"][x.replace("det_","")] = y
379
+ elif x == "det_donot_use_dilation":
380
+ ocrconfig["Det"]["use_dilation"] = not y
381
+ else:
382
+ ocrconfig["Det"][x] = y
383
+ elif x.startswith('cls_'):
384
+ if x in ["cls_label_list", "cls_model_path", "cls_use_cuda", "cls_use_dml"]:
385
+ ocrconfig["Cls"][x.replace("cls_","")] = y
386
+ else:
387
+ ocrconfig["Cls"][x] = y
388
+ elif x.startswith('rec_'):
389
+ if x in ["rec_model_path", "rec_use_cuda", "rec_use_dml"]:
390
+ ocrconfig["Rec"][x.replace("rec_","")] = y
391
+ else:
392
+ ocrconfig["Rec"][x] = y
393
+
394
+ self.ocrmodel = PipelineOCR(**ocrconfig)
395
+
396
+ def cls_line_wordbox(self, wordbox):
397
+ res = {'1': [wordbox[0]]}
398
+ for item in wordbox[1:]:
399
+ box = item[-1]
400
+ x1,y1 = box[0]
401
+ x2,y2 = box[2]
402
+ reference_y = (y2 + y1) / 2
403
+ flag = True
404
+ for k in list(res.keys()):
405
+ start_box = res[k][0][-1]
406
+ start_y1 = start_box[0][-1]
407
+ start_y2 = start_box[2][-1]
408
+ start_reference_y = (start_y2 + start_y1) / 2
409
+ threshold = start_y2 - start_reference_y
410
+ if abs(start_reference_y - reference_y) < threshold:
411
+ res[k].append(item)
412
+ flag = False
413
+ break
414
+ if flag:
415
+ item_key = f'{len(list(res.keys())) + 1}'
416
+ res[item_key] = [item]
417
+ return res
418
+
419
+ def completions(
420
+ self,
421
+ image,
422
+ use_det = True,
423
+ use_cls = True,
424
+ use_rec = True,
425
+ cls_line = True,
426
+ **kwargs
427
+ ):
428
+ res = []
429
+
430
+ result,elapse = self.ocrmodel(image,use_det=use_det,use_cls=use_cls,use_rec=use_rec,**kwargs)
431
+ if not result:
432
+ return ""
433
+ for line in result:
434
+ if line:
435
+ box,text,score = line
436
+ res.append([text,box])
437
+ if not cls_line:
438
+ content = ''.join([f"{x[0]}" for x in res])
439
+ else:
440
+ try:
441
+ content = ''
442
+ lines = self.cls_line_wordbox(res)
443
+ lines = list(sorted(lines.items(),key = lambda x: int(x[0])))
444
+ for line in lines:
445
+ content += f'{" ".join([str(x[0]) for x in line[-1]])}\n'
446
+ except:
447
+ content = ''.join([f"{x[0]}" for x in res])
448
+ return content
449
+
450
+ def raw_completions(
451
+ self,
452
+ image,
453
+ use_det = True,
454
+ use_cls = True,
455
+ use_rec = True,
456
+ **kwargs
457
+ ):
458
+ result,elapse = self.ocrmodel(image,use_det=use_det,use_cls=use_cls,use_rec=use_rec,**kwargs)
459
+ return result,elapse
anyocr/text_cls.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict, List, Tuple, Union
2
+ import copy
3
+ import math
4
+ import time
5
+ import cv2
6
+ import numpy as np
7
+ from anyocr.infer_engine import OrtInferSession
8
+
9
+
10
+ class ClsPostProcess:
11
+ def __init__(self, label_list: List[str]):
12
+ self.label_list = label_list
13
+
14
+ def __call__(self, preds: np.ndarray) -> List[Tuple[str, float]]:
15
+ pred_idxs = preds.argmax(axis=1)
16
+ decode_out = [
17
+ (self.label_list[idx], preds[i, idx]) for i, idx in enumerate(pred_idxs)
18
+ ]
19
+ return decode_out
20
+
21
+
22
+ class TextClassifier:
23
+ def __init__(self, config: Dict[str, Any]):
24
+ self.cls_image_shape = config["cls_image_shape"]
25
+ self.cls_batch_num = config["cls_batch_num"]
26
+ self.cls_thresh = config["cls_thresh"]
27
+ self.postprocess_op = ClsPostProcess(config["label_list"])
28
+
29
+ self.infer = OrtInferSession(config)
30
+
31
+ def __call__(
32
+ self, img_list: Union[np.ndarray, List[np.ndarray]]
33
+ ) -> Tuple[List[np.ndarray], List[List[Union[str, float]]], float]:
34
+ if isinstance(img_list, np.ndarray):
35
+ img_list = [img_list]
36
+
37
+ img_list = copy.deepcopy(img_list)
38
+
39
+ # Calculate the aspect ratio of all text bars
40
+ width_list = [img.shape[1] / float(img.shape[0]) for img in img_list]
41
+
42
+ # Sorting can speed up the cls process
43
+ indices = np.argsort(np.array(width_list))
44
+
45
+ img_num = len(img_list)
46
+ cls_res = [["", 0.0]] * img_num
47
+ batch_num = self.cls_batch_num
48
+ elapse = 0
49
+ for beg_img_no in range(0, img_num, batch_num):
50
+ end_img_no = min(img_num, beg_img_no + batch_num)
51
+
52
+ norm_img_batch = []
53
+ for ino in range(beg_img_no, end_img_no):
54
+ norm_img = self.resize_norm_img(img_list[indices[ino]])
55
+ norm_img = norm_img[np.newaxis, :]
56
+ norm_img_batch.append(norm_img)
57
+ norm_img_batch = np.concatenate(norm_img_batch).astype(np.float32)
58
+
59
+ starttime = time.time()
60
+ prob_out = self.infer(norm_img_batch)[0]
61
+ cls_result = self.postprocess_op(prob_out)
62
+ elapse += time.time() - starttime
63
+
64
+ for rno, (label, score) in enumerate(cls_result):
65
+ cls_res[indices[beg_img_no + rno]] = [label, score]
66
+ if "180" in label and score > self.cls_thresh:
67
+ img_list[indices[beg_img_no + rno]] = cv2.rotate(
68
+ img_list[indices[beg_img_no + rno]], 1
69
+ )
70
+ return img_list, cls_res, elapse
71
+
72
+ def resize_norm_img(self, img: np.ndarray) -> np.ndarray:
73
+ img_c, img_h, img_w = self.cls_image_shape
74
+ h, w = img.shape[:2]
75
+ ratio = w / float(h)
76
+ if math.ceil(img_h * ratio) > img_w:
77
+ resized_w = img_w
78
+ else:
79
+ resized_w = int(math.ceil(img_h * ratio))
80
+
81
+ resized_image = cv2.resize(img, (resized_w, img_h))
82
+ resized_image = resized_image.astype("float32")
83
+ if img_c == 1:
84
+ resized_image = resized_image / 255
85
+ resized_image = resized_image[np.newaxis, :]
86
+ else:
87
+ resized_image = resized_image.transpose((2, 0, 1)) / 255
88
+
89
+ resized_image -= 0.5
90
+ resized_image /= 0.5
91
+ padding_im = np.zeros((img_c, img_h, img_w), dtype=np.float32)
92
+ padding_im[:, :, :resized_w] = resized_image
93
+ return padding_im
anyocr/text_det.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, List, Dict, Optional, Tuple
2
+ import cv2
3
+ import time
4
+ import numpy as np
5
+ import pyclipper
6
+ from shapely.geometry import Polygon
7
+ from anyocr.infer_engine import OrtInferSession
8
+
9
+
10
+ class DetPreProcess:
11
+ def __init__(
12
+ self, limit_side_len: int = 736, limit_type: str = "min", mean=None, std=None
13
+ ):
14
+ if mean is None:
15
+ mean = [0.5, 0.5, 0.5]
16
+
17
+ if std is None:
18
+ std = [0.5, 0.5, 0.5]
19
+
20
+ self.mean = np.array(mean)
21
+ self.std = np.array(std)
22
+ self.scale = 1 / 255.0
23
+
24
+ self.limit_side_len = limit_side_len
25
+ self.limit_type = limit_type
26
+
27
+ def __call__(self, img: np.ndarray) -> Optional[np.ndarray]:
28
+ resized_img = self.resize(img)
29
+ if resized_img is None:
30
+ return None
31
+
32
+ img = self.normalize(resized_img)
33
+ img = self.permute(img)
34
+ img = np.expand_dims(img, axis=0).astype(np.float32)
35
+ return img
36
+
37
+ def normalize(self, img: np.ndarray) -> np.ndarray:
38
+ return (img.astype("float32") * self.scale - self.mean) / self.std
39
+
40
+ def permute(self, img: np.ndarray) -> np.ndarray:
41
+ return img.transpose((2, 0, 1))
42
+
43
+ def resize(self, img: np.ndarray) -> Optional[np.ndarray]:
44
+ """resize image to a size multiple of 32 which is required by the network"""
45
+ h, w = img.shape[:2]
46
+
47
+ if self.limit_type == "max":
48
+ if max(h, w) > self.limit_side_len:
49
+ if h > w:
50
+ ratio = float(self.limit_side_len) / h
51
+ else:
52
+ ratio = float(self.limit_side_len) / w
53
+ else:
54
+ ratio = 1.0
55
+ else:
56
+ if min(h, w) < self.limit_side_len:
57
+ if h < w:
58
+ ratio = float(self.limit_side_len) / h
59
+ else:
60
+ ratio = float(self.limit_side_len) / w
61
+ else:
62
+ ratio = 1.0
63
+
64
+ resize_h = int(h * ratio)
65
+ resize_w = int(w * ratio)
66
+
67
+ resize_h = int(round(resize_h / 32) * 32)
68
+ resize_w = int(round(resize_w / 32) * 32)
69
+
70
+ try:
71
+ if int(resize_w) <= 0 or int(resize_h) <= 0:
72
+ return None
73
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
74
+ except Exception as exc:
75
+ raise ResizeImgError from exc
76
+
77
+ return img
78
+
79
+
80
+ class ResizeImgError(Exception):
81
+ pass
82
+
83
+
84
+ class DBPostProcess:
85
+ """The post process for Differentiable Binarization (DB)."""
86
+
87
+ def __init__(
88
+ self,
89
+ thresh: float = 0.3,
90
+ box_thresh: float = 0.7,
91
+ max_candidates: int = 1000,
92
+ unclip_ratio: float = 2.0,
93
+ score_mode: str = "fast",
94
+ use_dilation: bool = False,
95
+ ):
96
+ self.thresh = thresh
97
+ self.box_thresh = box_thresh
98
+ self.max_candidates = max_candidates
99
+ self.unclip_ratio = unclip_ratio
100
+ self.min_size = 3
101
+ self.score_mode = score_mode
102
+
103
+ self.dilation_kernel = None
104
+ if use_dilation:
105
+ self.dilation_kernel = np.array([[1, 1], [1, 1]])
106
+
107
+ def __call__(
108
+ self, pred: np.ndarray, ori_shape: Tuple[int, int]
109
+ ) -> Tuple[np.ndarray, List[float]]:
110
+ src_h, src_w = ori_shape
111
+ pred = pred[:, 0, :, :]
112
+ segmentation = pred > self.thresh
113
+
114
+ mask = segmentation[0]
115
+ if self.dilation_kernel is not None:
116
+ mask = cv2.dilate(
117
+ np.array(segmentation[0]).astype(np.uint8), self.dilation_kernel
118
+ )
119
+ boxes, scores = self.boxes_from_bitmap(pred[0], mask, src_w, src_h)
120
+ return boxes, scores
121
+
122
+ def boxes_from_bitmap(
123
+ self, pred: np.ndarray, bitmap: np.ndarray, dest_width: int, dest_height: int
124
+ ) -> Tuple[np.ndarray, List[float]]:
125
+ """
126
+ bitmap: single map with shape (1, H, W),
127
+ whose values are binarized as {0, 1}
128
+ """
129
+
130
+ height, width = bitmap.shape
131
+
132
+ outs = cv2.findContours(
133
+ (bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE
134
+ )
135
+ if len(outs) == 3:
136
+ img, contours, _ = outs[0], outs[1], outs[2]
137
+ elif len(outs) == 2:
138
+ contours, _ = outs[0], outs[1]
139
+
140
+ num_contours = min(len(contours), self.max_candidates)
141
+
142
+ boxes, scores = [], []
143
+ for index in range(num_contours):
144
+ contour = contours[index]
145
+ points, sside = self.get_mini_boxes(contour)
146
+ if sside < self.min_size:
147
+ continue
148
+
149
+ if self.score_mode == "fast":
150
+ score = self.box_score_fast(pred, points.reshape(-1, 2))
151
+ else:
152
+ score = self.box_score_slow(pred, contour)
153
+
154
+ if self.box_thresh > score:
155
+ continue
156
+
157
+ box = self.unclip(points)
158
+ box, sside = self.get_mini_boxes(box)
159
+ if sside < self.min_size + 2:
160
+ continue
161
+
162
+ box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width)
163
+ box[:, 1] = np.clip(
164
+ np.round(box[:, 1] / height * dest_height), 0, dest_height
165
+ )
166
+ boxes.append(box.astype(np.int32))
167
+ scores.append(score)
168
+ return np.array(boxes, dtype=np.int32), scores
169
+
170
+ def get_mini_boxes(self, contour: np.ndarray) -> Tuple[np.ndarray, float]:
171
+ bounding_box = cv2.minAreaRect(contour)
172
+ points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])
173
+
174
+ index_1, index_2, index_3, index_4 = 0, 1, 2, 3
175
+ if points[1][1] > points[0][1]:
176
+ index_1 = 0
177
+ index_4 = 1
178
+ else:
179
+ index_1 = 1
180
+ index_4 = 0
181
+
182
+ if points[3][1] > points[2][1]:
183
+ index_2 = 2
184
+ index_3 = 3
185
+ else:
186
+ index_2 = 3
187
+ index_3 = 2
188
+
189
+ box = np.array(
190
+ [points[index_1], points[index_2], points[index_3], points[index_4]]
191
+ )
192
+ return box, min(bounding_box[1])
193
+
194
+ @staticmethod
195
+ def box_score_fast(bitmap: np.ndarray, _box: np.ndarray) -> float:
196
+ h, w = bitmap.shape[:2]
197
+ box = _box.copy()
198
+ xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int32), 0, w - 1)
199
+ xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int32), 0, w - 1)
200
+ ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int32), 0, h - 1)
201
+ ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int32), 0, h - 1)
202
+
203
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
204
+ box[:, 0] = box[:, 0] - xmin
205
+ box[:, 1] = box[:, 1] - ymin
206
+ cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1)
207
+ return cv2.mean(bitmap[ymin : ymax + 1, xmin : xmax + 1], mask)[0]
208
+
209
+ def box_score_slow(self, bitmap: np.ndarray, contour: np.ndarray) -> float:
210
+ """use polyon mean score as the mean score"""
211
+ h, w = bitmap.shape[:2]
212
+ contour = contour.copy()
213
+ contour = np.reshape(contour, (-1, 2))
214
+
215
+ xmin = np.clip(np.min(contour[:, 0]), 0, w - 1)
216
+ xmax = np.clip(np.max(contour[:, 0]), 0, w - 1)
217
+ ymin = np.clip(np.min(contour[:, 1]), 0, h - 1)
218
+ ymax = np.clip(np.max(contour[:, 1]), 0, h - 1)
219
+
220
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
221
+
222
+ contour[:, 0] = contour[:, 0] - xmin
223
+ contour[:, 1] = contour[:, 1] - ymin
224
+
225
+ cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype(np.int32), 1)
226
+ return cv2.mean(bitmap[ymin : ymax + 1, xmin : xmax + 1], mask)[0]
227
+
228
+ def unclip(self, box: np.ndarray) -> np.ndarray:
229
+ unclip_ratio = self.unclip_ratio
230
+ poly = Polygon(box)
231
+ distance = poly.area * unclip_ratio / poly.length
232
+ offset = pyclipper.PyclipperOffset()
233
+ offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
234
+ expanded = np.array(offset.Execute(distance)).reshape((-1, 1, 2))
235
+ return expanded
236
+
237
+
238
+ class TextDetector:
239
+ def __init__(self, config: Dict[str, Any]):
240
+ self.limit_side_len = config.get("limit_side_len")
241
+ self.limit_type = config.get("limit_type")
242
+ self.mean = config.get("mean")
243
+ self.std = config.get("std")
244
+ self.preprocess_op = None
245
+
246
+ post_process = {
247
+ "thresh": config.get("thresh", 0.3),
248
+ "box_thresh": config.get("box_thresh", 0.5),
249
+ "max_candidates": config.get("max_candidates", 1000),
250
+ "unclip_ratio": config.get("unclip_ratio", 1.6),
251
+ "use_dilation": config.get("use_dilation", True),
252
+ "score_mode": config.get("score_mode", "fast"),
253
+ }
254
+ # print(config)
255
+ self.postprocess_op = DBPostProcess(**post_process)
256
+
257
+ self.infer = OrtInferSession(config)
258
+
259
+ def __call__(self, img: np.ndarray) -> Tuple[Optional[np.ndarray], float]:
260
+ start_time = time.perf_counter()
261
+
262
+ if img is None:
263
+ raise ValueError("img is None")
264
+
265
+ ori_img_shape = img.shape[0], img.shape[1]
266
+ self.preprocess_op = self.get_preprocess(max(img.shape[0], img.shape[1]))
267
+ prepro_img = self.preprocess_op(img)
268
+ if prepro_img is None:
269
+ return None, 0
270
+
271
+ preds = self.infer(prepro_img)[0]
272
+ dt_boxes, dt_boxes_scores = self.postprocess_op(preds, ori_img_shape)
273
+ dt_boxes = self.filter_tag_det_res(dt_boxes, ori_img_shape)
274
+ elapse = time.perf_counter() - start_time
275
+ return dt_boxes, elapse
276
+
277
+ def get_preprocess(self, max_wh):
278
+ if self.limit_type == "min":
279
+ limit_side_len = self.limit_side_len
280
+ elif max_wh < 960:
281
+ limit_side_len = 960
282
+ elif max_wh < 1500:
283
+ limit_side_len = 1500
284
+ else:
285
+ limit_side_len = 2000
286
+ return DetPreProcess(limit_side_len, self.limit_type, self.mean, self.std)
287
+
288
+ def filter_tag_det_res(
289
+ self, dt_boxes: np.ndarray, image_shape: Tuple[int, int]
290
+ ) -> np.ndarray:
291
+ img_height, img_width = image_shape
292
+ dt_boxes_new = []
293
+ for box in dt_boxes:
294
+ box = self.order_points_clockwise(box)
295
+ box = self.clip_det_res(box, img_height, img_width)
296
+
297
+ rect_width = int(np.linalg.norm(box[0] - box[1]))
298
+ rect_height = int(np.linalg.norm(box[0] - box[3]))
299
+ if rect_width <= 3 or rect_height <= 3:
300
+ continue
301
+
302
+ dt_boxes_new.append(box)
303
+ return np.array(dt_boxes_new)
304
+
305
+ def order_points_clockwise(self, pts: np.ndarray) -> np.ndarray:
306
+ """
307
+ reference from:
308
+ https://github.com/jrosebr1/imutils/blob/master/imutils/perspective.py
309
+ sort the points based on their x-coordinates
310
+ """
311
+ xSorted = pts[np.argsort(pts[:, 0]), :]
312
+
313
+ # grab the left-most and right-most points from the sorted
314
+ # x-roodinate points
315
+ leftMost = xSorted[:2, :]
316
+ rightMost = xSorted[2:, :]
317
+
318
+ # now, sort the left-most coordinates according to their
319
+ # y-coordinates so we can grab the top-left and bottom-left
320
+ # points, respectively
321
+ leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
322
+ (tl, bl) = leftMost
323
+
324
+ rightMost = rightMost[np.argsort(rightMost[:, 1]), :]
325
+ (tr, br) = rightMost
326
+
327
+ rect = np.array([tl, tr, br, bl], dtype="float32")
328
+ return rect
329
+
330
+ def clip_det_res(
331
+ self, points: np.ndarray, img_height: int, img_width: int
332
+ ) -> np.ndarray:
333
+ for pno in range(points.shape[0]):
334
+ points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1))
335
+ points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1))
336
+ return points
anyocr/text_rec.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Optional,Dict, List, Tuple, Union
2
+ import math
3
+ import time
4
+ import cv2
5
+ import numpy as np
6
+ from pathlib import Path
7
+ from anyocr.infer_engine import OrtInferSession
8
+
9
+
10
+ class CTCLabelDecode:
11
+ def __init__(
12
+ self,
13
+ character: Optional[List[str]] = None,
14
+ character_path: Union[str, Path, None] = None,
15
+ ):
16
+ self.character = self.get_character(character, character_path)
17
+ self.dict = {char: i for i, char in enumerate(self.character)}
18
+
19
+ def __call__(
20
+ self, preds: np.ndarray, return_word_box: bool = False, **kwargs
21
+ ) -> List[Tuple[str, float]]:
22
+ preds_idx = preds.argmax(axis=2)
23
+ preds_prob = preds.max(axis=2)
24
+ text = self.decode(
25
+ preds_idx, preds_prob, return_word_box, is_remove_duplicate=True
26
+ )
27
+ if return_word_box:
28
+ for rec_idx, rec in enumerate(text):
29
+ wh_ratio = kwargs["wh_ratio_list"][rec_idx]
30
+ max_wh_ratio = kwargs["max_wh_ratio"]
31
+ rec[2][0] = rec[2][0] * (wh_ratio / max_wh_ratio)
32
+ return text
33
+
34
+ def get_character(
35
+ self,
36
+ character: Optional[List[str]] = None,
37
+ character_path: Union[str, Path, None] = None,
38
+ ) -> List[str]:
39
+ if character is None and character_path is None:
40
+ raise ValueError("character must not be None")
41
+
42
+ character_list = None
43
+ if character:
44
+ character_list = character
45
+
46
+ if character_path:
47
+ character_list = self.read_character_file(character_path)
48
+
49
+ if character_list is None:
50
+ raise ValueError("character must not be None")
51
+
52
+ character_list = self.insert_special_char(
53
+ character_list, " ", len(character_list)
54
+ )
55
+ character_list = self.insert_special_char(character_list, "blank", 0)
56
+ return character_list
57
+
58
+ @staticmethod
59
+ def read_character_file(character_path: Union[str, Path]) -> List[str]:
60
+ character_list = []
61
+ with open(character_path, "rb") as f:
62
+ lines = f.readlines()
63
+ for line in lines:
64
+ line = line.decode("utf-8").strip("\n").strip("\r\n")
65
+ character_list.append(line)
66
+ return character_list
67
+
68
+ @staticmethod
69
+ def insert_special_char(
70
+ character_list: List[str], special_char: str, loc: int = -1
71
+ ) -> List[str]:
72
+ character_list.insert(loc, special_char)
73
+ return character_list
74
+
75
+ def decode(
76
+ self,
77
+ text_index: np.ndarray,
78
+ text_prob: Optional[np.ndarray] = None,
79
+ return_word_box: bool = False,
80
+ is_remove_duplicate: bool = False,
81
+ ) -> List[Tuple[str, float]]:
82
+ """convert text-index into text-label."""
83
+ result_list = []
84
+ ignored_tokens = self.get_ignored_tokens()
85
+ batch_size = len(text_index)
86
+ for batch_idx in range(batch_size):
87
+ selection = np.ones(len(text_index[batch_idx]), dtype=bool)
88
+ if is_remove_duplicate:
89
+ selection[1:] = text_index[batch_idx][1:] != text_index[batch_idx][:-1]
90
+
91
+ for ignored_token in ignored_tokens:
92
+ selection &= text_index[batch_idx] != ignored_token
93
+
94
+ if text_prob is not None:
95
+ conf_list = np.array(text_prob[batch_idx][selection]).tolist()
96
+ else:
97
+ conf_list = [1] * len(selection)
98
+
99
+ if len(conf_list) == 0:
100
+ conf_list = [0]
101
+
102
+ char_list = [
103
+ self.character[text_id] for text_id in text_index[batch_idx][selection]
104
+ ]
105
+ text = "".join(char_list)
106
+ if return_word_box:
107
+ word_list, word_col_list, state_list = self.get_word_info(
108
+ text, selection
109
+ )
110
+ result_list.append(
111
+ (
112
+ text,
113
+ np.mean(conf_list).tolist(),
114
+ [
115
+ len(text_index[batch_idx]),
116
+ word_list,
117
+ word_col_list,
118
+ state_list,
119
+ conf_list,
120
+ ],
121
+ )
122
+ )
123
+ else:
124
+ result_list.append((text, np.mean(conf_list).tolist()))
125
+ return result_list
126
+
127
+ @staticmethod
128
+ def get_word_info(
129
+ text: str, selection: np.ndarray
130
+ ) -> Tuple[List[List[str]], List[List[int]], List[str]]:
131
+ """
132
+ Group the decoded characters and record the corresponding decoded positions.
133
+ from https://github.com/PaddlePaddle/PaddleOCR/blob/fbba2178d7093f1dffca65a5b963ec277f1a6125/ppocr/postprocess/rec_postprocess.py#L70
134
+
135
+ Args:
136
+ text: the decoded text
137
+ selection: the bool array that identifies which columns of features are decoded as non-separated characters
138
+ Returns:
139
+ word_list: list of the grouped words
140
+ word_col_list: list of decoding positions corresponding to each character in the grouped word
141
+ state_list: list of marker to identify the type of grouping words, including two types of grouping words:
142
+ - 'cn': continous chinese characters (e.g., 你好啊)
143
+ - 'en&num': continous english characters (e.g., hello), number (e.g., 123, 1.123), or mixed of them connected by '-' (e.g., VGG-16)
144
+ """
145
+ state = None
146
+ word_content = []
147
+ word_col_content = []
148
+ word_list = []
149
+ word_col_list = []
150
+ state_list = []
151
+ valid_col = np.where(selection)[0]
152
+ col_width = np.zeros(valid_col.shape)
153
+ if len(valid_col) > 0:
154
+ col_width[1:] = valid_col[1:] - valid_col[:-1]
155
+ col_width[0] = min(
156
+ 3 if "\u4e00" <= text[0] <= "\u9fff" else 2, int(valid_col[0])
157
+ )
158
+
159
+ for c_i, char in enumerate(text):
160
+ if "\u4e00" <= char <= "\u9fff":
161
+ c_state = "cn"
162
+ else:
163
+ c_state = "en&num"
164
+
165
+ if state is None:
166
+ state = c_state
167
+
168
+ if state != c_state or col_width[c_i] > 4:
169
+ if len(word_content) != 0:
170
+ word_list.append(word_content)
171
+ word_col_list.append(word_col_content)
172
+ state_list.append(state)
173
+ word_content = []
174
+ word_col_content = []
175
+ state = c_state
176
+
177
+ word_content.append(char)
178
+ word_col_content.append(int(valid_col[c_i]))
179
+
180
+ if len(word_content) != 0:
181
+ word_list.append(word_content)
182
+ word_col_list.append(word_col_content)
183
+ state_list.append(state)
184
+
185
+ return word_list, word_col_list, state_list
186
+
187
+ @staticmethod
188
+ def get_ignored_tokens() -> List[int]:
189
+ return [0] # for ctc blank
190
+
191
+
192
+ class TextRecognizer:
193
+ def __init__(self, config: Dict[str, Any]):
194
+ self.session = OrtInferSession(config)
195
+
196
+ character = None
197
+ if self.session.have_key():
198
+ character = self.session.get_character_list()
199
+
200
+ character_path = config.get("rec_keys_path", None)
201
+ self.postprocess_op = CTCLabelDecode(
202
+ character=character, character_path=character_path
203
+ )
204
+
205
+ self.rec_batch_num = config["rec_batch_num"]
206
+ self.rec_image_shape = config["rec_img_shape"]
207
+
208
+ def __call__(
209
+ self,
210
+ img_list: Union[np.ndarray, List[np.ndarray]],
211
+ return_word_box: bool = False,
212
+ ) -> Tuple[List[Tuple[str, float]], float]:
213
+ if isinstance(img_list, np.ndarray):
214
+ img_list = [img_list]
215
+
216
+ # Calculate the aspect ratio of all text bars
217
+ width_list = [img.shape[1] / float(img.shape[0]) for img in img_list]
218
+
219
+ # Sorting can speed up the recognition process
220
+ indices = np.argsort(np.array(width_list))
221
+
222
+ img_num = len(img_list)
223
+ rec_res = [("", 0.0)] * img_num
224
+
225
+ batch_num = self.rec_batch_num
226
+ elapse = 0
227
+ for beg_img_no in range(0, img_num, batch_num):
228
+ end_img_no = min(img_num, beg_img_no + batch_num)
229
+
230
+ # Parameter Alignment for PaddleOCR
231
+ imgC, imgH, imgW = self.rec_image_shape[:3]
232
+ max_wh_ratio = imgW / imgH
233
+ wh_ratio_list = []
234
+ for ino in range(beg_img_no, end_img_no):
235
+ h, w = img_list[indices[ino]].shape[0:2]
236
+ wh_ratio = w * 1.0 / h
237
+ max_wh_ratio = max(max_wh_ratio, wh_ratio)
238
+ wh_ratio_list.append(wh_ratio)
239
+
240
+ norm_img_batch = []
241
+ for ino in range(beg_img_no, end_img_no):
242
+ norm_img = self.resize_norm_img(img_list[indices[ino]], max_wh_ratio)
243
+ norm_img_batch.append(norm_img[np.newaxis, :])
244
+ norm_img_batch = np.concatenate(norm_img_batch).astype(np.float32)
245
+
246
+ starttime = time.time()
247
+ preds = self.session(norm_img_batch)[0]
248
+ rec_result = self.postprocess_op(
249
+ preds,
250
+ return_word_box,
251
+ wh_ratio_list=wh_ratio_list,
252
+ max_wh_ratio=max_wh_ratio,
253
+ )
254
+
255
+ for rno, one_res in enumerate(rec_result):
256
+ rec_res[indices[beg_img_no + rno]] = one_res
257
+ elapse += time.time() - starttime
258
+ return rec_res, elapse
259
+
260
+ def resize_norm_img(self, img: np.ndarray, max_wh_ratio: float) -> np.ndarray:
261
+ img_channel, img_height, img_width = self.rec_image_shape
262
+ assert img_channel == img.shape[2]
263
+
264
+ img_width = int(img_height * max_wh_ratio)
265
+
266
+ h, w = img.shape[:2]
267
+ ratio = w / float(h)
268
+ if math.ceil(img_height * ratio) > img_width:
269
+ resized_w = img_width
270
+ else:
271
+ resized_w = int(math.ceil(img_height * ratio))
272
+
273
+ resized_image = cv2.resize(img, (resized_w, img_height))
274
+ resized_image = resized_image.astype("float32")
275
+ resized_image = resized_image.transpose((2, 0, 1)) / 255
276
+ resized_image -= 0.5
277
+ resized_image /= 0.5
278
+
279
+ padding_im = np.zeros((img_channel, img_height, img_width), dtype=np.float32)
280
+ padding_im[:, :, 0:resized_w] = resized_image
281
+ return padding_im
anyocr/utils.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Tuple,Dict, List, Optional, Union
2
+ import cv2
3
+ import numpy as np
4
+ from pathlib import Path
5
+ from io import BytesIO
6
+ from PIL import Image, UnidentifiedImageError
7
+
8
+
9
+
10
+ def reduce_max_side(
11
+ img: np.ndarray, max_side_len: int = 2000
12
+ ) -> Tuple[np.ndarray, float, float]:
13
+ h, w = img.shape[:2]
14
+
15
+ ratio = 1.0
16
+ if max(h, w) > max_side_len:
17
+ if h > w:
18
+ ratio = float(max_side_len) / h
19
+ else:
20
+ ratio = float(max_side_len) / w
21
+
22
+ resize_h = int(h * ratio)
23
+ resize_w = int(w * ratio)
24
+
25
+ resize_h = int(round(resize_h / 32) * 32)
26
+ resize_w = int(round(resize_w / 32) * 32)
27
+
28
+ try:
29
+ if int(resize_w) <= 0 or int(resize_h) <= 0:
30
+ raise ResizeImgError("resize_w or resize_h is less than or equal to 0")
31
+ img = cv2.resize(img, (resize_w, resize_h))
32
+ except Exception as exc:
33
+ raise ResizeImgError() from exc
34
+
35
+ ratio_h = h / resize_h
36
+ ratio_w = w / resize_w
37
+ return img, ratio_h, ratio_w
38
+
39
+
40
+ def increase_min_side(
41
+ img: np.ndarray, min_side_len: int = 30
42
+ ) -> Tuple[np.ndarray, float, float]:
43
+ h, w = img.shape[:2]
44
+
45
+ ratio = 1.0
46
+ if min(h, w) < min_side_len:
47
+ if h < w:
48
+ ratio = float(min_side_len) / h
49
+ else:
50
+ ratio = float(min_side_len) / w
51
+
52
+ resize_h = int(h * ratio)
53
+ resize_w = int(w * ratio)
54
+
55
+ resize_h = int(round(resize_h / 32) * 32)
56
+ resize_w = int(round(resize_w / 32) * 32)
57
+
58
+ try:
59
+ if int(resize_w) <= 0 or int(resize_h) <= 0:
60
+ raise ResizeImgError("resize_w or resize_h is less than or equal to 0")
61
+ img = cv2.resize(img, (resize_w, resize_h))
62
+ except Exception as exc:
63
+ raise ResizeImgError() from exc
64
+
65
+ ratio_h = h / resize_h
66
+ ratio_w = w / resize_w
67
+ return img, ratio_h, ratio_w
68
+
69
+
70
+ def add_round_letterbox(
71
+ img: np.ndarray,
72
+ padding_tuple: Tuple[int, int, int, int],
73
+ ) -> np.ndarray:
74
+ padded_img = cv2.copyMakeBorder(
75
+ img,
76
+ padding_tuple[0],
77
+ padding_tuple[1],
78
+ padding_tuple[2],
79
+ padding_tuple[3],
80
+ cv2.BORDER_CONSTANT,
81
+ value=(0, 0, 0),
82
+ )
83
+ return padded_img
84
+
85
+
86
+ class ResizeImgError(Exception):
87
+ pass
88
+
89
+
90
+ InputType = Union[str, np.ndarray, bytes, Path, Image.Image]
91
+ class LoadImage:
92
+ def __init__(self):
93
+ pass
94
+
95
+ def __call__(self, img: InputType) -> np.ndarray:
96
+ if not isinstance(img, InputType.__args__):
97
+ raise LoadImageError(
98
+ f"The img type {type(img)} does not in {InputType.__args__}"
99
+ )
100
+
101
+ origin_img_type = type(img)
102
+ img = self.load_img(img)
103
+ img = self.convert_img(img, origin_img_type)
104
+ return img
105
+
106
+ def load_img(self, img: InputType) -> np.ndarray:
107
+ if isinstance(img, (str, Path)):
108
+ self.verify_exist(img)
109
+ try:
110
+ img = self.img_to_ndarray(Image.open(img))
111
+ except UnidentifiedImageError as e:
112
+ raise LoadImageError(f"cannot identify image file {img}") from e
113
+ return img
114
+
115
+ if isinstance(img, bytes):
116
+ img = self.img_to_ndarray(Image.open(BytesIO(img)))
117
+ return img
118
+
119
+ if isinstance(img, np.ndarray):
120
+ return img
121
+
122
+ if isinstance(img, Image.Image):
123
+ return self.img_to_ndarray(img)
124
+
125
+ raise LoadImageError(f"{type(img)} is not supported!")
126
+
127
+ def img_to_ndarray(self, img: Image.Image) -> np.ndarray:
128
+ if img.mode == "1":
129
+ img = img.convert("L")
130
+ return np.array(img)
131
+ return np.array(img)
132
+
133
+ def convert_img(self, img: np.ndarray, origin_img_type: Any) -> np.ndarray:
134
+ if img.ndim == 2:
135
+ return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
136
+
137
+ if img.ndim == 3:
138
+ channel = img.shape[2]
139
+ if channel == 1:
140
+ return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
141
+
142
+ if channel == 2:
143
+ return self.cvt_two_to_three(img)
144
+
145
+ if channel == 3:
146
+ if issubclass(origin_img_type, (str, Path, bytes, Image.Image)):
147
+ return cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
148
+ return img
149
+
150
+ if channel == 4:
151
+ return self.cvt_four_to_three(img)
152
+
153
+ raise LoadImageError(
154
+ f"The channel({channel}) of the img is not in [1, 2, 3, 4]"
155
+ )
156
+
157
+ raise LoadImageError(f"The ndim({img.ndim}) of the img is not in [2, 3]")
158
+
159
+ @staticmethod
160
+ def cvt_two_to_three(img: np.ndarray) -> np.ndarray:
161
+ """gray + alpha → BGR"""
162
+ img_gray = img[..., 0]
163
+ img_bgr = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2BGR)
164
+
165
+ img_alpha = img[..., 1]
166
+ not_a = cv2.bitwise_not(img_alpha)
167
+ not_a = cv2.cvtColor(not_a, cv2.COLOR_GRAY2BGR)
168
+
169
+ new_img = cv2.bitwise_and(img_bgr, img_bgr, mask=img_alpha)
170
+ new_img = cv2.add(new_img, not_a)
171
+ return new_img
172
+
173
+ @staticmethod
174
+ def cvt_four_to_three(img: np.ndarray) -> np.ndarray:
175
+ """RGBA → BGR"""
176
+ r, g, b, a = cv2.split(img)
177
+ new_img = cv2.merge((b, g, r))
178
+
179
+ not_a = cv2.bitwise_not(a)
180
+ not_a = cv2.cvtColor(not_a, cv2.COLOR_GRAY2BGR)
181
+
182
+ new_img = cv2.bitwise_and(new_img, new_img, mask=a)
183
+
184
+ mean_color = np.mean(new_img)
185
+ if mean_color <= 0.0:
186
+ new_img = cv2.add(new_img, not_a)
187
+ else:
188
+ new_img = cv2.bitwise_not(new_img)
189
+ return new_img
190
+
191
+ @staticmethod
192
+ def verify_exist(file_path: Union[str, Path]):
193
+ if not Path(file_path).exists():
194
+ raise LoadImageError(f"{file_path} does not exist.")
195
+
196
+
197
+ class LoadImageError(Exception):
198
+ pass
anyocr/version.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __version__ = "4.0.0"
2
+ __author__ = "AnyOCR"