Skip to content

Commit

Permalink
Review improvements (#11879)
Browse files Browse the repository at this point in the history
* Update segment even when number of active objects is the same

* add score to frigate+ chip

* Add support for selecting zones

* Add api support for filtering on zones

* Adjust UI

* Update filtering logic

* Clean up
  • Loading branch information
NickM-27 committed Jun 11, 2024
1 parent b3eab17 commit c9d253a
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/docs/integrations/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ Reviews from the database. Accepts the following query string parameters:
| `after` | int | Epoch time |
| `cameras` | str | , separated list of cameras |
| `labels` | str | , separated list of labels |
| `zones` | str | , separated list of zones |
| `reviewed` | int | Include items that have been reviewed (0 or 1) |
| `limit` | int | Limit the number of events returned |
| `severity` | str | Limit items to severity (alert, detection, significant_motion) |
Expand Down
30 changes: 30 additions & 0 deletions frigate/api/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
def review():
cameras = request.args.get("cameras", "all")
labels = request.args.get("labels", "all")
zones = request.args.get("zones", "all")
reviewed = request.args.get("reviewed", type=int, default=0)
limit = request.args.get("limit", type=int, default=None)
severity = request.args.get("severity", None)
Expand Down Expand Up @@ -60,6 +61,20 @@ def review():
label_clause = reduce(operator.or_, label_clauses)
clauses.append((label_clause))

if zones != "all":
# use matching so segments with multiple zones
# still match on a search where any zone matches
zone_clauses = []
filtered_zones = zones.split(",")

for zone in filtered_zones:
zone_clauses.append(
(ReviewSegment.data["zones"].cast("text") % f'*"{zone}"*')
)

zone_clause = reduce(operator.or_, zone_clauses)
clauses.append((zone_clause))

if reviewed == 0:
clauses.append((ReviewSegment.has_been_reviewed == False))

Expand Down Expand Up @@ -96,6 +111,7 @@ def review_summary():

cameras = request.args.get("cameras", "all")
labels = request.args.get("labels", "all")
zones = request.args.get("zones", "all")

clauses = [(ReviewSegment.start_time > day_ago)]

Expand All @@ -118,6 +134,20 @@ def review_summary():
label_clause = reduce(operator.or_, label_clauses)
clauses.append((label_clause))

if zones != "all":
# use matching so segments with multiple zones
# still match on a search where any zone matches
zone_clauses = []
filtered_zones = zones.split(",")

for zone in filtered_zones:
zone_clauses.append(
(ReviewSegment.data["zones"].cast("text") % f'*"{zone}"*')
)

zone_clause = reduce(operator.or_, zone_clauses)
clauses.append((zone_clause))

last_24 = (
ReviewSegment.select(
fn.SUM(
Expand Down
6 changes: 6 additions & 0 deletions frigate/review/maintainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ def update_existing_segment(
active_objects = get_active_objects(frame_time, camera_config, objects)

if len(active_objects) > 0:
should_update = False

if frame_time > segment.last_update:
segment.last_update = frame_time

Expand Down Expand Up @@ -270,12 +272,16 @@ def update_existing_segment(
)
):
segment.severity = SeverityEnum.alert
should_update = True

# keep zones up to date
if len(object["current_zones"]) > 0:
segment.zones.update(object["current_zones"])

if len(active_objects) > segment.frame_active_count:
should_update = True

if should_update:
try:
frame_id = f"{camera_config.name}{frame_time}"
yuv_frame = self.frame_manager.get(
Expand Down
126 changes: 118 additions & 8 deletions web/src/components/filter/ReviewFilterGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import MobileReviewSettingsDrawer, {
} from "../overlay/MobileReviewSettingsDrawer";
import useOptimisticState from "@/hooks/use-optimistic-state";
import FilterSwitch from "./FilterSwitch";
import { FilterList } from "@/types/filter";

const REVIEW_FILTERS = [
"cameras",
Expand All @@ -53,7 +54,7 @@ type ReviewFilterGroupProps = {
reviewSummary?: ReviewSummary;
filter?: ReviewFilter;
motionOnly: boolean;
filterLabels?: string[];
filterList?: FilterList;
onUpdateFilter: (filter: ReviewFilter) => void;
setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>;
};
Expand All @@ -64,15 +65,15 @@ export default function ReviewFilterGroup({
reviewSummary,
filter,
motionOnly,
filterLabels,
filterList,
onUpdateFilter,
setMotionOnly,
}: ReviewFilterGroupProps) {
const { data: config } = useSWR<FrigateConfig>("config");

const allLabels = useMemo<string[]>(() => {
if (filterLabels) {
return filterLabels;
if (filterList?.labels) {
return filterList.labels;
}

if (!config) {
Expand All @@ -99,14 +100,43 @@ export default function ReviewFilterGroup({
});

return [...labels].sort();
}, [config, filterLabels, filter]);
}, [config, filterList, filter]);

const allZones = useMemo<string[]>(() => {
if (filterList?.zones) {
return filterList.zones;
}

if (!config) {
return [];
}

const zones = new Set<string>();
const cameras = filter?.cameras || Object.keys(config.cameras);

cameras.forEach((camera) => {
if (camera == "birdseye") {
return;
}
const cameraConfig = config.cameras[camera];
cameraConfig.review.alerts.required_zones.forEach((zone) => {
zones.add(zone);
});
cameraConfig.review.detections.required_zones.forEach((zone) => {
zones.add(zone);
});
});

return [...zones].sort();
}, [config, filterList, filter]);

const filterValues = useMemo(
() => ({
cameras: Object.keys(config?.cameras || {}),
labels: Object.values(allLabels || {}),
zones: Object.values(allZones || {}),
}),
[config, allLabels],
[config, allLabels, allZones],
);

const groups = useMemo(() => {
Expand Down Expand Up @@ -189,12 +219,17 @@ export default function ReviewFilterGroup({
selectedLabels={filter?.labels}
currentSeverity={currentSeverity}
showAll={filter?.showAll == true}
allZones={filterValues.zones}
selectedZones={filter?.zones}
setShowAll={(showAll) => {
onUpdateFilter({ ...filter, showAll });
}}
updateLabelFilter={(newLabels) => {
onUpdateFilter({ ...filter, labels: newLabels });
}}
updateZoneFilter={(newZones) =>
onUpdateFilter({ ...filter, zones: newZones })
}
/>
)}
{isMobile && mobileSettingsFeatures.length > 0 && (
Expand All @@ -204,6 +239,7 @@ export default function ReviewFilterGroup({
currentSeverity={currentSeverity}
reviewSummary={reviewSummary}
allLabels={allLabels}
allZones={allZones}
onUpdateFilter={onUpdateFilter}
// not applicable as exports are not used
camera=""
Expand Down Expand Up @@ -495,21 +531,30 @@ type GeneralFilterButtonProps = {
selectedLabels: string[] | undefined;
currentSeverity?: ReviewSeverity;
showAll: boolean;
allZones: string[];
selectedZones?: string[];
setShowAll: (showAll: boolean) => void;
updateLabelFilter: (labels: string[] | undefined) => void;
updateZoneFilter: (zones: string[] | undefined) => void;
};
function GeneralFilterButton({
allLabels,
selectedLabels,
currentSeverity,
showAll,
allZones,
selectedZones,
setShowAll,
updateLabelFilter,
updateZoneFilter,
}: GeneralFilterButtonProps) {
const [open, setOpen] = useState(false);
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
selectedLabels,
);
const [currentZones, setCurrentZones] = useState<string[] | undefined>(
selectedZones,
);

const trigger = (
<Button
Expand All @@ -534,6 +579,11 @@ function GeneralFilterButton({
currentLabels={currentLabels}
currentSeverity={currentSeverity}
showAll={showAll}
allZones={allZones}
selectedZones={selectedZones}
currentZones={currentZones}
setCurrentZones={setCurrentZones}
updateZoneFilter={updateZoneFilter}
setShowAll={setShowAll}
updateLabelFilter={updateLabelFilter}
setCurrentLabels={setCurrentLabels}
Expand Down Expand Up @@ -584,9 +634,14 @@ type GeneralFilterContentProps = {
currentLabels: string[] | undefined;
currentSeverity?: ReviewSeverity;
showAll?: boolean;
allZones?: string[];
selectedZones?: string[];
currentZones?: string[];
setShowAll?: (showAll: boolean) => void;
updateLabelFilter: (labels: string[] | undefined) => void;
setCurrentLabels: (labels: string[] | undefined) => void;
updateZoneFilter?: (zones: string[] | undefined) => void;
setCurrentZones?: (zones: string[] | undefined) => void;
onClose: () => void;
};
export function GeneralFilterContent({
Expand All @@ -595,9 +650,14 @@ export function GeneralFilterContent({
currentLabels,
currentSeverity,
showAll,
allZones,
selectedZones,
currentZones,
setShowAll,
updateLabelFilter,
setCurrentLabels,
updateZoneFilter,
setCurrentZones,
onClose,
}: GeneralFilterContentProps) {
return (
Expand All @@ -622,7 +682,7 @@ export function GeneralFilterContent({
<DropdownMenuSeparator />
</div>
)}
<div className="my-2.5 flex items-center justify-between">
<div className="mb-5 mt-2.5 flex items-center justify-between">
<Label
className="mx-2 cursor-pointer text-primary"
htmlFor="allLabels"
Expand All @@ -640,7 +700,6 @@ export function GeneralFilterContent({
}}
/>
</div>
<DropdownMenuSeparator />
<div className="my-2.5 flex flex-col gap-2.5">
{allLabels.map((item) => (
<FilterSwitch
Expand All @@ -666,6 +725,53 @@ export function GeneralFilterContent({
))}
</div>
</div>
{allZones && setCurrentZones && (
<>
<DropdownMenuSeparator />
<div className="mb-5 mt-2.5 flex items-center justify-between">
<Label
className="mx-2 cursor-pointer text-primary"
htmlFor="allZones"
>
All Zones
</Label>
<Switch
className="ml-1"
id="allZones"
checked={currentZones == undefined}
onCheckedChange={(isChecked) => {
if (isChecked) {
setCurrentZones(undefined);
}
}}
/>
</div>
<div className="my-2.5 flex flex-col gap-2.5">
{allZones.map((item) => (
<FilterSwitch
label={item.replaceAll("_", " ")}
isChecked={currentZones?.includes(item) ?? false}
onCheckedChange={(isChecked) => {
if (isChecked) {
const updatedZones = currentZones ? [...currentZones] : [];

updatedZones.push(item);
setCurrentZones(updatedZones);
} else {
const updatedZones = currentZones ? [...currentZones] : [];

// can not deselect the last item
if (updatedZones.length > 1) {
updatedZones.splice(updatedZones.indexOf(item), 1);
setCurrentZones(updatedZones);
}
}
}}
/>
))}
</div>
</>
)}
<DropdownMenuSeparator />
<div className="flex items-center justify-evenly p-2">
<Button
Expand All @@ -675,6 +781,10 @@ export function GeneralFilterContent({
updateLabelFilter(currentLabels);
}

if (updateZoneFilter && selectedZones != currentZones) {
updateZoneFilter(currentZones);
}

onClose();
}}
>
Expand Down

0 comments on commit c9d253a

Please sign in to comment.