2024-10-07 22:30:45 +02:00
""" Maintain embeddings in SQLite-vec. """
2024-06-21 23:30:19 +02:00
import base64
import logging
2024-09-30 23:54:53 +02:00
import os
2024-06-21 23:30:19 +02:00
import threading
from multiprocessing . synchronize import Event as MpEvent
2024-12-23 15:05:34 +01:00
from pathlib import Path
2024-06-21 23:30:19 +02:00
from typing import Optional
import cv2
import numpy as np
2024-10-23 00:05:48 +02:00
import requests
2024-06-21 23:30:19 +02:00
from peewee import DoesNotExist
2024-10-07 22:30:45 +02:00
from playhouse . sqliteq import SqliteQueueDatabase
2024-06-21 23:30:19 +02:00
2024-10-10 17:42:24 +02:00
from frigate . comms . embeddings_updater import EmbeddingsRequestEnum , EmbeddingsResponder
2024-09-24 16:14:51 +02:00
from frigate . comms . event_metadata_updater import (
EventMetadataSubscriber ,
EventMetadataTypeEnum ,
)
2024-06-21 23:30:19 +02:00
from frigate . comms . events_updater import EventEndSubscriber , EventUpdateSubscriber
from frigate . comms . inter_process import InterProcessRequestor
from frigate . config import FrigateConfig
2024-10-23 00:05:48 +02:00
from frigate . const import CLIPS_DIR , FRIGATE_LOCALHOST , UPDATE_EVENT_DESCRIPTION
2024-06-21 23:30:19 +02:00
from frigate . events . types import EventTypeEnum
from frigate . genai import get_genai_client
from frigate . models import Event
2024-11-18 19:26:44 +01:00
from frigate . types import TrackedObjectUpdateTypesEnum
2024-10-10 17:42:24 +02:00
from frigate . util . builtin import serialize
2024-10-23 00:05:48 +02:00
from frigate . util . image import SharedMemoryFrameManager , area , calculate_region
2024-06-21 23:30:19 +02:00
2024-10-07 22:30:45 +02:00
from . embeddings import Embeddings
2024-06-21 23:30:19 +02:00
logger = logging . getLogger ( __name__ )
2024-10-23 17:03:18 +02:00
REQUIRED_FACES = 2
2024-10-20 22:14:51 +02:00
MAX_THUMBNAILS = 10
2024-06-21 23:30:19 +02:00
class EmbeddingMaintainer ( threading . Thread ) :
""" Handle embedding queue and post event updates. """
def __init__ (
self ,
2024-10-07 22:30:45 +02:00
db : SqliteQueueDatabase ,
2024-06-21 23:30:19 +02:00
config : FrigateConfig ,
stop_event : MpEvent ,
) - > None :
2024-10-10 23:37:43 +02:00
super ( ) . __init__ ( name = " embeddings_maintainer " )
2024-06-21 23:30:19 +02:00
self . config = config
2024-10-23 17:03:18 +02:00
self . embeddings = Embeddings ( config , db )
2024-10-10 23:37:43 +02:00
# Check if we need to re-index events
if config . semantic_search . reindex :
self . embeddings . reindex ( )
2024-06-21 23:30:19 +02:00
self . event_subscriber = EventUpdateSubscriber ( )
self . event_end_subscriber = EventEndSubscriber ( )
2024-09-24 16:14:51 +02:00
self . event_metadata_subscriber = EventMetadataSubscriber (
EventMetadataTypeEnum . regenerate_description
)
2024-10-10 17:42:24 +02:00
self . embeddings_responder = EmbeddingsResponder ( )
2024-06-21 23:30:19 +02:00
self . frame_manager = SharedMemoryFrameManager ( )
2024-10-23 00:05:48 +02:00
# set face recognition conditions
2024-10-23 17:03:18 +02:00
self . face_recognition_enabled = self . config . face_recognition . enabled
2024-10-23 00:05:48 +02:00
self . requires_face_detection = " face " not in self . config . model . all_attributes
2024-10-23 17:03:18 +02:00
self . detected_faces : dict [ str , float ] = { }
2024-10-23 00:05:48 +02:00
2024-06-21 23:30:19 +02:00
# create communication for updating event descriptions
self . requestor = InterProcessRequestor ( )
self . stop_event = stop_event
2024-10-23 00:05:48 +02:00
self . tracked_events : dict [ str , list [ any ] ] = { }
2024-11-09 14:48:53 +01:00
self . genai_client = get_genai_client ( config )
2024-06-21 23:30:19 +02:00
2024-10-23 21:50:58 +02:00
@property
def face_detector ( self ) - > cv2 . FaceDetectorYN :
# Lazily create the classifier.
if " face_detector " not in self . __dict__ :
self . __dict__ [ " face_detector " ] = cv2 . FaceDetectorYN . create (
" /config/model_cache/facenet/facedet.onnx " ,
config = " " ,
input_size = ( 320 , 320 ) ,
score_threshold = 0.8 ,
nms_threshold = 0.3 ,
)
return self . __dict__ [ " face_detector " ]
2024-06-21 23:30:19 +02:00
def run ( self ) - > None :
2024-10-07 22:30:45 +02:00
""" Maintain a SQLite-vec database for semantic search. """
2024-06-21 23:30:19 +02:00
while not self . stop_event . is_set ( ) :
2024-10-10 17:42:24 +02:00
self . _process_requests ( )
2024-06-21 23:30:19 +02:00
self . _process_updates ( )
self . _process_finalized ( )
2024-09-24 16:14:51 +02:00
self . _process_event_metadata ( )
2024-06-21 23:30:19 +02:00
self . event_subscriber . stop ( )
self . event_end_subscriber . stop ( )
2024-09-24 16:14:51 +02:00
self . event_metadata_subscriber . stop ( )
2024-10-10 17:42:24 +02:00
self . embeddings_responder . stop ( )
2024-06-21 23:30:19 +02:00
self . requestor . stop ( )
logger . info ( " Exiting embeddings maintenance... " )
2024-10-10 17:42:24 +02:00
def _process_requests ( self ) - > None :
""" Process embeddings requests """
2024-10-23 21:50:58 +02:00
def _handle_request ( topic : str , data : dict [ str , any ] ) - > str :
2024-10-10 23:37:43 +02:00
try :
if topic == EmbeddingsRequestEnum . embed_description . value :
return serialize (
2024-10-22 00:19:34 +02:00
self . embeddings . embed_description (
2024-10-10 23:37:43 +02:00
data [ " id " ] , data [ " description " ]
) ,
pack = False ,
)
elif topic == EmbeddingsRequestEnum . embed_thumbnail . value :
thumbnail = base64 . b64decode ( data [ " thumbnail " ] )
return serialize (
2024-10-22 00:19:34 +02:00
self . embeddings . embed_thumbnail ( data [ " id " ] , thumbnail ) ,
2024-10-10 23:37:43 +02:00
pack = False ,
)
elif topic == EmbeddingsRequestEnum . generate_search . value :
return serialize (
self . embeddings . text_embedding ( [ data ] ) [ 0 ] , pack = False
)
2024-10-23 00:05:48 +02:00
elif topic == EmbeddingsRequestEnum . register_face . value :
2024-10-23 21:50:58 +02:00
if data . get ( " cropped " ) :
self . embeddings . embed_face (
data [ " face_name " ] ,
base64 . b64decode ( data [ " image " ] ) ,
upsert = True ,
)
return True
else :
img = cv2 . imdecode (
np . frombuffer (
base64 . b64decode ( data [ " image " ] ) , dtype = np . uint8
) ,
cv2 . IMREAD_COLOR ,
)
face_box = self . _detect_face ( img )
if not face_box :
return False
face = img [ face_box [ 1 ] : face_box [ 3 ] , face_box [ 0 ] : face_box [ 2 ] ]
ret , webp = cv2 . imencode (
" .webp " , face , [ int ( cv2 . IMWRITE_WEBP_QUALITY ) , 100 ]
)
self . embeddings . embed_face (
data [ " face_name " ] , webp . tobytes ( ) , upsert = True
)
return False
2024-10-10 23:37:43 +02:00
except Exception as e :
logger . error ( f " Unable to handle embeddings request { e } " )
self . embeddings_responder . check_for_request ( _handle_request )
2024-10-10 17:42:24 +02:00
2024-06-21 23:30:19 +02:00
def _process_updates ( self ) - > None :
""" Process event updates """
2024-10-23 00:05:48 +02:00
update = self . event_subscriber . check_for_update ( timeout = 0.01 )
2024-06-21 23:30:19 +02:00
if update is None :
return
2024-11-19 19:20:04 +01:00
source_type , _ , camera , frame_name , data = update
2024-06-21 23:30:19 +02:00
if not camera or source_type != EventTypeEnum . tracked_object :
return
camera_config = self . config . cameras [ camera ]
2024-10-20 22:14:51 +02:00
2024-10-23 00:05:48 +02:00
# no need to process updated objects if face recognition and genai are disabled
if not camera_config . genai . enabled and not self . face_recognition_enabled :
return
2024-06-21 23:30:19 +02:00
# Create our own thumbnail based on the bounding box and the frame time
try :
2024-10-23 00:05:48 +02:00
yuv_frame = self . frame_manager . get ( frame_name , camera_config . frame_shape_yuv )
except FileNotFoundError :
pass
if yuv_frame is None :
logger . debug (
" Unable to process object update because frame is unavailable. "
2024-11-19 19:20:04 +01:00
)
2024-10-23 00:05:48 +02:00
return
2024-09-03 18:22:30 +02:00
2024-10-23 00:05:48 +02:00
if self . face_recognition_enabled :
self . _process_face ( data , yuv_frame )
2024-10-20 22:14:51 +02:00
2024-10-23 00:05:48 +02:00
# no need to save our own thumbnails if genai is not enabled
# or if the object has become stationary
if self . genai_client is not None and not data [ " stationary " ] :
if data [ " id " ] not in self . tracked_events :
self . tracked_events [ data [ " id " ] ] = [ ]
2024-10-20 22:14:51 +02:00
2024-10-23 00:05:48 +02:00
data [ " thumbnail " ] = self . _create_thumbnail ( yuv_frame , data [ " box " ] )
2024-10-20 22:14:51 +02:00
2024-10-23 00:05:48 +02:00
# Limit the number of thumbnails saved
if len ( self . tracked_events [ data [ " id " ] ] ) > = MAX_THUMBNAILS :
# Always keep the first thumbnail for the event
self . tracked_events [ data [ " id " ] ] . pop ( 1 )
self . tracked_events [ data [ " id " ] ] . append ( data )
self . frame_manager . close ( frame_name )
2024-06-21 23:30:19 +02:00
def _process_finalized ( self ) - > None :
""" Process the end of an event. """
while True :
2024-10-23 00:05:48 +02:00
ended = self . event_end_subscriber . check_for_update ( timeout = 0.01 )
2024-06-21 23:30:19 +02:00
if ended == None :
break
event_id , camera , updated_db = ended
camera_config = self . config . cameras [ camera ]
2024-10-23 17:03:18 +02:00
if event_id in self . detected_faces :
self . detected_faces . pop ( event_id )
2024-06-21 23:30:19 +02:00
if updated_db :
try :
event : Event = Event . get ( Event . id == event_id )
except DoesNotExist :
continue
# Skip the event if not an object
if event . data . get ( " type " ) != " object " :
continue
2024-10-07 22:30:45 +02:00
# Extract valid thumbnail
2024-06-21 23:30:19 +02:00
thumbnail = base64 . b64decode ( event . thumbnail )
# Embed the thumbnail
2024-10-07 22:30:45 +02:00
self . _embed_thumbnail ( event_id , thumbnail )
2024-06-21 23:30:19 +02:00
if (
camera_config . genai . enabled
and self . genai_client is not None
and event . data . get ( " description " ) is None
2024-09-25 17:42:39 +02:00
and (
2024-09-25 19:53:25 +02:00
not camera_config . genai . objects
2024-09-25 17:42:39 +02:00
or event . label in camera_config . genai . objects
)
and (
2024-09-25 19:53:25 +02:00
not camera_config . genai . required_zones
2024-09-25 17:42:39 +02:00
or set ( event . zones ) & set ( camera_config . genai . required_zones )
)
2024-06-21 23:30:19 +02:00
) :
2024-09-30 23:54:53 +02:00
if event . has_snapshot and camera_config . genai . use_snapshot :
with open (
os . path . join ( CLIPS_DIR , f " { event . camera } - { event . id } .jpg " ) ,
" rb " ,
) as image_file :
snapshot_image = image_file . read ( )
img = cv2 . imdecode (
np . frombuffer ( snapshot_image , dtype = np . int8 ) ,
cv2 . IMREAD_COLOR ,
)
# crop snapshot based on region before sending off to genai
height , width = img . shape [ : 2 ]
x1_rel , y1_rel , width_rel , height_rel = event . data [ " region " ]
x1 , y1 = int ( x1_rel * width ) , int ( y1_rel * height )
cropped_image = img [
y1 : y1 + int ( height_rel * height ) ,
x1 : x1 + int ( width_rel * width ) ,
]
_ , buffer = cv2 . imencode ( " .jpg " , cropped_image )
snapshot_image = buffer . tobytes ( )
2024-12-23 15:05:34 +01:00
num_thumbnails = len ( self . tracked_events . get ( event_id , [ ] ) )
2024-09-30 23:54:53 +02:00
embed_image = (
[ snapshot_image ]
if event . has_snapshot and camera_config . genai . use_snapshot
else (
2024-12-17 14:44:00 +01:00
[
data [ " thumbnail " ]
for data in self . tracked_events [ event_id ]
]
2024-12-23 15:05:34 +01:00
if num_thumbnails > 0
2024-09-30 23:54:53 +02:00
else [ thumbnail ]
)
)
2024-12-23 15:05:34 +01:00
if camera_config . genai . debug_save_thumbnails and num_thumbnails > 0 :
logger . debug (
f " Saving { num_thumbnails } thumbnails for event { event . id } "
)
Path (
os . path . join ( CLIPS_DIR , f " genai-requests/ { event . id } " )
) . mkdir ( parents = True , exist_ok = True )
for idx , data in enumerate ( self . tracked_events [ event_id ] , 1 ) :
jpg_bytes : bytes = data [ " thumbnail " ]
if jpg_bytes is None :
logger . warning (
f " Unable to save thumbnail { idx } for { event . id } . "
)
else :
with open (
os . path . join (
CLIPS_DIR ,
f " genai-requests/ { event . id } / { idx } .jpg " ,
) ,
" wb " ,
) as j :
j . write ( jpg_bytes )
2024-06-21 23:30:19 +02:00
# Generate the description. Call happens in a thread since it is network bound.
threading . Thread (
target = self . _embed_description ,
name = f " _embed_description_ { event . id } " ,
daemon = True ,
args = (
event ,
2024-09-30 23:54:53 +02:00
embed_image ,
2024-06-21 23:30:19 +02:00
) ,
) . start ( )
# Delete tracked events based on the event_id
if event_id in self . tracked_events :
del self . tracked_events [ event_id ]
2024-09-24 16:14:51 +02:00
def _process_event_metadata ( self ) :
# Check for regenerate description requests
2024-09-30 23:54:53 +02:00
( topic , event_id , source ) = self . event_metadata_subscriber . check_for_update (
2024-10-23 00:05:48 +02:00
timeout = 0.01
2024-09-30 23:54:53 +02:00
)
2024-09-24 16:14:51 +02:00
if topic is None :
return
if event_id :
2024-09-30 23:54:53 +02:00
self . handle_regenerate_description ( event_id , source )
2024-09-24 16:14:51 +02:00
2024-10-23 21:50:58 +02:00
def _search_face ( self , query_embedding : bytes ) - > list [ tuple [ str , float ] ] :
2024-10-23 00:05:48 +02:00
""" Search for the face most closely matching the embedding. """
2024-10-23 17:03:18 +02:00
sql_query = f """
2024-10-23 00:05:48 +02:00
SELECT
id ,
distance
FROM vec_faces
WHERE face_embedding MATCH ?
2024-10-23 17:03:18 +02:00
AND k = { REQUIRED_FACES } ORDER BY distance
2024-10-23 00:05:48 +02:00
"""
return self . embeddings . db . execute_sql ( sql_query , [ query_embedding ] ) . fetchall ( )
2024-10-23 21:50:58 +02:00
def _detect_face ( self , input : np . ndarray ) - > tuple [ int , int , int , int ] :
""" Detect faces in input image. """
self . face_detector . setInputSize ( ( input . shape [ 1 ] , input . shape [ 0 ] ) )
faces = self . face_detector . detect ( input )
if faces [ 1 ] is None :
return None
face = None
for _ , potential_face in enumerate ( faces [ 1 ] ) :
raw_bbox = potential_face [ 0 : 4 ] . astype ( np . uint16 )
x : int = max ( raw_bbox [ 0 ] , 0 )
y : int = max ( raw_bbox [ 1 ] , 0 )
w : int = raw_bbox [ 2 ]
h : int = raw_bbox [ 3 ]
bbox = ( x , y , x + w , y + h )
if face is None or area ( bbox ) > area ( face ) :
face = bbox
return face
2024-10-23 00:05:48 +02:00
def _process_face ( self , obj_data : dict [ str , any ] , frame : np . ndarray ) - > None :
""" Look for faces in image. """
2024-10-23 17:03:18 +02:00
id = obj_data [ " id " ]
2024-10-23 00:05:48 +02:00
# don't run for non person objects
if obj_data . get ( " label " ) != " person " :
logger . debug ( " Not a processing face for non person object. " )
return
2024-10-23 17:03:18 +02:00
# don't overwrite sub label for objects that have a sub label
# that is not a face
if obj_data . get ( " sub_label " ) and id not in self . detected_faces :
2024-10-23 00:05:48 +02:00
logger . debug (
f " Not processing face due to existing sub label: { obj_data . get ( ' sub_label ' ) } . "
)
return
face : Optional [ dict [ str , any ] ] = None
if self . requires_face_detection :
2024-10-23 21:50:58 +02:00
logger . debug ( " Running manual face detection. " )
person_box = obj_data . get ( " box " )
if not person_box :
return None
rgb = cv2 . cvtColor ( frame , cv2 . COLOR_YUV2RGB_I420 )
left , top , right , bottom = person_box
person = rgb [ top : bottom , left : right ]
face = self . _detect_face ( person )
if not face :
logger . debug ( " Detected no faces for person object. " )
return
face_frame = person [ face [ 1 ] : face [ 3 ] , face [ 0 ] : face [ 2 ] ]
face_frame = cv2 . cvtColor ( face_frame , cv2 . COLOR_RGB2BGR )
2024-10-23 00:05:48 +02:00
else :
# don't run for object without attributes
if not obj_data . get ( " current_attributes " ) :
logger . debug ( " No attributes to parse. " )
return
attributes : list [ dict [ str , any ] ] = obj_data . get ( " current_attributes " , [ ] )
for attr in attributes :
if attr . get ( " label " ) != " face " :
continue
if face is None or attr . get ( " score " , 0.0 ) > face . get ( " score " , 0.0 ) :
face = attr
2024-10-23 21:50:58 +02:00
# no faces detected in this frame
if not face :
return
2024-10-23 00:05:48 +02:00
2024-10-23 21:50:58 +02:00
face_box = face . get ( " box " )
2024-10-23 00:05:48 +02:00
2024-10-23 21:50:58 +02:00
# check that face is valid
if not face_box or area ( face_box ) < self . config . face_recognition . min_area :
logger . debug ( f " Invalid face box { face } " )
return
2024-10-23 00:05:48 +02:00
2024-10-23 21:50:58 +02:00
face_frame = cv2 . cvtColor ( frame , cv2 . COLOR_YUV2BGR_I420 )
face_frame = face_frame [
face_box [ 1 ] : face_box [ 3 ] , face_box [ 0 ] : face_box [ 2 ]
]
ret , webp = cv2 . imencode (
2024-10-23 00:05:48 +02:00
" .webp " , face_frame , [ int ( cv2 . IMWRITE_WEBP_QUALITY ) , 100 ]
)
if not ret :
logger . debug ( " Not processing face due to error creating cropped image. " )
return
2024-10-23 21:50:58 +02:00
embedding = self . embeddings . embed_face ( " unknown " , webp . tobytes ( ) , upsert = False )
2024-10-23 00:05:48 +02:00
query_embedding = serialize ( embedding )
best_faces = self . _search_face ( query_embedding )
logger . debug ( f " Detected best faces for person as: { best_faces } " )
2024-10-23 17:03:18 +02:00
if not best_faces or len ( best_faces ) < REQUIRED_FACES :
2024-10-23 21:50:58 +02:00
logger . debug ( f " { len ( best_faces ) } < { REQUIRED_FACES } min required faces. " )
2024-10-23 00:05:48 +02:00
return
sub_label = str ( best_faces [ 0 ] [ 0 ] ) . split ( " - " ) [ 0 ]
2024-10-23 17:03:18 +02:00
avg_score = 0
for face in best_faces :
score = 1.0 - face [ 1 ]
2024-10-23 21:50:58 +02:00
if face [ 0 ] . split ( " - " ) [ 0 ] != sub_label :
2024-10-23 17:03:18 +02:00
logger . debug ( " Detected multiple faces, result is not valid. " )
2024-10-23 21:50:58 +02:00
return
2024-10-23 00:05:48 +02:00
2024-10-23 17:03:18 +02:00
avg_score + = score
2024-10-23 21:50:58 +02:00
avg_score = round ( avg_score / REQUIRED_FACES , 2 )
2024-10-23 17:03:18 +02:00
2024-10-23 21:50:58 +02:00
if avg_score < self . config . face_recognition . threshold or (
2024-10-23 17:03:18 +02:00
id in self . detected_faces and avg_score < = self . detected_faces [ id ]
) :
logger . debug (
2024-10-23 21:50:58 +02:00
f " Recognized face score { avg_score } is less than threshold ( { self . config . face_recognition . threshold } ) / previous face score ( { self . detected_faces . get ( id ) } ). "
2024-10-23 17:03:18 +02:00
)
2024-10-23 21:50:58 +02:00
return
2024-10-23 00:05:48 +02:00
2024-10-23 21:50:58 +02:00
resp = requests . post (
2024-10-23 17:03:18 +02:00
f " { FRIGATE_LOCALHOST } /api/events/ { id } /sub_label " ,
2024-10-23 21:50:58 +02:00
json = {
" camera " : obj_data . get ( " camera " ) ,
" subLabel " : sub_label ,
" subLabelScore " : avg_score ,
} ,
2024-10-23 00:05:48 +02:00
)
2024-10-23 21:50:58 +02:00
if resp . status_code == 200 :
self . detected_faces [ id ] = avg_score
2024-06-21 23:30:19 +02:00
def _create_thumbnail ( self , yuv_frame , box , height = 500 ) - > Optional [ bytes ] :
""" Return jpg thumbnail of a region of the frame. """
frame = cv2 . cvtColor ( yuv_frame , cv2 . COLOR_YUV2BGR_I420 )
region = calculate_region (
frame . shape , box [ 0 ] , box [ 1 ] , box [ 2 ] , box [ 3 ] , height , multiplier = 1.4
)
frame = frame [ region [ 1 ] : region [ 3 ] , region [ 0 ] : region [ 2 ] ]
width = int ( height * frame . shape [ 1 ] / frame . shape [ 0 ] )
frame = cv2 . resize ( frame , dsize = ( width , height ) , interpolation = cv2 . INTER_AREA )
ret , jpg = cv2 . imencode ( " .jpg " , frame , [ int ( cv2 . IMWRITE_JPEG_QUALITY ) , 100 ] )
if ret :
return jpg . tobytes ( )
return None
2024-10-07 22:30:45 +02:00
def _embed_thumbnail ( self , event_id : str , thumbnail : bytes ) - > None :
2024-06-21 23:30:19 +02:00
""" Embed the thumbnail for an event. """
2024-10-22 00:19:34 +02:00
self . embeddings . embed_thumbnail ( event_id , thumbnail )
2024-06-21 23:30:19 +02:00
2024-10-07 22:30:45 +02:00
def _embed_description ( self , event : Event , thumbnails : list [ bytes ] ) - > None :
2024-06-21 23:30:19 +02:00
""" Embed the description for an event. """
2024-09-16 16:46:11 +02:00
camera_config = self . config . cameras [ event . camera ]
2024-06-21 23:30:19 +02:00
2024-09-16 16:46:11 +02:00
description = self . genai_client . generate_description (
2024-10-12 14:19:24 +02:00
camera_config , thumbnails , event
2024-09-16 16:46:11 +02:00
)
2024-06-21 23:30:19 +02:00
2024-09-23 14:53:19 +02:00
if not description :
2024-06-21 23:30:19 +02:00
logger . debug ( " Failed to generate description for %s " , event . id )
return
# fire and forget description update
self . requestor . send_data (
UPDATE_EVENT_DESCRIPTION ,
2024-11-18 19:26:44 +01:00
{
" type " : TrackedObjectUpdateTypesEnum . description ,
" id " : event . id ,
" description " : description ,
} ,
2024-06-21 23:30:19 +02:00
)
2024-10-22 00:19:34 +02:00
# Embed the description
self . embeddings . embed_description ( event . id , description )
2024-06-21 23:30:19 +02:00
logger . debug (
" Generated description for %s ( %d images): %s " ,
event . id ,
len ( thumbnails ) ,
description ,
)
2024-09-24 16:14:51 +02:00
2024-09-30 23:54:53 +02:00
def handle_regenerate_description ( self , event_id : str , source : str ) - > None :
2024-09-24 16:14:51 +02:00
try :
event : Event = Event . get ( Event . id == event_id )
except DoesNotExist :
logger . error ( f " Event { event_id } not found for description regeneration " )
return
camera_config = self . config . cameras [ event . camera ]
if not camera_config . genai . enabled or self . genai_client is None :
logger . error ( f " GenAI not enabled for camera { event . camera } " )
return
thumbnail = base64 . b64decode ( event . thumbnail )
2024-10-01 15:01:45 +02:00
logger . debug (
f " Trying { source } regeneration for { event } , has_snapshot: { event . has_snapshot } "
)
2024-09-30 23:54:53 +02:00
if event . has_snapshot and source == " snapshot " :
2024-12-16 03:51:23 +01:00
snapshot_file = os . path . join ( CLIPS_DIR , f " { event . camera } - { event . id } .jpg " )
if not os . path . isfile ( snapshot_file ) :
logger . error (
f " Cannot regenerate description for { event . id } , snapshot file not found: { snapshot_file } "
)
return
with open ( snapshot_file , " rb " ) as image_file :
2024-09-30 23:54:53 +02:00
snapshot_image = image_file . read ( )
img = cv2 . imdecode (
np . frombuffer ( snapshot_image , dtype = np . int8 ) , cv2 . IMREAD_COLOR
)
# crop snapshot based on region before sending off to genai
2024-12-16 00:03:19 +01:00
# provide full image if region doesn't exist (manual events)
region = event . data . get ( " region " , [ 0 , 0 , 1 , 1 ] )
2024-09-30 23:54:53 +02:00
height , width = img . shape [ : 2 ]
2024-12-16 00:03:19 +01:00
x1_rel , y1_rel , width_rel , height_rel = region
2024-09-30 23:54:53 +02:00
x1 , y1 = int ( x1_rel * width ) , int ( y1_rel * height )
cropped_image = img [
y1 : y1 + int ( height_rel * height ) , x1 : x1 + int ( width_rel * width )
]
_ , buffer = cv2 . imencode ( " .jpg " , cropped_image )
snapshot_image = buffer . tobytes ( )
embed_image = (
[ snapshot_image ]
if event . has_snapshot and source == " snapshot "
else (
2024-12-17 14:44:00 +01:00
[ data [ " thumbnail " ] for data in self . tracked_events [ event_id ] ]
2024-09-30 23:54:53 +02:00
if len ( self . tracked_events . get ( event_id , [ ] ) ) > 0
else [ thumbnail ]
)
)
2024-10-07 22:30:45 +02:00
self . _embed_description ( event , embed_image )