Compare commits

..

4 Commits

Author SHA1 Message Date
Josh Hawkins
b147b53522
add padding to dropdown text (#22420)
Some checks failed
CI / AMD64 Build (push) Has been cancelled
CI / ARM Build (push) Has been cancelled
CI / Jetson Jetpack 6 (push) Has been cancelled
CI / ARM Extra Build (push) Has been cancelled
CI / AMD64 Extra Build (push) Has been cancelled
CI / Synaptics Build (push) Has been cancelled
CI / Assemble and push default build (push) Has been cancelled
2026-03-13 09:43:07 -05:00
leccelecce
d2b2faa2d7
Update dev contrib docs with Python checks (#22419) 2026-03-13 08:16:10 -06:00
Josh Hawkins
614a6b39d4
consistently sort class names (#22415)
keep None at the bottom
2026-03-13 08:05:56 -05:00
Nicolas Mowen
f29ee53fb4
Add handler for license plate which is not expected to be stationary (#22416) 2026-03-13 07:02:42 -06:00
5 changed files with 125 additions and 72 deletions

View File

@ -89,6 +89,14 @@ After closing VS Code, you may still have containers running. To close everythin
### Testing
#### Unit Tests
GitHub will execute unit tests on new PRs. You must ensure that all tests pass.
```shell
python3 -u -m unittest
```
#### FFMPEG Hardware Acceleration
The following commands are used inside the container to ensure hardware acceleration is working properly.
@ -125,6 +133,28 @@ ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format
ffmpeg -c:v h264_qsv -re -stream_loop -1 -i https://streams.videolan.org/ffmpeg/incoming/720p60.mp4 -f rawvideo -pix_fmt yuv420p pipe: > /dev/null
```
### Submitting a pull request
Code must be formatted, linted and type-tested. GitHub will run these checks on pull requests, so it is advised to run them yourself prior to opening.
**Formatting**
```shell
ruff format frigate migrations docker *.py
```
**Linting**
```shell
ruff check frigate migrations docker *.py
```
**MyPy Static Typing**
```shell
python3 -u -m mypy --config-file frigate/mypy.ini frigate
```
## Web Interface
### Prerequisites

View File

@ -55,6 +55,14 @@ DYNAMIC_OBJECT_THRESHOLDS = StationaryThresholds(
motion_classifier_enabled=True,
)
# Thresholds for objects that are not expected to be stationary
NON_STATIONARY_OBJECT_THRESHOLDS = StationaryThresholds(
objects=["license_plate"],
known_active_iou=0.9,
stationary_check_iou=0.9,
max_stationary_history=4,
)
def get_stationary_threshold(label: str) -> StationaryThresholds:
"""Get the stationary thresholds for a given object label."""
@ -65,6 +73,9 @@ def get_stationary_threshold(label: str) -> StationaryThresholds:
if label in DYNAMIC_OBJECT_THRESHOLDS.objects:
return DYNAMIC_OBJECT_THRESHOLDS
if label in NON_STATIONARY_OBJECT_THRESHOLDS.objects:
return NON_STATIONARY_OBJECT_THRESHOLDS
return StationaryThresholds()

View File

@ -125,17 +125,23 @@ export default function ClassificationSelectionDialog({
isMobile && "gap-2 pb-4",
)}
>
{classes.sort().map((category) => (
<SelectorItem
key={category}
className="flex cursor-pointer gap-2 smart-capitalize"
onClick={() => onCategorizeImage(category)}
>
{category === "none"
? t("details.none")
: category.replaceAll("_", " ")}
</SelectorItem>
))}
{classes
.sort((a, b) => {
if (a === "none") return 1;
if (b === "none") return -1;
return a.localeCompare(b);
})
.map((category) => (
<SelectorItem
key={category}
className="flex cursor-pointer gap-2 smart-capitalize"
onClick={() => onCategorizeImage(category)}
>
{category === "none"
? t("details.none")
: category.replaceAll("_", " ")}
</SelectorItem>
))}
<Separator />
<SelectorItem
className="flex cursor-pointer gap-2 smart-capitalize"

View File

@ -598,18 +598,18 @@ function LibrarySelector({
{Object.values(faces).map((face) => (
<DropdownMenuItem
key={face}
className="group flex items-center justify-between"
className="group flex items-center justify-between p-0"
>
<div
className="flex-grow cursor-pointer"
onClick={() => setPageToggle(face)}
>
{face}
<span className="ml-2 text-muted-foreground">
<span className="ml-2 px-2 py-1.5 text-muted-foreground">
({faceData?.[face].length})
</span>
</div>
<div className="flex gap-0.5">
<div className="flex gap-0.5 px-2">
<Tooltip>
<TooltipTrigger asChild>
<Button

View File

@ -700,66 +700,72 @@ function LibrarySelector({
</div>
</>
)}
{Object.keys(dataset).map((id) => (
<DropdownMenuItem
key={id}
className="group flex items-center justify-between"
>
<div
className="flex-grow cursor-pointer capitalize"
onClick={() => setPageToggle(id)}
{Object.keys(dataset)
.sort((a, b) => {
if (a === "none") return 1;
if (b === "none") return -1;
return a.localeCompare(b);
})
.map((id) => (
<DropdownMenuItem
key={id}
className="group flex items-center justify-between p-0"
>
{id === "none" ? t("details.none") : id.replaceAll("_", " ")}
<span className="ml-2 text-muted-foreground">
({dataset?.[id].length})
</span>
</div>
{id != "none" && (
<div className="flex gap-0.5">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setRenameClass(id);
}}
>
<LuPencil className="size-4 text-primary" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.renameCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setConfirmDelete(id);
}}
>
<LuTrash2 className="size-4 text-destructive" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.deleteCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<div
className="flex-grow cursor-pointer px-2 py-1.5 capitalize"
onClick={() => setPageToggle(id)}
>
{id === "none" ? t("details.none") : id.replaceAll("_", " ")}
<span className="ml-2 text-muted-foreground">
({dataset?.[id].length})
</span>
</div>
)}
</DropdownMenuItem>
))}
{id != "none" && (
<div className="flex gap-0.5 px-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setRenameClass(id);
}}
>
<LuPencil className="size-4 text-primary" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.renameCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
onClick={(e) => {
e.stopPropagation();
setConfirmDelete(id);
}}
>
<LuTrash2 className="size-4 text-destructive" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.deleteCategory")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</>