diff --git a/app_factory.py b/app_factory.py new file mode 100644 index 0000000..23cc2b7 --- /dev/null +++ b/app_factory.py @@ -0,0 +1,6 @@ +from flask import Flask + +# Erstelle die Flask-App +app = Flask(__name__) + +# Konfigurationen und Erweiterungen hier hinzufügen \ No newline at end of file diff --git a/deck_endpoints.py b/deck_endpoints.py index 313c6c0..83b885a 100644 --- a/deck_endpoints.py +++ b/deck_endpoints.py @@ -1,10 +1,13 @@ # deck_endpoints.py - +from app_factory import app from flask import Blueprint, request, jsonify import sqlite3 import os import shutil import logging +from sqlalchemy import create_engine +from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.sql import text deck_bp = Blueprint('deck_bp', __name__) @@ -195,6 +198,43 @@ def get_decks(): deck_list = list(decks.values()) return jsonify(deck_list) +@deck_bp.route('/api/decks/', methods=['GET']) +def get_deck(deckname): + conn = get_db_connection() + cursor = conn.cursor() + # Einträge für das spezifische Deck abrufen + entries = cursor.execute('SELECT * FROM Deck WHERE deckname = ?', (deckname,)).fetchall() + conn.close() + + if not entries: + return jsonify({'error': 'Deck not found'}), 404 + + deck = { + 'name': deckname, + 'images': [] + } + for entry in entries: + if entry['bildname'] and entry['bildid']: + image = { + 'name': entry['bildname'], + 'id': entry['bildid'], + 'iconindex': entry['iconindex'], + 'boxid': entry['id'], + 'x1': entry['x1'], + 'x2': entry['x2'], + 'y1': entry['y1'], + 'y2': entry['y2'], + 'due': entry['due'], + 'ivl': entry['ivl'], + 'factor': entry['factor'], + 'reps': entry['reps'], + 'lapses': entry['lapses'], + 'isGraduated': bool(entry['isGraduated']) + } + deck['images'].append(image) + + return jsonify(deck) + @deck_bp.route('/api/decks/', methods=['DELETE']) def delete_deck(deckname): conn = get_db_connection() diff --git a/ocr_server.py b/ocr_server.py index 82d4e71..0a7081a 100644 --- a/ocr_server.py +++ b/ocr_server.py @@ -1,17 +1,98 @@ -from flask import Flask, request, jsonify +from flask import Flask, request, jsonify, send_file from paddleocr import PaddleOCR import base64 from PIL import Image from io import BytesIO import traceback -import numpy as np # Importieren von numpy +import numpy as np +import cv2 +import logging +import os +import uuid +import datetime +from app_factory import app +# from deck_endpoints import deck_bp # Importieren des Blueprints -app = Flask(__name__) -ocr = PaddleOCR(use_angle_cls=True, lang='en') # Passen Sie die Sprache nach Bedarf an +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) -@app.route('/ocr', methods=['POST']) +# app = Flask(__name__) +# app.register_blueprint(deck_bp) # Registrieren des Blueprints + +def get_dir_name(): + timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + unique_id = str(uuid.uuid4())[:8] + return f"{timestamp}_{unique_id}" + +def create_debug_directory(dir_name): + """Erstellt ein eindeutiges Verzeichnis für Debug-Bilder""" + base_dir = 'debug_images' + timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + unique_id = str(uuid.uuid4())[:8] + full_path = os.path.join(base_dir, dir_name) + + # Erstelle Hauptverzeichnis falls nicht vorhanden + if not os.path.exists(base_dir): + os.makedirs(base_dir) + + # Erstelle spezifisches Verzeichnis für diesen Durchlauf + os.makedirs(full_path) + + return full_path + +def preprocess_image(image, debug_dir): + """ + Verarbeitet das Bild und speichert Zwischenergebnisse im angegebenen Verzeichnis, + einschließlich einer komprimierten JPG-Version und eines Thumbnails. + """ + try: + # Umwandlung in Graustufen + gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) + # Anwendung von CLAHE zur Kontrastverbesserung + clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) + enhanced = clahe.apply(gray) + # Rauschunterdrückung + denoised = cv2.fastNlMeansDenoising(enhanced) + # Optional: Binärschwellenwert (auskommentiert) + # _, binary = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + + # Speichern der Zwischenergebnisse im spezifischen Verzeichnis + cv2.imwrite(os.path.join(debug_dir, 'gray.png'), gray) + cv2.imwrite(os.path.join(debug_dir, 'enhanced.png'), enhanced) + cv2.imwrite(os.path.join(debug_dir, 'denoised.png'), denoised) + # cv2.imwrite(os.path.join(debug_dir, 'binary.png'), binary) + + # Speichern der komprimierten JPG-Version des Originalbildes + compressed_jpg_path = os.path.join(debug_dir, 'original_compressed.jpg') + original_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) + cv2.imwrite(compressed_jpg_path, original_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) # Qualität auf 80 setzen + logger.info(f"Komprimiertes Original JPG gespeichert: {compressed_jpg_path}") + + # Erstellen und Speichern des Thumbnails + thumbnail_path = os.path.join(debug_dir, 'thumbnail.jpg') + image_pil = Image.fromarray(denoised) + image_pil.thumbnail((128, 128)) # Thumbnail-Größe auf 128x128 Pixel setzen + image_pil.save(thumbnail_path, 'JPEG') + logger.info(f"Thumbnail gespeichert: {thumbnail_path}") + + logger.info(f"Debug images saved in: {debug_dir}") + return denoised + except Exception as e: + logger.error(f"Preprocessing error: {str(e)}") + raise + + +@app.route('/api/ocr', methods=['POST']) def ocr_endpoint(): try: + # Erstelle eindeutiges Debug-Verzeichnis für diesen Request + dir_name = get_dir_name() + debug_dir = create_debug_directory(dir_name) + logger.info(f"Created debug directory: {debug_dir}") + if not request.is_json: return jsonify({'error': 'Content-Type must be application/json'}), 400 @@ -20,41 +101,167 @@ def ocr_endpoint(): return jsonify({'error': 'No image provided'}), 400 image_b64 = data['image'] - if not image_b64: - return jsonify({'error': 'Empty image data'}), 400 + # Base64 Dekodierung try: image_data = base64.b64decode(image_b64) except Exception as decode_err: - return jsonify({'error': 'Base64 decode error', 'details': str(decode_err)}), 400 - + logger.error(f"Base64 decode error: {str(decode_err)}") + return jsonify({'error': 'Base64 decode error'}), 400 + + # Bildverarbeitung try: image = Image.open(BytesIO(image_data)).convert('RGB') - image = np.array(image) # Konvertieren zu numpy.ndarray + image = np.array(image) + logger.info(f"Image loaded successfully. Shape: {image.shape}") + + # Originalbild speichern + cv2.imwrite(os.path.join(debug_dir, 'original.png'), + cv2.cvtColor(image, cv2.COLOR_RGB2BGR)) except Exception as img_err: - return jsonify({'error': 'Invalid image data', 'details': str(img_err)}), 400 - - # Optional: Bildgröße anpassen, falls erforderlich - # PaddleOCR kann große Bilder verarbeiten, aber zur Effizienz können Sie eine maximale Größe setzen - max_width = 1920 - max_height = 1080 - height, width, _ = image.shape - if width > max_width or height > max_height: - aspect_ratio = width / height - if aspect_ratio > 1: - new_width = max_width - new_height = int(max_width / aspect_ratio) - else: - new_height = max_height - new_width = int(max_height * aspect_ratio) - image = np.array(Image.fromarray(image).resize((new_width, new_height))) - - result = ocr.ocr(image, rec=True, cls=True) - return jsonify(result) + logger.error(f"Image processing error: {str(img_err)}") + return jsonify({'error': 'Invalid image data'}), 400 + + # Bildvorverarbeitung + processed_image = preprocess_image(image, debug_dir) + logger.info("Preprocessing completed") + + # PaddleOCR Konfiguration + ocr = PaddleOCR( + use_angle_cls=True, + lang='en', + det_db_thresh=0.3, + det_db_box_thresh=0.3, + det_db_unclip_ratio=2.0, + rec_char_type='en', + det_limit_side_len=960, + det_limit_type='max', + use_dilation=True, + det_db_score_mode='fast', + show_log=True + ) + + # OCR durchführen + try: + result = ocr.ocr(processed_image, rec=True, cls=True) + + # Debug-Informationen in Datei speichern + with open(os.path.join(debug_dir, 'ocr_results.txt'), 'w') as f: + f.write(f"Raw OCR result:\n{result}\n\n") + + if not result: + logger.warning("No results returned from OCR") + return jsonify({ + 'warning': 'No text detected', + 'debug_dir': debug_dir + }), 200 + + if not result[0]: + logger.warning("Empty results list from OCR") + return jsonify({ + 'warning': 'Empty results list', + 'debug_dir': debug_dir + }), 200 + + # Ergebnisse verarbeiten + extracted_results = [] + for idx, item in enumerate(result[0]): + try: + box = item[0] + text = item[1][0] if item[1] else '' + confidence = float(item[1][1]) if item[1] and len(item[1]) > 1 else 0.0 + + extracted_results.append({ + 'box': box, + 'text': text, + 'confidence': confidence, + 'name': dir_name + }) + except Exception as proc_err: + logger.error(f"Error processing result {idx}: {str(proc_err)}") + + # Statistiken in Debug-Datei speichern + with open(os.path.join(debug_dir, 'statistics.txt'), 'w') as f: + f.write(f"Total results: {len(extracted_results)}\n") + if extracted_results: + avg_confidence = np.mean([r['confidence'] for r in extracted_results]) + f.write(f"Average confidence: {avg_confidence}\n") + f.write("\nDetailed results:\n") + for idx, result in enumerate(extracted_results): + f.write(f"Result {idx+1}:\n") + f.write(f"Text: {result['text']}\n") + f.write(f"Confidence: {result['confidence']}\n") + f.write(f"Name: {dir_name}\n") + f.write(f"Box coordinates: {result['box']}\n\n") + + return jsonify({ + 'status': 'success', + 'results': extracted_results, + }) + + except Exception as ocr_err: + logger.error(f"OCR processing error: {str(ocr_err)}") + logger.error(traceback.format_exc()) + return jsonify({ + 'error': 'OCR processing failed', + 'details': str(ocr_err), + 'debug_dir': debug_dir + }), 500 + except Exception as e: - traceback.print_exc() - return jsonify({'error': str(e)}), 500 + logger.error(f"Unexpected error: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({ + 'error': 'Internal server error', + 'debug_dir': debug_dir if 'debug_dir' in locals() else None + }), 500 + +@app.route('/api/debug_image//', methods=['GET']) +def get_debug_image(name, filename): + """ + Gibt das angeforderte Bild unter 'debug_images/[name]/[filename]' direkt zurück. + """ + try: + # Sicherheitsmaßnahme: Nur erlaubte Zeichen im Verzeichnisnamen + if not all(c.isalnum() or c in ('_', '-') for c in name): + logger.warning(f"Ungültiger Verzeichnisname angefordert: {name}") + return jsonify({'error': 'Invalid directory name'}), 400 + + # Sicherheitsmaßnahme: Nur erlaubte Zeichen im Dateinamen + if not all(c.isalnum() or c in ('_', '-', '.',) for c in filename): + logger.warning(f"Ungültiger Dateiname angefordert: {filename}") + return jsonify({'error': 'Invalid file name'}), 400 + + # Vollständigen Pfad zum Bild erstellen + image_path = os.path.join('debug_images', name, filename) + + # Überprüfen, ob die Datei existiert + if not os.path.isfile(image_path): + logger.warning(f"Bild nicht gefunden: {image_path}") + return jsonify({'error': 'Image not found'}), 404 + + # Bestimmen des MIME-Typs basierend auf der Dateiendung + mime_type = 'image/png' # Standard-MIME-Typ + if filename.lower().endswith('.jpg') or filename.lower().endswith('.jpeg'): + mime_type = 'image/jpeg' + elif filename.lower().endswith('.gif'): + mime_type = 'image/gif' + elif filename.lower().endswith('.bmp'): + mime_type = 'image/bmp' + elif filename.lower().endswith('.tiff') or filename.lower().endswith('.tif'): + mime_type = 'image/tiff' + + return send_file( + image_path, + mimetype=mime_type, + as_attachment=False + ) + + except Exception as e: + logger.error(f"Fehler beim Abrufen des Bildes '{name}/{filename}': {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({'error': 'Failed to retrieve image'}), 500 + if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) - + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/ocr_server1.py b/ocr_server1.py deleted file mode 100644 index 3bf6f9d..0000000 --- a/ocr_server1.py +++ /dev/null @@ -1,90 +0,0 @@ -from flask import Flask, request, jsonify -from paddleocr import PaddleOCR -import base64 -from PIL import Image -from io import BytesIO -import traceback -import numpy as np -import cv2 # Import von OpenCV - -app = Flask(__name__) -def preprocess_image(image): - # Konvertierung zu Graustufen - gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) - - # Kontrastverstärkung - clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) - enhanced = clahe.apply(gray) - - # Rauschreduzierung - denoised = cv2.fastNlMeansDenoising(enhanced) - - # Binarisierung - _, binary = cv2.threshold(denoised, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) - - return binary - -@app.route('/ocr', methods=['POST']) -def ocr_endpoint(): - try: - if not request.is_json: - return jsonify({'error': 'Content-Type must be application/json'}), 400 - - data = request.get_json() - if not data or 'image' not in data: - return jsonify({'error': 'No image provided'}), 400 - - image_b64 = data['image'] - if not image_b64: - return jsonify({'error': 'Empty image data'}), 400 - - try: - image_data = base64.b64decode(image_b64) - except Exception as decode_err: - return jsonify({'error': 'Base64 decode error', 'details': str(decode_err)}), 400 - - try: - image = Image.open(BytesIO(image_data)).convert('RGB') - image = preprocess_image(image) - image = np.array(image) # Konvertieren zu numpy.ndarray - except Exception as img_err: - return jsonify({'error': 'Invalid image data', 'details': str(img_err)}), 400 - - # Optional: Bildgröße anpassen, falls erforderlich - max_width = 1920 - max_height = 1080 - height, width, _ = image.shape - if width > max_width or height > max_height: - aspect_ratio = width / height - if aspect_ratio > 1: - new_width = max_width - new_height = int(max_width / aspect_ratio) - else: - new_height = max_height - new_width = int(max_height * aspect_ratio) - image = np.array(Image.fromarray(image).resize((new_width, new_height))) - - # Initialisieren Sie PaddleOCR innerhalb des Handlers - ocr = PaddleOCR(use_angle_cls=True, lang='en') # Initialisierung innerhalb des Handlers - - result = ocr.ocr(image, rec=True, cls=True) - - # Extrahieren der Texte und Konfidenzwerte - extracted_results = [] - for item in result[0]: - box = item[0] # Die Koordinaten der Textbox - text = item[1][0] # Der erkannte Text - confidence = item[1][1] # Der Konfidenzwert - extracted_results.append({ - 'box': box, - 'text': text, - 'confidence': confidence - }) - - return jsonify(extracted_results) - except Exception as e: - traceback.print_exc() - return jsonify({'error': str(e)}), 500 - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/ocr_server2.py b/ocr_server2.py deleted file mode 100644 index 617d3c9..0000000 --- a/ocr_server2.py +++ /dev/null @@ -1,114 +0,0 @@ -from flask import Flask, request, jsonify -from paddleocr import PaddleOCR -import base64 -from PIL import Image -from io import BytesIO -import traceback -import numpy as np -import cv2 # Import von OpenCV -import os # Import für das Speichern von Dateien -import time # Import für Zeitstempel - -app = Flask(__name__) - -# Initialisiere PaddleOCR einmal außerhalb der Anfrage, um die Leistung zu verbessern -ocr = PaddleOCR(use_angle_cls=True, lang='en') # Initialisierung außerhalb des Handlers - -@app.route('/ocr', methods=['POST']) -def ocr_endpoint(): - try: - if not request.is_json: - return jsonify({'error': 'Content-Type must be application/json'}), 400 - - data = request.get_json() - if not data or 'image' not in data: - return jsonify({'error': 'No image provided'}), 400 - - image_b64 = data['image'] - if not image_b64: - return jsonify({'error': 'Empty image data'}), 400 - - try: - image_data = base64.b64decode(image_b64) - except Exception as decode_err: - return jsonify({'error': 'Base64 decode error', 'details': str(decode_err)}), 400 - - try: - image = Image.open(BytesIO(image_data)).convert('RGB') - image_np = np.array(image) # Konvertieren zu numpy.ndarray - except Exception as img_err: - return jsonify({'error': 'Invalid image data'}), 400 - - # Vorverarbeitung: Behalte nur dunkle (schwarze) Bereiche des Bildes - # Konvertiere das Bild zu Graustufen - gray = cv2.cvtColor(image_np, cv2.COLOR_RGB2GRAY) - - # Wende einen Schwellenwert an, um nur die dunklen Bereiche zu behalten - threshold_value = 150 # Passe diesen Wert nach Bedarf an - _, mask = cv2.threshold(gray, threshold_value, 255, cv2.THRESH_BINARY_INV) - - # Optional: Morphologische Operationen zur Verbesserung der Maske - kernel = np.ones((3,3), np.uint8) - mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1) - mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel, iterations=1) - - # Wende die Maske auf das Originalbild an - filtered_image_np = cv2.bitwise_and(image_np, image_np, mask=mask) - - # Konvertiere das gefilterte Bild zurück zu PIL Image - filtered_image = Image.fromarray(filtered_image_np) - - # Optional: Bildgröße anpassen, falls erforderlich - max_width = 1920 - max_height = 1080 - height, width, _ = filtered_image_np.shape - if width > max_width or height > max_height: - aspect_ratio = width / height - if aspect_ratio > 1: - new_width = max_width - new_height = int(max_width / aspect_ratio) - else: - new_height = max_height - new_width = int(max_height * aspect_ratio) - filtered_image = filtered_image.resize((new_width, new_height)) - filtered_image_np = np.array(filtered_image) - - # **Speichern des vorverarbeiteten Bildes zur Überprüfung** - output_dir = 'processed_images' - if not os.path.exists(output_dir): - os.makedirs(output_dir) - - # Generiere einen einzigartigen Dateinamen basierend auf dem aktuellen Zeitstempel - timestamp = int(time.time() * 1000) - processed_image_path = os.path.join(output_dir, f'processed_{timestamp}.png') - filtered_image.save(processed_image_path) - print(f'Processed image saved at: {processed_image_path}') - - # **Speichern der Maske zur Überprüfung** - mask_image = Image.fromarray(mask) - mask_image_path = os.path.join(output_dir, f'mask_{timestamp}.png') - mask_image.save(mask_image_path) - print(f'Mask image saved at: {mask_image_path}') - - # Führe OCR auf dem gefilterten Bild durch - result = ocr.ocr(filtered_image_np, rec=True, cls=True) - - # Extrahieren der Texte und Konfidenzwerte - extracted_results = [] - for item in result: - box = item[0] # Die Koordinaten der Textbox - text = item[1][0] # Der erkannte Text - confidence = item[1][1] # Der Konfidenzwert - extracted_results.append({ - 'box': box, - 'text': text, - 'confidence': confidence - }) - - return jsonify(extracted_results) - except Exception as e: - traceback.print_exc() - return jsonify({'error': str(e)}), 500 - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True, threaded=False) # Single-Threaded diff --git a/ocr_server3.py b/ocr_server3.py deleted file mode 100644 index c2d1dda..0000000 --- a/ocr_server3.py +++ /dev/null @@ -1,272 +0,0 @@ -from flask import Flask, request, jsonify, send_file -from paddleocr import PaddleOCR -import base64 -from PIL import Image -from io import BytesIO -import traceback -import numpy as np -import cv2 -import logging -import os -import uuid -import datetime - -from deck_endpoints import deck_bp # Importieren des Blueprints - -logging.basicConfig( - level=logging.DEBUG, - format='%(asctime)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger(__name__) - -app = Flask(__name__) -app.register_blueprint(deck_bp) # Registrieren des Blueprints - -def get_dir_name(): - timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') - unique_id = str(uuid.uuid4())[:8] - return f"{timestamp}_{unique_id}" - -def create_debug_directory(dir_name): - """Erstellt ein eindeutiges Verzeichnis für Debug-Bilder""" - base_dir = 'debug_images' - timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') - unique_id = str(uuid.uuid4())[:8] - full_path = os.path.join(base_dir, dir_name) - - # Erstelle Hauptverzeichnis falls nicht vorhanden - if not os.path.exists(base_dir): - os.makedirs(base_dir) - - # Erstelle spezifisches Verzeichnis für diesen Durchlauf - os.makedirs(full_path) - - return full_path - -def preprocess_image(image, debug_dir): - """ - Verarbeitet das Bild und speichert Zwischenergebnisse im angegebenen Verzeichnis, - einschließlich einer komprimierten JPG-Version und eines Thumbnails. - """ - try: - # Umwandlung in Graustufen - gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) - # Anwendung von CLAHE zur Kontrastverbesserung - clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) - enhanced = clahe.apply(gray) - # Rauschunterdrückung - denoised = cv2.fastNlMeansDenoising(enhanced) - # Optional: Binärschwellenwert (auskommentiert) - # _, binary = cv2.threshold(denoised, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) - - # Speichern der Zwischenergebnisse im spezifischen Verzeichnis - cv2.imwrite(os.path.join(debug_dir, 'gray.png'), gray) - cv2.imwrite(os.path.join(debug_dir, 'enhanced.png'), enhanced) - cv2.imwrite(os.path.join(debug_dir, 'denoised.png'), denoised) - # cv2.imwrite(os.path.join(debug_dir, 'binary.png'), binary) - - # Speichern der komprimierten JPG-Version des Originalbildes - compressed_jpg_path = os.path.join(debug_dir, 'original_compressed.jpg') - original_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) - cv2.imwrite(compressed_jpg_path, original_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) # Qualität auf 80 setzen - logger.info(f"Komprimiertes Original JPG gespeichert: {compressed_jpg_path}") - - # Erstellen und Speichern des Thumbnails - thumbnail_path = os.path.join(debug_dir, 'thumbnail.jpg') - image_pil = Image.fromarray(denoised) - image_pil.thumbnail((128, 128)) # Thumbnail-Größe auf 128x128 Pixel setzen - image_pil.save(thumbnail_path, 'JPEG') - logger.info(f"Thumbnail gespeichert: {thumbnail_path}") - - logger.info(f"Debug images saved in: {debug_dir}") - return denoised - except Exception as e: - logger.error(f"Preprocessing error: {str(e)}") - raise - - -@app.route('/api/ocr', methods=['POST']) -def ocr_endpoint(): - try: - # Erstelle eindeutiges Debug-Verzeichnis für diesen Request - dir_name = get_dir_name() - debug_dir = create_debug_directory(dir_name) - logger.info(f"Created debug directory: {debug_dir}") - - if not request.is_json: - return jsonify({'error': 'Content-Type must be application/json'}), 400 - - data = request.get_json() - if not data or 'image' not in data: - return jsonify({'error': 'No image provided'}), 400 - - image_b64 = data['image'] - - # Base64 Dekodierung - try: - image_data = base64.b64decode(image_b64) - except Exception as decode_err: - logger.error(f"Base64 decode error: {str(decode_err)}") - return jsonify({'error': 'Base64 decode error'}), 400 - - # Bildverarbeitung - try: - image = Image.open(BytesIO(image_data)).convert('RGB') - image = np.array(image) - logger.info(f"Image loaded successfully. Shape: {image.shape}") - - # Originalbild speichern - cv2.imwrite(os.path.join(debug_dir, 'original.png'), - cv2.cvtColor(image, cv2.COLOR_RGB2BGR)) - except Exception as img_err: - logger.error(f"Image processing error: {str(img_err)}") - return jsonify({'error': 'Invalid image data'}), 400 - - # Bildvorverarbeitung - processed_image = preprocess_image(image, debug_dir) - logger.info("Preprocessing completed") - - # PaddleOCR Konfiguration - ocr = PaddleOCR( - use_angle_cls=True, - lang='en', - det_db_thresh=0.3, - det_db_box_thresh=0.3, - det_db_unclip_ratio=2.0, - rec_char_type='en', - det_limit_side_len=960, - det_limit_type='max', - use_dilation=True, - det_db_score_mode='fast', - show_log=True - ) - - # OCR durchführen - try: - result = ocr.ocr(processed_image, rec=True, cls=True) - - # Debug-Informationen in Datei speichern - with open(os.path.join(debug_dir, 'ocr_results.txt'), 'w') as f: - f.write(f"Raw OCR result:\n{result}\n\n") - - if not result: - logger.warning("No results returned from OCR") - return jsonify({ - 'warning': 'No text detected', - 'debug_dir': debug_dir - }), 200 - - if not result[0]: - logger.warning("Empty results list from OCR") - return jsonify({ - 'warning': 'Empty results list', - 'debug_dir': debug_dir - }), 200 - - # Ergebnisse verarbeiten - extracted_results = [] - for idx, item in enumerate(result[0]): - try: - box = item[0] - text = item[1][0] if item[1] else '' - confidence = float(item[1][1]) if item[1] and len(item[1]) > 1 else 0.0 - - extracted_results.append({ - 'box': box, - 'text': text, - 'confidence': confidence, - 'name': dir_name - }) - except Exception as proc_err: - logger.error(f"Error processing result {idx}: {str(proc_err)}") - - # Statistiken in Debug-Datei speichern - with open(os.path.join(debug_dir, 'statistics.txt'), 'w') as f: - f.write(f"Total results: {len(extracted_results)}\n") - if extracted_results: - avg_confidence = np.mean([r['confidence'] for r in extracted_results]) - f.write(f"Average confidence: {avg_confidence}\n") - f.write("\nDetailed results:\n") - for idx, result in enumerate(extracted_results): - f.write(f"Result {idx+1}:\n") - f.write(f"Text: {result['text']}\n") - f.write(f"Confidence: {result['confidence']}\n") - f.write(f"Name: {dir_name}\n") - f.write(f"Box coordinates: {result['box']}\n\n") - - return jsonify({ - 'status': 'success', - 'results': extracted_results, - # 'debug_info': { - # 'total_boxes_detected': len(result[0]) if result and result[0] else 0, - # 'processed_results': len(extracted_results), - # 'debug_dir': debug_dir - # } - }) - - except Exception as ocr_err: - logger.error(f"OCR processing error: {str(ocr_err)}") - logger.error(traceback.format_exc()) - return jsonify({ - 'error': 'OCR processing failed', - 'details': str(ocr_err), - 'debug_dir': debug_dir - }), 500 - - except Exception as e: - logger.error(f"Unexpected error: {str(e)}") - logger.error(traceback.format_exc()) - return jsonify({ - 'error': 'Internal server error', - 'debug_dir': debug_dir if 'debug_dir' in locals() else None - }), 500 - -@app.route('/api/debug_image//', methods=['GET']) -def get_debug_image(name, filename): - """ - Gibt das angeforderte Bild unter 'debug_images/[name]/[filename]' direkt zurück. - """ - try: - # Sicherheitsmaßnahme: Nur erlaubte Zeichen im Verzeichnisnamen - if not all(c.isalnum() or c in ('_', '-') for c in name): - logger.warning(f"Ungültiger Verzeichnisname angefordert: {name}") - return jsonify({'error': 'Invalid directory name'}), 400 - - # Sicherheitsmaßnahme: Nur erlaubte Zeichen im Dateinamen - if not all(c.isalnum() or c in ('_', '-', '.',) for c in filename): - logger.warning(f"Ungültiger Dateiname angefordert: {filename}") - return jsonify({'error': 'Invalid file name'}), 400 - - # Vollständigen Pfad zum Bild erstellen - image_path = os.path.join('debug_images', name, filename) - - # Überprüfen, ob die Datei existiert - if not os.path.isfile(image_path): - logger.warning(f"Bild nicht gefunden: {image_path}") - return jsonify({'error': 'Image not found'}), 404 - - # Bestimmen des MIME-Typs basierend auf der Dateiendung - mime_type = 'image/png' # Standard-MIME-Typ - if filename.lower().endswith('.jpg') or filename.lower().endswith('.jpeg'): - mime_type = 'image/jpeg' - elif filename.lower().endswith('.gif'): - mime_type = 'image/gif' - elif filename.lower().endswith('.bmp'): - mime_type = 'image/bmp' - elif filename.lower().endswith('.tiff') or filename.lower().endswith('.tif'): - mime_type = 'image/tiff' - - return send_file( - image_path, - mimetype=mime_type, - as_attachment=False - ) - - except Exception as e: - logger.error(f"Fehler beim Abrufen des Bildes '{name}/{filename}': {str(e)}") - logger.error(traceback.format_exc()) - return jsonify({'error': 'Failed to retrieve image'}), 500 - - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fad7532..9f6d090 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ pillow>=10.0.0 numpy>=1.24.4,<2.0.0 opencv-python==4.6.0.66 paddlepaddle==2.6.2 -werkzeug<2.3 \ No newline at end of file +werkzeug<2.3 +SQLAlchemy==2.0.20 \ No newline at end of file