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__)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -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<HTMLDivElement>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
||||
const { data: events } = useSWR<Event[]>(`events?camera=${camera}&limit=50`, {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
const { data: events } = useSWR<Event[]>(
|
||||
`events?camera=${camera}&limit=100`,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
},
|
||||
);
|
||||
const apiHost = useApiHost();
|
||||
|
||||
const images = useMemo(() => {
|
||||
@ -69,7 +77,7 @@ export default function ImagePicker({
|
||||
}}
|
||||
>
|
||||
<DialogTrigger asChild>
|
||||
{!selectedImageId || !selectedImage ? (
|
||||
{!selectedImageId ? (
|
||||
<Button
|
||||
className="mt-2 w-full text-muted-foreground"
|
||||
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="flex flex-row items-center gap-2">
|
||||
<img
|
||||
src={`${apiHost}api/events/${selectedImage.id}/thumbnail.webp`}
|
||||
alt={selectedImage.label}
|
||||
src={
|
||||
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"
|
||||
/>
|
||||
<div className="text-sm">
|
||||
{selectedImage.label}
|
||||
{selectedImage.sub_label
|
||||
<div className="text-sm smart-capitalize">
|
||||
{selectedImage?.label || selectedImageId}
|
||||
{selectedImage?.sub_label
|
||||
? ` (${selectedImage.sub_label})`
|
||||
: ""}
|
||||
</div>
|
||||
@ -104,6 +116,9 @@ export default function ImagePicker({
|
||||
</div>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
<DialogTitle className="sr-only">
|
||||
{t("imagePicker.selectImage")}
|
||||
</DialogTitle>
|
||||
<DialogContent
|
||||
className={cn(
|
||||
"scrollbar-container overflow-y-auto",
|
||||
|
||||
@ -358,14 +358,21 @@ export default function TriggerView({
|
||||
<TableCell>
|
||||
<Badge
|
||||
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}`)}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{trigger.type === "image"
|
||||
{trigger.type === "thumbnail"
|
||||
? trigger.data
|
||||
: trigger.data.length > 30
|
||||
? `${trigger.data.substring(0, 30)}...`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user