Skip to content

Commit

Permalink
Add dataset export button for tasks in dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiltsov-max committed Nov 14, 2019
1 parent 0ae1162 commit f364a5a
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 12 deletions.
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
43 changes: 43 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`, {
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,31 @@
}
}

async function exportDataset(id, format) {
const { backendAPI } = config;
let url = `${backendAPI}/tasks/${id}/export?format=${format}`;

return new Promise((resolve, reject) => {
async function request() {
try {
const response = await Axios
.get(`${url}`, {
proxy: config.proxy,
});
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 +596,7 @@
about,
share,
formats,
datasetExportFormats,
exception,
login,
logout,
Expand All @@ -570,6 +612,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 @@ -1456,4 +1478,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;
};
})();
51 changes: 46 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,26 @@ class TaskView {
}
}

async _exportDataset(button, format) {
button.disabled = true;
try {
const url = await this._task.annotations.exportDataset(format);
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(format) {
return format == 'datumaro_project';
}

init(task) {
this._task = task;
}
Expand Down Expand Up @@ -169,6 +190,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}</li>`);
if (this._isDefaultExportFormat(format)) {
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,14 +244,15 @@ 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 = exportFormats;

this._setupList();
this._setupTaskSearch();
Expand Down Expand Up @@ -273,7 +311,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 +774,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
19 changes: 16 additions & 3 deletions cvat/apps/dataset_manager/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def log_exception(logger=None, exc_info=True):
_TASK_ANNO_EXTRACTOR = '_cvat_task_anno'
_TASK_IMAGES_REMOTE_EXTRACTOR = 'cvat_rest_api_task_images'

EXPORT_FORMAT_DATUMARO_PROJECT = "datumaro_project"
EXPORT_FORMAT_DATUMARO_REMOTE_PROJECT = "datumaro_project_remote"

class TaskProject:
@staticmethod
Expand Down Expand Up @@ -286,8 +288,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 @@ -343,4 +344,16 @@ 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,
EXPORT_FORMAT_DATUMARO_REMOTE_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.

9 changes: 8 additions & 1 deletion cvat/apps/engine/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,17 @@ def share(request):

@staticmethod
@action(detail=False, methods=['GET'], url_path='annotation/formats')
def formats(request):
def annotation_formats(request):
data = get_annotation_formats()
return Response(data)

@staticmethod
@action(detail=False, methods=['GET'], url_path='annotation/dataset_export_formats')
def annotation_export_formats(request):
data = DatumaroTask.get_export_formats()
data = JSONRenderer().render(data)
return Response(data)

class ProjectFilter(filters.FilterSet):
name = filters.CharFilter(field_name="name", lookup_expr="icontains")
owner = filters.CharFilter(field_name="owner__username", lookup_expr="icontains")
Expand Down

0 comments on commit f364a5a

Please sign in to comment.