This commit is contained in:
Mateusz Gruszczyński
2025-07-21 12:08:01 +02:00
parent 98f22e0bd1
commit 8ae9068ffa
6 changed files with 247 additions and 4 deletions

144
app.py
View File

@@ -9,6 +9,9 @@ import psutil
import secrets
import hashlib
import re
import tempfile
from pillow_heif import register_heif_opener
from datetime import datetime, timedelta, UTC, timezone
@@ -49,6 +52,11 @@ from sqlalchemy import func, extract
from collections import defaultdict, deque
from functools import wraps
# OCR
from collections import Counter
import pytesseract
app = Flask(__name__)
app.config.from_object(Config)
register_heif_opener() # pillow_heif dla HEIC
@@ -335,6 +343,89 @@ def _receipt_error(message):
return redirect(request.referrer or url_for("main_page"))
############# OCR ###########################
def preprocess_image_for_tesseract(pil_image):
import cv2
import numpy as np
from PIL import Image
# Konwersja PIL.Image → NumPy grayscale
image = np.array(pil_image.convert("L"))
# Zwiększenie skali dla lepszej czytelności OCR
image = cv2.resize(image, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_LINEAR)
# Adaptacyjne progowanie (lepsze niż THRESH_BINARY przy nierównym tle)
image = cv2.adaptiveThreshold(
image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, blockSize=15, C=10
)
# Konwersja z powrotem na PIL.Image (dla pytesseract)
return Image.fromarray(image)
def extract_total_tesseract(image):
text = pytesseract.image_to_string(image, lang="pol", config="--psm 6")
lines = text.splitlines()
candidates = []
fuzzy_regex = re.compile(r"[\dOo][.,:;g9zZ][\d]{2}")
for line in lines:
if not line.strip():
continue
matches = re.findall(r"\d{1,4}[.,]\d{2}", line)
for match in matches:
try:
val = float(match.replace(",", "."))
if 0.1 <= val <= 100000:
candidates.append((val, line))
except:
continue
fuzzy_matches = fuzzy_regex.findall(line)
for match in fuzzy_matches:
cleaned = (
match.replace("O", "0")
.replace("o", "0")
.replace(":", ".")
.replace(";", ".")
.replace(",", ".")
.replace("g", "9")
.replace("z", "9")
.replace("Z", "9")
)
try:
val = float(cleaned)
if 0.1 <= val <= 100:
candidates.append((val, line))
except:
continue
preferred = [
val
for val, line in candidates
if re.search(r"sum[aąo]?|razem|zapłaty", line.lower())
]
if preferred:
max_val = round(max(preferred), 2)
return max_val, lines
if candidates:
max_val = round(max([val for val, _ in candidates]), 2)
return max_val, lines
return 0.0, lines
############# END OCR #######################
# zabezpieczenie logowani do systemu - błędne hasła
def is_ip_blocked(ip):
now = time.time()
@@ -1037,6 +1128,59 @@ def reorder_items():
return jsonify(success=True)
# OCR
@app.route("/lists/<int:list_id>/analyze", methods=["POST"])
@login_required
def analyze_receipts_for_list(list_id):
list_obj = db.session.get(ShoppingList, list_id)
if not list_obj or list_obj.owner_id != current_user.id:
return jsonify({"error": "Brak dostępu"}), 403
receipt_objs = Receipt.query.filter_by(list_id=list_id).all()
results = []
total = 0.0
for receipt in receipt_objs:
filepath = os.path.join(app.config["UPLOAD_FOLDER"], receipt.filename)
if not os.path.exists(filepath):
continue
temp_path = None
try:
if filepath.lower().endswith(".webp"):
raw_image = Image.open(filepath).convert("RGB")
image = preprocess_image_for_tesseract(raw_image)
else:
raw_image = Image.open(filepath).convert("RGB")
image = preprocess_image_for_tesseract(raw_image)
value, lines = extract_total_tesseract(image)
except Exception as e:
print(f"OCR error for {receipt.filename}: {e}")
value = 0.0
lines = []
finally:
if temp_path and os.path.exists(temp_path):
os.unlink(temp_path)
results.append(
{
"id": receipt.id,
"filename": receipt.filename,
"amount": round(value, 2),
"debug_text": lines,
}
)
total += value
return jsonify({"results": results, "total": round(total, 2)})
@app.route("/admin")
@login_required
@admin_required