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

* Test Commit for Remove Range

Test Commit for Remove Range

* Remove annotations in range merged with remove annotations button merged

Remove annotations in range merged with remove annotations button merged

* Update annotation-reducer.ts

* Update annotation-actions.ts

* Update annotation-reducer.ts

* Converting remove range component to hook based component

Removed all the global states previously used and converted all the parameters to local state in annotation menu and remove range component.

* Improved clear in cvat core and implemented remove range

Added arguments of startframe and endframe to clear method in annotation-collection, and also added the updating of the states with payload on removeannotationsinrangeasync action in the reducer.

* Matching only the needed parts

There are few additional old files that were needed to be removed to be completely matched with develop branch of cvat

* Delete out.json

* Update annotations-collection.js

* Added a checkbox to remove range modal

Added a checkbox to remove range modal that can be used to select if only the keyframes should be deleted in tracks or the whole track

* ESLint fixed

All the updated files were formatted as per ESLint except one line in that even cvat base is also overlooking i.e.
Row 162, Column 15: "JSX props should not use functions" in cvat\cvat-ui\src\components\annotation-page\top-bar\annotation-menu.tsx.

* More ESLint and other updates

Changed all the suggested changes and also removed unnecessary files in dist.
Removed unnecessary explicit removals in objects and additional wrappers.

* Update annotation-menu.tsx

Fixed the mistake of wrong variable name.

* Update remove-range-confirm.tsx

Additional ESLint Issue fixed

* Changed the approach of removeAnnotations modal

Changed the approach of removeAnnotations modal so that it could match the implementation of all the other components

* Added to changelog

Fixed type annotations in the annotation-menu component for remove annotations, and updated cvat-ui and cvat-core npm versions.
main
gudipudiramanakumar 4 years ago committed by GitHub
parent 4b2769c3e8
commit 78158cbcf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Progress bar for manifest creating (<https://github.com/openvinotoolkit/cvat/pull/3712>) - Progress bar for manifest creating (<https://github.com/openvinotoolkit/cvat/pull/3712>)
- Add a tutorial on attaching cloud storage AWS-S3 (<https://github.com/openvinotoolkit/cvat/pull/3745>) - Add a tutorial on attaching cloud storage AWS-S3 (<https://github.com/openvinotoolkit/cvat/pull/3745>)
and Azure Blob Container (<https://github.com/openvinotoolkit/cvat/pull/3778>) and Azure Blob Container (<https://github.com/openvinotoolkit/cvat/pull/3778>)
- The feature to remove annotations in a specified range of frames (<https://github.com/openvinotoolkit/cvat/pull/3617>)
### Changed ### Changed

@ -1,12 +1,12 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.16.1", "version": "3.17.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cvat-core", "name": "cvat-core",
"version": "3.16.1", "version": "3.17.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^0.21.4", "axios": "^0.21.4",

@ -1,6 +1,6 @@
{ {
"name": "cvat-core", "name": "cvat-core",
"version": "3.16.1", "version": "3.17.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration", "description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js", "main": "babel.config.js",
"scripts": { "scripts": {

@ -553,14 +553,40 @@
return groupIdx; return groupIdx;
} }
clear() { clear(startframe, endframe, delTrackKeyframesOnly) {
this.shapes = {}; if (startframe !== undefined && endframe !== undefined) {
this.tags = {}; // If only a range of annotations need to be cleared
this.tracks = []; for (let frame = startframe; frame <= endframe; frame++) {
this.objects = {}; // by id this.shapes[frame] = [];
this.count = 0; this.tags[frame] = [];
}
this.flush = true; 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) {
const index = tracks.indexOf(track);
if (index > -1) { tracks.splice(index, 1); }
}
}
});
} else if (startframe === undefined && endframe === undefined) {
// If all annotations need to be cleared
this.shapes = {};
this.tags = {};
this.tracks = [];
this.objects = {}; // by id
this.count = 0;
this.flush = true;
} else {
// If inputs provided were wrong
throw Error('Could not remove the annotations, please provide both inputs or'
+ ' leave the inputs below empty to remove all the annotations from this job');
}
} }
statistics() { statistics() {

@ -172,13 +172,13 @@
return false; return false;
} }
async function clearAnnotations(session, reload) { async function clearAnnotations(session, reload, startframe, endframe, delTrackKeyframesOnly) {
checkObjectType('reload', reload, 'boolean', null); checkObjectType('reload', reload, 'boolean', null);
const sessionType = session instanceof Task ? 'task' : 'job'; const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType); const cache = getCache(sessionType);
if (cache.has(session)) { if (cache.has(session)) {
cache.get(session).collection.clear(); cache.get(session).collection.clear(startframe, endframe, delTrackKeyframesOnly);
} }
if (reload) { if (reload) {

@ -37,8 +37,8 @@
return result; return result;
}, },
async clear(reload = false) { async clear(reload = false, startframe = undefined, endframe = undefined, delTrackKeyframesOnly = true) {
const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.clear, reload); const result = await PluginRegistry.apiWrapper.call(this, prototype.annotations.clear, reload, startframe, endframe, delTrackKeyframesOnly);
return result; return result;
}, },
@ -1897,8 +1897,8 @@
return result; return result;
}; };
Job.prototype.annotations.clear.implementation = async function (reload) { Job.prototype.annotations.clear.implementation = async function (reload, startframe, endframe, delTrackKeyframesOnly) {
const result = await clearAnnotations(this, reload); const result = await clearAnnotations(this, reload, startframe, endframe, delTrackKeyframesOnly);
return result; return result;
}; };

@ -1,12 +1,12 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.24.1", "version": "1.25.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.24.1", "version": "1.25.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.6.3", "@ant-design/icons": "^4.6.3",

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.24.1", "version": "1.25.0",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {

@ -306,17 +306,24 @@ export function updateCanvasContextMenu(
}; };
} }
export function removeAnnotationsAsync(sessionInstance: any): ThunkAction { export function removeAnnotationsAsync(
startFrame: number, endFrame: number, delTrackKeyframesOnly: boolean,
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
await sessionInstance.annotations.clear(); const {
await sessionInstance.actions.clear(); filters, frame, showAllInterpolationTracks, jobInstance,
const history = await sessionInstance.actions.get(); } = 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({ dispatch({
type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS, type: AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS,
payload: { payload: {
history, history,
states,
}, },
}); });
} catch (error) { } catch (error) {

@ -3,8 +3,13 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React from 'react';
import Menu from 'antd/lib/menu'; import Menu from 'antd/lib/menu';
import Modal from 'antd/lib/modal'; import Modal from 'antd/lib/modal';
import Text from 'antd/lib/typography/Text';
import {
InputNumber, Tooltip, Checkbox, Collapse,
} from 'antd';
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface'; import { MenuInfo } from 'rc-menu/lib/interface';
@ -20,6 +25,8 @@ interface Props {
jobInstance: any; jobInstance: any;
onClickMenu(params: MenuInfo): void; onClickMenu(params: MenuInfo): void;
onUploadAnnotations(format: string, file: File): void; onUploadAnnotations(format: string, file: File): void;
stopFrame: number;
removeAnnotations(startnumber: number, endnumber: number, delTrackKeyframesOnly:boolean): void;
setForceExitAnnotationFlag(forceExit: boolean): void; setForceExitAnnotationFlag(forceExit: boolean): void;
saveAnnotations(jobInstance: any, afterSave?: () => void): void; saveAnnotations(jobInstance: any, afterSave?: () => void): void;
} }
@ -41,8 +48,10 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
loadActivity, loadActivity,
isReviewer, isReviewer,
jobInstance, jobInstance,
stopFrame,
onClickMenu, onClickMenu,
onUploadAnnotations, onUploadAnnotations,
removeAnnotations,
setForceExitAnnotationFlag, setForceExitAnnotationFlag,
saveAnnotations, saveAnnotations,
} = props; } = props;
@ -80,14 +89,52 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
} }
if (params.key === Actions.REMOVE_ANNO) { if (params.key === Actions.REMOVE_ANNO) {
let removeFrom: number;
let removeUpTo: number;
let removeOnlyKeyframes = false;
const { Panel } = Collapse;
Modal.confirm({ Modal.confirm({
title: 'All the annotations will be removed', title: 'Remove Annotations',
content: content: (
'You are going to remove all the annotations from the client. ' + <div>
'It will stay on the server till you save the job. Continue?', <Text>You are going to remove the annotations from the client. </Text>
<Text>It will stay on the server till you save the job. Continue?</Text>
<br />
<br />
<Collapse bordered={false}>
<Panel header={<Text>Select Range</Text>} key={1}>
<Text>From: </Text>
<InputNumber
min={0}
max={stopFrame}
onChange={(value) => {
removeFrom = value;
}}
/>
<Text> To: </Text>
<InputNumber
min={0}
max={stopFrame}
onChange={(value) => { removeUpTo = value; }}
/>
<Tooltip title='Applicable only for annotations in range'>
<br />
<br />
<Checkbox
onChange={(check) => {
removeOnlyKeyframes = check.target.checked;
}}
>
Delete only keyframes for tracks
</Checkbox>
</Tooltip>
</Panel>
</Collapse>
</div>
),
className: 'cvat-modal-confirm-remove-annotation', className: 'cvat-modal-confirm-remove-annotation',
onOk: () => { onOk: () => {
onClickMenu(params); removeAnnotations(removeFrom, removeUpTo, removeOnlyKeyframes);
}, },
okButtonProps: { okButtonProps: {
type: 'primary', type: 'primary',
@ -127,7 +174,7 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
const is2d = jobInstance.task.dimension === DimensionType.DIM_2D; const is2d = jobInstance.task.dimension === DimensionType.DIM_2D;
return ( return (
<Menu onClick={onClickMenuWrapper} className='cvat-annotation-menu' selectable={false}> <Menu onClick={(params: MenuInfo) => onClickMenuWrapper(params)} className='cvat-annotation-menu' selectable={false}>
{LoadSubmenu({ {LoadSubmenu({
loaders, loaders,
loadActivity, loadActivity,

@ -13,17 +13,18 @@ import AnnotationMenuComponent, { Actions } from 'components/annotation-page/top
import { updateJobAsync } from 'actions/tasks-actions'; import { updateJobAsync } from 'actions/tasks-actions';
import { import {
uploadJobAnnotationsAsync, uploadJobAnnotationsAsync,
removeAnnotationsAsync,
saveAnnotationsAsync, saveAnnotationsAsync,
switchRequestReviewDialog as switchRequestReviewDialogAction, switchRequestReviewDialog as switchRequestReviewDialogAction,
switchSubmitReviewDialog as switchSubmitReviewDialogAction, switchSubmitReviewDialog as switchSubmitReviewDialogAction,
setForceExitAnnotationFlag as setForceExitAnnotationFlagAction, setForceExitAnnotationFlag as setForceExitAnnotationFlagAction,
removeAnnotationsAsync as removeAnnotationsAsyncAction,
} from 'actions/annotation-actions'; } from 'actions/annotation-actions';
import { exportActions } from 'actions/export-actions'; import { exportActions } from 'actions/export-actions';
interface StateToProps { interface StateToProps {
annotationFormats: any; annotationFormats: any;
jobInstance: any; jobInstance: any;
stopFrame: number;
loadActivity: string | null; loadActivity: string | null;
user: any; user: any;
} }
@ -31,7 +32,7 @@ interface StateToProps {
interface DispatchToProps { interface DispatchToProps {
loadAnnotations(job: any, loader: any, file: File): void; loadAnnotations(job: any, loader: any, file: File): void;
showExportModal(task: any): void; showExportModal(task: any): void;
removeAnnotations(sessionInstance: any): void; removeAnnotations(startnumber:number, endnumber:number, delTrackKeyframesOnly:boolean): void;
switchRequestReviewDialog(visible: boolean): void; switchRequestReviewDialog(visible: boolean): void;
switchSubmitReviewDialog(visible: boolean): void; switchSubmitReviewDialog(visible: boolean): void;
setForceExitAnnotationFlag(forceExit: boolean): void; setForceExitAnnotationFlag(forceExit: boolean): void;
@ -43,7 +44,10 @@ function mapStateToProps(state: CombinedState): StateToProps {
const { const {
annotation: { annotation: {
activities: { loads: jobLoads }, activities: { loads: jobLoads },
job: { instance: jobInstance }, job: {
instance: jobInstance,
instance: { stopFrame },
},
}, },
formats: { annotationFormats }, formats: { annotationFormats },
tasks: { tasks: {
@ -58,6 +62,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
return { return {
loadActivity: taskID in loads || jobID in jobLoads ? loads[taskID] || jobLoads[jobID] : null, loadActivity: taskID in loads || jobID in jobLoads ? loads[taskID] || jobLoads[jobID] : null,
jobInstance, jobInstance,
stopFrame,
annotationFormats, annotationFormats,
user, user,
}; };
@ -71,8 +76,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
showExportModal(task: any): void { showExportModal(task: any): void {
dispatch(exportActions.openExportModal(task)); dispatch(exportActions.openExportModal(task));
}, },
removeAnnotations(sessionInstance: any): void { removeAnnotations(startnumber: number, endnumber: number, delTrackKeyframesOnly:boolean) {
dispatch(removeAnnotationsAsync(sessionInstance)); dispatch(removeAnnotationsAsyncAction(startnumber, endnumber, delTrackKeyframesOnly));
}, },
switchRequestReviewDialog(visible: boolean): void { switchRequestReviewDialog(visible: boolean): void {
dispatch(switchRequestReviewDialogAction(visible)); dispatch(switchRequestReviewDialogAction(visible));
@ -97,6 +102,7 @@ type Props = StateToProps & DispatchToProps & RouteComponentProps;
function AnnotationMenuContainer(props: Props): JSX.Element { function AnnotationMenuContainer(props: Props): JSX.Element {
const { const {
jobInstance, jobInstance,
stopFrame,
user, user,
annotationFormats: { loaders, dumpers }, annotationFormats: { loaders, dumpers },
history, history,
@ -122,8 +128,6 @@ function AnnotationMenuContainer(props: Props): JSX.Element {
const [action] = params.keyPath; const [action] = params.keyPath;
if (action === Actions.EXPORT_TASK_DATASET) { if (action === Actions.EXPORT_TASK_DATASET) {
showExportModal(jobInstance.task); showExportModal(jobInstance.task);
} else if (action === Actions.REMOVE_ANNO) {
removeAnnotations(jobInstance);
} else if (action === Actions.REQUEST_REVIEW) { } else if (action === Actions.REQUEST_REVIEW) {
switchRequestReviewDialog(true); switchRequestReviewDialog(true);
} else if (action === Actions.SUBMIT_REVIEW) { } else if (action === Actions.SUBMIT_REVIEW) {
@ -151,10 +155,12 @@ function AnnotationMenuContainer(props: Props): JSX.Element {
loadActivity={loadActivity} loadActivity={loadActivity}
onUploadAnnotations={onUploadAnnotations} onUploadAnnotations={onUploadAnnotations}
onClickMenu={onClickMenu} onClickMenu={onClickMenu}
removeAnnotations={removeAnnotations}
setForceExitAnnotationFlag={setForceExitAnnotationFlag} setForceExitAnnotationFlag={setForceExitAnnotationFlag}
saveAnnotations={saveAnnotations} saveAnnotations={saveAnnotations}
jobInstance={jobInstance} jobInstance={jobInstance}
isReviewer={isReviewer} isReviewer={isReviewer}
stopFrame={stopFrame}
/> />
); );
} }

@ -917,16 +917,19 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
}, },
}; };
} }
// Added Remove Annotations
case AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS: { case AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS: {
const { history } = action.payload; const { history } = action.payload;
const { states } = action.payload;
return { return {
...state, ...state,
annotations: { annotations: {
...state.annotations, ...state.annotations,
history, history,
states,
selectedStatesID: [],
activatedStateID: null, activatedStateID: null,
collapsed: {}, collapsed: {},
states: [],
}, },
}; };
} }

Loading…
Cancel
Save