anilpai commited on
Commit
5faa2b2
·
verified ·
1 Parent(s): a38f280

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +96 -18
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) + family metadata |
49
- | `attributes.json` | 46 visual-attribute scores (0–100) per family (VLM-labeled, fused two-pass) |
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, "loopdesk-ai/lipika-fontclip",
61
  subfolder="adapter").merge_and_unload().eval()
62
  tok = CLIPTokenizerFast.from_pretrained("openai/clip-vit-large-patch14")
63
 
64
- z = np.load("index.npz") # hf_hub_download(..., "index.npz")
65
  E = torch.from_numpy(z["embeddings"])
66
- fams, scripts = [str(x) for x in z["families"]], [str(x) for x in z["scripts"]]
 
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 # legacy = non-Unicode 90s DTP faces, opt-in
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==0.14.0`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 text probes;
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., 30 frozen queries) | 0.150 | **0.450** (3.0×) |
105
- | Attribute ranking (mean Spearman, held-out families) | −0.075 | **0.365** |
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; fine-tuning is what
110
- makes this usable.
 
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 — the
120
- `legacy` metadata flag is provided for this.
 
121
  - English queries only.
122
 
 
 
 
 
 
 
 
123
  ## License
124
 
125
- Apache-2.0 (adapter, index, labels). Fonts referenced are under their
126
- own licenses (mostly OFL); no font binaries are redistributed here.
 
 
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.