From 3006d9fcebf09bf5c830a2f54adb09b638706a21 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 1 Jul 2025 17:17:08 -0500 Subject: [PATCH] clean up logs and use saved thumbnail on frontend --- .../data_processing/post/semantic_trigger.py | 80 +++---------------- web/src/components/overlay/ImagePicker.tsx | 35 +++++--- web/src/views/settings/TriggerView.tsx | 11 ++- 3 files changed, 47 insertions(+), 79 deletions(-) diff --git a/frigate/data_processing/post/semantic_trigger.py b/frigate/data_processing/post/semantic_trigger.py index f6e83ce36..3ed0a9029 100644 --- a/frigate/data_processing/post/semantic_trigger.py +++ b/frigate/data_processing/post/semantic_trigger.py @@ -25,7 +25,7 @@ from ..types import DataProcessorMetrics logger = logging.getLogger(__name__) -WRITE_DEBUG_IMAGES = True +WRITE_DEBUG_IMAGES = False class SemanticTriggerProcessor(PostProcessorApi): @@ -61,11 +61,12 @@ class SemanticTriggerProcessor(PostProcessorApi): event_id = data["event_id"] camera = data["camera"] process_type = data["type"] - logger.info( - f"semantic trigger event_id: {event_id}, type: {process_type}, camera: {camera}" - ) - # TODO: check if triggers exist for this camera, bail if none + if self.config.cameras[camera].semantic_search.triggers is None: + logger.debug( + f"No semantic triggers configured for camera: {camera}, skipping processing." + ) + return # Get embeddings based on type thumbnail_embedding = None @@ -95,7 +96,7 @@ class SemanticTriggerProcessor(PostProcessorApi): # Skip processing if we don't have any embeddings if thumbnail_embedding is None and description_embedding is None: - logger.warning(f"No embeddings found for event_id: {event_id}") + logger.debug(f"No embeddings found for event id: {event_id}") return triggers = ( @@ -113,7 +114,9 @@ class SemanticTriggerProcessor(PostProcessorApi): ) for trigger in triggers: - logger.debug(f"Processing trigger: {trigger['camera']}_{trigger['name']}") + logger.debug( + f"Processing trigger for {trigger['camera']}: {trigger['name']}" + ) trigger_embedding = np.frombuffer(trigger["embedding"], dtype=np.float32) @@ -141,8 +144,8 @@ class SemanticTriggerProcessor(PostProcessorApi): similarity = 1 - normalized_distance logger.debug( - f"Trigger for {trigger['data'] if trigger['type'] == 'text' or trigger['type'] == 'description' else 'image'} " - f"(camera: {trigger['camera']}): normalized: {normalized_distance:.4f}, " + f"Trigger: {trigger['data'] if trigger['type'] == 'text' or trigger['type'] == 'description' else 'image'} " + f"(camera: {trigger['camera']}): normalized distance: {normalized_distance:.4f}, " f"similarity: {similarity:.4f}, threshold: {trigger['threshold']}" ) @@ -151,6 +154,7 @@ class SemanticTriggerProcessor(PostProcessorApi): logger.info( f"Trigger '{trigger['name']}' activated with similarity {similarity:.4f}" ) + # TODO: handle actions for the trigger if WRITE_DEBUG_IMAGES: try: @@ -185,64 +189,6 @@ class SemanticTriggerProcessor(PostProcessorApi): thumbnail, ) - if False: - if type == "image": - sql_query = """ - SELECT - id, - distance - FROM vec_thumbnails - WHERE thumbnail_embedding MATCH ? - AND k = 100 - """ - elif type == "text": - sql_query = """ - SELECT - id, - distance - FROM vec_descriptions - WHERE description_embedding MATCH ? - AND k = 100 - """ - - # Add the IN clause if event_ids is provided and not empty - # this is the only filter supported by sqlite-vec as of 0.1.3 - # but it seems to be broken in this version - # if event_id: - # sql_query += " AND id IN ({})".format(",".join("?" * len(event_id))) - - # order by distance DESC is not implemented in this version of sqlite-vec - # when it's implemented, we can use cosine similarity - sql_query += " ORDER BY distance" - - parameters = [ - trigger_embedding - ] # + event_ids if event_ids else [query_embedding] - - results = self.db.execute_sql(sql_query, parameters).fetchall() - # Extract raw distances - raw_distances = [r[1] for r in results] - - # Normalize - normalized_distances = self.thumb_stats.normalize( - raw_distances, save_stats=False - ) - - # Pair with IDs - normalized_results = list( - zip([r[0] for r in results], normalized_distances) - ) - - logger.info( - f"Semantic trigger results for event_id {event_id}: {len(normalized_results)} matches found." - ) - - # Optional: Log top few for inspection - for thumb_id, norm_score in normalized_results[:5]: - logger.debug( - f"Normalized match: {thumb_id} → z-score: {1 - norm_score:.4f}" - ) - def handle_request(self, topic, request_data): return None diff --git a/web/src/components/overlay/ImagePicker.tsx b/web/src/components/overlay/ImagePicker.tsx index 20467c16d..408338d0d 100644 --- a/web/src/components/overlay/ImagePicker.tsx +++ b/web/src/components/overlay/ImagePicker.tsx @@ -1,7 +1,12 @@ import { useCallback, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import useSWR from "swr"; -import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import { + Dialog, + DialogContent, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; import { IoClose } from "react-icons/io5"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; @@ -27,9 +32,12 @@ export default function ImagePicker({ const containerRef = useRef(null); const [searchTerm, setSearchTerm] = useState(""); - const { data: events } = useSWR(`events?camera=${camera}&limit=50`, { - revalidateOnFocus: false, - }); + const { data: events } = useSWR( + `events?camera=${camera}&limit=100`, + { + revalidateOnFocus: false, + }, + ); const apiHost = useApiHost(); const images = useMemo(() => { @@ -69,7 +77,7 @@ export default function ImagePicker({ }} > - {!selectedImageId || !selectedImage ? ( + {!selectedImageId ? (