Downloads · 30 days
0
AMEND09/calligraphy-repair-cgan
calligraphy-repair-cgan is a machine learning model from AMEND09. Use it for the machine learning task on the model card, and read the license before you ship it in a product.
A two-stage system for repairing damaged handwriting and calligraphy using combined pathfinding + conditional GAN approach.
Downloads · 30 days
0
Access
Public
Updated Apr 24, 2026
Repo size
—
Likes
0
Public
Click a slice to open those files.
.py133 KB · 91%
From the Hugging Face model README
A two-stage system for repairing damaged handwriting and calligraphy using combined pathfinding + conditional GAN approach.
Stage 1 uses a deterministic A*/Bezier pathfinding algorithm to repair large structural gaps in strokes. Stage 2 uses a conditional GAN to re-apply the artistic style, producing visually coherent results.
┌─────────────────────────────────────────────────────────────┐
│ INPUT: Damaged Calligraphy │
│ (RGB, H×W×3) │
└────────────────────────┬────────────────────────────────────┘
│
┌───────────────▼───────────────┐
│ STAGE 1: PATHFINDING │
│ (Deterministic Algorithm) │
│ │
│ 1. Binarize → ink mask │
│ 2. Skeletonize (Zhang-Suen) │
│ 3. Find gap endpoints │
│ 4. A*/Bezier gap bridging │
│ 5. Variable-width rendering │
└───────┬───────────┬───────────┘
│ │
stroke_mask gap_mask
(1,H,W) (1,H,W)
│ │
┌───────▼───────────▼───────────┐
│ STAGE 2: cGAN REFINEMENT │
│ (Learned Style Transfer) │
│ │
│ Generator Input (5ch): │
│ [damaged_RGB(3) + │
│ stroke_mask(1) + │
│ gap_mask(1)] │
│ │
│ ┌─────────────────────────┐ │
│ │ U-Net Generator │ │
│ │ + Gated Convolutions │ │
│ │ + 8 Dilated ResBlocks │ │
│ │ + Skip Connections │ │
│ └─────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ │
│ │ 70×70 SN-PatchGAN │ │
│ │ Discriminator │ │
│ │ (Spectral Normalized) │ │
│ └─────────────────────────┘ │
└───────────────┬───────────────┘
│
┌───────────────▼───────────────┐
│ OUTPUT: Repaired Image │
│ (RGB, H×W×3) │
└───────────────────────────────┘
pip install -r requirements.txt
# Generate 5000 synthetic training pairs
python damage_generator.py \
--output_dir data \
--num_train 5000 \
--num_val 500 \
--image_size 256
# Or use your own clean calligraphy images:
python damage_generator.py \
--output_dir data \
--source_dir /path/to/clean/calligraphy/ \
--num_train 5000
This creates:
data/
├── train/
│ ├── clean/ # Ground truth images
│ ├── damaged/ # Synthetically damaged images
│ └── mask/ # Damage location masks
└── val/
├── clean/
├── damaged/
└── mask/
# Full training (recommended)
python train.py \
--data_dir data \
--epochs 200 \
--batch_size 8 \
--image_size 256 \
--generator_type unet \
--gan_type lsgan \
--lr_g 1e-4 \
--lr_d 1e-4
# Quick test run
python train.py \
--data_dir data \
--epochs 5 \
--batch_size 4 \
--generate_data \
--num_train 100 \
--num_val 20
# With on-the-fly damage generation (only needs clean/ directory)
python train.py \
--data_dir data \
--on_the_fly_damage \
--epochs 200
# Resume from checkpoint
python train.py \
--data_dir data \
--resume checkpoints/best_model.pth \
--epochs 300
# With trained GAN (best quality)
python inference.py \
--input damaged_calligraphy.png \
--output repaired.png \
--checkpoint checkpoints/generator_best.pth
# Pathfinding only (no GAN needed, instant)
python inference.py \
--input damaged_calligraphy.png \
--output repaired.png \
--pathfinding_only
# Batch repair a directory
python inference.py \
--input_dir damaged_images/ \
--output_dir repaired_images/ \
--checkpoint checkpoints/generator_best.pth
# Save all intermediate stages for visualization
python inference.py \
--input damaged_calligraphy.png \
--output repaired.png \
--checkpoint checkpoints/generator_best.pth \
--save_stages
Based on literature from EdgeConnect, pix2pix, DE-GAN, and pix2pixHD:
| Parameter | Value | Source |
|---|---|---|
| Optimizer | Adam(β1=0.0, β2=0.9) | EdgeConnect |
| Learning Rate | 1e-4 (G and D) | EdgeConnect |
| LR Schedule | Constant first half, linear decay second half | pix2pix |
| GAN Type | LSGAN (MSE loss) | pix2pixHD |
| λ_adversarial | 1.0 | EdgeConnect |
| λ_L1 | 100.0 | pix2pix |
| λ_feature_matching | 10.0 | pix2pixHD |
| λ_perceptual | 0.1 | EdgeConnect |
| λ_style | 250.0 | EdgeConnect |
| λ_masked_L1 | 50.0 | DiffHDR |
| Image Size | 256×256 | Standard |
| Batch Size | 8 | EdgeConnect |
| Epochs | 200 | pix2pix |
| Weight Init | Gaussian(0, 0.02) | pix2pix |
The combined generator loss:
L_G = λ_adv · L_adversarial (fool the discriminator)
+ λ_L1 · L_L1 (pixel-level reconstruction)
+ λ_FM · L_feature_matching (stabilize training via D features)
+ λ_perc · L_perceptual (VGG relu1_2, relu2_2, relu3_3, relu4_3)
+ λ_style · L_style (Gram matrices for texture matching)
+ λ_mask · L_masked_L1 (focus on damaged regions)
The discriminator loss:
L_D = L_adversarial_D + λ_R1 · L_R1_regularization
Encoder:
[GatedConv 5→64, 7×7, stride 1] # Level 0
[GatedConv 64→128, 4×4, stride 2] # Level 1 (↓2×)
[GatedConv 128→256, 4×4, stride 2] # Level 2 (↓2×)
Bottleneck:
8× [DilatedResBlock 256→256, dilation=2] # ~200px receptive field
Decoder:
[ConvTranspose 512→128, 4×4, stride 2] # Skip from Level 2 (↑2×)
[ConvTranspose 256→64, 4×4, stride 2] # Skip from Level 1 (↑2×)
[Conv 128→3, 7×7, Tanh] # Skip from Level 0
All layers: InstanceNorm + ReLU (encoder: LeakyReLU)
Input: 5 channels (damaged_RGB + stroke_mask + gap_mask)
Output: 3 channels (repaired RGB in [-1, 1])
C64(no norm) → C128 → C256 → C512 → Conv→1
All convs: 4×4, spectral normalized
InstanceNorm on layers 2-4
LeakyReLU(0.2) throughout
Input: 8 channels (damaged + output + masks)
Output: H/16 × W/16 patch predictions
The deterministic Stage 1 pipeline:
├── pathfinding.py # Stage 1: Deterministic gap repair
├── damage_generator.py # Synthetic training data generation
├── models.py # cGAN architecture (Generator + Discriminator)
├── losses.py # Loss functions + metrics
├── dataset.py # Dataset loader with pathfinding integration
├── train.py # Training pipeline
├── inference.py # Inference / repair script
├── requirements.txt # Dependencies
└── README.md # This file
Place clean calligraphy images in a directory and the system will synthetically damage them:
# Generate paired data from your clean images
python damage_generator.py \
--source_dir /path/to/your/clean/calligraphy/ \
--output_dir data \
--num_train 5000
# Or train with on-the-fly damage
python train.py \
--data_dir data \
--on_the_fly_damage
Organize your data as:
data/
├── train/
│ ├── clean/001.png, 002.png, ...
│ └── damaged/001.png, 002.png, ...
└── val/
├── clean/001.png, 002.png, ...
└── damaged/001.png, 002.png, ...
Filenames must match between clean/ and damaged/.
checkpoints/samples/ for visual progress--max_gap_distance controls how large a gap the pathfinder will attempt to bridgeMIT