electblake commited on
Commit
c2a765a
·
1 Parent(s): 7de0174

refactor(app): consolidate runtime into two modules

Browse files

Replace the forwarding launchers and app package with a root Gradio app and one ZeroGPU model pipeline module. Remove legacy entry points and the standalone sample runner while preserving both live API workflows.

Files changed (2) hide show
  1. app.py +432 -1
  2. app/gradio.py +0 -435
app.py CHANGED
@@ -1,4 +1,435 @@
1
- from app.hf_space import demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  if __name__ == "__main__":
4
  demo.launch()
 
1
+ # ruff: noqa: I001
2
+
3
+ import spaces
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import gradio as gr
9
+ import torch
10
+ from transformers import AutoModelForImageTextToText, AutoProcessor
11
+
12
+ model_id = "numind/NuExtract3"
13
+
14
+ structured_json_templates = {
15
+ "Basic receipt": """{
16
+ "merchant": "verbatim-string",
17
+ "purchase_date": "date",
18
+ "purchase_time": "time",
19
+ "total": "number",
20
+ "currency": "currency",
21
+ "item_count": "integer",
22
+ "paid": "boolean",
23
+ "payment_method": ["cash", "credit-card", "debit-card", "other"],
24
+ "items": ["verbatim-string"]
25
+ }""",
26
+ "Advanced receipt": """{
27
+ "merchant": {
28
+ "name": "verbatim-string",
29
+ "address": "verbatim-string",
30
+ "phone": "phone-number"
31
+ },
32
+ "purchase_date": "date",
33
+ "purchase_time": "time",
34
+ "store_number": "verbatim-string",
35
+ "operator_number": "verbatim-string",
36
+ "terminal_number": "verbatim-string",
37
+ "transaction_number": "verbatim-string",
38
+ "items": [
39
+ {
40
+ "description": "verbatim-string",
41
+ "sku": "verbatim-string",
42
+ "quantity": "number",
43
+ "unit": "unit-code",
44
+ "unit_price": "number",
45
+ "discount": "number",
46
+ "line_total": "number",
47
+ "tax_code": "verbatim-string"
48
+ }
49
+ ],
50
+ "item_count": "integer",
51
+ "subtotal": "number",
52
+ "tax": "number",
53
+ "total": "number",
54
+ "currency": "currency",
55
+ "payment_method": ["cash", "credit-card", "debit-card", "other"],
56
+ "card_last_four": "verbatim-string",
57
+ "amount_tendered": "number",
58
+ "change_due": "number"
59
+ }""",
60
+ "Basic bank statement": """{
61
+ "financial_institution": "verbatim-string",
62
+ "account_holder": "verbatim-string",
63
+ "account_number": "verbatim-string",
64
+ "statement_period": {
65
+ "start_date": "date",
66
+ "end_date": "date"
67
+ },
68
+ "opening_balance": "number",
69
+ "closing_balance": "number",
70
+ "currency": "currency",
71
+ "transaction_count": "integer",
72
+ "transactions": [
73
+ {
74
+ "date": "date",
75
+ "description": "verbatim-string",
76
+ "transaction_type": ["debit", "credit", "other"],
77
+ "amount": "number",
78
+ "balance": "number"
79
+ }
80
+ ]
81
+ }""",
82
+ "Advanced bank statement": """{
83
+ "financial_institution": {
84
+ "name": "verbatim-string",
85
+ "address": "verbatim-string",
86
+ "phone": "phone-number",
87
+ "website": "url"
88
+ },
89
+ "account_holder": {
90
+ "name": "verbatim-string",
91
+ "address": "verbatim-string"
92
+ },
93
+ "account": {
94
+ "type": "verbatim-string",
95
+ "number": "verbatim-string",
96
+ "branch_number": "verbatim-string",
97
+ "transit_number": "verbatim-string",
98
+ "routing_number": "verbatim-string",
99
+ "iban": "iban",
100
+ "bic": "bic"
101
+ },
102
+ "statement_period": {
103
+ "start_date": "date",
104
+ "end_date": "date"
105
+ },
106
+ "summary": {
107
+ "opening_balance": "number",
108
+ "total_credits": "number",
109
+ "total_debits": "number",
110
+ "total_fees": "number",
111
+ "total_interest": "number",
112
+ "closing_balance": "number",
113
+ "currency": "currency"
114
+ },
115
+ "transaction_count": "integer",
116
+ "transactions": [
117
+ {
118
+ "transaction_date": "date",
119
+ "posting_date": "date",
120
+ "description": "verbatim-string",
121
+ "reference_number": "verbatim-string",
122
+ "category": [
123
+ "deposit",
124
+ "withdrawal",
125
+ "transfer",
126
+ "payment",
127
+ "purchase",
128
+ "fee",
129
+ "interest",
130
+ "refund",
131
+ "other"
132
+ ],
133
+ "debit": "number",
134
+ "credit": "number",
135
+ "balance": "number",
136
+ "currency": "currency"
137
+ }
138
+ ]
139
+ }""",
140
+ "Invoice with line items": """{
141
+ "document_type": "verbatim-string",
142
+ "vendor": {
143
+ "name": "verbatim-string",
144
+ "address": "verbatim-string",
145
+ "email": "email-address",
146
+ "phone": "phone-number"
147
+ },
148
+ "invoice_number": "verbatim-string",
149
+ "invoice_date": "verbatim-string",
150
+ "line_items": [
151
+ {
152
+ "description": "verbatim-string",
153
+ "category": ["product", "service", "shipping", "tax", "other"],
154
+ "quantity": "integer",
155
+ "unit_price": "number",
156
+ "unit": "unit-code",
157
+ "amount": "number"
158
+ }
159
+ ],
160
+ "subtotal": "number",
161
+ "tax": "number",
162
+ "total": "number",
163
+ "currency": "currency"
164
+ }""",
165
+ "Advanced global record": """{
166
+ "summary": "string",
167
+ "reference_text": "verbatim-string",
168
+ "sequence_number": "integer",
169
+ "amount": "number",
170
+ "effective_date": "date",
171
+ "effective_time": "time",
172
+ "signed_at": "date-time",
173
+ "term": "duration",
174
+ "active": "boolean",
175
+ "country": "country",
176
+ "currency": "currency",
177
+ "language": "language",
178
+ "language_tag": "language-tag",
179
+ "script": "script",
180
+ "website": "url",
181
+ "email": "email-address",
182
+ "phone": "phone-number",
183
+ "iban": "iban",
184
+ "bic": "bic",
185
+ "measurement_unit": "unit-code",
186
+ "regions": {
187
+ "us": "region:US",
188
+ "fr": "region:FR",
189
+ "ie": "region:IE",
190
+ "gb": "region:GB",
191
+ "it": "region:IT",
192
+ "es": "region:ES",
193
+ "de": "region:DE",
194
+ "pt": "region:PT",
195
+ "ca": "region:CA",
196
+ "mx": "region:MX",
197
+ "br": "region:BR",
198
+ "au": "region:AU",
199
+ "jp": "region:JP",
200
+ "kr": "region:KR"
201
+ },
202
+ "contacts": [
203
+ {
204
+ "name": "verbatim-string",
205
+ "role": "string",
206
+ "email": "email-address"
207
+ }
208
+ ],
209
+ "status": ["draft", "active", "expired", "unknown"],
210
+ "applicable_labels": [["legal", "financial", "technical", "personal"]],
211
+ "keywords": ["string"]
212
+ }""",
213
+ }
214
+
215
+ default_structured_json_template = "Invoice with line items"
216
+
217
+
218
+ def select_structured_json_template(name):
219
+ return structured_json_templates[name]
220
+
221
+
222
+ processor = AutoProcessor.from_pretrained(
223
+ model_id,
224
+ trust_remote_code=True,
225
+ )
226
+
227
+ model = (
228
+ AutoModelForImageTextToText.from_pretrained(
229
+ model_id,
230
+ attn_implementation="sdpa",
231
+ dtype=torch.bfloat16,
232
+ trust_remote_code=True,
233
+ )
234
+ .to("cuda")
235
+ .eval()
236
+ )
237
+
238
+
239
+ def run_nuextract(messages, **chat_template_kwargs):
240
+ inputs = processor.apply_chat_template(
241
+ messages,
242
+ add_generation_prompt=True,
243
+ tokenize=True,
244
+ return_dict=True,
245
+ return_tensors="pt",
246
+ **chat_template_kwargs,
247
+ ).to(model.device)
248
+
249
+ with torch.inference_mode():
250
+ generated_ids = model.generate( # pyright: ignore[reportAttributeAccessIssue]
251
+ **inputs,
252
+ max_new_tokens=4096,
253
+ do_sample=False,
254
+ )
255
+
256
+ generated_ids = generated_ids[:, inputs.input_ids.shape[1] :]
257
+ return processor.batch_decode(
258
+ generated_ids,
259
+ skip_special_tokens=True,
260
+ clean_up_tokenization_spaces=False,
261
+ )[0].strip()
262
+
263
+
264
+ @spaces.GPU
265
+ def extract(image, text, template, enable_thinking):
266
+ result = run_nuextract(
267
+ [
268
+ {
269
+ "role": "user",
270
+ "content": [
271
+ {"type": "image", "image": image},
272
+ {"type": "text", "text": text},
273
+ ],
274
+ }
275
+ ],
276
+ mode="structured",
277
+ template=template,
278
+ enable_thinking=enable_thinking,
279
+ )
280
+
281
+ return json.loads(result)
282
+
283
+
284
+ @spaces.GPU
285
+ def generate_template(image):
286
+ result = run_nuextract(
287
+ [
288
+ {
289
+ "role": "user",
290
+ "content": [
291
+ {"type": "image", "image": image},
292
+ {
293
+ "type": "text",
294
+ "text": (
295
+ "Create a reusable structured extraction template grounded only "
296
+ "in the visible document. Include fields supported by the document, "
297
+ "represent repeated records as arrays, use NuExtract template leaf "
298
+ "types, and return only the JSON template."
299
+ ),
300
+ },
301
+ ],
302
+ }
303
+ ],
304
+ mode="template-generation",
305
+ )
306
+
307
+ return json.dumps(json.loads(result), indent=2)
308
+
309
+
310
+ with gr.Blocks(title="NuMarkApp") as demo:
311
+ gr.Markdown(
312
+ "# NuMarkApp\n"
313
+ "Extract structured JSON from an image and optional text using "
314
+ "[numind/NuExtract3](https://huggingface.co/numind/NuExtract3)."
315
+ )
316
+ with gr.Row():
317
+ with gr.Column():
318
+ image = gr.Image(label="Document image", type="pil", height=430)
319
+ text = gr.Textbox(
320
+ label="Document text (optional)",
321
+ placeholder="Add text to process alongside the image",
322
+ lines=3,
323
+ )
324
+ enable_thinking = gr.Checkbox(label="Enable thinking")
325
+ template_preset = gr.Dropdown(
326
+ choices=list(structured_json_templates),
327
+ value=default_structured_json_template,
328
+ label="JSON template preset",
329
+ info="Choose a starting structure, then edit it below.",
330
+ )
331
+ generate_template_button = gr.Button("Generate template from Image")
332
+ with gr.Accordion("Structured JSON template", open=False):
333
+ template = gr.Textbox(
334
+ label="Structured JSON template",
335
+ value=structured_json_templates[default_structured_json_template],
336
+ lines=18,
337
+ info="Used for structured extraction. Edit the example to match your document.",
338
+ )
339
+ run = gr.Button("Extract structured JSON", variant="primary")
340
+ with gr.Column():
341
+ structured_output = gr.JSON(
342
+ label="Structured JSON",
343
+ open=True,
344
+ show_indices=True,
345
+ )
346
+
347
+ gr.Examples(
348
+ examples=[
349
+ [
350
+ "Basic receipt",
351
+ str(
352
+ Path(__file__).parents[1]
353
+ / "data"
354
+ / "samples"
355
+ / "receipt-ocr-original.webp"
356
+ ),
357
+ "",
358
+ False,
359
+ ],
360
+ [
361
+ "Invoice with line items",
362
+ str(
363
+ Path(__file__).parents[1]
364
+ / "data"
365
+ / "samples"
366
+ / "invoice-with-items.png"
367
+ ),
368
+ "",
369
+ False,
370
+ ],
371
+ [
372
+ "Advanced bank statement",
373
+ str(
374
+ Path(__file__).parents[1]
375
+ / "data"
376
+ / "samples"
377
+ / "BankStatementChequing.png"
378
+ ),
379
+ "",
380
+ False,
381
+ ],
382
+ [
383
+ "Basic bank statement",
384
+ str(
385
+ Path(__file__).parents[1]
386
+ / "data"
387
+ / "samples"
388
+ / "bank statement blog image.webp"
389
+ ),
390
+ "",
391
+ False,
392
+ ],
393
+ *[
394
+ [
395
+ default_structured_json_template,
396
+ str(path),
397
+ "",
398
+ False,
399
+ ]
400
+ for path in sorted(
401
+ path
402
+ for path in (Path(__file__).parents[1] / "data" / "samples").glob(
403
+ "*.png"
404
+ )
405
+ if path.name
406
+ not in {"BankStatementChequing.png", "invoice-with-items.png"}
407
+ )
408
+ ],
409
+ ],
410
+ inputs=[template_preset, image, text, enable_thinking],
411
+ )
412
+
413
+ template_preset.change(
414
+ select_structured_json_template,
415
+ inputs=template_preset,
416
+ outputs=template,
417
+ )
418
+
419
+ generate_template_button.click(
420
+ generate_template,
421
+ inputs=image,
422
+ outputs=template,
423
+ api_name="generate_template",
424
+ )
425
+
426
+ run.click(
427
+ extract,
428
+ inputs=[image, text, template, enable_thinking],
429
+ outputs=structured_output,
430
+ api_name="extract",
431
+ )
432
+
433
 
434
  if __name__ == "__main__":
435
  demo.launch()
app/gradio.py DELETED
@@ -1,435 +0,0 @@
1
- # ruff: noqa: I001
2
-
3
- import spaces
4
-
5
- import json
6
- from pathlib import Path
7
-
8
- import gradio as gr
9
- import torch
10
- from transformers import AutoModelForImageTextToText, AutoProcessor
11
-
12
- model_id = "numind/NuExtract3"
13
-
14
- structured_json_templates = {
15
- "Basic receipt": """{
16
- "merchant": "verbatim-string",
17
- "purchase_date": "date",
18
- "purchase_time": "time",
19
- "total": "number",
20
- "currency": "currency",
21
- "item_count": "integer",
22
- "paid": "boolean",
23
- "payment_method": ["cash", "credit-card", "debit-card", "other"],
24
- "items": ["verbatim-string"]
25
- }""",
26
- "Advanced receipt": """{
27
- "merchant": {
28
- "name": "verbatim-string",
29
- "address": "verbatim-string",
30
- "phone": "phone-number"
31
- },
32
- "purchase_date": "date",
33
- "purchase_time": "time",
34
- "store_number": "verbatim-string",
35
- "operator_number": "verbatim-string",
36
- "terminal_number": "verbatim-string",
37
- "transaction_number": "verbatim-string",
38
- "items": [
39
- {
40
- "description": "verbatim-string",
41
- "sku": "verbatim-string",
42
- "quantity": "number",
43
- "unit": "unit-code",
44
- "unit_price": "number",
45
- "discount": "number",
46
- "line_total": "number",
47
- "tax_code": "verbatim-string"
48
- }
49
- ],
50
- "item_count": "integer",
51
- "subtotal": "number",
52
- "tax": "number",
53
- "total": "number",
54
- "currency": "currency",
55
- "payment_method": ["cash", "credit-card", "debit-card", "other"],
56
- "card_last_four": "verbatim-string",
57
- "amount_tendered": "number",
58
- "change_due": "number"
59
- }""",
60
- "Basic bank statement": """{
61
- "financial_institution": "verbatim-string",
62
- "account_holder": "verbatim-string",
63
- "account_number": "verbatim-string",
64
- "statement_period": {
65
- "start_date": "date",
66
- "end_date": "date"
67
- },
68
- "opening_balance": "number",
69
- "closing_balance": "number",
70
- "currency": "currency",
71
- "transaction_count": "integer",
72
- "transactions": [
73
- {
74
- "date": "date",
75
- "description": "verbatim-string",
76
- "transaction_type": ["debit", "credit", "other"],
77
- "amount": "number",
78
- "balance": "number"
79
- }
80
- ]
81
- }""",
82
- "Advanced bank statement": """{
83
- "financial_institution": {
84
- "name": "verbatim-string",
85
- "address": "verbatim-string",
86
- "phone": "phone-number",
87
- "website": "url"
88
- },
89
- "account_holder": {
90
- "name": "verbatim-string",
91
- "address": "verbatim-string"
92
- },
93
- "account": {
94
- "type": "verbatim-string",
95
- "number": "verbatim-string",
96
- "branch_number": "verbatim-string",
97
- "transit_number": "verbatim-string",
98
- "routing_number": "verbatim-string",
99
- "iban": "iban",
100
- "bic": "bic"
101
- },
102
- "statement_period": {
103
- "start_date": "date",
104
- "end_date": "date"
105
- },
106
- "summary": {
107
- "opening_balance": "number",
108
- "total_credits": "number",
109
- "total_debits": "number",
110
- "total_fees": "number",
111
- "total_interest": "number",
112
- "closing_balance": "number",
113
- "currency": "currency"
114
- },
115
- "transaction_count": "integer",
116
- "transactions": [
117
- {
118
- "transaction_date": "date",
119
- "posting_date": "date",
120
- "description": "verbatim-string",
121
- "reference_number": "verbatim-string",
122
- "category": [
123
- "deposit",
124
- "withdrawal",
125
- "transfer",
126
- "payment",
127
- "purchase",
128
- "fee",
129
- "interest",
130
- "refund",
131
- "other"
132
- ],
133
- "debit": "number",
134
- "credit": "number",
135
- "balance": "number",
136
- "currency": "currency"
137
- }
138
- ]
139
- }""",
140
- "Invoice with line items": """{
141
- "document_type": "verbatim-string",
142
- "vendor": {
143
- "name": "verbatim-string",
144
- "address": "verbatim-string",
145
- "email": "email-address",
146
- "phone": "phone-number"
147
- },
148
- "invoice_number": "verbatim-string",
149
- "invoice_date": "verbatim-string",
150
- "line_items": [
151
- {
152
- "description": "verbatim-string",
153
- "category": ["product", "service", "shipping", "tax", "other"],
154
- "quantity": "integer",
155
- "unit_price": "number",
156
- "unit": "unit-code",
157
- "amount": "number"
158
- }
159
- ],
160
- "subtotal": "number",
161
- "tax": "number",
162
- "total": "number",
163
- "currency": "currency"
164
- }""",
165
- "Advanced global record": """{
166
- "summary": "string",
167
- "reference_text": "verbatim-string",
168
- "sequence_number": "integer",
169
- "amount": "number",
170
- "effective_date": "date",
171
- "effective_time": "time",
172
- "signed_at": "date-time",
173
- "term": "duration",
174
- "active": "boolean",
175
- "country": "country",
176
- "currency": "currency",
177
- "language": "language",
178
- "language_tag": "language-tag",
179
- "script": "script",
180
- "website": "url",
181
- "email": "email-address",
182
- "phone": "phone-number",
183
- "iban": "iban",
184
- "bic": "bic",
185
- "measurement_unit": "unit-code",
186
- "regions": {
187
- "us": "region:US",
188
- "fr": "region:FR",
189
- "ie": "region:IE",
190
- "gb": "region:GB",
191
- "it": "region:IT",
192
- "es": "region:ES",
193
- "de": "region:DE",
194
- "pt": "region:PT",
195
- "ca": "region:CA",
196
- "mx": "region:MX",
197
- "br": "region:BR",
198
- "au": "region:AU",
199
- "jp": "region:JP",
200
- "kr": "region:KR"
201
- },
202
- "contacts": [
203
- {
204
- "name": "verbatim-string",
205
- "role": "string",
206
- "email": "email-address"
207
- }
208
- ],
209
- "status": ["draft", "active", "expired", "unknown"],
210
- "applicable_labels": [["legal", "financial", "technical", "personal"]],
211
- "keywords": ["string"]
212
- }""",
213
- }
214
-
215
- default_structured_json_template = "Invoice with line items"
216
-
217
-
218
- def select_structured_json_template(name):
219
- return structured_json_templates[name]
220
-
221
-
222
- processor = AutoProcessor.from_pretrained(
223
- model_id,
224
- trust_remote_code=True,
225
- )
226
-
227
- model = (
228
- AutoModelForImageTextToText.from_pretrained(
229
- model_id,
230
- attn_implementation="sdpa",
231
- dtype=torch.bfloat16,
232
- trust_remote_code=True,
233
- )
234
- .to("cuda")
235
- .eval()
236
- )
237
-
238
-
239
- def run_nuextract(messages, **chat_template_kwargs):
240
- inputs = processor.apply_chat_template(
241
- messages,
242
- add_generation_prompt=True,
243
- tokenize=True,
244
- return_dict=True,
245
- return_tensors="pt",
246
- **chat_template_kwargs,
247
- ).to(model.device)
248
-
249
- with torch.inference_mode():
250
- generated_ids = model.generate( # pyright: ignore[reportAttributeAccessIssue]
251
- **inputs,
252
- max_new_tokens=4096,
253
- do_sample=False,
254
- )
255
-
256
- generated_ids = generated_ids[:, inputs.input_ids.shape[1] :]
257
- return processor.batch_decode(
258
- generated_ids,
259
- skip_special_tokens=True,
260
- clean_up_tokenization_spaces=False,
261
- )[0].strip()
262
-
263
-
264
- @spaces.GPU
265
- def extract(image, text, template, enable_thinking):
266
- result = run_nuextract(
267
- [
268
- {
269
- "role": "user",
270
- "content": [
271
- {"type": "image", "image": image},
272
- {"type": "text", "text": text},
273
- ],
274
- }
275
- ],
276
- mode="structured",
277
- template=template,
278
- enable_thinking=enable_thinking,
279
- )
280
-
281
- return json.loads(result)
282
-
283
-
284
- @spaces.GPU
285
- def generate_template(image):
286
- result = run_nuextract(
287
- [
288
- {
289
- "role": "user",
290
- "content": [
291
- {"type": "image", "image": image},
292
- {
293
- "type": "text",
294
- "text": (
295
- "Create a reusable structured extraction template grounded only "
296
- "in the visible document. Include fields supported by the document, "
297
- "represent repeated records as arrays, use NuExtract template leaf "
298
- "types, and return only the JSON template."
299
- ),
300
- },
301
- ],
302
- }
303
- ],
304
- mode="template-generation",
305
- )
306
-
307
- return json.dumps(json.loads(result), indent=2)
308
-
309
-
310
- with gr.Blocks(title="NuMarkApp") as demo:
311
- gr.Markdown(
312
- "# NuMarkApp\n"
313
- "Extract structured JSON from an image and optional text using "
314
- "[numind/NuExtract3](https://huggingface.co/numind/NuExtract3)."
315
- )
316
- with gr.Row():
317
- with gr.Column():
318
- image = gr.Image(label="Document image", type="pil", height=430)
319
- text = gr.Textbox(
320
- label="Document text (optional)",
321
- placeholder="Add text to process alongside the image",
322
- lines=3,
323
- )
324
- enable_thinking = gr.Checkbox(label="Enable thinking")
325
- template_preset = gr.Dropdown(
326
- choices=list(structured_json_templates),
327
- value=default_structured_json_template,
328
- label="JSON template preset",
329
- info="Choose a starting structure, then edit it below.",
330
- )
331
- generate_template_button = gr.Button("Generate template from Image")
332
- with gr.Accordion("Structured JSON template", open=False):
333
- template = gr.Textbox(
334
- label="Structured JSON template",
335
- value=structured_json_templates[default_structured_json_template],
336
- lines=18,
337
- info="Used for structured extraction. Edit the example to match your document.",
338
- )
339
- run = gr.Button("Extract structured JSON", variant="primary")
340
- with gr.Column():
341
- structured_output = gr.JSON(
342
- label="Structured JSON",
343
- open=True,
344
- show_indices=True,
345
- )
346
-
347
- gr.Examples(
348
- examples=[
349
- [
350
- "Basic receipt",
351
- str(
352
- Path(__file__).parents[1]
353
- / "data"
354
- / "samples"
355
- / "receipt-ocr-original.webp"
356
- ),
357
- "",
358
- False,
359
- ],
360
- [
361
- "Invoice with line items",
362
- str(
363
- Path(__file__).parents[1]
364
- / "data"
365
- / "samples"
366
- / "invoice-with-items.png"
367
- ),
368
- "",
369
- False,
370
- ],
371
- [
372
- "Advanced bank statement",
373
- str(
374
- Path(__file__).parents[1]
375
- / "data"
376
- / "samples"
377
- / "BankStatementChequing.png"
378
- ),
379
- "",
380
- False,
381
- ],
382
- [
383
- "Basic bank statement",
384
- str(
385
- Path(__file__).parents[1]
386
- / "data"
387
- / "samples"
388
- / "bank statement blog image.webp"
389
- ),
390
- "",
391
- False,
392
- ],
393
- *[
394
- [
395
- default_structured_json_template,
396
- str(path),
397
- "",
398
- False,
399
- ]
400
- for path in sorted(
401
- path
402
- for path in (Path(__file__).parents[1] / "data" / "samples").glob(
403
- "*.png"
404
- )
405
- if path.name
406
- not in {"BankStatementChequing.png", "invoice-with-items.png"}
407
- )
408
- ],
409
- ],
410
- inputs=[template_preset, image, text, enable_thinking],
411
- )
412
-
413
- template_preset.change(
414
- select_structured_json_template,
415
- inputs=template_preset,
416
- outputs=template,
417
- )
418
-
419
- generate_template_button.click(
420
- generate_template,
421
- inputs=image,
422
- outputs=template,
423
- api_name="generate_template",
424
- )
425
-
426
- run.click(
427
- extract,
428
- inputs=[image, text, template, enable_thinking],
429
- outputs=structured_output,
430
- api_name="extract",
431
- )
432
-
433
-
434
- if __name__ == "__main__":
435
- demo.launch()