MasteredUltraInstinct commited on
Commit
e736389
Β·
verified Β·
1 Parent(s): 042f3b9

Create image.py

Browse files
Files changed (1) hide show
  1. image.py +478 -0
image.py ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import sympy as sp
3
+ from pix2text import Pix2Text
4
+ from PIL import Image
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ import re
8
+ import io
9
+ import logging
10
+
11
+ # Configure logging for debugging
12
+ logging.basicConfig(level=logging.INFO)
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Define symbolic variables
16
+ x, y = sp.symbols('x y')
17
+
18
+ # Initialize Pix2Text model globally
19
+ try:
20
+ p2t_model = Pix2Text.from_config()
21
+ logger.info("Pix2Text model loaded successfully")
22
+ except Exception as e:
23
+ logger.error(f"Failed to load Pix2Text model: {e}")
24
+ p2t_model = None
25
+
26
+ def clean_latex_expression(latex_str):
27
+ """Clean and normalize LaTeX expression for SymPy parsing"""
28
+ if not latex_str:
29
+ return ""
30
+
31
+ latex_str = latex_str.strip()
32
+ latex_str = re.sub(r'^\$\$|\$\$$', '', latex_str) # Remove $$ delimiters
33
+ latex_str = re.sub(r'\\[a-zA-Z]+\{([^}]*)\}', r'\1', latex_str) # Remove LaTeX commands
34
+ latex_str = re.sub(r'\\{2,}', r'\\', latex_str) # Fix multiple backslashes
35
+ latex_str = re.sub(r'\s+', ' ', latex_str) # Normalize whitespace
36
+ latex_str = re.sub(r'\^{([^}]+)}', r'**\1', latex_str) # Convert x^{n} to x**n
37
+ latex_str = re.sub(r'(\d*\.?\d+)\s*([xy])', r'\1*\2', latex_str) # Add multiplication: 1.0x -> 1.0*x
38
+ latex_str = re.sub(r'\s*([+\-*/=])\s*', r'\1', latex_str) # Remove spaces around operators
39
+ if '=' in latex_str:
40
+ left, right = latex_str.split('=')
41
+ latex_str = f"{left} - ({right})" # Move right-hand side to left
42
+ return latex_str.strip()
43
+
44
+ def parse_equation_type(latex_str):
45
+ """Determine if the equation is polynomial (single-variable) or linear system (two-variable)"""
46
+ try:
47
+ cleaned = clean_latex_expression(latex_str)
48
+ if not cleaned:
49
+ return 'polynomial'
50
+
51
+ # Check for two-variable system
52
+ if 'y' in cleaned and 'x' in cleaned:
53
+ if '\\\\' in latex_str or '\n' in latex_str or len(re.split(r'\\\\|\n|;', latex_str)) >= 2:
54
+ return 'linear_system'
55
+ return 'linear' # Single equation with x and y
56
+
57
+ # Check for single-variable polynomial
58
+ try:
59
+ expr = sp.sympify(cleaned.split('-')[0] if '-' in cleaned else cleaned)
60
+ if x in expr.free_symbols and y not in expr.free_symbols:
61
+ degree = sp.degree(expr, x)
62
+ return 'polynomial' if degree > 0 else 'linear'
63
+ elif x not in expr.free_symbols and y in expr.free_symbols:
64
+ return 'polynomial' # Treat as polynomial in y if x is absent
65
+ else:
66
+ return 'polynomial' # Default to polynomial if no clear variables
67
+ except:
68
+ if 'x**' in cleaned or '^' in latex_str:
69
+ return 'polynomial'
70
+ return 'polynomial' # Fallback to polynomial
71
+ except Exception as e:
72
+ logger.error(f"Error determining equation type: {e}")
73
+ return 'polynomial'
74
+
75
+ def extract_polynomial_coefficients(latex_str):
76
+ """Extract polynomial coefficients from LaTeX string"""
77
+ try:
78
+ cleaned = clean_latex_expression(latex_str)
79
+ if '-' in cleaned:
80
+ cleaned = cleaned.split('-')[0].strip() # Use left side for polynomial
81
+
82
+ expr = sp.sympify(cleaned, evaluate=False)
83
+ if x not in expr.free_symbols and y not in expr.free_symbols:
84
+ raise ValueError("No variable (x or y) found in expression")
85
+
86
+ variable = x if x in expr.free_symbols else y
87
+ degree = sp.degree(expr, variable)
88
+ if degree < 1 or degree > 8:
89
+ raise ValueError(f"Polynomial degree {degree} is out of supported range (1-8)")
90
+
91
+ poly = sp.Poly(expr, variable)
92
+ coeffs = [float(poly.coeff_monomial(variable**i)) for i in range(degree, -1, -1)]
93
+
94
+ return {
95
+ "type": "polynomial",
96
+ "degree": degree,
97
+ "coeffs": " ".join(map(str, coeffs)),
98
+ "latex": latex_str,
99
+ "success": True,
100
+ "variable": str(variable)
101
+ }
102
+ except Exception as e:
103
+ logger.error(f"Error extracting polynomial coefficients: {e}")
104
+ return {
105
+ "type": "polynomial",
106
+ "degree": 2,
107
+ "coeffs": "1 0 0",
108
+ "latex": latex_str,
109
+ "success": False,
110
+ "error": str(e),
111
+ "variable": "x"
112
+ }
113
+
114
+ def extract_linear_system_coefficients(latex_str):
115
+ """Extract linear system coefficients from LaTeX string"""
116
+ try:
117
+ cleaned = clean_latex_expression(latex_str)
118
+ equations = re.split(r'\\\\|\n|;', latex_str)
119
+ if len(equations) < 2:
120
+ equations = re.split(r'(?<=[0-9])\s*(?=[+-]?\s*[0-9]*[xy])', cleaned)
121
+
122
+ if len(equations) < 2 or 'y' not in cleaned or 'x' not in cleaned:
123
+ raise ValueError("Could not find two equations or two variables (x, y) in system")
124
+
125
+ eq1_str = equations[0].strip()
126
+ eq2_str = equations[1].strip()
127
+
128
+ def parse_linear_eq(eq_str):
129
+ if '-' not in eq_str:
130
+ raise ValueError("No equals sign (converted to '-') found")
131
+ left, right = eq_str.split('-')
132
+ expr = sp.sympify(left) - sp.sympify(right or '0')
133
+ a = float(expr.coeff(x, 1)) if expr.coeff(x, 1) else 0
134
+ b = float(expr.coeff(y, 1)) if expr.coeff(y, 1) else 0
135
+ c = float(-expr.as_coefficients_dict()[1]) if 1 in expr.as_coefficients_dict() else 0
136
+ return f"{a} {b} {c}"
137
+
138
+ eq1_coeffs = parse_linear_eq(eq1_str)
139
+ eq2_coeffs = parse_linear_eq(eq2_str)
140
+
141
+ return {
142
+ "type": "linear",
143
+ "eq1_coeffs": eq1_coeffs,
144
+ "eq2_coeffs": eq2_coeffs,
145
+ "latex": latex_str,
146
+ "success": True
147
+ }
148
+ except Exception as e:
149
+ logger.error(f"Error extracting linear system coefficients: {e}")
150
+ return {
151
+ "type": "linear",
152
+ "eq1_coeffs": "1 1 3",
153
+ "eq2_coeffs": "1 -1 1",
154
+ "latex": latex_str,
155
+ "success": False,
156
+ "error": str(e)
157
+ }
158
+
159
+ def extract_equation_from_image(image_file):
160
+ """Extract equation from image using Pix2Text"""
161
+ try:
162
+ if p2t_model is None:
163
+ return {
164
+ "type": "error",
165
+ "latex": "Pix2Text model not loaded. Please check installation.",
166
+ "success": False
167
+ }
168
+
169
+ if image_file is None:
170
+ return {
171
+ "type": "error",
172
+ "latex": "No image file provided.",
173
+ "success": False
174
+ }
175
+
176
+ if isinstance(image_file, str):
177
+ image = Image.open(image_file)
178
+ else:
179
+ image = Image.open(image_file.name)
180
+
181
+ if image.mode != 'RGB':
182
+ image = image.convert('RGB')
183
+
184
+ logger.info(f"Processing image of size: {image.size}")
185
+
186
+ result = p2t_model.recognize_text_formula(image)
187
+ if not result or result.strip() == "":
188
+ return {
189
+ "type": "error",
190
+ "latex": "No text or formulas detected in the image.",
191
+ "success": False
192
+ }
193
+
194
+ logger.info(f"Extracted text: {result}")
195
+
196
+ eq_type = parse_equation_type(result)
197
+ if eq_type == 'polynomial':
198
+ return extract_polynomial_coefficients(result)
199
+ elif eq_type == 'linear_system':
200
+ return extract_linear_system_coefficients(result)
201
+ else:
202
+ return {
203
+ "type": "error",
204
+ "latex": f"Unsupported equation type detected: {eq_type}",
205
+ "success": False
206
+ }
207
+ except Exception as e:
208
+ logger.error(f"Error processing image: {e}")
209
+ return {
210
+ "type": "error",
211
+ "latex": f"Error processing image: {str(e)}",
212
+ "success": False
213
+ }
214
+
215
+ def solve_polynomial(degree, coeff_string, real_only):
216
+ """Solve polynomial equation"""
217
+ try:
218
+ coeffs = list(map(float, coeff_string.strip().split()))
219
+ if len(coeffs) != degree + 1:
220
+ return f"⚠️ Please enter exactly {degree + 1} coefficients.", None, None
221
+
222
+ poly = sum([coeffs[i] * x**(degree - i) for i in range(degree + 1)])
223
+ simplified = sp.simplify(poly)
224
+ factored = sp.factor(simplified)
225
+ roots = sp.solve(sp.Eq(simplified, 0), x)
226
+
227
+ if real_only:
228
+ roots = [r for r in roots if sp.im(r) == 0]
229
+
230
+ roots_output = "$$\n" + "\\ ".join(
231
+ [f"r_{{{i}}} = {sp.latex(sp.nsimplify(r, rational=True))}" for i, r in enumerate(roots, 1)]
232
+ ) + "\n$$"
233
+
234
+ steps_output = f"""
235
+ ### Polynomial Expression
236
+ $$ {sp.latex(poly)} = 0 $$
237
+ ### Simplified
238
+ $$ {sp.latex(simplified)} = 0 $$
239
+ ### Factored
240
+ $$ {sp.latex(factored)} = 0 $$
241
+ ### Roots {'(Only Real)' if real_only else '(All Roots)'}
242
+ {roots_output}
243
+ """
244
+
245
+ x_vals = np.linspace(-10, 10, 400)
246
+ y_vals = np.polyval(coeffs, x_vals)
247
+
248
+ fig, ax = plt.subplots(figsize=(6, 4))
249
+ ax.plot(x_vals, y_vals, label="Polynomial", color="blue")
250
+ ax.axhline(0, color='black', linewidth=0.5)
251
+ ax.axvline(0, color='black', linewidth=0.5)
252
+ ax.grid(True)
253
+ ax.set_title("Graph of the Polynomial")
254
+ ax.set_xlabel("x")
255
+ ax.set_ylabel("f(x)")
256
+ ax.legend()
257
+
258
+ return steps_output, fig, ""
259
+ except Exception as e:
260
+ return f"❌ Error: {e}", None, ""
261
+
262
+ def solve_linear_system_from_coeffs(eq1_str, eq2_str):
263
+ """Solve linear system"""
264
+ try:
265
+ coeffs1 = list(map(float, eq1_str.strip().split()))
266
+ coeffs2 = list(map(float, eq2_str.strip().split()))
267
+
268
+ if len(coeffs1) != 3 or len(coeffs2) != 3:
269
+ return "⚠️ Please enter exactly 3 coefficients for each equation.", None, None, None
270
+
271
+ a1, b1, c1 = coeffs1
272
+ a2, b2, c2 = coeffs2
273
+
274
+ eq1 = sp.Eq(a1 * x + b1 * y, c1)
275
+ eq2 = sp.Eq(a2 * x + b2 * y, c2)
276
+
277
+ sol = sp.solve([eq1, eq2], (x, y), dict=True)
278
+ if not sol:
279
+ return "❌ No unique solution.", None, None, None
280
+
281
+ solution = sol[0]
282
+ eq_latex = f"$$ {sp.latex(eq1)} \\ {sp.latex(eq2)} $$"
283
+
284
+ steps = rf"""
285
+ ### Step-by-step Solution
286
+ 1. **Original Equations:**
287
+ $$ {sp.latex(eq1)} $$
288
+ $$ {sp.latex(eq2)} $$
289
+ 2. **Standard Form:** Already provided.
290
+ 3. **Solve using SymPy `solve`:** Internally applies substitution/elimination.
291
+ 4. **Solve for `x` and `y`:**
292
+ $$ x = {sp.latex(solution[x])}, \quad y = {sp.latex(solution[y])} $$
293
+ 5. **Verification:** Substitute back into both equations."""
294
+
295
+ x_vals = np.linspace(-10, 10, 400)
296
+ f1 = sp.solve(eq1, y)
297
+ f2 = sp.solve(eq2, y)
298
+
299
+ fig, ax = plt.subplots()
300
+ if f1:
301
+ f1_func = sp.lambdify(x, f1[0], modules='numpy')
302
+ ax.plot(x_vals, f1_func(x_vals), label=sp.latex(eq1))
303
+ if f2:
304
+ f2_func = sp.lambdify(x, f2[0], modules='numpy')
305
+ ax.plot(x_vals, f2_func(x_vals), label=sp.latex(eq2))
306
+
307
+ ax.plot(solution[x], solution[y], 'ro', label=f"Solution ({solution[x]}, {solution[y]})")
308
+ ax.axhline(0, color='black', linewidth=0.5)
309
+ ax.axvline(0, color='black', linewidth=0.5)
310
+ ax.legend()
311
+ ax.set_title("Graph of the Linear System")
312
+ ax.grid(True)
313
+
314
+ return eq_latex, steps, fig, ""
315
+ except Exception as e:
316
+ return f"❌ Error: {e}", None, None, None
317
+
318
+ def solve_extracted_equation(eq_data, real_only):
319
+ """Route to appropriate solver based on equation type"""
320
+ if eq_data["type"] == "polynomial":
321
+ return solve_polynomial(eq_data["degree"], eq_data["coeffs"], real_only)
322
+ elif eq_data["type"] == "linear":
323
+ return "❌ Single linear equation not supported. Please upload a system of equations.", None, ""
324
+ elif eq_data["type"] == "linear_system":
325
+ return solve_linear_system_from_coeffs(eq_data["eq1_coeffs"], eq_data["eq2_coeffs"])
326
+ else:
327
+ return "❌ Unknown equation type", None, ""
328
+
329
+ def image_tab():
330
+ """Create the Image Upload Solver tab"""
331
+ with gr.Tab("Image Upload Solver"):
332
+ gr.Markdown("## Solve Equations from Image")
333
+
334
+ with gr.Row():
335
+ image_input = gr.File(
336
+ label="Upload Question Image",
337
+ file_types=[".pdf", ".png", ".jpg", ".jpeg"],
338
+ file_count="single"
339
+ )
340
+ image_upload_btn = gr.Button("Process Image")
341
+
342
+ gr.Markdown("**Supported Formats:** .pdf, .png, .jpg, .jpeg")
343
+
344
+ with gr.Row():
345
+ real_image_checkbox = gr.Checkbox(label="Show Only Real Roots (for Polynomials)", value=False)
346
+ preview_image_btn = gr.Button("Preview Equation")
347
+
348
+ image_equation_display = gr.Markdown()
349
+
350
+ with gr.Row():
351
+ confirm_image_btn = gr.Button("Display Solution", visible=False)
352
+ edit_image_btn = gr.Button("Make Changes Manually", visible=False)
353
+
354
+ edit_latex_input = gr.Textbox(label="Edit LaTeX Equation", visible=False, lines=3)
355
+ save_edit_btn = gr.Button("Save Changes", visible=False)
356
+
357
+ image_steps_md = gr.Markdown()
358
+ image_plot_output = gr.Plot()
359
+ extracted_eq_state = gr.State()
360
+
361
+ def handle_image_upload(image_file):
362
+ """Handle image upload and initial processing"""
363
+ if image_file is None:
364
+ return "", None, "", None, None
365
+
366
+ try:
367
+ eq_data = extract_equation_from_image(image_file)
368
+ if eq_data["success"]:
369
+ return "", eq_data, "", None, None
370
+ else:
371
+ return "", eq_data, "", None, None
372
+ except Exception as e:
373
+ return "", None, "", None, None
374
+
375
+ image_upload_btn.click(
376
+ fn=handle_image_upload,
377
+ inputs=[image_input],
378
+ outputs=[image_equation_display, extracted_eq_state, image_steps_md,
379
+ image_plot_output, edit_latex_input]
380
+ )
381
+
382
+ def preview_image_equation(eq_data, real_only):
383
+ """Preview the extracted equation"""
384
+ if eq_data is None:
385
+ return ("⚠️ No equation data available. Please upload and process an image first.",
386
+ gr.update(visible=False), gr.update(visible=False), "", None)
387
+
388
+ if eq_data["type"] == "error":
389
+ return (eq_data["latex"], gr.update(visible=False), gr.update(visible=False), "", None)
390
+
391
+ if eq_data["type"] == "polynomial":
392
+ eq_type_display = "Polynomial Equation"
393
+ elif eq_data["type"] == "linear_system":
394
+ eq_type_display = "Linear System"
395
+ else:
396
+ eq_type_display = "Unknown Equation Type"
397
+
398
+ preview_text = f"""
399
+ ### βœ… Confirm {eq_type_display}
400
+ **Extracted LaTeX:** {eq_data['latex']}
401
+ """
402
+
403
+ return (preview_text, gr.update(visible=True), gr.update(visible=True), "", None)
404
+
405
+ preview_image_btn.click(
406
+ fn=preview_image_equation,
407
+ inputs=[extracted_eq_state, real_image_checkbox],
408
+ outputs=[image_equation_display, confirm_image_btn, edit_image_btn,
409
+ image_steps_md, image_plot_output]
410
+ )
411
+
412
+ def confirm_image_solution(eq_data, real_only):
413
+ """Confirm and solve the extracted equation"""
414
+ if eq_data is None or eq_data["type"] == "error":
415
+ return "⚠️ No valid equation to solve.", None, ""
416
+
417
+ try:
418
+ steps, plot, error = solve_extracted_equation(eq_data, real_only)
419
+ return steps, plot, ""
420
+ except Exception as e:
421
+ return f"❌ Error solving equation: {str(e)}", None, ""
422
+
423
+ confirm_image_btn.click(
424
+ fn=confirm_image_solution,
425
+ inputs=[extracted_eq_state, real_image_checkbox],
426
+ outputs=[image_steps_md, image_plot_output, image_equation_display]
427
+ )
428
+
429
+ def enable_manual_edit(eq_data):
430
+ """Enable manual editing of the equation"""
431
+ if eq_data is None:
432
+ latex_value = "No equation to edit. Please upload an image first."
433
+ elif eq_data["type"] == "error":
434
+ latex_value = "Error in extraction. Please enter your equation manually."
435
+ else:
436
+ latex_value = eq_data.get("latex", "")
437
+
438
+ return (gr.update(visible=True, value=latex_value),
439
+ gr.update(visible=True),
440
+ gr.update(visible=False),
441
+ gr.update(visible=False))
442
+
443
+ edit_image_btn.click(
444
+ fn=enable_manual_edit,
445
+ inputs=[extracted_eq_state],
446
+ outputs=[edit_latex_input, save_edit_btn, confirm_image_btn, edit_image_btn]
447
+ )
448
+
449
+ def save_manual_changes(latex_input, real_only):
450
+ """Save manual changes and solve"""
451
+ try:
452
+ if not latex_input or latex_input.strip() == "":
453
+ return "⚠️ Please enter a valid equation.", None, ""
454
+
455
+ eq_type = parse_equation_type(latex_input)
456
+ if eq_type == 'polynomial':
457
+ eq_data = extract_polynomial_coefficients(latex_input)
458
+ steps, plot, error = solve_polynomial(eq_data["degree"], eq_data["coeffs"], real_only)
459
+ elif eq_type == 'linear_system':
460
+ eq_data = extract_linear_system_coefficients(latex_input)
461
+ eq_latex, steps, plot, error = solve_linear_system_from_coeffs(
462
+ eq_data["eq1_coeffs"], eq_data["eq2_coeffs"])
463
+ else:
464
+ return "❌ Unsupported equation type", None, ""
465
+
466
+ return steps, plot, ""
467
+ except Exception as e:
468
+ return f"❌ Error parsing manual input: {str(e)}", None, ""
469
+
470
+ save_edit_btn.click(
471
+ fn=save_manual_changes,
472
+ inputs=[edit_latex_input, real_image_checkbox],
473
+ outputs=[image_steps_md, image_plot_output, image_equation_display]
474
+ )
475
+
476
+ return (image_input, image_upload_btn, real_image_checkbox, preview_image_btn,
477
+ image_equation_display, confirm_image_btn, edit_image_btn, edit_latex_input,
478
+ save_edit_btn, image_steps_md, image_plot_output, extracted_eq_state)