diff --git a/frigate/api/classification.py b/frigate/api/classification.py index 3df55551e..32a466a03 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -970,6 +970,17 @@ def categorize_classification_image(request: Request, name: str, body: dict = No snapshot = get_event_snapshot(event) + if snapshot is None: + return JSONResponse( + content=( + { + "success": False, + "message": f"Failed to read snapshot for event {event_id}.", + } + ), + status_code=500, + ) + # Get object bounding box for the first detection if not event.data.get("attributes") or len(event.data["attributes"]) == 0: return JSONResponse( @@ -987,19 +998,7 @@ def categorize_classification_image(request: Request, name: str, body: dict = No try: # Extract the crop from the snapshot - detect_config: DetectConfig = config.cameras[event.camera].detect - frame = cv2.imread(snapshot) - - if frame is None: - return JSONResponse( - content=( - { - "success": False, - "message": f"Failed to read snapshot for event {event_id}.", - } - ), - status_code=500, - ) + frame = snapshot height, width = frame.shape[:2] diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 411ecb672..039eb5ddf 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -410,73 +410,53 @@ export default function FaceLibrary() { { - // Batch train all selected faces - let successCount = 0; - let failCount = 0; - const totalCount = selectedFaces.length; - - selectedFaces.forEach((filename, index) => { + const requests = selectedFaces.map((filename) => axios .post(`/faces/train/${name}/classify`, { training_file: filename, }) - .then((resp) => { - if (resp.status == 200) { - successCount++; - } else { - failCount++; - } + .then(() => true) + .catch(() => false), + ); - // Show final toast after all requests complete - if (index === totalCount - 1) { - if (successCount === totalCount) { - toast.success( - t("toast.success.batchTrainedFaces", { - count: successCount, - }), - { - position: "top-center", - }, - ); - } else if (successCount > 0) { - toast.warning( - t("toast.warning.partialBatchTrained", { - success: successCount, - total: totalCount, - }), - { - position: "top-center", - }, - ); - } else { - toast.error( - t("toast.error.batchTrainFailed", { - count: totalCount, - }), - { - position: "top-center", - }, - ); - } - setSelectedFaces([]); - refreshFaces(); - } - }) - .catch(() => { - failCount++; - if (index === totalCount - 1) { - toast.error( - t("toast.error.batchTrainFailed", { - count: totalCount, - }), - { - position: "top-center", - }, - ); - setSelectedFaces([]); - refreshFaces(); - } - }); + Promise.allSettled(requests).then((results) => { + const successCount = results.filter( + (result) => result.status === "fulfilled" && result.value, + ).length; + const totalCount = results.length; + + if (successCount === totalCount) { + toast.success( + t("toast.success.batchTrainedFaces", { + count: successCount, + }), + { + position: "top-center", + }, + ); + } else if (successCount > 0) { + toast.warning( + t("toast.warning.partialBatchTrained", { + success: successCount, + total: totalCount, + }), + { + position: "top-center", + }, + ); + } else { + toast.error( + t("toast.error.batchTrainFailed", { + count: totalCount, + }), + { + position: "top-center", + }, + ); + } + + setSelectedFaces([]); + refreshFaces(); }); }} > diff --git a/web/src/views/classification/ModelTrainingView.tsx b/web/src/views/classification/ModelTrainingView.tsx index 296bdb395..464807475 100644 --- a/web/src/views/classification/ModelTrainingView.tsx +++ b/web/src/views/classification/ModelTrainingView.tsx @@ -460,79 +460,62 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) { {pageToggle === "train" && ( { - // Batch categorize all selected images - let successCount = 0; - let failCount = 0; - const totalCount = selectedImages.length; - - selectedImages.forEach((filename, index) => { + const requests = selectedImages.map((filename) => axios - .post(`/classification/${model.name}/dataset/categorize`, { - category, - training_file: filename, - }) - .then((resp) => { - if (resp.status == 200) { - successCount++; - } else { - failCount++; - } + .post( + `/classification/${model.name}/dataset/categorize`, + { + category, + training_file: filename, + }, + ) + .then(() => true) + .catch(() => false), + ); - // Show final toast after all requests complete - if (index === totalCount - 1) { - if (successCount === totalCount) { - toast.success( - t("toast.success.batchCategorized", { - count: successCount, - }), - { - position: "top-center", - }, - ); - } else if (successCount > 0) { - toast.warning( - t("toast.warning.partialBatchCategorized", { - success: successCount, - total: totalCount, - }), - { - position: "top-center", - }, - ); - } else { - toast.error( - t("toast.error.batchCategorizeFailed", { - count: totalCount, - }), - { - position: "top-center", - }, - ); - } - setSelectedImages([]); - refreshAll(); - } - }) - .catch(() => { - failCount++; - if (index === totalCount - 1) { - toast.error( - t("toast.error.batchCategorizeFailed", { - count: totalCount, - }), - { - position: "top-center", - }, - ); - setSelectedImages([]); - refreshAll(); - } - }); + Promise.allSettled(requests).then((results) => { + const successCount = results.filter( + (result) => result.status === "fulfilled" && result.value, + ).length; + const totalCount = results.length; + + if (successCount === totalCount) { + toast.success( + t("toast.success.batchCategorized", { + count: successCount, + }), + { + position: "top-center", + }, + ); + } else if (successCount > 0) { + toast.warning( + t("toast.warning.partialBatchCategorized", { + success: successCount, + total: totalCount, + }), + { + position: "top-center", + }, + ); + } else { + toast.error( + t("toast.error.batchCategorizeFailed", { + count: totalCount, + }), + { + position: "top-center", + }, + ); + } + + setSelectedImages([]); + refreshAll(); }); }} >