False positives (#6217)

* add false positive submission

* switch timeline events to x,y,w,h

* update docs

* fix type checks

* convert to upsert

* fix config test
This commit is contained in:
Blake Blackshear
2023-04-24 07:24:28 -05:00
committed by GitHub
parent 9dca1e1d9f
commit 0d16bd0144
18 changed files with 572 additions and 234 deletions

View File

@@ -3,6 +3,7 @@ import { route } from 'preact-router';
import ActivityIndicator from '../components/ActivityIndicator';
import Heading from '../components/Heading';
import { Tabs, TextTab } from '../components/Tabs';
import Link from '../components/Link';
import { useApiHost } from '../api';
import useSWR from 'swr';
import useSWRInfinite from 'swr/infinite';
@@ -57,7 +58,12 @@ export default function Events({ path, ...props }) {
showDownloadMenu: false,
showDatePicker: false,
showCalendar: false,
showPlusConfig: false,
showPlusSubmit: false,
});
const [plusSubmitEvent, setPlusSubmitEvent] = useState({
id: null,
label: null,
validBox: null,
});
const [uploading, setUploading] = useState([]);
const [viewEvent, setViewEvent] = useState();
@@ -65,6 +71,8 @@ export default function Events({ path, ...props }) {
const [eventDetailType, setEventDetailType] = useState('clip');
const [downloadEvent, setDownloadEvent] = useState({
id: null,
label: null,
box: null,
has_clip: false,
has_snapshot: false,
plus_id: undefined,
@@ -198,6 +206,8 @@ export default function Events({ path, ...props }) {
e.stopPropagation();
setDownloadEvent((_prev) => ({
id: event.id,
box: event.box,
label: event.label,
has_clip: event.has_clip,
has_snapshot: event.has_snapshot,
plus_id: event.plus_id,
@@ -207,6 +217,16 @@ export default function Events({ path, ...props }) {
setState({ ...state, showDownloadMenu: true });
};
const showSubmitToPlus = (event_id, label, box, e) => {
if (e) {
e.stopPropagation();
}
// if any of the box coordinates are > 1, then the box data is from an older version
// and not valid to submit to plus with the snapshot image
setPlusSubmitEvent({ id: event_id, label, validBox: !box.some((d) => d > 1) });
setState({ ...state, showDownloadMenu: false, showPlusSubmit: true });
};
const handleSelectDateRange = useCallback(
(dates) => {
setSearchParams({ ...searchParams, before: dates.before, after: dates.after });
@@ -251,23 +271,16 @@ export default function Events({ path, ...props }) {
[size, setSize, isValidating, isDone]
);
const onSendToPlus = async (id, e) => {
if (e) {
e.stopPropagation();
}
const onSendToPlus = async (id, false_positive, validBox) => {
if (uploading.includes(id)) {
return;
}
if (!config.plus.enabled) {
setState({ ...state, showDownloadMenu: false, showPlusConfig: true });
return;
}
setUploading((prev) => [...prev, id]);
const response = await axios.post(`events/${id}/plus`);
const response = false_positive
? await axios.put(`events/${id}/false_positive`)
: await axios.post(`events/${id}/plus`, validBox ? { include_annotation: 1 } : {});
if (response.status === 200) {
mutate(
@@ -289,6 +302,8 @@ export default function Events({ path, ...props }) {
if (state.showDownloadMenu && downloadEvent.id === id) {
setState({ ...state, showDownloadMenu: false });
}
setState({ ...state, showPlusSubmit: false });
};
const handleEventDetailTabChange = (index) => {
@@ -375,12 +390,12 @@ export default function Events({ path, ...props }) {
download
/>
)}
{(downloadEvent.end_time && downloadEvent.has_snapshot && !downloadEvent.plus_id) && (
{downloadEvent.end_time && downloadEvent.has_snapshot && !downloadEvent.plus_id && (
<MenuItem
icon={UploadPlus}
label={uploading.includes(downloadEvent.id) ? 'Uploading...' : 'Send to Frigate+'}
value="plus"
onSelect={() => onSendToPlus(downloadEvent.id)}
onSelect={() => showSubmitToPlus(downloadEvent.id, downloadEvent.label, downloadEvent.box)}
/>
)}
{downloadEvent.plus_id && (
@@ -435,25 +450,96 @@ export default function Events({ path, ...props }) {
/>
</Menu>
)}
{state.showPlusConfig && (
{state.showPlusSubmit && (
<Dialog>
<div className="p-4">
<Heading size="lg">Setup a Frigate+ Account</Heading>
<p className="mb-2">In order to submit images to Frigate+, you first need to setup an account.</p>
<a
className="text-blue-500 hover:underline"
href="https://plus.frigate.video"
target="_blank"
rel="noopener noreferrer"
>
https://plus.frigate.video
</a>
</div>
<div className="p-2 flex justify-start flex-row-reverse space-x-2">
<Button className="ml-2" onClick={() => setState({ ...state, showPlusConfig: false })} type="text">
Close
</Button>
</div>
{config.plus.enabled ? (
<>
<div className="p-4">
<Heading size="lg">Submit to Frigate+</Heading>
<img
className="flex-grow-0"
src={`${apiHost}/api/events/${plusSubmitEvent.id}/snapshot.jpg`}
alt={`${plusSubmitEvent.label}`}
/>
{plusSubmitEvent.validBox ? (
<p className="mb-2">
Objects in locations you want to avoid are not false positives. Submitting them as false positives
will confuse the model.
</p>
) : (
<p className="mb-2">
Events prior to version 0.13 can only be submitted to Frigate+ without annotations.
</p>
)}
</div>
{plusSubmitEvent.validBox ? (
<div className="p-2 flex justify-start flex-row-reverse space-x-2">
<Button className="ml-2" onClick={() => setState({ ...state, showPlusSubmit: false })} type="text">
{uploading.includes(plusSubmitEvent.id) ? 'Close' : 'Cancel'}
</Button>
<Button
className="ml-2"
color="red"
onClick={() => onSendToPlus(plusSubmitEvent.id, true, plusSubmitEvent.validBox)}
disabled={uploading.includes(plusSubmitEvent.id)}
type="text"
>
This is not a {plusSubmitEvent.label}
</Button>
<Button
className="ml-2"
color="green"
onClick={() => onSendToPlus(plusSubmitEvent.id, false, plusSubmitEvent.validBox)}
disabled={uploading.includes(plusSubmitEvent.id)}
type="text"
>
This is a {plusSubmitEvent.label}
</Button>
</div>
) : (
<div className="p-2 flex justify-start flex-row-reverse space-x-2">
<Button
className="ml-2"
onClick={() => setState({ ...state, showPlusSubmit: false })}
disabled={uploading.includes(plusSubmitEvent.id)}
type="text"
>
{uploading.includes(plusSubmitEvent.id) ? 'Close' : 'Cancel'}
</Button>
<Button
className="ml-2"
onClick={() => onSendToPlus(plusSubmitEvent.id, false, plusSubmitEvent.validBox)}
disabled={uploading.includes(plusSubmitEvent.id)}
type="text"
>
Submit to Frigate+
</Button>
</div>
)}
</>
) : (
<>
<div className="p-4">
<Heading size="lg">Setup a Frigate+ Account</Heading>
<p className="mb-2">In order to submit images to Frigate+, you first need to setup an account.</p>
<a
className="text-blue-500 hover:underline"
href="https://plus.frigate.video"
target="_blank"
rel="noopener noreferrer"
>
https://plus.frigate.video
</a>
</div>
<div className="p-2 flex justify-start flex-row-reverse space-x-2">
<Button className="ml-2" onClick={() => setState({ ...state, showPlusSubmit: false })} type="text">
Close
</Button>
</div>
</>
)}
</Dialog>
)}
{deleteFavoriteState.showDeleteFavorite && (
@@ -539,12 +625,20 @@ export default function Events({ path, ...props }) {
{event.end_time && event.has_snapshot && (
<Fragment>
{event.plus_id ? (
<div className="uppercase text-xs">Sent to Frigate+</div>
<div className="uppercase text-xs underline">
<Link
href={`https://plus.frigate.video/dashboard/edit-image/?id=${event.plus_id}`}
target="_blank"
rel="nofollow"
>
Edit in Frigate+
</Link>
</div>
) : (
<Button
color="gray"
disabled={uploading.includes(event.id)}
onClick={(e) => onSendToPlus(event.id, e)}
onClick={(e) => showSubmitToPlus(event.id, event.label, event.box, e)}
>
{uploading.includes(event.id) ? 'Uploading...' : 'Send to Frigate+'}
</Button>
@@ -617,8 +711,12 @@ export default function Events({ path, ...props }) {
style={{
left: `${Math.round(eventOverlay.data.box[0] * 100)}%`,
top: `${Math.round(eventOverlay.data.box[1] * 100)}%`,
right: `${Math.round((1 - eventOverlay.data.box[2]) * 100)}%`,
bottom: `${Math.round((1 - eventOverlay.data.box[3]) * 100)}%`,
right: `${Math.round(
(1 - eventOverlay.data.box[2] - eventOverlay.data.box[0]) * 100
)}%`,
bottom: `${Math.round(
(1 - eventOverlay.data.box[3] - eventOverlay.data.box[1]) * 100
)}%`,
}}
>
{eventOverlay.class_type == 'entered_zone' ? (