diff --git a/.gitignore b/.gitignore index a94ea2b..0f7769b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .env __pycache__ database.db -debug_images \ No newline at end of file +debug_images +*.db +*.lock \ No newline at end of file diff --git a/deck_endpoints.py b/deck_endpoints.py index 7878eb6..a5c2fe9 100644 --- a/deck_endpoints.py +++ b/deck_endpoints.py @@ -3,11 +3,16 @@ from flask import Blueprint, request, jsonify import sqlite3 import os +import shutil +import logging deck_bp = Blueprint('deck_bp', __name__) DATABASE = 'mydatabase.db' +# Logger konfigurieren (angenommen, ocr_server3.py konfiguriert das Logging) +logger = logging.getLogger(__name__) + def get_db_connection(): conn = sqlite3.connect(DATABASE) conn.row_factory = sqlite3.Row @@ -41,6 +46,48 @@ def init_db(): conn.commit() conn.close() +def clean_debug_directories(): + """ + Löscht alle Verzeichnisse unter 'debug_images', die keinen Eintrag in der Image-Tabelle haben. + """ + debug_base_dir = 'debug_images' + if not os.path.exists(debug_base_dir): + logger.info(f"Debug-Verzeichnis '{debug_base_dir}' existiert nicht. Nichts zu bereinigen.") + return + + try: + conn = get_db_connection() + cursor = conn.cursor() + cursor.execute('SELECT DISTINCT bildname FROM Image') + bildnames = {row['bildname'] for row in cursor.fetchall()} + conn.close() + except sqlite3.Error as e: + logger.error(f"Fehler beim Abrufen der bildname aus der Datenbank: {e}") + return + + # Durchlaufe alle Verzeichnisse unter 'debug_images' + for dir_name in os.listdir(debug_base_dir): + dir_path = os.path.join(debug_base_dir, dir_name) + if os.path.isdir(dir_path): + if dir_name not in bildnames: + try: + shutil.rmtree(dir_path) + logger.info(f"Nicht verwendetes Debug-Verzeichnis gelöscht: {dir_path}") + except Exception as e: + logger.error(f"Fehler beim Löschen des Verzeichnisses '{dir_path}': {e}") + else: + logger.debug(f"Debug-Verzeichnis behalten: {dir_path}") + + logger.info("Bereinigung der Debug-Verzeichnisse abgeschlossen.") + +# ------ +# Endpoints +# ------ + +# ------ +# Deck - POST, GET, DELETE +# ------ + @deck_bp.route('/api/decks', methods=['POST']) def create_deck(): data = request.get_json() @@ -91,6 +138,7 @@ def get_decks(): conn.close() return jsonify(deck_list) + @deck_bp.route('/api/decks/', methods=['DELETE']) def delete_deck(deckname): conn = get_db_connection() @@ -110,36 +158,75 @@ def delete_deck(deckname): conn.close() return jsonify({'error': 'Deck not found'}), 404 -@deck_bp.route('/image', methods=['PUT']) +# ------ +# Image - POST, GET, DELETE +# ------ + +@deck_bp.route('/api/decks/image', methods=['POST']) def update_image(): data = request.get_json() if not data: return jsonify({'error': 'No data provided'}), 400 - required_fields = ['deckid', 'bildname', 'iconindex', 'x1', 'x2', 'y1', 'y2'] + # Überprüfen, ob die erforderlichen Felder vorhanden sind + required_fields = ['deckname', 'image', 'boxes'] if not all(field in data for field in required_fields): return jsonify({'error': 'Missing fields in data'}), 400 - deckid = data['deckid'] - bildname = data['bildname'] - iconindex = data['iconindex'] - x1 = data['x1'] - x2 = data['x2'] - y1 = data['y1'] - y2 = data['y2'] + deckname = data['deckname'] + bildname = data['image'] + boxes = data['boxes'] + # Überprüfen, ob 'boxes' eine Liste ist und mindestens ein Box-Element enthält + if not isinstance(boxes, list) or len(boxes) == 0: + return jsonify({'error': "'boxes' must be a non-empty list"}), 400 + + # Verbindung zur Datenbank herstellen conn = get_db_connection() cursor = conn.cursor() - cursor.execute(''' - INSERT INTO Image (deckid, bildname, iconindex, x1, x2, y1, y2) - VALUES (?, ?, ?, ?, ?, ?, ?) - ''', (deckid, bildname, iconindex, x1, x2, y1, y2)) - conn.commit() - image_id = cursor.lastrowid - conn.close() - return jsonify({'status': 'success', 'image_id': image_id}), 201 -@deck_bp.route('/image/', methods=['GET']) + try: + # Deck-ID anhand des Decknamens abrufen + cursor.execute('SELECT id FROM Deck WHERE deckname = ?', (deckname,)) + deck = cursor.fetchone() + if not deck: + return jsonify({'error': 'Deck not found'}), 404 + deck_id = deck['id'] + + inserted_image_ids = [] + + # Durch jede Box iterieren und einen Eintrag in der Image-Tabelle erstellen + for index, box in enumerate(boxes): + # Überprüfen, ob alle erforderlichen Koordinaten vorhanden sind + box_fields = ['x1', 'x2', 'y1', 'y2'] + if not all(field in box for field in box_fields): + return jsonify({'error': 'Missing fields in one of the boxes'}), 400 + + x1 = box['x1'] + x2 = box['x2'] + y1 = box['y1'] + y2 = box['y2'] + + # Setzen des iconindex auf den aktuellen Index der Box + iconindex = index + + cursor.execute(''' + INSERT INTO Image (deckid, bildname, iconindex, x1, x2, y1, y2) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', (deck_id, bildname, iconindex, x1, x2, y1, y2)) + inserted_image_ids.append(cursor.lastrowid) + + conn.commit() + return jsonify({'status': 'success', 'inserted_image_ids': inserted_image_ids}), 201 + + except sqlite3.Error as e: + conn.rollback() + return jsonify({'error': 'Database error', 'details': str(e)}), 500 + + finally: + conn.close() + +@deck_bp.route('/api/decks/image/', methods=['GET']) def get_images_by_bildname(bildname): conn = get_db_connection() images = conn.execute('SELECT * FROM Image WHERE bildname = ?', (bildname,)).fetchall() @@ -147,16 +234,64 @@ def get_images_by_bildname(bildname): image_list = [dict(image) for image in images] return jsonify(image_list) -@deck_bp.route('/image//', methods=['GET']) -def get_image_by_bildname_and_index(bildname, iconindex): - conn = get_db_connection() - image = conn.execute('SELECT * FROM Image WHERE bildname = ? AND iconindex = ?', (bildname, iconindex)).fetchone() - conn.close() - if image is None: - return jsonify({'error': 'Image not found'}), 404 - else: - return jsonify(dict(image)), 200 +@deck_bp.route('/api/decks/image/', methods=['DELETE']) +def delete_images_by_bildname(bildname): + """ + Löscht alle Einträge in der Image-Tabelle für den gegebenen bildname. + Optional: Löscht das zugehörige Debug-Verzeichnis, wenn keine weiteren Einträge bestehen. + """ + try: + # Sicherheitsmaßnahme: Nur erlaubte Zeichen im bildname + if not all(c.isalnum() or c in ('_', '-') for c in bildname): + logger.warning(f"Ungültiger bildname angefordert: {bildname}") + return jsonify({'error': 'Invalid image name'}), 400 + + conn = get_db_connection() + cursor = conn.cursor() + + # Überprüfen, ob es Einträge mit dem bildname gibt + cursor.execute('SELECT COUNT(*) as count FROM Image WHERE bildname = ?', (bildname,)) + result = cursor.fetchone() + count = result['count'] if result else 0 + + if count == 0: + conn.close() + return jsonify({'error': 'No entries found for the given image name'}), 404 + + # Löschen der Einträge + cursor.execute('DELETE FROM Image WHERE bildname = ?', (bildname,)) + conn.commit() + conn.close() + + # Optional: Löschen des Debug-Verzeichnisses, wenn keine weiteren Einträge bestehen + # Überprüfen, ob das Verzeichnis existiert + debug_dir = os.path.join('debug_images', bildname) + if os.path.exists(debug_dir): + try: + shutil.rmtree(debug_dir) + logger.info(f"Debug-Verzeichnis gelöscht: {debug_dir}") + except Exception as e: + logger.error(f"Fehler beim Löschen des Debug-Verzeichnisses '{debug_dir}': {e}") + # Sie können entscheiden, ob Sie einen Fehler zurückgeben oder nicht + # Hier geben wir eine Warnung zurück, aber setzen die Anfrage als erfolgreich fort + return jsonify({ + 'status': 'success', + 'message': 'Database entries deleted, but failed to delete debug directory.', + 'details': str(e) + }), 200 + + return jsonify({'status': 'success', 'message': f'All entries for image "{bildname}" have been deleted.'}), 200 + + except sqlite3.Error as e: + logger.error(f"Fehler beim Löschen der Image-Einträge für '{bildname}': {e}") + return jsonify({'error': 'Database error', 'details': str(e)}), 500 + + except Exception as e: + logger.error(f"Unerwarteter Fehler beim Löschen der Image-Einträge für '{bildname}': {e}") + return jsonify({'error': 'Failed to delete image entries', 'details': str(e)}), 500 + # Sicherstellen, dass die Datenbank existiert if not os.path.exists(DATABASE): init_db() +clean_debug_directories() \ No newline at end of file diff --git a/distinct_image.sqlite3-query b/distinct_image.sqlite3-query new file mode 100644 index 0000000..af8da69 --- /dev/null +++ b/distinct_image.sqlite3-query @@ -0,0 +1,3 @@ +-- database: ./mydatabase.db + +SELECT distinct bildname FROM Image; diff --git a/ocr_server3.py b/ocr_server3.py index 382b9e5..d29ae6b 100644 --- a/ocr_server3.py +++ b/ocr_server3.py @@ -1,4 +1,4 @@ -from flask import Flask, request, jsonify +from flask import Flask, request, jsonify, send_file from paddleocr import PaddleOCR import base64 from PIL import Image @@ -202,5 +202,32 @@ def ocr_endpoint(): 'debug_dir': debug_dir if 'debug_dir' in locals() else None }), 500 +@app.route('/api/debug_image/', methods=['GET']) +def get_debug_image(name): + """ + Gibt das Originalbild unter 'debug_images/[name]/original.png' direkt als image/png zurück. + """ + try: + # Sicherheitsmaßnahme: Nur erlaubte Zeichen im Namen + if not all(c.isalnum() or c in ('_', '-') for c in name): + logger.warning(f"Ungültiger Bildname angefordert: {name}") + return jsonify({'error': 'Invalid image name'}), 400 + + image_path = os.path.join('debug_images', name, 'original.png') + if not os.path.isfile(image_path): + logger.warning(f"Bild nicht gefunden: {image_path}") + return jsonify({'error': 'Image not found'}), 404 + + return send_file( + image_path, + mimetype='image/png', + as_attachment=False + ) + + except Exception as e: + logger.error(f"Fehler beim Abrufen des Bildes '{name}': {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/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3033053 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "vocab-backend" +version = "0.1.0" +description = "" +authors = ["Andreas Knuth "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +flask = "^2.2.3" +paddleocr = "^2.9.1" +pillow = "^9.4.0" +numpy = "^1.23.5" # Korrigiere dies auf eine 1.x-Version +opencv-python = "^4.6.0.66" +paddlepaddle = "^2.6.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api"