Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- app.py +59 -61
- ex_nutrition.jpg +0 -0
.gitattributes
CHANGED
|
@@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
sample_wood.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
sample_wood.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
ex_nutrition.jpg filter=lfs diff=lfs merge=lfs -text
|
app.py
CHANGED
|
@@ -46,41 +46,41 @@ for key, mid in MODEL_IDS.items():
|
|
| 46 |
# --------------------------------------------------------------------------- #
|
| 47 |
# Schema presets — fill the visual field builder with a single click. #
|
| 48 |
# --------------------------------------------------------------------------- #
|
|
|
|
| 49 |
PRESETS = {
|
| 50 |
"wood": {
|
| 51 |
"label": "🪵 Wood surface",
|
| 52 |
"fields": [
|
| 53 |
{"name": "wood_color", "description": "The overall coloration of the wood surface"},
|
| 54 |
{"name": "wood_texture", "description": "The tactile quality of the wood surface"},
|
| 55 |
-
{"name": "
|
| 56 |
],
|
| 57 |
},
|
| 58 |
"receipt": {
|
| 59 |
"label": "🧾 Receipt",
|
| 60 |
"fields": [
|
| 61 |
-
{"name": "
|
| 62 |
-
{"name": "
|
| 63 |
-
{"name": "
|
| 64 |
-
{"name": "
|
| 65 |
-
{"name": "payment_method", "description": "How the bill was paid"},
|
| 66 |
],
|
| 67 |
},
|
| 68 |
"nutrition": {
|
| 69 |
"label": "🥫 Nutrition label",
|
| 70 |
"fields": [
|
| 71 |
-
{"name": "
|
| 72 |
-
{"name": "
|
| 73 |
-
{"name": "
|
| 74 |
-
{"name": "
|
| 75 |
-
{"name": "
|
| 76 |
],
|
| 77 |
},
|
| 78 |
"card": {
|
| 79 |
"label": "💼 Business card",
|
| 80 |
"fields": [
|
| 81 |
-
{"name": "
|
| 82 |
-
{"name": "
|
| 83 |
-
{"name": "company", "description": "Company or
|
| 84 |
{"name": "email", "description": "Email address"},
|
| 85 |
{"name": "phone", "description": "Phone number"},
|
| 86 |
],
|
|
@@ -88,11 +88,11 @@ PRESETS = {
|
|
| 88 |
"product": {
|
| 89 |
"label": "🛍️ Product photo",
|
| 90 |
"fields": [
|
| 91 |
-
{"name": "
|
| 92 |
-
{"name": "brand", "description": "
|
| 93 |
-
{"name": "primary_color", "description": "
|
| 94 |
-
{"name": "
|
| 95 |
-
{"name": "
|
| 96 |
],
|
| 97 |
},
|
| 98 |
}
|
|
@@ -285,32 +285,38 @@ const LQ_PRESETS = __PRESETS__;
|
|
| 285 |
setter.call(store, key);
|
| 286 |
store.dispatchEvent(new Event('input', { bubbles: true }));
|
| 287 |
}
|
| 288 |
-
function loadPreset(mount, key
|
| 289 |
const p = LQ_PRESETS[key];
|
| 290 |
-
if (!p) return;
|
| 291 |
mount.querySelector('#lq-rows').innerHTML = '';
|
| 292 |
p.fields.forEach(f => makeRow(mount, f.name, f.description));
|
| 293 |
-
mount.querySelectorAll('.lq-chip').forEach(c =>
|
| 294 |
-
c.classList.toggle('active', c.dataset.preset === key));
|
| 295 |
syncStore(mount);
|
| 296 |
-
|
| 297 |
}
|
| 298 |
-
function
|
| 299 |
const mount = document.getElementById('lq-schema-builder');
|
| 300 |
if (!mount || mount.dataset.ready) return;
|
| 301 |
mount.dataset.ready = '1';
|
| 302 |
-
|
| 303 |
-
Object.keys(LQ_PRESETS).forEach(k => {
|
| 304 |
-
chips += '<button class="lq-chip" data-preset="' + k + '">' + LQ_PRESETS[k].label + '</button>';
|
| 305 |
-
});
|
| 306 |
-
chips += '</div>';
|
| 307 |
-
mount.innerHTML = chips + '<div id="lq-rows"></div>' +
|
| 308 |
'<button class="lq-add" id="lq-add">+ add field</button>';
|
| 309 |
-
mount.querySelectorAll('.lq-chip').forEach(c =>
|
| 310 |
-
c.addEventListener('click', () => loadPreset(mount, c.dataset.preset, true)));
|
| 311 |
mount.querySelector('#lq-add').addEventListener('click', () => { makeRow(mount, '', ''); });
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
}
|
|
|
|
| 314 |
setInterval(init, 250);
|
| 315 |
|
| 316 |
document.addEventListener('click', function (e) {
|
|
@@ -344,27 +350,13 @@ CSS = """
|
|
| 344 |
.gradio-container { max-width: 1280px !important; margin: 0 auto !important; }
|
| 345 |
#lq-schema-store, #lq-preset-store { display: none !important; }
|
| 346 |
|
| 347 |
-
#lq-hero {
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
box-shadow: 0 10px 30px -16px rgba(255,140,0,.55);
|
| 352 |
-
}
|
| 353 |
-
#lq-hero h1 { margin:0; font-size: 2.05rem; font-weight: 800; letter-spacing:-.02em;
|
| 354 |
-
color:#e0590a; -webkit-text-fill-color:#e0590a; text-shadow:0 1px 0 rgba(255,255,255,.4); }
|
| 355 |
-
#lq-hero p { margin:.4rem 0 0; color:#7a5a16; font-size:.98rem; max-width: 70ch; }
|
| 356 |
-
#lq-hero .lq-badges { margin-top:.7rem; display:flex; gap:.5rem; flex-wrap:wrap; }
|
| 357 |
-
#lq-hero .lq-badge { font-size:.74rem; font-weight:700; padding:.28rem .62rem; border-radius:999px;
|
| 358 |
-
background:rgba(255,255,255,.7); color:#a85f00; border:1px solid rgba(255,150,0,.3); }
|
| 359 |
|
| 360 |
/* schema builder */
|
| 361 |
#lq-schema-builder { display:block; }
|
| 362 |
-
.lq-presets { display:flex; flex-wrap:wrap; gap:.4rem; margin-bottom:.7rem; }
|
| 363 |
-
.lq-chip { border:1px solid rgba(255,150,0,.35); background:#fff; color:#a85f00;
|
| 364 |
-
border-radius:999px; padding:.34rem .7rem; font-size:.82rem; font-weight:600; cursor:pointer;
|
| 365 |
-
transition:all .12s ease; }
|
| 366 |
-
.lq-chip:hover { transform:translateY(-1px); box-shadow:0 4px 12px -6px rgba(255,140,0,.6); }
|
| 367 |
-
.lq-chip.active { background:linear-gradient(90deg,var(--lq-orange),var(--lq-amber)); color:#fff; border-color:transparent; }
|
| 368 |
.lq-row { display:flex; gap:.5rem; margin-bottom:.45rem; align-items:center; }
|
| 369 |
.lq-row input { border:1px solid rgba(180,160,120,.4); border-radius:11px; padding:.55rem .7rem;
|
| 370 |
font-size:.9rem; background:#fffdf8; transition:border .12s,box-shadow .12s; }
|
|
@@ -380,6 +372,17 @@ CSS = """
|
|
| 380 |
cursor:pointer; width:100%; transition:all .12s; }
|
| 381 |
.lq-add:hover { background:#fff6ea; border-color:var(--lq-orange); }
|
| 382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
/* result viewer */
|
| 384 |
.lq-result-shell { border-radius:18px; min-height:360px; padding:16px;
|
| 385 |
background:linear-gradient(160deg,#fffaf0,#fff7e6); border:1px solid rgba(255,160,0,.22); }
|
|
@@ -430,14 +433,8 @@ with gr.Blocks(title="Liquid Image → JSON") as demo:
|
|
| 430 |
'<div id="lq-hero">'
|
| 431 |
"<h1>💧 Liquid Image → JSON</h1>"
|
| 432 |
"<p>Define the fields you want, drop an image, and watch "
|
| 433 |
-
"<b>LFM2.5-VL Extract</b> turn pixels into clean structured JSON
|
| 434 |
-
"
|
| 435 |
-
'<div class="lq-badges">'
|
| 436 |
-
'<span class="lq-badge">⚡ ZeroGPU</span>'
|
| 437 |
-
'<span class="lq-badge">450M & 1.6B</span>'
|
| 438 |
-
'<span class="lq-badge">streaming extraction</span>'
|
| 439 |
-
'<span class="lq-badge">gr.Citrus</span>'
|
| 440 |
-
"</div></div>"
|
| 441 |
)
|
| 442 |
|
| 443 |
with gr.Row(equal_height=False):
|
|
@@ -448,11 +445,12 @@ with gr.Blocks(title="Liquid Image → JSON") as demo:
|
|
| 448 |
value="1.6B",
|
| 449 |
label="Model",
|
| 450 |
)
|
| 451 |
-
gr.Markdown("**Extraction schema** —
|
| 452 |
gr.HTML('<div id="lq-schema-builder"></div>')
|
| 453 |
schema_store = gr.Textbox(elem_id="lq-schema-store", value="[]")
|
| 454 |
preset_store = gr.Textbox(elem_id="lq-preset-store", value="")
|
| 455 |
go = gr.Button("💧 Extract JSON", variant="primary", elem_id="lq-go")
|
|
|
|
| 456 |
|
| 457 |
with gr.Column(scale=5):
|
| 458 |
result = gr.HTML(placeholder_html())
|
|
|
|
| 46 |
# --------------------------------------------------------------------------- #
|
| 47 |
# Schema presets — fill the visual field builder with a single click. #
|
| 48 |
# --------------------------------------------------------------------------- #
|
| 49 |
+
# Each example's fields are tailored to what is actually visible in its image.
|
| 50 |
PRESETS = {
|
| 51 |
"wood": {
|
| 52 |
"label": "🪵 Wood surface",
|
| 53 |
"fields": [
|
| 54 |
{"name": "wood_color", "description": "The overall coloration of the wood surface"},
|
| 55 |
{"name": "wood_texture", "description": "The tactile quality of the wood surface"},
|
| 56 |
+
{"name": "grain_pattern", "description": "The pattern of the wood grain"},
|
| 57 |
],
|
| 58 |
},
|
| 59 |
"receipt": {
|
| 60 |
"label": "🧾 Receipt",
|
| 61 |
"fields": [
|
| 62 |
+
{"name": "total_amount", "description": "The total amount printed on the receipt"},
|
| 63 |
+
{"name": "cash_paid", "description": "The amount of cash tendered"},
|
| 64 |
+
{"name": "change_due", "description": "The change given back"},
|
| 65 |
+
{"name": "gst_rate", "description": "The GST / tax percentage shown"},
|
|
|
|
| 66 |
],
|
| 67 |
},
|
| 68 |
"nutrition": {
|
| 69 |
"label": "🥫 Nutrition label",
|
| 70 |
"fields": [
|
| 71 |
+
{"name": "product_name", "description": "The name of the product on the label"},
|
| 72 |
+
{"name": "brand", "description": "The brand shown on the label"},
|
| 73 |
+
{"name": "net_weight", "description": "The net or drained weight"},
|
| 74 |
+
{"name": "servings_per_container", "description": "Number of servings per container"},
|
| 75 |
+
{"name": "best_before_date", "description": "The best-before or expiry date"},
|
| 76 |
],
|
| 77 |
},
|
| 78 |
"card": {
|
| 79 |
"label": "💼 Business card",
|
| 80 |
"fields": [
|
| 81 |
+
{"name": "full_name", "description": "The person's full name"},
|
| 82 |
+
{"name": "job_title", "description": "Their job title or role"},
|
| 83 |
+
{"name": "company", "description": "Company name or website"},
|
| 84 |
{"name": "email", "description": "Email address"},
|
| 85 |
{"name": "phone", "description": "Phone number"},
|
| 86 |
],
|
|
|
|
| 88 |
"product": {
|
| 89 |
"label": "🛍️ Product photo",
|
| 90 |
"fields": [
|
| 91 |
+
{"name": "product_type", "description": "What kind of product this is"},
|
| 92 |
+
{"name": "brand", "description": "The brand, if a logo is visible"},
|
| 93 |
+
{"name": "primary_color", "description": "The dominant color of the product"},
|
| 94 |
+
{"name": "accent_colors", "description": "Secondary or accent colors"},
|
| 95 |
+
{"name": "closure_type", "description": "How the item fastens or closes"},
|
| 96 |
],
|
| 97 |
},
|
| 98 |
}
|
|
|
|
| 285 |
setter.call(store, key);
|
| 286 |
store.dispatchEvent(new Event('input', { bubbles: true }));
|
| 287 |
}
|
| 288 |
+
function loadPreset(mount, key) {
|
| 289 |
const p = LQ_PRESETS[key];
|
| 290 |
+
if (!p || !mount) return;
|
| 291 |
mount.querySelector('#lq-rows').innerHTML = '';
|
| 292 |
p.fields.forEach(f => makeRow(mount, f.name, f.description));
|
|
|
|
|
|
|
| 293 |
syncStore(mount);
|
| 294 |
+
setPreset(key);
|
| 295 |
}
|
| 296 |
+
function initBuilder() {
|
| 297 |
const mount = document.getElementById('lq-schema-builder');
|
| 298 |
if (!mount || mount.dataset.ready) return;
|
| 299 |
mount.dataset.ready = '1';
|
| 300 |
+
mount.innerHTML = '<div id="lq-rows"></div>' +
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
'<button class="lq-add" id="lq-add">+ add field</button>';
|
|
|
|
|
|
|
| 302 |
mount.querySelector('#lq-add').addEventListener('click', () => { makeRow(mount, '', ''); });
|
| 303 |
+
makeRow(mount, '', ''); makeRow(mount, '', ''); makeRow(mount, '', '');
|
| 304 |
+
syncStore(mount);
|
| 305 |
+
}
|
| 306 |
+
function initExamples() {
|
| 307 |
+
const ex = document.getElementById('lq-examples');
|
| 308 |
+
if (!ex || ex.dataset.ready) return;
|
| 309 |
+
ex.dataset.ready = '1';
|
| 310 |
+
let html = '<div class="lq-ex-label">Examples — click to try one</div><div class="lq-ex-grid">';
|
| 311 |
+
Object.keys(LQ_PRESETS).forEach(k => {
|
| 312 |
+
html += '<button class="lq-ex" data-preset="' + k + '">' + LQ_PRESETS[k].label + '</button>';
|
| 313 |
+
});
|
| 314 |
+
html += '</div>';
|
| 315 |
+
ex.innerHTML = html;
|
| 316 |
+
ex.querySelectorAll('.lq-ex').forEach(c => c.addEventListener('click', () =>
|
| 317 |
+
loadPreset(document.getElementById('lq-schema-builder'), c.dataset.preset)));
|
| 318 |
}
|
| 319 |
+
function init() { initBuilder(); initExamples(); }
|
| 320 |
setInterval(init, 250);
|
| 321 |
|
| 322 |
document.addEventListener('click', function (e) {
|
|
|
|
| 350 |
.gradio-container { max-width: 1280px !important; margin: 0 auto !important; }
|
| 351 |
#lq-schema-store, #lq-preset-store { display: none !important; }
|
| 352 |
|
| 353 |
+
#lq-hero { padding: 6px 2px 2px; margin-bottom: 2px; }
|
| 354 |
+
#lq-hero h1 { margin:0; font-size: 1.75rem; font-weight: 800; letter-spacing:-.02em;
|
| 355 |
+
color:#e0590a; -webkit-text-fill-color:#e0590a; }
|
| 356 |
+
#lq-hero p { margin:.3rem 0 0; color:#8a6a2a; font-size:.95rem; max-width: 72ch; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
|
| 358 |
/* schema builder */
|
| 359 |
#lq-schema-builder { display:block; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
.lq-row { display:flex; gap:.5rem; margin-bottom:.45rem; align-items:center; }
|
| 361 |
.lq-row input { border:1px solid rgba(180,160,120,.4); border-radius:11px; padding:.55rem .7rem;
|
| 362 |
font-size:.9rem; background:#fffdf8; transition:border .12s,box-shadow .12s; }
|
|
|
|
| 372 |
cursor:pointer; width:100%; transition:all .12s; }
|
| 373 |
.lq-add:hover { background:#fff6ea; border-color:var(--lq-orange); }
|
| 374 |
|
| 375 |
+
/* examples */
|
| 376 |
+
#lq-examples { margin-top:.4rem; }
|
| 377 |
+
.lq-ex-label { font-size:.78rem; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
|
| 378 |
+
color:#b88a3a; margin-bottom:.5rem; }
|
| 379 |
+
.lq-ex-grid { display:flex; flex-wrap:wrap; gap:.45rem; }
|
| 380 |
+
.lq-ex { border:1px solid rgba(180,160,120,.35); background:#fffdf8; color:#7a5a20;
|
| 381 |
+
border-radius:11px; padding:.45rem .7rem; font-size:.85rem; font-weight:600; cursor:pointer;
|
| 382 |
+
transition:all .12s ease; }
|
| 383 |
+
.lq-ex:hover { transform:translateY(-1px); border-color:var(--lq-orange); color:#b35900;
|
| 384 |
+
box-shadow:0 5px 14px -8px rgba(255,140,0,.6); background:#fff; }
|
| 385 |
+
|
| 386 |
/* result viewer */
|
| 387 |
.lq-result-shell { border-radius:18px; min-height:360px; padding:16px;
|
| 388 |
background:linear-gradient(160deg,#fffaf0,#fff7e6); border:1px solid rgba(255,160,0,.22); }
|
|
|
|
| 433 |
'<div id="lq-hero">'
|
| 434 |
"<h1>💧 Liquid Image → JSON</h1>"
|
| 435 |
"<p>Define the fields you want, drop an image, and watch "
|
| 436 |
+
"<b>LFM2.5-VL Extract</b> turn pixels into clean structured JSON.</p>"
|
| 437 |
+
"</div>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
)
|
| 439 |
|
| 440 |
with gr.Row(equal_height=False):
|
|
|
|
| 445 |
value="1.6B",
|
| 446 |
label="Model",
|
| 447 |
)
|
| 448 |
+
gr.Markdown("**Extraction schema** — name a field and describe what to pull out")
|
| 449 |
gr.HTML('<div id="lq-schema-builder"></div>')
|
| 450 |
schema_store = gr.Textbox(elem_id="lq-schema-store", value="[]")
|
| 451 |
preset_store = gr.Textbox(elem_id="lq-preset-store", value="")
|
| 452 |
go = gr.Button("💧 Extract JSON", variant="primary", elem_id="lq-go")
|
| 453 |
+
gr.HTML('<div id="lq-examples"></div>')
|
| 454 |
|
| 455 |
with gr.Column(scale=5):
|
| 456 |
result = gr.HTML(placeholder_html())
|
ex_nutrition.jpg
CHANGED
|
|
Git LFS Details
|