diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..62bb4e28baf63f68e1fb89b8a7f88ac69d039d51 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,30 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +static/uploads/Assignment_1_Solution.pdf filter=lfs diff=lfs merge=lfs -text +static/uploads/b0d6064f-3245-40bc-87b5-bfbb459979fd.jpeg filter=lfs diff=lfs merge=lfs -text +static/uploads/capture.jpg filter=lfs diff=lfs merge=lfs -text +static/uploads/CSE331.pdf filter=lfs diff=lfs merge=lfs -text +static/uploads/input2.png filter=lfs diff=lfs merge=lfs -text +static/uploads/math_formula_sheet.pdf filter=lfs diff=lfs merge=lfs -text +static/uploads/page_10.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_11.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_2.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_3.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_4.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_5.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_6.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_7.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_8.png filter=lfs diff=lfs merge=lfs -text +static/uploads/page_9.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_26.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_27.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_28.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_29.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_30.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_31.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_32.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_33.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_34.png filter=lfs diff=lfs merge=lfs -text +static/uploads/table_35.png filter=lfs diff=lfs merge=lfs -text +static/uploads/WhatsApp[[:space:]]Image[[:space:]]2025-10-26[[:space:]]at[[:space:]]01.38.22_3d87e144.jpg filter=lfs diff=lfs merge=lfs -text diff --git a/controller/__init__.py b/controller/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/controller/__pycache__/__init__.cpython-311.pyc b/controller/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f88637e5126ddd6b1e42bdd939dc2131726b5e11 Binary files /dev/null and b/controller/__pycache__/__init__.cpython-311.pyc differ diff --git a/controller/__pycache__/__init__.cpython-313.pyc b/controller/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f44cae0084140be4abbde073782d8922059e6d56 Binary files /dev/null and b/controller/__pycache__/__init__.cpython-313.pyc differ diff --git a/controller/__pycache__/auth_controller.cpython-311.pyc b/controller/__pycache__/auth_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4de80028fb126d78001140164a28f070eef19a7 Binary files /dev/null and b/controller/__pycache__/auth_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/auth_controller.cpython-313.pyc b/controller/__pycache__/auth_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1d9b194311f9235fae976688d7635e4fbb8b292 Binary files /dev/null and b/controller/__pycache__/auth_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/bbbb.cpython-311.pyc b/controller/__pycache__/bbbb.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..623492c524928302527966b3d6b813f226aa77f5 Binary files /dev/null and b/controller/__pycache__/bbbb.cpython-311.pyc differ diff --git a/controller/__pycache__/chat_controller.cpython-313.pyc b/controller/__pycache__/chat_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52e1c458fc9bf16e26eda2ca2d66ac58c3a9e93d Binary files /dev/null and b/controller/__pycache__/chat_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/graph_controller.cpython-311.pyc b/controller/__pycache__/graph_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1aa6b071de60530e04778863d6f391ca30a34f7 Binary files /dev/null and b/controller/__pycache__/graph_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/graph_controller.cpython-313.pyc b/controller/__pycache__/graph_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..459f6e8f2e2b401ab50fccbf897b1884b10335db Binary files /dev/null and b/controller/__pycache__/graph_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/latex_to_text_controller.cpython-313.pyc b/controller/__pycache__/latex_to_text_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc23615535549c750f8a490a578d4b4708192673 Binary files /dev/null and b/controller/__pycache__/latex_to_text_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/mathcamera_controller.cpython-311.pyc b/controller/__pycache__/mathcamera_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d800fa4308e021fe65de40ea583bb0990d6312d3 Binary files /dev/null and b/controller/__pycache__/mathcamera_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/pdf_controller.cpython-311.pyc b/controller/__pycache__/pdf_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bee4b02157e6b29bc6bcdd630a065a013a2ea3c2 Binary files /dev/null and b/controller/__pycache__/pdf_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/pdf_controller.cpython-313.pyc b/controller/__pycache__/pdf_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3a2685a9528ededddf6149c648f31e00bb7d402 Binary files /dev/null and b/controller/__pycache__/pdf_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/pdffly_controller.cpython-311.pyc b/controller/__pycache__/pdffly_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c03eefe8e7ddff630571ee5a1ceb446d5f7a1016 Binary files /dev/null and b/controller/__pycache__/pdffly_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/pdffly_controller.cpython-313.pyc b/controller/__pycache__/pdffly_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4704d7181d3b0c581fb0d3824df04d7c96ed409f Binary files /dev/null and b/controller/__pycache__/pdffly_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/pdflly_controller.cpython-311.pyc b/controller/__pycache__/pdflly_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4af8d135f31d25d44ba49c6dc7ebfcc12746178f Binary files /dev/null and b/controller/__pycache__/pdflly_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/pdflly_controller.cpython-313.pyc b/controller/__pycache__/pdflly_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a941113797077ccb6085f2df873f264caaa28eff Binary files /dev/null and b/controller/__pycache__/pdflly_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/pix2text_controller.cpython-311.pyc b/controller/__pycache__/pix2text_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92ed48528a491a62aed8c948fad785d39e039b1a Binary files /dev/null and b/controller/__pycache__/pix2text_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/pix2text_controller.cpython-313.pyc b/controller/__pycache__/pix2text_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32b06852a27a9cf6549c2308b68e3d86eb18e111 Binary files /dev/null and b/controller/__pycache__/pix2text_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/scribble_controller.cpython-311.pyc b/controller/__pycache__/scribble_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55d31b15cf3211a906c1060edf5f24bbb718d8d8 Binary files /dev/null and b/controller/__pycache__/scribble_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/scribble_controller.cpython-313.pyc b/controller/__pycache__/scribble_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44379a21c8b68761ee96950e2cdd676861a9a6de Binary files /dev/null and b/controller/__pycache__/scribble_controller.cpython-313.pyc differ diff --git a/controller/__pycache__/table_controller.cpython-311.pyc b/controller/__pycache__/table_controller.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87dc4d9c7328e9f529a65d83eebac1337372162b Binary files /dev/null and b/controller/__pycache__/table_controller.cpython-311.pyc differ diff --git a/controller/__pycache__/table_controller.cpython-313.pyc b/controller/__pycache__/table_controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed560416aa00c3299f47cc5da0e5925f090939be Binary files /dev/null and b/controller/__pycache__/table_controller.cpython-313.pyc differ diff --git a/controller/auth_controller.py b/controller/auth_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..ddca16f3e59b00f6fb366aea01cb3b0119982585 --- /dev/null +++ b/controller/auth_controller.py @@ -0,0 +1,97 @@ +import os +import time +from flask import Blueprint, render_template, request, jsonify, redirect, url_for, session, current_app +from flask_login import LoginManager, login_user, logout_user, login_required, current_user +import requests +from models.user import User + +auth_bp = Blueprint('auth', __name__) + +# Google OAuth configuration (these would normally come from environment variables) +GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', 'your-google-client-id') +GOOGLE_CLIENT_SECRET = os.environ.get('GOOGLE_CLIENT_SECRET', 'your-google-client-secret') +GOOGLE_REDIRECT_URI = os.environ.get('GOOGLE_REDIRECT_URI', 'http://localhost:5000/auth/google/callback') + +# Initialize login manager +login_manager = LoginManager() + +def init_app(app): + """Initialize the login manager with the app""" + login_manager.init_app(app) + # Set login view - using setattr to avoid type checking issues + setattr(login_manager, 'login_view', 'auth.login') + return login_manager + +@login_manager.user_loader +def load_user(user_id): + return User.get(user_id) + + +@auth_bp.route("/login") +def login(): + """Display the login page""" + return render_template('login.html') + + +@auth_bp.route("/google/login") +def google_login(): + """Redirect to Google OAuth login""" + # In a real implementation, you would redirect to Google's OAuth endpoint + # For now, we'll create a dummy user for testing + user = User.create_or_update( + id="google_user_123", + email="user@gmail.com", + name="Google User", + picture=None + ) + + if user: + login_user(user) + return redirect(url_for('index')) + else: + return "Failed to create user", 500 + + +@auth_bp.route("/guest/login", methods=['POST']) +def guest_login(): + """Login as a guest user""" + name = request.form.get('name') + if not name: + return "Name is required", 400 + + # Create a guest user + user = User.create_or_update( + id=f"guest_{name.lower().replace(' ', '_')}_{int(time.time())}", + email=f"{name.lower().replace(' ', '.')}@guest.texlab.com", + name=name, + picture=None + ) + + if user: + login_user(user) + return redirect(url_for('index')) + else: + return "Failed to create user", 500 + + +@auth_bp.route("/logout") +@login_required +def logout(): + logout_user() + return redirect(url_for('auth.login')) + + +@auth_bp.route("/user") +def get_current_user(): + if current_user.is_authenticated: + return jsonify({ + 'authenticated': True, + 'user': { + 'id': current_user.id, + 'email': current_user.email, + 'name': current_user.name, + 'picture': current_user.picture + } + }) + else: + return jsonify({'authenticated': False}) \ No newline at end of file diff --git a/controller/graph_controller.py b/controller/graph_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..e188abd7564b6a307fb1600936a14a45cbd38d81 --- /dev/null +++ b/controller/graph_controller.py @@ -0,0 +1,189 @@ +import os +from flask import Blueprint, render_template, request, jsonify +import numpy as np +import matplotlib +matplotlib.use('Agg') # Use non-interactive backend +import matplotlib.pyplot as plt +import base64 +from io import BytesIO +import re + +graph_bp = Blueprint('graph_bp', __name__) + +UPLOAD_FOLDER = 'static/uploads' +GRAPHS_FOLDER = 'static/graphs' +os.makedirs(UPLOAD_FOLDER, exist_ok=True) +os.makedirs(GRAPHS_FOLDER, exist_ok=True) + + +def parse_plot_command(command): + """Parse plot command and extract function and range""" + # Remove extra spaces + command = command.strip() + + # Match patterns like "plot x^2 from -5 to 5" or "plot sin(x) from 0 to 2*pi" + pattern = r"plot\s+(.+?)\s+from\s+(-?[\d\*\.pi]+)\s+to\s+(-?[\d\*\.pi]+)" + match = re.match(pattern, command, re.IGNORECASE) + + if match: + function = match.group(1) + x_min = match.group(2) + x_max = match.group(3) + + # Convert pi expressions + x_min = x_min.replace('pi', 'np.pi') + x_max = x_max.replace('pi', 'np.pi') + + try: + x_min_val = eval(x_min) + x_max_val = eval(x_max) + return function, x_min_val, x_max_val + except: + raise ValueError("Invalid range values") + + # Match simpler patterns like "plot x^2" + simple_pattern = r"plot\s+(.+)" + simple_match = re.match(simple_pattern, command, re.IGNORECASE) + + if simple_match: + function = simple_match.group(1) + return function, -10, 10 # Default range + + raise ValueError("Invalid plot command format") + + +def evaluate_function(func_str, x): + """Safely evaluate mathematical function""" + # Replace common math functions + func_str = func_str.replace('^', '**') + func_str = func_str.replace('sin', 'np.sin') + func_str = func_str.replace('cos', 'np.cos') + func_str = func_str.replace('tan', 'np.tan') + func_str = func_str.replace('log', 'np.log') + func_str = func_str.replace('exp', 'np.exp') + func_str = func_str.replace('sqrt', 'np.sqrt') + func_str = func_str.replace('abs', 'np.abs') + + # Replace x with the actual value + func_str = func_str.replace('x', f'({x})') + + try: + return eval(func_str) + except: + raise ValueError(f"Error evaluating function: {func_str}") + + +def generate_tikz_latex(function, x_min, x_max): + """Generate TikZ/pgfplots LaTeX code for the function""" + # Convert function to pgfplots format + pgf_function = function.replace('^', '^') + pgf_function = pgf_function.replace('sqrt', 'sqrt') + + # Create complete LaTeX document with TikZ/pgfplots + latex_code = r'''\documentclass{article} +\usepackage{pgfplots} +\usepackage{amsmath} +\pgfplotsset{compat=1.18} + +\begin{document} + +\begin{figure}[h] +\centering +\begin{tikzpicture} +\begin{axis}[ + xlabel={$x$}, + ylabel={$y$}, + grid=major, + width=12cm, + height=8cm, + samples=200, + domain=''' + f"{x_min}:{x_max}" + r''', + legend pos=north west, + axis lines=middle, +] +\addplot[blue, thick] {''' + pgf_function + r'''}; +\legend{$f(x)=''' + function + r'''$} +\end{axis} +\end{tikzpicture} +\caption{Graph of $f(x) = ''' + function + r'''$} +\label{fig:graph} +\end{figure} + +\end{document}''' + + return latex_code + + +def generate_plot(function, x_min, x_max): + """Generate plot and return base64 image""" + try: + # Create x values + x = np.linspace(x_min, x_max, 1000) + + # Evaluate function for all x values + y = [] + for xi in x: + try: + yi = evaluate_function(function, xi) + y.append(yi) + except: + y.append(np.nan) # Handle undefined points + + y = np.array(y) + + # Create plot + plt.figure(figsize=(10, 6)) + plt.plot(x, y, linewidth=2, color='#667eea') + plt.grid(True, alpha=0.3) + plt.xlabel('x') + plt.ylabel('y') + plt.title(f'Graph of f(x) = {function}') + + # Save to base64 string + buffer = BytesIO() + plt.savefig(buffer, format='png', dpi=150, bbox_inches='tight') + buffer.seek(0) + image_base64 = base64.b64encode(buffer.getvalue()).decode() + plt.close() + + return image_base64 + except Exception as e: + raise ValueError(f"Error generating plot: {str(e)}") + + +@graph_bp.route("/graph") +def graph_page(): + return render_template("graph.html") + + +@graph_bp.route("/graph/generate", methods=["POST"]) +def generate_graph(): + """Generate graph from user input""" + try: + data = request.get_json() + if not data: + return jsonify({'error': 'No data provided'}), 400 + + command = data.get('command', '') + if not command: + return jsonify({'error': 'No command provided'}), 400 + + # Parse the command + function, x_min, x_max = parse_plot_command(command) + + # Generate the plot image + image_base64 = generate_plot(function, x_min, x_max) + + # Generate TikZ/pgfplots LaTeX code + latex_code = generate_tikz_latex(function, x_min, x_max) + + return jsonify({ + 'success': True, + 'image': image_base64, + 'function': function, + 'range': f'[{x_min}, {x_max}]', + 'latex': latex_code + }) + + except Exception as e: + return jsonify({'error': str(e)}), 500 \ No newline at end of file diff --git a/controller/models/__init__.py b/controller/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/controller/models/__pycache__/__init__.cpython-311.pyc b/controller/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b057a2f03373d7985ccafeda66a0ee14c27800f Binary files /dev/null and b/controller/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/controller/models/__pycache__/__init__.cpython-313.pyc b/controller/models/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fbba3fd89ab4ce3bb338afd59aba6784432d20b Binary files /dev/null and b/controller/models/__pycache__/__init__.cpython-313.pyc differ diff --git a/controller/models/__pycache__/camera_to_latex.cpython-311.pyc b/controller/models/__pycache__/camera_to_latex.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d8f3f9411b26d6bc826643385bd5ab0bd30aa22 Binary files /dev/null and b/controller/models/__pycache__/camera_to_latex.cpython-311.pyc differ diff --git a/controller/models/__pycache__/camera_to_latex.cpython-313.pyc b/controller/models/__pycache__/camera_to_latex.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d2d5a7431951e489db76aabc68bdcb3423c5754 Binary files /dev/null and b/controller/models/__pycache__/camera_to_latex.cpython-313.pyc differ diff --git a/controller/models/__pycache__/math_equation.cpython-311.pyc b/controller/models/__pycache__/math_equation.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c67bdbc13afb0fe9b1191505ff7ad18db204b26b Binary files /dev/null and b/controller/models/__pycache__/math_equation.cpython-311.pyc differ diff --git a/controller/models/__pycache__/math_equation.cpython-313.pyc b/controller/models/__pycache__/math_equation.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f1b13b9f7066d3e0908578e918642248d949628 Binary files /dev/null and b/controller/models/__pycache__/math_equation.cpython-313.pyc differ diff --git a/controller/models/__pycache__/scribble_to_latex.cpython-311.pyc b/controller/models/__pycache__/scribble_to_latex.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c1cc1b2b0bd26d7e4429285e6f0f03322dcf70b Binary files /dev/null and b/controller/models/__pycache__/scribble_to_latex.cpython-311.pyc differ diff --git a/controller/models/__pycache__/scribble_to_latex.cpython-313.pyc b/controller/models/__pycache__/scribble_to_latex.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..292a8c46dfd9f39310d02da0ee5e1c10319fc92e Binary files /dev/null and b/controller/models/__pycache__/scribble_to_latex.cpython-313.pyc differ diff --git a/controller/models/camera_to_latex.py b/controller/models/camera_to_latex.py new file mode 100644 index 0000000000000000000000000000000000000000..9d575ecdf3028cc6b8a41b38e570f9b2ad4fd45f --- /dev/null +++ b/controller/models/camera_to_latex.py @@ -0,0 +1,29 @@ +# controller/models/camera_to_latex.py + +import base64 +import io +from PIL import Image +from transformers import TrOCRProcessor +from optimum.onnxruntime import ORTModelForVision2Seq + +print("🔹 Loading Pix2Text model for Camera → LaTeX...") + +processor = TrOCRProcessor.from_pretrained("breezedeus/pix2text-mfr") +model = ORTModelForVision2Seq.from_pretrained("breezedeus/pix2text-mfr", use_cache=False) + + +def camera_to_latex(image_base64: str) -> str: + try: + # Remove header (e.g., "data:image/png;base64,") + image_data = image_base64.split(",")[1] + image_bytes = base64.b64decode(image_data) + image = Image.open(io.BytesIO(image_bytes)).convert("RGB") + + pixel_values = processor(images=image, return_tensors="pt").pixel_values + generated_ids = model.generate(pixel_values) + text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0] + + return text.strip() + except Exception as e: + print(f"❌ Error in camera_to_latex: {e}") + return "⚠️ Error generating LaTeX" diff --git a/controller/models/math_equation.py b/controller/models/math_equation.py new file mode 100644 index 0000000000000000000000000000000000000000..d98248affb0969e42a8d27dfd08aae4be8160c56 --- /dev/null +++ b/controller/models/math_equation.py @@ -0,0 +1,37 @@ +from flask import Flask, request, jsonify, render_template +import os +from werkzeug.utils import secure_filename +from pix2text import Pix2Text # Make sure you installed it +from PIL import Image + +app = Flask(__name__) +app.config['UPLOAD_FOLDER'] = 'static/uploads' +app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'} + +p2t = Pix2Text() # Load Pix2Text model + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'] + +@app.route('/math/process', methods=['POST']) +def process_math(): + if 'image' not in request.files: + return jsonify({'success': False, 'error': 'No file part'}) + + file = request.files['image'] + if file.filename == '': + return jsonify({'success': False, 'error': 'No selected file'}) + + if file and allowed_file(file.filename): + filename = secure_filename(file.filename) + filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) + os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) + file.save(filepath) + + try: + result = p2t(Image.open(filepath)) # Convert image → LaTeX + return jsonify({'success': True, 'latex': result, 'image_path': filepath}) + except Exception as e: + return jsonify({'success': False, 'error': str(e)}) + else: + return jsonify({'success': False, 'error': 'Invalid file type'}) diff --git a/controller/models/scribble_to_latex.py b/controller/models/scribble_to_latex.py new file mode 100644 index 0000000000000000000000000000000000000000..6216995dccee735247753c06aa8a59cc0efbc7fb --- /dev/null +++ b/controller/models/scribble_to_latex.py @@ -0,0 +1,22 @@ +# controller/models/scribble_to_latex.py +import base64 +from io import BytesIO +from PIL import Image +from .math_equation import image_to_latex # reuse your existing pix2tex model + +def scribble_to_latex(image_data: str): + """ + Convert scribble (base64 PNG) to LaTeX using Pix2Text model. + """ + try: + # Decode base64 image + image_bytes = base64.b64decode(image_data.split(',')[1]) + image = Image.open(BytesIO(image_bytes)).convert("RGB") + + # Call your existing model + latex_code = image_to_latex(image) + + return latex_code.strip() + except Exception as e: + print(f"❌ Error in scribble_to_latex: {e}") + return "⚠️ Failed to process scribble" diff --git a/controller/pdf_controller.py b/controller/pdf_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..65709b8ede048bf2ac6f148006a296d210153ea9 --- /dev/null +++ b/controller/pdf_controller.py @@ -0,0 +1,166 @@ +import os +import json +import base64 +import io +from flask import Blueprint, request, render_template, jsonify, current_app +import fitz # PyMuPDF +import PyPDF2 +from PIL import Image +import tempfile + +pdf_bp = Blueprint('pdf', __name__, url_prefix='/pdf') + +def extract_text_from_pdf(filepath, page_number=None, coordinates=None): + """Extract text from PDF using PyMuPDF for better accuracy""" + try: + doc = fitz.open(filepath) + + if page_number is not None: + # Extract from specific page + page = doc[page_number] + + if coordinates: + # Extract text from specific area + x, y, width, height = coordinates + rect = fitz.Rect(x, y, x + width, y + height) + text = page.get_text("text", clip=rect) + else: + # Extract text from entire page + text = page.get_text("text") + else: + # Extract from entire document + text = "" + for page in doc: + text += page.get_text("text") + + doc.close() + return text.strip() + except Exception as e: + print(f"Error extracting text with PyMuPDF: {e}") + # Fallback to PyPDF2 + return extract_text_with_pypdf2(filepath, page_number, coordinates) + +def extract_text_with_pypdf2(filepath, page_number=None, coordinates=None): + """Fallback text extraction using PyPDF2""" + try: + with open(filepath, 'rb') as file: + reader = PyPDF2.PdfReader(file) + + if page_number is not None and page_number < len(reader.pages): + page = reader.pages[page_number] + return page.extract_text() + else: + text = "" + for page in reader.pages: + text += page.extract_text() + return text + except Exception as e: + return f"Error extracting text: {str(e)}" + +def convert_text_to_latex(text): + """Simple conversion of text to LaTeX format""" + # This is a placeholder implementation + # In a real application, you would use an AI model to convert text to LaTeX + return text.replace('\\', '\\\\').replace('_', '\\_').replace('^', '\\^').replace('&', '\\&') + +@pdf_bp.route('/') +def pdf_converter(): + """Render the PDF converter page""" + return render_template('pdf.html') + +@pdf_bp.route('/upload', methods=['POST']) +def upload_pdf(): + """Handle PDF file upload""" + if 'pdf_file' not in request.files: + return jsonify({'success': False, 'error': 'No file provided'}) + + file = request.files['pdf_file'] + + if file.filename == '': + return jsonify({'success': False, 'error': 'No file selected'}) + + if file and file.filename.lower().endswith('.pdf'): + try: + # Save file temporarily + temp_dir = tempfile.gettempdir() + filename = file.filename + filepath = os.path.join(temp_dir, filename) + file.save(filepath) + + # Get page count + with open(filepath, 'rb') as f: + reader = PyPDF2.PdfReader(f) + page_count = len(reader.pages) + + return jsonify({ + 'success': True, + 'filename': filename, + 'filepath': filepath, + 'pages': page_count + }) + except Exception as e: + return jsonify({'success': False, 'error': f'Upload failed: {str(e)}'}) + + return jsonify({'success': False, 'error': 'Invalid file type. Please upload a PDF file.'}) + +@pdf_bp.route('/process', methods=['POST']) +def process_pdf(): + """Process PDF and convert to LaTeX""" + try: + data = request.get_json() + filename = data.get('filename') + coordinates = data.get('coordinates') + page = data.get('page', 0) + convert_all = data.get('convert_all', False) + + if not filename: + return jsonify({'success': False, 'error': 'No filename provided'}) + + # Get file path + temp_dir = tempfile.gettempdir() + filepath = os.path.join(temp_dir, filename) + + if not os.path.exists(filepath): + return jsonify({'success': False, 'error': 'File not found'}) + + if convert_all: + # Convert entire PDF + text = extract_text_from_pdf(filepath) + elif coordinates: + # Convert selected area + text = extract_text_from_pdf(filepath, page, coordinates) + else: + # Convert specific page + text = extract_text_from_pdf(filepath, page) + + # Convert to LaTeX (placeholder) + latex = convert_text_to_latex(text) + + return jsonify({ + 'success': True, + 'latex': latex, + 'text': text + }) + except Exception as e: + return jsonify({'success': False, 'error': f'Processing failed: {str(e)}'}) + +@pdf_bp.route('/solve', methods=['POST']) +def solve_equation(): + """Solve mathematical equations in LaTeX""" + try: + data = request.get_json() + latex = data.get('latex', '') + + # This is a placeholder implementation + # In a real application, you would use SymPy or similar library + solution = { + 'type': 'expression', + 'result': f"Simplified: {latex}" + } + + return jsonify({ + 'success': True, + 'solution': solution + }) + except Exception as e: + return jsonify({'success': False, 'error': f'Solving failed: {str(e)}'}) \ No newline at end of file diff --git a/controller/pdffly_controller.py b/controller/pdffly_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..98fb647ac30602f9efedeca2a3d3f1a40400be00 --- /dev/null +++ b/controller/pdffly_controller.py @@ -0,0 +1,274 @@ +from flask import Blueprint, request, jsonify, render_template +import os +import re +import fitz # PyMuPDF +from PIL import Image +from werkzeug.utils import secure_filename +try: + from pix2text import Pix2Text + PIX2TEXT_AVAILABLE = True +except ImportError: + PIX2TEXT_AVAILABLE = False + print("⚠️ Pix2Text not available. Install with: pip install pix2text") + +pdffly_bp = Blueprint('pdffly', __name__) +UPLOAD_FOLDER = 'static/uploads' +os.makedirs(UPLOAD_FOLDER, exist_ok=True) + +# Load Pix2Text model once (for efficiency) +if PIX2TEXT_AVAILABLE: + print("🔹 Loading Pix2Text model for PDF → LaTeX...") + try: + p2t = Pix2Text() + print("✅ Pix2Text model loaded successfully") + except Exception as e: + print(f"⚠️ Error loading Pix2Text: {e}") + p2t = None +else: + p2t = None + +def pdf_to_images(pdf_path): + """Convert PDF pages to images""" + doc = fitz.open(pdf_path) + image_paths = [] + for i, page in enumerate(doc): + pix = page.get_pixmap(dpi=200) + img_path = os.path.join(UPLOAD_FOLDER, f"page_{i+1}.png") + pix.save(img_path) + image_paths.append(img_path) + doc.close() + return image_paths + +def extract_text_from_pdf(pdf_path): + """Extract raw text from PDF (fallback method)""" + doc = fitz.open(pdf_path) + all_text = [] + for page_num, page in enumerate(doc): + text = page.get_text() + all_text.append(f"Page {page_num + 1}:\n{text}\n") + doc.close() + return "\n".join(all_text) + +def clean_latex_code(latex_str): + """Clean and format LaTeX code for Overleaf compilation""" + if not latex_str or not isinstance(latex_str, str): + return "" + + # Remove common OCR artifacts and spaces in commands + latex_str = re.sub(r'\\operatorname\*?\s*\{\s*([a-z])\s+([a-z])\s+([a-z])\s*\}', + lambda m: f'\\{m.group(1)}{m.group(2)}{m.group(3)}', latex_str) + + # Fix common math operators with spaces + replacements = { + r'\\operatorname\s*\{\s*l\s+i\s+m\s*\}': r'\\lim', + r'\\operatorname\s*\{\s*s\s+i\s+n\s*\}': r'\\sin', + r'\\operatorname\s*\{\s*c\s+o\s+s\s*\}': r'\\cos', + r'\\operatorname\s*\{\s*t\s+a\s+n\s*\}': r'\\tan', + r'\\operatorname\s*\{\s*l\s+o\s+g\s*\}': r'\\log', + r'\\operatorname\s*\{\s*l\s+n\s*\}': r'\\ln', + r'\\operatorname\s*\{\s*e\s+x\s+p\s*\}': r'\\exp', + r'\\operatorname\s*\{\s*m\s+a\s+x\s*\}': r'\\max', + r'\\operatorname\s*\{\s*m\s+i\s+n\s*\}': r'\\min', + } + + for pattern, replacement in replacements.items(): + latex_str = re.sub(pattern, replacement, latex_str, flags=re.IGNORECASE) + + # Remove spaces inside any remaining \operatorname commands + latex_str = re.sub(r'\\operatorname\*?\s*\{([^}]+)\}', + lambda m: f'\\operatorname{{{m.group(1).replace(" ", "")}}}', latex_str) + + # Replace $$ with \[ \] + latex_str = re.sub(r'\$\$([^$]+)\$\$', r'\\[\1\\]', latex_str) + + # Remove obvious OCR gibberish (sequences of random chars/symbols) + latex_str = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\xff]+', '', latex_str) + + # Balance braces and brackets + open_braces = latex_str.count('{') + close_braces = latex_str.count('}') + if open_braces > close_braces: + latex_str += '}' * (open_braces - close_braces) + elif close_braces > open_braces: + latex_str = '{' * (close_braces - open_braces) + latex_str + + # Balance brackets + open_brackets = latex_str.count('[') + close_brackets = latex_str.count(']') + if open_brackets > close_brackets: + latex_str += ']' * (open_brackets - close_brackets) + elif close_brackets > open_brackets: + latex_str = '[' * (close_brackets - open_brackets) + latex_str + + # Remove invalid math syntax patterns + latex_str = re.sub(r'\\\\+', r'\\\\', latex_str) # Multiple backslashes + latex_str = re.sub(r'\s+', ' ', latex_str) # Multiple spaces + + return latex_str.strip() + +def create_complete_latex_document(latex_content, title="PDF to LaTeX Conversion"): + """Wrap LaTeX content in a complete compilable document""" + document = r'''\documentclass{article} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{amsfonts} +\usepackage{graphicx} + +\title{''' + title + r'''} +\author{PDFly} +\date{\today} + +\begin{document} + +\maketitle + +\begin{center} +\textit{This document was automatically generated from a PDF file using OCR and LaTeX conversion.} +\end{center} + +\section*{Content} + +''' + latex_content + r''' + +\end{document}''' + + return document + +@pdffly_bp.route("/", methods=["GET"]) +def pdffly_page(): + """Render the main PDFfly page.""" + return render_template("pdffly.html") + +@pdffly_bp.route('/upload', methods=['POST']) +def upload_and_convert_pdf(): + """Upload PDF and convert to LaTeX""" + if 'file' not in request.files: + return jsonify({'error': 'No file found'}), 400 + + file = request.files['file'] + if not file or file.filename == '': + return jsonify({'error': 'No file selected'}), 400 + + if not file.filename.lower().endswith('.pdf'): + return jsonify({'error': 'Only PDF files are allowed'}), 400 + + filename = secure_filename(file.filename) + pdf_path = os.path.join(UPLOAD_FOLDER, filename) + file.save(pdf_path) + + try: + # Get page count + doc = fitz.open(pdf_path) + page_count = len(doc) + doc.close() + + # Convert PDF → images + images = pdf_to_images(pdf_path) + + # Run LaTeX recognition for each image + latex_results = [] + all_latex_pages = [] + for i, img_path in enumerate(images): + try: + if p2t: + result = p2t.recognize(img_path, resized_shape=768) + latex_code = result if isinstance(result, str) else str(result) + # Clean the LaTeX code + latex_code = clean_latex_code(latex_code) + all_latex_pages.append(f"% Page {i + 1}\n{latex_code}") + else: + # Fallback: extract text + latex_code = f"Text extraction (Pix2Text not available)" + all_latex_pages.append(latex_code) + + latex_results.append({ + 'page': i + 1, + 'image': img_path.replace('static/', '/static/'), + 'latex': latex_code + }) + except Exception as e: + latex_results.append({ + 'page': i + 1, + 'image': img_path.replace('static/', '/static/'), + 'error': str(e) + }) + all_latex_pages.append(f"% Page {i + 1}: Error - {str(e)}") + + # Create complete document + combined_latex = "\n\n".join(all_latex_pages) + complete_document = create_complete_latex_document(combined_latex, filename) + + return jsonify({ + 'success': True, + 'message': 'PDF converted successfully!', + 'pdf_path': pdf_path.replace('static/', '/static/'), + 'filename': filename, + 'pages': page_count, + 'results': latex_results, + 'complete_document': complete_document + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': f'Error processing PDF: {str(e)}' + }), 500 + +@pdffly_bp.route('/process', methods=['POST']) +def process_pdf(): + """Process specific area or entire PDF""" + data = request.get_json() + filename = data.get('filename') + convert_all = data.get('convert_all', False) + page_num = data.get('page', 0) + coordinates = data.get('coordinates') + + if not filename: + return jsonify({'success': False, 'error': 'No filename provided'}), 400 + + pdf_path = os.path.join(UPLOAD_FOLDER, filename) + + if not os.path.exists(pdf_path): + return jsonify({'success': False, 'error': 'PDF file not found'}), 404 + + try: + if convert_all: + # Extract text from entire PDF + text = extract_text_from_pdf(pdf_path) + latex = f"\\text{{{text}}}" + else: + # Extract from specific page + doc = fitz.open(pdf_path) + if page_num < len(doc): + page = doc[page_num] + text = page.get_text() + latex = f"\\text{{{text}}}" + else: + latex = "Page not found" + doc.close() + + return jsonify({ + 'success': True, + 'latex': latex + }) + + except Exception as e: + return jsonify({ + 'success': False, + 'error': str(e) + }), 500 + +@pdffly_bp.route('/solve', methods=['POST']) +def solve_latex(): + """Solve mathematical content""" + data = request.get_json() + latex = data.get('latex', '') + + # Simple solver response + return jsonify({ + 'success': True, + 'solution': { + 'type': 'info', + 'message': 'Math solver integration pending' + } + }) diff --git a/controller/pdflly_controller.py b/controller/pdflly_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..5984db47aaa6b71589f026bba45d6d153f82a16e --- /dev/null +++ b/controller/pdflly_controller.py @@ -0,0 +1,166 @@ +import os +import json +import base64 +import io +from flask import Blueprint, request, render_template, jsonify, current_app +import fitz # PyMuPDF +import PyPDF2 +from PIL import Image +import tempfile + +pdflly_bp = Blueprint('pdflly', __name__, url_prefix='/pdflly') + +def extract_text_from_pdf(filepath, page_number=None, coordinates=None): + """Extract text from PDF using PyMuPDF for better accuracy""" + try: + doc = fitz.open(filepath) + + if page_number is not None: + # Extract from specific page + page = doc[page_number] + + if coordinates: + # Extract text from specific area + x, y, width, height = coordinates + rect = fitz.Rect(x, y, x + width, y + height) + text = page.get_text("text", clip=rect) + else: + # Extract text from entire page + text = page.get_text("text") + else: + # Extract from entire document + text = "" + for page in doc: + text += page.get_text("text") + + doc.close() + return text.strip() + except Exception as e: + print(f"Error extracting text with PyMuPDF: {e}") + # Fallback to PyPDF2 + return extract_text_with_pypdf2(filepath, page_number, coordinates) + +def extract_text_with_pypdf2(filepath, page_number=None, coordinates=None): + """Fallback text extraction using PyPDF2""" + try: + with open(filepath, 'rb') as file: + reader = PyPDF2.PdfReader(file) + + if page_number is not None and page_number < len(reader.pages): + page = reader.pages[page_number] + return page.extract_text() + else: + text = "" + for page in reader.pages: + text += page.extract_text() + return text + except Exception as e: + return f"Error extracting text: {str(e)}" + +def convert_text_to_latex(text): + """Simple conversion of text to LaTeX format""" + # This is a placeholder implementation + # In a real application, you would use an AI model to convert text to LaTeX + return text.replace('\\', '\\\\').replace('_', '\\_').replace('^', '\\^').replace('&', '\\&') + +@pdflly_bp.route('/') +def pdflly_converter(): + """Render the PDFly converter page""" + return render_template('pdflly.html') + +@pdflly_bp.route('/upload', methods=['POST']) +def upload_pdf(): + """Handle PDF file upload""" + if 'pdf_file' not in request.files: + return jsonify({'success': False, 'error': 'No file provided'}) + + file = request.files['pdf_file'] + + if file.filename == '': + return jsonify({'success': False, 'error': 'No file selected'}) + + if file and file.filename.lower().endswith('.pdf'): + try: + # Save file temporarily + temp_dir = tempfile.gettempdir() + filename = file.filename + filepath = os.path.join(temp_dir, filename) + file.save(filepath) + + # Get page count + with open(filepath, 'rb') as f: + reader = PyPDF2.PdfReader(f) + page_count = len(reader.pages) + + return jsonify({ + 'success': True, + 'filename': filename, + 'filepath': filepath, + 'pages': page_count + }) + except Exception as e: + return jsonify({'success': False, 'error': f'Upload failed: {str(e)}'}) + + return jsonify({'success': False, 'error': 'Invalid file type. Please upload a PDF file.'}) + +@pdflly_bp.route('/process', methods=['POST']) +def process_pdf(): + """Process PDF and convert to LaTeX""" + try: + data = request.get_json() + filename = data.get('filename') + coordinates = data.get('coordinates') + page = data.get('page', 0) + convert_all = data.get('convert_all', False) + + if not filename: + return jsonify({'success': False, 'error': 'No filename provided'}) + + # Get file path + temp_dir = tempfile.gettempdir() + filepath = os.path.join(temp_dir, filename) + + if not os.path.exists(filepath): + return jsonify({'success': False, 'error': 'File not found'}) + + if convert_all: + # Convert entire PDF + text = extract_text_from_pdf(filepath) + elif coordinates: + # Convert selected area + text = extract_text_from_pdf(filepath, page, coordinates) + else: + # Convert specific page + text = extract_text_from_pdf(filepath, page) + + # Convert to LaTeX (placeholder) + latex = convert_text_to_latex(text) + + return jsonify({ + 'success': True, + 'latex': latex, + 'text': text + }) + except Exception as e: + return jsonify({'success': False, 'error': f'Processing failed: {str(e)}'}) + +@pdflly_bp.route('/solve', methods=['POST']) +def solve_equation(): + """Solve mathematical equations in LaTeX""" + try: + data = request.get_json() + latex = data.get('latex', '') + + # This is a placeholder implementation + # In a real application, you would use SymPy or similar library + solution = { + 'type': 'expression', + 'result': f"Simplified: {latex}" + } + + return jsonify({ + 'success': True, + 'solution': solution + }) + except Exception as e: + return jsonify({'success': False, 'error': f'Solving failed: {str(e)}'}) \ No newline at end of file diff --git a/controller/pix2text_controller.py b/controller/pix2text_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..261b97cf32aa048523bb8a6eb39b6e569887280d --- /dev/null +++ b/controller/pix2text_controller.py @@ -0,0 +1,223 @@ +# controller/pix2text_bp.py +import os +import cv2 +from flask import Blueprint, render_template, request, jsonify +from pix2text import Pix2Text +from utils.math_solver import solve_equation +from controller.models.camera_to_latex import camera_to_latex + +# Initialize Pix2Text globally once +print("🔹 Loading Pix2Text model (mfd)...") +try: + p2t = Pix2Text(analyzer_config=dict(model_name='mfd')) + print("✅ Pix2Text model loaded successfully.") +except Exception as e: + print(f"❌ Pix2Text failed to initialize: {e}") + p2t = None + +# Flask blueprint +pix2text_bp = Blueprint('pix2text_bp', __name__) + +UPLOAD_FOLDER = 'static/uploads' +os.makedirs(UPLOAD_FOLDER, exist_ok=True) + +# Optional preprocessing +def preprocess_image(image_path): + """Preprocess image for better OCR results""" + try: + # Read image + img = cv2.imread(image_path) + if img is None: + raise ValueError("Could not read image") + + # Convert to grayscale + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + # Apply mild Gaussian blur to reduce noise while preserving edges + blurred = cv2.GaussianBlur(gray, (3, 3), 0) + + # Apply adaptive thresholding with parameters better suited for text + thresh = cv2.adaptiveThreshold( + blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 2 + ) + + # Save processed image + processed_path = os.path.join( + PROCESSED_FOLDER, + os.path.basename(image_path).replace('.', '_processed.') + ) + cv2.imwrite(processed_path, thresh) + + return processed_path + except Exception as e: + print(f"Preprocessing error: {e}") + return image_path # Return original if preprocessing fails + +# ----------------------------- +# Math Routes +# ----------------------------- +@pix2text_bp.route("/math") +def math_page(): + return render_template("math.html") + +@pix2text_bp.route("/math/process", methods=["POST"]) +def process_math_image(): + try: + if 'image' not in request.files: + return jsonify({'error': 'No image file provided'}), 400 + + file = request.files['image'] + if not file.filename: + return jsonify({'error': 'No file selected'}), 400 + + filename = file.filename + filepath = os.path.join(UPLOAD_FOLDER, filename) + file.save(filepath) + + # Preprocess (optional) + processed_path = preprocess_image(filepath) + + # Run Pix2Text + if p2t: + result = p2t.recognize(processed_path) + if isinstance(result, dict): + latex = result.get('text', '') + elif isinstance(result, list) and result and isinstance(result[0], dict): + latex = result[0].get('text', '') + else: + latex = str(result) + else: + latex = "\\text{Pix2Text not initialized}" + + return jsonify({ + 'success': True, + 'latex': latex, + 'image_path': filepath + }) + + except Exception as e: + print(f"❌ Error in /math/process: {e}") + return jsonify({'error': str(e)}), 500 + +@pix2text_bp.route("/math/solve", methods=["POST"]) +def solve_math_equation(): + try: + data = request.get_json() + if not data or 'latex' not in data: + return jsonify({'error': 'No equation provided'}), 400 + + solution = solve_equation(data['latex']) + return jsonify({'success': True, 'solution': solution}) + + except Exception as e: + print(f"❌ Error in /math/solve: {e}") + return jsonify({'error': str(e)}), 500 + +# ----------------------------- +# Camera Routes +# ----------------------------- +# @pix2text_bp.route("/camera") +# def camera_page(): +# return render_template("camera.html") + +@pix2text_bp.route("/camera") +def camera_page(): + """Render the camera capture page""" + return render_template("camera.html") + + +@pix2text_bp.route("/camera/solve", methods=["POST"]) +def solve_camera_equation(): + """Solve a LaTeX equation from camera input""" + try: + data = request.get_json() + if not data: + return jsonify({'error': 'No data provided'}), 400 + + latex_equation = data.get('latex', '') + if not latex_equation: + return jsonify({'error': 'No equation provided'}), 400 + + # Solve the equation + solution = solve_equation(latex_equation) + + return jsonify({ + 'success': True, + 'solution': solution + }) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + return jsonify({'error': 'Unknown error'}), 500 + + +@pix2text_bp.route("/camera/process", methods=["POST"]) +def process_camera_image(): + """Process camera captured image using Pix2Text""" + try: + if 'image' not in request.files: + return jsonify({'error': 'No image file provided'}), 400 + + file = request.files['image'] + if file.filename == '': + return jsonify({'error': 'No image file selected'}), 400 + + if file and file.filename: + # Save original image + filename = file.filename + filepath = os.path.join(UPLOAD_FOLDER, filename) + file.save(filepath) + + # For camera captures, try processing the original image first + # as preprocessing might distort mathematical symbols + processed_path = filepath + + # Process with Pix2Text if available + if p2t: + print(f"Processing image: {processed_path}") + result = p2t.recognize(processed_path) + print(f"Raw result: {result}") + + # Handle different result types + if isinstance(result, dict): + latex_code = result.get('text', '') + elif isinstance(result, list): + # If result is a list, extract text from first item + if result and isinstance(result[0], dict): + latex_code = result[0].get('text', '') + else: + latex_code = str(result) + else: + latex_code = str(result) + + # If we get no result or very short result, try with preprocessing + if len(latex_code.strip()) < 2: + print("Result too short, trying with preprocessing...") + processed_path = preprocess_image(filepath) + result = p2t.recognize(processed_path) + print(f"Preprocessed result: {result}") + + if isinstance(result, dict): + latex_code = result.get('text', '') + elif isinstance(result, list): + if result and isinstance(result[0], dict): + latex_code = result[0].get('text', '') + else: + latex_code = str(result) + else: + latex_code = str(result) + + print(f"Final extracted LaTeX: {latex_code}") + else: + latex_code = "\\text{Pix2Text not available}" + + return jsonify({ + 'success': True, + 'latex': latex_code, + 'image_path': filepath + }) + + except Exception as e: + print(f"Error processing camera image: {e}") + return jsonify({'error': str(e)}), 500 + return jsonify({'error': 'Unknown error'}), 500 diff --git a/controller/scribble_controller.py b/controller/scribble_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..3b80f938706fc7db0d38fa709d837ef56ca94336 --- /dev/null +++ b/controller/scribble_controller.py @@ -0,0 +1,159 @@ +import os +from flask import Blueprint, render_template, request, jsonify +import base64 +from io import BytesIO +from PIL import Image +import numpy as np +import cv2 +from utils.math_solver import solve_equation +from typing import Union, Tuple, Any + +# Initialize Pix2Text with MFD (Mathematical Formula Detection) model for better accuracy +try: + from pix2text import Pix2Text + p2t = Pix2Text(analyzer_config=dict(model_name='mfd')) +except Exception as e: + print(f"Warning: Could not initialize Pix2Text: {e}") + p2t = None + +scribble_bp = Blueprint('scribble_bp', __name__) + +UPLOAD_FOLDER = 'static/uploads' +PROCESSED_FOLDER = 'static/processed' +os.makedirs(UPLOAD_FOLDER, exist_ok=True) +os.makedirs(PROCESSED_FOLDER, exist_ok=True) + + +def preprocess_image(image_data): + """Preprocess image for better OCR results""" + try: + # Convert base64 to image + image_data = image_data.split(',')[1] # Remove data URL prefix + image = Image.open(BytesIO(base64.b64decode(image_data))) + + # Convert to OpenCV format + opencv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) + + # Convert to grayscale + gray = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2GRAY) + + # Apply mild Gaussian blur to reduce noise while preserving edges + blurred = cv2.GaussianBlur(gray, (3, 3), 0) + + # Apply adaptive thresholding with parameters better suited for handwritten text + thresh = cv2.adaptiveThreshold( + blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 2 + ) + + # Save processed image + processed_path = os.path.join(PROCESSED_FOLDER, 'scribble_processed.png') + cv2.imwrite(processed_path, thresh) + + return processed_path + except Exception as e: + print(f"Preprocessing error: {e}") + # Save original if preprocessing fails + image_path = os.path.join(UPLOAD_FOLDER, 'scribble.png') + # Reopen image to save it + image = Image.open(BytesIO(base64.b64decode(image_data.split(',')[1]))) + image.save(image_path) + return image_path + + +@scribble_bp.route("/scribble") +def scribble_page(): + return render_template("scribble.html") + + +@scribble_bp.route("/scribble/process", methods=["POST"]) +def process_scribble(): + try: + data = request.get_json() + if not data: + return jsonify({'error': 'No data provided'}), 400 + + image_data = data.get('image', '') + if not image_data: + return jsonify({'error': 'No image data provided'}), 400 + + # Save original image first + image_path = os.path.join(UPLOAD_FOLDER, 'scribble_original.png') + image_data_content = image_data.split(',')[1] + image = Image.open(BytesIO(base64.b64decode(image_data_content))) + image.save(image_path) + + # Process with Pix2Text if available + if p2t: + print(f"Processing scribble with MFD model: {image_path}") + + # Try with original image first (works better for handwritten math) + result = p2t.recognize(image_path) + print(f"Original image result: {result}") + + # Handle different result types + if isinstance(result, dict): + latex_code = result.get('text', '') + elif isinstance(result, list): + # If result is a list, extract text from first item + if result and isinstance(result[0], dict): + latex_code = result[0].get('text', '') + else: + latex_code = str(result) + else: + latex_code = str(result) + + # If we get no result or very short result, try with preprocessing + if len(latex_code.strip()) < 2: + print("Result too short, trying with preprocessing...") + processed_path = preprocess_image(image_data) + result = p2t.recognize(processed_path) + print(f"Preprocessed image result: {result}") + + if isinstance(result, dict): + latex_code = result.get('text', '') + elif isinstance(result, list): + if result and isinstance(result[0], dict): + latex_code = result[0].get('text', '') + else: + latex_code = str(result) + else: + latex_code = str(result) + + print(f"Final extracted LaTeX: {latex_code}") + else: + latex_code = "\\text{Pix2Text not available}" + + return jsonify({ + 'success': True, + 'latex': latex_code + }) + + except Exception as e: + print(f"Error processing scribble: {e}") + return jsonify({'error': str(e)}), 500 + return jsonify({'error': 'Unknown error'}), 500 + + +@scribble_bp.route("/scribble/solve", methods=["POST"]) +def solve_scribble_equation(): + """Solve a LaTeX equation from scribble""" + try: + data = request.get_json() + if not data: + return jsonify({'error': 'No data provided'}), 400 + + latex_equation = data.get('latex', '') + if not latex_equation: + return jsonify({'error': 'No equation provided'}), 400 + + # Solve the equation + solution = solve_equation(latex_equation) + + return jsonify({ + 'success': True, + 'solution': solution + }) + + except Exception as e: + return jsonify({'error': str(e)}), 500 + return jsonify({'error': 'Unknown error'}), 500 \ No newline at end of file diff --git a/controller/table_controller.py b/controller/table_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..349e50c65ec4f070ed4fd16799da039779767519 --- /dev/null +++ b/controller/table_controller.py @@ -0,0 +1,192 @@ +import os +import cv2 +import numpy as np +import base64 +import io +from flask import Blueprint, render_template, request, jsonify +from PIL import Image +from utils.math_solver import solve_equation # optional, if you use math solving + +# ------------------------------- +# Blueprint setup +# ------------------------------- +table_bp = Blueprint('table_bp', __name__) + +UPLOAD_FOLDER = 'static/uploads' +os.makedirs(UPLOAD_FOLDER, exist_ok=True) + + +# ------------------------------- +# Core Table Detection Logic +# ------------------------------- +def detect_table(image_path): + """Detect rows and columns from table image using Hough line detection.""" + img = cv2.imread(image_path) + if img is None: + raise ValueError(f"Cannot read image at {image_path}") + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + gray = cv2.GaussianBlur(gray, (5, 5), 0) + edges = cv2.Canny(gray, 50, 150, apertureSize=3) + + lines = cv2.HoughLinesP( + edges, + rho=1, + theta=np.pi / 180, + threshold=80, + minLineLength=60, + maxLineGap=15 + ) + + if lines is None: + return 0, 0 + + horizontal, vertical = [], [] + + for x1, y1, x2, y2 in lines[:, 0]: + if abs(y2 - y1) < abs(x2 - x1): # horizontal line + horizontal.append((y1 + y2) / 2) + elif abs(x2 - x1) < abs(y2 - y1): # vertical line + vertical.append((x1 + x2) / 2) + + def merge_lines(coords, gap=25): + coords = sorted(coords) + merged = [] + if not coords: + return merged + group = [coords[0]] + for c in coords[1:]: + if abs(c - group[-1]) < gap: + group.append(c) + else: + merged.append(np.mean(group)) + group = [c] + merged.append(np.mean(group)) + return merged + + horizontal = merge_lines(horizontal) + vertical = merge_lines(vertical) + + num_rows = max(len(horizontal) - 1, 1) + num_cols = max(len(vertical) - 1, 1) + return num_rows, num_cols + + +# ------------------------------- +# Generate LaTeX Table Code +# ------------------------------- +def generate_latex_table(rows, cols): + col_format = '|' + '|'.join(['c'] * cols) + '|' + latex = f"\\begin{{tabular}}{{{col_format}}}\n\\hline\n" + for _ in range(rows): + latex += ' & '.join([''] * cols) + " \\\\ \\hline\n" + latex += "\\end{tabular}" + return latex + + +# ------------------------------- +# Helpers +# ------------------------------- +def save_base64_image(image_base64): + """Convert base64 image string to file and return the saved path.""" + try: + if ',' in image_base64: + image_base64 = image_base64.split(',')[1] + image_bytes = base64.b64decode(image_base64) + image = Image.open(io.BytesIO(image_bytes)).convert("RGB") + filename = f"table_{len(os.listdir(UPLOAD_FOLDER)) + 1}.png" + filepath = os.path.join(UPLOAD_FOLDER, filename) + image.save(filepath) + return filepath + except Exception as e: + raise ValueError(f"Failed to decode base64 image: {e}") + + +# ------------------------------- +# Routes +# ------------------------------- + +@table_bp.route("/table", methods=["GET"]) +def table_page(): + """Render the main table-to-LaTeX page.""" + return render_template("table.html") + + +@table_bp.route("/table/process", methods=["POST"]) +def process_table_image(): + """ + Handles both: + - Uploaded image file (FormData) + - Base64 image (drawn or camera) + """ + try: + # 🟢 Case 1: File upload (from drag & drop or browse) + if 'image' in request.files: + file = request.files['image'] + if not file.filename: + return jsonify({'success': False, 'error': 'No file selected.'}), 400 + + filename = file.filename + filepath = os.path.join(UPLOAD_FOLDER, filename) + file.save(filepath) + + # 🟢 Case 2: Base64 image (from canvas draw) + elif request.is_json: + data = request.get_json() + if 'image' not in data: + return jsonify({'success': False, 'error': 'No image data provided'}), 400 + filepath = save_base64_image(data['image']) + + else: + return jsonify({'success': False, 'error': 'Invalid image input.'}), 400 + + # Detect table structure + rows, cols = detect_table(filepath) + latex_code = generate_latex_table(rows, cols) + + return jsonify({ + 'success': True, + 'latex': latex_code, + 'rows': rows, + 'cols': cols, + 'image_path': filepath + }) + + except Exception as e: + print(f"❌ Error in /table/process: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + +@table_bp.route("/table/generate", methods=["POST"]) +def generate_table_from_input(): + """Generate LaTeX manually from user-input rows/cols.""" + try: + data = request.get_json() + rows = int(data.get('rows', 0)) + cols = int(data.get('cols', 0)) + + if rows <= 0 or cols <= 0: + return jsonify({'success': False, 'error': 'Invalid rows or columns'}), 400 + + latex_code = generate_latex_table(rows, cols) + + return jsonify({'success': True, 'latex': latex_code}) + except Exception as e: + print(f"❌ Error in /table/generate: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + + +@table_bp.route("/table/solve", methods=["POST"]) +def solve_table_content(): + """Optional: solve math expressions inside the LaTeX table (if supported).""" + try: + data = request.get_json() + if not data or 'latex' not in data: + return jsonify({'success': False, 'error': 'No LaTeX data provided'}), 400 + + solution = solve_equation(data['latex']) + return jsonify({'success': True, 'solution': solution}) + + except Exception as e: + print(f"❌ Error in /table/solve: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 diff --git a/data/users.json b/data/users.json new file mode 100644 index 0000000000000000000000000000000000000000..36866044464e5262b283c7e762390e71eb0042ce --- /dev/null +++ b/data/users.json @@ -0,0 +1,74 @@ +{ + "test_user_123": { + "id": "test_user_123", + "email": "test@example.com", + "name": "Test User", + "picture": null + }, + "google_user_123": { + "id": "google_user_123", + "email": "user@gmail.com", + "name": "Google User", + "picture": null + }, + "guest_gg_1762329533": { + "id": "guest_gg_1762329533", + "email": "gg@guest.texlab.com", + "name": "gg", + "picture": null + }, + "guest_gg_1762330083": { + "id": "guest_gg_1762330083", + "email": "gg@guest.texlab.com", + "name": "gg", + "picture": null + }, + "guest_syk_1762332308": { + "id": "guest_syk_1762332308", + "email": "syk@guest.texlab.com", + "name": "syk", + "picture": null + }, + "guest_gg_1762333085": { + "id": "guest_gg_1762333085", + "email": "gg@guest.texlab.com", + "name": "gg", + "picture": null + }, + "guest_syk_1762333094": { + "id": "guest_syk_1762333094", + "email": "syk@guest.texlab.com", + "name": "syk", + "picture": null + }, + "guest_syk_1762333160": { + "id": "guest_syk_1762333160", + "email": "syk@guest.texlab.com", + "name": "syk", + "picture": null + }, + "guest_syk_1762333181": { + "id": "guest_syk_1762333181", + "email": "syk@guest.texlab.com", + "name": "syk", + "picture": null + }, + "guest_ali_1762333646": { + "id": "guest_ali_1762333646", + "email": "ali@guest.texlab.com", + "name": "ali", + "picture": null + }, + "guest_ali_ashraf_1762334613": { + "id": "guest_ali_ashraf_1762334613", + "email": "ali.ashraf@guest.texlab.com", + "name": "ALI ASHRAF", + "picture": null + }, + "guest_ashraf_1762608609": { + "id": "guest_ashraf_1762608609", + "email": "ashraf@guest.texlab.com", + "name": "Ashraf", + "picture": null + } +} \ No newline at end of file diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d8cfe8af64317488d9ea63a427c282755b2ddd78 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1 @@ +# Models package \ No newline at end of file diff --git a/models/__pycache__/__init__.cpython-311.pyc b/models/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..870417e57c54de08e133a6c66485e00a4c8f8e96 Binary files /dev/null and b/models/__pycache__/__init__.cpython-311.pyc differ diff --git a/models/__pycache__/__init__.cpython-313.pyc b/models/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53d3092246571789b44aa034937855687b361bab Binary files /dev/null and b/models/__pycache__/__init__.cpython-313.pyc differ diff --git a/models/__pycache__/user.cpython-311.pyc b/models/__pycache__/user.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ae51cd66ad35004378309c8024448864df9da1f Binary files /dev/null and b/models/__pycache__/user.cpython-311.pyc differ diff --git a/models/__pycache__/user.cpython-313.pyc b/models/__pycache__/user.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6546db802bd30bfc4e64a827dd7a945c6cab389c Binary files /dev/null and b/models/__pycache__/user.cpython-313.pyc differ diff --git a/models/user.py b/models/user.py new file mode 100644 index 0000000000000000000000000000000000000000..08a7b6c7e9f0a00e4253a7d465828a99bded851a --- /dev/null +++ b/models/user.py @@ -0,0 +1,68 @@ +from flask_login import UserMixin +from datetime import datetime +import json +import os + +# Simple file-based storage for users +USERS_FILE = 'data/users.json' +os.makedirs(os.path.dirname(USERS_FILE), exist_ok=True) + +# Create empty users file if it doesn't exist +if not os.path.exists(USERS_FILE): + with open(USERS_FILE, 'w') as f: + json.dump({}, f) + + +class User(UserMixin): + def __init__(self, id, email, name, picture=None): + self.id = id + self.email = email + self.name = name + self.picture = picture + + @staticmethod + def get(user_id): + """Get user by ID""" + try: + with open(USERS_FILE, 'r') as f: + users = json.load(f) + + user_data = users.get(str(user_id)) + if user_data: + return User( + id=user_data['id'], + email=user_data['email'], + name=user_data['name'], + picture=user_data.get('picture') + ) + return None + except Exception: + return None + + @staticmethod + def create_or_update(id, email, name, picture=None): + """Create or update user""" + try: + # Load existing users + if os.path.exists(USERS_FILE): + with open(USERS_FILE, 'r') as f: + users = json.load(f) + else: + users = {} + + # Update or create user + users[str(id)] = { + 'id': id, + 'email': email, + 'name': name, + 'picture': picture + } + + # Save users + with open(USERS_FILE, 'w') as f: + json.dump(users, f, indent=2) + + return User(id, email, name, picture) + except Exception as e: + print(f"Error creating/updating user: {e}") + return None \ No newline at end of file diff --git a/static/.DS_Store b/static/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e959ba552c2c8ddef4883e8c3acd8d13a80d274c Binary files /dev/null and b/static/.DS_Store differ diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..201d8752e86cd1fe23c013cfaed4f1d2176873f2 --- /dev/null +++ b/static/images/logo.png @@ -0,0 +1 @@ +This is a placeholder for the logo file. In a real implementation, you would place your actual logo image here. \ No newline at end of file diff --git a/static/images/logo.svg b/static/images/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..33510b7f611cb10cb3a6ac44f94acb199cddda3b --- /dev/null +++ b/static/images/logo.svg @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/static/js/scribble.js b/static/js/scribble.js new file mode 100644 index 0000000000000000000000000000000000000000..bd48dd7d3a23c6d3b0e30874e1f1755edf0e85fd --- /dev/null +++ b/static/js/scribble.js @@ -0,0 +1,48 @@ +// static/js/scribble.js + +const canvas = document.getElementById("canvas"); +const ctx = canvas.getContext("2d"); +let drawing = false; + +canvas.addEventListener("mousedown", () => (drawing = true)); +canvas.addEventListener("mouseup", () => (drawing = false)); +canvas.addEventListener("mousemove", draw); + +function draw(e) { + if (!drawing) return; + ctx.lineWidth = 4; + ctx.lineCap = "round"; + ctx.strokeStyle = "#000"; + ctx.lineTo(e.offsetX, e.offsetY); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(e.offsetX, e.offsetY); +} + +document.getElementById("clear").addEventListener("click", () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); +}); + +document.getElementById("convert").addEventListener("click", async () => { + const image = canvas.toDataURL("image/png"); + const response = await fetch("/scribble/convert", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ image }), + }); + + const data = await response.json(); + if (data.latex) { + document.getElementById("result-box").style.display = "block"; + document.getElementById("latex-output").value = data.latex; + } else { + alert("Error generating LaTeX."); + } +}); + +document.getElementById("copy").addEventListener("click", () => { + const textarea = document.getElementById("latex-output"); + textarea.select(); + document.execCommand("copy"); + alert("✅ LaTeX code copied!"); +}); diff --git a/static/processed/dalk_processed.png b/static/processed/dalk_processed.png new file mode 100644 index 0000000000000000000000000000000000000000..23c85de8262d5f36729b92be6c301ec71643b327 Binary files /dev/null and b/static/processed/dalk_processed.png differ diff --git a/static/processed/scribble_10.png b/static/processed/scribble_10.png new file mode 100644 index 0000000000000000000000000000000000000000..af8f200d14db2fad3a9194a9bb8fea7abaad4fcc Binary files /dev/null and b/static/processed/scribble_10.png differ diff --git a/static/processed/scribble_11.png b/static/processed/scribble_11.png new file mode 100644 index 0000000000000000000000000000000000000000..af8f200d14db2fad3a9194a9bb8fea7abaad4fcc Binary files /dev/null and b/static/processed/scribble_11.png differ diff --git a/static/processed/scribble_12.png b/static/processed/scribble_12.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb56018dea7d574c9b4637b0177175906570b42 Binary files /dev/null and b/static/processed/scribble_12.png differ diff --git a/static/processed/scribble_13.png b/static/processed/scribble_13.png new file mode 100644 index 0000000000000000000000000000000000000000..3b01b080c8313638639d4b74a27fbd6d5661547c Binary files /dev/null and b/static/processed/scribble_13.png differ diff --git a/static/processed/scribble_14.png b/static/processed/scribble_14.png new file mode 100644 index 0000000000000000000000000000000000000000..49bab5f5b5b05fff318d5fa0bfbb3622ce1ac0f8 Binary files /dev/null and b/static/processed/scribble_14.png differ diff --git a/static/processed/scribble_5.png b/static/processed/scribble_5.png new file mode 100644 index 0000000000000000000000000000000000000000..4c7915d53c09e1a28d0b5905ae326ea794288a8e Binary files /dev/null and b/static/processed/scribble_5.png differ diff --git a/static/processed/scribble_6.png b/static/processed/scribble_6.png new file mode 100644 index 0000000000000000000000000000000000000000..6c4b772d335f21891fb8b01e1c0a81a696639369 Binary files /dev/null and b/static/processed/scribble_6.png differ diff --git a/static/processed/scribble_7.png b/static/processed/scribble_7.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb0c3a8d2486ce45c77e39bef7e839b9a6b5433 Binary files /dev/null and b/static/processed/scribble_7.png differ diff --git a/static/processed/scribble_8.png b/static/processed/scribble_8.png new file mode 100644 index 0000000000000000000000000000000000000000..1f45a50700f554a505d6a1ad0a7db7c6161cc3d3 Binary files /dev/null and b/static/processed/scribble_8.png differ diff --git a/static/processed/scribble_9.png b/static/processed/scribble_9.png new file mode 100644 index 0000000000000000000000000000000000000000..af8f200d14db2fad3a9194a9bb8fea7abaad4fcc Binary files /dev/null and b/static/processed/scribble_9.png differ diff --git a/static/processed/scribble_processed.png b/static/processed/scribble_processed.png new file mode 100644 index 0000000000000000000000000000000000000000..aca665abb021811fb936f6f47fc493272309b80c Binary files /dev/null and b/static/processed/scribble_processed.png differ diff --git a/static/processed/table_processed.png b/static/processed/table_processed.png new file mode 100644 index 0000000000000000000000000000000000000000..3c3ddd7b6e45010ed2dc1011a89a6c74dc864160 Binary files /dev/null and b/static/processed/table_processed.png differ diff --git a/static/processed/ziyXZ_processed.png b/static/processed/ziyXZ_processed.png new file mode 100644 index 0000000000000000000000000000000000000000..02833983917f22f983450b97ae42524f43c658e8 Binary files /dev/null and b/static/processed/ziyXZ_processed.png differ diff --git a/static/uploads/3x.pdf b/static/uploads/3x.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bd131bb9523c48e04d131bd990bb42a1858ca3f5 Binary files /dev/null and b/static/uploads/3x.pdf differ diff --git a/static/uploads/Assignment_1_Solution.pdf b/static/uploads/Assignment_1_Solution.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b7c211005f0e2c6055a772325e4bd220c5cbd77c --- /dev/null +++ b/static/uploads/Assignment_1_Solution.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e03f06a07396772e5230e11aaa726e9a28213e1480052537364ec77bb51cf30 +size 3337218 diff --git a/static/uploads/CSE331.pdf b/static/uploads/CSE331.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4dbc0c9b9cf19c5d51b9f3feb434fd3d024253a7 --- /dev/null +++ b/static/uploads/CSE331.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a0d9cb2be01b8ab4e89aa519fc7f11af465fbcf332bc67b01afb13467b70a78 +size 3070261 diff --git a/static/uploads/WhatsApp Image 2025-10-04 at 20.08.35_65cf8de7.jpg b/static/uploads/WhatsApp Image 2025-10-04 at 20.08.35_65cf8de7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f338d35c9d3e874fc9bec8a3e17394c992a6fe1 Binary files /dev/null and b/static/uploads/WhatsApp Image 2025-10-04 at 20.08.35_65cf8de7.jpg differ diff --git a/static/uploads/WhatsApp Image 2025-10-17 at 03.38.57_567a5745.jpg b/static/uploads/WhatsApp Image 2025-10-17 at 03.38.57_567a5745.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6ec9b4a7aa1e2068a0b7e4105793ad1227262b5d Binary files /dev/null and b/static/uploads/WhatsApp Image 2025-10-17 at 03.38.57_567a5745.jpg differ diff --git a/static/uploads/WhatsApp Image 2025-10-26 at 01.38.21_2cabb721.jpg b/static/uploads/WhatsApp Image 2025-10-26 at 01.38.21_2cabb721.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc1fdcf57afa3f60cac7e5a55eb6825679daeb5d Binary files /dev/null and b/static/uploads/WhatsApp Image 2025-10-26 at 01.38.21_2cabb721.jpg differ diff --git a/static/uploads/WhatsApp Image 2025-10-26 at 01.38.22_3d87e144.jpg b/static/uploads/WhatsApp Image 2025-10-26 at 01.38.22_3d87e144.jpg new file mode 100644 index 0000000000000000000000000000000000000000..abfc86d94f9494b484282d8fe7fda895fd8f467b --- /dev/null +++ b/static/uploads/WhatsApp Image 2025-10-26 at 01.38.22_3d87e144.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa27299dae4ec595b5c0364ea02bf36a6d88258b8bbf5bb0f484285af0af575a +size 102185 diff --git a/static/uploads/aaaasa.png b/static/uploads/aaaasa.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4fc11d41725345293dd561d56a17f81d7f0fcf Binary files /dev/null and b/static/uploads/aaaasa.png differ diff --git a/static/uploads/adas.jpg b/static/uploads/adas.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4a0e1d6aa44bae7f6de407d6fba5671d5cb39d1 Binary files /dev/null and b/static/uploads/adas.jpg differ diff --git a/static/uploads/as.png b/static/uploads/as.png new file mode 100644 index 0000000000000000000000000000000000000000..fd22cf8e34a5afc6e5ed0adafcfcc81ce2cf9ec3 Binary files /dev/null and b/static/uploads/as.png differ diff --git a/static/uploads/asasaaws.pdf b/static/uploads/asasaaws.pdf new file mode 100644 index 0000000000000000000000000000000000000000..012cfeacfd9e567d6961a24b9b9381687b8090c7 Binary files /dev/null and b/static/uploads/asasaaws.pdf differ diff --git a/static/uploads/awq.png b/static/uploads/awq.png new file mode 100644 index 0000000000000000000000000000000000000000..b90783083ab36d440c74a55bd1543660d52690e4 Binary files /dev/null and b/static/uploads/awq.png differ diff --git a/static/uploads/azaz.png b/static/uploads/azaz.png new file mode 100644 index 0000000000000000000000000000000000000000..5445257694b84ed7425a390dc72b65489a93f6eb Binary files /dev/null and b/static/uploads/azaz.png differ diff --git a/static/uploads/b0d6064f-3245-40bc-87b5-bfbb459979fd.jpeg b/static/uploads/b0d6064f-3245-40bc-87b5-bfbb459979fd.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..89c81f77bfc354b9bb544d3810d9663ade8a261e --- /dev/null +++ b/static/uploads/b0d6064f-3245-40bc-87b5-bfbb459979fd.jpeg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ac464fc189decfa93d4e7d6c8c26215276036fe2b4dcf0051cd0e66b3f55bcb +size 6673485 diff --git a/static/uploads/bvkil.jpg b/static/uploads/bvkil.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6b8c02a2406ee6891b8e7345efa4647d5574f807 Binary files /dev/null and b/static/uploads/bvkil.jpg differ diff --git a/static/uploads/capture.jpg b/static/uploads/capture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc86b3cb0e3a5b548a2216c46ea370053303f2d8 --- /dev/null +++ b/static/uploads/capture.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ada79c297bfd2a00f127f9e9a40f16d478f4fc0eec4eed7ee566db47a42c4488 +size 114650 diff --git a/static/uploads/dalk.png b/static/uploads/dalk.png new file mode 100644 index 0000000000000000000000000000000000000000..e1d1d47720bfd3041fe20f5c2d8ef520abf1da5b Binary files /dev/null and b/static/uploads/dalk.png differ diff --git a/static/uploads/hdia.jpg b/static/uploads/hdia.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7fa92e57ebd44d1d7fcc85cf7dddca74b4df9c31 Binary files /dev/null and b/static/uploads/hdia.jpg differ diff --git a/static/uploads/input 3.png b/static/uploads/input 3.png new file mode 100644 index 0000000000000000000000000000000000000000..b6d320717c3bcc4c59bfb6b51c75876ee5c90ff9 Binary files /dev/null and b/static/uploads/input 3.png differ diff --git a/static/uploads/input1.png b/static/uploads/input1.png new file mode 100644 index 0000000000000000000000000000000000000000..4d870a965b55157173656e56dddfbee0c1fb45a0 Binary files /dev/null and b/static/uploads/input1.png differ diff --git a/static/uploads/input2.png b/static/uploads/input2.png new file mode 100644 index 0000000000000000000000000000000000000000..66f76424c447b3990170b3151dc9b6d54fab41b8 --- /dev/null +++ b/static/uploads/input2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff9866256da80ec8ea98e3dd19a9dc10024cb01c3f9081687ed831c08fc38c3f +size 116409 diff --git a/static/uploads/math_formula_sheet.pdf b/static/uploads/math_formula_sheet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dd4a2aa173553412e30bf04bc05d32865ae6209c --- /dev/null +++ b/static/uploads/math_formula_sheet.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85061d2d2098ab8b593d10fcf6c5e20568069355f083449c5272f7a8a348970a +size 232378 diff --git a/static/uploads/no 11.png b/static/uploads/no 11.png new file mode 100644 index 0000000000000000000000000000000000000000..295808bdd9d937bcd70a27b51444b45b388db29f Binary files /dev/null and b/static/uploads/no 11.png differ diff --git a/static/uploads/page_1.png b/static/uploads/page_1.png new file mode 100644 index 0000000000000000000000000000000000000000..4812c3bab6bf09ad80f0c23f2184c6605c34ccfb Binary files /dev/null and b/static/uploads/page_1.png differ diff --git a/static/uploads/page_10.png b/static/uploads/page_10.png new file mode 100644 index 0000000000000000000000000000000000000000..0fd19b432ca9538075afddc89940c36be2ac4c3c --- /dev/null +++ b/static/uploads/page_10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02d21e78e10a3997182894111ca8849d9d12a291f3e77f5e5ae029f81005005f +size 824066 diff --git a/static/uploads/page_11.png b/static/uploads/page_11.png new file mode 100644 index 0000000000000000000000000000000000000000..c5003a137b02d593f7771bc50c205a0c8b60c721 --- /dev/null +++ b/static/uploads/page_11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78b447c62953f89eb90b2b4e2b3615264e86b1acf481f2252b8a6e9dd8b9f887 +size 471341 diff --git a/static/uploads/page_2.png b/static/uploads/page_2.png new file mode 100644 index 0000000000000000000000000000000000000000..544c6f2d5b3a79abd0d49cd22b0689a3d65c261e --- /dev/null +++ b/static/uploads/page_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03d985a8a292b1377dd7b3ba20626bf12849584eff1b34642b8ebc4504c37e06 +size 215218 diff --git a/static/uploads/page_3.png b/static/uploads/page_3.png new file mode 100644 index 0000000000000000000000000000000000000000..53afe95a9d6259fda64134a6b10dff4e60ca1dda --- /dev/null +++ b/static/uploads/page_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b9c32bf3a9101eec93c955138b035394c417dc4335517e8fa3bf5a1d12d830d +size 215587 diff --git a/static/uploads/page_4.png b/static/uploads/page_4.png new file mode 100644 index 0000000000000000000000000000000000000000..69f836b49fdbd933747b091143121e937ced6c12 --- /dev/null +++ b/static/uploads/page_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f63b448235f2912cf00f8a5a1e33bb98b51d317943531322a32ac5c33d6d929a +size 204774 diff --git a/static/uploads/page_5.png b/static/uploads/page_5.png new file mode 100644 index 0000000000000000000000000000000000000000..5a7ed31fee2aa1f265ba34f51c5d03e9ec6c6b48 --- /dev/null +++ b/static/uploads/page_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42cfbd24b865158e722cd9642e1af03b2e0eddf1ed7c76469ee726aaca53c486 +size 685946 diff --git a/static/uploads/page_6.png b/static/uploads/page_6.png new file mode 100644 index 0000000000000000000000000000000000000000..1833a9dadc8a7cd4639e8fc9157cb9b531913074 --- /dev/null +++ b/static/uploads/page_6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2eddd7f0aec783b22a6642c932a8ff0dfa1b0fd3df61d280f41a3633a01e3c74 +size 442657 diff --git a/static/uploads/page_7.png b/static/uploads/page_7.png new file mode 100644 index 0000000000000000000000000000000000000000..6c1bcd29a65832f24ad97ba29c6c1b96c65e8711 --- /dev/null +++ b/static/uploads/page_7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:222b6bd8989790d0eed5d6fbb2b56966ddc476aa81cf034cbd32354045ef11b5 +size 274176 diff --git a/static/uploads/page_8.png b/static/uploads/page_8.png new file mode 100644 index 0000000000000000000000000000000000000000..47e1706c20f3ee491ba1e4e1b664ee6d8b3a27b6 --- /dev/null +++ b/static/uploads/page_8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bef85bbcc7d0d991735475132ee44d8ddf01797e71231fbb808e869404fe5a5e +size 832772 diff --git a/static/uploads/page_9.png b/static/uploads/page_9.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd033db20a42eeb166ecbf271e652ebb1b70dfe --- /dev/null +++ b/static/uploads/page_9.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05e76a9759b20e0b78067e5108e0d440f52774b7d9adafe5feac44c4552f87f7 +size 978237 diff --git a/static/uploads/papa.jpg b/static/uploads/papa.jpg new file mode 100644 index 0000000000000000000000000000000000000000..71c8bdae7bb3791b740980eaf16517a4833f502d Binary files /dev/null and b/static/uploads/papa.jpg differ diff --git a/static/uploads/saj.png b/static/uploads/saj.png new file mode 100644 index 0000000000000000000000000000000000000000..37792efb0ffff89997bd3181e1fd77d90867749d Binary files /dev/null and b/static/uploads/saj.png differ diff --git a/static/uploads/scribble_original.png b/static/uploads/scribble_original.png new file mode 100644 index 0000000000000000000000000000000000000000..95a1957d82a72860618370bcca4a29d92a8972a9 Binary files /dev/null and b/static/uploads/scribble_original.png differ diff --git a/static/uploads/table_26.png b/static/uploads/table_26.png new file mode 100644 index 0000000000000000000000000000000000000000..595beb64861181a59ec5fef13807df396e4b9ab5 --- /dev/null +++ b/static/uploads/table_26.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f72def72489cbb99cd79fccb52a45067e68c6fa2e3356828d1df894b9ad6953 +size 370847 diff --git a/static/uploads/table_27.png b/static/uploads/table_27.png new file mode 100644 index 0000000000000000000000000000000000000000..a467904188a0b508413018a4574b9acbd9bea945 --- /dev/null +++ b/static/uploads/table_27.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52f7bfc0e04c1c9a86c0c223f720ba452fc281e5c9ba91286a1469f95475cc2a +size 358488 diff --git a/static/uploads/table_28.png b/static/uploads/table_28.png new file mode 100644 index 0000000000000000000000000000000000000000..74bea3c380b3cade5236f82d6c277dbfe5436004 --- /dev/null +++ b/static/uploads/table_28.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e5bdc24128ae2ec1c984fb4500df2a7f1a39f3f6243a431b56a171077da02ac +size 357667 diff --git a/static/uploads/table_29.png b/static/uploads/table_29.png new file mode 100644 index 0000000000000000000000000000000000000000..d12e22272de527b94994ab94e7614924bb5d586c --- /dev/null +++ b/static/uploads/table_29.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:418459ba5ae7dd7125e4d23f50037b531a1402f48ac8935387a03c906cf9a795 +size 329711 diff --git a/static/uploads/table_30.png b/static/uploads/table_30.png new file mode 100644 index 0000000000000000000000000000000000000000..15bd37a45805419e9eddd51f2aedc068ac8c7c9b --- /dev/null +++ b/static/uploads/table_30.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ce908965d2107d8bb41086822961cdc54d153d814d7a2d5cb1b06b0eb0f3feb +size 350304 diff --git a/static/uploads/table_31.png b/static/uploads/table_31.png new file mode 100644 index 0000000000000000000000000000000000000000..15bd37a45805419e9eddd51f2aedc068ac8c7c9b --- /dev/null +++ b/static/uploads/table_31.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ce908965d2107d8bb41086822961cdc54d153d814d7a2d5cb1b06b0eb0f3feb +size 350304 diff --git a/static/uploads/table_32.png b/static/uploads/table_32.png new file mode 100644 index 0000000000000000000000000000000000000000..d79d969ef05167e21bb1c1971e4280c257c6c18e --- /dev/null +++ b/static/uploads/table_32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:645e4cdf46d25b6a9bc9960c9931439df606eb53824d698f26b0bae283fa6a60 +size 388382 diff --git a/static/uploads/table_33.png b/static/uploads/table_33.png new file mode 100644 index 0000000000000000000000000000000000000000..d79d969ef05167e21bb1c1971e4280c257c6c18e --- /dev/null +++ b/static/uploads/table_33.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:645e4cdf46d25b6a9bc9960c9931439df606eb53824d698f26b0bae283fa6a60 +size 388382 diff --git a/static/uploads/table_34.png b/static/uploads/table_34.png new file mode 100644 index 0000000000000000000000000000000000000000..d79d969ef05167e21bb1c1971e4280c257c6c18e --- /dev/null +++ b/static/uploads/table_34.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:645e4cdf46d25b6a9bc9960c9931439df606eb53824d698f26b0bae283fa6a60 +size 388382 diff --git a/static/uploads/table_35.png b/static/uploads/table_35.png new file mode 100644 index 0000000000000000000000000000000000000000..f83e30c2135d9a2a91bab6d7a5188d959b196886 --- /dev/null +++ b/static/uploads/table_35.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3329a16b149ece5f2366742078ec66802ab0a0f51f53d6ce1c52c8e1d24b941f +size 389355 diff --git a/static/uploads/zaas.png b/static/uploads/zaas.png new file mode 100644 index 0000000000000000000000000000000000000000..24285f6ae98beebe556f45ab46de0adad123f12a Binary files /dev/null and b/static/uploads/zaas.png differ diff --git a/static/uploads/ziyXZ.png b/static/uploads/ziyXZ.png new file mode 100644 index 0000000000000000000000000000000000000000..a266f1cc57382745625d228c79b0b5eb59c944c8 Binary files /dev/null and b/static/uploads/ziyXZ.png differ diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000000000000000000000000000000000000..039c2e3122fc7ad1cb1b7746a9ebeea11a53469c --- /dev/null +++ b/templates/base.html @@ -0,0 +1,1258 @@ + + +
+ + ++ Capture mathematical equations with your camera and convert them to clean, editable LaTeX code. +
+Processing your equation... This may take a few seconds.
++ Generate beautiful mathematical graphs and plots from simple text commands. +
+Generating your graph... This may take a few seconds.
+Enter a plot command above and click "Generate Graph"
++ Our powerful AI-powered platform converts mathematical equations, handwritten notes, + camera captures, and tables into clean, editable LaTeX code with unmatched accuracy. +
++ Upload images of printed or handwritten mathematical equations and get precise LaTeX code. +
+ + Convert Now + ++ Take a photo of any equation with your device's camera and convert it to LaTeX in real-time. +
+ + Capture Now + ++ Draw your equations directly on our digital canvas and convert them to LaTeX instantly. +
+ + Start Drawing + ++ Automatically detect handwritten tables and convert them to perfectly formatted LaTeX tables. +
+ + Detect Tables + ++ Upload PDFs and use Converto to highlight areas for instant LaTeX conversion. +
+ + Try PDFly + ++ Generate beautiful mathematical graphs and plots from simple text commands. +
+ + Create Graph + +Transform your handwritten math into beautiful LaTeX
++ Upload an image of a mathematical equation and convert it to clean, editable LaTeX code. +
+Processing your equation... This may take a few seconds.
++ Upload a PDF document, highlight areas, and convert them to clean, editable LaTeX code. +
+Processing your PDF... This may take a few seconds.
++ Upload a PDF document, highlight areas with Converto, and convert them to clean, editable LaTeX code. +
+Processing your PDF... This may take a few seconds.
++ Upload a PDF document, highlight areas with Converto, and convert them to clean, editable LaTeX code. +
+Processing your PDF... This may take a few seconds.
++ Draw mathematical equations on the canvas and convert them to clean, editable LaTeX code. +
+Processing your scribble... This may take a few seconds.
++ Convert table images to clean, editable LaTeX code using AI-powered recognition. +
+Processing your table... This may take a few seconds.
+