mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-16 18:16:44 +03:00
Compare commits
8 Commits
0bcabf0731
...
8631dcfe8b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8631dcfe8b | ||
|
|
81d184936f | ||
|
|
8f92ebbe0e | ||
|
|
7e738c6538 | ||
|
|
10e9080093 | ||
|
|
fd7475ac34 | ||
|
|
90a5f3a63e | ||
|
|
e246d84ddd |
@ -197,7 +197,9 @@ class FaceRecognitionConfig(FrigateBaseModel):
|
|||||||
title="Min face recognitions for the sub label to be applied to the person object.",
|
title="Min face recognitions for the sub label to be applied to the person object.",
|
||||||
)
|
)
|
||||||
save_attempts: int = Field(
|
save_attempts: int = Field(
|
||||||
default=200, ge=0, title="Number of face attempts to save in the recent recognitions tab."
|
default=200,
|
||||||
|
ge=0,
|
||||||
|
title="Number of face attempts to save in the recent recognitions tab.",
|
||||||
)
|
)
|
||||||
blur_confidence_filter: bool = Field(
|
blur_confidence_filter: bool = Field(
|
||||||
default=True, title="Apply blur quality filter to face confidence."
|
default=True, title="Apply blur quality filter to face confidence."
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
} from "../mobile/MobilePage";
|
} from "../mobile/MobilePage";
|
||||||
|
|
||||||
type ClassificationCardProps = {
|
type ClassificationCardProps = {
|
||||||
|
className?: string;
|
||||||
imgClassName?: string;
|
imgClassName?: string;
|
||||||
data: ClassificationItemData;
|
data: ClassificationItemData;
|
||||||
threshold?: ClassificationThreshold;
|
threshold?: ClassificationThreshold;
|
||||||
@ -49,6 +50,7 @@ export const ClassificationCard = forwardRef<
|
|||||||
ClassificationCardProps
|
ClassificationCardProps
|
||||||
>(function ClassificationCard(
|
>(function ClassificationCard(
|
||||||
{
|
{
|
||||||
|
className,
|
||||||
imgClassName,
|
imgClassName,
|
||||||
data,
|
data,
|
||||||
threshold,
|
threshold,
|
||||||
@ -98,8 +100,8 @@ export const ClassificationCard = forwardRef<
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-pointer flex-col overflow-hidden rounded-lg outline outline-[3px]",
|
"relative flex size-full cursor-pointer flex-col overflow-hidden rounded-lg outline outline-[3px]",
|
||||||
isMobile ? "!size-full" : "size-48",
|
className,
|
||||||
selected
|
selected
|
||||||
? "shadow-selected outline-selected"
|
? "shadow-selected outline-selected"
|
||||||
: "outline-transparent duration-500",
|
: "outline-transparent duration-500",
|
||||||
@ -211,13 +213,13 @@ export function GroupedClassificationCard({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!best) {
|
if (!best) {
|
||||||
return group[0];
|
return group.at(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bestTyped: ClassificationItemData = best;
|
const bestTyped: ClassificationItemData = best;
|
||||||
return {
|
return {
|
||||||
...bestTyped,
|
...bestTyped,
|
||||||
name: event?.sub_label || bestTyped.name,
|
name: event ? (event.sub_label ?? "") : bestTyped.name,
|
||||||
score: event?.data?.sub_label_score || bestTyped.score,
|
score: event?.data?.sub_label_score || bestTyped.score,
|
||||||
};
|
};
|
||||||
}, [group, event]);
|
}, [group, event]);
|
||||||
@ -287,39 +289,18 @@ export function GroupedClassificationCard({
|
|||||||
<Content
|
<Content
|
||||||
className={cn(
|
className={cn(
|
||||||
"",
|
"",
|
||||||
isDesktop && "w-auto max-w-[85%]",
|
isDesktop && "min-w-[50%] max-w-[65%]",
|
||||||
isMobile && "flex flex-col",
|
isMobile && "flex flex-col",
|
||||||
)}
|
)}
|
||||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
{isDesktop && (
|
<Header
|
||||||
<div className="absolute right-10 top-4 flex flex-row justify-between">
|
className={cn(
|
||||||
{event && (
|
"mx-2 flex flex-row items-center gap-4",
|
||||||
<Tooltip>
|
isMobile && "flex-shrink-0",
|
||||||
<TooltipTrigger asChild>
|
)}
|
||||||
<div
|
|
||||||
className="cursor-pointer"
|
|
||||||
tabIndex={-1}
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/explore?event_id=${event.id}`);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<LuSearch className="size-4 text-secondary-foreground" />
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipPortal>
|
|
||||||
<TooltipContent>
|
|
||||||
{t("details.item.button.viewInExplore", {
|
|
||||||
ns: "views/explore",
|
|
||||||
})}
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPortal>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Header className={cn("mx-2", isMobile && "flex-shrink-0")}>
|
|
||||||
<div>
|
<div>
|
||||||
<ContentTitle
|
<ContentTitle
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -349,27 +330,42 @@ export function GroupedClassificationCard({
|
|||||||
)}
|
)}
|
||||||
</ContentDescription>
|
</ContentDescription>
|
||||||
</div>
|
</div>
|
||||||
|
{isDesktop && (
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
{event && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div
|
||||||
|
className="cursor-pointer"
|
||||||
|
tabIndex={-1}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/explore?event_id=${event.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LuSearch className="size-4 text-secondary-foreground" />
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent>
|
||||||
|
{t("details.item.button.viewInExplore", {
|
||||||
|
ns: "views/explore",
|
||||||
|
})}
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Header>
|
</Header>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-pointer flex-col gap-2 rounded-lg",
|
"grid w-full auto-rows-min grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-6 2xl:grid-cols-8",
|
||||||
isDesktop && "p-2",
|
isDesktop && "p-2",
|
||||||
isMobile && "scrollbar-container w-full flex-1 overflow-y-auto",
|
isMobile && "scrollbar-container flex-1 overflow-y-auto",
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"gap-2",
|
|
||||||
isDesktop
|
|
||||||
? "flex flex-row flex-wrap"
|
|
||||||
: "grid grid-cols-2 justify-items-center gap-2 px-2 sm:grid-cols-5 lg:grid-cols-6",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{group.map((data: ClassificationItemData) => (
|
{group.map((data: ClassificationItemData) => (
|
||||||
<div
|
<div key={data.filename} className="aspect-square w-full">
|
||||||
key={data.filename}
|
|
||||||
className={cn(isMobile && "aspect-square size-full")}
|
|
||||||
>
|
|
||||||
<ClassificationCard
|
<ClassificationCard
|
||||||
data={data}
|
data={data}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
@ -386,7 +382,6 @@ export function GroupedClassificationCard({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
</Content>
|
</Content>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
|||||||
@ -51,7 +51,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { isDesktop, isMobile, isMobileOnly } from "react-device-detect";
|
import { isDesktop } from "react-device-detect";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
LuFolderCheck,
|
LuFolderCheck,
|
||||||
@ -695,16 +695,13 @@ function TrainingGrid({
|
|||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"scrollbar-container gap-3 overflow-y-scroll p-1",
|
"scrollbar-container grid grid-cols-2 gap-3 overflow-y-scroll p-1 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10 3xl:grid-cols-12",
|
||||||
isMobile
|
|
||||||
? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8"
|
|
||||||
: "flex flex-wrap",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{Object.entries(faceGroups).map(([key, group]) => {
|
{Object.entries(faceGroups).map(([key, group]) => {
|
||||||
const event = events?.find((ev) => ev.id == key);
|
const event = events?.find((ev) => ev.id == key);
|
||||||
return (
|
return (
|
||||||
<div key={key} className={cn(isMobile && "aspect-square size-full")}>
|
<div key={key} className="aspect-square w-full">
|
||||||
<FaceAttemptGroup
|
<FaceAttemptGroup
|
||||||
config={config}
|
config={config}
|
||||||
group={group}
|
group={group}
|
||||||
@ -861,12 +858,12 @@ function FaceAttemptGroup({
|
|||||||
faceNames={faceNames}
|
faceNames={faceNames}
|
||||||
onTrainAttempt={(name) => onTrainAttempt(data, name)}
|
onTrainAttempt={(name) => onTrainAttempt(data, name)}
|
||||||
>
|
>
|
||||||
<AddFaceIcon className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground" />
|
<AddFaceIcon className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground/40" />
|
||||||
</FaceSelectionDialog>
|
</FaceSelectionDialog>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<LuRefreshCw
|
<LuRefreshCw
|
||||||
className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground"
|
className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground/40"
|
||||||
onClick={() => onReprocess(data)}
|
onClick={() => onReprocess(data)}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@ -914,13 +911,12 @@ function FaceGrid({
|
|||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"scrollbar-container gap-2 overflow-y-scroll p-1",
|
"scrollbar-container grid grid-cols-2 gap-2 overflow-y-scroll p-1 md:grid-cols-4 xl:grid-cols-8 2xl:grid-cols-10 3xl:grid-cols-12",
|
||||||
isDesktop ? "flex flex-wrap" : "grid grid-cols-2 md:grid-cols-4",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{sortedFaces.map((image: string) => (
|
{sortedFaces.map((image: string) => (
|
||||||
|
<div key={image} className="aspect-square w-full">
|
||||||
<ClassificationCard
|
<ClassificationCard
|
||||||
key={image}
|
|
||||||
data={{
|
data={{
|
||||||
name: pageToggle,
|
name: pageToggle,
|
||||||
filename: image,
|
filename: image,
|
||||||
@ -943,6 +939,7 @@ function FaceGrid({
|
|||||||
<TooltipContent>{t("button.deleteFaceAttempts")}</TooltipContent>
|
<TooltipContent>{t("button.deleteFaceAttempts")}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ClassificationCard>
|
</ClassificationCard>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -44,7 +44,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { isDesktop, isMobile, isMobileOnly } from "react-device-detect";
|
import { isDesktop } from "react-device-detect";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { LuPencil, LuTrash2 } from "react-icons/lu";
|
import { LuPencil, LuTrash2 } from "react-icons/lu";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@ -632,12 +632,11 @@ function DatasetGrid({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className="scrollbar-container flex flex-wrap gap-2 overflow-y-auto p-2"
|
className="scrollbar-container grid grid-cols-2 gap-2 overflow-y-scroll p-1 md:grid-cols-4 xl:grid-cols-8 2xl:grid-cols-10 3xl:grid-cols-12"
|
||||||
>
|
>
|
||||||
{classData.map((image) => (
|
{classData.map((image) => (
|
||||||
|
<div key={image} className="aspect-square w-full">
|
||||||
<ClassificationCard
|
<ClassificationCard
|
||||||
key={image}
|
|
||||||
imgClassName="size-auto"
|
|
||||||
data={{
|
data={{
|
||||||
filename: image,
|
filename: image,
|
||||||
filepath: `clips/${modelName}/dataset/${categoryName}/${image}`,
|
filepath: `clips/${modelName}/dataset/${categoryName}/${image}`,
|
||||||
@ -662,6 +661,7 @@ function DatasetGrid({
|
|||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ClassificationCard>
|
</ClassificationCard>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -791,14 +791,12 @@ function StateTrainGrid({
|
|||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"scrollbar-container flex flex-wrap gap-3 overflow-y-auto p-2",
|
"scrollbar-container grid grid-cols-2 gap-3 overflow-y-scroll p-1 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10 3xl:grid-cols-12",
|
||||||
isMobile && "justify-center",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{trainData?.map((data) => (
|
{trainData?.map((data) => (
|
||||||
|
<div key={data.filename} className="aspect-square w-full">
|
||||||
<ClassificationCard
|
<ClassificationCard
|
||||||
key={data.filename}
|
|
||||||
imgClassName="size-auto"
|
|
||||||
data={data}
|
data={data}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
selected={selectedImages.includes(data.filename)}
|
selected={selectedImages.includes(data.filename)}
|
||||||
@ -812,9 +810,10 @@ function StateTrainGrid({
|
|||||||
image={data.filename}
|
image={data.filename}
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
>
|
>
|
||||||
<TbCategoryPlus className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground" />
|
<TbCategoryPlus className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground/40" />
|
||||||
</ClassificationSelectionDialog>
|
</ClassificationSelectionDialog>
|
||||||
</ClassificationCard>
|
</ClassificationCard>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -928,21 +927,14 @@ function ObjectTrainGrid({
|
|||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
"scrollbar-container gap-3 overflow-y-scroll p-1",
|
"scrollbar-container grid grid-cols-2 gap-3 overflow-y-scroll p-1 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10 3xl:grid-cols-12",
|
||||||
isMobile
|
|
||||||
? "grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8"
|
|
||||||
: "flex flex-wrap",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{Object.entries(groups).map(([key, group]) => {
|
{Object.entries(groups).map(([key, group]) => {
|
||||||
const event = events?.find((ev) => ev.id == key);
|
const event = events?.find((ev) => ev.id == key);
|
||||||
return (
|
return (
|
||||||
<div
|
<div key={key} className="aspect-square w-full">
|
||||||
key={key}
|
|
||||||
className={cn(isMobile && "aspect-square size-full")}
|
|
||||||
>
|
|
||||||
<GroupedClassificationCard
|
<GroupedClassificationCard
|
||||||
key={key}
|
|
||||||
group={group}
|
group={group}
|
||||||
event={event}
|
event={event}
|
||||||
threshold={threshold}
|
threshold={threshold}
|
||||||
@ -965,7 +957,7 @@ function ObjectTrainGrid({
|
|||||||
image={data.filename}
|
image={data.filename}
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
>
|
>
|
||||||
<TbCategoryPlus className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground" />
|
<TbCategoryPlus className="size-7 cursor-pointer p-1 text-gray-200 hover:rounded-full hover:bg-primary-foreground/40" />
|
||||||
</ClassificationSelectionDialog>
|
</ClassificationSelectionDialog>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user