From cae11cbb86ec9115c8ecde7b695d752895aec2ea Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:45:19 -0500 Subject: [PATCH] Add ability to filter based on search type (#13641) --- frigate/api/event.py | 51 ++++++------ .../components/filter/SearchFilterGroup.tsx | 79 ++++++++++++++++++- web/src/pages/Search.tsx | 4 +- web/src/types/search.ts | 3 +- web/src/views/search/SearchView.tsx | 4 +- 5 files changed, 113 insertions(+), 28 deletions(-) diff --git a/frigate/api/event.py b/frigate/api/event.py index aeea871c4..efe9412df 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -274,7 +274,7 @@ def event_ids(): @EventBp.route("/events/search") def events_search(): query = request.args.get("query", type=str) - search_type = request.args.get("search_type", "text", type=str) + search_type = request.args.get("search_type", "thumbnail,description", type=str) include_thumbnails = request.args.get("include_thumbnails", default=1, type=int) limit = request.args.get("limit", 50, type=int) @@ -358,7 +358,7 @@ def events_search(): thumb_ids = {} desc_ids = {} - if search_type == "thumbnail": + if search_type == "similarity": # Grab the ids of events that match the thumbnail image embeddings try: search_event: Event = Event.get(Event.id == query) @@ -386,29 +386,34 @@ def events_search(): ) ) else: - thumb_result = context.embeddings.thumbnail.query( - query_texts=[query], - n_results=limit, - where=where, - ) - # Do a rudimentary normalization of the difference in distances returned by CLIP and MiniLM. - thumb_ids = dict( - zip( - thumb_result["ids"][0], - context.thumb_stats.normalize(thumb_result["distances"][0]), + search_types = search_type.split(",") + + if "thumbnail" in search_types: + thumb_result = context.embeddings.thumbnail.query( + query_texts=[query], + n_results=limit, + where=where, ) - ) - desc_result = context.embeddings.description.query( - query_texts=[query], - n_results=limit, - where=where, - ) - desc_ids = dict( - zip( - desc_result["ids"][0], - context.desc_stats.normalize(desc_result["distances"][0]), + # Do a rudimentary normalization of the difference in distances returned by CLIP and MiniLM. + thumb_ids = dict( + zip( + thumb_result["ids"][0], + context.thumb_stats.normalize(thumb_result["distances"][0]), + ) + ) + + if "description" in search_types: + desc_result = context.embeddings.description.query( + query_texts=[query], + n_results=limit, + where=where, + ) + desc_ids = dict( + zip( + desc_result["ids"][0], + context.desc_stats.normalize(desc_result["distances"][0]), + ) ) - ) results = {} for event_id in thumb_ids.keys() | desc_ids: diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index e27a65a47..8fabafa69 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -17,7 +17,7 @@ import FilterSwitch from "./FilterSwitch"; import { FilterList } from "@/types/filter"; import { CalendarRangeFilterButton } from "./CalendarFilterButton"; import { CamerasFilterButton } from "./CamerasFilterButton"; -import { SearchFilter } from "@/types/search"; +import { SearchFilter, SearchSource } from "@/types/search"; import { DateRange } from "react-day-picker"; const SEARCH_FILTERS = ["cameras", "date", "general"] as const; @@ -103,6 +103,7 @@ export default function SearchFilterGroup({ cameras: Object.keys(config?.cameras || {}), labels: Object.values(allLabels || {}), zones: Object.values(allZones || {}), + search_type: ["thumbnail", "description"] as SearchSource[], }), [config, allLabels, allZones], ); @@ -178,12 +179,18 @@ export default function SearchFilterGroup({ selectedLabels={filter?.labels} allZones={filterValues.zones} selectedZones={filter?.zones} + selectedSearchSources={ + filter?.search_type ?? ["thumbnail", "description"] + } updateLabelFilter={(newLabels) => { onUpdateFilter({ ...filter, labels: newLabels }); }} updateZoneFilter={(newZones) => onUpdateFilter({ ...filter, zones: newZones }) } + updateSearchSourceFilter={(newSearchSource) => + onUpdateFilter({ ...filter, search_type: newSearchSource }) + } /> )} {isMobile && mobileSettingsFeatures.length > 0 && ( @@ -211,16 +218,20 @@ type GeneralFilterButtonProps = { selectedLabels: string[] | undefined; allZones: string[]; selectedZones?: string[]; + selectedSearchSources: SearchSource[]; updateLabelFilter: (labels: string[] | undefined) => void; updateZoneFilter: (zones: string[] | undefined) => void; + updateSearchSourceFilter: (sources: SearchSource[]) => void; }; function GeneralFilterButton({ allLabels, selectedLabels, allZones, selectedZones, + selectedSearchSources, updateLabelFilter, updateZoneFilter, + updateSearchSourceFilter, }: GeneralFilterButtonProps) { const [open, setOpen] = useState(false); const [currentLabels, setCurrentLabels] = useState( @@ -229,6 +240,9 @@ function GeneralFilterButton({ const [currentZones, setCurrentZones] = useState( selectedZones, ); + const [currentSearchSources, setCurrentSearchSources] = useState< + SearchSource[] + >(selectedSearchSources); const trigger = (