Compare commits

...

7 Commits

Author SHA1 Message Date
tigattack
38a507b6a7
Merge 513bede475 into 644c7fa6b4 2025-12-07 18:50:25 +00:00
GuoQing Liu
644c7fa6b4
fix: fix classification missing i18n (#21179) 2025-12-07 11:35:48 -07:00
Josh Hawkins
88a8de0b1c
Miscellaneous Fixes (#21166)
* Improve model titles

* remove deprecated strftime_fmt

* remove

* remove restart wording

* add copilot instructions

* fix docs

* Move files into try for classification rollover

* Use friendly names for zones in notifications

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
2025-12-07 07:57:46 -07:00
tigattack
513bede475 Move hailo_platform import to get_hailo_temps 2025-12-01 19:21:35 +00:00
tigattack
7d5317959d Show Hailo temps in system UI 2025-12-01 12:52:41 +00:00
tigattack
f85a1fe3e8 Refactor get_hailo_temps() to use ctxmanager 2025-12-01 12:52:41 +00:00
tigattack
4d0456dcf0 Add Hailo temperature retrieval 2025-12-01 12:14:38 +00:00
19 changed files with 109 additions and 34 deletions

2
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,2 @@
Never write strings in the frontend directly, always write to and reference the relevant translations file.
Always conform new and refactored code to the existing coding style in the project.

View File

@ -116,4 +116,4 @@ Along with individual review item summaries, Generative AI provides the ability
Review reports can be requested via the [API](/integrations/api#review-summarization) by sending a POST request to `/api/review/summarize/start/{start_ts}/end/{end_ts}` with Unix timestamps.
For Home Assistant users, there is a built-in service (`frigate.generate_review_summary`) that makes it easy to request review reports as part of automations or scripts. This allows you to automatically generate daily summaries, vacation reports, or custom time period reports based on your specific needs.
For Home Assistant users, there is a built-in service (`frigate.review_summarize`) that makes it easy to request review reports as part of automations or scripts. This allows you to automatically generate daily summaries, vacation reports, or custom time period reports based on your specific needs.

View File

@ -28,7 +28,6 @@ To create a poly mask:
5. Click the plus icon under the type of mask or zone you would like to create
6. Click on the camera's latest image to create the points for a masked area. Click the first point again to close the polygon.
7. When you've finished creating your mask, press Save.
8. Restart Frigate to apply your changes.
Your config file will be updated with the relative coordinates of the mask/zone:

View File

@ -1002,10 +1002,6 @@ ui:
# full: 8:15:22 PM Mountain Standard Time
# (default: shown below).
time_style: medium
# Optional: Ability to manually override the date / time styling to use strftime format
# https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html
# possible values are shown above (default: not set)
strftime_fmt: "%Y/%m/%d %H:%M"
# Optional: Set the unit system to either "imperial" or "metric" (default: metric)
# Used in the UI and in MQTT topics
unit_system: metric

View File

@ -390,7 +390,20 @@ class WebPushClient(Communicator):
message = payload["after"]["data"]["metadata"]["scene"]
else:
title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {titlecase(', '.join(payload['after']['data']['zones']).replace('_', ' '))}"
zone_names = payload["after"]["data"]["zones"]
formatted_zone_names = []
for zone_name in zone_names:
if zone_name in self.config.cameras[camera].zones:
formatted_zone_names.append(
self.config.cameras[camera]
.zones[zone_name]
.get_formatted_name(zone_name)
)
else:
formatted_zone_names.append(titlecase(zone_name.replace("_", " ")))
title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {', '.join(formatted_zone_names)}"
message = f"Detected on {camera_name}"
if ended:

View File

@ -37,9 +37,6 @@ class UIConfig(FrigateBaseModel):
time_style: DateTimeStyleEnum = Field(
default=DateTimeStyleEnum.medium, title="Override UI timeStyle."
)
strftime_fmt: Optional[str] = Field(
default=None, title="Override date and time format using strftime syntax."
)
unit_system: UnitSystemEnum = Field(
default=UnitSystemEnum.metric, title="The unit system to use for measurements."
)

View File

@ -639,14 +639,14 @@ def write_classification_attempt(
os.makedirs(folder, exist_ok=True)
cv2.imwrite(file, frame)
# delete oldest face image if maximum is reached
try:
files = sorted(
filter(lambda f: (f.endswith(".webp")), os.listdir(folder)),
key=lambda f: os.path.getctime(os.path.join(folder, f)),
reverse=True,
)
# delete oldest face image if maximum is reached
try:
if len(files) > max_files:
os.unlink(os.path.join(folder, files[-1]))
except FileNotFoundError:

View File

@ -22,6 +22,7 @@ from frigate.util.services import (
get_bandwidth_stats,
get_cpu_stats,
get_fs_type,
get_hailo_temps,
get_intel_gpu_stats,
get_jetson_stats,
get_nvidia_gpu_stats,
@ -91,6 +92,9 @@ def get_temperatures() -> dict[str, float]:
if temp is not None:
temps[apex] = temp
# Get temperatures for Hailo devices
temps.update(get_hailo_temps())
return temps

View File

@ -549,6 +549,53 @@ def get_jetson_stats() -> Optional[dict[int, dict]]:
return results
def get_hailo_temps() -> dict[str, float]:
"""Get temperatures for Hailo devices."""
try:
from hailo_platform import Device
except ModuleNotFoundError:
return {}
temps = {}
try:
device_ids = Device.scan()
for i, device_id in enumerate(device_ids):
try:
with Device(device_id) as device:
temp_info = device.control.get_chip_temperature()
# Get board name and normalise it
identity = device.control.identify()
board_name = None
for line in str(identity).split("\n"):
if line.startswith("Board Name:"):
board_name = (
line.split(":", 1)[1].strip().lower().replace("-", "")
)
break
if not board_name:
board_name = f"hailo{i}"
# Use indexed name if multiple devices, otherwise just the board name
device_name = (
f"{board_name}-{i}" if len(device_ids) > 1 else board_name
)
# ts1_temperature is also available, but appeared to be the same as ts0 in testing.
temps[device_name] = round(temp_info.ts0_temperature, 1)
except Exception as e:
logger.debug(
f"Failed to get temperature for Hailo device {device_id}: {e}"
)
continue
except Exception as e:
logger.debug(f"Failed to scan for Hailo devices: {e}")
return temps
def ffprobe_stream(ffmpeg, path: str, detailed: bool = False) -> sp.CompletedProcess:
"""Run ffprobe on stream."""
clean_path = escape_special_characters(path)

View File

@ -13,9 +13,6 @@
"time_style": {
"label": "Override UI timeStyle."
},
"strftime_fmt": {
"label": "Override date and time format using strftime syntax."
},
"unit_system": {
"label": "The unit system to use for measurements."
}

View File

@ -1,5 +1,5 @@
{
"documentTitle": "Classification Models",
"documentTitle": "Classification Models - Frigate",
"details": {
"scoreInfo": "Score represents the average classification confidence across all detections of this object."
},
@ -83,6 +83,7 @@
"aria": "Select Recent Classifications"
},
"categories": "Classes",
"none": "None",
"createCategory": {
"new": "Create New Class"
},

View File

@ -77,7 +77,7 @@
"millisecondsToOffset": "Milliseconds to offset detect annotations by. <em>Default: 0</em>",
"tips": "Lower the value if the video playback is ahead of the boxes and path points, and increase the value if the video playback is behind them. This value can be negative.",
"toast": {
"success": "Annotation offset for {{camera}} has been saved to the config file. Restart Frigate to apply your changes."
"success": "Annotation offset for {{camera}} has been saved to the config file."
}
}
},

View File

@ -534,7 +534,7 @@
}
},
"toast": {
"success": "Zone ({{zoneName}}) has been saved. Restart Frigate to apply changes."
"success": "Zone ({{zoneName}}) has been saved."
}
},
"motionMasks": {
@ -558,8 +558,8 @@
},
"toast": {
"success": {
"title": "{{polygonName}} has been saved. Restart Frigate to apply changes.",
"noName": "Motion Mask has been saved. Restart Frigate to apply changes."
"title": "{{polygonName}} has been saved.",
"noName": "Motion Mask has been saved."
}
}
},
@ -583,8 +583,8 @@
},
"toast": {
"success": {
"title": "{{polygonName}} has been saved. Restart Frigate to apply changes.",
"noName": "Object Mask has been saved. Restart Frigate to apply changes."
"title": "{{polygonName}} has been saved.",
"noName": "Object Mask has been saved."
}
}
}

View File

@ -131,7 +131,9 @@ export default function ClassificationSelectionDialog({
className="flex cursor-pointer gap-2 smart-capitalize"
onClick={() => onCategorizeImage(category)}
>
{category.replaceAll("_", " ")}
{category === "none"
? t("none")
: category.replaceAll("_", " ")}
</SelectorItem>
))}
<Separator />

View File

@ -180,7 +180,9 @@ export function ClassFilterContent({
{allClasses.map((item) => (
<FilterSwitch
key={item}
label={item.replaceAll("_", " ")}
label={
item === "none" ? t("none") : item.replaceAll("_", " ")
}
isChecked={classes?.includes(item) ?? false}
onCheckedChange={(isChecked) => {
if (isChecked) {

View File

@ -371,7 +371,12 @@ export default function FaceLibrary() {
{selectedFaces?.length > 0 ? (
<div className="flex items-center justify-center gap-2">
<div className="mx-1 flex w-48 items-center justify-center text-sm text-muted-foreground">
<div className="p-1">{`${selectedFaces.length} selected`}</div>
<div className="p-1">
{t("selected", {
ns: "views/event",
count: selectedFaces.length,
})}
</div>
<div className="p-1">{"|"}</div>
<div
className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"

View File

@ -6,7 +6,6 @@ export interface UiConfig {
time_format?: "browser" | "12hour" | "24hour";
date_style?: "full" | "long" | "medium" | "short";
time_style?: "full" | "long" | "medium" | "short";
strftime_fmt?: string;
dashboard: boolean;
order: number;
unit_system?: "metric" | "imperial";

View File

@ -84,6 +84,12 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
const [page, setPage] = useState<string>("train");
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
// title
useEffect(() => {
document.title = `${model.name} - ${t("documentTitle")}`;
}, [model.name, t]);
// model state
const [wasTraining, setWasTraining] = useState(false);
@ -416,7 +422,12 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
)}
>
<div className="flex w-48 items-center justify-center text-sm text-muted-foreground">
<div className="p-1">{`${selectedImages.length} selected`}</div>
<div className="p-1">
{t("selected", {
ns: "views/event",
count: selectedImages.length,
})}
</div>
<div className="p-1">{"|"}</div>
<div
className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"
@ -676,7 +687,7 @@ function LibrarySelector({
className="flex-grow cursor-pointer capitalize"
onClick={() => setPageToggle(id)}
>
{id.replaceAll("_", " ")}
{id === "none" ? t("none") : id.replaceAll("_", " ")}
<span className="ml-2 text-muted-foreground">
({dataset?.[id].length})
</span>

View File

@ -144,7 +144,7 @@ export default function GeneralMetrics({
}
Object.entries(stats.detectors).forEach(([key], cIdx) => {
if (!key.includes("coral")) {
if (!key.includes("coral") && !key.includes("hailo")) {
return;
}