Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the feature to Remove annotation objects in a specified range of frames #3617

Merged
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
25527db
Test Commit for Remove Range
gudipudiramanakumar Aug 27, 2021
aab67f4
Remove annotations in range merged with remove annotations button merged
gudipudiramanakumar Aug 28, 2021
a085554
Update annotation-reducer.ts
gudipudiramanakumar Aug 28, 2021
7839fe8
Update annotation-actions.ts
gudipudiramanakumar Aug 28, 2021
c092a56
Update annotation-reducer.ts
gudipudiramanakumar Aug 28, 2021
4d39b94
Merge branch 'openvinotoolkit:develop' into feature-removeannotations…
gudipudiramanakumar Sep 4, 2021
cde900f
Converting remove range component to hook based component
gudipudiramanakumar Oct 12, 2021
5ed81c3
Improved clear in cvat core and implemented remove range
gudipudiramanakumar Oct 18, 2021
6f5b922
Merge branch 'feature-removeannotationsinrange' into develop
gudipudiramanakumar Oct 18, 2021
d47db3c
Merge pull request #1 from gudipudiramanakumar/develop
gudipudiramanakumar Oct 18, 2021
25bbb49
Matching only the needed parts
gudipudiramanakumar Oct 18, 2021
6dba4e8
Merge branch 'feature-removeannotationsinrange' of https://github.com…
gudipudiramanakumar Oct 18, 2021
d72833d
Delete out.json
gudipudiramanakumar Oct 18, 2021
abca64f
Update annotations-collection.js
gudipudiramanakumar Oct 18, 2021
fb30089
Added a checkbox to remove range modal
gudipudiramanakumar Oct 19, 2021
2e714f1
ESLint fixed
gudipudiramanakumar Oct 20, 2021
88d1ec1
Merge branch 'openvinotoolkit:develop' into feature-removeannotations…
gudipudiramanakumar Oct 20, 2021
6cc2403
More ESLint and other updates
gudipudiramanakumar Oct 20, 2021
5317c04
Merge branch 'feature-removeannotationsinrange' of https://github.com…
gudipudiramanakumar Oct 20, 2021
c7a4a03
Update annotation-menu.tsx
gudipudiramanakumar Oct 20, 2021
e5c23b0
Update remove-range-confirm.tsx
gudipudiramanakumar Oct 21, 2021
455466f
Changed the approach of removeAnnotations modal
gudipudiramanakumar Oct 23, 2021
a313f46
Merge branch 'openvinotoolkit:develop' into feature-removeannotations…
gudipudiramanakumar Oct 25, 2021
8b0dc67
Added to changelog
gudipudiramanakumar Oct 26, 2021
96e73a7
Merge branch 'feature-removeannotationsinrange' of https://github.com…
gudipudiramanakumar Oct 26, 2021
b92b93c
Merge branch 'openvinotoolkit:develop' into feature-removeannotations…
gudipudiramanakumar Oct 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion cvat-core/src/annotations-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,35 @@
return groupIdx;
}

clear() {
clear(startframe, endframe, delTrackKeyframesOnly) {
let objectsRemovable = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it is unused variable

// If only a range of annotations need to be cleared
if (startframe !== undefined && endframe !== undefined) {
for (let frame = startframe; frame <= endframe; frame++) {
const shapes = this.shapes[frame] || [];
const tags = this.tags[frame] || [];
objectsRemovable = objectsRemovable.concat(shapes, tags);
this.shapes[frame] = [];
this.tags[frame] = [];
}
const { tracks } = this;
tracks.forEach((track) => {
if (track.frame <= endframe) {
if (delTrackKeyframesOnly) {
for (const keyframe in track.shapes) {
if (keyframe >= startframe && keyframe <= endframe) { delete track.shapes[keyframe]; }
}
} else if (track.frame >= startframe) {
objectsRemovable.push(track);
const index = tracks.indexOf(track);
if (index > -1) { tracks.splice(index, 1); }
}
}
});

return;
}
// If all annotations need to be cleared
this.shapes = {};
this.tags = {};
this.tracks = [];
Expand Down
4 changes: 2 additions & 2 deletions cvat-core/src/annotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@
return false;
}

async function clearAnnotations(session, reload) {
async function clearAnnotations(session, reload, startframe, endframe, delTrackKeyframesOnly) {
checkObjectType('reload', reload, 'boolean', null);
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);

if (cache.has(session)) {
cache.get(session).collection.clear();
cache.get(session).collection.clear(startframe, endframe, delTrackKeyframesOnly);
}

if (reload) {
Expand Down
8 changes: 4 additions & 4 deletions cvat-core/src/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
return result;
},

async clear(reload = false) {
const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.clear, reload);
async clear(reload = false, startframe = undefined, endframe = undefined, delTrackKeyframesOnly = true) {
const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.clear, reload, startframe, endframe, delTrackKeyframesOnly);
return result;
},

Expand Down Expand Up @@ -1897,8 +1897,8 @@
return result;
};

Job.prototype.annotations.clear.implementation = async function (reload) {
const result = await clearAnnotations(this, reload);
Job.prototype.annotations.clear.implementation = async function (reload, startframe, endframe, delTrackKeyframesOnly) {
const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly);
return result;
};

Expand Down
34 changes: 34 additions & 0 deletions cvat-ui/src/actions/annotation-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ export enum AnnotationActionTypes {
UPLOAD_JOB_ANNOTATIONS_FAILED = 'UPLOAD_JOB_ANNOTATIONS_FAILED',
REMOVE_JOB_ANNOTATIONS_SUCCESS = 'REMOVE_JOB_ANNOTATIONS_SUCCESS',
REMOVE_JOB_ANNOTATIONS_FAILED = 'REMOVE_JOB_ANNOTATIONS_FAILED',
REMOVE_ANNOTATIONS_INRANGE_SUCCESS = 'REMOVE_ANNOTATIONS_INRANGE_SUCCESS',
REMOVE_ANNOTATIONS_INRANGE_FAILED = 'REMOVE_ANNOTATIONS_INRANGE_FAILED',
UPDATE_CANVAS_CONTEXT_MENU = 'UPDATE_CANVAS_CONTEXT_MENU',
UNDO_ACTION_SUCCESS = 'UNDO_ACTION_SUCCESS',
UNDO_ACTION_FAILED = 'UNDO_ACTION_FAILED',
Expand Down Expand Up @@ -504,6 +506,38 @@ export function changePropagateFrames(frames: number): AnyAction {
};
}

// To remove annotation objects present in given range of frames
export function removeAnnotationsinRangeAsync(
startFrame: number, endFrame: number, delTrackKeyframesOnly: boolean,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const {
filters, frame, showAllInterpolationTracks, jobInstance,
} = receiveAnnotationsParameters();
await jobInstance.annotations.clear(false, startFrame, endFrame, delTrackKeyframesOnly);
await jobInstance.actions.clear();
const history = await jobInstance.actions.get();
const states = await jobInstance.annotations.get(frame, showAllInterpolationTracks, filters);

dispatch({
type: AnnotationActionTypes.REMOVE_ANNOTATIONS_INRANGE_SUCCESS,
payload: {
history,
states,
},
});
} catch (error) {
dispatch({
type: AnnotationActionTypes.REMOVE_ANNOTATIONS_INRANGE_FAILED,
payload: {
error,
},
});
}
};
}

export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
//
// SPDX-License-Identifier: MIT

import React from 'react';
import React, { useState } from 'react';

import Menu from 'antd/lib/menu';
import Modal from 'antd/lib/modal';
// eslint-disable-next-line import/no-extraneous-dependencies
Expand All @@ -11,6 +12,8 @@ import { MenuInfo } from 'rc-menu/lib/interface';
import LoadSubmenu from 'components/actions-menu/load-submenu';
import { DimensionType } from '../../../reducers/interfaces';

import RemoveAnnotationsRangeComponent from './remove-range-confirm';

interface Props {
taskMode: string;
loaders: any[];
Expand All @@ -20,6 +23,8 @@ interface Props {
jobInstance: any;
onClickMenu(params: MenuInfo): void;
onUploadAnnotations(format: string, file: File): void;
stopFrame: number;
removeRange(startnumber: number, endnumber: number, deltrack_keyframes_only:boolean): void;
setForceExitAnnotationFlag(forceExit: boolean): void;
saveAnnotations(jobInstance: any, afterSave?: () => void): void;
}
Expand All @@ -41,12 +46,16 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
loadActivity,
isReviewer,
jobInstance,
stopFrame,
onClickMenu,
onUploadAnnotations,
removeRange,
setForceExitAnnotationFlag,
saveAnnotations,
} = props;

const [openRemoveRangeComponent, toggleOpenRemoveRangeComponent] = useState(false);

const jobStatus = jobInstance.status;
const taskID = jobInstance.task.id;

Expand Down Expand Up @@ -81,19 +90,40 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {

if (params.key === Actions.REMOVE_ANNO) {
Modal.confirm({
title: 'All the annotations will be removed',
title: 'Remove Annotations',
content:
'You are going to remove all the annotations from the client. ' +
'Select whether to remove all annotations from the job or remove within a range\n' +
'It will stay on the server till you save the job. Continue?',
className: 'cvat-modal-confirm-remove-annotation',
onOk: () => {
onClickMenu(params);
Modal.confirm({
Copy link
Member

@bsekachev bsekachev Oct 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest following:

pseudocode

let removeFrom;
let removeUpTo;
let removeOnlyKeyframes = false;
Modal.confirm({
    content: (
        <div>  // of course, need to format content properly  
            <Text>message</Text> // of course, need to format content properly 
            <Text>Leave the inputs below empty to remove all the annotations from this job</Text>
            <Text>From:</Text><InputNumber onChange={(value) => removeFrom = value} />
            <Text>To:</Text><InputNumbe onChange={(value) => removeUpTo= value} />
            <Tooltip title='Applicable if remove annotations in range'>
                <Checkbox onClick={(checked) => removeOnlyKeyframes = checked}>Delete only keyframes for tracks</Checkbox>
            </Tooltip>
        </div>
    )

    onOk: () => removeAnnotations(removeFrom, removeUpTo, )
})

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you do not need remove-range-confirm.tsx Component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll try this approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you also suggest clubbing the removeAnnotationsAsync and removeAnnotationsinRangeAsync actions as one action

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I think yes. They are very similar each other.

title: 'All the annotations will be removed',
content:
'You are going to remove all the annotations from the client. ' +
'It will stay on the server till you save the job. Continue?',
className: 'cvat-modal-confirm-remove-annotation',
onOk: () => {
onClickMenu(params);
},
okButtonProps: {
type: 'primary',
danger: true,
},
okText: 'Delete',
});
},
okButtonProps: {
type: 'primary',
danger: true,
},
okText: 'Delete',
okText: 'Remove All',
onCancel: () => {
toggleOpenRemoveRangeComponent(!openRemoveRangeComponent);
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it is the best solution. Cancel button is not obvious to do action you programmed.

cancelText: 'Select Range',
cancelButtonProps: {
type: 'primary',
},
});
} else if (params.key === Actions.REQUEST_REVIEW) {
checkUnsavedChanges(params);
Expand Down Expand Up @@ -127,7 +157,7 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
const is2d = jobInstance.task.dimension === DimensionType.DIM_2D;

return (
<Menu onClick={onClickMenuWrapper} className='cvat-annotation-menu' selectable={false}>
<Menu onClick={(params: MenuInfo) => onClickMenuWrapper(params)} className='cvat-annotation-menu' selectable={false}>
{LoadSubmenu({
loaders,
loadActivity,
Expand Down Expand Up @@ -164,6 +194,12 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
<Menu.Item key={Actions.SUBMIT_REVIEW}>Submit the review</Menu.Item>
)}
{jobStatus === 'completed' && <Menu.Item key={Actions.RENEW_JOB}>Renew the job</Menu.Item>}
<RemoveAnnotationsRangeComponent
visible={openRemoveRangeComponent}
removeinRange={removeRange}
stopFrame={stopFrame}
cancel={() => { toggleOpenRemoveRangeComponent(!openRemoveRangeComponent); }}
/>
</Menu>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (C) 2021 Intel Corporation
//
// SPDX-License-Identifier: MIT

import React, { useState } from 'react';

import Modal from 'antd/lib/modal';
import InputNumber from 'antd/lib/input-number';
import Text from 'antd/lib/typography/Text';
import Checkbox from 'antd/lib/checkbox';
import { clamp } from 'utils/math';

interface Props {
visible: boolean;
stopFrame: number;
removeinRange(startnumber:number, endnumber:number, delTrackKeyframesOnly:boolean): void;
cancel(): void;
}

export default function RemoveRangeConfirmComponent(props: Props): JSX.Element {
const {
visible,
stopFrame,
removeinRange,
cancel,
} = props;

const minStartFrames = 0;

const [startFrame, managestart] = useState<number>(0);
const [endFrame, manageend] = useState<number>(1);
const [delTrackKeyframesOnly, managedelTrackKeyframesOnly] = useState<boolean>(false);

const minEndFrames = Math.max(startFrame, 1);

return (
<Modal
okType='primary'
okText='Yes'
cancelText='Cancel'
onOk={() => {
removeinRange(startFrame, endFrame, delTrackKeyframesOnly);
cancel();
}}
onCancel={cancel}
title='Confirm to remove annotations in range'
visible={visible}
>
<div className='cvat-propagate-confirm'>
<Text>Do you want to remove the annotations on</Text>
<InputNumber
className='cvat-propagate-confirm-object-on-frames'
size='small'
min={minStartFrames}
max={stopFrame}
value={startFrame}
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
const newvalue = Math.floor(clamp(+value, 0, stopFrame - 1));
managestart(newvalue);
}
}}
/>
{startFrame > 1 ? <Text> frames </Text> : <Text> frame </Text>}
<Text>up to the </Text>
<InputNumber
className='cvat-propagate-confirm-object-up-to-frame'
size='small'
min={minEndFrames}
max={stopFrame}
value={endFrame}
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
const newvalue = Math.floor(clamp(+value, 1, stopFrame));
manageend(newvalue);
}
}}
/>
<Text>frame</Text>
,
<br />
<br />
<Checkbox onChange={() => { managedelTrackKeyframesOnly(!delTrackKeyframesOnly); }}>
Delete only track keyframes
</Checkbox>
</div>
</Modal>
);
}
Loading