From 78158cbcf5a19ea11389a7dfe7d6d6ac6df8dd06 Mon Sep 17 00:00:00 2001 From: gudipudiramanakumar <47365328+gudipudiramanakumar@users.noreply.github.com> Date: Tue, 26 Oct 2021 18:36:54 +0530 Subject: [PATCH] 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. --- CHANGELOG.md | 1 + cvat-core/package-lock.json | 4 +- cvat-core/package.json | 2 +- cvat-core/src/annotations-collection.js | 42 ++++++++++--- cvat-core/src/annotations.js | 4 +- cvat-core/src/session.js | 8 +-- cvat-ui/package-lock.json | 4 +- cvat-ui/package.json | 2 +- cvat-ui/src/actions/annotation-actions.ts | 15 +++-- .../top-bar/annotation-menu.tsx | 59 +++++++++++++++++-- .../top-bar/annotation-menu.tsx | 20 ++++--- cvat-ui/src/reducers/annotation-reducer.ts | 5 +- 12 files changed, 128 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b623b110e..37c4fd423ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Progress bar for manifest creating () - Add a tutorial on attaching cloud storage AWS-S3 () and Azure Blob Container () +- The feature to remove annotations in a specified range of frames () ### Changed diff --git a/cvat-core/package-lock.json b/cvat-core/package-lock.json index a3ed6aba99a..940de2eb3a6 100644 --- a/cvat-core/package-lock.json +++ b/cvat-core/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-core", - "version": "3.16.1", + "version": "3.17.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-core", - "version": "3.16.1", + "version": "3.17.0", "license": "MIT", "dependencies": { "axios": "^0.21.4", diff --git a/cvat-core/package.json b/cvat-core/package.json index ac58beec6fc..fa63b2d80ab 100644 --- a/cvat-core/package.json +++ b/cvat-core/package.json @@ -1,6 +1,6 @@ { "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", "main": "babel.config.js", "scripts": { diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 2c611eea820..beb63a47cf6 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -553,14 +553,40 @@ return groupIdx; } - clear() { - this.shapes = {}; - this.tags = {}; - this.tracks = []; - this.objects = {}; // by id - this.count = 0; - - this.flush = true; + clear(startframe, endframe, delTrackKeyframesOnly) { + if (startframe !== undefined && endframe !== undefined) { + // If only a range of annotations need to be cleared + for (let frame = startframe; frame <= endframe; frame++) { + 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) { + 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() { diff --git a/cvat-core/src/annotations.js b/cvat-core/src/annotations.js index 07ce90f9e2f..9b9cd6955ca 100644 --- a/cvat-core/src/annotations.js +++ b/cvat-core/src/annotations.js @@ -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) { diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index eaba48bc078..0914e9a892c 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -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; }, @@ -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; }; diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 0e292cc20c8..6c676a570aa 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "cvat-ui", - "version": "1.24.1", + "version": "1.25.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cvat-ui", - "version": "1.24.1", + "version": "1.25.0", "license": "MIT", "dependencies": { "@ant-design/icons": "^4.6.3", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index c703b786bac..a7a9ef5c1d8 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.24.1", + "version": "1.25.0", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 2aaa79c7bfa..ad01f440438 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -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): Promise => { try { - await sessionInstance.annotations.clear(); - await sessionInstance.actions.clear(); - const history = await sessionInstance.actions.get(); + 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_JOB_ANNOTATIONS_SUCCESS, payload: { history, + states, }, }); } catch (error) { diff --git a/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx index 1c120c2f015..72ff834dd43 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/annotation-menu.tsx @@ -3,8 +3,13 @@ // SPDX-License-Identifier: MIT import React from 'react'; + import Menu from 'antd/lib/menu'; 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 import { MenuInfo } from 'rc-menu/lib/interface'; @@ -20,6 +25,8 @@ interface Props { jobInstance: any; onClickMenu(params: MenuInfo): void; onUploadAnnotations(format: string, file: File): void; + stopFrame: number; + removeAnnotations(startnumber: number, endnumber: number, delTrackKeyframesOnly:boolean): void; setForceExitAnnotationFlag(forceExit: boolean): void; saveAnnotations(jobInstance: any, afterSave?: () => void): void; } @@ -41,8 +48,10 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { loadActivity, isReviewer, jobInstance, + stopFrame, onClickMenu, onUploadAnnotations, + removeAnnotations, setForceExitAnnotationFlag, saveAnnotations, } = props; @@ -80,14 +89,52 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { } if (params.key === Actions.REMOVE_ANNO) { + let removeFrom: number; + let removeUpTo: number; + let removeOnlyKeyframes = false; + const { Panel } = Collapse; Modal.confirm({ - 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?', + title: 'Remove Annotations', + content: ( +
+ You are going to remove the annotations from the client. + It will stay on the server till you save the job. Continue? +
+
+ + Select Range} key={1}> + From: + { + removeFrom = value; + }} + /> + To: + { removeUpTo = value; }} + /> + +
+
+ { + removeOnlyKeyframes = check.target.checked; + }} + > + Delete only keyframes for tracks + +
+
+
+
+ ), className: 'cvat-modal-confirm-remove-annotation', onOk: () => { - onClickMenu(params); + removeAnnotations(removeFrom, removeUpTo, removeOnlyKeyframes); }, okButtonProps: { type: 'primary', @@ -127,7 +174,7 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element { const is2d = jobInstance.task.dimension === DimensionType.DIM_2D; return ( - + onClickMenuWrapper(params)} className='cvat-annotation-menu' selectable={false}> {LoadSubmenu({ loaders, loadActivity, diff --git a/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx b/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx index 93ef4443520..bd2121595bb 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/annotation-menu.tsx @@ -13,17 +13,18 @@ import AnnotationMenuComponent, { Actions } from 'components/annotation-page/top import { updateJobAsync } from 'actions/tasks-actions'; import { uploadJobAnnotationsAsync, - removeAnnotationsAsync, saveAnnotationsAsync, switchRequestReviewDialog as switchRequestReviewDialogAction, switchSubmitReviewDialog as switchSubmitReviewDialogAction, setForceExitAnnotationFlag as setForceExitAnnotationFlagAction, + removeAnnotationsAsync as removeAnnotationsAsyncAction, } from 'actions/annotation-actions'; import { exportActions } from 'actions/export-actions'; interface StateToProps { annotationFormats: any; jobInstance: any; + stopFrame: number; loadActivity: string | null; user: any; } @@ -31,7 +32,7 @@ interface StateToProps { interface DispatchToProps { loadAnnotations(job: any, loader: any, file: File): void; showExportModal(task: any): void; - removeAnnotations(sessionInstance: any): void; + removeAnnotations(startnumber:number, endnumber:number, delTrackKeyframesOnly:boolean): void; switchRequestReviewDialog(visible: boolean): void; switchSubmitReviewDialog(visible: boolean): void; setForceExitAnnotationFlag(forceExit: boolean): void; @@ -43,7 +44,10 @@ function mapStateToProps(state: CombinedState): StateToProps { const { annotation: { activities: { loads: jobLoads }, - job: { instance: jobInstance }, + job: { + instance: jobInstance, + instance: { stopFrame }, + }, }, formats: { annotationFormats }, tasks: { @@ -58,6 +62,7 @@ function mapStateToProps(state: CombinedState): StateToProps { return { loadActivity: taskID in loads || jobID in jobLoads ? loads[taskID] || jobLoads[jobID] : null, jobInstance, + stopFrame, annotationFormats, user, }; @@ -71,8 +76,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { showExportModal(task: any): void { dispatch(exportActions.openExportModal(task)); }, - removeAnnotations(sessionInstance: any): void { - dispatch(removeAnnotationsAsync(sessionInstance)); + removeAnnotations(startnumber: number, endnumber: number, delTrackKeyframesOnly:boolean) { + dispatch(removeAnnotationsAsyncAction(startnumber, endnumber, delTrackKeyframesOnly)); }, switchRequestReviewDialog(visible: boolean): void { dispatch(switchRequestReviewDialogAction(visible)); @@ -97,6 +102,7 @@ type Props = StateToProps & DispatchToProps & RouteComponentProps; function AnnotationMenuContainer(props: Props): JSX.Element { const { jobInstance, + stopFrame, user, annotationFormats: { loaders, dumpers }, history, @@ -122,8 +128,6 @@ function AnnotationMenuContainer(props: Props): JSX.Element { const [action] = params.keyPath; if (action === Actions.EXPORT_TASK_DATASET) { showExportModal(jobInstance.task); - } else if (action === Actions.REMOVE_ANNO) { - removeAnnotations(jobInstance); } else if (action === Actions.REQUEST_REVIEW) { switchRequestReviewDialog(true); } else if (action === Actions.SUBMIT_REVIEW) { @@ -151,10 +155,12 @@ function AnnotationMenuContainer(props: Props): JSX.Element { loadActivity={loadActivity} onUploadAnnotations={onUploadAnnotations} onClickMenu={onClickMenu} + removeAnnotations={removeAnnotations} setForceExitAnnotationFlag={setForceExitAnnotationFlag} saveAnnotations={saveAnnotations} jobInstance={jobInstance} isReviewer={isReviewer} + stopFrame={stopFrame} /> ); } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 3344d5deaf6..72d84616350 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -917,16 +917,19 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + // Added Remove Annotations case AnnotationActionTypes.REMOVE_JOB_ANNOTATIONS_SUCCESS: { const { history } = action.payload; + const { states } = action.payload; return { ...state, annotations: { ...state.annotations, history, + states, + selectedStatesID: [], activatedStateID: null, collapsed: {}, - states: [], }, }; }