File size: 8,331 Bytes
7e55a7c
de2c13c
8cac5bb
 
 
 
 
7e55a7c
8cac5bb
0bc3219
 
 
f68692c
8cac5bb
bec810e
0f9d543
bec810e
325bf57
737ea8f
bec810e
 
480f7a3
bec810e
480f7a3
 
 
30f343b
ad0779c
737ea8f
 
c63cd75
0f9d543
 
 
 
 
 
 
 
 
 
 
 
 
3aa5fcb
0f9d543
 
 
 
 
62eb7bb
0f9d543
b3dcf15
f68692c
1bd6ac5
 
b4b9859
f68692c
e2e0855
f68692c
9f32fef
 
f68692c
83cf3fc
a760562
f68692c
 
 
 
 
 
 
 
9f32fef
f68692c
83cf3fc
f68692c
 
a760562
f68692c
 
 
 
 
 
 
 
 
 
 
 
 
83cf3fc
68e99a6
7e55a7c
de2c13c
8cac5bb
f68692c
a09b9ed
8cac5bb
143901b
3e4bb31
8cf9b2c
a18e934
be29b4b
95cb2cd
 
 
 
fbda286
95cb2cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fbda286
8cac5bb
95cb2cd
6884711
68e99a6
f68692c
 
0ae7b15
f68692c
fd61a77
d042fe9
 
 
 
 
 
f68692c
d042fe9
b4b9859
 
 
83cf3fc
 
d042fe9
83cf3fc
d042fe9
e943cdd
 
 
 
f68692c
e943cdd
 
 
 
 
f68692c
e943cdd
d042fe9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9e07120
d042fe9
 
 
 
 
83cf3fc
fd61a77
 
 
 
 
 
 
 
 
 
 
 
 
 
0f9d543
7e55a7c
 
299839b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import gradio as gr
from PIL import Image
from pix2tex.cli import LatexOCR
import sympy as sp
from sympy.parsing.latex import parse_latex
import re

model = LatexOCR()

def preprocess_handwritten_image(pil_img):
    return pil_img.convert('RGB')

# Tab 1 cleaning (KEEP UNCHANGED)
def clean_latex(latex):
    latex = latex.replace('\\ ', '')
    latex = latex.replace('\\\\', '\\')
    latex = re.sub(r'\\[ \t\n\r\f\v]*', '', latex)
    latex = re.sub(r'\\([+\-=])', r'\1', latex)
    replacements = {
        r'\\chi': 'x', r'chi': 'x',
        r'\\xi': 'x', r'xi': 'x',
        r'\\alpha': 'x', r'alpha': 'x',
        r'\\beta': 'b', r'beta': 'b',
        r'\\gamma': 'y', r'gamma': 'y',
        r'\\vartheta': '3', r'vartheta': '3',
        r'\\mathcalW': 'x', r'mathcalW': 'x',
        r'\\pi': 'pi', r'pi': 'pi',
        r'\\mathrm': '', r'mathrm': '',
    }
    for wrong, correct in replacements.items():
        latex = re.sub(wrong, correct, latex)
    latex = re.sub(r'\\(cal|mathcal)\s*\{?\s*[Xx]\s*\}?', 'x', latex)
    latex = re.sub(r'\\(cal|mathcal)\s*\{?\s*[Yy]\s*\}?', 'y', latex)
    latex = re.sub(r'\\(cal|mathcal)\s*\{?\s*[Zz]\s*\}?', 'z', latex)
    latex = latex.replace('cal x', 'x').replace('cal X', 'x')
    latex = latex.replace('mathcal x', 'x').replace('mathcal X', 'x')
    latex = latex.replace('{', '').replace('}', '')
    latex = latex.strip().rstrip(',.')
    latex = re.sub(r'(?<![a-zA-Z0-9])e(?![a-zA-Z0-9])', 'E', latex)
    latex = re.sub(r'(\d)([a-zA-Z])', r'\1*\2', latex)
    latex = re.sub(r'(\d+)\s*i', r'\1*I', latex)
    latex = re.sub(r'(?<![a-zA-Z0-9])i(?![a-zA-Z0-9])', 'I', latex)
    latex = re.sub(r'\(([^()]+?)\)\s*([a-zA-Z](\^\d+)?)', r'(\1)*\2', latex)
    latex = latex.replace(r'\cdot', '*')
    latex = latex.replace('βˆ’', '-')
    latex = re.sub(r'[^\w\s^=+*\-().]', '', latex)
    if '=' not in latex:
        latex += '=0'
    latex = latex.replace('pi', '3.1416')
    latex = latex.replace('e', '2.7183')
    latex = latex.replace('E', '2.7183')
    return latex

# βœ… Tab 2 specific cleaner
def clean_latex2(latex):
    latex = latex.replace('\\ ', '')
    latex = latex.replace('\\\\', '\\')
    latex = re.sub(r'\\[ \t\n\r\f\v]*', '', latex)
    latex = latex.replace(r'\times', 'x')
    latex = latex.replace(r'\cdot', '*')
    latex = latex.replace('βˆ’', '-').replace('–', '-')

    # Remove LaTeX wrappers like \mathcal{}, \mathbf{}, etc.
    latex = re.sub(r'\\(text|mathbf|mathrm|mathit|textbf|mathcal|cal)\s*\{([^{}]+)\}', r'\2', latex)

    # Remove remaining LaTeX commands (like \!\!\! and \,)
    latex = re.sub(r'\\[a-zA-Z]+', '', latex)

    # Remove nested or empty braces
    latex = re.sub(r'\{+\}+', '', latex)
    latex = re.sub(r'\{([^\{\}]*)\}', r'\1', latex)

    # Remove extra commas, semicolons, weird formatting characters
    latex = re.sub(r'[;,]', '\n', latex)
    latex = re.sub(r'[^\w\s=+\-*/().]', '', latex)

    # Replace multiple spaces with single
    latex = re.sub(r'\s+', ' ', latex)

    # Normalize equals
    latex = re.sub(r'(?<![=<>])=(?![=<>])', ' = ', latex)

    # Replace ambiguous variable artifacts
    replacements = {
        'chi': 'x', 'xi': 'x', 'alpha': 'x', 'beta': 'b',
        'gamma': 'y', 'vartheta': '3', 'mathcal': '',
        'cal': '', 'mathrm': ''
    }
    for wrong, right in replacements.items():
        latex = re.sub(wrong, right, latex)

    return latex.strip()

def solve_polynomial(image):
    try:
        img = preprocess_handwritten_image(image)
        latex_result = model(img)
        if not latex_result or len(latex_result.strip()) < 2:
            return "❌ Could not extract valid LaTeX from image.", "", ""
        cleaned_latex = clean_latex(latex_result)
        try:
            expr = parse_latex(cleaned_latex)
            expr = expr.subs(sp.pi, sp.Float(3.1416)).subs(sp.E, sp.Float(2.7183))
            if not isinstance(expr, sp.Equality):
                raise ValueError("Expression is not an equation.")
            lhs = expr.lhs - expr.rhs
            if not lhs.is_polynomial():
                raise ValueError("Not a polynomial")
        except:
            return f"❌ Could not parse expression:\n\n```latex\n{cleaned_latex}\n```", cleaned_latex, ""

        output = f"## πŸ“„ Extracted LaTeX\n```latex\n{latex_result}\n```\n"
        output += f"---\n## 🧹 Cleaned LaTeX\n```latex\n{cleaned_latex}\n```\n"
        output += f"---\n## 🧠 Parsed Expression\n$$ {sp.latex(expr)} $$\n"

        lhs = expr.lhs - expr.rhs
        factor = sp.factor(lhs)
        output += f"---\n## ✏️ Standard Form\n$$ {sp.latex(lhs)} = 0 $$\n"
        output += f"---\n## 🧩 Factorized\n$$ {sp.latex(factor)} = 0 $$\n"

        x = next(iter(lhs.free_symbols))
        roots = sp.solve(lhs, x)
        output += "## βœ… Roots\n"
        output += "$$\n\\begin{aligned}\n"
        for i, r in enumerate(roots, 1):
            root_val = sp.N(r, 6)
            output += f"\\text{{Root {i}}}:\\quad x &\\approx {sp.latex(root_val)} \\\\\n"
        output += "\\end{aligned}\n$$\n"

        return output, cleaned_latex, ""
    except Exception as e:
        return f"❌ Error: {str(e)}", "", ""

def wrapped_solver(img):
    result, cleaned, prompt = solve_polynomial(img)
    return result, cleaned, prompt

# βœ… FIXED: moved outside of gr.Blocks()
def solve_system_of_equations(image):
    try:
        img = preprocess_handwritten_image(image)
        raw_latex = model(img)
        if not raw_latex or len(raw_latex.strip()) < 3:
            return "❌ Could not extract valid LaTeX."

        # Handle array environment
        if r"\begin{array" in raw_latex:
            lines = re.findall(r'\\begin{array}{[^}]+}(.+?)\\end{array}', raw_latex, re.DOTALL)
            if lines:
                raw_latex = lines[0]
            raw_latex = raw_latex.replace(r'\\', '\n')
            raw_latex = re.sub(r'&', '', raw_latex)

        raw_latex = raw_latex.replace("\n", " ")
        equations = re.split(r'[;,]', raw_latex)

        cleaned_equations = []
        for eq in equations:
            cleaned = clean_latex2(eq)
            cleaned = re.sub(r'(?<![=<>])=(?![=<>])', ' = ', cleaned)
            if '=' in cleaned:
                try:
                    parsed = parse_latex(cleaned)
                    if isinstance(parsed, sp.Equality):
                        cleaned_equations.append(cleaned)
                except Exception:
                    continue

        sympy_eqs = []
        symbols = set()
        for eq in cleaned_equations:
            parsed = parse_latex(eq)
            if isinstance(parsed, sp.Equality):
                sympy_eqs.append(parsed)
                symbols.update(parsed.free_symbols)

        if len(sympy_eqs) < 2:
            return f"❌ Not enough valid equations detected.\n\n```latex\n{raw_latex}\n```"

        sol = sp.solve(sympy_eqs, list(symbols), dict=True)
        output = f"## 🧾 Detected Equations:\n```latex\n{raw_latex}\n```\n"
        output += "---\n## ✏️ Parsed & Cleaned:\n"
        for eq in sympy_eqs:
            output += f"$$ {sp.latex(eq)} $$\n"
        output += "---\n## βœ… Solution:\n"
        if sol:
            output += "$$" + sp.latex(sol[0]) + "$$"
        else:
            output += "❌ No solution found or system may be inconsistent."
        return output
    except Exception as e:
        return f"❌ Error: {str(e)}"

# === UI ===
with gr.Blocks() as demo:
    with gr.Tab("πŸ–ΌοΈ Parse from Image"):
        image_input = gr.Image(type="pil", label="πŸ“· Upload Image of Polynomial")
        hidden_latex = gr.Textbox(visible=False)
        explanation_prompt = gr.Textbox(visible=False)
        output_box = gr.Markdown(label="πŸ“‹ Step-by-step Solution")
        submit_btn = gr.Button("πŸ” Solve")
        submit_btn.click(fn=wrapped_solver, inputs=[image_input], outputs=[output_box, hidden_latex, explanation_prompt])

    with gr.Tab("πŸ“ Solve Simultaneous Equations"):
        sim_image_input = gr.Image(type="pil", label="πŸ“· Upload Image with Simultaneous Equations")
        sim_output = gr.Markdown(label="πŸ“‹ Solved System Output")
        sim_button = gr.Button("πŸ“Œ Solve System")
        sim_button.click(fn=solve_system_of_equations, inputs=[sim_image_input], outputs=[sim_output])

if __name__ == "__main__":
    demo.launch()