Skip to content

Commit

Permalink
Az/import export tasks (#3056)
Browse files Browse the repository at this point in the history
* initial version of task export/import feature

* fixed tests

* CLI

* fix comments

* updated license headers

* fix eslint issues

* fix comments

* fixed comments

* reverted changes in *.md files

* fixed comments

* fix pylint issues

* fix import for share case

* improved unit tests

* updated changelog

* fixed Maria's comments

* fixed comments

* Fixed position of create new task button

* Fixed span position

* fixed comments

Co-authored-by: Nikita Manovich <[email protected]>
Co-authored-by: Boris Sekachev <[email protected]>
  • Loading branch information
3 people authored Jun 8, 2021
1 parent 6665fe1 commit 72fdef4
Show file tree
Hide file tree
Showing 36 changed files with 1,894 additions and 258 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Support of context images for 2D image tasks (<https://github.com/openvinotoolkit/cvat/pull/3122>)
- Filter `is_active` for user list (<https://github.com/openvinotoolkit/cvat/pull/3235>)
- Ability to export/import tasks (<https://github.com/openvinotoolkit/cvat/pull/3056>)


### Changed

Expand Down
55 changes: 55 additions & 0 deletions cvat-core/src/server-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,59 @@
});
}

async function exportTask(id) {
const { backendAPI } = config;
const url = `${backendAPI}/tasks/${id}`;

return new Promise((resolve, reject) => {
async function request() {
try {
const response = await Axios.get(`${url}?action=export`, {
proxy: config.proxy,
});
if (response.status === 202) {
setTimeout(request, 3000);
} else {
resolve(`${url}?action=download`);
}
} catch (errorData) {
reject(generateError(errorData));
}
}

setTimeout(request);
});
}

async function importTask(file) {
const { backendAPI } = config;

let taskData = new FormData();
taskData.append('task_file', file);

return new Promise((resolve, reject) => {
async function request() {
try {
const response = await Axios.post(`${backendAPI}/tasks?action=import`, taskData, {
proxy: config.proxy,
});
if (response.status === 202) {
taskData = new FormData();
taskData.append('rq_id', response.data.rq_id);
setTimeout(request, 3000);
} else {
const importedTask = await getTasks(`?id=${response.data.id}`);
resolve(importedTask[0]);
}
} catch (errorData) {
reject(generateError(errorData));
}
}

setTimeout(request);
});
}

async function createTask(taskSpec, taskDataSpec, onUpdate) {
const { backendAPI } = config;

Expand Down Expand Up @@ -1157,6 +1210,8 @@
createTask,
deleteTask,
exportDataset,
exportTask,
importTask,
}),
writable: false,
},
Expand Down
40 changes: 40 additions & 0 deletions cvat-core/src/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,36 @@
const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.delete);
return result;
}

/**
* Method makes a backup of a task
* @method export
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async export() {
const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.export);
return result;
}

/**
* Method imports a task from a backup
* @method import
* @memberof module:API.cvat.classes.Task
* @readonly
* @instance
* @async
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
static async import(file) {
const result = await PluginRegistry.apiWrapper.call(this, Task.import, file);
return result;
}
}

module.exports = {
Expand Down Expand Up @@ -2073,6 +2103,16 @@
return result;
};

Task.prototype.export.implementation = async function () {
const result = await serverProxy.tasks.exportTask(this.id);
return result;
};

Task.import.implementation = async function (file) {
const result = await serverProxy.tasks.importTask(file);
return result;
};

Task.prototype.frames.get.implementation = async function (frame, isPlaying, step) {
if (!Number.isInteger(frame) || frame < 0) {
throw new ArgumentError(`Frame must be a positive integer. Got: "${frame}"`);
Expand Down
12 changes: 6 additions & 6 deletions cvat-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cvat-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"mousetrap": "^1.6.5",
"platform": "^1.3.6",
"prop-types": "^15.7.2",
"rc-menu": "^8.10.7",
"react": "^16.14.0",
"react-awesome-query-builder": "^3.0.0",
"react-color": "^2.19.3",
Expand Down
99 changes: 99 additions & 0 deletions cvat-ui/src/actions/tasks-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export enum TasksActionTypes {
UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
HIDE_EMPTY_TASKS = 'HIDE_EMPTY_TASKS',
EXPORT_TASK = 'EXPORT_TASK',
EXPORT_TASK_SUCCESS = 'EXPORT_TASK_SUCCESS',
EXPORT_TASK_FAILED = 'EXPORT_TASK_FAILED',
IMPORT_TASK = 'IMPORT_TASK',
IMPORT_TASK_SUCCESS = 'IMPORT_TASK_SUCCESS',
IMPORT_TASK_FAILED = 'IMPORT_TASK_FAILED',
SWITCH_MOVE_TASK_MODAL_VISIBLE = 'SWITCH_MOVE_TASK_MODAL_VISIBLE',
}

Expand Down Expand Up @@ -214,6 +220,49 @@ export function loadAnnotationsAsync(
};
}

function importTask(): AnyAction {
const action = {
type: TasksActionTypes.IMPORT_TASK,
payload: {},
};

return action;
}

function importTaskSuccess(task: any): AnyAction {
const action = {
type: TasksActionTypes.IMPORT_TASK_SUCCESS,
payload: {
task,
},
};

return action;
}

function importTaskFailed(error: any): AnyAction {
const action = {
type: TasksActionTypes.IMPORT_TASK_FAILED,
payload: {
error,
},
};

return action;
}

export function importTaskAsync(file: File): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch(importTask());
const taskInstance = await cvat.classes.Task.import(file);
dispatch(importTaskSuccess(taskInstance));
} catch (error) {
dispatch(importTaskFailed(error));
}
};
}

function exportDataset(task: any, exporter: any): AnyAction {
const action = {
type: TasksActionTypes.EXPORT_DATASET,
Expand Down Expand Up @@ -268,6 +317,56 @@ export function exportDatasetAsync(task: any, exporter: any): ThunkAction<Promis
};
}

function exportTask(taskID: number): AnyAction {
const action = {
type: TasksActionTypes.EXPORT_TASK,
payload: {
taskID,
},
};

return action;
}

function exportTaskSuccess(taskID: number): AnyAction {
const action = {
type: TasksActionTypes.EXPORT_TASK_SUCCESS,
payload: {
taskID,
},
};

return action;
}

function exportTaskFailed(taskID: number, error: Error): AnyAction {
const action = {
type: TasksActionTypes.EXPORT_TASK_FAILED,
payload: {
taskID,
error,
},
};

return action;
}

export function exportTaskAsync(taskInstance: any): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
dispatch(exportTask(taskInstance.id));

try {
const url = await taskInstance.export();
const downloadAnchor = window.document.getElementById('downloadAnchor') as HTMLAnchorElement;
downloadAnchor.href = url;
downloadAnchor.click();
dispatch(exportTaskSuccess(taskInstance.id));
} catch (error) {
dispatch(exportTaskFailed(taskInstance.id, error));
}
};
}

function deleteTask(taskID: number): AnyAction {
const action = {
type: TasksActionTypes.DELETE_TASK,
Expand Down
8 changes: 8 additions & 0 deletions cvat-ui/src/components/actions-menu/actions-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './styles.scss';
import React from 'react';
import Menu from 'antd/lib/menu';
import Modal from 'antd/lib/modal';
import { LoadingOutlined } from '@ant-design/icons';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface';
import DumpSubmenu from './dump-submenu';
Expand All @@ -25,6 +26,7 @@ interface Props {
inferenceIsActive: boolean;
taskDimension: DimensionType;
onClickMenu: (params: MenuInfo, file?: File) => void;
exportIsActive: boolean;
}

export enum Actions {
Expand All @@ -35,6 +37,7 @@ export enum Actions {
RUN_AUTO_ANNOTATION = 'run_auto_annotation',
MOVE_TASK_TO_PROJECT = 'move_task_to_project',
OPEN_BUG_TRACKER = 'open_bug_tracker',
EXPORT_TASK = 'export_task',
}

export default function ActionsMenuComponent(props: Props): JSX.Element {
Expand All @@ -50,6 +53,7 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
exportActivities,
loadActivity,
taskDimension,
exportIsActive,
} = props;

let latestParams: MenuInfo | null = null;
Expand Down Expand Up @@ -128,6 +132,10 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
<Menu.Item disabled={inferenceIsActive} key={Actions.RUN_AUTO_ANNOTATION}>
Automatic annotation
</Menu.Item>
<Menu.Item key={Actions.EXPORT_TASK} disabled={exportIsActive}>
{exportIsActive && <LoadingOutlined id='cvat-export-task-loading' />}
Export Task
</Menu.Item>
<hr />
<Menu.Item key={Actions.MOVE_TASK_TO_PROJECT}>Move to project</Menu.Item>
<Menu.Item key={Actions.DELETE_TASK}>Delete</Menu.Item>
Expand Down
4 changes: 4 additions & 0 deletions cvat-ui/src/components/actions-menu/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@
.cvat-menu-icon {
transform: scale(0.5);
}

#cvat-export-task-loading {
margin-left: 10;
}
7 changes: 6 additions & 1 deletion cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ function ShortcutsDialog(props: StateToProps & DispatchToProps): JSX.Element | n
zIndex={1001} /* default antd is 1000 */
className='cvat-shortcuts-modal-window'
>
<Table dataSource={dataSource} columns={columns} size='small' className='cvat-shortcuts-modal-window-table' />
<Table
dataSource={dataSource}
columns={columns}
size='small'
className='cvat-shortcuts-modal-window-table'
/>
</Modal>
);
}
Expand Down
Loading

0 comments on commit 72fdef4

Please sign in to comment.