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

Add a dataset export button for tasks #834

Merged
merged 6 commits into from
Nov 26, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
20 changes: 20 additions & 0 deletions cvat-core/src/annotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -238,5 +257,6 @@
selectObject,
uploadAnnotations,
dumpAnnotations,
exportDataset,
};
})();
5 changes: 5 additions & 0 deletions cvat-core/src/api-implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
return result.map((el) => new AnnotationFormat(el));
};

cvat.server.datasetExportFormats.implementation = async () => {
const result = await serverProxy.server.datasetExportFormats();
return result;
};

cvat.server.register.implementation = async (username, firstName, lastName,
email, password1, password2) => {
await serverProxy.server.register(username, firstName, lastName, email,
Expand Down
14 changes: 14 additions & 0 deletions cvat-core/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 datasetExportFormats() {
const result = await PluginRegistry
.apiWrapper(cvat.server.datasetExportFormats);
return result;
},
/**
* Method allows to register on a server
* @method register
Expand Down
47 changes: 47 additions & 0 deletions cvat-core/src/server-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@
return response.data;
}

async function datasetExportFormats() {
const { backendAPI } = config;

let response = null;
try {
response = await Axios.get(`${backendAPI}/server/annotation/dataset_export_formats`, {
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down Expand Up @@ -223,6 +239,35 @@
}
}

async function exportDataset(id, format) {
const { backendAPI } = config;
let url = `${backendAPI}/tasks/${id}/export?format=${format}`;
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved

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;

Expand Down Expand Up @@ -555,6 +600,7 @@
about,
share,
formats,
datasetExportFormats,
exception,
login,
logout,
Expand All @@ -570,6 +616,7 @@
saveTask,
createTask,
deleteTask,
exportDataset,
}),
writable: false,
},
Expand Down
27 changes: 27 additions & 0 deletions cvat-core/src/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand Down Expand Up @@ -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
*/


/**
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -1195,6 +1216,7 @@
annotationsStatistics,
uploadAnnotations,
dumpAnnotations,
exportDataset,
} = require('./annotations');

buildDublicatedAPI(Job.prototype);
Expand Down Expand Up @@ -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;
};
})();
83 changes: 78 additions & 5 deletions cvat/apps/dashboard/static/dashboard/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -109,6 +110,32 @@ 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;
}
}

_isDefaultExportFormat(formatTag) {
return formatTag == 'datumaro_project';
}

init(task) {
this._task = task;
}
Expand Down Expand Up @@ -169,6 +196,22 @@ class TaskView {
downloadButton.appendTo(buttonsContainer);
uploadButton.appendTo(buttonsContainer);

const exportButton = $('<select class="regular dashboardButtonUI"'
+ 'style="text-align-last: center;"> Export as Dataset </select>');
$('<option selected disabled> Export as Dataset </option>').appendTo(exportButton);
for (const format of this._exportFormats) {
const item = $(`<option>${format.name}</li>`);
if (this._isDefaultExportFormat(format.tag)) {
item.addClass('bold');
}
item.appendTo(exportButton);
}
exportButton.on('change', (e) => {
this._exportDataset(e.target, e.target.value);
exportButton.prop('value', 'Export as Dataset');
});
exportButton.appendTo(buttonsContainer)

$('<button class="regular dashboardButtonUI"> Update Task </button>').on('click', () => {
this._update();
}).appendTo(buttonsContainer);
Expand Down Expand Up @@ -207,20 +250,47 @@ class TaskView {


class DashboardView {
constructor(metaData, taskData, annotationFormats) {
constructor(metaData, taskData, annotationFormats, exportFormats) {
this._dashboardList = taskData.results;
this._maxUploadSize = metaData.max_upload_size;
this._maxUploadCount = metaData.max_upload_count;
this._baseURL = metaData.base_url;
this._sharePath = metaData.share_path;
this._params = {};
this._annotationFormats = annotationFormats;
this._exportFormats = [];

this._setupExportFormats(exportFormats);
this._setupList();
this._setupTaskSearch();
this._setupCreateDialog();
}

_setupExportFormats(availableFormats) {
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved
const publicFormats = [];

if (-1 != availableFormats.indexOf('datumaro_project')) {
publicFormats.push({
tag: 'datumaro_project',
name: 'Datumaro',
});
}
if (-1 != availableFormats.indexOf('voc')) {
publicFormats.push({
tag: 'voc',
name: 'PASCAL VOC 2012',
});
}
if (-1 != availableFormats.indexOf('coco')) {
publicFormats.push({
tag: 'coco',
name: 'MS COCO',
});
}

this._exportFormats = publicFormats;
}

_setupList() {
const dashboardList = $('#dashboardList');
const dashboardPagination = $('#dashboardPagination');
Expand Down Expand Up @@ -273,7 +343,8 @@ class DashboardView {
}));

for (const task of tasks) {
const taskView = new TaskView(task, this._annotationFormats);
const taskView = new TaskView(task,
this._annotationFormats, this._exportFormats);
dashboardList.append(taskView.render(baseURL));
}

Expand Down Expand Up @@ -735,9 +806,11 @@ window.addEventListener('DOMContentLoaded', () => {
$.get('/dashboard/meta'),
$.get(`/api/v1/tasks${window.location.search}`),
window.cvat.server.formats(),
).then((metaData, taskData, annotationFormats) => {
window.cvat.server.datasetExportFormats(),
).then((metaData, taskData, annotationFormats, exportFormats) => {
try {
new DashboardView(metaData[0], taskData[0], annotationFormats);
new DashboardView(metaData[0], taskData[0],
annotationFormats, exportFormats);
} catch (exception) {
$('#content').empty();
const message = `Can not build CVAT dashboard. Exception: ${exception}.`;
Expand Down
23 changes: 17 additions & 6 deletions cvat/apps/dataset_manager/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def log_exception(logger=None, exc_info=True):
def get_export_cache_dir(db_task):
return osp.join(db_task.get_task_dirname(), 'export_cache')

EXPORT_FORMAT_DATUMARO_PROJECT = "datumaro_project"
zhiltsov-max marked this conversation as resolved.
Show resolved Hide resolved


class TaskProject:
@staticmethod
def _get_datumaro_project_dir(db_task):
Expand Down Expand Up @@ -211,9 +214,7 @@ def save(self, save_dir=None, save_images=False):
def export(self, dst_format, save_dir, save_images=False, server_url=None):
if self._dataset is None:
self._init_dataset()
if dst_format == DEFAULT_FORMAT:
self._dataset.save(save_dir=save_dir, save_images=save_images)
elif dst_format == DEFAULT_FORMAT_REMOTE:
if dst_format == EXPORT_FORMAT_DATUMARO_PROJECT:
self._remote_export(save_dir=save_dir, server_url=server_url)
else:
self._dataset.export(output_format=dst_format,
Expand Down Expand Up @@ -291,8 +292,7 @@ def _remote_export(self, save_dir, server_url=None):
])


DEFAULT_FORMAT = "datumaro_project"
DEFAULT_FORMAT_REMOTE = "datumaro_project_remote"
DEFAULT_FORMAT = EXPORT_FORMAT_DATUMARO_PROJECT
DEFAULT_CACHE_TTL = timedelta(hours=10)
CACHE_TTL = DEFAULT_CACHE_TTL

Expand Down Expand Up @@ -348,4 +348,15 @@ def clear_export_cache(task_id, file_path, file_ctime):
.format(file_path))
except Exception:
log_exception(slogger.task[task_id])
raise
raise

def get_export_formats():
from datumaro.components import converters

formats = [
EXPORT_FORMAT_DATUMARO_PROJECT,
]

for name, _ in converters.items:
formats.append(name)
return formats
6 changes: 3 additions & 3 deletions cvat/apps/engine/static/engine/js/cvat-core.min.js

Large diffs are not rendered by default.

Loading