github-actions[bot] commited on
Commit
1383efa
·
0 Parent(s):

Sync from https://github.com/ryanlinjui/menu-text-detection

Browse files
.checkpoints/.gitkeep ADDED
File without changes
.env.example ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ HUGGINGFACE_TOKEN="HUGGINGFACE_TOKEN"
2
+ GEMINI_API_TOKEN="GEMINI_API_TOKEN"
3
+ OPENAI_API_TOKEN="OPENAI_API_TOKEN"
.github/workflows/sync.yml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face Spaces
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ jobs:
8
+ sync:
9
+ name: Sync
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout Repository
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Remove bad files
16
+ run: rm -rf examples assets
17
+
18
+ - name: Sync to Hugging Face Spaces
19
+ uses: JacobLinCool/huggingface-sync@v1
20
+ with:
21
+ github: ${{ secrets.GITHUB_TOKEN }}
22
+ user: ryanlinjui # Hugging Face username or organization name
23
+ space: menu-text-detection # Hugging Face space name
24
+ token: ${{ secrets.HF_TOKEN }} # Hugging Face token
25
+ python_version: 3.11 # Python version
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # mac
2
+ .DS_Store
3
+
4
+ # cache
5
+ __pycache__
6
+
7
+ # datasets
8
+ datasets
9
+
10
+ # papers
11
+ docs/papers
12
+
13
+ # uv
14
+ .venv
15
+
16
+ # gradio
17
+ .gradio
18
+
19
+ # env
20
+ .env
21
+
22
+ # checkpoint
23
+ .checkpoints/*
24
+ !.checkpoints/.gitkeep
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.11
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 RyanLin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: menu text detection
3
+ emoji: 🦄
4
+ colorFrom: indigo
5
+ colorTo: pink
6
+ sdk: gradio
7
+ python_version: 3.11
8
+ short_description: Extract structured menu information from images into JSON...
9
+ tags: [ "document-understanding","donut","fine-tuning","image-text-to-text","transformer" ]
10
+ ---
11
+
12
+ # Menu Text Detection System
13
+
14
+ Extract structured menu information from images into JSON using a fine-tuned E2E model or LLM.
15
+
16
+ [![Gradio Space Demo](https://img.shields.io/badge/GradioSpace-Demo-important?logo=huggingface)](https://huggingface.co/spaces/ryanlinjui/menu-text-detection)
17
+ [![Hugging Face Models & Datasets](https://img.shields.io/badge/HuggingFace-Models_&_Datasets-important?logo=huggingface)](https://huggingface.co/collections/ryanlinjui/menu-text-detection-670ccf527626bb004bbfb39b)
18
+
19
+ https://github.com/user-attachments/assets/80e5d54c-f2c8-4593-ad9b-499e5b71d8f6
20
+
21
+ ## 🚀 Features
22
+ ### Overview
23
+ Currently supports the following information from menu images:
24
+
25
+ - **Restaurant Name**
26
+ - **Business Hours**
27
+ - **Address**
28
+ - **Phone Number**
29
+ - **Dish Information**
30
+ - Name
31
+ - Price
32
+
33
+ > For the JSON schema, see [tools directory](./tools).
34
+
35
+ ### Supported Methods to Extract Menu Information
36
+ #### Fine-tuned E2E model and Training metrics
37
+ - [**Donut (Document Parsing Task)**](https://huggingface.co/ryanlinjui/donut-base-finetuned-menu) - Base model by [Clova AI (ECCV ’22)](https://github.com/clovaai/donut)
38
+
39
+ #### LLM Function Calling
40
+ - Google Gemini API
41
+ - OpenAI GPT API
42
+
43
+ ## 💻 Training / Fine-Tuning
44
+ ### Setup
45
+ Use [uv](https://github.com/astral-sh/uv) to set up the development environment:
46
+
47
+ ```bash
48
+ uv sync
49
+ ```
50
+
51
+ > or use `pip install -r requirements.txt` if it has any problems
52
+
53
+ ### Training Script (Datasets collecting, Fine-Tuning)
54
+ Please refer [`train.ipynb`](./train.ipynb). Use Jupyter Notebook for training:
55
+
56
+ ```bash
57
+ uv run jupyter-notebook
58
+ ```
59
+
60
+ > For VSCode users, please install Jupyter extension, then select `.venv/bin/python` as your kernel.
61
+
62
+ ### Run Demo Locally
63
+ ```bash
64
+ uv run python app.py
65
+ ```
app.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ from io import BytesIO
5
+ from typing import List
6
+
7
+ import gradio as gr
8
+ from PIL import Image
9
+ from dotenv import load_dotenv
10
+ from pillow_heif import register_heif_opener
11
+
12
+ from menu.llm import (
13
+ GeminiAPI,
14
+ OpenAIAPI
15
+ )
16
+ from menu.donut import DonutFinetuned
17
+
18
+ donut_finetuned = DonutFinetuned("ryanlinjui/donut-base-finetuned-menu")
19
+
20
+ register_heif_opener()
21
+ load_dotenv(override=True)
22
+ GEMINI_API_TOKEN = os.getenv("GEMINI_API_TOKEN", "")
23
+ OPENAI_API_TOKEN = os.getenv("OPENAI_API_TOKEN", "")
24
+
25
+ SOURCE_CODE_GH_URL = "https://github.com/ryanlinjui/menu-text-detection"
26
+ BADGE_URL = "https://img.shields.io/badge/GitHub_Code-Click_Here!!-default?logo=github"
27
+
28
+ GITHUB_RAW_URL = "https://raw.githubusercontent.com/ryanlinjui/menu-text-detection/main"
29
+ EXAMPLE_IMAGE_LIST = [
30
+ [f"{GITHUB_RAW_URL}/examples/menu-hd.jpg"],
31
+ [f"{GITHUB_RAW_URL}/examples/menu-vs.jpg"],
32
+ [f"{GITHUB_RAW_URL}/examples/menu-si.jpg"],
33
+ [f"{GITHUB_RAW_URL}/examples/menu-hd.jpg", f"{GITHUB_RAW_URL}/examples/menu-si.jpg"],
34
+ [f"{GITHUB_RAW_URL}/examples/menu-si.jpg", f"{GITHUB_RAW_URL}/examples/menu-vs.jpg", f"{GITHUB_RAW_URL}/examples/menu-hd.jpg", f"{GITHUB_RAW_URL}/examples/menu-vs.jpg"],
35
+ ]
36
+ FINETUNED_MODEL_LIST = [
37
+ "Donut (Document Parsing Task) Fine-tuned Model"
38
+ ]
39
+ LLM_MODEL_LIST = [
40
+ "gemini-2.5-pro",
41
+ "gemini-2.5-flash",
42
+ "gemini-2.0-flash",
43
+ "gpt-4.1",
44
+ "gpt-4o",
45
+ "o4-mini"
46
+ ]
47
+ CSS_STYLE = """
48
+ .image-panel {
49
+ display: flex;
50
+ flex-direction: column;
51
+ height: 600px;
52
+ }
53
+ .image-panel img {
54
+ object-fit: contain;
55
+ max-height: 550px;
56
+ max-width: 550px;
57
+ width: 100%;
58
+ }
59
+ .large-text textarea {
60
+ font-size: 20px !important;
61
+ height: 600px !important;
62
+ width: 100% !important;
63
+ }
64
+ .control-row {
65
+ margin-top: -10px !important;
66
+ margin-bottom: -10px !important;
67
+ align-items: center !important;
68
+ justify-content: center !important;
69
+ }
70
+ .page-info {
71
+ text-align: center !important;
72
+ font-size: 20px !important;
73
+ display: flex !important;
74
+ align-items: center !important;
75
+ justify-content: center !important;
76
+ height: 100% !important;
77
+ font-weight: 900 !important;
78
+ color: #374151; /* Darker gray for clarity */
79
+ }
80
+ .page-info p {
81
+ margin: 0 !important;
82
+ width: 100% !important;
83
+ text-align: center !important;
84
+ }
85
+ .upload-btn {
86
+ margin-top: 2px !important;
87
+ background-color: #e0f2fe !important; /* Light blue background */
88
+ color: #0369a1 !important; /* Dark blue text */
89
+ border: 1px solid #0ea5e9 !important;
90
+ }
91
+ .upload-btn:hover {
92
+ background-color: #bae6fd !important;
93
+ }
94
+ .clear-btn {
95
+ margin-top: 2px !important;
96
+ }
97
+ .image-container {
98
+ height: 650px !important;
99
+ display: flex;
100
+ flex-direction: column;
101
+ border: 1px solid #e5e7eb;
102
+ border-radius: 8px;
103
+ padding: 4px;
104
+ }
105
+ """
106
+
107
+ def handle(images: List[str], model: str, api_token: str) -> str:
108
+ if not images:
109
+ raise gr.Error("Please upload an image first.")
110
+
111
+ # Convert file paths or URLs to PIL Images
112
+ pil_images = []
113
+ for img in images:
114
+ if img.startswith("http://") or img.startswith("https://"):
115
+ try:
116
+ response = requests.get(img)
117
+ response.raise_for_status()
118
+ pil_images.append(Image.open(BytesIO(response.content)))
119
+ except Exception as e:
120
+ raise gr.Error(f"Failed to load image from URL: {str(e)}")
121
+ else:
122
+ pil_images.append(Image.open(img))
123
+
124
+ if model == FINETUNED_MODEL_LIST[0]:
125
+ result = donut_finetuned.predict(pil_images[0])
126
+
127
+ elif model in LLM_MODEL_LIST:
128
+ if len(api_token) < 10:
129
+ raise gr.Error(f"Please provide a valid token for {model}.")
130
+ try:
131
+ if model in LLM_MODEL_LIST[:3]:
132
+ result = GeminiAPI.call(pil_images, model, api_token)
133
+ else:
134
+ result = OpenAIAPI.call(pil_images, model, api_token)
135
+ except Exception as e:
136
+ raise gr.Error(f"Failed to process with API model {model}: {str(e)}")
137
+ else:
138
+ raise gr.Error("Invalid model selection. Please choose a valid model.")
139
+
140
+ return json.dumps(result, indent=4, ensure_ascii=False, sort_keys=True)
141
+
142
+ def UserInterface() -> gr.Interface:
143
+ with gr.Blocks(delete_cache=(86400, 86400), css=CSS_STYLE) as gradio_interface:
144
+ gr.HTML(f'<a href="{SOURCE_CODE_GH_URL}"><img src="{BADGE_URL}" alt="GitHub Code"/></a>')
145
+ gr.Markdown("# Menu Text Detection")
146
+
147
+ images_state = gr.State([])
148
+ current_index_state = gr.State(0)
149
+
150
+ with gr.Row():
151
+ with gr.Column(scale=1, min_width=500):
152
+ gr.Markdown("## 📷 Menu Image")
153
+
154
+ with gr.Column(elem_classes="image-container"):
155
+ menu_image_display = gr.Image(
156
+ label="Input menu image",
157
+ type="filepath",
158
+ elem_classes="image-panel",
159
+ interactive=False,
160
+ show_download_button=False,
161
+ show_label=True
162
+ )
163
+ with gr.Row(elem_classes="control-row"):
164
+ prev_btn = gr.Button("◀️ Previous", variant="secondary", scale=1)
165
+ with gr.Column(scale=2, min_width=50):
166
+ page_info = gr.Markdown("Page 1 / 1", elem_classes="page-info")
167
+ next_btn = gr.Button("Next ▶️", variant="secondary", scale=1)
168
+
169
+ with gr.Row():
170
+ upload_btn = gr.UploadButton(
171
+ "📷 Upload Menu Images",
172
+ file_types=["image"],
173
+ file_count="multiple",
174
+ scale=3,
175
+ elem_classes="upload-btn",
176
+ variant="primary"
177
+ )
178
+ clear_btn = gr.Button("🗑️ Remove", variant="stop", scale=1, elem_classes="clear-btn")
179
+
180
+ gr.Markdown("## 🤖 Model Selection")
181
+ model_choice_dropdown = gr.Dropdown(
182
+ choices=FINETUNED_MODEL_LIST + LLM_MODEL_LIST,
183
+ value=FINETUNED_MODEL_LIST[0],
184
+ label="Select Text Detection Model"
185
+ )
186
+
187
+ api_token_textbox = gr.Textbox(
188
+ label="API Token",
189
+ placeholder="Enter your API token here...",
190
+ type="password",
191
+ visible=False
192
+ )
193
+
194
+ generate_button = gr.Button("Generate Menu Information", variant="primary")
195
+ example_receiver = gr.Image(visible=False, label="Example Preview", type="filepath")
196
+
197
+ examples_component = gr.Examples(
198
+ examples=[[img_list[0]] for img_list in EXAMPLE_IMAGE_LIST],
199
+ inputs=example_receiver,
200
+ label="Example Menu Images"
201
+ )
202
+
203
+ with gr.Column(scale=1):
204
+ gr.Markdown("## 🍽️ Menu Info")
205
+ menu_json_textbox = gr.Textbox(
206
+ label="Output JSON",
207
+ interactive=True,
208
+ text_align="left",
209
+ elem_classes="large-text"
210
+ )
211
+
212
+ def update_display(images, index):
213
+ if not images:
214
+ return None, "Page 1 / 1"
215
+ idx = max(0, min(index, len(images) - 1))
216
+ return images[idx], f"Page {idx + 1} / {len(images)}"
217
+
218
+ def on_upload(new_files, current_images):
219
+ if current_images is None:
220
+ current_images = []
221
+ if new_files:
222
+ new_paths = [f.name for f in new_files]
223
+ current_images.extend(new_paths)
224
+ new_index = len(current_images) - 1
225
+ img, info = update_display(current_images, new_index)
226
+ return current_images, new_index, img, info
227
+
228
+ upload_btn.upload(
229
+ fn=on_upload,
230
+ inputs=[upload_btn, images_state],
231
+ outputs=[images_state, current_index_state, menu_image_display, page_info]
232
+ )
233
+
234
+ def on_clear(images, index):
235
+ if not images:
236
+ return [], 0, None, "Page 1 / 1"
237
+
238
+ new_images = list(images)
239
+ if 0 <= index < len(new_images):
240
+ new_images.pop(index)
241
+
242
+ if not new_images:
243
+ return [], 0, None, "Page 1 / 1"
244
+
245
+ new_index = index
246
+ if new_index >= len(new_images):
247
+ new_index = len(new_images) - 1
248
+
249
+ img, info = update_display(new_images, new_index)
250
+ return new_images, new_index, img, info
251
+
252
+ clear_btn.click(
253
+ fn=on_clear,
254
+ inputs=[images_state, current_index_state],
255
+ outputs=[images_state, current_index_state, menu_image_display, page_info]
256
+ )
257
+
258
+ def on_prev(images, index):
259
+ if not images:
260
+ return 0, None, "Page 1 / 1"
261
+ new_index = max(0, index - 1)
262
+ img, info = update_display(images, new_index)
263
+ return new_index, img, info
264
+
265
+ def on_next(images, index):
266
+ if not images:
267
+ return 0, None, "Page 1 / 1"
268
+ new_index = min(len(images) - 1, index + 1)
269
+ img, info = update_display(images, new_index)
270
+ return new_index, img, info
271
+
272
+ prev_btn.click(on_prev, [images_state, current_index_state], [current_index_state, menu_image_display, page_info])
273
+ next_btn.click(on_next, [images_state, current_index_state], [current_index_state, menu_image_display, page_info])
274
+
275
+ def on_example_click(evt: gr.SelectData):
276
+ if evt.index is None:
277
+ return [], 0, None, "Page 1 / 1"
278
+
279
+ # Retrieve the full batch based on the clicked index
280
+ if 0 <= evt.index < len(EXAMPLE_IMAGE_LIST):
281
+ current_images = EXAMPLE_IMAGE_LIST[evt.index]
282
+ else:
283
+ current_images = []
284
+
285
+ new_index = 0
286
+ img, info = update_display(current_images, new_index)
287
+ return current_images, new_index, img, info
288
+
289
+ examples_component.dataset.select(
290
+ fn=on_example_click,
291
+ inputs=None,
292
+ outputs=[images_state, current_index_state, menu_image_display, page_info]
293
+ )
294
+
295
+ def update_token_visibility(choice):
296
+ if choice in LLM_MODEL_LIST:
297
+ current_token = ""
298
+ if choice in LLM_MODEL_LIST[:3]:
299
+ current_token = GEMINI_API_TOKEN
300
+ else:
301
+ current_token = OPENAI_API_TOKEN
302
+ return gr.update(visible=True, value=current_token)
303
+ else:
304
+ return gr.update(visible=False)
305
+
306
+ model_choice_dropdown.change(
307
+ fn=update_token_visibility,
308
+ inputs=model_choice_dropdown,
309
+ outputs=api_token_textbox
310
+ )
311
+
312
+ generate_button.click(
313
+ fn=handle,
314
+ inputs=[images_state, model_choice_dropdown, api_token_textbox],
315
+ outputs=menu_json_textbox
316
+ )
317
+
318
+ return gradio_interface
319
+
320
+ if __name__ == "__main__":
321
+ demo = UserInterface()
322
+ demo.launch()
menu/donut.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This file is modified from the HuggingFace transformers tutorial script for fine-tuning Donut on a custom dataset.
3
+ It's defined from `.ipynb` to the module implementation for better reusability and maintainability.
4
+ Reference: https://github.com/NielsRogge/Transformers-Tutorials/blob/master/Donut/CORD/Fine_tune_Donut_on_a_custom_dataset_(CORD)_with_PyTorch_Lightning.ipynb
5
+ """
6
+
7
+ import re
8
+ import random
9
+ from typing import Any, List, Tuple, Dict
10
+
11
+ import torch
12
+ import numpy as np
13
+ from PIL import Image
14
+ from tqdm.auto import tqdm
15
+ from nltk import edit_distance
16
+ import pytorch_lightning as pl
17
+ from datasets import DatasetDict
18
+ from donut import JSONParseEvaluator
19
+ from huggingface_hub import upload_folder
20
+ from pillow_heif import register_heif_opener
21
+ from pytorch_lightning.callbacks import Callback
22
+ from pytorch_lightning.loggers import TensorBoardLogger
23
+ from torch.utils.data import (
24
+ Dataset,
25
+ DataLoader
26
+ )
27
+ from transformers import (
28
+ DonutProcessor,
29
+ VisionEncoderDecoderModel,
30
+ VisionEncoderDecoderConfig
31
+ )
32
+
33
+ TASK_PROMPT_NAME = "<s_menu-text-detection>"
34
+ register_heif_opener()
35
+
36
+ class DonutFinetuned:
37
+ def __init__(self, pretrained_model_repo_id: str = "ryanlinjui/donut-test"):
38
+ self.device = (
39
+ "cuda"
40
+ if torch.cuda.is_available()
41
+ else "mps" if torch.backends.mps.is_available() else "cpu"
42
+ )
43
+ self.processor = DonutProcessor.from_pretrained(pretrained_model_repo_id)
44
+ self.model = VisionEncoderDecoderModel.from_pretrained(pretrained_model_repo_id)
45
+ self.model.eval()
46
+ self.model.to(self.device)
47
+ print(f"Using {self.device} device")
48
+
49
+ def predict(self, image: Image.Image) -> Dict[str, Any]:
50
+ # prepare encoder inputs
51
+ pixel_values = self.processor(image.convert("RGB"), return_tensors="pt").pixel_values
52
+ pixel_values = pixel_values.to(self.device)
53
+
54
+ # prepare decoder inputs
55
+ decoder_input_ids = self.processor.tokenizer(TASK_PROMPT_NAME, add_special_tokens=False, return_tensors="pt").input_ids
56
+ decoder_input_ids = decoder_input_ids.to(self.device)
57
+
58
+ # autoregressively generate sequence
59
+ outputs = self.model.generate(
60
+ pixel_values,
61
+ decoder_input_ids=decoder_input_ids,
62
+ max_length=self.model.decoder.config.max_position_embeddings,
63
+ early_stopping=True,
64
+ pad_token_id=self.processor.tokenizer.pad_token_id,
65
+ eos_token_id=self.processor.tokenizer.eos_token_id,
66
+ use_cache=True,
67
+ num_beams=1,
68
+ bad_words_ids=[[self.processor.tokenizer.unk_token_id]],
69
+ return_dict_in_generate=True
70
+ )
71
+
72
+ # turn into JSON
73
+ seq = self.processor.batch_decode(outputs.sequences)[0]
74
+ seq = seq.replace(self.processor.tokenizer.eos_token, "").replace(self.processor.tokenizer.pad_token, "")
75
+ seq = re.sub(r"<.*?>", "", seq, count=1).strip() # remove first task start token
76
+ seq = self.processor.token2json(seq)
77
+ return seq
78
+
79
+ def evaluate(self, dataset: Dataset, ground_truth_key: str = "ground_truth") -> Tuple[Dict[str, Any], List[Any]]:
80
+ output_list = []
81
+ accs = []
82
+ ted_accs = []
83
+ f1_accs = []
84
+
85
+ for idx, sample in tqdm(enumerate(dataset), total=len(dataset)):
86
+ seq = self.predict(sample["image"])
87
+ ground_truth = sample[ground_truth_key]
88
+
89
+ # Original JSON accuracy
90
+ evaluator = JSONParseEvaluator()
91
+ score = evaluator.cal_acc(seq, ground_truth)
92
+ accs.append(score)
93
+ output_list.append(seq)
94
+
95
+ # TED (Tree Edit Distance) Accuracy
96
+ # Convert predictions and ground truth to string format for comparison
97
+ pred_str = str(seq) if seq else ""
98
+ gt_str = str(ground_truth) if ground_truth else ""
99
+
100
+ # Calculate normalized edit distance (1 - normalized_edit_distance = accuracy)
101
+ if len(pred_str) == 0 and len(gt_str) == 0:
102
+ ted_acc = 1.0
103
+ elif len(pred_str) == 0 or len(gt_str) == 0:
104
+ ted_acc = 0.0
105
+ else:
106
+ edit_dist = edit_distance(pred_str, gt_str)
107
+ max_len = max(len(pred_str), len(gt_str))
108
+ ted_acc = 1 - (edit_dist / max_len)
109
+ ted_accs.append(ted_acc)
110
+
111
+ # F1 Score Accuracy (character-level)
112
+ if len(pred_str) == 0 and len(gt_str) == 0:
113
+ f1_acc = 1.0
114
+ elif len(pred_str) == 0 or len(gt_str) == 0:
115
+ f1_acc = 0.0
116
+ else:
117
+ # Character-level precision and recall
118
+ pred_chars = set(pred_str)
119
+ gt_chars = set(gt_str)
120
+
121
+ if len(pred_chars) == 0:
122
+ precision = 0.0
123
+ else:
124
+ precision = len(pred_chars.intersection(gt_chars)) / len(pred_chars)
125
+
126
+ if len(gt_chars) == 0:
127
+ recall = 0.0
128
+ else:
129
+ recall = len(pred_chars.intersection(gt_chars)) / len(gt_chars)
130
+
131
+ if precision + recall == 0:
132
+ f1_acc = 0.0
133
+ else:
134
+ f1_acc = 2 * (precision * recall) / (precision + recall)
135
+ f1_accs.append(f1_acc)
136
+
137
+ scores = {
138
+ "accuracies": accs,
139
+ "mean_accuracy": np.mean(accs),
140
+ "ted_accuracies": ted_accs,
141
+ "mean_ted_accuracy": np.mean(ted_accs),
142
+ "f1_accuracies": f1_accs,
143
+ "mean_f1_accuracy": np.mean(f1_accs),
144
+ "length": len(accs)
145
+ }
146
+ return scores, output_list
147
+
148
+ class DonutTrainer:
149
+ processor = None
150
+ max_length = 768
151
+ image_size = [1280, 960]
152
+ added_tokens = []
153
+ train_dataloader = None
154
+ val_dataloader = None
155
+ huggingface_model_id = None
156
+
157
+ class DonutDataset(Dataset):
158
+ """
159
+ PyTorch Dataset for Donut. This class takes a HuggingFace Dataset as input.
160
+
161
+ Each row, consists of image path(png/jpg/jpeg) and gt data (json/jsonl/txt),
162
+ and it will be converted into pixel_values (vectorized image) and labels (input_ids of the tokenized string).
163
+
164
+ Args:
165
+ dataset: HuggingFace DatasetDict containing the dataset to be used
166
+ max_length: the max number of tokens for the target sequences
167
+ split: whether to load "train", "validation" or "test" split
168
+ ignore_id: ignore_index for torch.nn.CrossEntropyLoss
169
+ task_start_token: the special token to be fed to the decoder to conduct the target task
170
+ prompt_end_token: the special token at the end of the sequences
171
+ sort_json_key: whether or not to sort the JSON keys
172
+ """
173
+
174
+ def __init__(
175
+ self,
176
+ dataset: DatasetDict,
177
+ ground_truth_key: str,
178
+ max_length: int,
179
+ split: str = "train",
180
+ ignore_id: int = -100,
181
+ task_start_token: str = "<s>",
182
+ prompt_end_token: str = None,
183
+ sort_json_key: bool = True,
184
+ ):
185
+ super().__init__()
186
+
187
+ self.dataset = dataset[split]
188
+ self.ground_truth_key = ground_truth_key
189
+ self.max_length = max_length
190
+ self.split = split
191
+ self.ignore_id = ignore_id
192
+ self.task_start_token = task_start_token
193
+ self.prompt_end_token = prompt_end_token if prompt_end_token else task_start_token
194
+ self.sort_json_key = sort_json_key
195
+
196
+ self.dataset_length = len(self.dataset)
197
+
198
+ self.gt_token_sequences = []
199
+ for sample in self.dataset:
200
+ ground_truth = sample[self.ground_truth_key]
201
+ self.gt_token_sequences.append(
202
+ [
203
+ self.json2token(
204
+ gt_json,
205
+ update_special_tokens_for_json_key=self.split == "train",
206
+ sort_json_key=self.sort_json_key,
207
+ )
208
+ + DonutTrainer.processor.tokenizer.eos_token
209
+ for gt_json in [ground_truth] # load json from list of json
210
+ ]
211
+ )
212
+
213
+ self.add_tokens([self.task_start_token, self.prompt_end_token])
214
+ self.prompt_end_token_id = DonutTrainer.processor.tokenizer.convert_tokens_to_ids(self.prompt_end_token)
215
+
216
+ def json2token(self, obj: Any, update_special_tokens_for_json_key: bool = True, sort_json_key: bool = True):
217
+ """
218
+ Convert an ordered JSON object into a token sequence
219
+ """
220
+ if type(obj) == dict:
221
+ if len(obj) == 1 and "text_sequence" in obj:
222
+ return obj["text_sequence"]
223
+ else:
224
+ output = ""
225
+ if sort_json_key:
226
+ keys = sorted(obj.keys(), reverse=True)
227
+ else:
228
+ keys = obj.keys()
229
+ for k in keys:
230
+ if update_special_tokens_for_json_key:
231
+ self.add_tokens([fr"<s_{k}>", fr"</s_{k}>"])
232
+ output += (
233
+ fr"<s_{k}>"
234
+ + self.json2token(obj[k], update_special_tokens_for_json_key, sort_json_key)
235
+ + fr"</s_{k}>"
236
+ )
237
+ return output
238
+ elif type(obj) == list:
239
+ return r"<sep/>".join(
240
+ [self.json2token(item, update_special_tokens_for_json_key, sort_json_key) for item in obj]
241
+ )
242
+ else:
243
+ obj = str(obj)
244
+ if f"<{obj}/>" in DonutTrainer.added_tokens:
245
+ obj = f"<{obj}/>" # for categorical special tokens
246
+ return obj
247
+
248
+ def add_tokens(self, list_of_tokens: List[str]):
249
+ """
250
+ Add special tokens to tokenizer and resize the token embeddings of the decoder
251
+ """
252
+ newly_added_num = DonutTrainer.processor.tokenizer.add_tokens(list_of_tokens)
253
+ if newly_added_num > 0:
254
+ DonutTrainer.model.decoder.resize_token_embeddings(len(DonutTrainer.processor.tokenizer))
255
+ DonutTrainer.added_tokens.extend(list_of_tokens)
256
+
257
+ def __len__(self) -> int:
258
+ return self.dataset_length
259
+
260
+ def __getitem__(self, idx: int) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
261
+ """
262
+ Load image from image_path of given dataset_path and convert into input_tensor and labels
263
+ Convert gt data into input_ids (tokenized string)
264
+ Returns:
265
+ input_tensor : preprocessed image
266
+ input_ids : tokenized gt_data
267
+ labels : masked labels (model doesn't need to predict prompt and pad token)
268
+ """
269
+ sample = self.dataset[idx]
270
+
271
+ # inputs
272
+ pixel_values = DonutTrainer.processor(sample["image"], random_padding=self.split == "train", return_tensors="pt").pixel_values
273
+ pixel_values = pixel_values.squeeze()
274
+
275
+ # targets
276
+ target_sequence = random.choice(self.gt_token_sequences[idx]) # can be more than one, e.g., DocVQA Task 1
277
+ input_ids = DonutTrainer.processor.tokenizer(
278
+ target_sequence,
279
+ add_special_tokens=False,
280
+ max_length=self.max_length,
281
+ padding="max_length",
282
+ truncation=True,
283
+ return_tensors="pt",
284
+ )["input_ids"].squeeze(0)
285
+
286
+ labels = input_ids.clone()
287
+ labels[labels == DonutTrainer.processor.tokenizer.pad_token_id] = self.ignore_id # model doesn't need to predict pad token
288
+ # labels[: torch.nonzero(labels == self.prompt_end_token_id).sum() + 1] = self.ignore_id # model doesn't need to predict prompt (for VQA)
289
+ return pixel_values, labels, target_sequence
290
+
291
+
292
+ class DonutModelPLModule(pl.LightningModule):
293
+ def __init__(self, config, processor, model):
294
+ super().__init__()
295
+ self.config = config
296
+ self.processor = processor
297
+ self.model = model
298
+
299
+ def training_step(self, batch, batch_idx):
300
+ pixel_values, labels, _ = batch
301
+
302
+ outputs = self.model(pixel_values, labels=labels)
303
+ loss = outputs.loss
304
+ self.log("train_loss", loss)
305
+ return loss
306
+
307
+ def validation_step(self, batch, batch_idx, dataset_idx=0):
308
+ pixel_values, labels, answers = batch
309
+ batch_size = pixel_values.shape[0]
310
+ # we feed the prompt to the model
311
+ decoder_input_ids = torch.full((batch_size, 1), self.model.config.decoder_start_token_id, device=self.device)
312
+
313
+ outputs = self.model.generate(pixel_values,
314
+ decoder_input_ids=decoder_input_ids,
315
+ max_length=DonutTrainer.max_length,
316
+ early_stopping=True,
317
+ pad_token_id=self.processor.tokenizer.pad_token_id,
318
+ eos_token_id=self.processor.tokenizer.eos_token_id,
319
+ use_cache=True,
320
+ num_beams=1,
321
+ bad_words_ids=[[self.processor.tokenizer.unk_token_id]],
322
+ return_dict_in_generate=True,)
323
+
324
+ predictions = []
325
+ for seq in self.processor.tokenizer.batch_decode(outputs.sequences):
326
+ seq = seq.replace(self.processor.tokenizer.eos_token, "").replace(self.processor.tokenizer.pad_token, "")
327
+ seq = re.sub(r"<.*?>", "", seq, count=1).strip() # remove first task start token
328
+ predictions.append(seq)
329
+
330
+ scores = []
331
+ for pred, answer in zip(predictions, answers):
332
+ pred = re.sub(r"(?:(?<=>) | (?=</s_))", "", pred)
333
+ # NOT NEEDED ANYMORE
334
+ # answer = re.sub(r"<.*?>", "", answer, count=1)
335
+ answer = answer.replace(self.processor.tokenizer.eos_token, "")
336
+ scores.append(edit_distance(pred, answer) / max(len(pred), len(answer)))
337
+
338
+ if self.config.get("verbose", False) and len(scores) == 1:
339
+ print(f"Prediction: {pred}")
340
+ print(f" Answer: {answer}")
341
+ print(f" Normed ED: {scores[0]}")
342
+
343
+ val_edit_distance = np.mean(scores)
344
+ self.log("val_edit_distance", val_edit_distance)
345
+ print(f"Validation Edit Distance: {val_edit_distance}")
346
+
347
+ return scores
348
+
349
+ def configure_optimizers(self):
350
+ # you could also add a learning rate scheduler if you want
351
+ optimizer = torch.optim.Adam(self.parameters(), lr=self.config.get("lr"))
352
+
353
+ return optimizer
354
+
355
+ def train_dataloader(self):
356
+ return DonutTrainer.train_dataloader
357
+
358
+ def val_dataloader(self):
359
+ return DonutTrainer.val_dataloader
360
+
361
+ class PushToHubCallback(Callback):
362
+ def on_train_epoch_end(self, trainer, pl_module):
363
+ print(f"Pushing model to the hub, epoch {trainer.current_epoch}")
364
+ pl_module.model.push_to_hub(DonutTrainer.huggingface_model_id, commit_message=f"Training in progress, epoch {trainer.current_epoch}")
365
+ self._upload_logs(trainer.logger.log_dir, trainer.current_epoch)
366
+
367
+ def on_train_end(self, trainer, pl_module):
368
+ print(f"Pushing model to the hub after training")
369
+ pl_module.processor.push_to_hub(DonutTrainer.huggingface_model_id,commit_message=f"Training done")
370
+ pl_module.model.push_to_hub(DonutTrainer.huggingface_model_id, commit_message=f"Training done")
371
+ self._upload_logs(trainer.logger.log_dir, "final")
372
+
373
+ def _upload_logs(self, log_dir: str, epoch_info):
374
+ try:
375
+ print(f"Attempting to upload logs from: {log_dir}")
376
+ upload_folder(folder_path=log_dir, repo_id=DonutTrainer.huggingface_model_id,
377
+ path_in_repo="tensorboard_logs",
378
+ commit_message=f"Upload logs - epoch {epoch_info}", ignore_patterns=["*.tmp", "*.lock"])
379
+ print(f"Successfully uploaded logs for epoch {epoch_info}")
380
+ except Exception as e:
381
+ print(f"Failed to upload logs: {e}")
382
+ pass
383
+
384
+ @classmethod
385
+ def train(
386
+ cls,
387
+ dataset: DatasetDict,
388
+ pretrained_model_repo_id: str,
389
+ huggingface_model_id: str,
390
+ epochs: int,
391
+ train_batch_size: int,
392
+ val_batch_size: int,
393
+ learning_rate: float,
394
+ val_check_interval: float,
395
+ check_val_every_n_epoch: int,
396
+ gradient_clip_val: float,
397
+ num_training_samples_per_epoch: int,
398
+ num_nodes: int,
399
+ warmup_steps: int,
400
+ ground_truth_key: str = "ground_truth"
401
+ ):
402
+ cls.huggingface_model_id = huggingface_model_id
403
+ config = VisionEncoderDecoderConfig.from_pretrained(pretrained_model_repo_id)
404
+ config.encoder.image_size = cls.image_size
405
+ config.decoder.max_length = cls.max_length
406
+
407
+ cls.processor = DonutProcessor.from_pretrained(pretrained_model_repo_id)
408
+ cls.model = VisionEncoderDecoderModel.from_pretrained(pretrained_model_repo_id, config=config)
409
+ cls.processor.image_processor.size = cls.image_size[::-1]
410
+ cls.processor.image_processor.do_align_long_axis = False
411
+
412
+ train_dataset = cls.DonutDataset(
413
+ dataset=dataset,
414
+ ground_truth_key=ground_truth_key,
415
+ max_length=cls.max_length,
416
+ split="train",
417
+ task_start_token=TASK_PROMPT_NAME,
418
+ prompt_end_token=TASK_PROMPT_NAME,
419
+ sort_json_key=True
420
+ )
421
+ val_dataset = cls.DonutDataset(
422
+ dataset=dataset,
423
+ ground_truth_key=ground_truth_key,
424
+ max_length=cls.max_length,
425
+ split="validation",
426
+ task_start_token=TASK_PROMPT_NAME,
427
+ prompt_end_token=TASK_PROMPT_NAME,
428
+ sort_json_key=True
429
+ )
430
+
431
+ cls.model.config.pad_token_id = cls.processor.tokenizer.pad_token_id
432
+ cls.model.config.decoder_start_token_id = cls.processor.tokenizer.convert_tokens_to_ids([TASK_PROMPT_NAME])[0]
433
+
434
+ cls.train_dataloader = DataLoader(train_dataset, batch_size=1, shuffle=True, num_workers=4)
435
+ cls.val_dataloader = DataLoader(val_dataset, batch_size=1, shuffle=False, num_workers=4)
436
+
437
+ config = {
438
+ "max_epochs": epochs,
439
+ "val_check_interval": val_check_interval, # how many times we want to validate during an epoch
440
+ "check_val_every_n_epoch": check_val_every_n_epoch,
441
+ "gradient_clip_val": gradient_clip_val,
442
+ "num_training_samples_per_epoch": num_training_samples_per_epoch,
443
+ "lr": learning_rate,
444
+ "train_batch_sizes": [train_batch_size],
445
+ "val_batch_sizes": [val_batch_size],
446
+ # "seed":2022,
447
+ "num_nodes": num_nodes,
448
+ "warmup_steps": warmup_steps, # 10%
449
+ "result_path": "./.checkpoints",
450
+ "verbose": True,
451
+ }
452
+ model_module = cls.DonutModelPLModule(config, cls.processor, cls.model)
453
+
454
+ device = (
455
+ "cuda"
456
+ if torch.cuda.is_available()
457
+ else "mps" if torch.backends.mps.is_available() else "cpu"
458
+ )
459
+ print(f"Using {device} device")
460
+ trainer = pl.Trainer(
461
+ accelerator="gpu" if device == "cuda" else "mps" if device == "mps" else "cpu",
462
+ devices=1 if device == "cuda" else 0,
463
+ max_epochs=config.get("max_epochs"),
464
+ val_check_interval=config.get("val_check_interval"),
465
+ check_val_every_n_epoch=config.get("check_val_every_n_epoch"),
466
+ gradient_clip_val=config.get("gradient_clip_val"),
467
+ precision=16 if device == "cuda" else 32, # we'll use mixed precision if device == "cuda"
468
+ num_sanity_val_steps=0,
469
+ logger=TensorBoardLogger(save_dir="./.checkpoints", name="donut_training", version=None),
470
+ callbacks=[cls.PushToHubCallback()]
471
+ )
472
+ trainer.fit(model_module)
menu/llm/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .gemini import GeminiAPI
2
+ from .openai import OpenAIAPI
menu/llm/base.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from abc import ABC, abstractmethod
3
+
4
+ from PIL import Image
5
+
6
+ PROMPT = "The provided images display a menu. IMPORTANT: There may be MULTIPLE images representing different pages. You MUST examine EVERY image provided and combine all extracted information into the final result. Do not miss any dishes from any page."
7
+
8
+ class LLMBase(ABC):
9
+ @classmethod
10
+ @abstractmethod
11
+ def call(cls, images: List[Image.Image], model: str, token: str) -> dict:
12
+ raise NotImplementedError
menu/llm/gemini.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from typing import List
3
+
4
+ from PIL import Image
5
+ from google import genai
6
+ from google.genai import types
7
+
8
+ from .base import LLMBase, PROMPT
9
+
10
+ FUNCTION_CALL = json.load(open("tools/schema_gemini.json", "r"))
11
+
12
+ class GeminiAPI(LLMBase):
13
+ @classmethod
14
+ def call(cls, images: List[Image.Image], model: str, token: str) -> dict:
15
+ client = genai.Client(api_key=token) # Initialize the client with the API key
16
+
17
+ config = types.GenerateContentConfig(
18
+ tools=[types.Tool(function_declarations=[FUNCTION_CALL])],
19
+ tool_config={
20
+ "function_calling_config": {
21
+ "mode": "ANY",
22
+ "allowed_function_names": [FUNCTION_CALL["name"]]
23
+ }
24
+ }
25
+ )
26
+
27
+ response = client.models.generate_content(
28
+ model=model,
29
+ contents=images + [PROMPT],
30
+ config=config
31
+ )
32
+ if response.candidates[0].content.parts[0].function_call:
33
+ function_call = response.candidates[0].content.parts[0].function_call
34
+ return function_call.args
35
+
36
+ return {}
menu/llm/openai.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import base64
3
+ from io import BytesIO
4
+ from typing import List
5
+
6
+ from PIL import Image
7
+ from openai import OpenAI
8
+
9
+ from .base import LLMBase, PROMPT
10
+
11
+ FUNCTION_CALL = json.load(open("tools/schema_openai.json", "r"))
12
+
13
+ class OpenAIAPI(LLMBase):
14
+ @classmethod
15
+ def call(cls, images: List[Image.Image], model: str, token: str) -> dict:
16
+ client = OpenAI(api_key=token) # Initialize the client with the API key
17
+
18
+ content = []
19
+ for image in images:
20
+ buffer = BytesIO()
21
+ image.save(buffer, format="JPEG")
22
+ encode_img = base64.b64encode(buffer.getvalue()).decode("utf-8")
23
+ content.append({
24
+ "type": "input_image",
25
+ "image_url": {"url": f"data:image/jpeg;base64,{encode_img}"},
26
+ })
27
+
28
+ content.append({"type": "text", "text": PROMPT})
29
+
30
+ response = client.responses.create(
31
+ model=model,
32
+ input=[
33
+ {
34
+ "role": "user",
35
+ "content": content,
36
+ }
37
+ ],
38
+ tools=[FUNCTION_CALL],
39
+ )
40
+ if response and response.output:
41
+ if hasattr(response.output[0], "arguments"):
42
+ return json.loads(response.output[0].arguments)
43
+ return {}
menu/utils.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ from datasets import Dataset, DatasetDict
4
+
5
+ def split_dataset(
6
+ dataset: Dataset,
7
+ train: float,
8
+ validation: float,
9
+ test: float,
10
+ seed: Optional[int] = None
11
+ ) -> DatasetDict:
12
+ """
13
+ Split a single-split Hugging Face Dataset into train/validation/test subsets.
14
+
15
+ Args:
16
+ dataset (Dataset): The input dataset (e.g. load_dataset(...)['train']).
17
+ train (float): Proportion of data for the train split (0 < train < 1).
18
+ val (float): Proportion of data for the validation split (0 < val < 1).
19
+ test (float): Proportion of data for the test split (0 < test < 1).
20
+ Must satisfy train + val + test == 1.0.
21
+ seed (int): Random seed for reproducibility (default: None).
22
+
23
+ Returns:
24
+ DatasetDict: A dictionary with keys "train", "validation", and "test".
25
+ """
26
+ # Verify ratios sum to 1.0
27
+ total = train + validation + test
28
+ if abs(total - 1.0) > 1e-8:
29
+ raise ValueError(f"train + validation + test must equal 1.0 (got {total})")
30
+
31
+ # First split: extract train vs. temp (validation + test)
32
+ temp_size = validation + test
33
+ split_1 = dataset.train_test_split(test_size=temp_size, seed=seed)
34
+ train_ds = split_1["train"]
35
+ temp_ds = split_1["test"]
36
+
37
+ # Second split: divide temp into validation vs. test
38
+ relative_test_size = test / temp_size
39
+ split_2 = temp_ds.train_test_split(test_size=relative_test_size, seed=seed)
40
+ validation_ds = split_2["train"]
41
+ test_ds = split_2["test"]
42
+
43
+ # Return a DatasetDict with all three splits
44
+ return DatasetDict({
45
+ "train": train_ds,
46
+ "validation": validation_ds,
47
+ "test": test_ds,
48
+ })
pyproject.toml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ authors = [{name = "ryanlinjui", email = "ryanlinjui@gmail.com"}]
3
+ name = "menu-text-detection"
4
+ version = "0.1.0"
5
+ description = "Extract structured menu information from images into JSON using a fine-tuned Donut E2E model."
6
+ readme = "README.md"
7
+ requires-python = "==3.11.*"
8
+ dependencies = [
9
+ "datasets>=3.6.0",
10
+ "dotenv>=0.9.9",
11
+ "google-genai>=1.14.0",
12
+ "gradio>=5.29.0",
13
+ "huggingface-hub>=0.31.1,<1.0",
14
+ "matplotlib>=3.10.1",
15
+ "nltk>=3.9.1",
16
+ "notebook>=7.4.2",
17
+ "openai>=1.77.0",
18
+ "pillow>=11.2.1",
19
+ "pillow-heif>=0.22.0",
20
+ "protobuf>=6.30.2",
21
+ "pytorch-lightning>=2.5.2",
22
+ "sentencepiece>=0.2.0",
23
+ "tensorboardx>=2.6.2.2",
24
+ "transformers==4.49",
25
+ "torch==2.4.1",
26
+ "donut-python>=1.0.9",
27
+ ]
requirements.txt ADDED
@@ -0,0 +1,1475 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv export --format requirements-txt
3
+ aiofiles==24.1.0 \
4
+ --hash=sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c \
5
+ --hash=sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5
6
+ # via gradio
7
+ aiohappyeyeballs==2.6.1 \
8
+ --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \
9
+ --hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8
10
+ # via aiohttp
11
+ aiohttp==3.11.18 \
12
+ --hash=sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717 \
13
+ --hash=sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d \
14
+ --hash=sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361 \
15
+ --hash=sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b \
16
+ --hash=sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d \
17
+ --hash=sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756 \
18
+ --hash=sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9 \
19
+ --hash=sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66 \
20
+ --hash=sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f \
21
+ --hash=sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1 \
22
+ --hash=sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4 \
23
+ --hash=sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f \
24
+ --hash=sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421 \
25
+ --hash=sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e \
26
+ --hash=sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a \
27
+ --hash=sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd \
28
+ --hash=sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6
29
+ # via fsspec
30
+ aiosignal==1.3.2 \
31
+ --hash=sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5 \
32
+ --hash=sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54
33
+ # via aiohttp
34
+ annotated-types==0.7.0 \
35
+ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
36
+ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
37
+ # via pydantic
38
+ anyio==4.9.0 \
39
+ --hash=sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028 \
40
+ --hash=sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c
41
+ # via
42
+ # google-genai
43
+ # gradio
44
+ # httpx
45
+ # jupyter-server
46
+ # openai
47
+ # starlette
48
+ appnope==0.1.4 ; sys_platform == 'darwin' \
49
+ --hash=sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee \
50
+ --hash=sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c
51
+ # via ipykernel
52
+ argon2-cffi==23.1.0 \
53
+ --hash=sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08 \
54
+ --hash=sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea
55
+ # via jupyter-server
56
+ argon2-cffi-bindings==21.2.0 \
57
+ --hash=sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c \
58
+ --hash=sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082 \
59
+ --hash=sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f \
60
+ --hash=sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d \
61
+ --hash=sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f \
62
+ --hash=sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae \
63
+ --hash=sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3 \
64
+ --hash=sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86 \
65
+ --hash=sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367 \
66
+ --hash=sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93 \
67
+ --hash=sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e
68
+ # via argon2-cffi
69
+ arrow==1.3.0 \
70
+ --hash=sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80 \
71
+ --hash=sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85
72
+ # via isoduration
73
+ asttokens==3.0.0 \
74
+ --hash=sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7 \
75
+ --hash=sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2
76
+ # via stack-data
77
+ async-lru==2.0.5 \
78
+ --hash=sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb \
79
+ --hash=sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943
80
+ # via jupyterlab
81
+ attrs==25.3.0 \
82
+ --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \
83
+ --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b
84
+ # via
85
+ # aiohttp
86
+ # jsonschema
87
+ # referencing
88
+ babel==2.17.0 \
89
+ --hash=sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d \
90
+ --hash=sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2
91
+ # via jupyterlab-server
92
+ beautifulsoup4==4.13.4 \
93
+ --hash=sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b \
94
+ --hash=sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195
95
+ # via nbconvert
96
+ bleach==6.2.0 \
97
+ --hash=sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e \
98
+ --hash=sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f
99
+ # via nbconvert
100
+ cachetools==5.5.2 \
101
+ --hash=sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4 \
102
+ --hash=sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a
103
+ # via google-auth
104
+ certifi==2025.4.26 \
105
+ --hash=sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6 \
106
+ --hash=sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3
107
+ # via
108
+ # httpcore
109
+ # httpx
110
+ # requests
111
+ cffi==1.17.1 \
112
+ --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \
113
+ --hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \
114
+ --hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \
115
+ --hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \
116
+ --hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \
117
+ --hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \
118
+ --hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \
119
+ --hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \
120
+ --hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \
121
+ --hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \
122
+ --hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \
123
+ --hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \
124
+ --hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b
125
+ # via
126
+ # argon2-cffi-bindings
127
+ # pyzmq
128
+ charset-normalizer==3.4.2 \
129
+ --hash=sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0 \
130
+ --hash=sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7 \
131
+ --hash=sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8 \
132
+ --hash=sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63 \
133
+ --hash=sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5 \
134
+ --hash=sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0 \
135
+ --hash=sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645 \
136
+ --hash=sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2 \
137
+ --hash=sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd \
138
+ --hash=sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a \
139
+ --hash=sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28 \
140
+ --hash=sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82 \
141
+ --hash=sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9 \
142
+ --hash=sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544 \
143
+ --hash=sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f
144
+ # via requests
145
+ click==8.1.8 \
146
+ --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \
147
+ --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
148
+ # via
149
+ # nltk
150
+ # typer
151
+ # uvicorn
152
+ colorama==0.4.6 ; sys_platform == 'win32' \
153
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
154
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
155
+ # via
156
+ # click
157
+ # ipython
158
+ # tqdm
159
+ comm==0.2.2 \
160
+ --hash=sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e \
161
+ --hash=sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3
162
+ # via ipykernel
163
+ contourpy==1.3.2 \
164
+ --hash=sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f \
165
+ --hash=sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7 \
166
+ --hash=sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5 \
167
+ --hash=sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878 \
168
+ --hash=sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0 \
169
+ --hash=sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab \
170
+ --hash=sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445 \
171
+ --hash=sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43 \
172
+ --hash=sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5 \
173
+ --hash=sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54 \
174
+ --hash=sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773 \
175
+ --hash=sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1 \
176
+ --hash=sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd \
177
+ --hash=sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83
178
+ # via matplotlib
179
+ cycler==0.12.1 \
180
+ --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \
181
+ --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c
182
+ # via matplotlib
183
+ datasets==3.6.0 \
184
+ --hash=sha256:1b2bf43b19776e2787e181cfd329cb0ca1a358ea014780c3581e0f276375e041 \
185
+ --hash=sha256:25000c4a2c0873a710df127d08a202a06eab7bf42441a6bc278b499c2f72cd1b
186
+ # via
187
+ # donut-python
188
+ # menu-text-detection
189
+ debugpy==1.8.14 \
190
+ --hash=sha256:1b2ac8c13b2645e0b1eaf30e816404990fbdb168e193322be8f545e8c01644a9 \
191
+ --hash=sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20 \
192
+ --hash=sha256:7816acea4a46d7e4e50ad8d09d963a680ecc814ae31cdef3622eb05ccacf7b01 \
193
+ --hash=sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322 \
194
+ --hash=sha256:c99295c76161ad8d507b413cd33422d7c542889fbb73035889420ac1fad354f2 \
195
+ --hash=sha256:cf431c343a99384ac7eab2f763980724834f933a271e90496944195318c619e2
196
+ # via ipykernel
197
+ decorator==5.2.1 \
198
+ --hash=sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360 \
199
+ --hash=sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a
200
+ # via ipython
201
+ defusedxml==0.7.1 \
202
+ --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \
203
+ --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
204
+ # via nbconvert
205
+ dill==0.3.8 \
206
+ --hash=sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca \
207
+ --hash=sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7
208
+ # via
209
+ # datasets
210
+ # multiprocess
211
+ distro==1.9.0 \
212
+ --hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \
213
+ --hash=sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2
214
+ # via openai
215
+ donut-python==1.0.9 \
216
+ --hash=sha256:cf2e99fa0515f6f7bd8da6d1198c1bb5532760d8595425b2036d62ec92532329
217
+ # via menu-text-detection
218
+ dotenv==0.9.9 \
219
+ --hash=sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9
220
+ # via menu-text-detection
221
+ executing==2.2.0 \
222
+ --hash=sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa \
223
+ --hash=sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755
224
+ # via stack-data
225
+ fastapi==0.115.12 \
226
+ --hash=sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681 \
227
+ --hash=sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d
228
+ # via gradio
229
+ fastjsonschema==2.21.1 \
230
+ --hash=sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4 \
231
+ --hash=sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667
232
+ # via nbformat
233
+ ffmpy==0.5.0 \
234
+ --hash=sha256:277e131f246d18e9dcfee9bb514c50749031c43582ce5ef82c57b51e3d3955c3 \
235
+ --hash=sha256:df3799cf5816daa56d4959a023630ee53c6768b66009dae6d131519ba4b80233
236
+ # via gradio
237
+ filelock==3.18.0 \
238
+ --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \
239
+ --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de
240
+ # via
241
+ # datasets
242
+ # huggingface-hub
243
+ # torch
244
+ # transformers
245
+ # triton
246
+ fonttools==4.57.0 \
247
+ --hash=sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d \
248
+ --hash=sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f \
249
+ --hash=sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4 \
250
+ --hash=sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746 \
251
+ --hash=sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f \
252
+ --hash=sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de \
253
+ --hash=sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683 \
254
+ --hash=sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344 \
255
+ --hash=sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8 \
256
+ --hash=sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36
257
+ # via matplotlib
258
+ fqdn==1.5.1 \
259
+ --hash=sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f \
260
+ --hash=sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014
261
+ # via jsonschema
262
+ frozenlist==1.6.0 \
263
+ --hash=sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a \
264
+ --hash=sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626 \
265
+ --hash=sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603 \
266
+ --hash=sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606 \
267
+ --hash=sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0 \
268
+ --hash=sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191 \
269
+ --hash=sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e \
270
+ --hash=sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff \
271
+ --hash=sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860 \
272
+ --hash=sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba \
273
+ --hash=sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595 \
274
+ --hash=sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0 \
275
+ --hash=sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe \
276
+ --hash=sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584 \
277
+ --hash=sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d \
278
+ --hash=sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a \
279
+ --hash=sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68 \
280
+ --hash=sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a \
281
+ --hash=sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1
282
+ # via
283
+ # aiohttp
284
+ # aiosignal
285
+ fsspec==2025.3.0 \
286
+ --hash=sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972 \
287
+ --hash=sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3
288
+ # via
289
+ # datasets
290
+ # gradio-client
291
+ # huggingface-hub
292
+ # pytorch-lightning
293
+ # torch
294
+ google-auth==2.40.1 \
295
+ --hash=sha256:58f0e8416a9814c1d86c9b7f6acf6816b51aba167b2c76821965271bac275540 \
296
+ --hash=sha256:ed4cae4f5c46b41bae1d19c036e06f6c371926e97b19e816fc854eff811974ee
297
+ # via google-genai
298
+ google-genai==1.14.0 \
299
+ --hash=sha256:5916ee985bf69ac7b68c4488949225db71e21579afc7ba5ecd5321173b60d3b2 \
300
+ --hash=sha256:7c608de5bb173486a546f5ec4562255c26bae72d33d758a3207bb26f695d0087
301
+ # via menu-text-detection
302
+ gradio==5.29.0 \
303
+ --hash=sha256:56fdd8b7cb31de8e4c382677560ef8a79f4ae0c3fbb139341bce052aab728064 \
304
+ --hash=sha256:6b58321c3b2d596a701e9b1660334772a160262e4c67f18848bd54b6c0db7d89
305
+ # via menu-text-detection
306
+ gradio-client==1.10.0 \
307
+ --hash=sha256:463bd60f0b11580185de13a01a1621fbff179fe29359dfa8810c1b3f9df5e342 \
308
+ --hash=sha256:47bfc878a3715604f6f0a554dc63f9ed78d14b126274f7d137e25447c737cd63
309
+ # via gradio
310
+ groovy==0.1.2 \
311
+ --hash=sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083 \
312
+ --hash=sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64
313
+ # via gradio
314
+ h11==0.16.0 \
315
+ --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
316
+ --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
317
+ # via
318
+ # httpcore
319
+ # uvicorn
320
+ hf-xet==1.1.0 ; platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' \
321
+ --hash=sha256:0322c42551e275fcb7949c083a54a81b2898e50787c9aa74284fcb8d2c58c12c \
322
+ --hash=sha256:3aee847da362393331f515c4010d0aaa1c2669acfcca1f4b28946d6949cc0086 \
323
+ --hash=sha256:4ee9222bf9274b1c198b88a929de0b5a49349c4962d89c5b3b2f0f7f47d9761c \
324
+ --hash=sha256:667153a0304ac2debf2af95a8ff7687186f885b493f4cd16344869af270cd110 \
325
+ --hash=sha256:68c5813a6074aa36e12ef5983230e3b03148cce61e0fcdd294096493795565b4 \
326
+ --hash=sha256:73153eab9abf3d6973b21e94a67ccba5d595c3e12feb8c0bf50be02964e7f126 \
327
+ --hash=sha256:995eeffb119636ea617b96c7d7bf3c3f5ea8727fa57974574e25d700b8532d48 \
328
+ --hash=sha256:a7c2a4c2b6eee9ce0a1a367a82b60d95ba634420ef1c250addad7aa4af419cf4
329
+ # via huggingface-hub
330
+ httpcore==1.0.9 \
331
+ --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
332
+ --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
333
+ # via httpx
334
+ httpx==0.28.1 \
335
+ --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
336
+ --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
337
+ # via
338
+ # google-genai
339
+ # gradio
340
+ # gradio-client
341
+ # jupyterlab
342
+ # openai
343
+ # safehttpx
344
+ huggingface-hub==0.31.1 \
345
+ --hash=sha256:43f73124819b48b42d140cbc0d7a2e6bd15b2853b1b9d728d4d55ad1750cac5b \
346
+ --hash=sha256:492bb5f545337aa9e2f59b75ef4c5f535a371e8958a6ce90af056387e67f1180
347
+ # via
348
+ # datasets
349
+ # gradio
350
+ # gradio-client
351
+ # menu-text-detection
352
+ # timm
353
+ # tokenizers
354
+ # transformers
355
+ idna==3.10 \
356
+ --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
357
+ --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
358
+ # via
359
+ # anyio
360
+ # httpx
361
+ # jsonschema
362
+ # requests
363
+ # yarl
364
+ ipykernel==6.29.5 \
365
+ --hash=sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5 \
366
+ --hash=sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215
367
+ # via jupyterlab
368
+ ipython==9.2.0 \
369
+ --hash=sha256:62a9373dbc12f28f9feaf4700d052195bf89806279fc8ca11f3f54017d04751b \
370
+ --hash=sha256:fef5e33c4a1ae0759e0bba5917c9db4eb8c53fee917b6a526bd973e1ca5159f6
371
+ # via ipykernel
372
+ ipython-pygments-lexers==1.1.1 \
373
+ --hash=sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81 \
374
+ --hash=sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c
375
+ # via ipython
376
+ isoduration==20.11.0 \
377
+ --hash=sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9 \
378
+ --hash=sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042
379
+ # via jsonschema
380
+ jedi==0.19.2 \
381
+ --hash=sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0 \
382
+ --hash=sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9
383
+ # via ipython
384
+ jinja2==3.1.6 \
385
+ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
386
+ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
387
+ # via
388
+ # gradio
389
+ # jupyter-server
390
+ # jupyterlab
391
+ # jupyterlab-server
392
+ # nbconvert
393
+ # torch
394
+ jiter==0.9.0 \
395
+ --hash=sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5 \
396
+ --hash=sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572 \
397
+ --hash=sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965 \
398
+ --hash=sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b \
399
+ --hash=sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b \
400
+ --hash=sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af \
401
+ --hash=sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58 \
402
+ --hash=sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd \
403
+ --hash=sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2 \
404
+ --hash=sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893 \
405
+ --hash=sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15 \
406
+ --hash=sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043 \
407
+ --hash=sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419
408
+ # via openai
409
+ joblib==1.5.0 \
410
+ --hash=sha256:206144b320246485b712fc8cc51f017de58225fa8b414a1fe1764a7231aca491 \
411
+ --hash=sha256:d8757f955389a3dd7a23152e43bc297c2e0c2d3060056dad0feefc88a06939b5
412
+ # via nltk
413
+ json5==0.12.0 \
414
+ --hash=sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a \
415
+ --hash=sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db
416
+ # via jupyterlab-server
417
+ jsonpointer==3.0.0 \
418
+ --hash=sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 \
419
+ --hash=sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef
420
+ # via jsonschema
421
+ jsonschema==4.23.0 \
422
+ --hash=sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4 \
423
+ --hash=sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566
424
+ # via
425
+ # jupyter-events
426
+ # jupyterlab-server
427
+ # nbformat
428
+ jsonschema-specifications==2025.4.1 \
429
+ --hash=sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af \
430
+ --hash=sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608
431
+ # via jsonschema
432
+ jupyter-client==8.6.3 \
433
+ --hash=sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419 \
434
+ --hash=sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f
435
+ # via
436
+ # ipykernel
437
+ # jupyter-server
438
+ # nbclient
439
+ jupyter-core==5.7.2 \
440
+ --hash=sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409 \
441
+ --hash=sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9
442
+ # via
443
+ # ipykernel
444
+ # jupyter-client
445
+ # jupyter-server
446
+ # jupyterlab
447
+ # nbclient
448
+ # nbconvert
449
+ # nbformat
450
+ jupyter-events==0.12.0 \
451
+ --hash=sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb \
452
+ --hash=sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b
453
+ # via jupyter-server
454
+ jupyter-lsp==2.2.5 \
455
+ --hash=sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da \
456
+ --hash=sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001
457
+ # via jupyterlab
458
+ jupyter-server==2.15.0 \
459
+ --hash=sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3 \
460
+ --hash=sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084
461
+ # via
462
+ # jupyter-lsp
463
+ # jupyterlab
464
+ # jupyterlab-server
465
+ # notebook
466
+ # notebook-shim
467
+ jupyter-server-terminals==0.5.3 \
468
+ --hash=sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa \
469
+ --hash=sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269
470
+ # via jupyter-server
471
+ jupyterlab==4.4.2 \
472
+ --hash=sha256:857111a50bed68542bf55dca784522fe728f9f88b4fe69e8c585db5c50900419 \
473
+ --hash=sha256:afa9caf28c0cb966488be18e5e8daba9f018a1c4273a406b7d5006344cbc6d16
474
+ # via notebook
475
+ jupyterlab-pygments==0.3.0 \
476
+ --hash=sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d \
477
+ --hash=sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780
478
+ # via nbconvert
479
+ jupyterlab-server==2.27.3 \
480
+ --hash=sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4 \
481
+ --hash=sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4
482
+ # via
483
+ # jupyterlab
484
+ # notebook
485
+ kiwisolver==1.4.8 \
486
+ --hash=sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79 \
487
+ --hash=sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2 \
488
+ --hash=sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab \
489
+ --hash=sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e \
490
+ --hash=sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0 \
491
+ --hash=sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6 \
492
+ --hash=sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc \
493
+ --hash=sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561 \
494
+ --hash=sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc \
495
+ --hash=sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84 \
496
+ --hash=sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25 \
497
+ --hash=sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7 \
498
+ --hash=sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03 \
499
+ --hash=sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67 \
500
+ --hash=sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954 \
501
+ --hash=sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34
502
+ # via matplotlib
503
+ lightning-utilities==0.14.3 \
504
+ --hash=sha256:37e2f83f273890052955a44054382c211a303012ee577619efbaa5df9e65e9f5 \
505
+ --hash=sha256:4ab9066aa36cd7b93a05713808901909e96cc3f187ea6fd3052b2fd91313b468
506
+ # via
507
+ # pytorch-lightning
508
+ # torchmetrics
509
+ markdown-it-py==3.0.0 ; sys_platform != 'emscripten' \
510
+ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \
511
+ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb
512
+ # via rich
513
+ markupsafe==3.0.2 \
514
+ --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \
515
+ --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \
516
+ --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \
517
+ --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \
518
+ --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \
519
+ --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \
520
+ --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \
521
+ --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \
522
+ --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \
523
+ --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \
524
+ --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0
525
+ # via
526
+ # gradio
527
+ # jinja2
528
+ # nbconvert
529
+ matplotlib==3.10.1 \
530
+ --hash=sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401 \
531
+ --hash=sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c \
532
+ --hash=sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a \
533
+ --hash=sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7 \
534
+ --hash=sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd \
535
+ --hash=sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe \
536
+ --hash=sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba
537
+ # via menu-text-detection
538
+ matplotlib-inline==0.1.7 \
539
+ --hash=sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90 \
540
+ --hash=sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca
541
+ # via
542
+ # ipykernel
543
+ # ipython
544
+ mdurl==0.1.2 ; sys_platform != 'emscripten' \
545
+ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
546
+ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
547
+ # via markdown-it-py
548
+ mistune==3.1.3 \
549
+ --hash=sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9 \
550
+ --hash=sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0
551
+ # via nbconvert
552
+ mpmath==1.3.0 \
553
+ --hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \
554
+ --hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
555
+ # via sympy
556
+ multidict==6.4.3 \
557
+ --hash=sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378 \
558
+ --hash=sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08 \
559
+ --hash=sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508 \
560
+ --hash=sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852 \
561
+ --hash=sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8 \
562
+ --hash=sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec \
563
+ --hash=sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589 \
564
+ --hash=sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56 \
565
+ --hash=sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9 \
566
+ --hash=sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229 \
567
+ --hash=sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0 \
568
+ --hash=sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618 \
569
+ --hash=sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7 \
570
+ --hash=sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777 \
571
+ --hash=sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7 \
572
+ --hash=sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2 \
573
+ --hash=sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8 \
574
+ --hash=sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd \
575
+ --hash=sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad
576
+ # via
577
+ # aiohttp
578
+ # yarl
579
+ multiprocess==0.70.16 \
580
+ --hash=sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1 \
581
+ --hash=sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3 \
582
+ --hash=sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435 \
583
+ --hash=sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a \
584
+ --hash=sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02
585
+ # via datasets
586
+ munch==4.0.0 \
587
+ --hash=sha256:542cb151461263216a4e37c3fd9afc425feeaf38aaa3025cd2a981fadb422235 \
588
+ --hash=sha256:71033c45db9fb677a0b7eb517a4ce70ae09258490e419b0e7f00d1e386ecb1b4
589
+ # via sconf
590
+ nbclient==0.10.2 \
591
+ --hash=sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d \
592
+ --hash=sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193
593
+ # via nbconvert
594
+ nbconvert==7.16.6 \
595
+ --hash=sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b \
596
+ --hash=sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582
597
+ # via jupyter-server
598
+ nbformat==5.10.4 \
599
+ --hash=sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a \
600
+ --hash=sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b
601
+ # via
602
+ # jupyter-server
603
+ # nbclient
604
+ # nbconvert
605
+ nest-asyncio==1.6.0 \
606
+ --hash=sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe \
607
+ --hash=sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c
608
+ # via ipykernel
609
+ networkx==3.4.2 \
610
+ --hash=sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1 \
611
+ --hash=sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f
612
+ # via torch
613
+ nltk==3.9.1 \
614
+ --hash=sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1 \
615
+ --hash=sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868
616
+ # via
617
+ # donut-python
618
+ # menu-text-detection
619
+ notebook==7.4.2 \
620
+ --hash=sha256:9ccef602721aaa5530852e3064710b8ae5415c4e2ce26f8896d0433222755259 \
621
+ --hash=sha256:e739defd28c3f615a6bfb0a2564bd75018a9cc6613aa00bbd9c15e68eed2de1b
622
+ # via menu-text-detection
623
+ notebook-shim==0.2.4 \
624
+ --hash=sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef \
625
+ --hash=sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb
626
+ # via
627
+ # jupyterlab
628
+ # notebook
629
+ numpy==2.2.5 \
630
+ --hash=sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b \
631
+ --hash=sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610 \
632
+ --hash=sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906 \
633
+ --hash=sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda \
634
+ --hash=sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d \
635
+ --hash=sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54 \
636
+ --hash=sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291 \
637
+ --hash=sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be \
638
+ --hash=sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd \
639
+ --hash=sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b \
640
+ --hash=sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175
641
+ # via
642
+ # contourpy
643
+ # datasets
644
+ # gradio
645
+ # matplotlib
646
+ # pandas
647
+ # tensorboardx
648
+ # torchmetrics
649
+ # torchvision
650
+ # transformers
651
+ nvidia-cublas-cu12==12.1.3.1 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
652
+ --hash=sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728
653
+ # via
654
+ # nvidia-cudnn-cu12
655
+ # nvidia-cusolver-cu12
656
+ # torch
657
+ nvidia-cuda-cupti-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
658
+ --hash=sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e
659
+ # via torch
660
+ nvidia-cuda-nvrtc-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
661
+ --hash=sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2
662
+ # via torch
663
+ nvidia-cuda-runtime-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
664
+ --hash=sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40
665
+ # via torch
666
+ nvidia-cudnn-cu12==9.1.0.70 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
667
+ --hash=sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f
668
+ # via torch
669
+ nvidia-cufft-cu12==11.0.2.54 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
670
+ --hash=sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56
671
+ # via torch
672
+ nvidia-curand-cu12==10.3.2.106 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
673
+ --hash=sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0
674
+ # via torch
675
+ nvidia-cusolver-cu12==11.4.5.107 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
676
+ --hash=sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd
677
+ # via torch
678
+ nvidia-cusparse-cu12==12.1.0.106 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
679
+ --hash=sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c
680
+ # via
681
+ # nvidia-cusolver-cu12
682
+ # torch
683
+ nvidia-nccl-cu12==2.20.5 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
684
+ --hash=sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56
685
+ # via torch
686
+ nvidia-nvjitlink-cu12==12.6.85 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
687
+ --hash=sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a
688
+ # via
689
+ # nvidia-cusolver-cu12
690
+ # nvidia-cusparse-cu12
691
+ nvidia-nvtx-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
692
+ --hash=sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5
693
+ # via torch
694
+ openai==1.77.0 \
695
+ --hash=sha256:07706e91eb71631234996989a8ea991d5ee56f0744ef694c961e0824d4f39218 \
696
+ --hash=sha256:897969f927f0068b8091b4b041d1f8175bcf124f7ea31bab418bf720971223bc
697
+ # via menu-text-detection
698
+ orjson==3.10.18 \
699
+ --hash=sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4 \
700
+ --hash=sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947 \
701
+ --hash=sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b \
702
+ --hash=sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679 \
703
+ --hash=sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e \
704
+ --hash=sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d \
705
+ --hash=sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a \
706
+ --hash=sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334 \
707
+ --hash=sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5 \
708
+ --hash=sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7 \
709
+ --hash=sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7 \
710
+ --hash=sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a \
711
+ --hash=sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8 \
712
+ --hash=sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17 \
713
+ --hash=sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53 \
714
+ --hash=sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1
715
+ # via gradio
716
+ overrides==7.7.0 \
717
+ --hash=sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a \
718
+ --hash=sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49
719
+ # via jupyter-server
720
+ packaging==25.0 \
721
+ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
722
+ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
723
+ # via
724
+ # datasets
725
+ # gradio
726
+ # gradio-client
727
+ # huggingface-hub
728
+ # ipykernel
729
+ # jupyter-events
730
+ # jupyter-server
731
+ # jupyterlab
732
+ # jupyterlab-server
733
+ # lightning-utilities
734
+ # matplotlib
735
+ # nbconvert
736
+ # pytorch-lightning
737
+ # tensorboardx
738
+ # torchmetrics
739
+ # transformers
740
+ pandas==2.2.3 \
741
+ --hash=sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32 \
742
+ --hash=sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5 \
743
+ --hash=sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667 \
744
+ --hash=sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3 \
745
+ --hash=sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039 \
746
+ --hash=sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd \
747
+ --hash=sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc \
748
+ --hash=sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698
749
+ # via
750
+ # datasets
751
+ # gradio
752
+ pandocfilters==1.5.1 \
753
+ --hash=sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e \
754
+ --hash=sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc
755
+ # via nbconvert
756
+ parso==0.8.4 \
757
+ --hash=sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18 \
758
+ --hash=sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d
759
+ # via jedi
760
+ pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' \
761
+ --hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \
762
+ --hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f
763
+ # via ipython
764
+ pillow==11.2.1 \
765
+ --hash=sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b \
766
+ --hash=sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193 \
767
+ --hash=sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f \
768
+ --hash=sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044 \
769
+ --hash=sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70 \
770
+ --hash=sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7 \
771
+ --hash=sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd \
772
+ --hash=sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed \
773
+ --hash=sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788 \
774
+ --hash=sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076 \
775
+ --hash=sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e \
776
+ --hash=sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6 \
777
+ --hash=sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8 \
778
+ --hash=sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6 \
779
+ --hash=sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7 \
780
+ --hash=sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf \
781
+ --hash=sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600 \
782
+ --hash=sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c \
783
+ --hash=sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e
784
+ # via
785
+ # datasets
786
+ # gradio
787
+ # matplotlib
788
+ # menu-text-detection
789
+ # pillow-heif
790
+ # torchvision
791
+ pillow-heif==0.22.0 \
792
+ --hash=sha256:550093ee350c8cd404dbba61f7449d4ecc018109ab65f6f89b96a6dc39dde177 \
793
+ --hash=sha256:61d473929340d3073722f6316b7fbbdb11132faa6bac0242328e8436cc55b39a \
794
+ --hash=sha256:646f2d05dd4a84ee1feb3a4aaa092269bf8f3f615c8f36d6e5d15b22a79d7cdd \
795
+ --hash=sha256:782eef461c836b3947fe253baaa5dcf6c75fba484c4905b4ac6b5b1a0bb04b7c \
796
+ --hash=sha256:8ccd70ff3c30e93f8153fa08b5533aee54a73574c980784f0b74f5fba7b61e19 \
797
+ --hash=sha256:b3035b4b4304e7624f9559989618ccad8ac614144befd30361ff51941b8c1b44 \
798
+ --hash=sha256:bf30bcaab9d2c0dbc43bb58d385faa9d3d8a693392beb50287aa6cda7a2f769e \
799
+ --hash=sha256:f73b7ce0d47e2dbd4f89de765a0c2d6e75f7dde95c5a5d87c4a6ad11bc9da4a3
800
+ # via menu-text-detection
801
+ platformdirs==4.3.8 \
802
+ --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \
803
+ --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4
804
+ # via jupyter-core
805
+ prometheus-client==0.21.1 \
806
+ --hash=sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb \
807
+ --hash=sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301
808
+ # via jupyter-server
809
+ prompt-toolkit==3.0.51 \
810
+ --hash=sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07 \
811
+ --hash=sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed
812
+ # via ipython
813
+ propcache==0.3.1 \
814
+ --hash=sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5 \
815
+ --hash=sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d \
816
+ --hash=sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf \
817
+ --hash=sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371 \
818
+ --hash=sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005 \
819
+ --hash=sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0 \
820
+ --hash=sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a \
821
+ --hash=sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9 \
822
+ --hash=sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5 \
823
+ --hash=sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7 \
824
+ --hash=sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40 \
825
+ --hash=sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da \
826
+ --hash=sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0 \
827
+ --hash=sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073 \
828
+ --hash=sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f \
829
+ --hash=sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a \
830
+ --hash=sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256 \
831
+ --hash=sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744
832
+ # via
833
+ # aiohttp
834
+ # yarl
835
+ protobuf==6.30.2 \
836
+ --hash=sha256:0eb523c550a66a09a0c20f86dd554afbf4d32b02af34ae53d93268c1f73bc65b \
837
+ --hash=sha256:35c859ae076d8c56054c25b59e5e59638d86545ed6e2b6efac6be0b6ea3ba048 \
838
+ --hash=sha256:4f6c687ae8efae6cf6093389a596548214467778146b7245e886f35e1485315d \
839
+ --hash=sha256:50f32cc9fd9cb09c783ebc275611b4f19dfdfb68d1ee55d2f0c7fa040df96815 \
840
+ --hash=sha256:7653c99774f73fe6b9301b87da52af0e69783a2e371e8b599b3e9cb4da4b12b9 \
841
+ --hash=sha256:ae86b030e69a98e08c77beab574cbcb9fff6d031d57209f574a5aea1445f4b51 \
842
+ --hash=sha256:b12ef7df7b9329886e66404bef5e9ce6a26b54069d7f7436a0853ccdeb91c103
843
+ # via
844
+ # menu-text-detection
845
+ # tensorboardx
846
+ psutil==7.0.0 \
847
+ --hash=sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25 \
848
+ --hash=sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91 \
849
+ --hash=sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da \
850
+ --hash=sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34 \
851
+ --hash=sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553 \
852
+ --hash=sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456 \
853
+ --hash=sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993 \
854
+ --hash=sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99
855
+ # via ipykernel
856
+ ptyprocess==0.7.0 ; os_name != 'nt' or (sys_platform != 'emscripten' and sys_platform != 'win32') \
857
+ --hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \
858
+ --hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220
859
+ # via
860
+ # pexpect
861
+ # terminado
862
+ pure-eval==0.2.3 \
863
+ --hash=sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0 \
864
+ --hash=sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42
865
+ # via stack-data
866
+ pyarrow==20.0.0 \
867
+ --hash=sha256:24ca380585444cb2a31324c546a9a56abbe87e26069189e14bdba19c86c049f0 \
868
+ --hash=sha256:3346babb516f4b6fd790da99b98bed9708e3f02e734c84971faccb20736848dc \
869
+ --hash=sha256:5f0fb1041267e9968c6d0d2ce3ff92e3928b243e2b6d11eeb84d9ac547308232 \
870
+ --hash=sha256:6bb830757103a6cb300a04610e08d9636f0cd223d32f388418ea893a3e655f1c \
871
+ --hash=sha256:7a3a5dcf54286e6141d5114522cf31dd67a9e7c9133d150799f30ee302a7a1ab \
872
+ --hash=sha256:95b330059ddfdc591a3225f2d272123be26c8fa76e8c9ee1a77aad507361cfdb \
873
+ --hash=sha256:96e37f0766ecb4514a899d9a3554fadda770fb57ddf42b63d80f14bc20aa7db3 \
874
+ --hash=sha256:a6ad3e7758ecf559900261a4df985662df54fb7fdb55e8e3b3aa99b23d526b62 \
875
+ --hash=sha256:b8ff87cc837601532cc8242d2f7e09b4e02404de1b797aee747dd4ba4bd6313f \
876
+ --hash=sha256:febc4a913592573c8d5805091a6c2b5064c8bd6e002131f01061797d91c783c1
877
+ # via datasets
878
+ pyasn1==0.6.1 \
879
+ --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \
880
+ --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034
881
+ # via
882
+ # pyasn1-modules
883
+ # rsa
884
+ pyasn1-modules==0.4.2 \
885
+ --hash=sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a \
886
+ --hash=sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6
887
+ # via google-auth
888
+ pycparser==2.22 \
889
+ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
890
+ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
891
+ # via cffi
892
+ pydantic==2.11.4 \
893
+ --hash=sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d \
894
+ --hash=sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb
895
+ # via
896
+ # fastapi
897
+ # google-genai
898
+ # gradio
899
+ # openai
900
+ pydantic-core==2.33.2 \
901
+ --hash=sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab \
902
+ --hash=sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf \
903
+ --hash=sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7 \
904
+ --hash=sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612 \
905
+ --hash=sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1 \
906
+ --hash=sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a \
907
+ --hash=sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7 \
908
+ --hash=sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51 \
909
+ --hash=sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e \
910
+ --hash=sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65 \
911
+ --hash=sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de \
912
+ --hash=sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc \
913
+ --hash=sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb \
914
+ --hash=sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef \
915
+ --hash=sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc \
916
+ --hash=sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30 \
917
+ --hash=sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e \
918
+ --hash=sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593 \
919
+ --hash=sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f \
920
+ --hash=sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8 \
921
+ --hash=sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf \
922
+ --hash=sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246 \
923
+ --hash=sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8 \
924
+ --hash=sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d
925
+ # via pydantic
926
+ pydub==0.25.1 \
927
+ --hash=sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6 \
928
+ --hash=sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f
929
+ # via gradio
930
+ pygments==2.19.1 \
931
+ --hash=sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f \
932
+ --hash=sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c
933
+ # via
934
+ # ipython
935
+ # ipython-pygments-lexers
936
+ # nbconvert
937
+ # rich
938
+ pyparsing==3.2.3 \
939
+ --hash=sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf \
940
+ --hash=sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be
941
+ # via matplotlib
942
+ python-dateutil==2.9.0.post0 \
943
+ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
944
+ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
945
+ # via
946
+ # arrow
947
+ # jupyter-client
948
+ # matplotlib
949
+ # pandas
950
+ python-dotenv==1.1.0 \
951
+ --hash=sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5 \
952
+ --hash=sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d
953
+ # via dotenv
954
+ python-json-logger==3.3.0 \
955
+ --hash=sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84 \
956
+ --hash=sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7
957
+ # via jupyter-events
958
+ python-multipart==0.0.20 \
959
+ --hash=sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104 \
960
+ --hash=sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13
961
+ # via gradio
962
+ pytorch-lightning==2.5.2 \
963
+ --hash=sha256:17cfdf89bd98074e389101f097cdf34c486a1f5c6d3fdcefbaf4dea7f97ff0bf \
964
+ --hash=sha256:f817087d611be8d43b777dd4e543d72703e235510936677a13e6c29f7fd790e3
965
+ # via
966
+ # donut-python
967
+ # menu-text-detection
968
+ pytz==2025.2 \
969
+ --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \
970
+ --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00
971
+ # via pandas
972
+ pywin32==310 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32' \
973
+ --hash=sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c \
974
+ --hash=sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582 \
975
+ --hash=sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd
976
+ # via jupyter-core
977
+ pywinpty==2.0.15 ; os_name == 'nt' \
978
+ --hash=sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2 \
979
+ --hash=sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca
980
+ # via
981
+ # jupyter-server
982
+ # jupyter-server-terminals
983
+ # terminado
984
+ pyyaml==6.0.2 \
985
+ --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
986
+ --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
987
+ --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
988
+ --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
989
+ --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
990
+ --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
991
+ --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
992
+ --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
993
+ --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
994
+ --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
995
+ # via
996
+ # datasets
997
+ # gradio
998
+ # huggingface-hub
999
+ # jupyter-events
1000
+ # pytorch-lightning
1001
+ # timm
1002
+ # transformers
1003
+ pyzmq==26.4.0 \
1004
+ --hash=sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88 \
1005
+ --hash=sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6 \
1006
+ --hash=sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef \
1007
+ --hash=sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494 \
1008
+ --hash=sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb \
1009
+ --hash=sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e \
1010
+ --hash=sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d \
1011
+ --hash=sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9 \
1012
+ --hash=sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1 \
1013
+ --hash=sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896 \
1014
+ --hash=sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0 \
1015
+ --hash=sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54 \
1016
+ --hash=sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3 \
1017
+ --hash=sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01 \
1018
+ --hash=sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca \
1019
+ --hash=sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030 \
1020
+ --hash=sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df
1021
+ # via
1022
+ # ipykernel
1023
+ # jupyter-client
1024
+ # jupyter-server
1025
+ referencing==0.36.2 \
1026
+ --hash=sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa \
1027
+ --hash=sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0
1028
+ # via
1029
+ # jsonschema
1030
+ # jsonschema-specifications
1031
+ # jupyter-events
1032
+ regex==2024.11.6 \
1033
+ --hash=sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60 \
1034
+ --hash=sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d \
1035
+ --hash=sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114 \
1036
+ --hash=sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3 \
1037
+ --hash=sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d \
1038
+ --hash=sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7 \
1039
+ --hash=sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f \
1040
+ --hash=sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34 \
1041
+ --hash=sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638 \
1042
+ --hash=sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519 \
1043
+ --hash=sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20 \
1044
+ --hash=sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89 \
1045
+ --hash=sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45 \
1046
+ --hash=sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55 \
1047
+ --hash=sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9 \
1048
+ --hash=sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0
1049
+ # via
1050
+ # nltk
1051
+ # transformers
1052
+ requests==2.32.3 \
1053
+ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
1054
+ --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
1055
+ # via
1056
+ # datasets
1057
+ # google-genai
1058
+ # huggingface-hub
1059
+ # jupyterlab-server
1060
+ # transformers
1061
+ rfc3339-validator==0.1.4 \
1062
+ --hash=sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b \
1063
+ --hash=sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa
1064
+ # via
1065
+ # jsonschema
1066
+ # jupyter-events
1067
+ rfc3986-validator==0.1.1 \
1068
+ --hash=sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9 \
1069
+ --hash=sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055
1070
+ # via
1071
+ # jsonschema
1072
+ # jupyter-events
1073
+ rich==14.0.0 ; sys_platform != 'emscripten' \
1074
+ --hash=sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0 \
1075
+ --hash=sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725
1076
+ # via typer
1077
+ rpds-py==0.24.0 \
1078
+ --hash=sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc \
1079
+ --hash=sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032 \
1080
+ --hash=sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718 \
1081
+ --hash=sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef \
1082
+ --hash=sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4 \
1083
+ --hash=sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586 \
1084
+ --hash=sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a \
1085
+ --hash=sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c \
1086
+ --hash=sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba \
1087
+ --hash=sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e \
1088
+ --hash=sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25 \
1089
+ --hash=sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d \
1090
+ --hash=sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793 \
1091
+ --hash=sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba \
1092
+ --hash=sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea \
1093
+ --hash=sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d \
1094
+ --hash=sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e \
1095
+ --hash=sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a \
1096
+ --hash=sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97 \
1097
+ --hash=sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6 \
1098
+ --hash=sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae \
1099
+ --hash=sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c \
1100
+ --hash=sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d \
1101
+ --hash=sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d \
1102
+ --hash=sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5
1103
+ # via
1104
+ # jsonschema
1105
+ # referencing
1106
+ rsa==4.9.1 \
1107
+ --hash=sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762 \
1108
+ --hash=sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75
1109
+ # via google-auth
1110
+ ruamel-yaml==0.18.14 \
1111
+ --hash=sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2 \
1112
+ --hash=sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7
1113
+ # via sconf
1114
+ ruamel-yaml-clib==0.2.12 ; platform_python_implementation == 'CPython' \
1115
+ --hash=sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3 \
1116
+ --hash=sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6 \
1117
+ --hash=sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f \
1118
+ --hash=sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2 \
1119
+ --hash=sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52 \
1120
+ --hash=sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb \
1121
+ --hash=sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e \
1122
+ --hash=sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4 \
1123
+ --hash=sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642 \
1124
+ --hash=sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e
1125
+ # via ruamel-yaml
1126
+ ruff==0.11.8 ; sys_platform != 'emscripten' \
1127
+ --hash=sha256:0eba551324733efc76116d9f3a0d52946bc2751f0cd30661564117d6fd60897c \
1128
+ --hash=sha256:161eb4cff5cfefdb6c9b8b3671d09f7def2f960cee33481dd898caf2bcd02304 \
1129
+ --hash=sha256:258f3585057508d317610e8a412788cf726efeefa2fec4dba4001d9e6f90d46c \
1130
+ --hash=sha256:304432e4c4a792e3da85b7699feb3426a0908ab98bf29df22a31b0cdd098fac2 \
1131
+ --hash=sha256:3dca977cc4fc8f66e89900fa415ffe4dbc2e969da9d7a54bfca81a128c5ac219 \
1132
+ --hash=sha256:4d9aaa91035bdf612c8ee7266153bcf16005c7c7e2f5878406911c92a31633cb \
1133
+ --hash=sha256:5b18caa297a786465cc511d7f8be19226acf9c0a1127e06e736cd4e1878c3ea2 \
1134
+ --hash=sha256:6d742d10626f9004b781f4558154bb226620a7242080e11caeffab1a40e99df8 \
1135
+ --hash=sha256:6e70d11043bef637c5617297bdedec9632af15d53ac1e1ba29c448da9341b0c4 \
1136
+ --hash=sha256:727d01702f7c30baed3fc3a34901a640001a2828c793525043c29f7614994a8c \
1137
+ --hash=sha256:7f024d32e62faad0f76b2d6afd141b8c171515e4fb91ce9fd6464335c81244e5 \
1138
+ --hash=sha256:896a37516c594805e34020c4a7546c8f8a234b679a7716a3f08197f38913e1a3 \
1139
+ --hash=sha256:ab86d22d3d721a40dd3ecbb5e86ab03b2e053bc93c700dc68d1c3346b36ce835 \
1140
+ --hash=sha256:c1dba3135ca503727aa4648152c0fa67c3b1385d3dc81c75cd8a229c4b2a1458 \
1141
+ --hash=sha256:c657fa987d60b104d2be8b052d66da0a2a88f9bd1d66b2254333e84ea2720c7f \
1142
+ --hash=sha256:d365618d3ad747432e1ae50d61775b78c055fee5936d77fb4d92c6f559741948 \
1143
+ --hash=sha256:f2e74b021d0de5eceb8bd32919f6ff8a9b40ee62ed97becd44993ae5b9949474 \
1144
+ --hash=sha256:f9b5ef39820abc0f2c62111f7045009e46b275f5b99d5e59dda113c39b7f4f38
1145
+ # via gradio
1146
+ safehttpx==0.1.6 \
1147
+ --hash=sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c \
1148
+ --hash=sha256:b356bfc82cee3a24c395b94a2dbeabbed60aff1aa5fa3b5fe97c4f2456ebce42
1149
+ # via gradio
1150
+ safetensors==0.5.3 \
1151
+ --hash=sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d \
1152
+ --hash=sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467 \
1153
+ --hash=sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7 \
1154
+ --hash=sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135 \
1155
+ --hash=sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04 \
1156
+ --hash=sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9 \
1157
+ --hash=sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e \
1158
+ --hash=sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b \
1159
+ --hash=sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11 \
1160
+ --hash=sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d \
1161
+ --hash=sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965 \
1162
+ --hash=sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073 \
1163
+ --hash=sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a \
1164
+ --hash=sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace \
1165
+ --hash=sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff
1166
+ # via
1167
+ # timm
1168
+ # transformers
1169
+ sconf==0.2.5 \
1170
+ --hash=sha256:c557b5a87d39dd7d5334a1236112873d2e0b1b71b386d24e2f01726aec1d957a
1171
+ # via donut-python
1172
+ semantic-version==2.10.0 \
1173
+ --hash=sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c \
1174
+ --hash=sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177
1175
+ # via gradio
1176
+ send2trash==1.8.3 \
1177
+ --hash=sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9 \
1178
+ --hash=sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf
1179
+ # via jupyter-server
1180
+ sentencepiece==0.2.0 \
1181
+ --hash=sha256:0993dbc665f4113017892f1b87c3904a44d0640eda510abcacdfb07f74286d36 \
1182
+ --hash=sha256:17982700c4f6dbb55fa3594f3d7e5dd1c8659a274af3738e33c987d2a27c9d5c \
1183
+ --hash=sha256:27f90c55a65013cbb8f4d7aab0599bf925cde4adc67ae43a0d323677b5a1c6cb \
1184
+ --hash=sha256:7c867012c0e8bcd5bdad0f791609101cb5c66acb303ab3270218d6debc68a65e \
1185
+ --hash=sha256:7fd6071249c74f779c5b27183295b9202f8dedb68034e716784364443879eaa6 \
1186
+ --hash=sha256:a52c19171daaf2e697dc6cbe67684e0fa341b1248966f6aebb541de654d15843 \
1187
+ --hash=sha256:b293734059ef656dcd65be62ff771507bea8fed0a711b6733976e1ed3add4553 \
1188
+ --hash=sha256:c581258cf346b327c62c4f1cebd32691826306f6a41d8c4bec43b010dee08e75 \
1189
+ --hash=sha256:e58b47f933aca74c6a60a79dcb21d5b9e47416256c795c2d58d55cec27f9551d
1190
+ # via
1191
+ # donut-python
1192
+ # menu-text-detection
1193
+ setuptools==80.3.1 \
1194
+ --hash=sha256:31e2c58dbb67c99c289f51c16d899afedae292b978f8051efaf6262d8212f927 \
1195
+ --hash=sha256:ea8e00d7992054c4c592aeb892f6ad51fe1b4d90cc6947cc45c45717c40ec537
1196
+ # via
1197
+ # jupyterlab
1198
+ # lightning-utilities
1199
+ shellingham==1.5.4 ; sys_platform != 'emscripten' \
1200
+ --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \
1201
+ --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de
1202
+ # via typer
1203
+ six==1.17.0 \
1204
+ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
1205
+ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
1206
+ # via
1207
+ # python-dateutil
1208
+ # rfc3339-validator
1209
+ # zss
1210
+ sniffio==1.3.1 \
1211
+ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
1212
+ --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
1213
+ # via
1214
+ # anyio
1215
+ # openai
1216
+ soupsieve==2.7 \
1217
+ --hash=sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4 \
1218
+ --hash=sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a
1219
+ # via beautifulsoup4
1220
+ stack-data==0.6.3 \
1221
+ --hash=sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9 \
1222
+ --hash=sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695
1223
+ # via ipython
1224
+ starlette==0.46.2 \
1225
+ --hash=sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35 \
1226
+ --hash=sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5
1227
+ # via
1228
+ # fastapi
1229
+ # gradio
1230
+ sympy==1.14.0 \
1231
+ --hash=sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517 \
1232
+ --hash=sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5
1233
+ # via torch
1234
+ tensorboardx==2.6.2.2 \
1235
+ --hash=sha256:160025acbf759ede23fd3526ae9d9bfbfd8b68eb16c38a010ebe326dc6395db8 \
1236
+ --hash=sha256:c6476d7cd0d529b0b72f4acadb1269f9ed8b22f441e87a84f2a3b940bb87b666
1237
+ # via menu-text-detection
1238
+ terminado==0.18.1 \
1239
+ --hash=sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0 \
1240
+ --hash=sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e
1241
+ # via
1242
+ # jupyter-server
1243
+ # jupyter-server-terminals
1244
+ timm==1.0.16 \
1245
+ --hash=sha256:a3b8130dd2cb8dc3b9f5e3d09ab6d677a6315a8695fd5264eb6d52a4a46c1044 \
1246
+ --hash=sha256:a640e58f4ae41e0445517d1133b34be75bb2bd49cdb830d739925ce1fb7d2526
1247
+ # via donut-python
1248
+ tinycss2==1.4.0 \
1249
+ --hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
1250
+ --hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
1251
+ # via bleach
1252
+ tokenizers==0.21.1 \
1253
+ --hash=sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382 \
1254
+ --hash=sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3 \
1255
+ --hash=sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f \
1256
+ --hash=sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a \
1257
+ --hash=sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c \
1258
+ --hash=sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf \
1259
+ --hash=sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d \
1260
+ --hash=sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab \
1261
+ --hash=sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0 \
1262
+ --hash=sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8 \
1263
+ --hash=sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3 \
1264
+ --hash=sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf \
1265
+ --hash=sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f \
1266
+ --hash=sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41 \
1267
+ --hash=sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6
1268
+ # via transformers
1269
+ tomlkit==0.13.2 \
1270
+ --hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \
1271
+ --hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79
1272
+ # via gradio
1273
+ torch==2.4.1 \
1274
+ --hash=sha256:092e7c2280c860eff762ac08c4bdcd53d701677851670695e0c22d6d345b269c \
1275
+ --hash=sha256:0b5f88afdfa05a335d80351e3cea57d38e578c8689f751d35e0ff36bce872113 \
1276
+ --hash=sha256:ddddbd8b066e743934a4200b3d54267a46db02106876d21cf31f7da7a96f98ea \
1277
+ --hash=sha256:ef503165f2341942bfdf2bd520152f19540d0c0e34961232f134dc59ad435be8
1278
+ # via
1279
+ # menu-text-detection
1280
+ # pytorch-lightning
1281
+ # timm
1282
+ # torchmetrics
1283
+ # torchvision
1284
+ torchmetrics==1.7.3 \
1285
+ --hash=sha256:08450a19cdb67ba1608aac0b213e5dc73033e11b60ad4719696ebcede591621e \
1286
+ --hash=sha256:7b6fd43e92f0a1071c8bcb029637f252b0630699140a93ed8817ce7afe9db34e
1287
+ # via pytorch-lightning
1288
+ torchvision==0.19.1 \
1289
+ --hash=sha256:40514282b4896d62765b8e26d7091c32e17c35817d00ec4be2362ea3ba3d1787 \
1290
+ --hash=sha256:5a91be061ae5d6d5b95e833b93e57ca4d3c56c5a57444dd15da2e3e7fba96050 \
1291
+ --hash=sha256:70dea324174f5e9981b68e4b7cd524512c106ba64aedef560a86a0bbf2fbf62c \
1292
+ --hash=sha256:d71a6a6fe3a5281ca3487d4c56ad4aad20ff70f82f1d7c79bcb6e7b0c2af00c8
1293
+ # via timm
1294
+ tornado==6.4.2 \
1295
+ --hash=sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803 \
1296
+ --hash=sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec \
1297
+ --hash=sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482 \
1298
+ --hash=sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634 \
1299
+ --hash=sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38 \
1300
+ --hash=sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b \
1301
+ --hash=sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c \
1302
+ --hash=sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf \
1303
+ --hash=sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946 \
1304
+ --hash=sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73 \
1305
+ --hash=sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1
1306
+ # via
1307
+ # ipykernel
1308
+ # jupyter-client
1309
+ # jupyter-server
1310
+ # jupyterlab
1311
+ # notebook
1312
+ # terminado
1313
+ tqdm==4.67.1 \
1314
+ --hash=sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2 \
1315
+ --hash=sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2
1316
+ # via
1317
+ # datasets
1318
+ # huggingface-hub
1319
+ # nltk
1320
+ # openai
1321
+ # pytorch-lightning
1322
+ # transformers
1323
+ traitlets==5.14.3 \
1324
+ --hash=sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7 \
1325
+ --hash=sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f
1326
+ # via
1327
+ # comm
1328
+ # ipykernel
1329
+ # ipython
1330
+ # jupyter-client
1331
+ # jupyter-core
1332
+ # jupyter-events
1333
+ # jupyter-server
1334
+ # jupyterlab
1335
+ # matplotlib-inline
1336
+ # nbclient
1337
+ # nbconvert
1338
+ # nbformat
1339
+ transformers==4.49.0 \
1340
+ --hash=sha256:6b4fded1c5fee04d384b1014495b4235a2b53c87503d7d592423c06128cbbe03 \
1341
+ --hash=sha256:7e40e640b5b8dc3f48743f5f5adbdce3660c82baafbd3afdfc04143cdbd2089e
1342
+ # via
1343
+ # donut-python
1344
+ # menu-text-detection
1345
+ triton==3.0.0 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
1346
+ --hash=sha256:5ce8520437c602fb633f1324cc3871c47bee3b67acf9756c1a66309b60e3216c
1347
+ # via torch
1348
+ typer==0.15.3 ; sys_platform != 'emscripten' \
1349
+ --hash=sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c \
1350
+ --hash=sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd
1351
+ # via gradio
1352
+ types-python-dateutil==2.9.0.20241206 \
1353
+ --hash=sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb \
1354
+ --hash=sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53
1355
+ # via arrow
1356
+ typing-extensions==4.13.2 \
1357
+ --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \
1358
+ --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef
1359
+ # via
1360
+ # anyio
1361
+ # beautifulsoup4
1362
+ # fastapi
1363
+ # google-genai
1364
+ # gradio
1365
+ # gradio-client
1366
+ # huggingface-hub
1367
+ # ipython
1368
+ # lightning-utilities
1369
+ # openai
1370
+ # pydantic
1371
+ # pydantic-core
1372
+ # pytorch-lightning
1373
+ # referencing
1374
+ # torch
1375
+ # typer
1376
+ # typing-inspection
1377
+ typing-inspection==0.4.0 \
1378
+ --hash=sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f \
1379
+ --hash=sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122
1380
+ # via pydantic
1381
+ tzdata==2025.2 \
1382
+ --hash=sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8 \
1383
+ --hash=sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9
1384
+ # via pandas
1385
+ uri-template==1.3.0 \
1386
+ --hash=sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7 \
1387
+ --hash=sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363
1388
+ # via jsonschema
1389
+ urllib3==2.4.0 \
1390
+ --hash=sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466 \
1391
+ --hash=sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813
1392
+ # via
1393
+ # gradio
1394
+ # requests
1395
+ uvicorn==0.34.2 ; sys_platform != 'emscripten' \
1396
+ --hash=sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328 \
1397
+ --hash=sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403
1398
+ # via gradio
1399
+ wcwidth==0.2.13 \
1400
+ --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \
1401
+ --hash=sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5
1402
+ # via prompt-toolkit
1403
+ webcolors==24.11.1 \
1404
+ --hash=sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9 \
1405
+ --hash=sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6
1406
+ # via jsonschema
1407
+ webencodings==0.5.1 \
1408
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
1409
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
1410
+ # via
1411
+ # bleach
1412
+ # tinycss2
1413
+ websocket-client==1.8.0 \
1414
+ --hash=sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526 \
1415
+ --hash=sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da
1416
+ # via jupyter-server
1417
+ websockets==15.0.1 \
1418
+ --hash=sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85 \
1419
+ --hash=sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065 \
1420
+ --hash=sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf \
1421
+ --hash=sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792 \
1422
+ --hash=sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57 \
1423
+ --hash=sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3 \
1424
+ --hash=sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431 \
1425
+ --hash=sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee \
1426
+ --hash=sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413 \
1427
+ --hash=sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8 \
1428
+ --hash=sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905 \
1429
+ --hash=sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562 \
1430
+ --hash=sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f
1431
+ # via
1432
+ # google-genai
1433
+ # gradio-client
1434
+ xxhash==3.5.0 \
1435
+ --hash=sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1 \
1436
+ --hash=sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da \
1437
+ --hash=sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166 \
1438
+ --hash=sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2 \
1439
+ --hash=sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8 \
1440
+ --hash=sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88 \
1441
+ --hash=sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f \
1442
+ --hash=sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a \
1443
+ --hash=sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623 \
1444
+ --hash=sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839 \
1445
+ --hash=sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084 \
1446
+ --hash=sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d \
1447
+ --hash=sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58 \
1448
+ --hash=sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7 \
1449
+ --hash=sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3 \
1450
+ --hash=sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c
1451
+ # via datasets
1452
+ yarl==1.20.0 \
1453
+ --hash=sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2 \
1454
+ --hash=sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902 \
1455
+ --hash=sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2 \
1456
+ --hash=sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5 \
1457
+ --hash=sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124 \
1458
+ --hash=sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2 \
1459
+ --hash=sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307 \
1460
+ --hash=sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e \
1461
+ --hash=sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791 \
1462
+ --hash=sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb \
1463
+ --hash=sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da \
1464
+ --hash=sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8 \
1465
+ --hash=sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a \
1466
+ --hash=sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4 \
1467
+ --hash=sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6 \
1468
+ --hash=sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f \
1469
+ --hash=sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9 \
1470
+ --hash=sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a \
1471
+ --hash=sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3
1472
+ # via aiohttp
1473
+ zss==1.2.0 \
1474
+ --hash=sha256:07bb937441929ccb82961f4f7b80fbce9e2b20d0e46ddcbcbc1fcb094f585b50
1475
+ # via donut-python
tools/schema_gemini.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "extract_menu_data",
3
+ "description": "Extract structured menu information from images.",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {
7
+ "restaurant": {
8
+ "type": "string",
9
+ "description": "Name of the restaurant. If the name is not available, it should be ''."
10
+ },
11
+ "address": {
12
+ "type": "string",
13
+ "description": "Address of the restaurant. If the address is not available, it should be ''."
14
+ },
15
+ "phone": {
16
+ "type": "string",
17
+ "description": "Phone number of the restaurant. If the phone number is not available, it should be ''."
18
+ },
19
+ "business_hours": {
20
+ "type": "string",
21
+ "description": "Business hours of the restaurant. If the business hours are not available, it should be ''."
22
+ },
23
+ "dishes": {
24
+ "type": "array",
25
+ "items": {
26
+ "type": "object",
27
+ "properties": {
28
+ "name": {
29
+ "type": "string",
30
+ "description": "Name of the menu item."
31
+ },
32
+ "price": {
33
+ "type": "string",
34
+ "description": "Price of the menu item. If the price is not available, it should be -1."
35
+ }
36
+ },
37
+ "required": ["name", "price"]
38
+ },
39
+ "description": "List of menu dishes item."
40
+ }
41
+ },
42
+ "required": ["restaurant", "address", "phone", "business_hours", "dishes"]
43
+ }
44
+ }
tools/schema_openai.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "function",
3
+ "name": "extract_menu_data",
4
+ "description": "Extract structured menu information from images.",
5
+ "parameters": {
6
+ "type": "object",
7
+ "properties": {
8
+ "restaurant": {
9
+ "type": "string",
10
+ "description": "Name of the restaurant. If the name is not available, it should be ''."
11
+ },
12
+ "address": {
13
+ "type": "string",
14
+ "description": "Address of the restaurant. If the address is not available, it should be ''."
15
+ },
16
+ "phone": {
17
+ "type": "string",
18
+ "description": "Phone number of the restaurant. If the phone number is not available, it should be ''."
19
+ },
20
+ "business_hours": {
21
+ "type": "string",
22
+ "description": "Business hours of the restaurant. If the business hours are not available, it should be ''."
23
+ },
24
+ "dishes": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": "object",
28
+ "properties": {
29
+ "name": {
30
+ "type": "string",
31
+ "description": "Name of the menu item."
32
+ },
33
+ "price": {
34
+ "type": "string",
35
+ "description": "Price of the menu item. If the price is not available, it should be -1."
36
+ }
37
+ },
38
+ "required": ["name", "price"],
39
+ "additionalProperties": false
40
+ },
41
+ "description": "List of menu dishes item."
42
+ }
43
+ },
44
+ "required": ["restaurant", "address", "phone", "business_hours", "dishes"],
45
+ "additionalProperties": false
46
+ }
47
+ }
train.ipynb ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Login to HuggingFace (just login once)"
8
+ ]
9
+ },
10
+ {
11
+ "cell_type": "code",
12
+ "execution_count": null,
13
+ "metadata": {},
14
+ "outputs": [],
15
+ "source": [
16
+ "from huggingface_hub import interpreter_login\n",
17
+ "interpreter_login()"
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "markdown",
22
+ "metadata": {},
23
+ "source": [
24
+ "# Collect Menu Image Datasets\n",
25
+ "- Use `metadata.jsonl` to label the images's ground truth. You can visit [here](https://github.com/ryanlinjui/menu-text-detection/tree/main/examples) to see the examples.\n",
26
+ "- After finishing, push to HuggingFace Datasets.\n",
27
+ "- For labeling:\n",
28
+ " - [Google AI Studio](https://aistudio.google.com) or [OpenAI ChatGPT](https://chatgpt.com).\n",
29
+ " - Use function calling by API. Start the gradio app locally or visit [here](https://huggingface.co/spaces/ryanlinjui/menu-text-detection).\n",
30
+ "\n",
31
+ "### Menu Type\n",
32
+ "- **h**: horizontal menu\n",
33
+ "- **v**: vertical menu\n",
34
+ "- **d**: document-style menu\n",
35
+ "- **s**: in-scene menu (non-document style)\n",
36
+ "- **i**: irregular menu (menu with irregular text layout)\n",
37
+ "\n",
38
+ "> Please see the [examples](https://github.com/ryanlinjui/menu-text-detection/tree/main/examples) for more details."
39
+ ]
40
+ },
41
+ {
42
+ "cell_type": "code",
43
+ "execution_count": null,
44
+ "metadata": {},
45
+ "outputs": [],
46
+ "source": [
47
+ "import os\n",
48
+ "import json\n",
49
+ "\n",
50
+ "import numpy as np\n",
51
+ "from PIL import Image\n",
52
+ "from pillow_heif import register_heif_opener\n",
53
+ "\n",
54
+ "from menu.llm import (\n",
55
+ " GeminiAPI,\n",
56
+ " OpenAIAPI\n",
57
+ ")\n",
58
+ "\n",
59
+ "IMAGE_DIR = \"datasets/images\" # set your image directory here\n",
60
+ "SELECTED_MODEL = \"gemini-2.5-flash\" # set model name here, refer MODEL_LIST from app.py for more\n",
61
+ "API_TOKEN = \"\" # set your API token here\n",
62
+ "SELECTED_FUNCTION = GeminiAPI # set \"GeminiAPI\" or \"OpenAIAPI\"\n",
63
+ "\n",
64
+ "register_heif_opener()\n",
65
+ "\n",
66
+ "for file in os.listdir(IMAGE_DIR):\n",
67
+ " print(f\"Processing image: {file}\")\n",
68
+ " try:\n",
69
+ " image = np.array(Image.open(os.path.join(IMAGE_DIR, file)))\n",
70
+ " data = {\n",
71
+ " \"file_name\": file,\n",
72
+ " \"menu\": SELECTED_FUNCTION.call(image, SELECTED_MODEL, API_TOKEN)\n",
73
+ " }\n",
74
+ " with open(os.path.join(IMAGE_DIR, \"metadata.jsonl\"), \"a\", encoding=\"utf-8\") as metaf:\n",
75
+ " metaf.write(json.dumps(data, ensure_ascii=False, sort_keys=True) + \"\\n\")\n",
76
+ " except Exception as e:\n",
77
+ " print(f\"Skipping invalid image '{file}': {e}\")\n",
78
+ " continue"
79
+ ]
80
+ },
81
+ {
82
+ "cell_type": "markdown",
83
+ "metadata": {},
84
+ "source": [
85
+ "# Push Datasets to HuggingFace"
86
+ ]
87
+ },
88
+ {
89
+ "cell_type": "code",
90
+ "execution_count": null,
91
+ "metadata": {},
92
+ "outputs": [],
93
+ "source": [
94
+ "from datasets import load_dataset\n",
95
+ "\n",
96
+ "dataset = load_dataset(path=\"datasets/menu-zh-TW\") # load dataset from the local directory including the metadata.jsonl, images files.\n",
97
+ "dataset.push_to_hub(repo_id=\"ryanlinjui/menu-zh-TW\") # push to the huggingface dataset hub"
98
+ ]
99
+ },
100
+ {
101
+ "cell_type": "markdown",
102
+ "metadata": {},
103
+ "source": [
104
+ "# Prepare the dataset for training"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "execution_count": null,
110
+ "metadata": {},
111
+ "outputs": [],
112
+ "source": [
113
+ "from menu.utils import split_dataset\n",
114
+ "from datasets import load_dataset\n",
115
+ "\n",
116
+ "dataset = load_dataset(path=\"ryanlinjui/menu-zh-TW\") # set your dataset repo id for training\n",
117
+ "dataset = split_dataset(dataset[\"train\"], train=0.8, validation=0.1, test=0.1, seed=42) # (optional) use it if your dataset is not split into train/validation/test\n",
118
+ "print(f\"Dataset split: {len(dataset['train'])} train, {len(dataset['validation'])} validation, {len(dataset['test'])} test\")"
119
+ ]
120
+ },
121
+ {
122
+ "cell_type": "markdown",
123
+ "metadata": {},
124
+ "source": [
125
+ "# Fine-tune Donut Model"
126
+ ]
127
+ },
128
+ {
129
+ "cell_type": "code",
130
+ "execution_count": null,
131
+ "metadata": {},
132
+ "outputs": [],
133
+ "source": [
134
+ "import logging\n",
135
+ "from menu.donut import DonutTrainer\n",
136
+ "\n",
137
+ "logging.getLogger(\"transformers\").setLevel(logging.ERROR) # filter output message from transformers\n",
138
+ "\n",
139
+ "DonutTrainer.train(\n",
140
+ " dataset=dataset,\n",
141
+ " pretrained_model_repo_id=\"naver-clova-ix/donut-base\", # set your pretrained model repo id for fine-tuning\n",
142
+ " ground_truth_key=\"menu\", # set your ground truth key for training\n",
143
+ " huggingface_model_id=\"ryanlinjui/donut-base-finetuned-menu\", # set your huggingface model repo id for saving / pushing to the hub\n",
144
+ " epochs=15, # set your training epochs\n",
145
+ " train_batch_size=8, # set your training batch size\n",
146
+ " val_batch_size=1, # set your validation batch size\n",
147
+ " learning_rate=3e-5, # set your learning rate\n",
148
+ " val_check_interval=0.5, # how many times we want to validate during an epoch\n",
149
+ " check_val_every_n_epoch=1, # how many epochs we want to validate\n",
150
+ " gradient_clip_val=1.0, # gradient clipping value for training stability\n",
151
+ " num_training_samples_per_epoch=198, # set num_training_samples_per_epoch = training set size\n",
152
+ " num_nodes=1, # number of nodes for distributed training\n",
153
+ " warmup_steps=75 # number of warmup steps for learning rate scheduler, 198/8*30/10, 10%\n",
154
+ ")"
155
+ ]
156
+ },
157
+ {
158
+ "cell_type": "markdown",
159
+ "metadata": {},
160
+ "source": [
161
+ "# Evaluate Donut Model"
162
+ ]
163
+ },
164
+ {
165
+ "cell_type": "code",
166
+ "execution_count": null,
167
+ "metadata": {},
168
+ "outputs": [],
169
+ "source": [
170
+ "import json\n",
171
+ "from datasets import load_dataset\n",
172
+ "\n",
173
+ "from menu.utils import split_dataset\n",
174
+ "from menu.donut import DonutFinetuned\n",
175
+ "\n",
176
+ "dataset = load_dataset(\"ryanlinjui/menu-zh-TW\")\n",
177
+ "dataset = split_dataset(dataset[\"train\"], train=0.8, validation=0.1, test=0.1, seed=42) # (optional) use it if your dataset is not split into train/validation/test\n",
178
+ "donut_finetuned = DonutFinetuned(pretrained_model_repo_id=\"ryanlinjui/donut-base-finetuned-menu\")\n",
179
+ "scores, output_list = donut_finetuned.evaluate(dataset=dataset[\"test\"], ground_truth_key=\"menu\")\n",
180
+ "\n",
181
+ "print(\"Evaluation scores:\")\n",
182
+ "for key, value in scores.items():\n",
183
+ " print(f\"{key}: {value}\")\n",
184
+ "\n",
185
+ "print(\"\\nSample outputs:\")\n",
186
+ "for output in output_list[:5]:\n",
187
+ " print(json.dumps(output, ensure_ascii=False, indent=4))"
188
+ ]
189
+ },
190
+ {
191
+ "cell_type": "markdown",
192
+ "metadata": {},
193
+ "source": [
194
+ "# Test Donut Model"
195
+ ]
196
+ },
197
+ {
198
+ "cell_type": "code",
199
+ "execution_count": null,
200
+ "metadata": {},
201
+ "outputs": [],
202
+ "source": [
203
+ "from PIL import Image\n",
204
+ "from menu.donut import DonutFinetuned\n",
205
+ "\n",
206
+ "image = Image.open(\"./examples/menu-hd.jpg\")\n",
207
+ "\n",
208
+ "donut_finetuned = DonutFinetuned(pretrained_model_repo_id=\"ryanlinjui/donut-base-finetuned-menu\")\n",
209
+ "outputs = donut_finetuned.predict(image=image)\n",
210
+ "print(outputs)"
211
+ ]
212
+ }
213
+ ],
214
+ "metadata": {
215
+ "kernelspec": {
216
+ "display_name": "menu-text-detection",
217
+ "language": "python",
218
+ "name": "python3"
219
+ },
220
+ "language_info": {
221
+ "codemirror_mode": {
222
+ "name": "ipython",
223
+ "version": 3
224
+ },
225
+ "file_extension": ".py",
226
+ "mimetype": "text/x-python",
227
+ "name": "python",
228
+ "nbconvert_exporter": "python",
229
+ "pygments_lexer": "ipython3",
230
+ "version": "3.11.12"
231
+ }
232
+ },
233
+ "nbformat": 4,
234
+ "nbformat_minor": 2
235
+ }
uv.lock ADDED
The diff for this file is too large to render. See raw diff