This is the official implementation of the paper:
Blame Errant Words, Not the Canvas: Defect-Aware Spatial Credit Assignment for Visual Text Generation
Sujie Hu1,6, Huaisong Zhang2,6, Pujing Yang3,6, Qian Wang4,6, Xiaomin Li5,6, Xinhang Leng6,†, Chen Li6, Jing Lyu6, Xiu Li2, Ling Pan1,*
1The Hong Kong University of Science and Technology 2Tsinghua University 3Zhejiang University 4Peking University 5Dalian University of Technology 6Weixin, Tencent
†Project lead *Corresponding author
- [2026/10/27] Code, the TextDART-8B checkpoint, the TextDART-123K dataset and PolyText-Bench have been released. 🚀
- [2026/10/14] Paper released on arXiv. 🎉
Detailed interactive demo is on the project page.
- Overview
- Recognition Results
- Qualitative Results
- Quantitative Results
- Repository Layout
- Part I: Reward Model (TextDART)
- Part II: Generative-Model RL (SCANNER)
- Evaluation
- Acknowledgements
- Citation
Reinforcement learning has improved visual text generation, but generated images still contain wrong words and malformed glyphs. Two coupled problems cause this. First, recognition-based rewards tend to overlook visible glyph defects. Second, a single whole-image score hides how unevenly the individual text regions are rendered. Our principle is to blame errant words, not the canvas: keep the quality differences between regions intact, from evaluation all the way to policy optimization.
- 🎯 TextDART (Defect-Aware Region-Targeted rewards) localizes every text region as a quadrilateral and transcribes it, marking malformed glyphs with
<#>/<###>/<#####>tokens. Each region then gets its own semantic-correctness and glyph-quality scores. - 🔍 SCANNER (Spatial Credit AssigNment with NoisE-awaReness) turns these regional judgments into spatially differentiated policy feedback, in three steps:
- It normalizes each text target's reward across the sampled images of a prompt (target-group advantages) and projects the resulting advantages onto that target's latent tokens.
- It blends the global and regional advantages along the denoising trajectory, so feedback moves from global guidance at high noise to local feedback at low noise.
- It pools policy ratios over each target's token support, so regional differences survive the optimization step.
PP-OCRv5, TextPecker and TextDART on five challenging scenes. TextDART localizes every text region and marks malformed glyphs with <#>/<###>/<#####>. Interactive overlays are on the project page.
The backbone, PP-OCRv5 + Flow-GRPO, TextPecker + Flow-GRPO and TextDART + SCANNER, on Qwen-Image-2512 (top) and FLUX.1-dev (bottom). Circles highlight rendering errors.
Visual text generation across five benchmarks: English results for FLUX.1-dev, and English and Chinese results for Qwen-Image-2512. ↑ Higher is better. ↓ Lower is better. Bold is the best within each backbone and language. “—” denotes no reward / no RL, or an unsupported metric.
FLUX.1-dev · English
| Training | LongText-Bench | OneIG-Bench | CVTG-2K | GenTextEval | PolyText-Bench | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Reward | RL algorithm | TextScore ↑ | TextScore ↑ | ED ↓ | CR ↑ | WAC ↑ | WordAcc ↑ | NED ↑ | Qua ↑ | Sem ↑ | CS ↑ | CG ↑ | OG ↑ |
| — | — | 0.620 | 0.543 | 85.31 | 0.138 | 0.379 | 0.503 | 0.694 | 0.661 | 0.352 | 0.426 | 0.464 | 0.281 |
| OCRv5 | Flow-GRPO | 0.769 | 0.751 | 64.55 | 0.220 | 0.506 | 0.686 | 0.818 | 0.838 | 0.589 | 0.703 | 0.726 | 0.365 |
| TextPecker | Flow-GRPO | 0.801 | 0.770 | 63.19 | 0.221 | 0.533 | 0.772 | 0.879 | 0.836 | 0.582 | 0.789 | 0.809 | 0.405 |
| TextDART | SCANNER | 0.827 | 0.858 | 51.06 | 0.309 | 0.598 | 0.834 | 0.914 | 0.839 | 0.613 | 0.882 | 0.905 | 0.653 |
Qwen-Image-2512 · English
| Training | LongText-Bench | OneIG-Bench | CVTG-2K | GenTextEval | PolyText-Bench | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Reward | RL algorithm | TextScore ↑ | TextScore ↑ | ED ↓ | CR ↑ | WAC ↑ | WordAcc ↑ | NED ↑ | Qua ↑ | Sem ↑ | CS ↑ | CG ↑ | OG ↑ |
| — | — | 0.952 | 0.986 | 24.54 | 0.461 | 0.895 | 0.844 | 0.922 | 0.944 | 0.787 | 0.887 | 0.899 | 0.252 |
| OCRv5 | Flow-GRPO | 0.947 | 0.989 | 19.58 | 0.509 | 0.890 | 0.832 | 0.914 | 0.946 | 0.795 | 0.876 | 0.876 | 0.273 |
| TextPecker | Flow-GRPO | 0.960 | 0.988 | 22.14 | 0.484 | 0.891 | 0.870 | 0.936 | 0.956 | 0.801 | 0.908 | 0.918 | 0.269 |
| TextDART | SCANNER | 0.972 | 0.995 | 13.44 | 0.585 | 0.919 | 0.878 | 0.939 | 0.970 | 0.844 | 0.953 | 0.970 | 0.496 |
Qwen-Image-2512 · Chinese
| Training | LongText-Bench | OneIG-Bench | CVTG-2K | GenTextEval | PolyText-Bench | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Reward | RL algorithm | TextScore ↑ | TextScore ↑ | ED ↓ | CR ↑ | WAC ↑ | WordAcc ↑ | NED ↑ | Qua ↑ | Sem ↑ | CS ↑ | CG ↑ | OG ↑ |
| — | — | 0.966 | 0.983 | 13.06 | 0.554 | 0.851 | — | — | 0.955 | 0.853 | 0.931 | 0.945 | 0.198 |
| OCRv5 | Flow-GRPO | 0.965 | 0.986 | 10.88 | 0.584 | 0.850 | — | — | 0.959 | 0.855 | 0.931 | 0.935 | 0.224 |
| TextPecker | Flow-GRPO | 0.974 | 0.985 | 11.83 | 0.574 | 0.853 | — | — | 0.962 | 0.866 | 0.937 | 0.945 | 0.190 |
| TextDART | SCANNER | 0.980 | 0.992 | 8.56 | 0.691 | 0.856 | — | — | 0.975 | 0.892 | 0.956 | 0.970 | 0.403 |
TextDART_SCANNER/
├── TextDART/ # Part I: reward-model training, serving and demo (ms-swift + vLLM)
├── src/ # Part II: RL framework with the SCANNER trainer
├── examples/grpo/lora/ # SCANNER configs for FLUX.1-dev and Qwen-Image-2512
├── multinode_examples/ # 4-node x 8-GPU launchers for those configs
├── dataset/ # RL prompts with per-prompt target text lists
├── config/ # accelerate / DeepSpeed configs
└── eval/ # five-benchmark evaluation
The two parts are connected by a single interface: the TextDART model from Part I is deployed as an OpenAI-compatible server, and the textdart reward in Part II queries it during RL.
| Resource | Hugging Face |
|---|---|
| Model | SujieHu/TextDART-8B-Qwen3VL |
| Training data | SujieHu/TextDART-123K |
| Base model | Qwen/Qwen3-VL-8B-Instruct |
TextDART receives only the image and answers with every text region it finds:
{"regions": [{"quad": [x1, y1, x2, y2, x3, y3, x4, y4], "recognized_text": "recognized text"}]}Quads are on a 0-1000 grid normalized to the image size. <#>, <###> and <#####> mark a malformed character, word and text block.
Start from the TextDART_SCANNER project root. Run the remaining commands in
Part I from TextDART/train.
cd TextDART/train
conda create -n TextDART python=3.11 -y
conda activate TextDART
bash install_all.sh# bash deploy_server.sh [MODEL] [PORT] [GPUS]; each listed GPU runs a full replica
bash deploy_server.sh SujieHu/TextDART-8B-Qwen3VL 1925 0,1,2,3,4,5,6,7
# in another shell, activate TextDART and enter TextDART/train before running the demo
python infer_demo.py --server-url http://127.0.0.1:1925127.0.0.1 is only for a client on the same machine as the server. If the server runs on another machine, replace it with that machine's IP. For example the machine IP is 10.0.0.1, then the server address should be http://10.0.0.1:1925.
The demo writes TextDART/demo_results/results.json and an annotated <image>_vis.png per image.
The SFT launcher requires FlashAttention and a CUDA development toolkit with nvcc.
See the training guide for installation and dataset preparation.
INSTALL_FLASH_ATTN=1 bash install_all.sh
bash ../scripts/download_dataset.sh # SujieHu/TextDART-123K -> TextDART/data/TextDART-123K
bash prepare_dataset.sh # parquet -> ms-swift cached dataset (CPU only)
# 4 nodes x 8 GPUs, run on every node
NNODES=4 NODE_RANK=<0..3> MASTER_ADDR=<node0 ip> MASTER_PORT=29500 bash train_textdart_qwen3vl.shSee TextDART/train/README.md for multi-machine dataset preparation and all options.
Run the following commands from the project root (TextDART_SCANNER):
conda create -n scanner python=3.12 -y
conda activate scanner
pip install -c constraints-scanner.txt -e ".[deepspeed,wandb]"
pip checkThe RL configs talk to a TextDART OpenAI-compatible server (start it with TextDART/train/deploy_server.sh; see Part I.2). Several servers can be listed; ranks are spread across them. Replace the <TODO: TEXTDART_SERVER_HOST> placeholders in the config you plan to run.
Both configs were tuned on 4 nodes x 8 GPUs.
The evaluation pipeline supports LongText, OneIG-Bench, CVTG-2K, GenTextEval and PolyText-Bench. Follow the evaluation guide to install the environments, download benchmark tools and data, configure model paths, and start the scoring servers.
After setup, run from the TextDART_SCANNER project root:
conda activate textdart_eval
cd eval
bash scripts/gen.sh # generate images for every benchmark
bash scripts/eval_metrics.sh # score (needs the TextPecker / TextDART servers)
bash scripts/cal_metrics.sh # summary table -> eval/metric_summary.mdGenTextEval and PolyText-Bench are included in the repository. The setup
command bash eval/setup_third_party.sh all downloads official LongText,
OneIG-Bench and CVTG-2K tools at fixed commits.
The bundled PolyText-Bench workflow uses three entry points:
generate.py for image generation, evaluate.py for resumable TextDART
scoring, and summarize.py for the final CS/CG/OG report. The released
TextDART checkpoint
can be downloaded directly from Hugging Face.
This repository is built on the following repositories, and we thank their authors:
- Flow-Factory: the RL framework that SCANNER extends.
- TextPecker: the structural-anomaly marker scheme and text scoring that TextDART builds on, and the GenTextEval benchmark.
We also rely on ms-swift, vLLM, diffusers, accelerate and peft. See THIRD_PARTY_NOTICES.md for bundled components, separately licensed datasets, and optional evaluation code downloaded from upstream.
If you find this work useful, please cite:
@article{hu2026textdartscanner,
title={Blame Errant Words, Not the Canvas: Defect-Aware Spatial Credit Assignment for Visual Text Generation},
author={Hu, Sujie and Zhang, Huaisong and Yang, Pujing and Wang, Qian and Li, Xiaomin and Leng, Xinhang and Li, Chen and Lyu, Jing and Li, Xiu and Pan, Ling},
journal={arXiv preprint arXiv:2610.20794},
year={2026}
}


