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,402 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AsterOCR Multi-Source Unified Dataset Packer (Industrial LMDB)
|
|
3
|
+
Merges multiple diverse real and synthetic data streams into a single unified LMDB database:
|
|
4
|
+
1. Real Vietnamese Handwriting from VNOnDB (InkData_line_processed in ocr_dataset.zip)
|
|
5
|
+
2. Real Receipts, Bills, Invoices & Vouchers (meta in ocr_dataset.zip)
|
|
6
|
+
3. Real Scanned Documents (vi_00, vi_01 in ocr_dataset.zip)
|
|
7
|
+
4. Millions of Synthetic Realistic Vietnamese Handwriting Lines (notebook paper, pen pressure, slants)
|
|
8
|
+
5. Millions of Synthetic Realistic Thermal Receipts (dot-matrix, store names, VAT, amounts)
|
|
9
|
+
6. Open-Source Hugging Face Vietnamese Handwriting datasets
|
|
10
|
+
|
|
11
|
+
Output Format:
|
|
12
|
+
- Single LMDB directory: dataset_unified.lmdb (data.mdb)
|
|
13
|
+
- image-%09d: compressed JPEG bytes (zero NTFS file fragmentation)
|
|
14
|
+
- label-%09d: UTF-8 Vietnamese ground-truth string
|
|
15
|
+
- source-%09d: source tag string (real_handwriting, real_receipt, synth_handwriting, etc.)
|
|
16
|
+
- num-samples: total number of samples
|
|
17
|
+
- dataset_summary.json: complete category and distribution report
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import io
|
|
22
|
+
import sys
|
|
23
|
+
import json
|
|
24
|
+
import time
|
|
25
|
+
import random
|
|
26
|
+
import zipfile
|
|
27
|
+
import argparse
|
|
28
|
+
|
|
29
|
+
# Suppress OpenBLAS / MKL multi-threading collisions in child worker processes
|
|
30
|
+
os.environ["OMP_NUM_THREADS"] = "1"
|
|
31
|
+
os.environ["OPENBLAS_NUM_THREADS"] = "1"
|
|
32
|
+
os.environ["MKL_NUM_THREADS"] = "1"
|
|
33
|
+
os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
|
|
34
|
+
os.environ["NUMEXPR_NUM_THREADS"] = "1"
|
|
35
|
+
|
|
36
|
+
from multiprocessing import Pool, cpu_count
|
|
37
|
+
import numpy as np
|
|
38
|
+
from PIL import Image
|
|
39
|
+
|
|
40
|
+
if sys.platform == "win32":
|
|
41
|
+
try:
|
|
42
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
43
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
import lmdb
|
|
49
|
+
except ImportError:
|
|
50
|
+
lmdb = None
|
|
51
|
+
|
|
52
|
+
# Ensure root in sys.path
|
|
53
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
|
54
|
+
from asterocr.synthesizer.handwriting_generator import (
|
|
55
|
+
RealisticHandwritingRenderer,
|
|
56
|
+
RealisticReceiptRenderer,
|
|
57
|
+
sample_handwritten_text,
|
|
58
|
+
sample_receipt_text,
|
|
59
|
+
_worker_chunk_handwriting
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def _read_real_zip_sample(zip_ref, img_path, txt_path, target_height=32):
|
|
63
|
+
"""Safely extracts, resizes, and encodes a sample from the real OCR zip."""
|
|
64
|
+
try:
|
|
65
|
+
img_bytes = zip_ref.read(img_path)
|
|
66
|
+
txt_bytes = zip_ref.read(txt_path)
|
|
67
|
+
text = txt_bytes.decode('utf-8', errors='ignore').strip()
|
|
68
|
+
if not text:
|
|
69
|
+
return None, None
|
|
70
|
+
|
|
71
|
+
img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
|
|
72
|
+
w, h = img.size
|
|
73
|
+
if w < 4 or h < 4:
|
|
74
|
+
return None, None
|
|
75
|
+
|
|
76
|
+
new_w = max(16, int((float(target_height) / max(1, h)) * w))
|
|
77
|
+
resized_img = img.resize((new_w, target_height), Image.LANCZOS)
|
|
78
|
+
|
|
79
|
+
buf = io.BytesIO()
|
|
80
|
+
resized_img.save(buf, format="JPEG", quality=92)
|
|
81
|
+
return buf.getvalue(), text
|
|
82
|
+
except Exception:
|
|
83
|
+
return None, None
|
|
84
|
+
|
|
85
|
+
def pack_unified_dataset(
|
|
86
|
+
output_dir="dataset_unified.lmdb",
|
|
87
|
+
zip_path="dataset_download/ocr_dataset.zip",
|
|
88
|
+
index_path="dataset_download/ocr_dataset_index.json",
|
|
89
|
+
num_synth_handwriting=100000,
|
|
90
|
+
num_synth_receipt=50000,
|
|
91
|
+
include_real_zip=True,
|
|
92
|
+
max_real_handwriting=None,
|
|
93
|
+
max_real_receipts=None,
|
|
94
|
+
max_real_documents=None,
|
|
95
|
+
hf_parquet_files=None,
|
|
96
|
+
num_workers=10,
|
|
97
|
+
target_height=32
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Master pipeline to assemble the unified OCR dataset.
|
|
101
|
+
"""
|
|
102
|
+
if lmdb is None:
|
|
103
|
+
raise ImportError("Gói 'lmdb' chưa được cài đặt! Hãy chạy: pip install lmdb")
|
|
104
|
+
|
|
105
|
+
num_workers = min(10, max(1, num_workers)) # Cap at 10 workers for CPU stability
|
|
106
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
107
|
+
|
|
108
|
+
print("=" * 72)
|
|
109
|
+
print(" ASTER-OCR UNIFIED MULTI-SOURCE DATASET PACKER (LMDB)")
|
|
110
|
+
print("=" * 72)
|
|
111
|
+
print(f" Thư mục đích : {output_dir}")
|
|
112
|
+
print(f" Số luồng CPU tối đa : {num_workers} workers")
|
|
113
|
+
print(f" Chiều cao ảnh chuẩn hóa: {target_height} px")
|
|
114
|
+
print(f" Chữ viết tay giả lập : {num_synth_handwriting:,} mẫu")
|
|
115
|
+
print(f" Biên lai giả lập : {num_synth_receipt:,} mẫu")
|
|
116
|
+
print("=" * 72)
|
|
117
|
+
|
|
118
|
+
# 1. Estimate total samples to size the LMDB map
|
|
119
|
+
total_estimated = num_synth_handwriting + num_synth_receipt
|
|
120
|
+
real_items = []
|
|
121
|
+
|
|
122
|
+
if include_real_zip and os.path.exists(zip_path) and os.path.exists(index_path):
|
|
123
|
+
print(f"[*] Đang tải chỉ mục dữ liệu thực tế từ: {index_path}...")
|
|
124
|
+
try:
|
|
125
|
+
with open(index_path, "r", encoding="utf-8") as f:
|
|
126
|
+
all_index = json.load(f)
|
|
127
|
+
|
|
128
|
+
# Categorize real items
|
|
129
|
+
hw_items = [x for x in all_index if "InkData" in x[0]]
|
|
130
|
+
rc_items = [x for x in all_index if x[0].startswith("meta/")]
|
|
131
|
+
doc_items = [x for x in all_index if x[0].startswith(("vi_00/", "vi_01/"))]
|
|
132
|
+
other_items = [x for x in all_index if x[0].startswith(("random/", "en_00/", "en_01/"))]
|
|
133
|
+
|
|
134
|
+
print(f" - Viết tay thật VNOnDB : {len(hw_items):,} mẫu")
|
|
135
|
+
print(f" - Hóa đơn / biên lai : {len(rc_items):,} mẫu")
|
|
136
|
+
print(f" - Tài liệu in tiếng Việt: {len(doc_items):,} mẫu")
|
|
137
|
+
print(f" - Mã số & song ngữ : {len(other_items):,} mẫu")
|
|
138
|
+
|
|
139
|
+
# Slice according to quotas
|
|
140
|
+
if max_real_handwriting is not None:
|
|
141
|
+
hw_items = hw_items[:max_real_handwriting]
|
|
142
|
+
if max_real_receipts is not None:
|
|
143
|
+
rc_items = rc_items[:max_real_receipts]
|
|
144
|
+
if max_real_documents is not None:
|
|
145
|
+
doc_items = doc_items[:max_real_documents]
|
|
146
|
+
|
|
147
|
+
real_items.extend([(item, "real_handwriting") for item in hw_items])
|
|
148
|
+
real_items.extend([(item, "real_receipt") for item in rc_items])
|
|
149
|
+
real_items.extend([(item, "real_document") for item in doc_items])
|
|
150
|
+
|
|
151
|
+
total_estimated += len(real_items)
|
|
152
|
+
print(f"[+] Sẽ gom {len(real_items):,} mẫu thực tế từ kho zip!")
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"[-] Lỗi khi nạp chỉ mục zip: {e}")
|
|
155
|
+
|
|
156
|
+
# Map size: ~35KB per sample, minimum 64MB, with 1.3x buffer
|
|
157
|
+
map_size = max(1024 * 1024 * 64, int(total_estimated * 45 * 1024))
|
|
158
|
+
|
|
159
|
+
print(f"[*] Khởi tạo LMDB môi trường: map_size = {map_size // (1024 * 1024):,} MB...")
|
|
160
|
+
env = lmdb.open(output_dir, map_size=map_size)
|
|
161
|
+
txn = env.begin(write=True)
|
|
162
|
+
|
|
163
|
+
samples_written = 0
|
|
164
|
+
uncommitted = 0
|
|
165
|
+
commit_interval = 5000
|
|
166
|
+
start_time = time.time()
|
|
167
|
+
|
|
168
|
+
stats = {
|
|
169
|
+
"real_handwriting": 0,
|
|
170
|
+
"real_receipt": 0,
|
|
171
|
+
"real_document": 0,
|
|
172
|
+
"synth_handwriting": 0,
|
|
173
|
+
"synth_receipt": 0,
|
|
174
|
+
"hf_handwriting": 0,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
# -------------------------------------------------------------
|
|
179
|
+
# PHASE 1: Stream Real Images from ZIP directly into LMDB
|
|
180
|
+
# -------------------------------------------------------------
|
|
181
|
+
if real_items and os.path.exists(zip_path):
|
|
182
|
+
print("\n[GIAI ĐOẠN 1] Bắt đầu nạp dữ liệu thực tế (Viết tay thật + Hóa đơn + Tài liệu)...")
|
|
183
|
+
with zipfile.ZipFile(zip_path, "r") as z:
|
|
184
|
+
for idx, (item, source_tag) in enumerate(real_items):
|
|
185
|
+
img_path, txt_path = item
|
|
186
|
+
img_bytes, text = _read_real_zip_sample(z, img_path, txt_path, target_height)
|
|
187
|
+
if img_bytes is None or not text:
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
samples_written += 1
|
|
191
|
+
img_key = f"image-{samples_written:09d}".encode("utf-8")
|
|
192
|
+
lbl_key = f"label-{samples_written:09d}".encode("utf-8")
|
|
193
|
+
src_key = f"source-{samples_written:09d}".encode("utf-8")
|
|
194
|
+
|
|
195
|
+
txn.put(img_key, img_bytes)
|
|
196
|
+
txn.put(lbl_key, text.encode("utf-8"))
|
|
197
|
+
txn.put(src_key, source_tag.encode("utf-8"))
|
|
198
|
+
|
|
199
|
+
stats[source_tag] = stats.get(source_tag, 0) + 1
|
|
200
|
+
uncommitted += 1
|
|
201
|
+
|
|
202
|
+
if uncommitted >= commit_interval:
|
|
203
|
+
txn.commit()
|
|
204
|
+
txn = env.begin(write=True)
|
|
205
|
+
uncommitted = 0
|
|
206
|
+
|
|
207
|
+
if samples_written % 1000 == 0 or idx == len(real_items) - 1:
|
|
208
|
+
elapsed = max(0.001, time.time() - start_time)
|
|
209
|
+
speed = samples_written / elapsed
|
|
210
|
+
sys.stdout.write(
|
|
211
|
+
f"\r[*] [Real Data] Đã nạp: {samples_written:,} mẫu | "
|
|
212
|
+
f"Tốc độ: {speed:,.0f} mẫu/s "
|
|
213
|
+
)
|
|
214
|
+
sys.stdout.flush()
|
|
215
|
+
|
|
216
|
+
print(f"\n[+] Đã nạp xong toàn bộ dữ liệu thực tế!")
|
|
217
|
+
|
|
218
|
+
# -------------------------------------------------------------
|
|
219
|
+
# PHASE 2: Parallel Synthetic Generation (Handwriting & Receipts)
|
|
220
|
+
# -------------------------------------------------------------
|
|
221
|
+
synth_tasks = []
|
|
222
|
+
chunk_size = 1000
|
|
223
|
+
|
|
224
|
+
# Handwriting tasks
|
|
225
|
+
curr_cnt = 0
|
|
226
|
+
while curr_cnt < num_synth_handwriting:
|
|
227
|
+
cnt = min(chunk_size, num_synth_handwriting - curr_cnt)
|
|
228
|
+
synth_tasks.append((samples_written + 1, cnt, "handwriting", target_height))
|
|
229
|
+
curr_cnt += cnt
|
|
230
|
+
samples_written += cnt # pre-allocate ID space
|
|
231
|
+
|
|
232
|
+
# Receipt tasks
|
|
233
|
+
curr_rc_cnt = 0
|
|
234
|
+
while curr_rc_cnt < num_synth_receipt:
|
|
235
|
+
cnt = min(chunk_size, num_synth_receipt - curr_rc_cnt)
|
|
236
|
+
synth_tasks.append((samples_written + 1, cnt, "receipt", target_height))
|
|
237
|
+
curr_rc_cnt += cnt
|
|
238
|
+
samples_written += cnt
|
|
239
|
+
|
|
240
|
+
# Reset actual counter for writing
|
|
241
|
+
samples_written -= (num_synth_handwriting + num_synth_receipt)
|
|
242
|
+
|
|
243
|
+
if synth_tasks:
|
|
244
|
+
print(f"\n[GIAI ĐOẠN 2] Sinh dữ liệu viết tay & hóa đơn song song ({num_workers} workers)...")
|
|
245
|
+
synth_start_time = time.time()
|
|
246
|
+
with Pool(num_workers) as pool:
|
|
247
|
+
for chunk_data in pool.imap_unordered(_worker_chunk_handwriting, synth_tasks, chunksize=1):
|
|
248
|
+
for _, img_bytes, text, source_type in chunk_data:
|
|
249
|
+
samples_written += 1
|
|
250
|
+
img_key = f"image-{samples_written:09d}".encode("utf-8")
|
|
251
|
+
lbl_key = f"label-{samples_written:09d}".encode("utf-8")
|
|
252
|
+
src_key = f"source-{samples_written:09d}".encode("utf-8")
|
|
253
|
+
|
|
254
|
+
txn.put(img_key, img_bytes)
|
|
255
|
+
txn.put(lbl_key, text.encode("utf-8"))
|
|
256
|
+
txn.put(src_key, source_type.encode("utf-8"))
|
|
257
|
+
|
|
258
|
+
stats[source_type] = stats.get(source_type, 0) + 1
|
|
259
|
+
uncommitted += 1
|
|
260
|
+
|
|
261
|
+
if uncommitted >= commit_interval:
|
|
262
|
+
txn.commit()
|
|
263
|
+
txn = env.begin(write=True)
|
|
264
|
+
uncommitted = 0
|
|
265
|
+
|
|
266
|
+
elapsed = max(0.001, time.time() - start_time)
|
|
267
|
+
speed = samples_written / elapsed
|
|
268
|
+
pct = (samples_written / max(1, total_estimated)) * 100
|
|
269
|
+
|
|
270
|
+
mdb_path = os.path.join(output_dir, "data.mdb")
|
|
271
|
+
db_mb = os.path.getsize(mdb_path) / (1024 * 1024) if os.path.exists(mdb_path) else 0
|
|
272
|
+
|
|
273
|
+
sys.stdout.write(
|
|
274
|
+
f"\r[*] [Synth Parallel] Đã ghi: {samples_written:,}/{total_estimated:,} ({pct:.1f}%) | "
|
|
275
|
+
f"Tốc độ: {speed:,.0f} mẫu/s | LMDB: {db_mb:,.1f} MB "
|
|
276
|
+
)
|
|
277
|
+
sys.stdout.flush()
|
|
278
|
+
|
|
279
|
+
# -------------------------------------------------------------
|
|
280
|
+
# PHASE 3: HuggingFace Parquet integration (if any specified)
|
|
281
|
+
# -------------------------------------------------------------
|
|
282
|
+
if hf_parquet_files:
|
|
283
|
+
print("\n[GIAI ĐOẠN 3] Nạp dữ liệu bổ sung từ Hugging Face Parquet...")
|
|
284
|
+
try:
|
|
285
|
+
import pandas as pd
|
|
286
|
+
for pq_file in hf_parquet_files:
|
|
287
|
+
if os.path.exists(pq_file):
|
|
288
|
+
df = pd.read_parquet(pq_file)
|
|
289
|
+
for _, row in df.iterrows():
|
|
290
|
+
# Find text and image bytes
|
|
291
|
+
raw_bytes = None
|
|
292
|
+
text = None
|
|
293
|
+
for col in df.columns:
|
|
294
|
+
if "text" in col.lower() or "label" in col.lower() or "transcription" in col.lower():
|
|
295
|
+
text = str(row[col]).strip()
|
|
296
|
+
elif "image" in col.lower():
|
|
297
|
+
val = row[col]
|
|
298
|
+
if isinstance(val, dict) and "bytes" in val:
|
|
299
|
+
raw_bytes = val["bytes"]
|
|
300
|
+
elif isinstance(val, bytes):
|
|
301
|
+
raw_bytes = val
|
|
302
|
+
|
|
303
|
+
if raw_bytes and text:
|
|
304
|
+
try:
|
|
305
|
+
img = Image.open(io.BytesIO(raw_bytes)).convert("RGB")
|
|
306
|
+
w, h = img.size
|
|
307
|
+
new_w = max(16, int((float(target_height) / max(1, h)) * w))
|
|
308
|
+
resized = img.resize((new_w, target_height), Image.LANCZOS)
|
|
309
|
+
buf = io.BytesIO()
|
|
310
|
+
resized.save(buf, format="JPEG", quality=90)
|
|
311
|
+
|
|
312
|
+
samples_written += 1
|
|
313
|
+
img_key = f"image-{samples_written:09d}".encode("utf-8")
|
|
314
|
+
lbl_key = f"label-{samples_written:09d}".encode("utf-8")
|
|
315
|
+
src_key = f"source-{samples_written:09d}".encode("utf-8")
|
|
316
|
+
|
|
317
|
+
txn.put(img_key, buf.getvalue())
|
|
318
|
+
txn.put(lbl_key, text.encode("utf-8"))
|
|
319
|
+
txn.put(src_key, b"hf_handwriting")
|
|
320
|
+
|
|
321
|
+
stats["hf_handwriting"] = stats.get("hf_handwriting", 0) + 1
|
|
322
|
+
uncommitted += 1
|
|
323
|
+
if uncommitted >= commit_interval:
|
|
324
|
+
txn.commit()
|
|
325
|
+
txn = env.begin(write=True)
|
|
326
|
+
uncommitted = 0
|
|
327
|
+
except Exception:
|
|
328
|
+
continue
|
|
329
|
+
except Exception as e:
|
|
330
|
+
print(f"[-] Lỗi nạp HuggingFace Parquet: {e}")
|
|
331
|
+
|
|
332
|
+
# Final commit and write metadata header
|
|
333
|
+
txn.put(b"num-samples", str(samples_written).encode("utf-8"))
|
|
334
|
+
txn.commit()
|
|
335
|
+
|
|
336
|
+
finally:
|
|
337
|
+
env.sync()
|
|
338
|
+
env.close()
|
|
339
|
+
|
|
340
|
+
total_time = max(0.001, time.time() - start_time)
|
|
341
|
+
avg_speed = samples_written / total_time
|
|
342
|
+
mdb_size_mb = os.path.getsize(os.path.join(output_dir, "data.mdb")) / (1024 * 1024)
|
|
343
|
+
|
|
344
|
+
# Save comprehensive summary JSON
|
|
345
|
+
summary = {
|
|
346
|
+
"dataset_name": "AsterOCR Unified Multi-Source Dataset",
|
|
347
|
+
"output_dir": output_dir,
|
|
348
|
+
"total_samples": samples_written,
|
|
349
|
+
"target_height": target_height,
|
|
350
|
+
"distribution": stats,
|
|
351
|
+
"size_mb": round(mdb_size_mb, 2),
|
|
352
|
+
"total_seconds": round(total_time, 2),
|
|
353
|
+
"avg_samples_per_sec": round(avg_speed, 1),
|
|
354
|
+
"created_at": time.strftime("%Y-%m-%d %H:%M:%S")
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
with open(os.path.join(output_dir, "dataset_summary.json"), "w", encoding="utf-8") as f:
|
|
358
|
+
json.dump(summary, f, ensure_ascii=False, indent=2)
|
|
359
|
+
|
|
360
|
+
print("\n\n" + "=" * 72)
|
|
361
|
+
print(" [+] HOÀN THÀNH ĐÓNG GÓI BỘ DỮ LIỆU HỢP NHẤT DUY NHẤT!")
|
|
362
|
+
print("=" * 72)
|
|
363
|
+
print(f" Tổng số mẫu trong kho : {samples_written:,} mẫu")
|
|
364
|
+
print(f" Thời gian hoàn thành : {total_time:.1f}s (~{total_time/60:.1f} phút)")
|
|
365
|
+
print(f" Tốc độ ghi trung bình : {avg_speed:,.0f} mẫu/giây")
|
|
366
|
+
print(f" Dung lượng LMDB (MDB) : {mdb_size_mb:,.2f} MB")
|
|
367
|
+
print(f" Phân bố nguồn dữ liệu :")
|
|
368
|
+
for k, v in stats.items():
|
|
369
|
+
if v > 0:
|
|
370
|
+
pct = (v / max(1, samples_written)) * 100
|
|
371
|
+
print(f" * {k:<20}: {v:>10,} mẫu ({pct:.1f}%)")
|
|
372
|
+
print("=" * 72)
|
|
373
|
+
return summary
|
|
374
|
+
|
|
375
|
+
def main():
|
|
376
|
+
parser = argparse.ArgumentParser(description="AsterOCR Unified Multi-Source Dataset Packer")
|
|
377
|
+
parser.add_argument("--output_dir", type=str, default="dataset_unified.lmdb", help="Thư mục LMDB duy nhất")
|
|
378
|
+
parser.add_argument("--num_synth_hw", type=int, default=50000, help="Số mẫu chữ viết tay giả lập")
|
|
379
|
+
parser.add_argument("--num_synth_rc", type=int, default=25000, help="Số mẫu hóa đơn biên lai giả lập")
|
|
380
|
+
parser.add_argument("--max_real_hw", type=int, default=None, help="Giới hạn số mẫu viết tay thật (None = lấy hết 7.2k)")
|
|
381
|
+
parser.add_argument("--max_real_rc", type=int, default=50000, help="Giới hạn số mẫu hóa đơn thật")
|
|
382
|
+
parser.add_argument("--max_real_doc", type=int, default=50000, help="Giới hạn số mẫu văn bản tài liệu thật")
|
|
383
|
+
parser.add_argument("--no_real_zip", action="store_true", help="Không lấy dữ liệu từ zip")
|
|
384
|
+
parser.add_argument("--num_workers", type=int, default=10, help="Số luồng CPU tối đa (mặc định 10)")
|
|
385
|
+
parser.add_argument("--target_height", type=int, default=32, help="Chiều cao ảnh chuẩn hóa")
|
|
386
|
+
|
|
387
|
+
args = parser.parse_args()
|
|
388
|
+
|
|
389
|
+
pack_unified_dataset(
|
|
390
|
+
output_dir=args.output_dir,
|
|
391
|
+
num_synth_handwriting=args.num_synth_hw,
|
|
392
|
+
num_synth_receipt=args.num_synth_rc,
|
|
393
|
+
include_real_zip=not args.no_real_zip,
|
|
394
|
+
max_real_handwriting=args.max_real_hw,
|
|
395
|
+
max_real_receipts=args.max_real_rc,
|
|
396
|
+
max_real_documents=args.max_real_doc,
|
|
397
|
+
num_workers=args.num_workers,
|
|
398
|
+
target_height=args.target_height
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
if __name__ == "__main__":
|
|
402
|
+
main()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
if sys.platform == "win32":
|
|
7
|
+
try:
|
|
8
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
9
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
10
|
+
except Exception:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def get_filename_from_cd(cd):
|
|
14
|
+
if not cd:
|
|
15
|
+
return None
|
|
16
|
+
fname = re.findall(r'filename=(.+)', cd)
|
|
17
|
+
if len(fname) == 0:
|
|
18
|
+
fname = re.findall(r"filename\*=UTF-8''(.+)", cd)
|
|
19
|
+
if len(fname) > 0:
|
|
20
|
+
return fname[0].strip(' "\'')
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
def download_file_from_google_drive(file_id, output_dir="dataset_download"):
|
|
24
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
25
|
+
session = requests.Session()
|
|
26
|
+
|
|
27
|
+
# Initial request
|
|
28
|
+
url = f"https://drive.usercontent.google.com/download?id={file_id}&export=download&authuser=0"
|
|
29
|
+
headers = {
|
|
30
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
print(f"[*] Đang kết nối tới Google Drive (ID: {file_id})...")
|
|
34
|
+
res = session.get(url, headers=headers, stream=True)
|
|
35
|
+
|
|
36
|
+
content_type = res.headers.get('Content-Type', '')
|
|
37
|
+
if 'text/html' in content_type:
|
|
38
|
+
html = res.text
|
|
39
|
+
# Tìm token xác nhận hoặc link download trực tiếp
|
|
40
|
+
token_match = re.search(r'name="confirm"\s+value="([^"]+)"', html)
|
|
41
|
+
uuid_match = re.search(r'name="uuid"\s+value="([^"]+)"', html)
|
|
42
|
+
action_match = re.search(r'action="([^"]+)"', html)
|
|
43
|
+
|
|
44
|
+
if action_match and token_match:
|
|
45
|
+
download_url = action_match.group(1)
|
|
46
|
+
params = {
|
|
47
|
+
'id': file_id,
|
|
48
|
+
'export': 'download',
|
|
49
|
+
'confirm': token_match.group(1),
|
|
50
|
+
}
|
|
51
|
+
if uuid_match:
|
|
52
|
+
params['uuid'] = uuid_match.group(1)
|
|
53
|
+
print(f"[*] Bỏ qua cảnh báo virus, gửi mã xác nhận: {token_match.group(1)}...")
|
|
54
|
+
res = session.get(download_url, params=params, headers=headers, stream=True)
|
|
55
|
+
else:
|
|
56
|
+
# Thử qua docs.google.com/uc
|
|
57
|
+
alt_url = "https://docs.google.com/uc?export=download"
|
|
58
|
+
res = session.get(alt_url, params={'id': file_id, 'confirm': 't'}, headers=headers, stream=True)
|
|
59
|
+
|
|
60
|
+
# Lấy tên file thực tế từ header hoặc đặt mặc định
|
|
61
|
+
filename = get_filename_from_cd(res.headers.get('Content-Disposition'))
|
|
62
|
+
if not filename:
|
|
63
|
+
filename = f"dataset_{file_id}.zip"
|
|
64
|
+
|
|
65
|
+
out_path = os.path.join(output_dir, filename)
|
|
66
|
+
print(f"[*] Đang tải về: {filename}")
|
|
67
|
+
total_size = int(res.headers.get('content-length', 0))
|
|
68
|
+
if total_size:
|
|
69
|
+
print(f"[*] Dung lượng: {total_size / (1024*1024):.2f} MB")
|
|
70
|
+
|
|
71
|
+
downloaded = 0
|
|
72
|
+
chunk_size = 1024 * 1024 # 1MB
|
|
73
|
+
with open(out_path, "wb") as f:
|
|
74
|
+
for chunk in res.iter_content(chunk_size):
|
|
75
|
+
if chunk:
|
|
76
|
+
f.write(chunk)
|
|
77
|
+
downloaded += len(chunk)
|
|
78
|
+
if total_size > 0:
|
|
79
|
+
percent = (downloaded / total_size) * 100
|
|
80
|
+
mb_down = downloaded / (1024 * 1024)
|
|
81
|
+
mb_tot = total_size / (1024 * 1024)
|
|
82
|
+
sys.stdout.write(f"\r--> Tiến độ: {percent:.1f}% ({mb_down:.1f}/{mb_tot:.1f} MB)")
|
|
83
|
+
else:
|
|
84
|
+
mb_down = downloaded / (1024 * 1024)
|
|
85
|
+
sys.stdout.write(f"\r--> Đã tải: {mb_down:.1f} MB")
|
|
86
|
+
sys.stdout.flush()
|
|
87
|
+
|
|
88
|
+
print(f"\n[+] Tải xong thành công: {out_path}")
|
|
89
|
+
return out_path
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
file_id = "1T0cmkhTgu3ahyMIwGZeby612RpVdDxOR"
|
|
93
|
+
download_file_from_google_drive(file_id, "dataset_download")
|