phucsd commited on
Commit
efd2c7f
·
verified ·
1 Parent(s): decc7d3

docs(audit): complete academic precision and reproducibility cleanup

Browse files
ACADEMIC_AUDIT.md CHANGED
@@ -120,13 +120,13 @@ To adhere to rigorous academic scholarship, multiple unverified or hyperbolic as
120
  To situate this project appropriately within existing scientific literature, a dedicated related work section and bibliography were added citing seminal contributions:
121
 
122
  1. **Nguyen, M.-T., Schweyer, A.-V., Le, T.-L., Tran, T.-H., & Vu, H. (2019a)**: *Preliminary Results on Ancient Cham Glyph Recognition from Cham Inscription Images*. In 2019 International Conference on Multimedia Analysis and Pattern Recognition (MAPR 2019), IEEE, pp. 1–6. DOI: 10.1109/MAPR.2019.8743540. (Evaluated HOG, NPW, and CNN-derived features with k-NN and linear SVM).
123
- 2. **Nguyen, M.-T., Schweyer, A.-V., Le, T.-L., Tran, T.-H., & Vu, H. (2019b)**: *Improving Ancient Cham Glyph Recognition Using Data Augmentation and Transfer Learning*. In International Conference on Image Analysis and Processing (ICIAP 2019 Workshops: PatReCH), Springer, Cham, LNCS 11808, pp. 115–125. DOI: 10.1007/978-3-030-30754-7_12. (Evaluated data augmentation and transfer learning from rendered synthetic glyph variants to weathered stone inscriptions).
124
  3. **Nguyen, T.-N. (2023)**: *Segmentation, Recognition and Indexing of Cham characters in Cham documents* (*Segmentation, reconnaissance et indexation des caractères Cham dans les documents Cham*). Ph.D. Dissertation, Université de La Rochelle, France.
125
  4. **Nguyen, T.-N., Burie, J.-C., Le, T.-L., & Schweyer, A.-V. (2022)**: *An effective method for text line segmentation in historical document images*. In 26th International Conference on Pattern Recognition (ICPR 2022), IEEE, pp. 2686–2692. DOI: 10.1109/ICPR56361.2022.9956617.
126
  5. **Nguyen, T.-N., Burie, J.-C., Le, T.-L., & Schweyer, A.-V. (2023)**: *A Two-Step Sequence Transformer Based Method for Cham to Latin Script Transliteration*. In Proceedings of the 7th International Workshop on Historical Document Imaging and Processing (HIP@ICDAR 2023), ACM, pp. 25–30. DOI: 10.1145/3604951.3605525.
127
  6. **Nguyen, T.-N., Burie, J.-C., Le, T.-L., & Schweyer, A.-V. (2025)**: *Text line segmentation approach combining deep learning model and traditional image processing techniques - application to transliteration of Cham manuscripts*. *Multimedia Tools and Applications*, Springer. DOI: 10.1007/s11042-025-20602-x. (Evaluates line segmentation and transliteration across 627 images, ~8,300 lines).
128
  7. **ANR CHAMDOC Project (2019–2024)**: *Cham Documentation (CHAMDOC)*, Project ANR-19-CE27-0018, Agence Nationale de la Recherche, France.
129
- 8. **Digitization Archives**: *Historic Cham Manuscripts of Vietnam* collection (Southeast Asia Digital Library / SEADL, Northern Illinois University, preserving 977 digitized manuscripts, >57,800 page scans) and Cham community collections (~3,000 physical manuscripts across Vietnam and Cambodia).
130
 
131
  ---
132
 
@@ -134,9 +134,9 @@ To situate this project appropriately within existing scientific literature, a d
134
 
135
  The project openly documents four major technical limitations:
136
 
137
- 1. **Synthetic-to-Real Domain Gap**: The neural recognizer is trained primarily on synthetic textline images rendered with digital TrueType fonts (*Noto Sans Cham*, *Cham Roman*, *EFEO Cham*). Real manuscripts feature idiosyncratic scribal handwriting, varying pen pressure, and ink bleed that may degrade recognition accuracy in archival field settings.
138
  2. **Absence of a Large-Scale Annotated Real Manuscript Benchmark**: To date, there is no publicly available, character-level annotated historical Cham manuscript dataset of sufficient volume. Curating such a corpus requires close, sustained collaboration with native Cham scholars, paleographers, and community elders.
139
- 3. **Extreme Biological Degradation & Epigraphy**: Inscription rubbings and severely mold-damaged palm-leaves (where $>40\%$ of glyph strokes are eroded) cannot be resolved by visual CTC sequence decoders alone without integrating language modeling priors (MLMs).
140
  4. **Interlinear Overlap Breaking Point (Level 5)**: When sinusoidal document warping exceeds line spacing ($\text{Amplitude} > \text{Gap}$), 1D projection and horizontal bounding boxes break down, necessitating future research into 2D polygonal baseline tracking.
141
 
142
  ---
@@ -154,16 +154,19 @@ The project openly documents four major technical limitations:
154
  | File Audited | Changes Applied | Status | Verification Mechanism |
155
  | :--- | :--- | :---: | :--- |
156
  | `LICENSE` | Clean MIT License for code; pointers to DATA_PROVENANCE.md | **Verified** | Standard legal wording |
157
- | `DATA_PROVENANCE.md` | Documented text corpora, fonts (*Noto Sans Cham*, *EFEO Cham*), rights disclaimer, and synthetic splits | **Verified** | Created & linked across docs |
158
- | `RESEARCH.md` | Reconciled title, scope, Unicode order, citations (DOIs, ICPR 2022, 2025 paper), detector parameters, manuscript counts, 25/20/15/15/15/10 splits | **Verified** | 100% matched with benchmarks |
159
- | `ocr-studio/research.html` | Verbatim synchronization with RESEARCH.md; fixed pre-vowels (O/AI only); updated tables and citations | **Verified** | `node --check` static JS syntax validated (Code 0) |
160
  | `ocr-studio/index.html` | Updated model picker, Tab 1 Unicode, Tab 5 empirical benchmarks | **Verified** | `node --check` static JS syntax validated (Code 0) |
161
- | `README.md` & `README_VI.md` | Standardized title, qualified scope to Eastern Cham, updated synthetic splits and font classifications | **Verified** | Synchronized across languages |
162
  | `generate_data.py` | Fixed PRE_SIGNS (`ꨯꨰ`), MEDIAL_SIGNS (`ꨴꨵꨳꨶ`), cleaned VOWEL_DIACRITIC_SIGNS, enforced canonical Unicode order | **Verified** | Unit tests passing |
 
 
 
163
  | `test_vol_roundtrip.py` | Added comprehensive test suites for VA initial/final, medials, pre-vowels, dependent vowels, stanzas | **Verified** | 100% test pass rate |
164
  | `validate_unicode_source_labels.py` | Fixed PRE_SIGNS, MEDIAL_SIGNS, and regex for diacritic validation | **Verified** | Script execution clean |
165
  | `paddleocr_cham_finetune.ipynb` | Synchronized character sets and canonical cluster reordering with generate_data.py | **Verified** | JSON validated |
166
- | `ACADEMIC_AUDIT.md` | Internal Documentation Audit & Reconciliation Log documenting all items | **Verified** | Fully documented |
167
 
168
  ---
169
 
@@ -193,4 +196,4 @@ To maintain full transparency with academic collaborators and external reviewers
193
 
194
  ---
195
 
196
- **Conclusion**: The repository `phucsd/Cham-OCR` is in full compliance with rigorous academic, scientific, and paleographic standards. Zero ungrounded claims remain in the codebase.
 
120
  To situate this project appropriately within existing scientific literature, a dedicated related work section and bibliography were added citing seminal contributions:
121
 
122
  1. **Nguyen, M.-T., Schweyer, A.-V., Le, T.-L., Tran, T.-H., & Vu, H. (2019a)**: *Preliminary Results on Ancient Cham Glyph Recognition from Cham Inscription Images*. In 2019 International Conference on Multimedia Analysis and Pattern Recognition (MAPR 2019), IEEE, pp. 1–6. DOI: 10.1109/MAPR.2019.8743540. (Evaluated HOG, NPW, and CNN-derived features with k-NN and linear SVM).
123
+ 2. **Nguyen, M.-T., Schweyer, A.-V., Le, T.-L., Tran, T.-H., & Vu, H. (2019b)**: *Improving Ancient Cham Glyph Recognition Using Data Augmentation and Transfer Learning*. In International Conference on Image Analysis and Processing (ICIAP 2019 Workshops: PatReCH), Springer, Cham, LNCS 11808, pp. 115–125. DOI: 10.1007/978-3-030-30754-7_12. (Evaluated data augmentation and transfer learning from scripts of similar or the same language family to ancient stone inscription glyphs).
124
  3. **Nguyen, T.-N. (2023)**: *Segmentation, Recognition and Indexing of Cham characters in Cham documents* (*Segmentation, reconnaissance et indexation des caractères Cham dans les documents Cham*). Ph.D. Dissertation, Université de La Rochelle, France.
125
  4. **Nguyen, T.-N., Burie, J.-C., Le, T.-L., & Schweyer, A.-V. (2022)**: *An effective method for text line segmentation in historical document images*. In 26th International Conference on Pattern Recognition (ICPR 2022), IEEE, pp. 2686–2692. DOI: 10.1109/ICPR56361.2022.9956617.
126
  5. **Nguyen, T.-N., Burie, J.-C., Le, T.-L., & Schweyer, A.-V. (2023)**: *A Two-Step Sequence Transformer Based Method for Cham to Latin Script Transliteration*. In Proceedings of the 7th International Workshop on Historical Document Imaging and Processing (HIP@ICDAR 2023), ACM, pp. 25–30. DOI: 10.1145/3604951.3605525.
127
  6. **Nguyen, T.-N., Burie, J.-C., Le, T.-L., & Schweyer, A.-V. (2025)**: *Text line segmentation approach combining deep learning model and traditional image processing techniques - application to transliteration of Cham manuscripts*. *Multimedia Tools and Applications*, Springer. DOI: 10.1007/s11042-025-20602-x. (Evaluates line segmentation and transliteration across 627 images, ~8,300 lines).
128
  7. **ANR CHAMDOC Project (2019–2024)**: *Cham Documentation (CHAMDOC)*, Project ANR-19-CE27-0018, Agence Nationale de la Recherche, France.
129
+ 8. **Digitization Archives**: The Historic Cham Manuscripts of Vietnam collection in the Southeast Asia Digital Library (SEADL) contains 977 digitized manuscripts comprising more than 57,800 page scans. Cham community collections preserve an estimated 3,000 physical manuscripts across Vietnam and Cambodia.
130
 
131
  ---
132
 
 
134
 
135
  The project openly documents four major technical limitations:
136
 
137
+ 1. **Synthetic-to-Real Domain Gap**: The neural recognizer is trained predominantly on synthetic textline images rendered with bundled *Noto Sans Cham* (SIL OFL 1.1). Real manuscripts feature idiosyncratic scribal handwriting, varying pen pressure, and ink bleed that may degrade recognition accuracy in archival field settings.
138
  2. **Absence of a Large-Scale Annotated Real Manuscript Benchmark**: To date, there is no publicly available, character-level annotated historical Cham manuscript dataset of sufficient volume. Curating such a corpus requires close, sustained collaboration with native Cham scholars, paleographers, and community elders.
139
+ 3. **Extreme Biological Degradation & Epigraphy**: Inscription rubbings and severely mold-damaged palm-leaves (where character ink has substantially eroded) cannot be resolved by visual CTC sequence decoders alone without integrating language modeling priors (MLMs).
140
  4. **Interlinear Overlap Breaking Point (Level 5)**: When sinusoidal document warping exceeds line spacing ($\text{Amplitude} > \text{Gap}$), 1D projection and horizontal bounding boxes break down, necessitating future research into 2D polygonal baseline tracking.
141
 
142
  ---
 
154
  | File Audited | Changes Applied | Status | Verification Mechanism |
155
  | :--- | :--- | :---: | :--- |
156
  | `LICENSE` | Clean MIT License for code; pointers to DATA_PROVENANCE.md | **Verified** | Standard legal wording |
157
+ | `DATA_PROVENANCE.md` | Documented text corpora, fonts (*Noto Sans Cham*, external reference fonts), rights disclaimer, and dual-generator architecture | **Verified** | Created & linked across docs |
158
+ | `RESEARCH.md` | Reconciled title, scope, Unicode order, citations (DOIs, ICPR 2022, 2025 paper), detector parameters, manuscript counts, dual generators (Pipeline A baseline 25/20/15/15/15/10 and Pipeline B V25 5-pillar) | **Verified** | 100% matched with benchmarks |
159
+ | `ocr-studio/research.html` | Verbatim synchronization with RESEARCH.md; updated title, tag pill, abstract grammar, citations, and dual generators | **Verified** | `node --check` static JS syntax validated (Code 0) |
160
  | `ocr-studio/index.html` | Updated model picker, Tab 1 Unicode, Tab 5 empirical benchmarks | **Verified** | `node --check` static JS syntax validated (Code 0) |
161
+ | `README.md` & `README_VI.md` | Standardized title, qualified scope to Eastern Cham, updated synthetic pipeline to dual generators | **Verified** | Synchronized across languages |
162
  | `generate_data.py` | Fixed PRE_SIGNS (`ꨯꨰ`), MEDIAL_SIGNS (`ꨴꨵꨳꨶ`), cleaned VOWEL_DIACRITIC_SIGNS, enforced canonical Unicode order | **Verified** | Unit tests passing |
163
+ | `generate_data_v25.py` | Fixed docstring (140k/10k), renamed `MINIMAL_PAIRS_UE_WA` (`ꨲ` UE vs `ꨶ` WA), synchronized with manifest | **Verified** | Code inspection & syntax clean |
164
+ | `v25_dataset_manifest.json` | Reconciled batch size (32/card, 64 global), stages (1-12, 13-22, 23-40), and diacritic naming with `rec_cham_v25.yml` | **Verified** | JSON syntax validated |
165
+ | `TRAINING_ROADMAP_V25.md` | Aligned data distribution table to 5 pillars, updated execution plan stages to 1-12, 13-22, 23-40, fixed diacritic naming | **Verified** | Documented & reconciled |
166
  | `test_vol_roundtrip.py` | Added comprehensive test suites for VA initial/final, medials, pre-vowels, dependent vowels, stanzas | **Verified** | 100% test pass rate |
167
  | `validate_unicode_source_labels.py` | Fixed PRE_SIGNS, MEDIAL_SIGNS, and regex for diacritic validation | **Verified** | Script execution clean |
168
  | `paddleocr_cham_finetune.ipynb` | Synchronized character sets and canonical cluster reordering with generate_data.py | **Verified** | JSON validated |
169
+ | `ACADEMIC_AUDIT.md` | Documentation Audit & Reconciliation Log tracking known issues and empirical uncertainties | **Verified** | Fully documented |
170
 
171
  ---
172
 
 
196
 
197
  ---
198
 
199
+ **Summary of Documentation & Verification Status**: The documentation across `phucsd/Cham-OCR` has been calibrated against actual code artifacts and empirical benchmark logs. Open empirical uncertainties and ongoing data collection needs remain cataloged above to guide future research and collaborative fieldwork.
DATA_PROVENANCE.md CHANGED
@@ -14,10 +14,10 @@ This document provides a transparent, verifiable record of the provenance, right
14
 
15
  | Source / Title | Author / Editor / Institution | Web Reference / Archive | Acquisition Method | Rights / License Status | Redistributed in Repo? |
16
  | :--- | :--- | :--- | :--- | :--- | :---: |
17
- | *Akayet Inra Patra* (Classical verse epic) | Traditional Cham scribal transmission; modern editorial transliterations by Cham scholars | Academic & community preservation publications | Curated and transcribed from public domain cultural heritage texts | Traditional cultural heritage; public domain for classical text | Yes (as normalized Unicode textlines in `cham_text.txt`) |
18
- | *Ariya Po Pareng* (Classical narrative poem) | Traditional Cham scribal transmission | Cham cultural preservation archives | Curated and transcribed from traditional manuscripts | Traditional cultural heritage; public domain for classical text | Yes (normalized Unicode textlines in `cham_text.txt`) |
19
- | Historical chronicles & folk narratives (*Dalikal*) | Cham traditional oral and written literature | Cham community collections and regional cultural publications | Digitized and normalized into Unicode | Traditional cultural heritage | Yes (normalized Unicode textlines in `cham_text.txt`) |
20
- | Cham-Vietnamese-French Lexical Compendia | Historical missionary and linguistic wordlists (e.g. Aymonier & Cabaton 1906; Moussay 1971; modern pedagogical materials) | Academic library collections & SEADL references | Digital normalization into standard Unicode 16.0 Cham | Historical reference works / non-exclusive educational use | Yes (vocabulary tokens merged into `cham_text.txt`) |
21
 
22
  ### 1.2 Corpus Curation & Linguistic Preprocessing
23
  * **Orthographic Normalization**: Conversion into canonical Unicode 16.0 Cham representation (`U+AA00`–`U+AA5F`).
@@ -56,10 +56,10 @@ The following typefaces were evaluated as reference designs during research expe
56
 
57
  ## 3. Synthetic Document & Textline Datasets
58
 
59
- Due to the acute scarcity of character-level annotated historical Cham manuscript datasets, training and benchmark datasets were synthesized programmatically from `cham_text.txt`:
60
 
61
- ### 3.1 150,000 Textline Synthesis Dataset (`scripts/generate_data.py`)
62
- The generator partitions the 150,000 synthetic corpus into **15% evaluation/locked splits** (22,500 lines) and **85% training split** (127,500 lines):
63
  * **Evaluation & Locked Splits (15% total / 22,500 lines)**:
64
  - `val_clean_short` (3% / 4,500 lines)
65
  - `val_clean_long` (3% / 4,500 lines)
@@ -74,14 +74,22 @@ The generator partitions the 150,000 synthetic corpus into **15% evaluation/lock
74
  - `train_noisy_long` (**15%** / 19,125 lines): Long verse lines with severe photometric degradation and spatial elastic distortion.
75
  - `train_hard_examples` (**10%** / 12,750 lines): Adversarial minimal pairs (`ꨲ` vs `ꨶ`, `꩝꩝` vs `꩝`), verse numerals (1–99) with section marks (`꩑꩞`), and stacked subjoined clusters.
76
 
77
- ### 3.2 Controlled 200-Page Synthetic Document Stress-Test (`ocr-benchmark/`)
 
 
 
 
 
 
 
 
78
  * 200 full-page synthetic documents comprising 903 ground-truth textlines across 5 difficulty levels (Standard, Aged Paper, Narrow Gap, Wavy Sinusoid, Extreme Overlap).
79
  * Generated by `ocr-benchmark/scripts/generate_benchmark_200.py` to establish the mathematical breaking point of line segmentation and CTC recognition.
80
 
81
- ### 3.3 50-Test Stratified Diagnostic Suite (`ocr-studio/data/benchmark_50_tests_results.json`)
82
  * 50 targeted synthetic image crops covering 10 distinct failure modes (noise, tilt, blur, diacritic confusion, low resolution).
83
 
84
- ### 3.4 Rights Status of Synthetic Outputs
85
  Synthetically rendered document images and transcriptions generated by repository scripts inherit the rights and usage status of the underlying linguistic corpora and fonts. The synthetic generator code and annotations produced by the project maintainers are provided under the project's [MIT License](LICENSE), while redistribution of generated textline images remains subject to the rights status of the respective underlying source texts.
86
 
87
  ---
 
14
 
15
  | Source / Title | Author / Editor / Institution | Web Reference / Archive | Acquisition Method | Rights / License Status | Redistributed in Repo? |
16
  | :--- | :--- | :--- | :--- | :--- | :---: |
17
+ | *Akayet Inra Patra* (Classical verse epic) | Traditional Cham scribal transmission; modern editorial transliterations by Cham scholars | Academic & community preservation publications | Curated and transcribed from traditional cultural heritage texts | Traditional cultural heritage / specific edition rights unverified / non-commercial educational & preservation use (line-by-line edition mapping subject to archival verification) | Yes (as normalized Unicode textlines in `cham_text.txt`) |
18
+ | *Ariya Po Pareng* (Classical narrative poem) | Traditional Cham scribal transmission | Cham cultural preservation archives | Curated and transcribed from traditional manuscripts | Traditional cultural heritage / specific edition rights unverified / non-commercial educational & preservation use (line-by-line edition mapping subject to archival verification) | Yes (normalized Unicode textlines in `cham_text.txt`) |
19
+ | Historical chronicles & folk narratives (*Dalikal*) | Cham traditional oral and written literature | Cham community collections and regional cultural publications | Digitized and normalized into Unicode | Traditional cultural heritage / specific edition rights unverified / non-commercial educational & preservation use (line-by-line edition mapping subject to archival verification) | Yes (normalized Unicode textlines in `cham_text.txt`) |
20
+ | Cham-Vietnamese-French Lexical Compendia | Historical missionary and linguistic wordlists (e.g. Aymonier & Cabaton 1906; Moussay 1971; modern pedagogical materials) | Academic library collections & SEADL references | Digital normalization into standard Unicode 16.0 Cham | Historical reference works / specific edition rights unverified / non-commercial educational & preservation use (line-by-line edition mapping subject to archival verification) | Yes (vocabulary tokens merged into `cham_text.txt`) |
21
 
22
  ### 1.2 Corpus Curation & Linguistic Preprocessing
23
  * **Orthographic Normalization**: Conversion into canonical Unicode 16.0 Cham representation (`U+AA00`–`U+AA5F`).
 
56
 
57
  ## 3. Synthetic Document & Textline Datasets
58
 
59
+ Due to the acute scarcity of character-level annotated historical Cham manuscript datasets, training and benchmark datasets were synthesized programmatically from `cham_text.txt`. The repository maintains two distinct synthetic generation pipelines:
60
 
61
+ ### 3.1 Pipeline A: Baseline General Generator (`scripts/generate_data.py`)
62
+ The baseline generator produces a 150,000 textline corpus partitioned into **15% evaluation/locked splits** (22,500 lines) and **85% training split** (127,500 lines):
63
  * **Evaluation & Locked Splits (15% total / 22,500 lines)**:
64
  - `val_clean_short` (3% / 4,500 lines)
65
  - `val_clean_long` (3% / 4,500 lines)
 
74
  - `train_noisy_long` (**15%** / 19,125 lines): Long verse lines with severe photometric degradation and spatial elastic distortion.
75
  - `train_hard_examples` (**10%** / 12,750 lines): Adversarial minimal pairs (`ꨲ` vs `ꨶ`, `꩝꩝` vs `꩝`), verse numerals (1–99) with section marks (`꩑꩞`), and stacked subjoined clusters.
76
 
77
+ ### 3.2 Pipeline B: Experimental V25 Generator (`scripts/generate_data_v25.py`)
78
+ The V25 generator synthesizes 150,000 textline images (140,000 train + 10,000 val) synchronized with `configs/v25_dataset_manifest.json` across 5 core pillars:
79
+ 1. **Canonical Cham Literature (43.3% / 65,000 lines)**: Classical literary vocabulary (*Po Klong Garai*, 57-stanza verse).
80
+ 2. **Anti-Motion Blur Base (20.0% / 30,000 lines)**: Clean base images paired with dynamic in-RAM directional motion blur ($7\times 7$ to $13\times 13$) and defocus.
81
+ 3. **Inline Bilingual Code-Switching (16.7% / 25,000 lines)**: Intra-line mixed phrases with Vietnamese diacritics and Latin annotations.
82
+ 4. **Stanza Numerals & Boundary Punctuation (12.0% / 18,000 lines)**: Verse numbers $1$–$99$ (`꩑꩞`..`꩙꩙꩞`) and boundary marks (`꩞`, `:`, `–`).
83
+ 5. **Adversarial Minimal Pairs (8.0% / 12,000 lines)**: Micro-stroke pairs (`ꨲ` U+AA32 Vowel Sign UE vs `ꨶ` U+AA36 Medial WA, variable Double Danda `꩝꩝` spacing 2–8px, 3-tier diacritic stacks).
84
+
85
+ ### 3.3 Controlled 200-Page Synthetic Document Stress-Test (`ocr-benchmark/`)
86
  * 200 full-page synthetic documents comprising 903 ground-truth textlines across 5 difficulty levels (Standard, Aged Paper, Narrow Gap, Wavy Sinusoid, Extreme Overlap).
87
  * Generated by `ocr-benchmark/scripts/generate_benchmark_200.py` to establish the mathematical breaking point of line segmentation and CTC recognition.
88
 
89
+ ### 3.4 50-Test Stratified Diagnostic Suite (`ocr-studio/data/benchmark_50_tests_results.json`)
90
  * 50 targeted synthetic image crops covering 10 distinct failure modes (noise, tilt, blur, diacritic confusion, low resolution).
91
 
92
+ ### 3.5 Rights Status of Synthetic Outputs
93
  Synthetically rendered document images and transcriptions generated by repository scripts inherit the rights and usage status of the underlying linguistic corpora and fonts. The synthetic generator code and annotations produced by the project maintainers are provided under the project's [MIT License](LICENSE), while redistribution of generated textline images remains subject to the rights status of the respective underlying source texts.
94
 
95
  ---
README.md CHANGED
@@ -38,10 +38,12 @@ An end-to-end deep learning research platform and paleographic transcription sys
38
  - Dual-head multi-task learning with **Connectionist Temporal Classification (CTC)** sequence alignment and **NRTR Multi-Head Cross-Attention** auxiliary decoding.
39
  3. **Canonical Unicode & Logical Order Normalization**:
40
  - Automatically parses recognized grapheme clusters via `normalize_unicode` to reconstruct standard **Brahmic Logical Order** (`Base Consonant + Medials + Pre-Vowels + Dependent Vowels + Vowel Lengthener AA + Finals / Signs / Final VA`), resolving local visual permutation artifacts.
41
- 4. **150,000 Textline Synthetic Pipeline**:
42
  - Font map validation via `fontTools` cmap parsing, eliminating tofu/missing glyph artifacts.
43
  - Dynamic In-RAM augmentation applying directional motion blur on clean disk renders to prevent double-blur degradation.
44
- - Calibrated dataset generator (`scripts/generate_data.py`) partitioning 150,000 textlines into 15% evaluation/locked splits and 85% training across 6 categories: clean short (25%), clean medium (20%), clean long (15%), noisy short (15%), noisy long (15%), and hard adversarial examples (10%).
 
 
45
  5. **Distributed Multi-GPU Cloud Training**:
46
  - Adaptive data-parallel execution (`paddle.distributed.launch --gpus '0,1'`) on Kaggle Nvidia Tesla T4x2 and Lightning Cloud A100 SXM4.
47
  - 3-Stage Checkpoint & Resume protocol ensuring uninterrupted progress across Kaggle's 12-hour session timeout limits.
@@ -172,13 +174,13 @@ Logged in `ocr-studio/data/benchmark_v24_vs_v25_results.json`:
172
  If you utilize Cham-OCR, our synthetic dataset pipeline, or the OCR Studio in your research, please cite:
173
 
174
  ```bibtex
175
- @software{cham_ocr_studio,
176
- author = {Nguyen, Phuc},
177
- title = {Cham OCR Studio: Deep Learning Pipeline and Paleographic Transcription Workbench for Historical Cham Manuscripts},
178
  year = {2026},
179
  publisher = {GitHub},
180
  url = {https://github.com/phucsd/Cham-OCR},
181
- note = {Live web service: https://ocr.cham.asia}
182
  }
183
  ```
184
 
 
38
  - Dual-head multi-task learning with **Connectionist Temporal Classification (CTC)** sequence alignment and **NRTR Multi-Head Cross-Attention** auxiliary decoding.
39
  3. **Canonical Unicode & Logical Order Normalization**:
40
  - Automatically parses recognized grapheme clusters via `normalize_unicode` to reconstruct standard **Brahmic Logical Order** (`Base Consonant + Medials + Pre-Vowels + Dependent Vowels + Vowel Lengthener AA + Finals / Signs / Final VA`), resolving local visual permutation artifacts.
41
+ 4. **150,000 Textline Synthetic Pipelines**:
42
  - Font map validation via `fontTools` cmap parsing, eliminating tofu/missing glyph artifacts.
43
  - Dynamic In-RAM augmentation applying directional motion blur on clean disk renders to prevent double-blur degradation.
44
+ - **Dual-Generator Architecture**:
45
+ - *Pipeline A (Baseline: `scripts/generate_data.py`)*: 150,000 textlines partitioned into 15% evaluation/locked splits and 85% training across 6 categories: clean short (25%), clean medium (20%), clean long (15%), noisy short (15%), noisy long (15%), and hard adversarial examples (10%).
46
+ - *Pipeline B (Experimental V25: `scripts/generate_data_v25.py`)*: 150,000 textlines (140,000 train + 10,000 val) synchronized with `configs/v25_dataset_manifest.json` across 5 core pillars: canonical Cham literature (43.3%), anti-blur base (20.0%), inline bilingual (16.7%), stanza numerals & punctuation (12.0%), and minimal pairs (8.0%).
47
  5. **Distributed Multi-GPU Cloud Training**:
48
  - Adaptive data-parallel execution (`paddle.distributed.launch --gpus '0,1'`) on Kaggle Nvidia Tesla T4x2 and Lightning Cloud A100 SXM4.
49
  - 3-Stage Checkpoint & Resume protocol ensuring uninterrupted progress across Kaggle's 12-hour session timeout limits.
 
174
  If you utilize Cham-OCR, our synthetic dataset pipeline, or the OCR Studio in your research, please cite:
175
 
176
  ```bibtex
177
+ @software{cham_ocr_pipeline,
178
+ author = {Nguyen, Phuc H.},
179
+ title = {Cham-OCR: A Deep-Learning OCR Pipeline for Unicode Eastern Cham, toward Historical Manuscript and Epigraphic Recognition},
180
  year = {2026},
181
  publisher = {GitHub},
182
  url = {https://github.com/phucsd/Cham-OCR},
183
+ note = {Production web service: https://ocr.cham.asia}
184
  }
185
  ```
186
 
README_VI.md CHANGED
@@ -83,9 +83,11 @@ Cham-OCR/
83
  - Mạng nơ-ron **PP-OCRv4 SVTR-LCNet** mở rộng tensor đầu vào lên `[3, 48, 480]`.
84
  - Cơ chế giải mã kép: CTC Loss kết hợp NRTR Multi-Head Cross-Attention.
85
  - Chuẩn hóa thứ tự gõ Logic Chăm `normalize_unicode` (Brahmic Logical Order: Phụ âm cơ sở + Dấu phụ dưới chân/quấn RA/LA + Dấu phụ YA/WA + Nguyên âm đứng trước O/AI + Các nguyên âm phụ thuộc khác + Dấu kéo dài âm AA + Phụ âm cuối/Dấu ngắt).
86
- 3. **Sinh Dữ liệu Tổng hợp & Tăng cường**:
87
- - 150,000 dòng dữ liệu tổng hợp qua 4 gói phân tầng.
88
  - Cơ chế lọc chữ lỗi ô vuông (tofu) bằng `fontTools` cmap và làm mờ động học trong RAM (*On-the-Fly Motion Blur*).
 
 
 
89
  4. **Triển khai Trực tuyến**:
90
  - Đóng gói container Docker trên **Hugging Face Spaces** (`phucsd/cham-ocr-studio`).
91
  - Tên miền dịch vụ chính thức: **[https://ocr.cham.asia](https://ocr.cham.asia)**.
@@ -115,13 +117,13 @@ Mọi số liệu công bố đều được đối chiếu trực tiếp từ c
115
  Nếu bạn sử dụng tài liệu, bộ sinh dữ liệu hoặc công cụ Cham OCR Studio trong nghiên cứu, vui lòng trích dẫn:
116
 
117
  ```bibtex
118
- @software{cham_ocr_studio,
119
- author = {Nguyen, Phuc},
120
- title = {Cham OCR Studio: Deep Learning Pipeline and Paleographic Transcription Workbench for Historical Cham Manuscripts},
121
  year = {2026},
122
  publisher = {GitHub},
123
  url = {https://github.com/phucsd/Cham-OCR},
124
- note = {Live web service: https://ocr.cham.asia}
125
  }
126
  ```
127
 
 
83
  - Mạng nơ-ron **PP-OCRv4 SVTR-LCNet** mở rộng tensor đầu vào lên `[3, 48, 480]`.
84
  - Cơ chế giải mã kép: CTC Loss kết hợp NRTR Multi-Head Cross-Attention.
85
  - Chuẩn hóa thứ tự gõ Logic Chăm `normalize_unicode` (Brahmic Logical Order: Phụ âm cơ sở + Dấu phụ dưới chân/quấn RA/LA + Dấu phụ YA/WA + Nguyên âm đứng trước O/AI + Các nguyên âm phụ thuộc khác + Dấu kéo dài âm AA + Phụ âm cuối/Dấu ngắt).
86
+ 3. **Hệ thống Sinh Dữ liệu Tổng hợp 150.000 dòng**:
 
87
  - Cơ chế lọc chữ lỗi ô vuông (tofu) bằng `fontTools` cmap và làm mờ động học trong RAM (*On-the-Fly Motion Blur*).
88
+ - **Kiến trúc Hai Bộ Sinh Dữ liệu (Dual Generators)**:
89
+ - *Pipeline A (Bộ sinh chuẩn: `scripts/generate_data.py`)*: 150.000 dòng chia 15% kiểm thử/đóng băng và 85% huấn luyện qua 6 nhóm: ngắn sạch (25%), vừa sạch (20%), dài sạch (15%), ngắn nhiễu (15%), dài nhiễu (15%), cặp đối kháng (10%).
90
+ - *Pipeline B (Bộ sinh thực nghiệm V25: `scripts/generate_data_v25.py`)*: 150.000 dòng (140.000 train + 10.000 val) đồng bộ trực tiếp với `configs/v25_dataset_manifest.json` qua 5 trụ cột: ngữ liệu chuẩn (43,3%), chống mờ rung tay (20,0%), song ngữ kẹp dòng (16,7%), số khổ & dấu câu (12,0%), cặp đối kháng vi mô (8,0%).
91
  4. **Triển khai Trực tuyến**:
92
  - Đóng gói container Docker trên **Hugging Face Spaces** (`phucsd/cham-ocr-studio`).
93
  - Tên miền dịch vụ chính thức: **[https://ocr.cham.asia](https://ocr.cham.asia)**.
 
117
  Nếu bạn sử dụng tài liệu, bộ sinh dữ liệu hoặc công cụ Cham OCR Studio trong nghiên cứu, vui lòng trích dẫn:
118
 
119
  ```bibtex
120
+ @software{cham_ocr_pipeline,
121
+ author = {Nguyen, Phuc H.},
122
+ title = {Cham-OCR: A Deep-Learning OCR Pipeline for Unicode Eastern Cham, toward Historical Manuscript and Epigraphic Recognition},
123
  year = {2026},
124
  publisher = {GitHub},
125
  url = {https://github.com/phucsd/Cham-OCR},
126
+ note = {Production web service: https://ocr.cham.asia}
127
  }
128
  ```
129
 
RESEARCH.md CHANGED
@@ -115,7 +115,7 @@ However, pre-vowels such as `ꨯ` (Vowel Sign O, U+AA2F) and `ꨰ` (Vowel Sign A
115
  ### 2.1 Early Epigraphic & Glyph Recognition
116
  Automated processing of Cham script originated in the analysis of stone inscriptions from ancient Champa sanctuaries (Mỹ Sơn, Po Nagar, Đồng Dương). Nguyen, Schweyer, Le, Tran, & Vu (2019a) presented preliminary results on recognizing ancient Cham glyphs extracted from stone rubbings and photographs using Histogram of Oriented Gradients (HOG), Non-Parametric Weighted (NPW) features, and CNN-derived feature representations coupled with $k$-NN and linear SVM classifiers at the IEEE MAPR 2019 conference. Due to severe stone weathering, erosion, and irregular surface texture, isolated glyph classification across noisy background substrates was evaluated.
117
 
118
- To mitigate extreme sample scarcity in epigraphic corpora, Nguyen, Schweyer, Le, Tran, & Vu (2019b) evaluated data augmentation strategies and transfer learning from rendered synthetic character variants to ancient stone inscription glyphs at the ICIAP 2019 Workshops (PatReCH), demonstrating quantitative gains on synthetic-to-real transfer across weathered stone carvings.
119
 
120
  ### 2.2 Deep Learning for Cham Epigraphy & Transliteration
121
  In his doctoral dissertation, Nguyen (2023) developed comprehensive document image processing and understanding methodologies dedicated to ancient Cham documents. His work investigated stone inscription image restoration, automatic glyph segmentation under rough stone substrate conditions, and deep convolutional representations for historical Brahmic scripts.
@@ -123,7 +123,7 @@ In his doctoral dissertation, Nguyen (2023) developed comprehensive document ima
123
  Complementing glyph recognition, Nguyen, Burie, Le, & Schweyer (2022) proposed an effective multi-stage method for text line segmentation in historical document images presented at the 26th International Conference on Pattern Recognition (ICPR 2022), addressing line curvature and touching diacritics in historical archives. Expanding to linguistic representation, Nguyen, Burie, Le, & Schweyer (2023) introduced a two-step sequence transformer model for Cham-to-Latin script transliteration presented at the HIP@ICDAR 2023 workshop. Their system demonstrated the efficacy of sequence-to-sequence transformers in mapping recognized Cham text sequences to standardized Latin phonological romanizations (*Cam-Latin*). Most recently, Nguyen, Burie, Le, & Schweyer (2025) published a comprehensive study entitled *Text line segmentation approach combining deep learning model and traditional image processing techniques - application to transliteration of Cham manuscripts* in *Multimedia Tools and Applications*, evaluating text-line segmentation and automatic transliteration across 627 manuscript images and approximately 8,300 textlines (focusing on line segmentation and transliteration rather than end-to-end character OCR benchmarking).
124
 
125
  ### 2.3 Manuscript Preservation & Digitization Initiatives
126
- Beyond epigraphy on stone, historical Cham manuscripts on paper (*kertas*) and palm-leaf have been documented through preservation missions. Most notably, the *Historic Cham Manuscripts of Vietnam* collection—hosted and preserved by the Southeast Asia Digital Library (SEADL) at Northern Illinois University—preserves 977 digitized manuscripts comprising more than 57,800 high-resolution page scans. In addition, field surveys estimate approximately 3,000 physical manuscripts preserved within Cham community families and religious dignitaries across Vietnam and Cambodia. The French National Research Agency funded the CHAMDOC project (*Cham Documentation*, ANR-19-CE27-0018, 2019–2024) to support preservation, cataloging, and linguistic documentation.
127
 
128
  However, despite the preservation of tens of thousands of scanned folios, almost none of these archives possess open, machine-readable character-level or line-level bounding box ground-truth annotations suitable for training supervised deep learning models. The bottleneck for Cham OCR is therefore not an absolute absence of physical manuscripts, but rather the severe scarcity of annotated, verified training ground truth.
129
 
@@ -139,7 +139,7 @@ Prior research has focused predominantly on either **isolated glyph recognition
139
  ## 3. Text Detection & Indic Line Segmentation (Cham-DBNet)
140
 
141
  ### 3.1 The Interlinear Diacritic Collision Problem
142
- Historical Cham manuscripts frequently exhibit tight interlinear spacing (observed in synthetic and normalized rendering spaces at standard font scale as tight as 3–12 pixels between lines). Because upper dependent vowels reach high above the core text band (ascender excursions extending up to $+18 ext{px}$ at nominal 32–36px glyph heights) and subjoined medials plunge below (descender excursions down to $-22 ext{px}$), the ascenders of line $n+1$ routinely touch or intertwine with the descenders of line $n$ under dense formatting. Standard off-the-shelf text detection models (e.g., baseline DBNet trained on natural scene Latin or CJK text) frequently encounter two severe failure modes when applied to dense historical manuscripts:
143
  1. **Line Merging**: Grouping adjacent lines into a single bounding box when diacritics touch.
144
  2. **Diacritic Amputation**: Slicing off upper vowel signs or subjoined medials during rectangular cropping, which causes fatal downstream recognition errors.
145
 
@@ -211,8 +211,11 @@ def normalize_unicode(text: str) -> str:
211
  ### 5.1 The Data Scarcity Bottleneck
212
  Deep neural recognition networks require hundreds of thousands of diverse labeled textline instances. However, surviving physical Cham manuscripts lack character-level bounding box ground truth. To overcome this limitation, we engineered a synthetic generation pipeline (`scripts/generate_data.py`) capable of synthesizing 150,000 diverse textline crops from authentic literary corpora.
213
 
214
- ### 5.2 Calibrated Synthetic Dataset Partitioning
215
- The synthetic dataset generator (`scripts/generate_data.py`) partitions the target 150,000 textline synthesis corpus into **15% evaluation/locked splits** (22,500 lines: `val_clean_short` 3%, `val_clean_long` 3%, `val_noisy_short` 3%, `val_noisy_long` 3%, `locked_test` 3%) and **85% training split** (127,500 lines) structured across 6 calibrated sub-categories:
 
 
 
216
 
217
  | Training Sub-Category | Proportion | Line Count | Target Composition & Protocol | Target Failure Mode Addressed |
218
  | :--- | :---: | :---: | :--- | :--- |
@@ -223,6 +226,14 @@ The synthetic dataset generator (`scripts/generate_data.py`) partitions the targ
223
  | `train_noisy_long` | **15%** | 19,125 | Long verse lines with severe photometric degradation and spatial elastic distortion. | Prevents alignment collapse on degraded multi-word lines. |
224
  | `train_hard_examples` | **10%** | 12,750 | Adversarial minimal pairs (`ꨲ` vs `ꨶ`, `꩝꩝` vs `꩝`), verse numerals (1–99) with section marks (`꩑꩞`), and stacked subjoined clusters. | Eliminates CTC misclassification of numerals as letters (`꩔` vs `ꨤ`, `꩕` vs `ꨅ`) and diacritic collapsing. |
225
 
 
 
 
 
 
 
 
 
226
  ### 5.3 Digital Typefaces & TrueType cmap Tofu Glyph Safeguards
227
  When rendering synthetic text, digital Cham typefaces must be verified for glyph coverage. The primary bundled and reproducible typeface distributed directly within this repository is **Noto Sans Cham** (`Regular`, `Bold`, `Black`; SIL Open Font License 1.1, located in `ocr-training/data/fonts/`). External reference typefaces evaluated during research—including *Cham Roman*, *EFEO Cham*, and community fonts—are not redistributed in this repository and must be acquired independently if researchers wish to reproduce specific historical font variations.
228
 
@@ -324,9 +335,9 @@ if not hasattr(np, 'typeDict'): np.typeDict = {}
324
 
325
  While our pipeline demonstrates substantial gains on synthetic and semi-controlled documents, rigorous academic honesty requires acknowledging the following boundaries:
326
 
327
- 1. **Synthetic-to-Real Domain Gap**: The recognizer is trained predominantly on synthetic textlines rendered with available digital TrueType fonts (*Noto Sans Cham*, *Cham Roman*, *EFEO Cham*). Historical manuscripts feature idiosyncratic scribal hands, variable ink viscosity, and non-standard ligature variations that may exhibit lower recognition confidence in the field.
328
  2. **Absence of Large-Scale Real Manuscript Ground Truth**: Due to the acute scarcity of digitized historical Cham archives with line-level annotations, current benchmarks rely on controlled synthetic stress-tests and curated test slices. Establishing an open, expert-verified historical palm-leaf manuscript benchmark represents an urgent necessity requiring future collaborative scholarship with native Cham elders and linguists.
329
- 3. **Severe Biological Degradation & Epigraphy**: Inscription stone rubbings and severely mold-damaged palm leaves (where over 40% of character ink has flaked away) remain beyond the capabilities of pure vision-based CTC sequence models. Integrating masked language models (MLMs) trained on historical Cham corpora is an active subject of future study.
330
  4. **Extreme Interlinear Overlap (Level 5)**: When severe paper wrinkling causes interlinear wave amplitude to exceed line spacing ($\text{Amplitude} > \text{Gap}$), 1D projection and horizontal bounding boxes collapse, indicating the need for 2D polygonal baseline tracking models in future iterations.
331
 
332
  ---
@@ -334,13 +345,13 @@ While our pipeline demonstrates substantial gains on synthetic and semi-controll
334
  ## 9. BibTeX Citation & Academic References
335
 
336
  ```bibtex
337
- @software{cham_ocr_studio,
338
- author = {Nguyen, Phuc},
339
- title = {Cham OCR Studio: Deep Learning Pipeline and Paleographic Transcription Workbench for Historical Cham Manuscripts},
340
  year = {2026},
341
  publisher = {GitHub},
342
  url = {https://github.com/phucsd/Cham-OCR},
343
- note = {Official web service: https://ocr.cham.asia}
344
  }
345
  ```
346
 
 
115
  ### 2.1 Early Epigraphic & Glyph Recognition
116
  Automated processing of Cham script originated in the analysis of stone inscriptions from ancient Champa sanctuaries (Mỹ Sơn, Po Nagar, Đồng Dương). Nguyen, Schweyer, Le, Tran, & Vu (2019a) presented preliminary results on recognizing ancient Cham glyphs extracted from stone rubbings and photographs using Histogram of Oriented Gradients (HOG), Non-Parametric Weighted (NPW) features, and CNN-derived feature representations coupled with $k$-NN and linear SVM classifiers at the IEEE MAPR 2019 conference. Due to severe stone weathering, erosion, and irregular surface texture, isolated glyph classification across noisy background substrates was evaluated.
117
 
118
+ To mitigate extreme sample scarcity in epigraphic corpora, Nguyen, Schweyer, Le, Tran, & Vu (2019b) evaluated data augmentation strategies and transfer learning from scripts of similar or the same language family to ancient stone inscription glyphs at the ICIAP 2019 Workshops (PatReCH), demonstrating quantitative gains on script-to-inscription transfer across weathered stone carvings.
119
 
120
  ### 2.2 Deep Learning for Cham Epigraphy & Transliteration
121
  In his doctoral dissertation, Nguyen (2023) developed comprehensive document image processing and understanding methodologies dedicated to ancient Cham documents. His work investigated stone inscription image restoration, automatic glyph segmentation under rough stone substrate conditions, and deep convolutional representations for historical Brahmic scripts.
 
123
  Complementing glyph recognition, Nguyen, Burie, Le, & Schweyer (2022) proposed an effective multi-stage method for text line segmentation in historical document images presented at the 26th International Conference on Pattern Recognition (ICPR 2022), addressing line curvature and touching diacritics in historical archives. Expanding to linguistic representation, Nguyen, Burie, Le, & Schweyer (2023) introduced a two-step sequence transformer model for Cham-to-Latin script transliteration presented at the HIP@ICDAR 2023 workshop. Their system demonstrated the efficacy of sequence-to-sequence transformers in mapping recognized Cham text sequences to standardized Latin phonological romanizations (*Cam-Latin*). Most recently, Nguyen, Burie, Le, & Schweyer (2025) published a comprehensive study entitled *Text line segmentation approach combining deep learning model and traditional image processing techniques - application to transliteration of Cham manuscripts* in *Multimedia Tools and Applications*, evaluating text-line segmentation and automatic transliteration across 627 manuscript images and approximately 8,300 textlines (focusing on line segmentation and transliteration rather than end-to-end character OCR benchmarking).
124
 
125
  ### 2.3 Manuscript Preservation & Digitization Initiatives
126
+ Beyond epigraphy on stone, historical Cham manuscripts on paper (*kertas*) and palm-leaf have been documented through preservation missions. The Historic Cham Manuscripts of Vietnam collection in the Southeast Asia Digital Library (SEADL) contains 977 digitized manuscripts comprising more than 57,800 page scans. In addition, field surveys estimate approximately 3,000 physical manuscripts preserved within Cham community families and religious dignitaries across Vietnam and Cambodia. The French National Research Agency funded the CHAMDOC project (*Cham Documentation*, ANR-19-CE27-0018, 2019–2024) to support preservation, cataloging, and linguistic documentation.
127
 
128
  However, despite the preservation of tens of thousands of scanned folios, almost none of these archives possess open, machine-readable character-level or line-level bounding box ground-truth annotations suitable for training supervised deep learning models. The bottleneck for Cham OCR is therefore not an absolute absence of physical manuscripts, but rather the severe scarcity of annotated, verified training ground truth.
129
 
 
139
  ## 3. Text Detection & Indic Line Segmentation (Cham-DBNet)
140
 
141
  ### 3.1 The Interlinear Diacritic Collision Problem
142
+ In digital modeling and synthetic text rendering at nominal glyph heights (32–36px), interlinear clearance often narrows to 3–12 pixels between adjacent lines (with ascender excursions extending up to $+18\text{px}$ and descender excursions down to $-22\text{px}$ in normalized coordinate space). Because upper dependent vowels reach high above the core text band and subjoined medials plunge below, the ascenders of line $n+1$ routinely touch or intertwine with the descenders of line $n$ under dense formatting. Standard off-the-shelf text detection models (e.g., baseline DBNet trained on natural scene Latin or CJK text) frequently encounter two severe failure modes when applied to dense historical manuscripts:
143
  1. **Line Merging**: Grouping adjacent lines into a single bounding box when diacritics touch.
144
  2. **Diacritic Amputation**: Slicing off upper vowel signs or subjoined medials during rectangular cropping, which causes fatal downstream recognition errors.
145
 
 
211
  ### 5.1 The Data Scarcity Bottleneck
212
  Deep neural recognition networks require hundreds of thousands of diverse labeled textline instances. However, surviving physical Cham manuscripts lack character-level bounding box ground truth. To overcome this limitation, we engineered a synthetic generation pipeline (`scripts/generate_data.py`) capable of synthesizing 150,000 diverse textline crops from authentic literary corpora.
213
 
214
+ ### 5.2 Calibrated Synthetic Dataset Partitioning & Dual-Generator Architecture
215
+ To support both baseline reproducibility and ongoing experimental modeling, the repository provides two distinct synthetic dataset generation pipelines:
216
+
217
+ #### Pipeline A: Baseline General Generator (`scripts/generate_data.py`)
218
+ The baseline generator produces a 150,000 textline corpus partitioned into **15% evaluation/locked splits** (22,500 lines: `val_clean_short` 3%, `val_clean_long` 3%, `val_noisy_short` 3%, `val_noisy_long` 3%, `locked_test` 3%) and an **85% training split** (127,500 lines) structured across 6 calibrated sub-categories:
219
 
220
  | Training Sub-Category | Proportion | Line Count | Target Composition & Protocol | Target Failure Mode Addressed |
221
  | :--- | :---: | :---: | :--- | :--- |
 
226
  | `train_noisy_long` | **15%** | 19,125 | Long verse lines with severe photometric degradation and spatial elastic distortion. | Prevents alignment collapse on degraded multi-word lines. |
227
  | `train_hard_examples` | **10%** | 12,750 | Adversarial minimal pairs (`ꨲ` vs `ꨶ`, `꩝꩝` vs `꩝`), verse numerals (1–99) with section marks (`꩑꩞`), and stacked subjoined clusters. | Eliminates CTC misclassification of numerals as letters (`꩔` vs `ꨤ`, `꩕` vs `ꨅ`) and diacritic collapsing. |
228
 
229
+ #### Pipeline B: Experimental V25 Generator (`scripts/generate_data_v25.py`)
230
+ For the V25 experimental iteration, a dedicated multi-threaded generator (`generate_data_v25.py`) synthesizes 150,000 textline images (140,000 train + 10,000 validation) directly synchronized with `configs/v25_dataset_manifest.json` across 5 core pillars:
231
+ 1. **Canonical Cham Literature (43.3% / 65,000 lines)**: Classical literary vocabulary (*Po Klong Garai*, 57-stanza verse).
232
+ 2. **Anti-Motion Blur Base (20.0% / 30,000 lines)**: Clean base images paired with dynamic in-RAM directional motion blur ($7\times 7$ to $13\times 13$) and defocus.
233
+ 3. **Inline Bilingual Code-Switching (16.7% / 25,000 lines)**: Intra-line mixed phrases with Vietnamese diacritics and Latin annotations.
234
+ 4. **Stanza Numerals & Boundary Punctuation (12.0% / 18,000 lines)**: Verse numbers $1$–$99$ (`꩑꩞`..`꩙꩙꩞`) and boundary marks (`꩞`, `:`, `–`).
235
+ 5. **Adversarial Minimal Pairs (8.0% / 12,000 lines)**: Micro-stroke pairs (`ꨲ` U+AA32 Vowel Sign UE vs `ꨶ` U+AA36 Medial WA, variable Double Danda `꩝꩝` spacing 2–8px, 3-tier diacritic stacks).
236
+
237
  ### 5.3 Digital Typefaces & TrueType cmap Tofu Glyph Safeguards
238
  When rendering synthetic text, digital Cham typefaces must be verified for glyph coverage. The primary bundled and reproducible typeface distributed directly within this repository is **Noto Sans Cham** (`Regular`, `Bold`, `Black`; SIL Open Font License 1.1, located in `ocr-training/data/fonts/`). External reference typefaces evaluated during research—including *Cham Roman*, *EFEO Cham*, and community fonts—are not redistributed in this repository and must be acquired independently if researchers wish to reproduce specific historical font variations.
239
 
 
335
 
336
  While our pipeline demonstrates substantial gains on synthetic and semi-controlled documents, rigorous academic honesty requires acknowledging the following boundaries:
337
 
338
+ 1. **Synthetic-to-Real Domain Gap**: The neural recognizer is trained predominantly on synthetic textlines rendered with bundled *Noto Sans Cham* (SIL OFL 1.1). Historical manuscripts feature idiosyncratic scribal hands, variable ink viscosity, and non-standard ligature variations that may exhibit lower recognition confidence in the field.
339
  2. **Absence of Large-Scale Real Manuscript Ground Truth**: Due to the acute scarcity of digitized historical Cham archives with line-level annotations, current benchmarks rely on controlled synthetic stress-tests and curated test slices. Establishing an open, expert-verified historical palm-leaf manuscript benchmark represents an urgent necessity requiring future collaborative scholarship with native Cham elders and linguists.
340
+ 3. **Severe Biological Degradation & Epigraphy**: Inscription stone rubbings and severely mold-damaged palm leaves (where character ink has substantially eroded) remain beyond the capabilities of pure vision-based CTC sequence models. Integrating masked language models (MLMs) trained on historical Cham corpora is an active subject of future study.
341
  4. **Extreme Interlinear Overlap (Level 5)**: When severe paper wrinkling causes interlinear wave amplitude to exceed line spacing ($\text{Amplitude} > \text{Gap}$), 1D projection and horizontal bounding boxes collapse, indicating the need for 2D polygonal baseline tracking models in future iterations.
342
 
343
  ---
 
345
  ## 9. BibTeX Citation & Academic References
346
 
347
  ```bibtex
348
+ @software{cham_ocr_pipeline,
349
+ author = {Nguyen, Phuc H.},
350
+ title = {Cham-OCR: A Deep-Learning OCR Pipeline for Unicode Eastern Cham, toward Historical Manuscript and Epigraphic Recognition},
351
  year = {2026},
352
  publisher = {GitHub},
353
  url = {https://github.com/phucsd/Cham-OCR},
354
+ note = {Production web service: https://ocr.cham.asia}
355
  }
356
  ```
357
 
ocr-studio/research.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>End-to-End Deep Learning Pipeline & Paleographic Transcription Workbench for Historical Cham Manuscripts — Technical Report</title>
7
 
8
  <!-- Google Fonts: Google Sans Flex & JetBrains Mono -->
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
@@ -750,10 +750,10 @@
750
  <span class="tag-pill">Not Peer Reviewed</span>
751
  <span class="tag-pill tag-pill-green">PaddleOCR PP-OCRv4</span>
752
  <span class="tag-pill tag-pill-blue">Brahmic Paleography</span>
753
- <span class="tag-pill">Akhar Thrah & Cam Srak</span>
754
  </div>
755
 
756
- <h1 class="paper-title">End-to-End Deep Learning Pipeline & Paleographic Transcription Workbench for Historical Cham Manuscripts</h1>
757
 
758
  <div class="author-box">
759
  <div class="author-info">
@@ -771,7 +771,7 @@
771
  <section id="abstract" class="abstract-box">
772
  <div class="abstract-title">Abstract</div>
773
  <p class="abstract-text">
774
- Historical manuscripts of the Cham civilization—primarily preserved in Eastern Cham (<em>Akhar Thrah</em>) alongside Western Cham (<em>Cam Srak</em>) traditions—represent a fragile, millennia-old Brahmic epigraphic and literary heritage in Southeast Asia. While this project validates an end-to-end deep learning OCR pipeline specifically on Unicode Eastern Cham (<em>Akhar Thrah</em>), Western Cham (<em>Cam Srak</em>), real physical manuscripts, and epigraphic stone carvings represent active and ongoing future extensions. Automatic transcription via Optical Character Recognition (OCR) has historically faced steep barriers due to complex Brahmic abugida orthography, multi-directional dependent vowel diacritics, predominantly continuous script (<em>scriptio continua</em>) in classical manuscripts, physical carrier degradation, and tight interlinear diacritic collisions. In this technical report, we describe the design, implementation, and empirical evaluation of an end-to-end deep learning pipeline and diagnostic transcription workbench engineered for Cham documents: (1) a hybrid text detection and line segmentation engine combining a fine-tuned <strong>Mobile DBNet (PPLCNetV3)</strong> with an <strong>Indic Valley-Cut Heuristic Engine</strong> that preserves boundary strokes via mathematical set-difference masking (<code>difference_update</code>) and \(0.40 \times \text{median line height}\) vertical safety cushions; (2) a neural recognition backbone based on <strong>PP-OCRv4 SVTR-LCNet</strong> with expanded input dimensions \([3, 48, 480]\), joint Connectionist Temporal Classification (CTC) alignment, and post-OCR deterministic Unicode cluster canonicalization; and (3) a 150,000-textline synthetic dataset pipeline featuring on-the-fly motion blur, TrueType cmap tofu filtering, and hard-example adversarial mining for verse numerals (\(1\)–\(99\)) and confusing diacritic minimal pairs. Evaluations across a <strong>controlled 200-page synthetic document stress-test</strong> (903 textlines across 5 difficulty levels) and a <strong>50-test stratified failure-mode benchmark</strong> demonstrate that our line detector demonstrate robust line detection with a <strong>100.0% line detection rate</strong> across standard, aged paper, and narrow-gap folios (Levels 1–3) and graceful degradation up to Level 4 (99.44% detection), before encountering an empirical breaking point at Level 5 (61.45% detection) where interlinear wave amplitude exceeds the interlinear gap. For textline recognition, our validated baseline model (<strong>V24</strong>) achieves <strong>13.88% mean CER</strong> across the 50-test suite (reducing noise CER from 50.59% down to 8.82% over V23), while experimental checkpoints (<strong>V25</strong>) remain under active training and evaluation. Multi-GPU distributed scaling benchmarks across Kaggle Tesla T4x2, Cloud A100, L40S, and L4 characterize hardware efficiency for non-profit cultural preservation. Finally, we discuss critical limitations regarding synthetic-to-real domain gaps and provide a production reference deployed at <code>https://ocr.cham.asia</code>.
775
  </p>
776
  <div class="keywords-strip">
777
  <strong>Keywords:</strong> Optical Character Recognition (OCR), Cham Script, Akhar Thrah, Cam Srak, Brahmic Paleography, Differentiable Binarization (DBNet), SVTR-LCNet, Connectionist Temporal Classification (CTC), Indic Line Segmentation.
@@ -852,7 +852,7 @@
852
  Automated processing of Cham script originated in the analysis of stone inscriptions from ancient Champa sanctuaries (Mỹ Sơn, Po Nagar, Đồng Dương). Nguyen, Schweyer, Le, Tran, & Vu (2019a) presented preliminary results on recognizing ancient Cham glyphs extracted from stone rubbings and photographs using Histogram of Oriented Gradients (HOG), Non-Parametric Weighted (NPW) features, and CNN-derived feature representations coupled with $k$-NN and linear SVM classifiers at the IEEE MAPR 2019 conference. Due to severe stone weathering, erosion, and irregular surface texture, isolated glyph classification across noisy background substrates was evaluated.
853
  </p>
854
  <p>
855
- To mitigate extreme sample scarcity in epigraphic corpora, Nguyen, Schweyer, Le, Tran, & Vu (2019b) evaluated data augmentation strategies and transfer learning from rendered synthetic character variants to ancient stone inscription glyphs at the ICIAP 2019 Workshops (PatReCH), demonstrating quantitative gains on synthetic-to-real transfer across weathered stone carvings.
856
  </p>
857
 
858
  <h3 id="sec2-2">2.2 Deep Learning for Cham Epigraphy & Transliteration</h3>
@@ -865,7 +865,7 @@
865
 
866
  <h3 id="sec2-3">2.3 Manuscript Preservation & Digitization Initiatives</h3>
867
  <p>
868
- Beyond epigraphy on stone, historical Cham manuscripts on paper (<em>kertas</em>) and palm-leaf have been documented through preservation missions. Most notably, the <em>Historic Cham Manuscripts of Vietnam</em> collection—hosted and preserved by the Southeast Asia Digital Library (SEADL) at Northern Illinois University—preserves 977 digitized manuscripts comprising more than 57,800 high-resolution page scans. In addition, field surveys estimate approximately 3,000 physical manuscripts preserved within Cham community families and religious dignitaries across Vietnam and Cambodia. The French National Research Agency funded the CHAMDOC project (<em>Cham Documentation</em>, ANR-19-CE27-0018, 2019–2024) to support preservation, cataloging, and linguistic documentation.
869
  </p>
870
  <p>
871
  However, despite the preservation of tens of thousands of scanned folios, almost none of these archives possess open, machine-readable character-level or line-level bounding box ground-truth annotations suitable for training supervised deep learning models. The bottleneck for Cham OCR is therefore not an absolute absence of physical manuscripts, but rather the severe scarcity of annotated, verified training ground truth.
@@ -889,7 +889,7 @@
889
 
890
  <h3 id="sec3-1">3.1 The Interlinear Diacritic Collision Problem</h3>
891
  <p>
892
- Historical Cham manuscripts frequently exhibit tight interlinear spacing (observed in synthetic and normalized rendering spaces at standard font scale as tight as 3–12 pixels between lines). Because upper dependent vowels reach high above the core text band (ascender excursions extending up to \(+18 ext{px}\) at nominal 32–36px glyph heights) and subjoined medials plunge below (descender excursions down to \(-22 ext{px}\)), the ascenders of line \(n+1\) routinely touch or intertwine with the descenders of line \(n\) under dense formatting. Standard off-the-shelf text detection models (such as baseline DBNet trained on natural scene Latin or CJK text) frequently encounter two severe failure modes when applied to dense historical manuscripts:
893
  </p>
894
  <ul>
895
  <li>Merging adjacent lines into single monolithic boxes.</li>
@@ -1090,9 +1090,14 @@
1090
  Deep neural OCR networks require hundreds of thousands of diverse labeled textlines. However, surviving physical Cham manuscripts lack open character-level bounding box ground truth. To overcome this limitation, we engineered an algorithmic synthesizer (<code>scripts/generate_data.py</code>) capable of rendering 150,000 diverse textline crops from authentic literary corpora.
1091
  </p>
1092
 
1093
- <h3 id="sec5-2">5.2 Calibrated Synthetic Dataset Partitioning</h3>
1094
  <p>
1095
- The synthetic dataset generator (<code>scripts/generate_data.py</code>) partitions the target 150,000 textline synthesis corpus into <strong>15% evaluation/locked splits</strong> (22,500 lines: <code>val_clean_short</code> 3%, <code>val_clean_long</code> 3%, <code>val_noisy_short</code> 3%, <code>val_noisy_long</code> 3%, <code>locked_test</code> 3%) and <strong>85% training split</strong> (127,500 lines) structured across 6 calibrated sub-categories:
 
 
 
 
 
1096
  </p>
1097
  <div class="table-responsive">
1098
  <table>
@@ -1152,6 +1157,18 @@
1152
  </table>
1153
  </div>
1154
 
 
 
 
 
 
 
 
 
 
 
 
 
1155
  <h3 id="sec5-3">5.3 Digital Typefaces & TrueType cmap Tofu Glyph Safeguards</h3>
1156
  <p>
1157
  When rendering synthetic text, digital Cham typefaces must be verified for glyph coverage. The primary bundled and reproducible typeface distributed directly within this repository is <strong>Noto Sans Cham</strong> (<code>Regular</code>, <code>Bold</code>, <code>Black</code>; SIL Open Font License 1.1, located in <code>ocr-training/data/fonts/</code>). External reference typefaces evaluated during research—including <em>Cham Roman</em>, <em>EFEO Cham</em>, and community fonts—are not redistributed in this repository and must be acquired independently if researchers wish to reproduce specific historical font variations.
@@ -1554,9 +1571,9 @@ if not hasattr(np, 'typeDict'): np.typeDict = {}</code></pre>
1554
  While our pipeline demonstrates substantial gains on synthetic and semi-controlled documents, rigorous academic honesty requires acknowledging the following boundaries:
1555
  </p>
1556
  <ul>
1557
- <li><strong>Synthetic-to-Real Domain Gap</strong>: The recognizer is trained predominantly on synthetic textlines rendered with available digital TrueType fonts (<em>Noto Sans Cham</em>, <em>Cham Roman</em>, <em>EFEO Cham</em>). Historical manuscripts feature idiosyncratic scribal hands, variable ink viscosity, and non-standard ligature variations that may exhibit lower recognition confidence in the field.</li>
1558
  <li><strong>Absence of Large-Scale Real Manuscript Ground Truth</strong>: Due to the acute scarcity of digitized historical Cham archives with line-level annotations, current benchmarks rely on controlled synthetic stress-tests and curated test slices. Establishing an open, expert-verified historical palm-leaf manuscript benchmark represents an urgent necessity requiring future collaborative scholarship with native Cham elders and linguists.</li>
1559
- <li><strong>Severe Biological Degradation & Epigraphy</strong>: Inscription stone rubbings and severely mold-damaged palm leaves (where over 40% of character ink has flaked away) remain beyond the capabilities of pure vision-based CTC sequence models. Integrating masked language models (MLMs) trained on historical Cham corpora is an active subject of future study.</li>
1560
  <li><strong>Extreme Interlinear Overlap (Level 5)</strong>: When severe paper wrinkling causes interlinear wave amplitude to exceed line spacing (\(\text{Amplitude} > \text{Gap}\)), 1D projection and horizontal bounding boxes collapse, indicating the need for 2D polygonal baseline tracking models in future iterations.</li>
1561
  </ul>
1562
  </section>
@@ -1574,13 +1591,13 @@ if not hasattr(np, 'typeDict'): np.typeDict = {}</code></pre>
1574
  <span>BibTeX Citation</span>
1575
  <button class="copy-btn" onclick="navigator.clipboard.writeText(document.getElementById('bibtex-code').innerText)">Copy BibTeX</button>
1576
  </div>
1577
- <pre id="bibtex-code"><code>@software{cham_ocr_studio,
1578
- author = {Nguyen, Phuc},
1579
- title = {Cham OCR Studio: Deep Learning Pipeline and Paleographic Transcription Workbench for Historical Cham Manuscripts},
1580
  year = {2026},
1581
  publisher = {GitHub},
1582
  url = {https://github.com/phucsd/Cham-OCR},
1583
- note = {Official web service: https://ocr.cham.asia}
1584
  }</code></pre>
1585
  </div>
1586
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cham-OCR: A Deep-Learning OCR Pipeline for Unicode Eastern Cham, toward Historical Manuscript and Epigraphic Recognition — Technical Report</title>
7
 
8
  <!-- Google Fonts: Google Sans Flex & JetBrains Mono -->
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
 
750
  <span class="tag-pill">Not Peer Reviewed</span>
751
  <span class="tag-pill tag-pill-green">PaddleOCR PP-OCRv4</span>
752
  <span class="tag-pill tag-pill-blue">Brahmic Paleography</span>
753
+ <span class="tag-pill">Unicode Eastern Cham (Akhar Thrah)</span>
754
  </div>
755
 
756
+ <h1 class="paper-title">Cham-OCR: A Deep-Learning OCR Pipeline for Unicode Eastern Cham, toward Historical Manuscript and Epigraphic Recognition</h1>
757
 
758
  <div class="author-box">
759
  <div class="author-info">
 
771
  <section id="abstract" class="abstract-box">
772
  <div class="abstract-title">Abstract</div>
773
  <p class="abstract-text">
774
+ Historical manuscripts of the Cham civilization—primarily preserved in Eastern Cham (<em>Akhar Thrah</em>) alongside Western Cham (<em>Cam Srak</em>) traditions—represent a fragile, millennia-old Brahmic epigraphic and literary heritage in Southeast Asia. While this project validates an end-to-end deep learning OCR pipeline specifically on Unicode Eastern Cham (<em>Akhar Thrah</em>), Western Cham (<em>Cam Srak</em>), real physical manuscripts, and epigraphic stone carvings represent active and ongoing future extensions. Automatic transcription via Optical Character Recognition (OCR) has historically faced steep barriers due to complex Brahmic abugida orthography, multi-directional dependent vowel diacritics, predominantly continuous script (<em>scriptio continua</em>) in classical manuscripts, physical carrier degradation, and tight interlinear diacritic collisions. In this technical report, we describe the design, implementation, and empirical evaluation of an end-to-end deep learning pipeline and diagnostic transcription workbench engineered for Cham documents: (1) a hybrid text detection and line segmentation engine combining a fine-tuned <strong>Mobile DBNet (PPLCNetV3)</strong> with an <strong>Indic Valley-Cut Heuristic Engine</strong> that preserves boundary strokes via mathematical set-difference masking (<code>difference_update</code>) and \(0.40 \times \text{median line height}\) vertical safety cushions; (2) a neural recognition backbone based on <strong>PP-OCRv4 SVTR-LCNet</strong> with expanded input dimensions \([3, 48, 480]\), joint Connectionist Temporal Classification (CTC) alignment, and post-OCR deterministic Unicode cluster canonicalization; and (3) a 150,000-textline synthetic dataset pipeline featuring on-the-fly motion blur, TrueType cmap tofu filtering, and hard-example adversarial mining for verse numerals (\(1\)–\(99\)) and confusing diacritic minimal pairs. Evaluations across a <strong>controlled 200-page synthetic document stress-test</strong> (903 textlines across 5 difficulty levels) and a <strong>50-test stratified failure-mode benchmark</strong> demonstrate robust textline detection with a <strong>100.0% line detection rate</strong> across standard, aged paper, and narrow-gap folios (Levels 1–3) and graceful degradation up to Level 4 (99.44% detection), before encountering an empirical breaking point at Level 5 (61.45% detection) where interlinear wave amplitude exceeds the interlinear gap. For textline recognition, our validated baseline model (<strong>V24</strong>) achieves <strong>13.88% mean CER</strong> across the 50-test suite (reducing noise CER from 50.59% down to 8.82% over V23), while experimental checkpoints (<strong>V25</strong>) remain under active training and evaluation. Multi-GPU distributed scaling benchmarks across Kaggle Tesla T4x2, Cloud A100, L40S, and L4 characterize hardware efficiency for non-profit cultural preservation. Finally, we discuss critical limitations regarding synthetic-to-real domain gaps and provide a production reference deployed at <code>https://ocr.cham.asia</code>.
775
  </p>
776
  <div class="keywords-strip">
777
  <strong>Keywords:</strong> Optical Character Recognition (OCR), Cham Script, Akhar Thrah, Cam Srak, Brahmic Paleography, Differentiable Binarization (DBNet), SVTR-LCNet, Connectionist Temporal Classification (CTC), Indic Line Segmentation.
 
852
  Automated processing of Cham script originated in the analysis of stone inscriptions from ancient Champa sanctuaries (Mỹ Sơn, Po Nagar, Đồng Dương). Nguyen, Schweyer, Le, Tran, & Vu (2019a) presented preliminary results on recognizing ancient Cham glyphs extracted from stone rubbings and photographs using Histogram of Oriented Gradients (HOG), Non-Parametric Weighted (NPW) features, and CNN-derived feature representations coupled with $k$-NN and linear SVM classifiers at the IEEE MAPR 2019 conference. Due to severe stone weathering, erosion, and irregular surface texture, isolated glyph classification across noisy background substrates was evaluated.
853
  </p>
854
  <p>
855
+ To mitigate extreme sample scarcity in epigraphic corpora, Nguyen, Schweyer, Le, Tran, & Vu (2019b) evaluated data augmentation strategies and transfer learning from scripts of similar or the same language family to ancient stone inscription glyphs at the ICIAP 2019 Workshops (PatReCH), demonstrating quantitative gains on script-to-inscription transfer across weathered stone carvings.
856
  </p>
857
 
858
  <h3 id="sec2-2">2.2 Deep Learning for Cham Epigraphy & Transliteration</h3>
 
865
 
866
  <h3 id="sec2-3">2.3 Manuscript Preservation & Digitization Initiatives</h3>
867
  <p>
868
+ Beyond epigraphy on stone, historical Cham manuscripts on paper (<em>kertas</em>) and palm-leaf have been documented through preservation missions. The Historic Cham Manuscripts of Vietnam collection in the Southeast Asia Digital Library (SEADL) contains 977 digitized manuscripts comprising more than 57,800 page scans. In addition, field surveys estimate approximately 3,000 physical manuscripts preserved within Cham community families and religious dignitaries across Vietnam and Cambodia. The French National Research Agency funded the CHAMDOC project (<em>Cham Documentation</em>, ANR-19-CE27-0018, 2019–2024) to support preservation, cataloging, and linguistic documentation.
869
  </p>
870
  <p>
871
  However, despite the preservation of tens of thousands of scanned folios, almost none of these archives possess open, machine-readable character-level or line-level bounding box ground-truth annotations suitable for training supervised deep learning models. The bottleneck for Cham OCR is therefore not an absolute absence of physical manuscripts, but rather the severe scarcity of annotated, verified training ground truth.
 
889
 
890
  <h3 id="sec3-1">3.1 The Interlinear Diacritic Collision Problem</h3>
891
  <p>
892
+ In digital modeling and synthetic text rendering at nominal glyph heights (32–36px), interlinear clearance often narrows to 3–12 pixels between adjacent lines (with ascender excursions extending up to \(+18\text{px}\) and descender excursions down to \(-22\text{px}\) in normalized coordinate space). Because upper dependent vowels reach high above the core text band and subjoined medials plunge below, the ascenders of line \(n+1\) routinely touch or intertwine with the descenders of line \(n\) under dense formatting. Standard off-the-shelf text detection models (such as baseline DBNet trained on natural scene Latin or CJK text) frequently encounter two severe failure modes when applied to dense historical manuscripts:
893
  </p>
894
  <ul>
895
  <li>Merging adjacent lines into single monolithic boxes.</li>
 
1090
  Deep neural OCR networks require hundreds of thousands of diverse labeled textlines. However, surviving physical Cham manuscripts lack open character-level bounding box ground truth. To overcome this limitation, we engineered an algorithmic synthesizer (<code>scripts/generate_data.py</code>) capable of rendering 150,000 diverse textline crops from authentic literary corpora.
1091
  </p>
1092
 
1093
+ <h3 id="sec5-2">5.2 Calibrated Synthetic Dataset Partitioning & Dual-Generator Architecture</h3>
1094
  <p>
1095
+ To support both baseline reproducibility and ongoing experimental modeling, the repository provides two distinct synthetic dataset generation pipelines:
1096
+ </p>
1097
+
1098
+ <h4 style="margin-top: 1.25rem; font-size: 1.05rem; color: var(--text-primary);">Pipeline A: Baseline General Generator (<code>scripts/generate_data.py</code>)</h4>
1099
+ <p>
1100
+ The baseline generator produces a 150,000 textline corpus partitioned into <strong>15% evaluation/locked splits</strong> (22,500 lines: <code>val_clean_short</code> 3%, <code>val_clean_long</code> 3%, <code>val_noisy_short</code> 3%, <code>val_noisy_long</code> 3%, <code>locked_test</code> 3%) and an <strong>85% training split</strong> (127,500 lines) structured across 6 calibrated sub-categories:
1101
  </p>
1102
  <div class="table-responsive">
1103
  <table>
 
1157
  </table>
1158
  </div>
1159
 
1160
+ <h4 style="margin-top: 1.5rem; font-size: 1.05rem; color: var(--text-primary);">Pipeline B: Experimental V25 Generator (<code>scripts/generate_data_v25.py</code>)</h4>
1161
+ <p>
1162
+ For the V25 experimental iteration, a dedicated multi-threaded generator (<code>generate_data_v25.py</code>) synthesizes 150,000 textline images (140,000 train + 10,000 validation) directly synchronized with <code>configs/v25_dataset_manifest.json</code> across 5 core pillars:
1163
+ </p>
1164
+ <ol style="margin-bottom: 1.5rem; line-height: 1.7; color: var(--text-secondary);">
1165
+ <li><strong>Canonical Cham Literature (43.3% / 65,000 lines)</strong>: Classical literary vocabulary (<em>Po Klong Garai</em>, 57-stanza verse).</li>
1166
+ <li><strong>Anti-Motion Blur Base (20.0% / 30,000 lines)</strong>: Clean base images paired with dynamic in-RAM directional motion blur (\(7\times 7\) to \(13\times 13\)) and defocus.</li>
1167
+ <li><strong>Inline Bilingual Code-Switching (16.7% / 25,000 lines)</strong>: Intra-line mixed phrases with Vietnamese diacritics and Latin annotations.</li>
1168
+ <li><strong>Stanza Numerals & Boundary Punctuation (12.0% / 18,000 lines)</strong>: Verse numbers \(1\)–\(99\) (<span class="cham-script">꩑꩞</span>..<span class="cham-script">꩙꩙꩞</span>) and boundary marks (<span class="cham-script">꩞</span>, <code>:</code>, <code>–</code>).</li>
1169
+ <li><strong>Adversarial Minimal Pairs (8.0% / 12,000 lines)</strong>: Micro-stroke pairs (<span class="cham-script">ꨲ</span> U+AA32 Vowel Sign UE vs <span class="cham-script">ꨶ</span> U+AA36 Medial WA, variable Double Danda <span class="cham-script">꩝꩝</span> spacing 2–8px, 3-tier diacritic stacks).</li>
1170
+ </ol>
1171
+
1172
  <h3 id="sec5-3">5.3 Digital Typefaces & TrueType cmap Tofu Glyph Safeguards</h3>
1173
  <p>
1174
  When rendering synthetic text, digital Cham typefaces must be verified for glyph coverage. The primary bundled and reproducible typeface distributed directly within this repository is <strong>Noto Sans Cham</strong> (<code>Regular</code>, <code>Bold</code>, <code>Black</code>; SIL Open Font License 1.1, located in <code>ocr-training/data/fonts/</code>). External reference typefaces evaluated during research—including <em>Cham Roman</em>, <em>EFEO Cham</em>, and community fonts—are not redistributed in this repository and must be acquired independently if researchers wish to reproduce specific historical font variations.
 
1571
  While our pipeline demonstrates substantial gains on synthetic and semi-controlled documents, rigorous academic honesty requires acknowledging the following boundaries:
1572
  </p>
1573
  <ul>
1574
+ <li><strong>Synthetic-to-Real Domain Gap</strong>: The neural recognizer is trained predominantly on synthetic textlines rendered with bundled <em>Noto Sans Cham</em> (SIL OFL 1.1). Historical manuscripts feature idiosyncratic scribal hands, variable ink viscosity, and non-standard ligature variations that may exhibit lower recognition confidence in the field.</li>
1575
  <li><strong>Absence of Large-Scale Real Manuscript Ground Truth</strong>: Due to the acute scarcity of digitized historical Cham archives with line-level annotations, current benchmarks rely on controlled synthetic stress-tests and curated test slices. Establishing an open, expert-verified historical palm-leaf manuscript benchmark represents an urgent necessity requiring future collaborative scholarship with native Cham elders and linguists.</li>
1576
+ <li><strong>Severe Biological Degradation & Epigraphy</strong>: Inscription stone rubbings and severely mold-damaged palm leaves (where character ink has substantially eroded) remain beyond the capabilities of pure vision-based CTC sequence models. Integrating masked language models (MLMs) trained on historical Cham corpora is an active subject of future study.</li>
1577
  <li><strong>Extreme Interlinear Overlap (Level 5)</strong>: When severe paper wrinkling causes interlinear wave amplitude to exceed line spacing (\(\text{Amplitude} > \text{Gap}\)), 1D projection and horizontal bounding boxes collapse, indicating the need for 2D polygonal baseline tracking models in future iterations.</li>
1578
  </ul>
1579
  </section>
 
1591
  <span>BibTeX Citation</span>
1592
  <button class="copy-btn" onclick="navigator.clipboard.writeText(document.getElementById('bibtex-code').innerText)">Copy BibTeX</button>
1593
  </div>
1594
+ <pre id="bibtex-code"><code>@software{cham_ocr_pipeline,
1595
+ author = {Nguyen, Phuc H.},
1596
+ title = {Cham-OCR: A Deep-Learning OCR Pipeline for Unicode Eastern Cham, toward Historical Manuscript and Epigraphic Recognition},
1597
  year = {2026},
1598
  publisher = {GitHub},
1599
  url = {https://github.com/phucsd/Cham-OCR},
1600
+ note = {Production web service: https://ocr.cham.asia}
1601
  }</code></pre>
1602
  </div>
1603
 
ocr-training/TRAINING_ROADMAP_V25.md CHANGED
@@ -12,14 +12,14 @@ flowchart TD
12
  E1["Mờ rung tay (Motion Blur)<br/>CER vọt 38.74% (Gấp 2.7 lần)<br/>Sót 8.3% dòng chữ"]
13
  E2["Song ngữ nội dòng (Inline Mixed)<br/>CER vọt 27.84%<br/>Mô hình Chăm mù chữ Việt, Việt mù Chăm"]
14
  E3["Mất dấu ngắt câu ꩞, :, –<br/>Gây nối nhầm dòng đoạn văn<br/>Paragraph Flow F1 tụt còn 70.7%"]
15
- E4["Nhầm cặp dấu phụ chân ꨲ (Au) vs ꨶ (O)<br/>Khoảng cách Double Danda ꩝꩝"]
16
  end
17
 
18
  subgraph Solutions_V25["4 Trụ Cột Huấn Luyện Cho Mô Hình V25"]
19
  P1["Trụ cột 1: Data Augmentation Mờ Động Cực Hạn (On-the-Fly)<br/>(Directional Motion Blur 7x7 đến 13x13, Defocus trong RAM)"]
20
  P2["Trụ cột 2: Từ Điển Hợp Nhất Chăm - Việt Động Chuẩn Hóa NFC<br/>(build_dict_v25.py giữ thứ tự V24 + Preflight checks + Weight Surgery)"]
21
  P3["Trụ cột 3: Gói Dữ Liệu Đặc Trị Số Khổ & Ngắt Câu Biên<br/>(Dãy số 1-99 ꩑꩞..꩙꩙꩞, bảo tồn ꩞, :, –, padding biên)"]
22
- P4["Trụ cột 4: Cặp Đối Kháng Hard-Examples Minimal Pairs<br/>(Phân biệt ꨲ vs ꨶ, bảo tồn ꨯ, ꨯꨱ, Double Danda ꩝꩝ 2-8px)"]
23
  end
24
 
25
  E1 ==> P1
@@ -67,10 +67,10 @@ flowchart TD
67
 
68
  ### Trụ Cột 4: Cặp Đối Kháng Hard-Examples Cho Dấu Phụ Dễ Nhầm
69
  - **Thực trạng**:
70
- - Dấu `ꨲ` (Au, U+AA32) và `ꨶ` (O, U+AA36) có cấu trúc vi mô rất tương đồng.
71
  - Dấu Double Danda `꩝꩝` hay bị gộp thành Single Danda `꩝`.
72
- - Nguyên âm trước `ꨯ` (E) và `ꨯꨱ` (Au) dễ bị nuốt khi nét vẽ thanh mảnh.
73
- - **Giải pháp cho v25 (Gói 4: 20,000 dòng)**:
74
  - Sinh **mẫu cặp từ tối thiểu (Minimal Pairs)**:
75
  - `{Phụ âm} + ꨲ` đối sánh trực tiếp với `{Phụ âm} + ꨶ` (ví dụ `ꨀꨲꩆ` vs `ꨀꨶꩆ`, `ꨓꨆꨴꨲꨩ` vs `ꨓꨆꨴꨶꨩ`, `ꨚꨲ` vs `ꨚꨶ`).
76
  - Dấu Double Danda `꩝꩝` với khoảng cách thay đổi từ 2px đến 8px.
@@ -82,14 +82,15 @@ flowchart TD
82
 
83
  Theo tệp đặc tả chuẩn `configs/v25_dataset_manifest.json`:
84
 
85
- | Gói Dữ Liệu (Bucket) | Số Lượng Dòng (Train) | Tỷ Lệ | Mục Tiêu Kỹ Thuật | Đặc Điểm Augmentation |
86
  | :--- | :---: | :---: | :--- | :--- |
87
- | **Gói 1: Ngữ liệu Chăm chuẩn & Cổ tích** | 70,000 | 50.0% | Nền tảng từ vựng Chăm chuẩn xác (Po Klong Garai, sử thi, thơ 57 khổ) | Nền giấy cổ, texture, tiêu chuẩn sạch |
88
- | **Gói 2: Song ngữ nội dòng (Chăm + Việt)** | 25,000 | 17.9% | Nhận diện mượt mà Chăm kẹp Việt/Latin | Font kết hợp NotoSansCham + NotoSans |
89
- | **Gói 3: Số thứ tự khổ thơ & Dấu câu biên** | 25,000 | 17.9% | Nhận diện số 1-99, dấu `꩞`, `:`, `–` | Padding biên biến thiên 0-12px |
90
- | **Gói 4: Cặp đối kháng Hard-Examples** | 20,000 | 14.3% | Khắc phục triệt để `ꨲ` vs `ꨶ`, `꩝꩝` vs `꩝` | Minimal pairs, zoom nét chân |
 
91
  | **TỔNG CỘNG TẬP TRAIN v25** | **140,000** | **100%** | **Toàn diện mọi điều kiện thực tế** | **Ảnh gốc sinh sạch trên đĩa** |
92
- | **TẬP KIỂM THỬ (VALIDATION)** | **10,000** | — | **Đánh giá khách quan, cân đối 4 nhóm** | **Cố định hạt giống ngẫu nhiên** |
93
  | **TỔNG QUY MÔ DATASET** | **150,000** | — | **Đồng bộ duy nhất với Manifest** | **On-the-fly Blur: 35% qua RecAug** |
94
 
95
  > [!IMPORTANT]
@@ -175,9 +176,9 @@ flowchart LR
175
  ### Bước 4: Khởi Động Huấn Luyện 3 Chặng Trên Kaggle GPU T4x2
176
  - Sử dụng cấu hình [rec_cham_v25.yml](file:///e:/Phuc's%20Data/Github/Cham-OCR/ocr-training/configs/rec_cham_v25.yml).
177
  - Tự động nạp thông tin xác thực bảo mật qua [kaggle_auth.py](file:///e:/Phuc's%20Data/Github/Cham-OCR/ocr-training/scripts/kaggle_auth.py) và `.env`.
178
- - **Chặng 1** (Epoch 1 -> 14, ~7.8h): Khởi động với initial weights từ Bước 3. Xuất checkpoint `latest`.
179
- - **Chặng 2** (Epoch 15 -> 27, ~7.3h): Resume thông qua `Global.checkpoints` trỏ vào full checkpoint Chặng 1 (`.pdparams + .pdopt + .states`).
180
- - **Chặng 3** (Epoch 28 -> 40, ~7.3h): Resume từ full checkpoint Chặng 2, hoàn thành toàn bộ 40 epochs.
181
 
182
  ### Bước 5: Đóng Gói Inference & Kiểm Thử Nghiệm Thu (Non-Regression Gate)
183
  - Xuất model inference:
 
12
  E1["Mờ rung tay (Motion Blur)<br/>CER vọt 38.74% (Gấp 2.7 lần)<br/>Sót 8.3% dòng chữ"]
13
  E2["Song ngữ nội dòng (Inline Mixed)<br/>CER vọt 27.84%<br/>Mô hình Chăm mù chữ Việt, Việt mù Chăm"]
14
  E3["Mất dấu ngắt câu ꩞, :, –<br/>Gây nối nhầm dòng đoạn văn<br/>Paragraph Flow F1 tụt còn 70.7%"]
15
+ E4["Nhầm cặp dấu phụ chân ꨲ (Vowel Sign UE) vs ꨶ (Medial WA)<br/>Khoảng cách Double Danda ꩝꩝"]
16
  end
17
 
18
  subgraph Solutions_V25["4 Trụ Cột Huấn Luyện Cho Mô Hình V25"]
19
  P1["Trụ cột 1: Data Augmentation Mờ Động Cực Hạn (On-the-Fly)<br/>(Directional Motion Blur 7x7 đến 13x13, Defocus trong RAM)"]
20
  P2["Trụ cột 2: Từ Điển Hợp Nhất Chăm - Việt Động Chuẩn Hóa NFC<br/>(build_dict_v25.py giữ thứ tự V24 + Preflight checks + Weight Surgery)"]
21
  P3["Trụ cột 3: Gói Dữ Liệu Đặc Trị Số Khổ & Ngắt Câu Biên<br/>(Dãy số 1-99 ꩑꩞..꩙꩙꩞, bảo tồn ꩞, :, –, padding biên)"]
22
+ P4["Trụ cột 4: Cặp Đối Kháng Hard-Examples Minimal Pairs<br/>(Phân biệt ꨲ (Vowel Sign UE) vs ꨶ (Medial WA), bảo tồn ꨯ (Vowel Sign E), ꨯꨱ, Double Danda ꩝꩝ 2-8px)"]
23
  end
24
 
25
  E1 ==> P1
 
67
 
68
  ### Trụ Cột 4: Cặp Đối Kháng Hard-Examples Cho Dấu Phụ Dễ Nhầm
69
  - **Thực trạng**:
70
+ - Dấu `ꨲ` (Vowel Sign UE, U+AA32) và `ꨶ` (Medial WA, U+AA36) có cấu trúc vi mô rất tương đồng.
71
  - Dấu Double Danda `꩝꩝` hay bị gộp thành Single Danda `꩝`.
72
+ - Nguyên âm phụ thuộc `ꨯ` (Vowel Sign E, U+AA2F) và `ꨯꨱ` (Vowel Sign AU, U+AA2F U+AA31) dễ bị nuốt khi nét vẽ thanh mảnh.
73
+ - **Giải pháp cho v25 (Gói 5: 12,000 dòng)**:
74
  - Sinh **mẫu cặp từ tối thiểu (Minimal Pairs)**:
75
  - `{Phụ âm} + ꨲ` đối sánh trực tiếp với `{Phụ âm} + ꨶ` (ví dụ `ꨀꨲꩆ` vs `ꨀꨶꩆ`, `ꨓꨆꨴꨲꨩ` vs `ꨓꨆꨴꨶꨩ`, `ꨚꨲ` vs `ꨚꨶ`).
76
  - Dấu Double Danda `꩝꩝` với khoảng cách thay đổi từ 2px đến 8px.
 
82
 
83
  Theo tệp đặc tả chuẩn `configs/v25_dataset_manifest.json`:
84
 
85
+ | Gói Dữ Liệu (Pillar / Package) | Số Lượng Dòng (Train) | Tỷ Lệ | Mục Tiêu Kỹ Thuật | Đặc Điểm Augmentation |
86
  | :--- | :---: | :---: | :--- | :--- |
87
+ | **Gói 1: Ngữ liệu Chăm chuẩn & Văn học kinh điển** | 65,000 | 43.3% | Nền tảng từ vựng Chăm chuẩn xác (Po Klong Garai, sử thi, thơ 57 khổ) | Nền giấy cổ, texture, tiêu chuẩn sạch |
88
+ | **Gói 2: Ngữ liệu đặc trị Mờ Rung Tay (Anti-Blur Base)** | 30,000 | 20.0% | Tăng cường độ thích nghi mờ rung tay | Ảnh cơ sở sinh sạch, làm mờ on-the-fly trong RAM |
89
+ | **Gói 3: Song ngữ nội dòng (Chăm + Việt kẹp dòng)** | 25,000 | 16.7% | Nhận diện mượt mà Chăm kẹp Việt/Latin | Font kết hợp NotoSansCham + NotoSans |
90
+ | **Gói 4: Số thứ tự khổ thơ & Dấu câu biên** | 18,000 | 12.0% | Nhận diện số 1-99, dấu `꩞`, `:`, `–` | Padding biên biến thiên 0-12px |
91
+ | **Gói 5: Cặp đối kháng Hard-Examples Minimal Pairs** | 12,000 | 8.0% | Khắc phục triệt để `ꨲ` (UE) vs `ꨶ` (WA), `꩝꩝` vs `꩝` | Minimal pairs, zoom nét chân, tổ hợp 3 tầng |
92
  | **TỔNG CỘNG TẬP TRAIN v25** | **140,000** | **100%** | **Toàn diện mọi điều kiện thực tế** | **Ảnh gốc sinh sạch trên đĩa** |
93
+ | **TẬP KIỂM THỬ (VALIDATION)** | **10,000** | — | **Đánh giá khách quan, cân đối 5 nhóm** | **Cố định hạt giống ngẫu nhiên** |
94
  | **TỔNG QUY MÔ DATASET** | **150,000** | — | **Đồng bộ duy nhất với Manifest** | **On-the-fly Blur: 35% qua RecAug** |
95
 
96
  > [!IMPORTANT]
 
176
  ### Bước 4: Khởi Động Huấn Luyện 3 Chặng Trên Kaggle GPU T4x2
177
  - Sử dụng cấu hình [rec_cham_v25.yml](file:///e:/Phuc's%20Data/Github/Cham-OCR/ocr-training/configs/rec_cham_v25.yml).
178
  - Tự động nạp thông tin xác thực bảo mật qua [kaggle_auth.py](file:///e:/Phuc's%20Data/Github/Cham-OCR/ocr-training/scripts/kaggle_auth.py) và `.env`.
179
+ - **Chặng 1** (Epoch 1 -> 12, ~7.5h): Khởi động với initial weights từ Bước 3. Ngắt chặng tại epoch 12 (`stage_end_epoch: 12`), xuất checkpoint `latest` & `best_accuracy`.
180
+ - **Chặng 2** (Epoch 13 -> 22, ~6.5h): Resume thông qua `Global.checkpoints` trỏ vào full checkpoint Chặng 1 (`.pdparams + .pdopt + .states`). Ngắt chặng tại epoch 22 (`stage_end_epoch: 22`).
181
+ - **Chặng 3** (Epoch 23 -> 40, ~11.0h): Resume từ full checkpoint Chặng 2, hoàn thành toàn bộ 40 epochs, xuất model `inference` v25.
182
 
183
  ### Bước 5: Đóng Gói Inference & Kiểm Thử Nghiệm Thu (Non-Regression Gate)
184
  - Xuất model inference:
ocr-training/configs/v25_dataset_manifest.json ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "name": "Cham-OCR V25 Unified Dataset Manifest",
4
+ "version": "v25.0",
5
+ "created_at": "2026-09-11",
6
+ "description": "Source of truth thống nhất cho toàn bộ quy mô, phân bổ dữ liệu và chính sách huấn luyện V25",
7
+ "data_distribution": {
8
+ "total_samples": 150000,
9
+ "train_samples": 140000,
10
+ "val_samples": 10000,
11
+ "train_val_ratio": "93.3% / 6.7%",
12
+ "packages": [
13
+ {
14
+ "id": "pkg_01_cham_canonical",
15
+ "name": "Ngữ liệu Chăm chuẩn & Văn học kinh điển",
16
+ "count": 65000,
17
+ "percentage": "43.3%",
18
+ "description": "Nền tảng từ vựng Chăm chuẩn xác từ sử thi Po Klong Garai, thơ 57 khổ, truyền thuyết và truyện cổ tích."
19
+ },
20
+ {
21
+ "id": "pkg_02_anti_blur_base",
22
+ "name": "Ngữ liệu đặc trị Mờ Rung Tay (Anti-Blur Base)",
23
+ "count": 30000,
24
+ "percentage": "20.0%",
25
+ "description": "Ảnh cơ sở được sinh sạch, áp dụng Directional Motion Blur (7x7-13x13) và Defocus on-the-fly trong DataLoader."
26
+ },
27
+ {
28
+ "id": "pkg_03_inline_mixed_bilingual",
29
+ "name": "Song ngữ Chăm - Việt kẹp nội dòng (Inline Mixed)",
30
+ "count": 25000,
31
+ "percentage": "16.7%",
32
+ "description": "Các cấu trúc Chăm kèm chú thích dịch nghĩa tiếng Việt có dấu, ngày tháng năm và ký hiệu."
33
+ },
34
+ {
35
+ "id": "pkg_04_stanza_and_punctuation",
36
+ "name": "Số thứ tự khổ thơ Chăm 1-99 & Dấu ngắt câu biên",
37
+ "count": 18000,
38
+ "percentage": "12.0%",
39
+ "description": "Dãy số khổ thơ Chăm 1-99 (꩑꩞..꩙꩙꩞), bảo tồn dấu ngắt ꩞, dấu hai chấm :, gạch ngang – và padding biên."
40
+ },
41
+ {
42
+ "id": "pkg_05_minimal_pairs_hard",
43
+ "name": "Cặp đối kháng Hard-Examples Minimal Pairs",
44
+ "count": 12000,
45
+ "percentage": "8.0%",
46
+ "description": "Tập trung phân biệt cặp dấu chân ꨲ (Vowel Sign UE, U+AA32) vs ꨶ (Medial WA, U+AA36), khoảng cách Double Danda ꩝꩝ (2-8px), tổ hợp dấu 3 tầng."
47
+ }
48
+ ]
49
+ },
50
+ "training_policy": {
51
+ "image_shape": [3, 48, 480],
52
+ "image_shape_rationale": "Chiều cao 48px bảo vệ dấu phụ mỏng; chiều rộng 480px chống co bẹp nét chữ trên dòng song ngữ dài.",
53
+ "batch_size_per_card": 32,
54
+ "global_batch_size": 64,
55
+ "num_workers": 2,
56
+ "amp_enabled": true,
57
+ "amp_level": "O1",
58
+ "blur_augmentation": "on_the_fly",
59
+ "blur_rationale": "Tuyệt đối không sinh ảnh mờ tĩnh lưu đĩa để tránh lỗi double-blur; biến dạng mờ được sinh động ngẫu nhiên trên RAM.",
60
+ "max_text_length_policy": "P99_dynamic_scan",
61
+ "max_text_length_rationale": "Quét toàn bộ nhãn tập train/val và đặt max_text_length = P99 để không bị cắt cụt nhãn song ngữ.",
62
+ "kaggle_execution_plan": {
63
+ "timeout_limit_hours": 12.0,
64
+ "stages": [
65
+ {
66
+ "stage": 1,
67
+ "epochs": "1 -> 12",
68
+ "est_hours": 7.5,
69
+ "checkpoint_save": "save_epoch_step: 1",
70
+ "output": "ch_PP-OCRv4_rec_v25_stage1/latest"
71
+ },
72
+ {
73
+ "stage": 2,
74
+ "epochs": "13 -> 22",
75
+ "est_hours": 6.5,
76
+ "resume_from": "stage 1 latest (pdparams + pdopt + states)",
77
+ "output": "ch_PP-OCRv4_rec_v25_stage2/latest"
78
+ },
79
+ {
80
+ "stage": 3,
81
+ "epochs": "23 -> 40",
82
+ "est_hours": 11.0,
83
+ "resume_from": "stage 2 latest (pdparams + pdopt + states)",
84
+ "output": "ch_PP-OCRv4_rec_v25_final/latest"
85
+ }
86
+ ]
87
+ }
88
+ },
89
+ "acceptance_gates": {
90
+ "non_regression_clean_a4": {
91
+ "description": "Đảm bảo độ chính xác trên văn bản Chăm in chuẩn A4 không bị suy giảm khi tối ưu các miền dữ liệu mới.",
92
+ "metric": "CER",
93
+ "max_allowed_delta": "+0.50%"
94
+ },
95
+ "anti_blur_target": {
96
+ "metric": "CER on motion blur tests",
97
+ "target": "< 18.0%"
98
+ },
99
+ "mixed_bilingual_target": {
100
+ "metric": "CER on inline mixed Cham-Viet",
101
+ "target": "< 12.0%"
102
+ },
103
+ "paragraph_flow_f1": {
104
+ "metric": "Paragraph reconstruction F1",
105
+ "target": "> 88.0%"
106
+ }
107
+ }
108
+ }
ocr-training/scripts/generate_data_v25.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Master Synthetic Dataset Generator for Cham OCR Model V25.
5
+ Optimized for 32-core Data Prep CPU Machine on Lightning AI Studios.
6
+
7
+ Produces 140,000 Train + 10,000 Val (150,000 total) line images across 5 core pillars:
8
+ 1. Standard & Authentic Cham Literature (Po Klong Garai, 57-stanza poem, administration)
9
+ 2. Extreme Anti-Motion Blur & Defocus Augmentation (Kernels 7x7 to 13x13, downsampling)
10
+ 3. Intra-line Bilingual Code-Switching (Cham phrase + Vietnamese diacritics + Latin)
11
+ 4. Full Stanza Digits (1-99: ꩑꩞..꩙꩙꩞) & Boundary Punctuation (꩞, :, –, ?, !)
12
+ 5. Minimal Pairs Hard-Examples (Vowel Sign UE ꨲ U+AA32 vs Medial WA ꨶ U+AA36, ꨯꨮꨩ vs ꨯꨱ, ꩝꩝ variable gap 2-8px)
13
+ """
14
+
15
+ import os
16
+ import sys
17
+ import math
18
+ import random
19
+ import time
20
+ import json
21
+ import unicodedata
22
+ import argparse
23
+ import cv2
24
+ import numpy as np
25
+ from multiprocessing import Pool, cpu_count
26
+ from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance
27
+
28
+ # UTF-8 stdout encoding
29
+ if sys.stdout and hasattr(sys.stdout, 'reconfigure'):
30
+ sys.stdout.reconfigure(encoding='utf-8')
31
+ if sys.stderr and hasattr(sys.stderr, 'reconfigure'):
32
+ sys.stderr.reconfigure(encoding='utf-8')
33
+
34
+ # NumPy 2.x compatibility monkeypatch
35
+ if not hasattr(np, 'sctypes'):
36
+ np.sctypes = {
37
+ 'int': [np.int8, np.int16, np.int32, np.int64],
38
+ 'uint': [np.uint8, np.uint16, np.uint32, np.uint64],
39
+ 'float': [np.float16, np.float32, np.float64],
40
+ 'complex': [np.complex64, np.complex128],
41
+ 'others': [bool, object, bytes, str]
42
+ }
43
+ if not hasattr(np, 'bool'): np.bool = bool
44
+ if not hasattr(np, 'int'): np.int = int
45
+ if not hasattr(np, 'float'): np.float = float
46
+ if not hasattr(np, 'typeDict'): np.typeDict = {}
47
+
48
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
49
+ TRAINING_DIR = os.path.dirname(SCRIPT_DIR)
50
+ PROJECT_ROOT = os.path.dirname(TRAINING_DIR)
51
+
52
+ # Fonts
53
+ FONT_CHAM_REG = os.path.join(TRAINING_DIR, "data", "fonts", "NotoSansCham-Regular.ttf")
54
+ FONT_CHAM_BOLD = os.path.join(TRAINING_DIR, "data", "fonts", "NotoSansCham-Bold.ttf")
55
+
56
+ # ==============================================================================
57
+ # 1. Ký Tự & Ngữ Liệu Tiếng Chăm & Song Ngữ V25
58
+ # ==============================================================================
59
+ CHAM_CONSONANTS = list("ꨆꨇꨈꨉꨊꨋꨌꨍꨎꨏꨐꨑꨒꨓꨔꨕꨖꨗꨘꨙꨚꨛꨜꨝꨞꨟꨠꨡꨢꨣꨤꨥꨦꨧꨨꨀꨁꨂꨃꨄꨅ")
60
+ CHAM_DIGITS_LIST = list("꩐꩑꩒꩓꩔꩕꩖꩗꩘꩙")
61
+
62
+ def to_cham_num(n):
63
+ """Chuyển số nguyên n thành chuỗi chữ số Chăm"""
64
+ return "".join(CHAM_DIGITS_LIST[int(d)] for d in str(n))
65
+
66
+ # Ngữ liệu từ bài thơ 57 khổ và truyền thuyết Po Klong Garai
67
+ CHAM_VOCAB_POOL = [
68
+ "ꨛꨯꨮ", "ꨆꨵꨯꨱꩃ", "ꨈꨣꩈ", "ꨧꨯꨱꩃ", "ꨚꩆ", "ꨆꩀ", "ꨨꨤꨩ", "ꨟꨐꨪꩌ", "ꨕꨶꨩ", "ꨝꨯꨱꩍ",
69
+ "ꨝꨰ", "ꨙꨮ", "ꨎꨤꨭꩀ", "ꨟꨐꨭꩌ", "ꨀꨳꨩ", "ꨝꨵꨯꨱꩍ", "ꨝꨩ", "ꨧꨩ", "ꨝꨵꩍ", "ꨀꨇꩆ",
70
+ "ꨗꨯꨱ", "ꨝꨣꨭꨥ", "ꨟꩃ", "ꨓꨮꩊ", "ꨊꨯꨱꩀ", "ꨣꨈꨵꨰ", "ꨚꨢꨯꨱꩌ", "ꨘꩆ", "ꨤꩄ", "ꨆꨳꨮꩃ",
71
+ "ꨧꨤꨪꩍ", "ꨕꨨꨵꩀ", "ꨨꨕꨯꩌ", "ꨀꨣꩀ", "ꨗꨫ", "ꨢꨭꩅ", "ꨚꨰꩀ", "ꨝꨪ", "ꨝꩀ", "ꨆꨮꨭ",
72
+ "ꨝꨴꨬ", "ꨆꨩ", "ꨨꨲꨩ", "ꨡꨯꩍ", "ꨀꨦꨪꩅ", "ꨟꩀ", "ꨟꨰ", "ꨝꨭꩍ", "ꨕꨫ", "ꨕꨤꩌ",
73
+ "ꨅꨩ", "ꨨꨕꨳꨮꩇ", "ꨀꨗꩀ", "ꨓꨌꨯꨱꨥ", "ꨙꩃ", "ꨀꨝꨪꩍ", "ꨤꨝꨭꩀ", "ꨎꨥ", "ꨥꨮꩀ", "ꨢꨮ",
74
+ "ꨐꨭꨩ", "ꨓꨴꨩ", "ꨚꨤꨬ", "ꨂꩀ", "ꨀꨗꨯꨱꩃ", "ꨧꨝꨭꩃ", "ꨈꨮꩇ", "ꨀꨧꨪꩅ", "ꨢꨮꨭ", "ꨕꨘꩀ",
75
+ "ꨎꨮ", "ꨚꨶꨮꩄ", "ꨚꨆꨴꨲꨩ", "ꨟꨪꩆ", "ꨙꨩ", "ꨝꨳꩀ", "ꨚꨗꨶꨮꩄ", "ꨔꨮꨭ", "ꨕꨯꨱꩀ", "ꨓꨳꩆ",
76
+ "ꨀꨶꨮꩆ", "ꨓꨝꨶꨮꩆ", "ꨚꨎꨳꩀ", "ꨆꨵꨮꨭ", "ꨀꨮꩈ", "ꨧꨝꩀ", "ꨈꨗꨴꨮꩍ", "ꨚꩀ", "ꨨꨚꨭꩍ", "ꨤꨪꨎꩃ",
77
+ "ꨝꨵꨬ", "ꨣꨬ", "ꨈꨴꨮꩇ", "ꨧꩃ", "ꨠꨎꨰꩀ", "ꨚꨙꨳꩀ", "ꨤꨯꨩ", "ꨤꨮꩍ", "ꨚꨕꨬ", "ꨈꨵꨰꩍ",
78
+ "ꨀꨤꨩ", "ꨨꨤꨶꨬ", "ꨆꨭꨢꨮꨭ", "ꨤꨪꨔꨬ", "ꨨꨤꨬ", "ꨇꨪꩀ", "ꨙꨪꩍ", "ꨥꩉ", "ꨕꨴꨬ", "ꨁꨗꨩ",
79
+ "ꨈꨭꨣꩈ", "ꨤꨳꩍ", "ꨣꨭꩇ", "ꨤꨶꩀ", "ꨓꨟꨩ", "ꨓꨗꩍ", "ꨣꨪꨢꨩ", "ꨖꨪꩅ", "ꨀꨧꩉ", "ꨚꨓꨯꨱ",
80
+ "ꨇꨪꩆ", "ꨨꨶꩀ", "ꨃꨥ", "ꨧꨣꨥꨩ", "ꨚꨙꩉ", "ꨢꩍ", "ꨄꨰ", "ꨆꨯꨱꩍ", "ꨚꨓꨬ", "ꨓꨭꩍ",
81
+ "ꨓꨌꨬ", "ꨌꨪꩍ", "ꨈꩆ", "ꨆꨴꨲꩍ", "ꨓꨟꨯꨱꩃ", "ꨨꨭꨩ", "ꨗꩌ", "ꨈꩍ", "ꨆꨕꨯꨱꩍ", "ꨕꨨꨵꨮꨭ",
82
+ "ꨌꨙꨳꩀ", "ꨓꨆꨶꨯꩈ", "ꨀꨥꨰꩅ", "ꨗꨆꨓꨯꨮꨩ", "ꨀꨆꨢꨯꨮꩅ", "ꨓꨆꨴꨲꨩ", "ꨚꨟꨆꨴꨲꨩ", "ꨣꨳꨪꩌ", "ꨨꨈꨴꨮꩌ"
83
+ ]
84
+
85
+ # Minimal pairs cho Vowel Sign UE ꨲ (U+AA32) vs Consonant Sign Medial WA ꨶ (U+AA36)
86
+ MINIMAL_PAIRS_UE_WA = [
87
+ ("ꨀꨲꩆ", "ꨀꨶꩆ"),
88
+ ("ꨓꨆꨴꨲꨩ", "ꨓꨆꨴꨶꨩ"),
89
+ ("ꨚꨴꨲꨩ", "ꨚꨴꨶꨩ"),
90
+ ("ꨆꨲꩆ", "ꨆꨶꩆ"),
91
+ ("ꨟꨲꩆ", "ꨟꨶꩆ"),
92
+ ("ꨣꨲꨩ", "ꨣꨶꨩ"),
93
+ ("ꨚꨟꨆꨴꨲꨩ", "ꨚꨟꨆꨴꨶꨩ"),
94
+ ("ꨚꨲ", "ꨚꨶ"),
95
+ ("ꨆꨲ", "ꨆꨶ"),
96
+ ("ꨀꨲ", "ꨀꨶ")
97
+ ]
98
+
99
+ # Cụm dấu 3 tầng & tổ hợp nguyên âm phức tạp
100
+ COMPLEX_CLUSTERS = [
101
+ "ꨣꨳꨪꩌ", "ꨓꨳꨪꩌ", "ꨚꨳꨪꩌ", "ꨕꨨꨵꩀ", "ꨝꨪꨗꨳꨪ",
102
+ "ꨨꨕꨳꨮꩇ", "ꨞꨯꨚꨓꨪꩍ", "ꨧꨪꩆꨝꨳꨪ", "ꨆꨵꨯꨱꩃ", "ꨚꨗꨴꨯꨱꩃ",
103
+ "ꨗꨆꨓꨯꨮꨩ", "ꨚꨓꨯꨮꨩ", "ꨤꨯꨮꨩ", "ꨨꨝꨳꨯꨮꩆ", "ꨨꨈꨴꨮꩌ", "ꨣꨪꨈꨮꩌ"
104
+ ]
105
+
106
+ # Mẫu song ngữ nội dòng (Cham phrase + Viet translation/commentary)
107
+ BILINGUAL_TEMPLATES = [
108
+ ("ꨛꨯꨮ ꨆꨵꨯꨱꩃ ꨈꨣꩈ", "Vua Pô Klông Gia-rai"),
109
+ ("ꨛꨯꨮ ꨚꩆ", "Pô Păt - Po Pat"),
110
+ ("ꨚꨰꩀ ꨨꨤꨩ", "đắp bờ đập ngăn nước"),
111
+ ("ꨕꨶꨩ ꨝꨯꨱꩍ ꨝꨰ", "dẫn nguồn nước vào ruộng"),
112
+ ("ꨞꩇ ꨂꨣꩃ ꨌꩌ", "xứ sở người Chăm Panduranga"),
113
+ ("yutꨢꨮ", "bờ đập nước sông lớn"),
114
+ ("ꨁꨗꨩ ꨈꨭꨣꩈ", "Bà Thầy truyền dạy giáo lý"),
115
+ ("ꨤꨪꨔꨬ ꨓꨝꨳꩀ", "cơm nắm gói trong lá chuối"),
116
+ ("ꨝꨣꨭꨥ ꨟꩃ ꨣꨈꨵꨰ", "sau cuộc thi tài kết thúc"),
117
+ ("ꨕꨨꨵꨀ ꨨꨕꨯꩌ", "nước chảy quanh năm trên đồng"),
118
+ ("ꨎꨤꨭꩀ ꨟꨐꨭꩌ", "mở rộng hệ thống mương máng"),
119
+ ("ꨣꨈꨵꨰ ꨚꨤꨬ ꨂꩀ", "thi tài đắp đập ngăn sông"),
120
+ ("ꨓꩀ ꨕꨫ ꨚꨰꩀ ꨨꨤꨩ", "đến lúc đắp bờ đập nước"),
121
+ ("ꨝꨰ ꨘꩆ ꨀꨦꨪꩅ", "ruộng lúa tốt tươi màu mỡ"),
122
+ ("ꨣꨈꨵꨰ ꨧꨝꨭꩃ", "thi tài đắp đập đá lớn")
123
+ ]
124
+
125
+ # ==============================================================================
126
+ # 2. Bộ Sinh Nội Dung Văn Bản (V25 Text Sampler)
127
+ # ==============================================================================
128
+ def sample_v25_text(pillar_type):
129
+ """
130
+ Sinh dòng văn bản theo từng trụ cột kỹ thuật:
131
+ - pillar 1: Cham chuẩn (chuẩn bị cho Anti-Motion Blur)
132
+ - pillar 2: Song ngữ nội dòng (Cham + Viet + Latin)
133
+ - pillar 3: Số khổ thơ 1-99 & Dấu ngắt câu (꩑꩞..꩔꩓꩞, :, –, ?)
134
+ - pillar 4: Cặp đối kháng Minimal Pairs (ꨲ vs ꨶ, ꩝꩝ variable gap, ꨯꨮꨩ)
135
+ """
136
+ if pillar_type == "bilingual":
137
+ # Trụ cột 2: Song ngữ nội dòng
138
+ pair = random.choice(BILINGUAL_TEMPLATES)
139
+ style = random.choice(["bracket", "colon", "hyphen", "en_dash"])
140
+ if style == "bracket":
141
+ return f"{pair[0]} ({pair[1]})"
142
+ elif style == "colon":
143
+ return f"{pair[0]}: {pair[1]}"
144
+ elif style == "en_dash":
145
+ return f"{pair[0]} – {pair[1]}"
146
+ else:
147
+ return f"{pair[0]} - {pair[1]}"
148
+
149
+ elif pillar_type == "stanza_punct":
150
+ # Trụ cột 3: Số khổ 1-99 & Dấu ngắt câu
151
+ num_val = random.randint(1, 99)
152
+ cham_num_str = to_cham_num(num_val)
153
+ sep = random.choice(["꩞ ", "꩝ "])
154
+ words = random.sample(CHAM_VOCAB_POOL, random.randint(2, 4))
155
+ content = " ".join(words)
156
+ end_mark = random.choice(["꩞", "꩝꩝", "꩝", ":", "?", "!"])
157
+ return f"{cham_num_str}{sep}{content}{end_mark}"
158
+
159
+ elif pillar_type == "minimal_pairs":
160
+ # Trụ cột 4: Cặp đối kháng
161
+ sub = random.choice(["ue_wa", "double_danda", "complex_clusters"])
162
+ if sub == "ue_wa":
163
+ pair = random.choice(MINIMAL_PAIRS_UE_WA)
164
+ target = pair[0] if random.random() < 0.5 else pair[1]
165
+ surrounding = random.sample(CHAM_VOCAB_POOL, random.randint(2, 3))
166
+ return f"{target} " + " ".join(surrounding) + random.choice(["꩝", "꩝꩝", "꩞", ""])
167
+ elif sub == "double_danda":
168
+ words = random.sample(CHAM_VOCAB_POOL, random.randint(3, 5))
169
+ return " ".join(words) + "꩝꩝"
170
+ else:
171
+ c = random.choice(COMPLEX_CLUSTERS)
172
+ words = random.sample(CHAM_VOCAB_POOL, random.randint(2, 3))
173
+ return f"{c} " + " ".join(words) + random.choice(["꩝", "꩞", ""])
174
+
175
+ else:
176
+ # Chuẩn (sẽ áp dụng Motion Blur nếu thuộc pillar anti_blur)
177
+ words = random.sample(CHAM_VOCAB_POOL, random.randint(3, 6))
178
+ text = " ".join(words)
179
+ if random.random() < 0.5:
180
+ text += random.choice(["꩝", "꩝꩝", "꩞", ""])
181
+ return text
182
+
183
+ # ==============================================================================
184
+ # 3. Kỹ Thuật Render Ảnh & Tinh Chỉnh Nét Chữ
185
+ # ==============================================================================
186
+ def render_line_v25(text, is_bold=False, font_size=26, pad_left=12, pad_right=12, danda_gap=3):
187
+ """
188
+ Render dòng chữ Chăm / Song ngữ.
189
+ Hỗ trợ biến thiên khoảng cách Double Danda '꩝꩝' từ 2px đến 8px.
190
+ """
191
+ font_path = FONT_CHAM_BOLD if is_bold else FONT_CHAM_REG
192
+ font = ImageFont.truetype(font_path, font_size)
193
+
194
+ # Nếu có ꩝꩝ ở cuối và cần biến thiên khoảng cách
195
+ if text.endswith("꩝꩝") and danda_gap != 3:
196
+ base_text = text[:-2]
197
+ bbox_base = font.getbbox(base_text) if base_text else (0, 0, 0, font_size)
198
+ w_base = (bbox_base[2] - bbox_base[0]) if base_text else 0
199
+
200
+ bbox_danda = font.getbbox("꩝")
201
+ w_danda = bbox_danda[2] - bbox_danda[0]
202
+ h_line = max(bbox_base[3] - bbox_base[1], bbox_danda[3] - bbox_danda[1]) + 16
203
+ w_total = pad_left + w_base + (6 if base_text else 0) + w_danda + danda_gap + w_danda + pad_right
204
+
205
+ img = Image.new("RGB", (max(w_total, 60), max(h_line, 48)), (255, 255, 255))
206
+ draw = ImageDraw.Draw(img)
207
+
208
+ cur_x = pad_left
209
+ y = 8
210
+ if base_text:
211
+ draw.text((cur_x, y), base_text, fill=(20, 20, 25), font=font)
212
+ cur_x += w_base + 6
213
+ draw.text((cur_x, y), "꩝", fill=(20, 20, 25), font=font)
214
+ cur_x += w_danda + danda_gap
215
+ draw.text((cur_x, y), "꩝", fill=(20, 20, 25), font=font)
216
+ return img
217
+
218
+ # Render thông thường
219
+ bbox = font.getbbox(text)
220
+ tw = bbox[2] - bbox[0]
221
+ th = bbox[3] - bbox[1]
222
+ w = tw + pad_left + pad_right
223
+ h = max(th + 16, 48)
224
+
225
+ img = Image.new("RGB", (max(w, 60), h), (255, 255, 255))
226
+ draw = ImageDraw.Draw(img)
227
+ x = pad_left - bbox[0]
228
+ y = 8 - bbox[1]
229
+ draw.text((x, y), text, fill=(20, 20, 25), font=font)
230
+ return img
231
+
232
+ # ==============================================================================
233
+ # 4. Pipeline Biến Dạng Thực Địa (Augmentations) - Đặc Trị Motion Blur
234
+ # ==============================================================================
235
+ def apply_heavy_motion_blur(img_np, ksize=9, angle=45.0):
236
+ """Tạo ma trận directional motion blur kernel mô phỏng rung tay máy ảnh di động."""
237
+ kernel = np.zeros((ksize, ksize), dtype=np.float32)
238
+ rad = math.radians(angle)
239
+ dx = math.cos(rad)
240
+ dy = math.sin(rad)
241
+ cx, cy = ksize // 2, ksize // 2
242
+ for r in range(-ksize // 2, ksize // 2 + 1):
243
+ kx = int(round(cx + r * dx))
244
+ ky = int(round(cy + r * dy))
245
+ if 0 <= kx < ksize and 0 <= ky < ksize:
246
+ kernel[ky, kx] = 1.0
247
+ if kernel.sum() > 0:
248
+ kernel /= kernel.sum()
249
+ else:
250
+ kernel[cx, cy] = 1.0
251
+ return cv2.filter2D(img_np, -1, kernel)
252
+
253
+ def augment_image_v25(img_pil, is_motion_blur_pillar=False, bake_blur=False):
254
+ """
255
+ Áp dụng các phép biến dạng hình ảnh thực tế:
256
+ - Nếu bake_blur=False (MẶC ĐỊNH CHO V25): Không nướng (bake) motion blur nặng vào ảnh đĩa.
257
+ Mọi thao tác làm mờ được thực hiện On-the-fly trong DataLoader qua RecAug để tránh double-blur.
258
+ - Giữ biến dạng nhẹ: nhiễu hạt giấy cổ, độ tương phản và chuẩn hóa chiều cao 48px.
259
+ """
260
+ img_bgr = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
261
+ h, w = img_bgr.shape[:2]
262
+
263
+ if bake_blur and is_motion_blur_pillar:
264
+ # Chỉ áp dụng nếu người dùng cố ý bật bake_blur
265
+ ksize = random.choice([7, 9, 11, 13])
266
+ angle = random.uniform(0.0, 180.0)
267
+ img_bgr = apply_heavy_motion_blur(img_bgr, ksize=ksize, angle=angle)
268
+ if random.random() < 0.4:
269
+ scale = random.uniform(0.5, 0.75)
270
+ small = cv2.resize(img_bgr, (max(int(w * scale), 20), max(int(h * scale), 16)), interpolation=cv2.INTER_LINEAR)
271
+ img_bgr = cv2.resize(small, (w, h), interpolation=cv2.INTER_CUBIC)
272
+
273
+ # Thêm nhiễu nền giấy cổ nhẹ
274
+ if random.random() < 0.6:
275
+ noise = np.random.normal(0, random.uniform(1.0, 3.5), (h, w, 3)).astype(np.float32)
276
+ img_bgr = np.clip(img_bgr.astype(np.float32) + noise, 0, 255).astype(np.uint8)
277
+
278
+ # Thay đổi độ tương phản và độ sáng
279
+ if random.random() < 0.5:
280
+ alpha = random.uniform(0.85, 1.15)
281
+ beta = random.uniform(-10, 10)
282
+ img_bgr = np.clip(img_bgr.astype(np.float32) * alpha + beta, 0, 255).astype(np.uint8)
283
+
284
+ # Đảm bảo resize về chuẩn height 48px cho V25
285
+ target_h = 48
286
+ scale = target_h / float(h)
287
+ new_w = max(int(round(w * scale)), 32)
288
+ img_resized = cv2.resize(img_bgr, (new_w, target_h), interpolation=cv2.INTER_CUBIC)
289
+ return img_resized
290
+
291
+ # ==============================================================================
292
+ # 5. Hàm Worker Đa Tiến Trình (Multiprocessing Task)
293
+ # ==============================================================================
294
+ def generate_sample_worker(args):
295
+ """Worker tạo một mẫu ảnh và nhãn."""
296
+ sample_idx, pillar_type, out_img_path, bake_blur = args
297
+
298
+ # 1. Sinh văn bản và chuẩn hóa tuyệt đối Unicode NFC
299
+ raw_text = sample_v25_text(pillar_type)
300
+ text = unicodedata.normalize('NFC', raw_text)
301
+
302
+ # 2. Render ảnh
303
+ is_bold = (random.random() < 0.35)
304
+ pad_l = random.randint(2, 14)
305
+ pad_r = random.randint(2, 14)
306
+ danda_gap = random.randint(2, 8) if "꩝꩝" in text else 3
307
+
308
+ img_pil = render_line_v25(text, is_bold=is_bold, pad_left=pad_l, pad_right=pad_r, danda_gap=danda_gap)
309
+
310
+ # 3. Biến dạng (Augmentation sạch cho lưu trữ đĩa)
311
+ is_blur_pillar = (pillar_type == "anti_blur")
312
+ img_final = augment_image_v25(img_pil, is_motion_blur_pillar=is_blur_pillar, bake_blur=bake_blur)
313
+
314
+ # 4. Ghi file ảnh
315
+ cv2.imwrite(out_img_path, img_final)
316
+
317
+ # 5. Trả về tên file tương đối và nhãn
318
+ rel_path = os.path.basename(os.path.dirname(out_img_path)) + "/" + os.path.basename(out_img_path)
319
+ return f"{rel_path}\t{text}\n"
320
+
321
+ # ==============================================================================
322
+ # 6. Điều Phối Huấn Luyện Toàn Cục (Master Generator Loop)
323
+ # ==============================================================================
324
+ def main():
325
+ default_manifest = os.path.join(TRAINING_DIR, "configs", "v25_dataset_manifest.json")
326
+ parser = argparse.ArgumentParser(description="Generate Cham OCR V25 Synthetic Dataset (Manifest-Aligned)")
327
+ parser.add_argument("--manifest", type=str, default=default_manifest, help="Đường dẫn file manifest JSON chuẩn")
328
+ parser.add_argument("--output_dir", type=str, default="./data/cham_synthetic_v25", help="Thư mục xuất dữ liệu")
329
+ parser.add_argument("--num_train", type=int, default=None, help="Số mẫu Train (mặc định lấy từ manifest: 140,000)")
330
+ parser.add_argument("--num_val", type=int, default=None, help="Số mẫu Val (mặc định lấy từ manifest: 10,000)")
331
+ parser.add_argument("--bake_blur", action="store_true", default=False, help="Bake mờ nặng vào ảnh đĩa (mặc định False: on-the-fly)")
332
+ parser.add_argument("--workers", type=int, default=None, help="Số tiến trình CPU song song (mặc định cpu_count)")
333
+ args = parser.parse_args()
334
+
335
+ # Nạp cấu hình từ manifest nếu có
336
+ num_train = args.num_train
337
+ num_val = args.num_val
338
+ pillars = ["standard", "anti_blur", "bilingual", "stanza_punct", "minimal_pairs"]
339
+ weights = [0.433, 0.200, 0.167, 0.120, 0.080] # Tương ứng 65k, 30k, 25k, 18k, 12k
340
+
341
+ if os.path.exists(args.manifest):
342
+ try:
343
+ with open(args.manifest, "r", encoding="utf-8") as f:
344
+ manifest_data = json.load(f)
345
+ dist = manifest_data.get("data_distribution", {})
346
+ if num_train is None:
347
+ num_train = dist.get("train_samples", 140000)
348
+ if num_val is None:
349
+ num_val = dist.get("val_samples", 10000)
350
+ print(f"📖 Đã nạp cấu hình phân bổ dữ liệu từ Manifest: {args.manifest}")
351
+ except Exception as e:
352
+ print(f"⚠️ Không đọc được manifest ({e}), dùng thông số mặc định.")
353
+
354
+ if num_train is None: num_train = 140000
355
+ if num_val is None: num_val = 10000
356
+
357
+ num_workers = args.workers or cpu_count()
358
+ print("=" * 75)
359
+ print("🚀 MASTER SYNTHETIC DATASET GENERATOR V25 (MANIFEST SYNCHRONIZED)")
360
+ print(f" • Số CPU Workers song song : {num_workers} tiến trình")
361
+ print(f" • Quy mô mẫu Huấn luyện : {num_train:,} ảnh")
362
+ print(f" • Quy mô mẫu Kiểm thử : {num_val:,} ảnh")
363
+ print(f" • Tổng quy mô dữ liệu : {num_train + num_val:,} ảnh")
364
+ print(f" • Chính sách Mờ Động Học : {'Bake tĩnh trên đĩa' if args.bake_blur else 'On-The-Fly trong DataLoader (Khuyến nghị)'}")
365
+ print(f" • Thư mục đầu ra : {args.output_dir}")
366
+ print("=" * 75)
367
+
368
+ train_img_dir = os.path.join(args.output_dir, "train_images")
369
+ val_img_dir = os.path.join(args.output_dir, "val_images")
370
+ os.makedirs(train_img_dir, exist_ok=True)
371
+ os.makedirs(val_img_dir, exist_ok=True)
372
+
373
+ # Chuẩn bị danh sách tham số cho Train
374
+ print(f"📦 Chuẩn bị danh mục tác vụ cho {num_train:,} mẫu Train...")
375
+ train_tasks = []
376
+ for i in range(num_train):
377
+ p_type = random.choices(pillars, weights=weights)[0]
378
+ img_name = f"train_{i+1:07d}.png"
379
+ out_path = os.path.join(train_img_dir, img_name)
380
+ train_tasks.append((i, p_type, out_path, args.bake_blur))
381
+
382
+ # Chuẩn bị danh sách tham số cho Val
383
+ print(f"📦 Chuẩn bị danh mục tác vụ cho {num_val:,} mẫu Val...")
384
+ val_tasks = []
385
+ for i in range(num_val):
386
+ p_type = random.choices(pillars, weights=weights)[0]
387
+ img_name = f"val_{i+1:06d}.png"
388
+ out_path = os.path.join(val_img_dir, img_name)
389
+ val_tasks.append((i, p_type, out_path, args.bake_blur))
390
+
391
+ t_start = time.time()
392
+
393
+ # 1. Sinh tập Train với đa tiến trình
394
+ print(f"\n⚡ Đang sinh tập Train ({num_train:,} ảnh) trên {num_workers} CPU cores...")
395
+ train_label_path = os.path.join(args.output_dir, "train_label.txt")
396
+ with open(train_label_path, "w", encoding="utf-8") as f_train:
397
+ with Pool(processes=num_workers) as pool:
398
+ chunksize = 250
399
+ completed = 0
400
+ for label_line in pool.imap_unordered(generate_sample_worker, train_tasks, chunksize=chunksize):
401
+ f_train.write(label_line)
402
+ completed += 1
403
+ if completed % 25000 == 0 or completed == num_train:
404
+ elapsed = time.time() - t_start
405
+ fps = completed / max(elapsed, 1e-5)
406
+ print(f" [Train Progress] {completed:,} / {num_train:,} ảnh ({completed/num_train*100:.1f}%) - Tốc độ: {fps:.1f} ảnh/s")
407
+
408
+ t_train_done = time.time()
409
+ print(f"✅ Hoàn thành tập Train trong {t_train_done - t_start:.2f}s (~{(t_train_done - t_start)/60:.1f} phút)")
410
+
411
+ # 2. Sinh tập Val với đa tiến trình
412
+ if num_val > 0:
413
+ print(f"\n⚡ Đang sinh tập Val ({num_val:,} ảnh) trên {num_workers} CPU cores...")
414
+ val_label_path = os.path.join(args.output_dir, "val_label.txt")
415
+ with open(val_label_path, "w", encoding="utf-8") as f_val:
416
+ with Pool(processes=num_workers) as pool:
417
+ chunksize = 250
418
+ completed = 0
419
+ for label_line in pool.imap_unordered(generate_sample_worker, val_tasks, chunksize=chunksize):
420
+ f_val.write(label_line)
421
+ completed += 1
422
+ if completed % 5000 == 0 or completed == num_val:
423
+ print(f" [Val Progress] {completed:,} / {num_val:,} ảnh ({completed/num_val*100:.1f}%)")
424
+ else:
425
+ val_label_path = os.path.join(args.output_dir, "val_label.txt")
426
+ print(f"\n❄️ Bỏ qua sinh tập Val (num_val = 0). Giữ nguyên tập Val đóng băng tại: {val_label_path}")
427
+
428
+ total_time = time.time() - t_start
429
+ print("=" * 75)
430
+ print(f"🎉 HOÀN TẤT SINH TOÀN BỘ BỘ DỮ LIỆU V25 ({num_train + num_val:,} ẢNH)!")
431
+ print(f" • Tổng thời gian thực thi: {total_time:.2f}s (~{total_time/60:.2f} phút)")
432
+ print(f" • Tệp nhãn Train : {train_label_path}")
433
+ print(f" • Tệp nhãn Val : {val_label_path}")
434
+ print("=" * 75)
435
+
436
+ if __name__ == "__main__":
437
+ main()