diff --git a/cvat-core/src/annotations.js b/cvat-core/src/annotations.js index 8c9014caf52d..a892512c2811 100644 --- a/cvat-core/src/annotations.js +++ b/cvat-core/src/annotations.js @@ -225,6 +225,25 @@ return result; } + async function exportDataset(session, format) { + if (!(format instanceof String || typeof format === 'string')) { + throw new ArgumentError( + 'Format must be a string', + ); + } + if (!(session instanceof Task)) { + throw new ArgumentError( + 'A dataset can only be created from a task', + ); + } + + let result = null; + result = await serverProxy.tasks + .exportDataset(session.id, format); + + return result; + } + module.exports = { getAnnotations, putAnnotations, @@ -238,5 +257,6 @@ selectObject, uploadAnnotations, dumpAnnotations, + exportDataset, }; })(); diff --git a/cvat-core/src/api-implementation.js b/cvat-core/src/api-implementation.js index 72213c942a0f..2aef836ddb5e 100644 --- a/cvat-core/src/api-implementation.js +++ b/cvat-core/src/api-implementation.js @@ -70,6 +70,11 @@ return result.map((el) => new AnnotationFormat(el)); }; + cvat.server.datasetFormats.implementation = async () => { + const result = await serverProxy.server.datasetFormats(); + return result; + }; + cvat.server.register.implementation = async (username, firstName, lastName, email, password1, password2) => { await serverProxy.server.register(username, firstName, lastName, email, diff --git a/cvat-core/src/api.js b/cvat-core/src/api.js index 21c2299a2493..51d07627fa76 100644 --- a/cvat-core/src/api.js +++ b/cvat-core/src/api.js @@ -115,6 +115,20 @@ function build() { .apiWrapper(cvat.server.formats); return result; }, + /** + * Method returns available dataset export formats + * @method exportFormats + * @async + * @memberof module:API.cvat.server + * @returns {module:String[]} + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + */ + async datasetFormats() { + const result = await PluginRegistry + .apiWrapper(cvat.server.datasetFormats); + return result; + }, /** * Method allows to register on a server * @method register diff --git a/cvat-core/src/server-proxy.js b/cvat-core/src/server-proxy.js index 143cb8e16592..aa9982347fe6 100644 --- a/cvat-core/src/server-proxy.js +++ b/cvat-core/src/server-proxy.js @@ -101,6 +101,22 @@ return response.data; } + async function datasetFormats() { + const { backendAPI } = config; + + let response = null; + try { + response = await Axios.get(`${backendAPI}/server/dataset/formats`, { + proxy: config.proxy, + }); + response = JSON.parse(response.data); + } catch (errorData) { + throw generateError(errorData, 'Could not get export formats from the server'); + } + + return response; + } + async function register(username, firstName, lastName, email, password1, password2) { let response = null; try { @@ -223,6 +239,35 @@ } } + async function exportDataset(id, format) { + const { backendAPI } = config; + let url = `${backendAPI}/tasks/${id}/dataset?format=${format}`; + + return new Promise((resolve, reject) => { + async function request() { + try { + const response = await Axios + .get(`${url}`, { + proxy: config.proxy, + }); + if (response.status === 202) { + setTimeout(request, 3000); + } else { + url = `${url}&action=download`; + resolve(url); + } + } catch (errorData) { + reject(generateError( + errorData, + `Failed to export the task ${id} as a dataset`, + )); + } + } + + setTimeout(request); + }); + } + async function createTask(taskData, files, onUpdate) { const { backendAPI } = config; @@ -555,6 +600,7 @@ about, share, formats, + datasetFormats, exception, login, logout, @@ -570,6 +616,7 @@ saveTask, createTask, deleteTask, + exportDataset, }), writable: false, }, diff --git a/cvat-core/src/session.js b/cvat-core/src/session.js index aa681c0e3059..55ff05b35d1b 100644 --- a/cvat-core/src/session.js +++ b/cvat-core/src/session.js @@ -100,6 +100,12 @@ objectStates, reset); return result; }, + + async exportDataset(format) { + const result = await PluginRegistry + .apiWrapper.call(this, prototype.annotations.exportDataset, format); + return result; + }, }, writable: true, }), @@ -367,6 +373,19 @@ * @instance * @async */ + /** + * Export as a dataset. + * Method builds a dataset in the specified format. + * @method exportDataset + * @memberof Session.annotations + * @param {module:String} format - a format + * @returns {string} An URL to the dataset file + * @throws {module:API.cvat.exceptions.PluginError} + * @throws {module:API.cvat.exceptions.ServerError} + * @throws {module:API.cvat.exceptions.ArgumentError} + * @instance + * @async + */ /** @@ -1132,6 +1151,8 @@ statistics: Object.getPrototypeOf(this).annotations.statistics.bind(this), hasUnsavedChanges: Object.getPrototypeOf(this) .annotations.hasUnsavedChanges.bind(this), + exportDataset: Object.getPrototypeOf(this) + .annotations.exportDataset.bind(this), }; this.frames = { @@ -1195,6 +1216,7 @@ annotationsStatistics, uploadAnnotations, dumpAnnotations, + exportDataset, } = require('./annotations'); buildDublicatedAPI(Job.prototype); @@ -1457,4 +1479,9 @@ const result = await dumpAnnotations(this, name, dumper); return result; }; + + Task.prototype.annotations.exportDataset.implementation = async function (format) { + const result = await exportDataset(this, format); + return result; + }; })(); diff --git a/cvat/apps/dashboard/static/dashboard/js/dashboard.js b/cvat/apps/dashboard/static/dashboard/js/dashboard.js index 8ebf05ea934c..bd9e6bb5de30 100644 --- a/cvat/apps/dashboard/static/dashboard/js/dashboard.js +++ b/cvat/apps/dashboard/static/dashboard/js/dashboard.js @@ -13,9 +13,10 @@ */ class TaskView { - constructor(task, annotationFormats) { + constructor(task, annotationFormats, exportFormats) { this.init(task); this._annotationFormats = annotationFormats; + this._exportFormats = exportFormats; this._UI = null; } @@ -109,6 +110,28 @@ class TaskView { } } + async _exportDataset(button, formatName) { + button.disabled = true; + try { + const format = this._exportFormats.find((x) => { + return x.name == formatName; + }); + if (!format) { + throw `Unknown dataset export format '${formatName}'`; + } + const url = await this._task.annotations.exportDataset(format.tag); + const tempElem = document.createElement('a'); + tempElem.href = `${url}`; + document.body.appendChild(tempElem); + tempElem.click(); + tempElem.remove(); + } catch (error) { + showMessage(error.message); + } finally { + button.disabled = false; + } + } + init(task) { this._task = task; } @@ -169,6 +192,22 @@ class TaskView { downloadButton.appendTo(buttonsContainer); uploadButton.appendTo(buttonsContainer); + const exportButton = $(''); + $('').appendTo(exportButton); + for (const format of this._exportFormats) { + const item = $(`