mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-27 17:17:40 +03:00
clean up logs and use saved thumbnail on frontend
This commit is contained in:
parent
8226429baf
commit
3006d9fceb
@ -25,7 +25,7 @@ from ..types import DataProcessorMetrics
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
WRITE_DEBUG_IMAGES = True
|
WRITE_DEBUG_IMAGES = False
|
||||||
|
|
||||||
|
|
||||||
class SemanticTriggerProcessor(PostProcessorApi):
|
class SemanticTriggerProcessor(PostProcessorApi):
|
||||||
@ -61,11 +61,12 @@ class SemanticTriggerProcessor(PostProcessorApi):
|
|||||||
event_id = data["event_id"]
|
event_id = data["event_id"]
|
||||||
camera = data["camera"]
|
camera = data["camera"]
|
||||||
process_type = data["type"]
|
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
|
# Get embeddings based on type
|
||||||
thumbnail_embedding = None
|
thumbnail_embedding = None
|
||||||
@ -95,7 +96,7 @@ class SemanticTriggerProcessor(PostProcessorApi):
|
|||||||
|
|
||||||
# Skip processing if we don't have any embeddings
|
# Skip processing if we don't have any embeddings
|
||||||
if thumbnail_embedding is None and description_embedding is None:
|
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
|
return
|
||||||
|
|
||||||
triggers = (
|
triggers = (
|
||||||
@ -113,7 +114,9 @@ class SemanticTriggerProcessor(PostProcessorApi):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for trigger in triggers:
|
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)
|
trigger_embedding = np.frombuffer(trigger["embedding"], dtype=np.float32)
|
||||||
|
|
||||||
@ -141,8 +144,8 @@ class SemanticTriggerProcessor(PostProcessorApi):
|
|||||||
similarity = 1 - normalized_distance
|
similarity = 1 - normalized_distance
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Trigger for {trigger['data'] if trigger['type'] == 'text' or trigger['type'] == 'description' else 'image'} "
|
f"Trigger: {trigger['data'] if trigger['type'] == 'text' or trigger['type'] == 'description' else 'image'} "
|
||||||
f"(camera: {trigger['camera']}): normalized: {normalized_distance:.4f}, "
|
f"(camera: {trigger['camera']}): normalized distance: {normalized_distance:.4f}, "
|
||||||
f"similarity: {similarity:.4f}, threshold: {trigger['threshold']}"
|
f"similarity: {similarity:.4f}, threshold: {trigger['threshold']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -151,6 +154,7 @@ class SemanticTriggerProcessor(PostProcessorApi):
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"Trigger '{trigger['name']}' activated with similarity {similarity:.4f}"
|
f"Trigger '{trigger['name']}' activated with similarity {similarity:.4f}"
|
||||||
)
|
)
|
||||||
|
# TODO: handle actions for the trigger
|
||||||
|
|
||||||
if WRITE_DEBUG_IMAGES:
|
if WRITE_DEBUG_IMAGES:
|
||||||
try:
|
try:
|
||||||
@ -185,64 +189,6 @@ class SemanticTriggerProcessor(PostProcessorApi):
|
|||||||
thumbnail,
|
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):
|
def handle_request(self, topic, request_data):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useSWR from "swr";
|
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 { IoClose } from "react-icons/io5";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -27,9 +32,12 @@ export default function ImagePicker({
|
|||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
|
||||||
const { data: events } = useSWR<Event[]>(`events?camera=${camera}&limit=50`, {
|
const { data: events } = useSWR<Event[]>(
|
||||||
revalidateOnFocus: false,
|
`events?camera=${camera}&limit=100`,
|
||||||
});
|
{
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
|
|
||||||
const images = useMemo(() => {
|
const images = useMemo(() => {
|
||||||
@ -69,7 +77,7 @@ export default function ImagePicker({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{!selectedImageId || !selectedImage ? (
|
{!selectedImageId ? (
|
||||||
<Button
|
<Button
|
||||||
className="mt-2 w-full text-muted-foreground"
|
className="mt-2 w-full text-muted-foreground"
|
||||||
aria-label={t("imagePicker.selectImage")}
|
aria-label={t("imagePicker.selectImage")}
|
||||||
@ -81,13 +89,17 @@ export default function ImagePicker({
|
|||||||
<div className="my-3 flex w-full flex-row items-center justify-between gap-2">
|
<div className="my-3 flex w-full flex-row items-center justify-between gap-2">
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
<img
|
<img
|
||||||
src={`${apiHost}api/events/${selectedImage.id}/thumbnail.webp`}
|
src={
|
||||||
alt={selectedImage.label}
|
selectedImage
|
||||||
|
? `${apiHost}api/events/${selectedImage.id}/thumbnail.webp`
|
||||||
|
: `${apiHost}clips/triggers/${camera}/${selectedImageId}.webp`
|
||||||
|
}
|
||||||
|
alt={selectedImage?.label || "Selected image"}
|
||||||
className="h-8 w-8 rounded object-cover"
|
className="h-8 w-8 rounded object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">
|
<div className="text-sm smart-capitalize">
|
||||||
{selectedImage.label}
|
{selectedImage?.label || selectedImageId}
|
||||||
{selectedImage.sub_label
|
{selectedImage?.sub_label
|
||||||
? ` (${selectedImage.sub_label})`
|
? ` (${selectedImage.sub_label})`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@ -104,6 +116,9 @@ export default function ImagePicker({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
<DialogTitle className="sr-only">
|
||||||
|
{t("imagePicker.selectImage")}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"scrollbar-container overflow-y-auto",
|
"scrollbar-container overflow-y-auto",
|
||||||
|
|||||||
@ -358,14 +358,21 @@ export default function TriggerView({
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
trigger.type === "image" ? "default" : "outline"
|
trigger.type === "thumbnail"
|
||||||
|
? "default"
|
||||||
|
: "outline"
|
||||||
|
}
|
||||||
|
className={
|
||||||
|
trigger.type === "thumbnail"
|
||||||
|
? "bg-primary/20 text-primary hover:bg-primary/30"
|
||||||
|
: ""
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t(`triggers.type.${trigger.type}`)}
|
{t(`triggers.type.${trigger.type}`)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{trigger.type === "image"
|
{trigger.type === "thumbnail"
|
||||||
? trigger.data
|
? trigger.data
|
||||||
: trigger.data.length > 30
|
: trigger.data.length > 30
|
||||||
? `${trigger.data.substring(0, 30)}...`
|
? `${trigger.data.substring(0, 30)}...`
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user