asterocr 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- asterocr/__init__.py +12 -0
- asterocr/cli.py +35 -0
- asterocr/comparator.py +412 -0
- asterocr/dataset/export_real_zip.py +117 -0
- asterocr/dataset/loader.py +371 -0
- asterocr/dataset/unified_packer.py +402 -0
- asterocr/download_dataset.py +93 -0
- asterocr/inference/decoder.py +302 -0
- asterocr/inference/lexicon.py +136 -0
- asterocr/inference/predictor.py +205 -0
- asterocr/inference/viet_linguistics.py +340 -0
- asterocr/kaggle_bridge/__init__.py +8 -0
- asterocr/kaggle_bridge/chunker.py +252 -0
- asterocr/kaggle_bridge/colab_auto_runner.py +380 -0
- asterocr/kaggle_bridge/kaggle_worker.py +1911 -0
- asterocr/kaggle_bridge/launcher.py +245 -0
- asterocr/kaggle_bridge/tunnel.py +190 -0
- asterocr/models/backbone.py +102 -0
- asterocr/models/language.py +104 -0
- asterocr/models/model.py +113 -0
- asterocr/models/sequence.py +34 -0
- asterocr/pipeline.py +179 -0
- asterocr/studio.py +371 -0
- asterocr/synthesizer/corpus.py +378 -0
- asterocr/synthesizer/font_manager.py +170 -0
- asterocr/synthesizer/generator.py +205 -0
- asterocr/synthesizer/handwriting_generator.py +420 -0
- asterocr/synthesizer/mega_generator.py +527 -0
- asterocr/trainer/metrics.py +53 -0
- asterocr/trainer/trainer.py +1295 -0
- asterocr/vocab.py +175 -0
- asterocr-1.0.0.dist-info/METADATA +102 -0
- asterocr-1.0.0.dist-info/RECORD +36 -0
- asterocr-1.0.0.dist-info/WHEEL +5 -0
- asterocr-1.0.0.dist-info/entry_points.txt +2 -0
- asterocr-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AsterOCR Vietnamese Text Corpus Synthesizer
|
|
3
|
+
Generates realistic Vietnamese text for training:
|
|
4
|
+
- Full names (Họ và tên)
|
|
5
|
+
- Addresses (Số nhà, Phường/Xã, Quận/Huyện, Tỉnh/Thành)
|
|
6
|
+
- Administrative data (CCCD 12 số, CMND, Ngày sinh, Hộ khẩu)
|
|
7
|
+
- Relationships (Chủ hộ, Vợ, Con, v.v.)
|
|
8
|
+
- Numbers, money, dates, codes, license plates
|
|
9
|
+
- Administrative & common Vietnamese sentences
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import random
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
|
|
16
|
+
if sys.platform == "win32":
|
|
17
|
+
try:
|
|
18
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
19
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
20
|
+
except Exception:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
LAST_NAMES = [
|
|
24
|
+
"Nguyễn", "Trần", "Lê", "Phạm", "Hoàng", "Huỳnh", "Phan", "Vũ", "Võ",
|
|
25
|
+
"Đặng", "Bùi", "Đỗ", "Hồ", "Ngô", "Dương", "Lý", "Đào", "Đinh", "Đoàn",
|
|
26
|
+
"Mai", "Lâm", "Phùng", "Tạ", "Trịnh", "Lương", "Trương", "Hà", "Chu"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
MIDDLE_NAMES_MALE = [
|
|
30
|
+
"Văn", "Đức", "Hữu", "Minh", "Quang", "Xuân", "Thanh", "Đình", "Gia",
|
|
31
|
+
"Tuấn", "Hải", "Quốc", "Anh", "Bảo", "Hoàng", "Công", "Ngọc", "Mạnh"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
MIDDLE_NAMES_FEMALE = [
|
|
35
|
+
"Thị", "Ngọc", "Thanh", "Mai", "Phương", "Thu", "Ánh", "Hồng", "Kim",
|
|
36
|
+
"Mỹ", "Bích", "Tuyết", "Khánh", "Diệu", "Thúy", "Bảo", "Hải", "Hương"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
FIRST_NAMES_MALE = [
|
|
40
|
+
"An", "Bình", "Cường", "Dũng", "Đạt", "Hải", "Hiếu", "Hùng", "Huy",
|
|
41
|
+
"Khoa", "Kiên", "Long", "Minh", "Nam", "Nghĩa", "Phong", "Phúc", "Quân",
|
|
42
|
+
"Sơn", "Thắng", "Thành", "Thịnh", "Tiến", "Toàn", "Trung", "Tùng", "Việt"
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
FIRST_NAMES_FEMALE = [
|
|
46
|
+
"Anh", "Châu", "Dung", "Duyên", "Giang", "Hà", "Hằng", "Hạnh", "Hoa",
|
|
47
|
+
"Huệ", "Hương", "Huyền", "Lan", "Linh", "Mai", "Nga", "Ngân", "Ngọc",
|
|
48
|
+
"Nhung", "Oanh", "Phương", "Quỳnh", "Thảo", "Thu", "Trang", "Trâm", "Vy"
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
CITIES = [
|
|
52
|
+
"Hà Nội", "TP. Hồ Chí Minh", "Hải Phòng", "Đà Nẵng", "Cần Thơ",
|
|
53
|
+
"An Giang", "Bà Rịa - Vũng Tàu", "Bắc Giang", "Bắc Kạn", "Bạc Liêu",
|
|
54
|
+
"Bắc Ninh", "Bến Tre", "Bình Định", "Bình Dương", "Bình Phước",
|
|
55
|
+
"Bình Thuận", "Cà Mau", "Cao Bằng", "Đắk Lắk", "Đắk Nông", "Điện Biên",
|
|
56
|
+
"Đồng Nai", "Đồng Tháp", "Gia Lai", "Hà Giang", "Hà Nam", "Hà Tĩnh",
|
|
57
|
+
"Hải Dương", "Hậu Giang", "Hòa Bình", "Hưng Yên", "Khánh Hòa", "Kiên Giang",
|
|
58
|
+
"Kon Tum", "Lai Châu", "Lâm Đồng", "Lạng Sơn", "Lào Cai", "Long An",
|
|
59
|
+
"Nam Định", "Nghệ An", "Ninh Bình", "Ninh Thuận", "Phú Thọ", "Phú Yên",
|
|
60
|
+
"Quảng Bình", "Quảng Nam", "Quảng Ngãi", "Quảng Ninh", "Quảng Trị",
|
|
61
|
+
"Sóc Trăng", "Sơn La", "Tây Ninh", "Thái Bình", "Thái Nguyên", "Thanh Hóa",
|
|
62
|
+
"Thừa Thiên Huế", "Tiền Giang", "Trà Vinh", "Tuyên Quang", "Vĩnh Long",
|
|
63
|
+
"Vĩnh Phúc", "Yên Bái"
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
DISTRICT_PREFIX = ["Quận", "Huyện", "Thị xã", "Thành phố"]
|
|
67
|
+
WARD_PREFIX = ["Phường", "Xã", "Thị trấn"]
|
|
68
|
+
STREET_NAMES = [
|
|
69
|
+
"Nguyễn Huệ", "Lê Lợi", "Trần Hưng Đạo", "Hai Bà Trưng", "Phan Chu Trinh",
|
|
70
|
+
"Lý Thường Kiệt", "Điện Biên Phủ", "Cách Mạng Tháng Tám", "Võ Thị Sáu",
|
|
71
|
+
"Nguyễn Thị Minh Khai", "Bà Huyện Thanh Quan", "Hoàng Diệu", "Kim Mã",
|
|
72
|
+
"Đội Cấn", "Cầu Giấy", "Giải Phóng", "Xã Đàn", "Trường Chinh", "Đinh Tiên Hoàng"
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
RELATIONS = [
|
|
76
|
+
"Chủ hộ", "Vợ", "Chồng", "Con", "Con trai", "Con gái", "Con dâu", "Con rể",
|
|
77
|
+
"Bố đẻ", "Mẹ đẻ", "Bố vợ", "Mẹ vợ", "Bố chồng", "Mẹ chồng", "Ông nội",
|
|
78
|
+
"Bà nội", "Ông ngoại", "Bà ngoại", "Anh ruột", "Chị ruột", "Em ruột",
|
|
79
|
+
"Cháu nội", "Cháu ngoại", "Cháu ruột"
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
ETHNICITIES = [
|
|
83
|
+
"Kinh", "Tày", "Thái", "Mường", "H'Mông", "Dao", "Khmer", "Nùng", "Hoa",
|
|
84
|
+
"Gia Rai", "Ê Đê", "Ba Na", "Xơ Đăng", "Sán Chay", "Cơ Ho", "Chăm"
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
RELIGIONS = ["Không", "Phật giáo", "Công giáo", "Tin Lành", "Cao Đài", "Hòa Hảo"]
|
|
88
|
+
|
|
89
|
+
OCCUPATIONS = [
|
|
90
|
+
"Làm ruộng", "Công nhân", "Học sinh", "Sinh viên", "Cán bộ", "Hưu trí",
|
|
91
|
+
"Kinh doanh", "Buôn bán", "Tự do", "Nội trợ", "Giáo viên", "Bác sĩ",
|
|
92
|
+
"Kỹ sư", "Nhân viên văn phòng", "Quân nhân", "Công an", "Lái xe"
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
ADMIN_PHRASES = [
|
|
96
|
+
"CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM",
|
|
97
|
+
"Độc lập - Tự do - Hạnh phúc",
|
|
98
|
+
"SỔ HỘ KHẨU", "BẢN SAO TRÍCH LỤC", "GIẤY KHAI SINH", "GIẤY CHỨNG NHẬN",
|
|
99
|
+
"Họ và tên chủ hộ:", "Nơi đăng ký thường trú:", "Số sổ hộ khẩu:",
|
|
100
|
+
"Công an quận/huyện:", "Đăng ký ngày:", "Cán bộ hộ khẩu:",
|
|
101
|
+
"Căn cước công dân số:", "Chứng minh nhân dân số:", "Quốc tịch: Việt Nam",
|
|
102
|
+
"Nơi sinh:", "Nguyên quán:", "Nơi thường trú trước khi chuyển đến:"
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
def random_date(start_year=1940, end_year=2024):
|
|
106
|
+
start = datetime(start_year, 1, 1)
|
|
107
|
+
end = datetime(end_year, 12, 31)
|
|
108
|
+
d = start + timedelta(days=random.randint(0, (end - start).days))
|
|
109
|
+
fmt = random.choice(["%d/%m/%Y", "%d-%m-%Y", "%d.%m.%Y", "%d / %m / %Y"])
|
|
110
|
+
return d.strftime(fmt)
|
|
111
|
+
|
|
112
|
+
def generate_vietnamese_name():
|
|
113
|
+
gender = random.choice(["male", "female"])
|
|
114
|
+
last = random.choice(LAST_NAMES)
|
|
115
|
+
if gender == "male":
|
|
116
|
+
mid = random.choice(MIDDLE_NAMES_MALE)
|
|
117
|
+
first = random.choice(FIRST_NAMES_MALE)
|
|
118
|
+
else:
|
|
119
|
+
mid = random.choice(MIDDLE_NAMES_FEMALE)
|
|
120
|
+
first = random.choice(FIRST_NAMES_FEMALE)
|
|
121
|
+
|
|
122
|
+
full = f"{last} {mid} {first}"
|
|
123
|
+
case = random.choice(["upper", "title", "title", "upper"])
|
|
124
|
+
return full.upper() if case == "upper" else full
|
|
125
|
+
|
|
126
|
+
def generate_cccd():
|
|
127
|
+
# 12 digits, e.g. 079082001425
|
|
128
|
+
province = f"{random.randint(1, 96):03d}"
|
|
129
|
+
century_gender = str(random.randint(0, 3))
|
|
130
|
+
year = f"{random.randint(40, 99):02d}"
|
|
131
|
+
random_num = f"{random.randint(1, 999999):06d}"
|
|
132
|
+
return f"{province}{century_gender}{year}{random_num}"
|
|
133
|
+
|
|
134
|
+
def generate_address():
|
|
135
|
+
# Multi-tier realistic Vietnamese addresses (from 35 to 95 chars)
|
|
136
|
+
types = random.random()
|
|
137
|
+
if types < 0.25:
|
|
138
|
+
# Urban address with alley/lane/floor
|
|
139
|
+
floor_apt = random.choice([f"P.{random.randint(101, 1908)}", f"Căn hộ {random.randint(201, 2505)}", f"Tầng {random.randint(2, 25)}"])
|
|
140
|
+
building = random.choice(["Tòa nhà Landmark 81", "Chung cư Vinhomes Central Park", "Tòa Sapphire 2", "Chung cư Golden Land", "Tòa tháp Bitexco", "Chung cư Masteri Thảo Điền"])
|
|
141
|
+
street = f"đường {random.choice(STREET_NAMES)}"
|
|
142
|
+
ward = f"{random.choice(WARD_PREFIX)} {random.choice(FIRST_NAMES_MALE if random.random() < 0.5 else FIRST_NAMES_FEMALE)}"
|
|
143
|
+
district = f"{random.choice(DISTRICT_PREFIX)} {random.choice(['1', '3', '5', '7', '10', 'Bình Thạnh', 'Tân Bình', 'Cầu Giấy', 'Đống Đa', 'Hải Châu'])}"
|
|
144
|
+
city = random.choice(CITIES)
|
|
145
|
+
return f"{floor_apt}, {building}, {street}, {ward}, {district}, {city}"
|
|
146
|
+
elif types < 0.65:
|
|
147
|
+
# Traditional house / alley address
|
|
148
|
+
num = f"Số {random.randint(1, 980)}"
|
|
149
|
+
if random.random() < 0.6:
|
|
150
|
+
num += f"/{random.randint(1, 120)}"
|
|
151
|
+
if random.random() < 0.3:
|
|
152
|
+
num += f"/{random.randint(1, 45)}"
|
|
153
|
+
lane = random.choice(["", f"Ngõ {random.randint(1, 350)}, ", f"Hẻm {random.randint(1, 200)}, "]) if random.random() < 0.5 else ""
|
|
154
|
+
street = f"đường {random.choice(STREET_NAMES)}"
|
|
155
|
+
ward = f"{random.choice(WARD_PREFIX)} {random.choice(FIRST_NAMES_MALE if random.random() < 0.5 else FIRST_NAMES_FEMALE)}"
|
|
156
|
+
district = f"{random.choice(DISTRICT_PREFIX)} {random.choice(['1', '3', '7', 'Bình Thạnh', 'Phú Nhuận', 'Gò Vấp', 'Hoàn Kiếm', 'Ba Đình', 'Thanh Xuân', 'Sơn Trà'])}"
|
|
157
|
+
city = random.choice(CITIES)
|
|
158
|
+
return f"{num}, {lane}{street}, {ward}, {district}, {city}"
|
|
159
|
+
else:
|
|
160
|
+
# Rural / provincial address
|
|
161
|
+
hamlet = random.choice(["Thôn 1", "Thôn 2", "Thôn Tân Lập", "Thôn Phú Thịnh", "Ấp Bình Hòa", "Ấp 3", "Ấp Tân Thành", "Bản Nà Lạc", "Xóm Chùa", "Tổ dân phố 8"])
|
|
162
|
+
commune = f"Xã {random.choice(['An Hòa', 'Phú Lộc', 'Quảng Tân', 'Hòa Tiến', 'Tân Lập', 'Bình Minh', 'Đại Đồng', 'Cẩm Giàng'])}"
|
|
163
|
+
dist = f"Huyện {random.choice(['Tuy An', 'Châu Đức', 'Củ Chi', 'Đan Phượng', 'Gia Viễn', 'Hưng Hà', 'Nho Quan', 'Phú Lộc'])}"
|
|
164
|
+
province = f"Tỉnh {random.choice(['Phú Yên', 'Bà Rịa - Vũng Tàu', 'Bình Định', 'Quảng Nam', 'Ninh Bình', 'Thái Bình', 'Thanh Hóa', 'Nghệ An'])}"
|
|
165
|
+
return f"{hamlet}, {commune}, {dist}, {province}"
|
|
166
|
+
|
|
167
|
+
DOCUMENT_CLAUSES = [
|
|
168
|
+
"CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM - Độc lập - Tự do - Hạnh phúc",
|
|
169
|
+
"QUYẾT ĐỊNH Về việc ban hành quy chế bảo vệ và xử lý dữ liệu cá nhân nội bộ",
|
|
170
|
+
"Căn cứ Luật Doanh nghiệp số 59/2020/QH14 được Quốc hội thông qua ngày 17/06/2020",
|
|
171
|
+
"Căn cứ Nghị định số 13/2023/NĐ-CP ngày 17 tháng 04 năm 2023 của Chính phủ về bảo vệ dữ liệu cá nhân",
|
|
172
|
+
"Điều 1. Nay bổ nhiệm Ông/Bà có tên sau đây giữ chức vụ Trưởng phòng Kế hoạch Tài chính",
|
|
173
|
+
"Điều 2. Quyết định này có hiệu lực thi hành kể từ ngày ký và được phổ biến tới toàn thể cán bộ",
|
|
174
|
+
"Bên A cam kết thanh toán đầy đủ 100% giá trị hợp đồng theo đúng thời hạn quy định tại Điều 4",
|
|
175
|
+
"Tổng giá trị quyết toán công trình (đã bao gồm thuế giá trị gia tăng 10% VAT):",
|
|
176
|
+
"Số tiền viết bằng chữ: Một trăm hai mươi lăm triệu tám trăm năm mươi nghìn đồng chẵn.",
|
|
177
|
+
"Giấy chứng nhận đăng ký kinh doanh và mã số doanh nghiệp do Sở Kế hoạch & Đầu tư cấp lần đầu ngày",
|
|
178
|
+
"Người đại diện theo pháp luật: Chủ tịch Hội đồng Quản trị kiêm Tổng Giám đốc Điều hành",
|
|
179
|
+
"Địa chỉ trụ sở chính: Tòa nhà Discovery Complex, số 302 đường Cầu Giấy, Phường Dịch Vọng, Quận Cầu Giấy, TP. Hà Nội",
|
|
180
|
+
"Biên bản nghiệm thu và bàn giao hạng mục công trình hoàn thành đưa vào sử dụng",
|
|
181
|
+
"Hai bên thống nhất ký biên bản thanh lý hợp đồng kinh tế và không còn khiếu nại hay tranh chấp nào",
|
|
182
|
+
"Kính gửi: Ủy ban Nhân dân Phường Bến Nghé, Quận 1, Thành phố Hồ Chí Minh",
|
|
183
|
+
"Nơi nhận: Như Điều 3; Ban Giám đốc; Phòng Kế toán; Lưu: VT, HS (03 bản chính lưu trữ)."
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
def generate_document_clause():
|
|
187
|
+
return random.choice(DOCUMENT_CLAUSES)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Comprehensive character sets
|
|
191
|
+
VIETNAMESE_ALL_CHARS = (
|
|
192
|
+
"aàáảãạăằắẳẵặâầấẩẫậ"
|
|
193
|
+
"bcdđeèéẻẽẹêềếểễệ"
|
|
194
|
+
"fghiìíỉĩịjklmnoòóỏõọôồốổỗộơờớởỡợ"
|
|
195
|
+
"pqrstuùúủũụưừứửữự"
|
|
196
|
+
"vwxyỳýỷỹỵz"
|
|
197
|
+
"AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬ"
|
|
198
|
+
"BCDĐEÈÉẺẼẸÊỀẾỂỄỆ"
|
|
199
|
+
"FGHIÌÍỈĨỊJKLMNOÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢ"
|
|
200
|
+
"PQRSTUÙÚỦŨỤƯỪỨỬỮỰ"
|
|
201
|
+
"VWXYỲÝỶỸỴZ"
|
|
202
|
+
)
|
|
203
|
+
DIGITS_CHARS = "0123456789"
|
|
204
|
+
PUNCT_CHARS = " ,.-:;\"'?!()[]{}/%+$&=*#@_\\<>°"
|
|
205
|
+
FULL_CHARSET = list(dict.fromkeys(list(VIETNAMESE_ALL_CHARS + DIGITS_CHARS + PUNCT_CHARS)))
|
|
206
|
+
|
|
207
|
+
INITIAL_CONSONANTS = ["b", "c", "ch", "d", "đ", "g", "gh", "h", "k", "kh", "l", "m", "n", "ng", "ngh", "nh", "p", "ph", "qu", "r", "s", "t", "th", "tr", "v", "x", ""]
|
|
208
|
+
VOWEL_NUCLEI = [
|
|
209
|
+
"a", "à", "á", "ả", "ã", "ạ", "ă", "ằ", "ắ", "ẳ", "ẵ", "ặ", "â", "ầ", "ấ", "ẩ", "ẫ", "ậ",
|
|
210
|
+
"e", "è", "é", "ẻ", "ẽ", "ẹ", "ê", "ề", "ế", "ể", "ễ", "ệ",
|
|
211
|
+
"i", "ì", "í", "ỉ", "ĩ", "ị", "o", "ò", "ó", "ỏ", "õ", "ọ", "ô", "ồ", "ố", "ổ", "ỗ", "ộ",
|
|
212
|
+
"ơ", "ờ", "ớ", "ở", "ỡ", "ợ", "u", "ù", "ú", "ủ", "ũ", "ụ", "ư", "ừ", "ứ", "ử", "ữ", "ự",
|
|
213
|
+
"y", "ỳ", "ý", "ỷ", "ỹ", "ỵ", "ia", "iê", "uô", "ươ", "uy", "oa", "oe"
|
|
214
|
+
]
|
|
215
|
+
FINAL_CONSONANTS = ["", "c", "ch", "m", "n", "ng", "nh", "p", "t"]
|
|
216
|
+
|
|
217
|
+
def generate_pure_random_string(min_len=2, max_len=22):
|
|
218
|
+
"""Generates purely random sequence from all 134+ characters."""
|
|
219
|
+
length = random.randint(min_len, max_len)
|
|
220
|
+
chars = []
|
|
221
|
+
for _ in range(length):
|
|
222
|
+
chars.append(random.choice(FULL_CHARSET))
|
|
223
|
+
res = "".join(chars).strip()
|
|
224
|
+
return res if res else "A"
|
|
225
|
+
|
|
226
|
+
def generate_pseudo_words(min_words=1, max_words=5):
|
|
227
|
+
"""Generates realistic Vietnamese pseudo-words using phonotactics."""
|
|
228
|
+
num_words = random.randint(min_words, max_words)
|
|
229
|
+
words = []
|
|
230
|
+
for _ in range(num_words):
|
|
231
|
+
c1 = random.choice(INITIAL_CONSONANTS)
|
|
232
|
+
v = random.choice(VOWEL_NUCLEI)
|
|
233
|
+
c2 = random.choice(FINAL_CONSONANTS)
|
|
234
|
+
w = c1 + v + c2
|
|
235
|
+
if random.random() < 0.3:
|
|
236
|
+
w = w.capitalize()
|
|
237
|
+
elif random.random() < 0.15:
|
|
238
|
+
w = w.upper()
|
|
239
|
+
words.append(w)
|
|
240
|
+
return " ".join(words)
|
|
241
|
+
|
|
242
|
+
def generate_code_or_token():
|
|
243
|
+
"""Generates identifiers, serials, codes, phone numbers, license plates."""
|
|
244
|
+
t = random.choice([
|
|
245
|
+
"serial", "code", "phone", "plate", "tax", "ref", "hash", "date_code"
|
|
246
|
+
])
|
|
247
|
+
if t == "serial":
|
|
248
|
+
prefix = random.choice(["SN", "SERI", "NO", "MÃ", "ID", "LOT"])
|
|
249
|
+
nums = "".join(random.choices("0123456789ABCDEF", k=random.randint(6, 12)))
|
|
250
|
+
return f"{prefix}: {nums}"
|
|
251
|
+
elif t == "code":
|
|
252
|
+
p1 = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=random.randint(2, 4)))
|
|
253
|
+
p2 = "".join(random.choices("0123456789", k=random.randint(3, 6)))
|
|
254
|
+
sep = random.choice(["-", "/", "_", "."])
|
|
255
|
+
return f"{p1}{sep}{p2}"
|
|
256
|
+
elif t == "phone":
|
|
257
|
+
prefix = random.choice(["090", "091", "098", "097", "086", "083", "079", "034"])
|
|
258
|
+
body1 = "".join(random.choices("0123456789", k=3))
|
|
259
|
+
body2 = "".join(random.choices("0123456789", k=4))
|
|
260
|
+
fmt = random.choice([
|
|
261
|
+
f"{prefix}.{body1}.{body2}",
|
|
262
|
+
f"{prefix} {body1} {body2}",
|
|
263
|
+
f"+84 {prefix[1:]} {body1} {body2}",
|
|
264
|
+
f"({prefix}) {body1}-{body2}"
|
|
265
|
+
])
|
|
266
|
+
return fmt
|
|
267
|
+
elif t == "plate":
|
|
268
|
+
prov = random.randint(11, 99)
|
|
269
|
+
letter = random.choice("ABCDEFGHKLMNPSTUVXYZ")
|
|
270
|
+
num1 = random.randint(100, 999)
|
|
271
|
+
num2 = random.randint(10, 99)
|
|
272
|
+
return f"{prov}{letter}-{num1}.{num2}"
|
|
273
|
+
elif t == "tax":
|
|
274
|
+
mst = "".join(random.choices("0123456789", k=10))
|
|
275
|
+
if random.random() < 0.3:
|
|
276
|
+
mst += f"-{random.randint(1, 99):03d}"
|
|
277
|
+
return f"MST: {mst}"
|
|
278
|
+
elif t == "ref":
|
|
279
|
+
kind = random.choice(["Số", "QĐ", "NĐ", "TT", "CV", "HĐ"])
|
|
280
|
+
num = random.randint(1, 9999)
|
|
281
|
+
yr = random.randint(2015, 2026)
|
|
282
|
+
dept = random.choice(["UBND", "BCA", "BTC", "BYT", "BGDĐT", "TCT", "CP"])
|
|
283
|
+
return f"{kind}: {num}/{yr}/{dept}"
|
|
284
|
+
else:
|
|
285
|
+
# short hash / token
|
|
286
|
+
token = "".join(random.choices("0123456789abcdefABCDEF", k=random.randint(8, 16)))
|
|
287
|
+
return f"token_{token}" if random.random() < 0.5 else token
|
|
288
|
+
|
|
289
|
+
def generate_math_or_currency():
|
|
290
|
+
"""Generates currencies, percentages, math formulas, units."""
|
|
291
|
+
t = random.choice(["money", "percent", "math", "units"])
|
|
292
|
+
if t == "money":
|
|
293
|
+
val = random.choice([
|
|
294
|
+
random.randint(1, 999) * 1000,
|
|
295
|
+
random.randint(1, 99) * 1000000,
|
|
296
|
+
random.randint(1000, 99999999)
|
|
297
|
+
])
|
|
298
|
+
fmt_val = f"{val:,}".replace(",", ".")
|
|
299
|
+
unit = random.choice(["VNĐ", "đ", "VND", "$", "USD", "EUR"])
|
|
300
|
+
if unit == "$":
|
|
301
|
+
return f"${val:,.2f}"
|
|
302
|
+
return f"{fmt_val} {unit}"
|
|
303
|
+
elif t == "percent":
|
|
304
|
+
val = round(random.uniform(0.1, 99.9), random.choice([0, 1, 2]))
|
|
305
|
+
sign = random.choice(["", "+", "-", "±"])
|
|
306
|
+
return f"{sign}{val}%"
|
|
307
|
+
elif t == "units":
|
|
308
|
+
val = round(random.uniform(1.0, 500.0), random.choice([0, 1, 2]))
|
|
309
|
+
u = random.choice(["kg", "m²", "km", "cm", "m³", "lít", "giờ", "phút", "°C", "kW/h"])
|
|
310
|
+
return f"{val} {u}"
|
|
311
|
+
else:
|
|
312
|
+
# Math expression
|
|
313
|
+
v1, v2 = random.randint(1, 50), random.randint(1, 50)
|
|
314
|
+
op = random.choice(["+", "-", "x", "/", "="])
|
|
315
|
+
return random.choice([
|
|
316
|
+
f"f(x) = {v1}x + {v2}",
|
|
317
|
+
f"[{v1} {op} {v2} = {v1 + v2}]",
|
|
318
|
+
f"(A + B) / {v1} <= {v2}",
|
|
319
|
+
f"X_{v1} = {v2 * 1.5:.1f}"
|
|
320
|
+
])
|
|
321
|
+
|
|
322
|
+
def generate_random_corpus_text(mode="mixed"):
|
|
323
|
+
"""
|
|
324
|
+
Generates text sample according to configurable distribution:
|
|
325
|
+
- 'mixed': Balanced mix of random chars, tokens, math, pseudo-words, and admin sentences.
|
|
326
|
+
- 'pure_random': Strictly random character sequences from full 134+ charset.
|
|
327
|
+
- 'admin': Realistic administrative text (names, addresses, CCCD, dates).
|
|
328
|
+
"""
|
|
329
|
+
if mode == "pure_random":
|
|
330
|
+
return generate_pure_random_string()
|
|
331
|
+
elif mode == "admin":
|
|
332
|
+
sub = random.random()
|
|
333
|
+
if sub < 0.35:
|
|
334
|
+
return generate_address()
|
|
335
|
+
elif sub < 0.65:
|
|
336
|
+
return generate_document_clause()
|
|
337
|
+
elif sub < 0.85:
|
|
338
|
+
return f"{generate_vietnamese_name()} - CCCD: {generate_cccd()}"
|
|
339
|
+
else:
|
|
340
|
+
return random.choice(ADMIN_PHRASES)
|
|
341
|
+
|
|
342
|
+
r = random.random()
|
|
343
|
+
if r < 0.20:
|
|
344
|
+
# 1. Văn bản tài liệu, quyết định, hợp đồng, điều khoản pháp lý dài (A4 document lines)
|
|
345
|
+
return generate_document_clause()
|
|
346
|
+
elif r < 0.35:
|
|
347
|
+
# 2. Địa chỉ đa cấp chi tiết (tòa nhà, số nhà, ngõ/ngách, xã/phường, quận/huyện, tỉnh/TP)
|
|
348
|
+
return generate_address()
|
|
349
|
+
elif r < 0.50:
|
|
350
|
+
# 3. Họ tên, CCCD, ngày tháng, chức danh
|
|
351
|
+
sub = random.random()
|
|
352
|
+
if sub < 0.4:
|
|
353
|
+
return generate_vietnamese_name()
|
|
354
|
+
elif sub < 0.7:
|
|
355
|
+
return generate_cccd()
|
|
356
|
+
else:
|
|
357
|
+
return random_date()
|
|
358
|
+
elif r < 0.65:
|
|
359
|
+
# 4. Tiền tệ, số liệu, hóa đơn, phần trăm, mã số
|
|
360
|
+
if random.random() < 0.5:
|
|
361
|
+
return generate_math_or_currency()
|
|
362
|
+
else:
|
|
363
|
+
return generate_code_or_token()
|
|
364
|
+
elif r < 0.80:
|
|
365
|
+
# 5. Pseudo-words (Bao phủ âm tiết tiếng Việt theo ngữ âm học)
|
|
366
|
+
return generate_pseudo_words(min_words=2, max_words=7)
|
|
367
|
+
else:
|
|
368
|
+
# 6. Chuỗi ký tự ngẫu nhiên & cụm từ hành chính
|
|
369
|
+
if random.random() < 0.5:
|
|
370
|
+
return generate_pure_random_string(min_len=2, max_len=random.randint(6, 26))
|
|
371
|
+
else:
|
|
372
|
+
return random.choice(ADMIN_PHRASES)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
if __name__ == "__main__":
|
|
376
|
+
print("=== TEST 15 MẪU RANDOM CORPUS MỚI ===")
|
|
377
|
+
for i in range(15):
|
|
378
|
+
print(f"Sample {i+1:02d}: {generate_random_corpus_text()}")
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AsterOCR Vietnamese Font Manager & Downloader
|
|
3
|
+
Manages and downloads open-source Vietnamese handwriting and receipt fonts from Google Fonts.
|
|
4
|
+
Combines with Windows system fonts for rich visual diversity.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import urllib.request
|
|
10
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
11
|
+
|
|
12
|
+
if sys.platform == "win32":
|
|
13
|
+
try:
|
|
14
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
15
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
16
|
+
except Exception:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
FONTS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "fonts"))
|
|
20
|
+
WINDOWS_FONT_DIR = "C:/Windows/Fonts"
|
|
21
|
+
|
|
22
|
+
# Curated Google Fonts repository URLs with verified Vietnamese glyph coverage (SIL OFL)
|
|
23
|
+
GOOGLE_HANDWRITING_FONTS = {
|
|
24
|
+
"Caveat": "https://github.com/google/fonts/raw/main/ofl/caveat/Caveat%5Bwght%5D.ttf",
|
|
25
|
+
"DancingScript": "https://github.com/google/fonts/raw/main/ofl/dancingscript/DancingScript%5Bwght%5D.ttf",
|
|
26
|
+
"PatrickHand": "https://github.com/google/fonts/raw/main/ofl/patrickhand/PatrickHand-Regular.ttf",
|
|
27
|
+
"Mali": "https://github.com/google/fonts/raw/main/ofl/mali/Mali-Regular.ttf",
|
|
28
|
+
"Itim": "https://github.com/google/fonts/raw/main/ofl/itim/Itim-Regular.ttf",
|
|
29
|
+
"Sriracha": "https://github.com/google/fonts/raw/main/ofl/sriracha/Sriracha-Regular.ttf",
|
|
30
|
+
"Pangolin": "https://github.com/google/fonts/raw/main/ofl/pangolin/Pangolin-Regular.ttf",
|
|
31
|
+
"SedgwickAve": "https://github.com/google/fonts/raw/main/ofl/sedgwickave/SedgwickAve-Regular.ttf",
|
|
32
|
+
"MarckScript": "https://github.com/google/fonts/raw/main/ofl/marckscript/MarckScript-Regular.ttf",
|
|
33
|
+
"Comfortaa": "https://github.com/google/fonts/raw/main/ofl/comfortaa/Comfortaa%5Bwght%5D.ttf",
|
|
34
|
+
"VT323": "https://github.com/google/fonts/raw/main/ofl/vt323/VT323-Regular.ttf", # Thermal receipt / dot-matrix
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
WINDOWS_CANDIDATE_FONTS = [
|
|
38
|
+
"Inkfree.ttf",
|
|
39
|
+
"segoepr.ttf",
|
|
40
|
+
"segoeprb.ttf",
|
|
41
|
+
"segoesc.ttf",
|
|
42
|
+
"segoescb.ttf",
|
|
43
|
+
"comic.ttf",
|
|
44
|
+
"comicbd.ttf",
|
|
45
|
+
"LHANDW.TTF",
|
|
46
|
+
"consola.ttf",
|
|
47
|
+
"consolab.ttf",
|
|
48
|
+
"cour.ttf",
|
|
49
|
+
"courbd.ttf",
|
|
50
|
+
"arial.ttf",
|
|
51
|
+
"times.ttf",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
TEST_VIETNAMESE_STRING = "Hà Nội - Đất Thăng Long ngàn năm văn hiến, phở bò 125.000đ"
|
|
55
|
+
|
|
56
|
+
def download_font(name, url, target_dir=FONTS_DIR):
|
|
57
|
+
"""Downloads a font file safely."""
|
|
58
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
59
|
+
filename = f"{name}.ttf"
|
|
60
|
+
target_path = os.path.join(target_dir, filename)
|
|
61
|
+
if os.path.exists(target_path) and os.path.getsize(target_path) > 1024:
|
|
62
|
+
return target_path
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
req = urllib.request.Request(
|
|
66
|
+
url,
|
|
67
|
+
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
|
|
68
|
+
)
|
|
69
|
+
with urllib.request.urlopen(req, timeout=15) as response, open(target_path, "wb") as out_file:
|
|
70
|
+
out_file.write(response.read())
|
|
71
|
+
print(f" [+] Đã tải thành công font: {name} ({os.path.getsize(target_path)//1024} KB)")
|
|
72
|
+
return target_path
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f" [-] Không thể tải {name} từ {url}: {e}")
|
|
75
|
+
if os.path.exists(target_path):
|
|
76
|
+
try:
|
|
77
|
+
os.remove(target_path)
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def download_all_handwriting_fonts():
|
|
83
|
+
"""Downloads all verified Google Fonts handwriting & receipt fonts."""
|
|
84
|
+
print("=" * 65)
|
|
85
|
+
print(" TẢI KHO FONT CHỮ VIẾT TAY & BIÊN LAI TIẾNG VIỆT (GOOGLE FONTS)")
|
|
86
|
+
print("=" * 65)
|
|
87
|
+
downloaded = []
|
|
88
|
+
for name, url in GOOGLE_HANDWRITING_FONTS.items():
|
|
89
|
+
path = download_font(name, url)
|
|
90
|
+
if path:
|
|
91
|
+
downloaded.append(path)
|
|
92
|
+
return downloaded
|
|
93
|
+
|
|
94
|
+
def test_font_vietnamese_support(font_path):
|
|
95
|
+
"""Verifies that the font can render Vietnamese diacritics without missing glyphs."""
|
|
96
|
+
try:
|
|
97
|
+
font = ImageFont.truetype(font_path, size=24)
|
|
98
|
+
# Create test canvas
|
|
99
|
+
im = Image.new("L", (400, 50), 255)
|
|
100
|
+
draw = ImageDraw.Draw(im)
|
|
101
|
+
draw.text((10, 10), TEST_VIETNAMESE_STRING, fill=0, font=font)
|
|
102
|
+
# Check if text was actually drawn (not purely white)
|
|
103
|
+
arr = list(im.getdata())
|
|
104
|
+
has_black = any(p < 200 for p in arr)
|
|
105
|
+
return has_black
|
|
106
|
+
except Exception:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
def get_handwriting_fonts():
|
|
110
|
+
"""
|
|
111
|
+
Returns list of verified handwriting font paths (local downloaded + Windows fonts).
|
|
112
|
+
"""
|
|
113
|
+
fonts = []
|
|
114
|
+
# 1. Downloaded fonts
|
|
115
|
+
if os.path.exists(FONTS_DIR):
|
|
116
|
+
for f in os.listdir(FONTS_DIR):
|
|
117
|
+
if f.lower().endswith((".ttf", ".otf")):
|
|
118
|
+
p = os.path.join(FONTS_DIR, f)
|
|
119
|
+
if test_font_vietnamese_support(p):
|
|
120
|
+
fonts.append(p)
|
|
121
|
+
|
|
122
|
+
# 2. Windows candidate handwriting fonts
|
|
123
|
+
if os.path.exists(WINDOWS_FONT_DIR):
|
|
124
|
+
for cf in ["Inkfree.ttf", "segoepr.ttf", "segoeprb.ttf", "segoesc.ttf", "segoescb.ttf", "comic.ttf", "comicbd.ttf", "LHANDW.TTF"]:
|
|
125
|
+
p = os.path.join(WINDOWS_FONT_DIR, cf)
|
|
126
|
+
if os.path.exists(p) and test_font_vietnamese_support(p):
|
|
127
|
+
fonts.append(p)
|
|
128
|
+
|
|
129
|
+
return sorted(list(set(fonts)))
|
|
130
|
+
|
|
131
|
+
def get_receipt_fonts():
|
|
132
|
+
"""
|
|
133
|
+
Returns fonts suitable for receipts, bills, invoices, dot-matrix, vouchers.
|
|
134
|
+
"""
|
|
135
|
+
fonts = []
|
|
136
|
+
# VT323 dot-matrix
|
|
137
|
+
vt323 = os.path.join(FONTS_DIR, "VT323.ttf")
|
|
138
|
+
if os.path.exists(vt323):
|
|
139
|
+
fonts.append(vt323)
|
|
140
|
+
|
|
141
|
+
for cf in ["consola.ttf", "consolab.ttf", "cour.ttf", "courbd.ttf", "arial.ttf", "calibri.ttf"]:
|
|
142
|
+
p = os.path.join(WINDOWS_FONT_DIR, cf)
|
|
143
|
+
if os.path.exists(p) and test_font_vietnamese_support(p):
|
|
144
|
+
fonts.append(p)
|
|
145
|
+
|
|
146
|
+
return sorted(list(set(fonts)))
|
|
147
|
+
|
|
148
|
+
def get_all_mixed_fonts():
|
|
149
|
+
"""Returns combined collection of all fonts for maximum diversity."""
|
|
150
|
+
hw = get_handwriting_fonts()
|
|
151
|
+
rc = get_receipt_fonts()
|
|
152
|
+
# Add any general fonts in Windows
|
|
153
|
+
gen = []
|
|
154
|
+
for cf in ["arial.ttf", "times.ttf", "tahoma.ttf", "segoeui.ttf", "cambria.ttf"]:
|
|
155
|
+
p = os.path.join(WINDOWS_FONT_DIR, cf)
|
|
156
|
+
if os.path.exists(p) and test_font_vietnamese_support(p):
|
|
157
|
+
gen.append(p)
|
|
158
|
+
|
|
159
|
+
return list(set(hw + rc + gen))
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
download_all_handwriting_fonts()
|
|
163
|
+
hw_fonts = get_handwriting_fonts()
|
|
164
|
+
rc_fonts = get_receipt_fonts()
|
|
165
|
+
print(f"\n[+] Đã tìm thấy {len(hw_fonts)} font viết tay hợp lệ:")
|
|
166
|
+
for f in hw_fonts:
|
|
167
|
+
print(f" - {os.path.basename(f)}")
|
|
168
|
+
print(f"\n[+] Đã tìm thấy {len(rc_fonts)} font hóa đơn / biên lai hợp lệ:")
|
|
169
|
+
for f in rc_fonts:
|
|
170
|
+
print(f" - {os.path.basename(f)}")
|