Hawk3388 commited on
Commit
c170470
·
1 Parent(s): b66b473

new file: app.py

Browse files

new file: model/gap_detection_model.pt
new file: requirements.txt

Files changed (3) hide show
  1. app.py +168 -0
  2. model/gap_detection_model.pt +3 -0
  3. requirements.txt +98 -0
app.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import uuid
4
+ import warnings
5
+
6
+ import gradio as gr
7
+ import requests
8
+ from PIL import Image
9
+
10
+ from main import WorksheetSolver
11
+
12
+ warnings.filterwarnings("ignore")
13
+
14
+ ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "webp", "bmp"}
15
+ GAP_DETECTION_MODEL_PATH = "./model/gap_detection_model.pt"
16
+ GAP_MODEL_URL = "https://github.com/Hawk3388/solver/releases/download/v1.1.0/gap_detection_model.pt"
17
+
18
+
19
+ def ensure_gap_model() -> str:
20
+ os.makedirs("./model", exist_ok=True)
21
+ if os.path.exists(GAP_DETECTION_MODEL_PATH):
22
+ return GAP_DETECTION_MODEL_PATH
23
+
24
+ with requests.get(GAP_MODEL_URL, stream=True, timeout=60) as response:
25
+ response.raise_for_status()
26
+ with open(GAP_DETECTION_MODEL_PATH, "wb") as model_file:
27
+ for chunk in response.iter_content(chunk_size=8192):
28
+ if chunk:
29
+ model_file.write(chunk)
30
+
31
+ return GAP_DETECTION_MODEL_PATH
32
+
33
+
34
+ def _is_allowed_image(filename: str) -> bool:
35
+ return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
36
+
37
+
38
+ def solve_worksheet(
39
+ image_path: str,
40
+ model_name: str,
41
+ local: bool,
42
+ think: bool,
43
+ thinking_budget: int,
44
+ debug: bool,
45
+ experimental: bool,
46
+ ):
47
+ if not image_path:
48
+ raise gr.Error("Please upload an image first.")
49
+
50
+ if not _is_allowed_image(image_path):
51
+ raise gr.Error("Please upload a valid image file (PNG, JPG, JPEG, WEBP, BMP).")
52
+
53
+ try:
54
+ model_path = ensure_gap_model()
55
+ except Exception as error:
56
+ raise gr.Error(f"Could not load the gap detection model: {error}") from error
57
+
58
+ with tempfile.TemporaryDirectory() as tmp_dir:
59
+ unique_id = uuid.uuid4().hex
60
+ input_path = os.path.join(tmp_dir, f"{unique_id}.png")
61
+ output_path = os.path.join(tmp_dir, f"{unique_id}_solved.png")
62
+
63
+ try:
64
+ Image.open(image_path).convert("RGB").save(input_path)
65
+
66
+ solver = WorksheetSolver(
67
+ input_path,
68
+ gap_detection_model_path=model_path,
69
+ llm_model_name=model_name.strip() or "gemini-2.5-flash",
70
+ think=think,
71
+ local=local,
72
+ thinking_budget=int(thinking_budget),
73
+ debug=debug,
74
+ experimental=experimental,
75
+ )
76
+
77
+ gaps, detected_image = solver.detect_gaps()
78
+ if not gaps:
79
+ raise gr.Error("No gaps were detected. Please try a clearer worksheet image.")
80
+
81
+ marked_image = solver.mark_gaps(detected_image, gaps)
82
+ solutions = solver.solve_all_gaps(marked_image)
83
+
84
+ if not solutions:
85
+ raise gr.Error("The AI could not find any solutions.")
86
+
87
+ solver.fill_gaps_in_image(input_path, solutions, output_path=output_path)
88
+
89
+ solved_image = Image.open(output_path).copy()
90
+ return solved_image
91
+
92
+ except Exception as error:
93
+ raise gr.Error(f"Processing error: {error}") from error
94
+
95
+
96
+ def build_app() -> gr.Blocks:
97
+ with gr.Blocks(title="Worksheet Solver", css="""
98
+ .app-shell {max-width: 1200px; margin: 0 auto;}
99
+ .hero {text-align: center; margin: 14px 0 8px;}
100
+ .hero h1 {font-size: 2rem; margin-bottom: 6px;}
101
+ .hero p {opacity: 0.85;}
102
+ """) as demo:
103
+ gr.HTML(
104
+ """
105
+ <div class='hero'>
106
+ <h1>Worksheet Solver</h1>
107
+ <p>Upload a worksheet image, configure the options, and generate the solved version.</p>
108
+ </div>
109
+ """
110
+ )
111
+
112
+ with gr.Row(elem_classes=["app-shell"]):
113
+ with gr.Column(scale=1):
114
+ image_input = gr.Image(
115
+ type="filepath",
116
+ label="Worksheet Image",
117
+ sources=["upload"],
118
+ )
119
+
120
+ model_name = gr.Textbox(
121
+ label="LLM Model Name",
122
+ value="gemini-2.5-flash",
123
+ placeholder="e.g. gemini-2.5-flash or qwen3.5:35b",
124
+ )
125
+
126
+ with gr.Row():
127
+ local = gr.Checkbox(label="Local Mode (Ollama)", value=False)
128
+ think = gr.Checkbox(label="Thinking", value=True)
129
+
130
+ thinking_budget = gr.Slider(
131
+ minimum=0,
132
+ maximum=8192,
133
+ step=1,
134
+ value=2048,
135
+ label="Thinking Budget",
136
+ info="Only relevant when Thinking is enabled.",
137
+ )
138
+
139
+ with gr.Row():
140
+ debug = gr.Checkbox(label="Debug Mode", value=False)
141
+ experimental = gr.Checkbox(label="Experimental Mode", value=False)
142
+
143
+ solve_button = gr.Button("Solve", variant="primary")
144
+
145
+ with gr.Column(scale=1):
146
+ image_output = gr.Image(type="pil", label="Solved Worksheet")
147
+
148
+ solve_button.click(
149
+ fn=solve_worksheet,
150
+ inputs=[
151
+ image_input,
152
+ model_name,
153
+ local,
154
+ think,
155
+ thinking_budget,
156
+ debug,
157
+ experimental,
158
+ ],
159
+ outputs=image_output,
160
+ )
161
+
162
+ return demo
163
+
164
+
165
+ demo = build_app()
166
+
167
+ if __name__ == "__main__":
168
+ demo.queue().launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")), share=True)
model/gap_detection_model.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a09d72ab83480428164c040356af5dce6b59fd42d305621901d9d234f0657c09
3
+ size 53210085
requirements.txt ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==24.1.0
2
+ altgraph==0.17.5
3
+ annotated-doc==0.0.4
4
+ annotated-types==0.7.0
5
+ anyio==4.12.1
6
+ blinker==1.9.0
7
+ brotli==1.2.0
8
+ certifi==2026.2.25
9
+ cffi==2.0.0
10
+ charset-normalizer==3.4.5
11
+ click==8.3.1
12
+ colorama==0.4.6
13
+ contourpy==1.3.2
14
+ cryptography==46.0.5
15
+ cycler==0.12.1
16
+ distro==1.9.0
17
+ exceptiongroup==1.3.1
18
+ fastapi==0.135.1
19
+ ffmpy==1.0.0
20
+ filelock==3.25.0
21
+ Flask==3.1.3
22
+ fonttools==4.61.1
23
+ fsspec==2026.2.0
24
+ google-auth==2.49.0
25
+ google-genai==1.66.0
26
+ gradio==6.9.0
27
+ gradio_client==2.3.0
28
+ groovy==0.1.2
29
+ h11==0.16.0
30
+ hf-xet==1.4.2
31
+ httpcore==1.0.9
32
+ httpx==0.28.1
33
+ huggingface_hub==1.7.1
34
+ idna==3.11
35
+ itsdangerous==2.2.0
36
+ Jinja2==3.1.6
37
+ kiwisolver==1.4.9
38
+ lap==0.5.13
39
+ markdown-it-py==4.0.0
40
+ MarkupSafe==3.0.3
41
+ matplotlib==3.10.8
42
+ mdurl==0.1.2
43
+ mpmath==1.3.0
44
+ networkx==3.4.2
45
+ numpy==2.2.6
46
+ ollama==0.6.1
47
+ opencv-python==4.13.0.92
48
+ orjson==3.11.7
49
+ packaging==25.0
50
+ pandas==2.3.3
51
+ pefile==2024.8.26
52
+ pillow==12.1.1
53
+ polars==1.38.1
54
+ polars-runtime-32==1.38.1
55
+ psutil==7.2.2
56
+ pyasn1==0.6.2
57
+ pyasn1_modules==0.4.2
58
+ pycparser==3.0
59
+ pydantic==2.12.5
60
+ pydantic_core==2.41.5
61
+ pydub==0.25.1
62
+ Pygments==2.19.2
63
+ pyinstaller==6.19.0
64
+ pyinstaller-hooks-contrib==2026.2
65
+ pyparsing==3.3.2
66
+ python-dateutil==2.9.0.post0
67
+ python-dotenv==1.2.2
68
+ python-multipart==0.0.22
69
+ pytz==2026.1.post1
70
+ pywin32-ctypes==0.2.3
71
+ PyYAML==6.0.3
72
+ requests==2.32.5
73
+ rich==14.3.3
74
+ rsa==4.9.1
75
+ safehttpx==0.1.7
76
+ scipy==1.15.3
77
+ semantic-version==2.10.0
78
+ shellingham==1.5.4
79
+ six==1.17.0
80
+ sniffio==1.3.1
81
+ starlette==0.52.1
82
+ sympy==1.14.0
83
+ tenacity==9.1.4
84
+ tomlkit==0.13.3
85
+ torch==2.10.0
86
+ torchvision==0.25.0
87
+ tqdm==4.67.3
88
+ typer==0.24.1
89
+ typing-inspection==0.4.2
90
+ typing_extensions==4.15.0
91
+ tzdata==2025.3
92
+ ultralytics==8.4.21
93
+ ultralytics-thop==2.0.18
94
+ urllib3==2.6.3
95
+ uvicorn==0.41.0
96
+ waitress==3.0.2
97
+ websockets==16.0
98
+ Werkzeug==3.1.6