From a6fcf8fc5addb6620f5406719045f2d0985d1eb2 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 12 Mar 2025 18:45:16 -0500 Subject: [PATCH] Rename identifier field (#17128) * backend rename * frontend * docs * fix api path --- .../license_plate_recognition.md | 8 +- docs/docs/integrations/mqtt.md | 8 +- frigate/api/app.py | 38 +++--- .../api/defs/query/events_query_parameters.py | 4 +- frigate/api/event.py | 110 ++++++++++------- frigate/camera/state.py | 7 +- frigate/comms/event_metadata_updater.py | 2 +- .../common/license_plate/mixin.py | 3 +- frigate/events/maintainer.py | 15 ++- frigate/track/object_processing.py | 27 +++-- frigate/track/tracked_object.py | 10 +- .../overlay/detail/SearchDetailDialog.tsx | 22 ++-- .../overlay/dialog/SearchFilterDialog.tsx | 113 ++++++++++-------- web/src/pages/Explore.tsx | 6 +- web/src/types/search.ts | 8 +- web/src/views/search/SearchView.tsx | 15 ++- 16 files changed, 238 insertions(+), 158 deletions(-) diff --git a/docs/docs/configuration/license_plate_recognition.md b/docs/docs/configuration/license_plate_recognition.md index f8b9030ffe..776f30cf9e 100644 --- a/docs/docs/configuration/license_plate_recognition.md +++ b/docs/docs/configuration/license_plate_recognition.md @@ -3,16 +3,16 @@ id: license_plate_recognition title: License Plate Recognition (LPR) --- -Frigate can recognize license plates on vehicles and automatically add the detected characters to the `identifier` field or a known name as a `sub_label` to objects that are of type `car`. A common use case may be to read the license plates of cars pulling into a driveway or cars passing by on a street. +Frigate can recognize license plates on vehicles and automatically add the detected characters to the `recognized_license_plate` field or a known name as a `sub_label` to objects that are of type `car`. A common use case may be to read the license plates of cars pulling into a driveway or cars passing by on a street. LPR works best when the license plate is clearly visible to the camera. For moving vehicles, Frigate continuously refines the recognition process, keeping the most confident result. However, LPR does not run on stationary vehicles. When a plate is recognized, the recognized name is: -- Added to the `car` tracked object as a `sub_label` (if known) or the `identifier` field (if unknown) +- Added to the `car` tracked object as a `sub_label` (if known) or the `recognized_license_plate` field (if unknown) - Viewable in the Review Item Details pane in Review and the Tracked Object Details pane in Explore. - Filterable through the More Filters menu in Explore. -- Published via the `frigate/events` MQTT topic as a `sub_label` (known) or `identifier` (unknown) for the tracked object. +- Published via the `frigate/events` MQTT topic as a `sub_label` (known) or `recognized_license_plate` (unknown) for the tracked object. ## Model Requirements @@ -71,7 +71,7 @@ Fine-tune the LPR feature using these optional parameters: - **`known_plates`**: List of strings or regular expressions that assign custom a `sub_label` to `car` objects when a recognized plate matches a known value. - These labels appear in the UI, filters, and notifications. - - Unknown plates are still saved but are added to the `identifier` field rather than the `sub_label`. + - Unknown plates are still saved but are added to the `recognized_license_plate` field rather than the `sub_label`. - **`match_distance`**: Allows for minor variations (missing/incorrect characters) when matching a detected plate to a known plate. - For example, setting `match_distance: 1` allows a plate `ABCDE` to match `ABCBE` or `ABCD`. - This parameter will _not_ operate on known plates that are defined as regular expressions. You should define the full string of your plate in `known_plates` in order to use `match_distance`. diff --git a/docs/docs/integrations/mqtt.md b/docs/docs/integrations/mqtt.md index bcdbe7046a..abbc12974f 100644 --- a/docs/docs/integrations/mqtt.md +++ b/docs/docs/integrations/mqtt.md @@ -55,8 +55,8 @@ Message published for each changed tracked object. The first message is publishe "current_attributes": [], // detailed data about the current attributes in this frame "current_estimated_speed": 0.71, // current estimated speed (mph or kph) for objects moving through zones with speed estimation enabled "velocity_angle": 180, // direction of travel relative to the frame for objects moving through zones with speed estimation enabled - "identifier": "ABC12345", // an identifier for this object - in this case, an unrecognized license plate - "identifier_score": 0.933451 + "recognized_license_plate": "ABC12345", // a recognized license plate for car objects + "recognized_license_plate_score": 0.933451 }, "after": { "id": "1607123955.475377-mxklsc", @@ -96,8 +96,8 @@ Message published for each changed tracked object. The first message is publishe ], "current_estimated_speed": 0.77, // current estimated speed (mph or kph) for objects moving through zones with speed estimation enabled "velocity_angle": 180, // direction of travel relative to the frame for objects moving through zones with speed estimation enabled - "identifier": "ABC12345", // an identifier for this object - in this case, an unrecognized license plate - "identifier_score": 0.933451 + "recognized_license_plate": "ABC12345", // a recognized license plate for car objects + "recognized_license_plate_score": 0.933451 } } ``` diff --git a/frigate/api/app.py b/frigate/api/app.py index d9a57a3c16..05013ed125 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -619,37 +619,39 @@ def get_sub_labels(split_joined: Optional[int] = None): return JSONResponse(content=sub_labels) -@router.get("/identifiers") -def get_identifiers(split_joined: Optional[int] = None): +@router.get("/recognized_license_plates") +def get_recognized_license_plates(split_joined: Optional[int] = None): try: events = Event.select(Event.data).distinct() except Exception: return JSONResponse( - content=({"success": False, "message": "Failed to get identifiers"}), + content=( + {"success": False, "message": "Failed to get recognized license plates"} + ), status_code=404, ) - identifiers = [] + recognized_license_plates = [] for e in events: - if e.data is not None and "identifier" in e.data: - identifiers.append(e.data["identifier"]) + if e.data is not None and "recognized_license_plate" in e.data: + recognized_license_plates.append(e.data["recognized_license_plate"]) - while None in identifiers: - identifiers.remove(None) + while None in recognized_license_plates: + recognized_license_plates.remove(None) if split_joined: - original_identifiers = identifiers.copy() - for identifier in original_identifiers: - if identifier and "," in identifier: - identifiers.remove(identifier) - parts = identifier.split(",") + original_recognized_license_plates = recognized_license_plates.copy() + for recognized_license_plate in original_recognized_license_plates: + if recognized_license_plate and "," in recognized_license_plate: + recognized_license_plates.remove(recognized_license_plate) + parts = recognized_license_plate.split(",") for part in parts: - if part.strip() not in identifiers: - identifiers.append(part.strip()) + if part.strip() not in recognized_license_plates: + recognized_license_plates.append(part.strip()) - identifiers = list(set(identifiers)) - identifiers.sort() - return JSONResponse(content=identifiers) + recognized_license_plates = list(set(recognized_license_plates)) + recognized_license_plates.sort() + return JSONResponse(content=recognized_license_plates) @router.get("/timeline") diff --git a/frigate/api/defs/query/events_query_parameters.py b/frigate/api/defs/query/events_query_parameters.py index 9f73d8583b..d707ba8cc8 100644 --- a/frigate/api/defs/query/events_query_parameters.py +++ b/frigate/api/defs/query/events_query_parameters.py @@ -27,7 +27,7 @@ class EventsQueryParams(BaseModel): max_score: Optional[float] = None min_speed: Optional[float] = None max_speed: Optional[float] = None - identifier: Optional[str] = "all" + recognized_license_plate: Optional[str] = "all" is_submitted: Optional[int] = None min_length: Optional[float] = None max_length: Optional[float] = None @@ -56,7 +56,7 @@ class EventsSearchQueryParams(BaseModel): max_score: Optional[float] = None min_speed: Optional[float] = None max_speed: Optional[float] = None - identifier: Optional[str] = "all" + recognized_license_plate: Optional[str] = "all" sort: Optional[str] = None diff --git a/frigate/api/event.py b/frigate/api/event.py index e9cf2fea41..88a8653185 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -101,7 +101,7 @@ def events(params: EventsQueryParams = Depends()): min_length = params.min_length max_length = params.max_length event_id = params.event_id - identifier = params.identifier + recognized_license_plate = params.recognized_license_plate sort = params.sort @@ -159,31 +159,44 @@ def events(params: EventsQueryParams = Depends()): sub_label_clause = reduce(operator.or_, sub_label_clauses) clauses.append((sub_label_clause)) - if identifier != "all": - # use matching so joined identifiers are included - # for example an identifier 'ABC123' would get events - # with identifiers 'ABC123' and 'ABC123, XYZ789' - identifier_clauses = [] - filtered_identifiers = identifier.split(",") + if recognized_license_plate != "all": + # use matching so joined recognized_license_plates are included + # for example a recognized license plate 'ABC123' would get events + # with recognized license plates 'ABC123' and 'ABC123, XYZ789' + recognized_license_plate_clauses = [] + filtered_recognized_license_plates = recognized_license_plate.split(",") - if "None" in filtered_identifiers: - filtered_identifiers.remove("None") - identifier_clauses.append((Event.data["identifier"].is_null())) + if "None" in filtered_recognized_license_plates: + filtered_recognized_license_plates.remove("None") + recognized_license_plate_clauses.append( + (Event.data["recognized_license_plate"].is_null()) + ) - for identifier in filtered_identifiers: + for recognized_license_plate in filtered_recognized_license_plates: # Exact matching plus list inclusion - identifier_clauses.append( - (Event.data["identifier"].cast("text") == identifier) + recognized_license_plate_clauses.append( + ( + Event.data["recognized_license_plate"].cast("text") + == recognized_license_plate + ) ) - identifier_clauses.append( - (Event.data["identifier"].cast("text") % f"*{identifier},*") + recognized_license_plate_clauses.append( + ( + Event.data["recognized_license_plate"].cast("text") + % f"*{recognized_license_plate},*" + ) ) - identifier_clauses.append( - (Event.data["identifier"].cast("text") % f"*, {identifier}*") + recognized_license_plate_clauses.append( + ( + Event.data["recognized_license_plate"].cast("text") + % f"*, {recognized_license_plate}*" + ) ) - identifier_clause = reduce(operator.or_, identifier_clauses) - clauses.append((identifier_clause)) + recognized_license_plate_clause = reduce( + operator.or_, recognized_license_plate_clauses + ) + clauses.append((recognized_license_plate_clause)) if zones != "all": # use matching so events with multiple zones @@ -367,8 +380,8 @@ def events_explore(limit: int = 10): "average_estimated_speed", "velocity_angle", "path_data", - "identifier", - "identifier_score", + "recognized_license_plate", + "recognized_license_plate_score", ] }, "event_count": label_counts[event.label], @@ -426,7 +439,7 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends()) has_clip = params.has_clip has_snapshot = params.has_snapshot is_submitted = params.is_submitted - identifier = params.identifier + recognized_license_plate = params.recognized_license_plate # for similarity search event_id = params.event_id @@ -496,31 +509,44 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends()) event_filters.append((reduce(operator.or_, zone_clauses))) - if identifier != "all": - # use matching so joined identifiers are included - # for example an identifier 'ABC123' would get events - # with identifiers 'ABC123' and 'ABC123, XYZ789' - identifier_clauses = [] - filtered_identifiers = identifier.split(",") + if recognized_license_plate != "all": + # use matching so joined recognized_license_plates are included + # for example an recognized_license_plate 'ABC123' would get events + # with recognized_license_plates 'ABC123' and 'ABC123, XYZ789' + recognized_license_plate_clauses = [] + filtered_recognized_license_plates = recognized_license_plate.split(",") - if "None" in filtered_identifiers: - filtered_identifiers.remove("None") - identifier_clauses.append((Event.data["identifier"].is_null())) + if "None" in filtered_recognized_license_plates: + filtered_recognized_license_plates.remove("None") + recognized_license_plate_clauses.append( + (Event.data["recognized_license_plate"].is_null()) + ) - for identifier in filtered_identifiers: + for recognized_license_plate in filtered_recognized_license_plates: # Exact matching plus list inclusion - identifier_clauses.append( - (Event.data["identifier"].cast("text") == identifier) + recognized_license_plate_clauses.append( + ( + Event.data["recognized_license_plate"].cast("text") + == recognized_license_plate + ) ) - identifier_clauses.append( - (Event.data["identifier"].cast("text") % f"*{identifier},*") + recognized_license_plate_clauses.append( + ( + Event.data["recognized_license_plate"].cast("text") + % f"*{recognized_license_plate},*" + ) ) - identifier_clauses.append( - (Event.data["identifier"].cast("text") % f"*, {identifier}*") + recognized_license_plate_clauses.append( + ( + Event.data["recognized_license_plate"].cast("text") + % f"*, {recognized_license_plate}*" + ) ) - identifier_clause = reduce(operator.or_, identifier_clauses) - event_filters.append((identifier_clause)) + recognized_license_plate_clause = reduce( + operator.or_, recognized_license_plate_clauses + ) + event_filters.append((recognized_license_plate_clause)) if after: event_filters.append((Event.start_time > after)) @@ -683,8 +709,8 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends()) "average_estimated_speed", "velocity_angle", "path_data", - "identifier", - "identifier_score", + "recognized_license_plate", + "recognized_license_plate_score", ] } diff --git a/frigate/camera/state.py b/frigate/camera/state.py index 2ffa54798b..0e02c6c144 100644 --- a/frigate/camera/state.py +++ b/frigate/camera/state.py @@ -141,8 +141,11 @@ class CameraState: if ( obj.get("sub_label") and is_label_printable(obj["sub_label"][0]) ) - else obj.get("identifier", [None])[0] - if (obj.get("identifier") and obj["identifier"][0]) + else obj.get("recognized_license_plate", [None])[0] + if ( + obj.get("recognized_license_plate") + and obj["recognized_license_plate"][0] + ) else obj["label"] ) draw_box_with_label( diff --git a/frigate/comms/event_metadata_updater.py b/frigate/comms/event_metadata_updater.py index c5881e6862..c702208bc4 100644 --- a/frigate/comms/event_metadata_updater.py +++ b/frigate/comms/event_metadata_updater.py @@ -14,7 +14,7 @@ class EventMetadataTypeEnum(str, Enum): manual_event_end = "manual_event_end" regenerate_description = "regenerate_description" sub_label = "sub_label" - identifier = "identifier" + recognized_license_plate = "recognized_license_plate" class EventMetadataPublisher(Publisher): diff --git a/frigate/data_processing/common/license_plate/mixin.py b/frigate/data_processing/common/license_plate/mixin.py index 37530b2055..751a674f55 100644 --- a/frigate/data_processing/common/license_plate/mixin.py +++ b/frigate/data_processing/common/license_plate/mixin.py @@ -1064,7 +1064,8 @@ class LicensePlateProcessingMixin: ) self.sub_label_publisher.publish( - EventMetadataTypeEnum.identifier, (id, top_plate, avg_confidence) + EventMetadataTypeEnum.recognized_license_plate, + (id, top_plate, avg_confidence), ) self.detected_license_plates[id] = { diff --git a/frigate/events/maintainer.py b/frigate/events/maintainer.py index 947763f40b..7788c83e9f 100644 --- a/frigate/events/maintainer.py +++ b/frigate/events/maintainer.py @@ -27,7 +27,8 @@ def should_update_db(prev_event: Event, current_event: Event) -> bool: or prev_event["average_estimated_speed"] != current_event["average_estimated_speed"] or prev_event["velocity_angle"] != current_event["velocity_angle"] - or prev_event["identifier"] != current_event["identifier"] + or prev_event["recognized_license_plate"] + != current_event["recognized_license_plate"] or prev_event["path_data"] != current_event["path_data"] ): return True @@ -227,10 +228,14 @@ class EventProcessor(threading.Thread): event[Event.sub_label] = event_data["sub_label"][0] event[Event.data]["sub_label_score"] = event_data["sub_label"][1] - # only overwrite the identifier in the database if it's set - if event_data.get("identifier") is not None: - event[Event.data]["identifier"] = event_data["identifier"][0] - event[Event.data]["identifier_score"] = event_data["identifier"][1] + # only overwrite the recognized_license_plate in the database if it's set + if event_data.get("recognized_license_plate") is not None: + event[Event.data]["recognized_license_plate"] = event_data[ + "recognized_license_plate" + ][0] + event[Event.data]["recognized_license_plate_score"] = event_data[ + "recognized_license_plate" + ][1] ( Event.insert(event) diff --git a/frigate/track/object_processing.py b/frigate/track/object_processing.py index bfd4b2a10e..2ab884cd71 100644 --- a/frigate/track/object_processing.py +++ b/frigate/track/object_processing.py @@ -346,10 +346,10 @@ class TrackedObjectProcessor(threading.Thread): return True - def set_identifier( - self, event_id: str, identifier: str | None, score: float | None + def set_recognized_license_plate( + self, event_id: str, recognized_license_plate: str | None, score: float | None ) -> None: - """Update identifier for given event id.""" + """Update recognized license plate for given event id.""" tracked_obj: TrackedObject = None for state in self.camera_states.values(): @@ -367,15 +367,18 @@ class TrackedObjectProcessor(threading.Thread): return if tracked_obj: - tracked_obj.obj_data["identifier"] = (identifier, score) + tracked_obj.obj_data["recognized_license_plate"] = ( + recognized_license_plate, + score, + ) if event: data = event.data - data["identifier"] = identifier - if identifier is None: - data["identifier_score"] = None + data["recognized_license_plate"] = recognized_license_plate + if recognized_license_plate is None: + data["recognized_license_plate_score"] = None elif score is not None: - data["identifier_score"] = score + data["recognized_license_plate_score"] = score event.data = data event.save() @@ -542,9 +545,11 @@ class TrackedObjectProcessor(threading.Thread): if topic.endswith(EventMetadataTypeEnum.sub_label.value): (event_id, sub_label, score) = payload self.set_sub_label(event_id, sub_label, score) - if topic.endswith(EventMetadataTypeEnum.identifier.value): - (event_id, identifier, score) = payload - self.set_identifier(event_id, identifier, score) + if topic.endswith(EventMetadataTypeEnum.recognized_license_plate.value): + (event_id, recognized_license_plate, score) = payload + self.set_recognized_license_plate( + event_id, recognized_license_plate, score + ) elif topic.endswith(EventMetadataTypeEnum.manual_event_create.value): self.create_manual_event(payload) elif topic.endswith(EventMetadataTypeEnum.manual_event_end.value): diff --git a/frigate/track/tracked_object.py b/frigate/track/tracked_object.py index 9c19595b58..7a4829c2a1 100644 --- a/frigate/track/tracked_object.py +++ b/frigate/track/tracked_object.py @@ -153,8 +153,12 @@ class TrackedObject: "current_estimated_speed": self.current_estimated_speed, "velocity_angle": self.velocity_angle, "path_data": self.path_data, - "identifier": obj_data.get("identifier"), - "identifier_score": obj_data.get("identifier_score"), + "recognized_license_plate": obj_data.get( + "recognized_license_plate" + ), + "recognized_license_plate_score": obj_data.get( + "recognized_license_plate_score" + ), } thumb_update = True @@ -367,7 +371,7 @@ class TrackedObject: "average_estimated_speed": self.average_estimated_speed, "velocity_angle": self.velocity_angle, "path_data": self.path_data, - "identifier": self.obj_data.get("identifier"), + "recognized_license_plate": self.obj_data.get("recognized_license_plate"), } return event diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index 7f69f2ca22..ed472c742c 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -333,13 +333,18 @@ function ObjectDetailsTab({ } }, [search]); - const identifierScore = useMemo(() => { + const recognizedLicensePlateScore = useMemo(() => { if (!search) { return undefined; } - if (search.data.identifier && search.data?.identifier_score) { - return Math.round((search.data?.identifier_score ?? 0) * 100); + if ( + search.data.recognized_license_plate && + search.data?.recognized_license_plate_score + ) { + return Math.round( + (search.data?.recognized_license_plate_score ?? 0) * 100, + ); } else { return undefined; } @@ -550,13 +555,16 @@ function ObjectDetailsTab({ - {search?.data.identifier && ( + {search?.data.recognized_license_plate && (
Failed to load identifiers
- ) : !allIdentifiers ? ( -Loading identifiers...
++ Failed to load recognized license plates. +
+ ) : !allRecognizedLicensePlates ? ( ++ Loading recognized license plates... +
) : ( <>- Select one or more identifiers from the list. + Select one or more plates from the list.