From ea987850972e9c0cacf143c2bc7360bef873d392 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 13 Apr 2025 12:06:59 -0600 Subject: [PATCH] UI improvements (#17684) * Add sub label to explore chip * Add bird classification to settings * Remove score * Cleanup --- web/public/locales/en/views/settings.json | 4 ++ web/src/components/card/SearchThumbnail.tsx | 28 +++++++++- web/src/types/frigateConfig.ts | 7 +++ .../settings/ClassificationSettingsView.tsx | 56 ++++++++++++++++++- 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index f6c5b2e99..9f9ced4d8 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -83,6 +83,10 @@ }, "classification": { "title": "Classification Settings", + "birdClassification": { + "title": "Bird Classification", + "desc": "Bird classification identifies known birds using a quantized Tensorflow model. When a known bird is recognized, its common name will be added as a sub_label. This information is included in the UI, filters, as well as in notifications." + }, "semanticSearch": { "title": "Semantic Search", "desc": "Semantic Search in Frigate allows you to find tracked objects within your review items using either the image itself, a user-defined text description, or an automatically generated one.", diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index a2c5cd37f..e58977f74 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -69,6 +69,30 @@ export default function SearchThumbnail({ return `${searchResult.label}-verified`; }, [config, hasRecognizedPlate, searchResult]); + const objectDetail = useMemo(() => { + if (!config) { + return undefined; + } + + if (!searchResult.sub_label) { + if (hasRecognizedPlate) { + return `(${searchResult.data.recognized_license_plate})`; + } + + return undefined; + } + + if ( + config.model.attributes_map[searchResult.label]?.includes( + searchResult.sub_label, + ) + ) { + return ""; + } + + return `(${searchResult.sub_label})`; + }, [config, hasRecognizedPlate, searchResult]); + return (
onClick(searchResult, false, true)} > {getIconForLabel(objectLabel, "size-3 text-white")} @@ -116,7 +140,7 @@ export default function SearchThumbnail({ searchResult.data.top_score ?? searchResult.top_score) * 100, )} - % + % {objectDetail}
diff --git a/web/src/types/frigateConfig.ts b/web/src/types/frigateConfig.ts index d66d5edcb..bf49746a2 100644 --- a/web/src/types/frigateConfig.ts +++ b/web/src/types/frigateConfig.ts @@ -298,6 +298,13 @@ export interface FrigateConfig { [cameraName: string]: CameraConfig; }; + classification: { + bird: { + enabled: boolean; + threshold: number; + }; + }; + database: { path: string; }; diff --git a/web/src/views/settings/ClassificationSettingsView.tsx b/web/src/views/settings/ClassificationSettingsView.tsx index d12008f8f..90dfb98f2 100644 --- a/web/src/views/settings/ClassificationSettingsView.tsx +++ b/web/src/views/settings/ClassificationSettingsView.tsx @@ -45,6 +45,9 @@ type ClassificationSettings = { lpr: { enabled?: boolean; }; + bird: { + enabled?: boolean; + }; }; type ClassificationSettingsViewProps = { @@ -67,6 +70,7 @@ export default function ClassificationSettingsView({ search: { enabled: undefined, model_size: undefined }, face: { enabled: undefined, model_size: undefined }, lpr: { enabled: undefined }, + bird: { enabled: undefined }, }); const [origSearchSettings, setOrigSearchSettings] = @@ -74,6 +78,7 @@ export default function ClassificationSettingsView({ search: { enabled: undefined, model_size: undefined }, face: { enabled: undefined, model_size: undefined }, lpr: { enabled: undefined }, + bird: { enabled: undefined }, }); useEffect(() => { @@ -89,6 +94,9 @@ export default function ClassificationSettingsView({ model_size: config.face_recognition.model_size, }, lpr: { enabled: config.lpr.enabled }, + bird: { + enabled: config.classification.bird.enabled, + }, }); } @@ -102,6 +110,7 @@ export default function ClassificationSettingsView({ model_size: config.face_recognition.model_size, }, lpr: { enabled: config.lpr.enabled }, + bird: { enabled: config.classification.bird.enabled }, }); } // we know that these deps are correct @@ -115,6 +124,7 @@ export default function ClassificationSettingsView({ search: { ...prevConfig.search, ...newConfig.search }, face: { ...prevConfig.face, ...newConfig.face }, lpr: { ...prevConfig.lpr, ...newConfig.lpr }, + bird: { ...prevConfig.bird, ...newConfig.bird }, })); setUnsavedChanges(true); setChangedValue(true); @@ -125,7 +135,7 @@ export default function ClassificationSettingsView({ axios .put( - `config/set?semantic_search.enabled=${classificationSettings.search.enabled ? "True" : "False"}&semantic_search.model_size=${classificationSettings.search.model_size}&face_recognition.enabled=${classificationSettings.face.enabled ? "True" : "False"}&face_recognition.model_size=${classificationSettings.face.model_size}&lpr.enabled=${classificationSettings.lpr.enabled ? "True" : "False"}`, + `config/set?semantic_search.enabled=${classificationSettings.search.enabled ? "True" : "False"}&semantic_search.model_size=${classificationSettings.search.model_size}&face_recognition.enabled=${classificationSettings.face.enabled ? "True" : "False"}&face_recognition.model_size=${classificationSettings.face.model_size}&lpr.enabled=${classificationSettings.lpr.enabled ? "True" : "False"}&classification.bird.enabled=${classificationSettings.bird.enabled ? "True" : "False"}`, { requires_restart: 0 }, ) .then((res) => { @@ -526,6 +536,50 @@ export default function ClassificationSettingsView({ + + {t("classification.birdClassification.title")} + +
+
+

{t("classification.birdClassification.desc")}

+ +
+ + {t("classification.semanticSearch.readTheDocumentation")} + + +
+
+
+ +
+
+ { + handleClassificationConfigChange({ + bird: { enabled: isChecked }, + }); + }} + /> +
+ +
+
+
+ + +