-
-
Notifications
You must be signed in to change notification settings - Fork 623
Fix: Real-Time Tagging Progress via WebSockets + TaggingCompleted Fix #691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1eeb227
1035d06
6dd42af
ca950f5
b2e7687
d942dbc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,11 @@ | |
| import uuid | ||
| import datetime | ||
| import json | ||
| import logging | ||
| from typing import List, Tuple, Dict, Any, Mapping | ||
| from PIL import Image, ExifTags | ||
| from pathlib import Path | ||
| from collections import defaultdict | ||
| from app.utils.webSocket.webSocket import publish_progress_from_thread | ||
|
|
||
| from app.config.settings import THUMBNAIL_IMAGES_PATH | ||
| from app.database.images import ( | ||
|
|
@@ -26,8 +27,6 @@ | |
| # GPS EXIF tag constant | ||
| GPS_INFO_TAG = 34853 | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def image_util_process_folder_images(folder_data: List[Tuple[str, int, bool]]) -> bool: | ||
| """Main function to process images in multiple folders based on provided folder data. | ||
|
|
@@ -81,6 +80,7 @@ def image_util_process_folder_images(folder_data: List[Tuple[str, int, bool]]) - | |
| return True # No images to process is not an error | ||
| except Exception as e: | ||
| logger.error(f"Error processing folders: {e}") | ||
|
|
||
| return False | ||
|
|
||
|
|
||
|
|
@@ -104,36 +104,86 @@ def image_util_process_untagged_images() -> bool: | |
| def image_util_classify_and_face_detect_images( | ||
| untagged_images: List[Dict[str, str]], | ||
| ) -> None: | ||
| """Classify untagged images and detect faces if applicable.""" | ||
| """Classify untagged images and detect faces if applicable. | ||
|
|
||
| Progress updates are sent via publish_progress_from_thread using folder_id as job_id. | ||
| """ | ||
| object_classifier = ObjectClassifier() | ||
| face_detector = FaceDetector() | ||
| try: | ||
| for image in untagged_images: | ||
| image_path = image["path"] | ||
| image_id = image["id"] | ||
|
|
||
| # Step 1: Get classes | ||
| classes = object_classifier.get_classes(image_path) | ||
| # Group images by folder_id (folder_id is a string) | ||
| images_by_folder = defaultdict(list) | ||
| for image in untagged_images: | ||
| folder_id = image.get("folder_id") | ||
| if folder_id: # Skip images without folder_id | ||
| images_by_folder[folder_id].append(image) | ||
|
|
||
| # Step 2: Insert class-image pairs if classes were detected | ||
| if len(classes) > 0: | ||
| # Create image-class pairs | ||
| image_class_pairs = [(image_id, class_id) for class_id in classes] | ||
| logger.debug(f"Image-class pairs: {image_class_pairs}") | ||
| try: | ||
| for folder_id, folder_images in images_by_folder.items(): | ||
| total = len(folder_images) | ||
| last_bucket = -1 | ||
|
|
||
| for idx, image in enumerate(folder_images, start=1): | ||
| image_path = image["path"] | ||
| image_id = image["id"] | ||
|
|
||
| # Step 1: Get classes | ||
| classes = object_classifier.get_classes(image_path) | ||
|
|
||
| # Step 2: Insert class-image pairs if classes were detected | ||
| if classes: | ||
| image_class_pairs = [(image_id, class_id) for class_id in classes] | ||
| logger.debug(f"Image-class pairs: {image_class_pairs}") | ||
| db_insert_image_classes_batch(image_class_pairs) | ||
|
|
||
| # Step 3: Detect faces if "person" class is present | ||
| # (assuming class id 0 denotes "person") | ||
| if classes and 0 in classes and 0 < classes.count(0) < 7: | ||
| face_detector.detect_faces(image_id, image_path) | ||
|
|
||
| # Step 4: Update the image status in the database | ||
| db_update_image_tagged_status(image_id, True) | ||
|
|
||
| percentage = (idx / total) * 100 | ||
| bucket = int(percentage // 2) | ||
|
|
||
| if bucket != last_bucket or idx == total: | ||
| publish_progress_from_thread( | ||
| { | ||
| "job_id": str(folder_id), | ||
| "processed": idx, | ||
| "total": total, | ||
| "percent": round(percentage, 2), | ||
| "status": "running", | ||
| } | ||
| ) | ||
| last_bucket = bucket | ||
|
|
||
| # Insert the pairs into the database | ||
| db_insert_image_classes_batch(image_class_pairs) | ||
| # Step 5: Mark folder tagging as completed after processing all images | ||
| from app.database.folders import db_update_folder_tagging_completed | ||
|
|
||
| # Step 3: Detect faces if "person" class is present | ||
| if classes and 0 in classes and 0 < classes.count(0) < 7: | ||
| face_detector.detect_faces(image_id, image_path) | ||
| db_update_folder_tagging_completed(folder_id, True) | ||
| logger.info(f"Folder {folder_id} tagging completed") | ||
| # Publish final done event after database update | ||
| publish_progress_from_thread( | ||
| { | ||
| "job_id": str(folder_id), | ||
| "processed": total, | ||
| "total": total, | ||
| "percent": 100.0, | ||
| "status": "done", | ||
| } | ||
| ) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+165
to
+176
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add exception handling to prevent one folder's failure from blocking others. If Apply this diff to continue processing remaining folders even if one fails: - # Step 5: Mark folder tagging as completed after processing all images
- from app.database.folders import db_update_folder_tagging_completed
-
- db_update_folder_tagging_completed(folder_id, True)
- logger.info(f"Folder {folder_id} tagging completed")
- # Publish final done event after database update
- publish_progress_from_thread(
- {
- "job_id": str(folder_id),
- "processed": total,
- "total": total,
- "percent": 100.0,
- "status": "done",
- }
- )
+ # Step 5: Mark folder tagging as completed after processing all images
+ from app.database.folders import db_update_folder_tagging_completed
+
+ try:
+ db_update_folder_tagging_completed(folder_id, True)
+ logger.info(f"Folder {folder_id} tagging completed")
+ # Publish final done event after database update
+ publish_progress_from_thread(
+ {
+ "job_id": str(folder_id),
+ "processed": total,
+ "total": total,
+ "percent": 100.0,
+ "status": "done",
+ }
+ )
+ except Exception as e:
+ logger.error(f"Failed to mark folder {folder_id} as completed: {e}")
+ # Publish error status so frontend knows this folder failed
+ publish_progress_from_thread(
+ {
+ "job_id": str(folder_id),
+ "processed": total,
+ "total": total,
+ "percent": 100.0,
+ "status": "error",
+ }
+ )
🤖 Prompt for AI Agents |
||
|
|
||
| # Step 4: Update the image status in the database | ||
| db_update_image_tagged_status(image_id, True) | ||
| finally: | ||
| # Ensure resources are cleaned up | ||
| object_classifier.close() | ||
| face_detector.close() | ||
| try: | ||
| object_classifier.close() | ||
| except Exception: | ||
| logger.exception("Error closing object_classifier") | ||
| try: | ||
| face_detector.close() | ||
| except Exception: | ||
| logger.exception("Error closing face_detector") | ||
|
|
||
|
|
||
| def image_util_prepare_image_records( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.