| 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
|
|
|
|
|
|
|
|
|
| table_bp = Blueprint('table_bp', __name__)
|
|
|
| UPLOAD_FOLDER = 'static/uploads'
|
| os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
| 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.append((y1 + y2) / 2)
|
| elif abs(x2 - x1) < abs(y2 - y1):
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
| 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}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| @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:
|
|
|
| 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)
|
|
|
|
|
| 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
|
|
|
|
|
| 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
|
|
|