Feature Extraction
PEFT
Safetensors
English
clip
lora
fonts
typography
indic
devanagari
tamil
telugu
kannada
malayalam
bengali
gurmukhi
gujarati
odia
text-to-font
retrieval
Instructions to use loopdesk-ai/lipika-fontclip with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use loopdesk-ai/lipika-fontclip with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Upload README.md with huggingface_hub
Browse files
README.md
CHANGED
|
@@ -18,6 +18,7 @@ tags:
|
|
| 18 |
- gujarati
|
| 19 |
- odia
|
| 20 |
- text-to-font
|
|
|
|
| 21 |
library_name: peft
|
| 22 |
base_model: openai/clip-vit-large-patch14
|
| 23 |
pipeline_tag: feature-extraction
|
|
@@ -40,30 +41,73 @@ Indic font-intelligence family.
|
|
| 40 |
This is, to our knowledge, the first open font↔language embedding
|
| 41 |
model for Indic scripts (prior art: FontCLIP, Latin-centric).
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
## What's in this repo
|
| 44 |
|
| 45 |
| file | description |
|
| 46 |
|---|---|
|
| 47 |
| `adapter/` | LoRA adapter (r16, q/v proj, last 8 blocks of both towers) for `openai/clip-vit-large-patch14` |
|
| 48 |
-
| `index.npz` | Precomputed embeddings for 595 font families × up to 3 faces (regular / heaviest / lightest) × per-script specimens (1250 entries)
|
| 49 |
-
| `attributes.json` | 46 visual-attribute scores (0–100) per family (VLM-labeled,
|
| 50 |
| `vocab.json` | The attribute vocabulary |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
## Quickstart
|
| 53 |
|
| 54 |
```python
|
| 55 |
import json, numpy as np, torch, torch.nn.functional as F
|
|
|
|
| 56 |
from transformers import CLIPModel, CLIPTokenizerFast
|
| 57 |
from peft import PeftModel
|
| 58 |
|
|
|
|
| 59 |
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
|
| 60 |
-
model = PeftModel.from_pretrained(model,
|
| 61 |
subfolder="adapter").merge_and_unload().eval()
|
| 62 |
tok = CLIPTokenizerFast.from_pretrained("openai/clip-vit-large-patch14")
|
| 63 |
|
| 64 |
-
z = np.load(
|
| 65 |
E = torch.from_numpy(z["embeddings"])
|
| 66 |
-
fams
|
|
|
|
| 67 |
meta = json.loads(z["meta"].tobytes().decode())["families"]
|
| 68 |
|
| 69 |
q = "a thin delicate Devanagari font"
|
|
@@ -75,14 +119,32 @@ best = {}
|
|
| 75 |
for i in sims.argsort(descending=True).tolist():
|
| 76 |
f = fams[i]
|
| 77 |
if scripts[i] != "devanagari" or meta[f].get("legacy"):
|
| 78 |
-
continue
|
| 79 |
best.setdefault(f, sims[i].item())
|
| 80 |
if len(best) == 5:
|
| 81 |
break
|
| 82 |
print(list(best))
|
| 83 |
```
|
| 84 |
|
| 85 |
-
Tested with `transformers==4.49.0`, `peft=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
## Training
|
| 88 |
|
|
@@ -94,20 +156,27 @@ Tested with `transformers==4.49.0`, `peft==0.14.0`.
|
|
| 94 |
- **Recipe**: LoRA (r16, α16) on q/v projections of the last 8 blocks of
|
| 95 |
both towers; symmetric InfoNCE with same-family negatives masked;
|
| 96 |
hinged cross-script pair loss (same family, different script →
|
| 97 |
-
cos ≥ 0.9); hinged attribute-ranking loss against frozen
|
| 98 |
-
batch 256, 4k steps, best @2000.
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
## Evaluation
|
| 101 |
|
|
|
|
|
|
|
|
|
|
| 102 |
| metric | zero-shot CLIP | lipika-fontclip |
|
| 103 |
|---|---|---|
|
| 104 |
-
| Text→font P@5 (norm.,
|
| 105 |
-
| Attribute ranking (mean Spearman, held-out
|
| 106 |
| Cross-script consistency (same family, cos) | 0.618 | **0.884** |
|
| 107 |
| Bold-above-light ordering | 50.7% | **100%** |
|
| 108 |
|
| 109 |
-
Zero-shot CLIP is near-chance on Indic typography
|
| 110 |
-
|
|
|
|
| 111 |
|
| 112 |
## Limitations
|
| 113 |
|
|
@@ -116,11 +185,20 @@ makes this usable.
|
|
| 116 |
- "Serif/high-contrast" concepts are weakly represented in the corpus
|
| 117 |
and retrieval quality reflects that.
|
| 118 |
- 326/595 families are legacy (non-Unicode) 90s DTP fonts (Kruti Dev,
|
| 119 |
-
DevLys, …); they flood stylistic queries unless filtered —
|
| 120 |
-
|
|
|
|
| 121 |
- English queries only.
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
## License
|
| 124 |
|
| 125 |
-
Apache-2.0 (adapter, index, labels). Fonts referenced
|
| 126 |
-
own licenses (mostly OFL); no font binaries are
|
|
|
|
|
|
| 18 |
- gujarati
|
| 19 |
- odia
|
| 20 |
- text-to-font
|
| 21 |
+
- retrieval
|
| 22 |
library_name: peft
|
| 23 |
base_model: openai/clip-vit-large-patch14
|
| 24 |
pipeline_tag: feature-extraction
|
|
|
|
| 41 |
This is, to our knowledge, the first open font↔language embedding
|
| 42 |
model for Indic scripts (prior art: FontCLIP, Latin-centric).
|
| 43 |
|
| 44 |
+
## 🖥️ Try it live
|
| 45 |
+
|
| 46 |
+
**[→ Gradio demo: anilpai/lipika-demo](https://huggingface.co/spaces/anilpai/lipika-demo)**
|
| 47 |
+
— the *Search by description* tab runs this model. Results render live
|
| 48 |
+
in each font (hosted WOFF2), with attribute chips, similarity bars,
|
| 49 |
+
match counts, a script filter, and a toggle for legacy 90s DTP fonts.
|
| 50 |
+
The *Recognize* tab is the companion
|
| 51 |
+
[image→font recognizer](https://huggingface.co/loopdesk-ai/lipika).
|
| 52 |
+
|
| 53 |
## What's in this repo
|
| 54 |
|
| 55 |
| file | description |
|
| 56 |
|---|---|
|
| 57 |
| `adapter/` | LoRA adapter (r16, q/v proj, last 8 blocks of both towers) for `openai/clip-vit-large-patch14` |
|
| 58 |
+
| `index.npz` | Precomputed embeddings for 595 font families × up to 3 faces (regular / heaviest / lightest) × per-script specimens (1250 entries), plus per-family metadata: attribute scores, scripts, `legacy` flag, legacy `encoding` |
|
| 59 |
+
| `attributes.json` | 46 visual-attribute scores (0–100) per family (VLM-labeled, two-pass fused) |
|
| 60 |
| `vocab.json` | The attribute vocabulary |
|
| 61 |
+
| `specimens/` | 596 rendered specimen banners (1024×220 PNG), one per family — preview images used by the demo when no hosted WOFF2 exists |
|
| 62 |
+
|
| 63 |
+
## Quickstart (lipika package — recommended)
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
pip install "lipika[search] @ git+https://github.com/Loopdesk-AI/lipika.git"
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
```python
|
| 70 |
+
from pathlib import Path
|
| 71 |
+
from huggingface_hub import snapshot_download
|
| 72 |
+
from fontrecog.fontclip.search import FontClipSearcher
|
| 73 |
+
|
| 74 |
+
fc = Path(snapshot_download("loopdesk-ai/lipika-fontclip",
|
| 75 |
+
allow_patterns=["index.npz", "adapter/*"]))
|
| 76 |
+
searcher = FontClipSearcher(fc / "index.npz", ckpt=str(fc / "adapter"))
|
| 77 |
+
|
| 78 |
+
hits = searcher.search("a thin delicate Devanagari font",
|
| 79 |
+
k=5, script="devanagari")
|
| 80 |
+
# [{'family': 'Khula', 'score': 0.21, 'matched_script': 'devanagari',
|
| 81 |
+
# 'matched_face': 'light', 'scripts': ['devanagari', 'latin']}, ...]
|
| 82 |
+
|
| 83 |
+
stats = searcher.search_with_stats("a round playful Tamil font",
|
| 84 |
+
k=10, script="tamil")
|
| 85 |
+
# {'results': [...], 'n_candidates': 31, 'n_shown': 10}
|
| 86 |
+
|
| 87 |
+
# Legacy (non-Unicode 90s DTP) families — 326 of 595, e.g. Kruti Dev,
|
| 88 |
+
# DevLys — are excluded by default; opt in explicitly:
|
| 89 |
+
searcher.search("a retro 90s desktop-publishing Hindi font",
|
| 90 |
+
script="devanagari", include_legacy=True)
|
| 91 |
+
```
|
| 92 |
|
| 93 |
+
## Quickstart (raw transformers + peft)
|
| 94 |
|
| 95 |
```python
|
| 96 |
import json, numpy as np, torch, torch.nn.functional as F
|
| 97 |
+
from huggingface_hub import hf_hub_download
|
| 98 |
from transformers import CLIPModel, CLIPTokenizerFast
|
| 99 |
from peft import PeftModel
|
| 100 |
|
| 101 |
+
repo = "loopdesk-ai/lipika-fontclip"
|
| 102 |
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
|
| 103 |
+
model = PeftModel.from_pretrained(model, repo,
|
| 104 |
subfolder="adapter").merge_and_unload().eval()
|
| 105 |
tok = CLIPTokenizerFast.from_pretrained("openai/clip-vit-large-patch14")
|
| 106 |
|
| 107 |
+
z = np.load(hf_hub_download(repo, "index.npz"))
|
| 108 |
E = torch.from_numpy(z["embeddings"])
|
| 109 |
+
fams = [str(x) for x in z["families"]]
|
| 110 |
+
scripts = [str(x) for x in z["scripts"]]
|
| 111 |
meta = json.loads(z["meta"].tobytes().decode())["families"]
|
| 112 |
|
| 113 |
q = "a thin delicate Devanagari font"
|
|
|
|
| 119 |
for i in sims.argsort(descending=True).tolist():
|
| 120 |
f = fams[i]
|
| 121 |
if scripts[i] != "devanagari" or meta[f].get("legacy"):
|
| 122 |
+
continue
|
| 123 |
best.setdefault(f, sims[i].item())
|
| 124 |
if len(best) == 5:
|
| 125 |
break
|
| 126 |
print(list(best))
|
| 127 |
```
|
| 128 |
|
| 129 |
+
Tested with `transformers==4.49.0`, `peft>=0.14`. (`transformers>=5`
|
| 130 |
+
changed the `get_text_features` return type — use `<5` or the lipika
|
| 131 |
+
package.)
|
| 132 |
+
|
| 133 |
+
## Index metadata
|
| 134 |
+
|
| 135 |
+
`index.npz` → `meta` (JSON) → `families[family]`:
|
| 136 |
+
|
| 137 |
+
| key | description |
|
| 138 |
+
|---|---|
|
| 139 |
+
| `attrs` | 46 attribute scores 0–100 (family-fused) |
|
| 140 |
+
| `scripts` | scripts covered by the family |
|
| 141 |
+
| `files` | font file per indexed face (`rep`/`heavy`/`light`) |
|
| 142 |
+
| `legacy` | `true` for non-Unicode 90s DTP families |
|
| 143 |
+
| `encoding` | legacy cmap encoding (e.g. `krutidev010`) or `null` — text must be converted before rendering in these fonts |
|
| 144 |
+
|
| 145 |
+
Each family is indexed with up to 3 static faces so weight-specific
|
| 146 |
+
queries ("very heavy…") can match the right face; `matched_face` in
|
| 147 |
+
results tells you which one won.
|
| 148 |
|
| 149 |
## Training
|
| 150 |
|
|
|
|
| 156 |
- **Recipe**: LoRA (r16, α16) on q/v projections of the last 8 blocks of
|
| 157 |
both towers; symmetric InfoNCE with same-family negatives masked;
|
| 158 |
hinged cross-script pair loss (same family, different script →
|
| 159 |
+
cos ≥ 0.9); hinged attribute-ranking loss against frozen (detached)
|
| 160 |
+
text probes; batch 256, 4k steps on 1×H100, best checkpoint @2000.
|
| 161 |
+
- Trainable probes for the ranking loss were falsified (the text tower
|
| 162 |
+
games the hinge and attribute directions collapse); detaching them is
|
| 163 |
+
what made the loss help.
|
| 164 |
|
| 165 |
## Evaluation
|
| 166 |
|
| 167 |
+
Frozen 50-query benchmark + held-out families (30 families never seen
|
| 168 |
+
in training), multi-face index:
|
| 169 |
+
|
| 170 |
| metric | zero-shot CLIP | lipika-fontclip |
|
| 171 |
|---|---|---|
|
| 172 |
+
| Text→font P@5 (norm., answerable queries) | 0.150 | **0.450** (3.0×) |
|
| 173 |
+
| Attribute ranking (mean Spearman, held-out) | −0.075 | **0.365** |
|
| 174 |
| Cross-script consistency (same family, cos) | 0.618 | **0.884** |
|
| 175 |
| Bold-above-light ordering | 50.7% | **100%** |
|
| 176 |
|
| 177 |
+
Zero-shot CLIP is near-chance on Indic typography (weight ordering is a
|
| 178 |
+
coin flip; attribute correlations are negative) — fine-tuning is what
|
| 179 |
+
makes this usable. 8/8 retrieval regression spot-checks pass.
|
| 180 |
|
| 181 |
## Limitations
|
| 182 |
|
|
|
|
| 185 |
- "Serif/high-contrast" concepts are weakly represented in the corpus
|
| 186 |
and retrieval quality reflects that.
|
| 187 |
- 326/595 families are legacy (non-Unicode) 90s DTP fonts (Kruti Dev,
|
| 188 |
+
DevLys, …); they flood stylistic queries unless filtered — excluded
|
| 189 |
+
by default in the lipika searcher, `include_legacy=True` restores
|
| 190 |
+
them, and the `legacy`/`encoding` metadata supports custom handling.
|
| 191 |
- English queries only.
|
| 192 |
|
| 193 |
+
## Related
|
| 194 |
+
|
| 195 |
+
- **Demo Space**: [anilpai/lipika-demo](https://huggingface.co/spaces/anilpai/lipika-demo)
|
| 196 |
+
- **Recognizer model** (image→font): [loopdesk-ai/lipika](https://huggingface.co/loopdesk-ai/lipika)
|
| 197 |
+
- **Code**: [Loopdesk-AI/lipika](https://github.com/Loopdesk-AI/lipika)
|
| 198 |
+
(`fontrecog.fontclip` — training, eval gates, index builder, searcher)
|
| 199 |
+
|
| 200 |
## License
|
| 201 |
|
| 202 |
+
Apache-2.0 (adapter, index, labels, specimen images). Fonts referenced
|
| 203 |
+
are under their own licenses (mostly OFL); no font binaries are
|
| 204 |
+
redistributed here.
|