|
|
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
|
|
|
|