diff --git a/.gitignore b/.gitignore index cb48028971f0..f446bb7f4a9d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ /logs /components/openvino/*.tgz /profiles +/ssh/* +!/ssh/README.md + # Ignore temporary files docker-compose.override.yml diff --git a/.vscode/launch.json b/.vscode/launch.json index ac399cc33425..745392455fa3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -84,6 +84,25 @@ "env": {}, "envFile": "${workspaceFolder}/.env", }, + { + "name": "CVAT git", + "type": "python", + "request": "launch", + "debugStdLib": true, + "stopOnEntry": false, + "pythonPath": "${config:python.pythonPath}", + "program": "${workspaceRoot}/manage.py", + "args": [ + "update_git_states" + ], + "debugOptions": [ + "RedirectOutput", + "DjangoDebugging" + ], + "cwd": "${workspaceFolder}", + "env": {}, + "envFile": "${workspaceFolder}/.env", + }, ], "compounds": [ { @@ -93,6 +112,7 @@ "CVAT Server", "CVAT RQ - default", "CVAT RQ - low", + "CVAT git", ] } ] diff --git a/Dockerfile b/Dockerfile index 80bfba6a045c..4445d3d29e68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,11 +3,13 @@ FROM ubuntu:16.04 ARG http_proxy ARG https_proxy ARG no_proxy +ARG socks_proxy ENV TERM=xterm \ http_proxy=${http_proxy} \ https_proxy=${https_proxy} \ - no_proxy=${no_proxy} + no_proxy=${no_proxy} \ + socks_proxy=${socks_proxy} ENV LANG='C.UTF-8' \ LC_ALL='C.UTF-8' @@ -102,9 +104,23 @@ COPY cvat/requirements/ /tmp/requirements/ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ RUN pip3 install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt COPY cvat/ ${HOME}/cvat + +COPY ssh ${HOME}/.ssh + +# Install git application dependencies +RUN apt-get update && \ + apt-get install -y ssh netcat-openbsd git curl zip && \ + curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \ + apt-get install -y git-lfs && \ + git lfs install && \ + rm -rf /var/lib/apt/lists/* && \ + if [ -n ${socks_proxy} ]; then \ + echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30 -o ProxyCommand='nc -X 5 -x ${socks_proxy} %h %p'\"" >> ${HOME}/.bashrc; \ + fi + COPY tests ${HOME}/tests RUN patch -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch -RUN chown -R ${USER}:${USER} . +RUN chown -R ${USER}:${USER} . # RUN all commands below as 'django' user USER ${USER} diff --git a/cvat/apps/auto_annotation/static/auto_annotation/js/auto_annotation.js b/cvat/apps/auto_annotation/static/auto_annotation/js/auto_annotation.js index 767c9744753e..76920175557d 100644 --- a/cvat/apps/auto_annotation/static/auto_annotation/js/auto_annotation.js +++ b/cvat/apps/auto_annotation/static/auto_annotation/js/auto_annotation.js @@ -25,7 +25,7 @@ window.cvat.dashboard.uiCallbacks.push(function(newElements) { let elem = $(this); let tid = +elem.attr("id").split("_")[1]; - const autoAnnoButton = $("").addClass("semiBold dashboardButtonUI dashboardAutoAnno"); + const autoAnnoButton = $("").addClass("regular dashboardButtonUI dashboardAutoAnno"); autoAnnoButton.appendTo(elem.find("div.dashboardButtonsUI")[0]); if (tid in data && data[tid].active) { diff --git a/cvat/apps/dashboard/static/dashboard/js/dashboard.js b/cvat/apps/dashboard/static/dashboard/js/dashboard.js index d3815969891a..e3a7af0c9d29 100644 --- a/cvat/apps/dashboard/static/dashboard/js/dashboard.js +++ b/cvat/apps/dashboard/static/dashboard/js/dashboard.js @@ -5,6 +5,219 @@ */ "use strict"; +/* Server requests */ +function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, onComplete, onUpdateStatus) { + $.ajax({ + url: "/create/task", + type: "POST", + data: oData, + contentType: false, + processData: false, + success: function(data) { + onSuccessRequest(); + requestCreatingStatus(data); + }, + error: function(data) { + onComplete(); + onError(data.responseText); + } + }); + + function requestCreatingStatus(data) { + let tid = data.tid; + let request_frequency_ms = 1000; + let done = false; + + let requestInterval = setInterval(function() { + $.ajax({ + url: "/check/task/" + tid, + success: receiveStatus, + error: function(data) { + clearInterval(requestInterval); + onComplete(); + onError(data.responseText); + } + }); + }, request_frequency_ms); + + function receiveStatus(data) { + if (done) return; + if (data["state"] === "created") { + done = true; + clearInterval(requestInterval); + onComplete(); + onSuccessCreate(tid); + } + else if (data["state"] === "error") { + done = true; + clearInterval(requestInterval); + onComplete(); + onError(data.stderr); + } + else if (data["state"] === "started" && "status" in data) { + onUpdateStatus(data["status"]); + } + } + } +} + + +function updateTaskRequest(labels) { + let oData = new FormData(); + oData.append("labels", labels); + + $.ajax({ + url: "/update/task/" + window.cvat.dashboard.taskID, + type: "POST", + data: oData, + contentType: false, + processData: false, + success: function() { + $("#dashboardNewLabels").prop("value", ""); + showMessage("Task successfully updated."); + }, + error: function(data) { + showMessage("Task update error. " + data.responseText); + }, + complete: () => $("#dashboardUpdateModal").addClass("hidden") + }); +} + + +function removeTaskRequest() { + confirm("The action can not be undone. Are you sure?", confirmCallback); + + function confirmCallback() { + $.ajax ({ + url: "/delete/task/" + window.cvat.dashboard.taskID, + success: function() { + $(`#dashboardTask_${window.cvat.dashboard.taskID}`).remove(); + showMessage("Task removed."); + }, + error: function(response) { + let message = "Abort. Reason: " + response.responseText; + showMessage(message); + throw Error(message); + } + }); + } +} + + +function uploadAnnotationRequest() { + let input = $("").attr({ + type: "file", + accept: "text/xml" + }).on("change", loadXML).click(); + + function loadXML(e) { + input.remove(); + let overlay = showOverlay("File is being uploaded.."); + let file = e.target.files[0]; + let fileReader = new FileReader(); + fileReader.onload = (e) => parseFile(e, overlay); + fileReader.readAsText(file); + } + + function parseFile(e, overlay) { + let xmlText = e.target.result; + overlay.setMessage("Request task data from server.."); + $.ajax({ + url: "/get/task/" + window.cvat.dashboard.taskID, + success: function(data) { + let annotationParser = new AnnotationParser( + { + start: 0, + stop: data.size, + image_meta_data: data.image_meta_data, + flipped: data.flipped + }, + new LabelsInfo(data.spec), + new ConstIdGenerator(-1) + ); + + let asyncParse = function() { + let parsed = null; + try { + parsed = annotationParser.parse(xmlText); + } + catch(error) { + overlay.remove(); + showMessage("Parsing errors was occurred. " + error); + return; + } + + let asyncSave = function() { + $.ajax({ + url: "/delete/annotation/task/" + window.cvat.dashboard.taskID, + type: "DELETE", + success: function() { + asyncSaveChunk(0); + }, + error: function(response) { + let message = "Previous annotations cannot be deleted: " + + response.responseText; + showMessage(message); + overlay.remove(); + }, + }); + }; + + let asyncSaveChunk = function(start) { + const CHUNK_SIZE = 100000; + let end = start + CHUNK_SIZE; + let chunk = {}; + let next = false; + for (let prop in parsed) { + if (parsed.hasOwnProperty(prop)) { + chunk[prop] = parsed[prop].slice(start, end); + next |= chunk[prop].length > 0; + } + } + + if (next) { + let exportData = createExportContainer(); + exportData.create = chunk; + + $.ajax({ + url: "/save/annotation/task/" + window.cvat.dashboard.taskID, + type: "POST", + data: JSON.stringify(exportData), + contentType: "application/json", + success: function() { + asyncSaveChunk(end); + }, + error: function(response) { + let message = "Annotations uploading errors were occurred: " + + response.responseText; + showMessage(message); + overlay.remove(); + }, + }); + } else { + let message = "Annotations were uploaded successfully"; + showMessage(message); + overlay.remove(); + } + }; + + overlay.setMessage("Annotation is being saved.."); + setTimeout(asyncSave); + }; + + overlay.setMessage("File is being parsed.."); + setTimeout(asyncParse); + }, + error: function(response) { + overlay.remove(); + let message = "Bad task request: " + response.responseText; + showMessage(message); + throw Error(message); + } + }); + } +} + /* Dashboard entrypoint */ window.cvat = window.cvat || {}; @@ -15,46 +228,46 @@ window.cvat.config = new Config(); window.cvat.dashboard.uiCallbacks.push(function(elements) { elements.each(function(idx) { let elem = $(elements[idx]); - let taskID = +elem.attr('id').split('_')[1]; - let taskName = $.trim($( elem.find('label.dashboardTaskNameLabel')[0] ).text()); - let buttonsUI = elem.find('div.dashboardButtonsUI')[0]; + let taskID = +elem.attr("id").split("_")[1]; + let taskName = $.trim($( elem.find("label.dashboardTaskNameLabel")[0] ).text()); + let buttonsUI = elem.find("div.dashboardButtonsUI")[0]; - let dumpButton = $( $(buttonsUI).find('button.dashboardDumpAnnotation')[0] ); - let uploadButton = $( $(buttonsUI).find('button.dashboardUploadAnnotation')[0] ); - let updateButton = $( $(buttonsUI).find('button.dashboardUpdateTask')[0] ); - let deleteButton = $( $(buttonsUI).find('button.dashboardDeleteTask')[0] ); + let dumpButton = $( $(buttonsUI).find("button.dashboardDumpAnnotation")[0] ); + let uploadButton = $( $(buttonsUI).find("button.dashboardUploadAnnotation")[0] ); + let updateButton = $( $(buttonsUI).find("button.dashboardUpdateTask")[0] ); + let deleteButton = $( $(buttonsUI).find("button.dashboardDeleteTask")[0] ); - let bugTrackerButton = $(buttonsUI).find('.dashboardOpenTrackerButton'); + let bugTrackerButton = $(buttonsUI).find(".dashboardOpenTrackerButton"); if (bugTrackerButton.length) { bugTrackerButton = $(bugTrackerButton[0]); - bugTrackerButton.on('click', function() { - window.open($(buttonsUI).find('a.dashboardBugTrackerLink').attr('href')); + bugTrackerButton.on("click", function() { + window.open($(buttonsUI).find("a.dashboardBugTrackerLink").attr("href")); }); } - dumpButton.on('click', function() { + dumpButton.on("click", function() { window.cvat.dashboard.taskID = taskID; window.cvat.dashboard.taskName = taskName; dumpAnnotationRequest(dumpButton, taskID, taskName); }); - uploadButton.on('click', function() { + uploadButton.on("click", function() { window.cvat.dashboard.taskID = taskID; window.cvat.dashboard.taskName = taskName; - confirm('The current annotation will be lost. Are you sure?', uploadAnnotationRequest); + confirm("The current annotation will be lost. Are you sure?", uploadAnnotationRequest); }); - updateButton.on('click', function() { + updateButton.on("click", function() { window.cvat.dashboard.taskID = taskID; window.cvat.dashboard.taskName = taskName; - $('#dashboardUpdateModal').removeClass('hidden'); - $('#dashboardUpdateModal')[0].loadCurrentLabels(); + $("#dashboardUpdateModal").removeClass("hidden"); + $("#dashboardUpdateModal")[0].loadCurrentLabels(); }); - deleteButton.on('click', function() { + deleteButton.on("click", function() { window.cvat.dashboard.taskID = taskID; window.cvat.dashboard.taskName = taskName; - RemoveTaskRequest(); + removeTaskRequest(); }); }); }); @@ -68,54 +281,53 @@ function buildDashboard() { setupTaskUpdater(); setupSearch(); - $(window).on('click', function(e) { - let target = $(e.target); - if ( target.hasClass('modal') ) { - target.addClass('hidden'); + $(window).on("click", function(event) { + if (event.target.classList.contains("modal")) { + event.target.classList.add("hidden"); } }); /* Setup task UIs */ for (let callback of window.cvat.dashboard.uiCallbacks) { - callback( $('.dashboardTaskUI') ); + callback( $(".dashboardTaskUI") ); } - $('#loadingOverlay').remove(); + $("#loadingOverlay").remove(); } function setupTaskCreator() { - let dashboardCreateTaskButton = $('#dashboardCreateTaskButton'); - let createModal = $('#dashboardCreateModal'); - let nameInput = $('#dashboardNameInput'); - let labelsInput = $('#dashboardLabelsInput'); - let bugTrackerInput = $('#dashboardBugTrackerInput'); - let localSourceRadio = $('#dashboardLocalSource'); - let shareSourceRadio = $('#dashboardShareSource'); - let selectFiles = $('#dashboardSelectFiles'); - let filesLabel = $('#dashboardFilesLabel'); - let localFileSelector = $('#dashboardLocalFileSelector'); - let shareFileSelector = $('#dashboardShareBrowseModal'); - let shareBrowseTree = $('#dashboardShareBrowser'); - let cancelBrowseServer = $('#dashboardCancelBrowseServer'); - let submitBrowseServer = $('#dashboardSubmitBrowseServer'); - let flipImagesBox = $('#dashboardFlipImages'); - let zOrderBox = $('#dashboardZOrder'); - let segmentSizeInput = $('#dashboardSegmentSize'); - let customSegmentSize = $('#dashboardCustomSegment'); - let overlapSizeInput = $('#dashboardOverlap'); - let customOverlapSize = $('#dashboardCustomOverlap'); - let imageQualityInput = $('#dashboardImageQuality'); - let customCompressQuality = $('#dashboardCustomQuality'); - - let taskMessage = $('#dashboardCreateTaskMessage'); - let submitCreate = $('#dashboardSubmitTask'); - let cancelCreate = $('#dashboardCancelTask'); - - let name = nameInput.prop('value'); - let labels = labelsInput.prop('value'); - let bugTrackerLink = bugTrackerInput.prop('value'); - let source = 'local'; + let dashboardCreateTaskButton = $("#dashboardCreateTaskButton"); + let createModal = $("#dashboardCreateModal"); + let nameInput = $("#dashboardNameInput"); + let labelsInput = $("#dashboardLabelsInput"); + let bugTrackerInput = $("#dashboardBugTrackerInput"); + let localSourceRadio = $("#dashboardLocalSource"); + let shareSourceRadio = $("#dashboardShareSource"); + let selectFiles = $("#dashboardSelectFiles"); + let filesLabel = $("#dashboardFilesLabel"); + let localFileSelector = $("#dashboardLocalFileSelector"); + let shareFileSelector = $("#dashboardShareBrowseModal"); + let shareBrowseTree = $("#dashboardShareBrowser"); + let cancelBrowseServer = $("#dashboardCancelBrowseServer"); + let submitBrowseServer = $("#dashboardSubmitBrowseServer"); + let flipImagesBox = $("#dashboardFlipImages"); + let zOrderBox = $("#dashboardZOrder"); + let segmentSizeInput = $("#dashboardSegmentSize"); + let customSegmentSize = $("#dashboardCustomSegment"); + let overlapSizeInput = $("#dashboardOverlap"); + let customOverlapSize = $("#dashboardCustomOverlap"); + let imageQualityInput = $("#dashboardImageQuality"); + let customCompressQuality = $("#dashboardCustomQuality"); + + let taskMessage = $("#dashboardCreateTaskMessage"); + let submitCreate = $("#dashboardSubmitTask"); + let cancelCreate = $("#dashboardCancelTask"); + + let name = nameInput.prop("value"); + let labels = labelsInput.prop("value"); + let bugTrackerLink = bugTrackerInput.prop("value"); + let source = "local"; let flipImages = false; let zOrder = false; let segmentSize = 5000; @@ -123,187 +335,191 @@ function setupTaskCreator() { let compressQuality = 50; let files = []; - dashboardCreateTaskButton.on('click', function() { - $('#dashboardCreateModal').removeClass('hidden'); + dashboardCreateTaskButton.on("click", function() { + $("#dashboardCreateModal").removeClass("hidden"); }); - nameInput.on('change', (e) => {name = e.target.value;}); - bugTrackerInput.on('change', (e) => {bugTrackerLink = e.target.value;}); - labelsInput.on('change', (e) => {labels = e.target.value;}); + nameInput.on("change", (e) => {name = e.target.value;}); + bugTrackerInput.on("change", (e) => {bugTrackerLink = e.target.value;}); + labelsInput.on("change", (e) => {labels = e.target.value;}); - localSourceRadio.on('click', function() { - if (source == 'local') return; - source = 'local'; + localSourceRadio.on("click", function() { + if (source === "local") { + return; + } + source = "local"; files = []; updateSelectedFiles(); }); - shareSourceRadio.on('click', function() { - if (source == 'share') return; - source = 'share'; + shareSourceRadio.on("click", function() { + if (source === "share") { + return; + } + source = "share"; files = []; updateSelectedFiles(); }); - selectFiles.on('click', function() { - if (source == 'local') { + selectFiles.on("click", function() { + if (source === "local") { localFileSelector.click(); } else { shareBrowseTree.jstree("refresh"); - shareFileSelector.removeClass('hidden'); + shareFileSelector.removeClass("hidden"); shareBrowseTree.jstree({ core: { data: { - url: 'get_share_nodes', - data: (node) => { return {'id' : node.id}; } + url: "get_share_nodes", + data: (node) => { return {"id" : node.id}; } } }, - plugins: ['checkbox', 'sort'], + plugins: ["checkbox", "sort"], }); } }); - localFileSelector.on('change', function(e) { + localFileSelector.on("change", function(e) { files = e.target.files; updateSelectedFiles(); }); - cancelBrowseServer.on('click', () => shareFileSelector.addClass('hidden')); - submitBrowseServer.on('click', function() { + cancelBrowseServer.on("click", () => shareFileSelector.addClass("hidden")); + submitBrowseServer.on("click", function() { files = shareBrowseTree.jstree(true).get_selected(); cancelBrowseServer.click(); updateSelectedFiles(); }); - flipImagesBox.on('click', (e) => { + flipImagesBox.on("click", (e) => { flipImages = e.target.checked; }); - zOrderBox.on('click', (e) => { + zOrderBox.on("click", (e) => { zOrder = e.target.checked; }); - customSegmentSize.on('change', (e) => segmentSizeInput.prop('disabled', !e.target.checked)); - customOverlapSize.on('change', (e) => overlapSizeInput.prop('disabled', !e.target.checked)); - customCompressQuality.on('change', (e) => imageQualityInput.prop('disabled', !e.target.checked)); + customSegmentSize.on("change", (e) => segmentSizeInput.prop("disabled", !e.target.checked)); + customOverlapSize.on("change", (e) => overlapSizeInput.prop("disabled", !e.target.checked)); + customCompressQuality.on("change", (e) => imageQualityInput.prop("disabled", !e.target.checked)); - segmentSizeInput.on('change', function() { + segmentSizeInput.on("change", function() { let value = Math.clamp( - +segmentSizeInput.prop('value'), - +segmentSizeInput.prop('min'), - +segmentSizeInput.prop('max') + +segmentSizeInput.prop("value"), + +segmentSizeInput.prop("min"), + +segmentSizeInput.prop("max") ); - segmentSizeInput.prop('value', value); + segmentSizeInput.prop("value", value); segmentSize = value; }); - overlapSizeInput.on('change', function() { + overlapSizeInput.on("change", function() { let value = Math.clamp( - +overlapSizeInput.prop('value'), - +overlapSizeInput.prop('min'), - +overlapSizeInput.prop('max') + +overlapSizeInput.prop("value"), + +overlapSizeInput.prop("min"), + +overlapSizeInput.prop("max") ); - overlapSizeInput.prop('value', value); + overlapSizeInput.prop("value", value); overlapSize = value; }); - imageQualityInput.on('change', function() { + imageQualityInput.on("change", function() { let value = Math.clamp( - +imageQualityInput.prop('value'), - +imageQualityInput.prop('min'), - +imageQualityInput.prop('max') + +imageQualityInput.prop("value"), + +imageQualityInput.prop("min"), + +imageQualityInput.prop("max") ); - imageQualityInput.prop('value', value); + imageQualityInput.prop("value", value); compressQuality = value; }); - submitCreate.on('click', function() { + submitCreate.on("click", function() { if (!validateName(name)) { - taskMessage.css('color', 'red'); - taskMessage.text('Invalid task name'); + taskMessage.css("color", "red"); + taskMessage.text("Invalid task name"); return; } if (!validateLabels(labels)) { - taskMessage.css('color', 'red'); - taskMessage.text('Invalid task labels'); + taskMessage.css("color", "red"); + taskMessage.text("Invalid task labels"); return; } if (!validateSegmentSize(segmentSize)) { - taskMessage.css('color', 'red'); - taskMessage.text('Segment size out of range'); + taskMessage.css("color", "red"); + taskMessage.text("Segment size out of range"); return; } if (!validateOverlapSize(overlapSize, segmentSize)) { - taskMessage.css('color', 'red'); - taskMessage.text('Overlap size must be positive and not more then segment size'); + taskMessage.css("color", "red"); + taskMessage.text("Overlap size must be positive and not more then segment size"); return; } if (files.length <= 0) { - taskMessage.css('color', 'red'); - taskMessage.text('Need specify files for task'); + taskMessage.css("color", "red"); + taskMessage.text("Need specify files for task"); return; } - else if (files.length > maxUploadCount && source == 'local') { - taskMessage.css('color', 'red'); - taskMessage.text('Too many files. Please use share functionality'); + else if (files.length > window.maxUploadCount && source === "local") { + taskMessage.css("color", "red"); + taskMessage.text("Too many files. Please use share functionality"); return; } - else if (source == 'local') { + else if (source === "local") { let commonSize = 0; for (let file of files) { commonSize += file.size; } - if (commonSize > maxUploadSize) { - taskMessage.css('color', 'red'); - taskMessage.text('Too big size. Please use share functionality'); + if (commonSize > window.maxUploadSize) { + taskMessage.css("color", "red"); + taskMessage.text("Too big size. Please use share functionality"); return; } } let taskData = new FormData(); - taskData.append('task_name', name); - taskData.append('bug_tracker_link', bugTrackerLink); - taskData.append('labels', labels); - taskData.append('flip_flag', flipImages); - taskData.append('z_order', zOrder); - taskData.append('storage', source); - - if (customSegmentSize.prop('checked')) { - taskData.append('segment_size', segmentSize); + taskData.append("task_name", name); + taskData.append("bug_tracker_link", bugTrackerLink); + taskData.append("labels", labels); + taskData.append("flip_flag", flipImages); + taskData.append("z_order", zOrder); + taskData.append("storage", source); + + if (customSegmentSize.prop("checked")) { + taskData.append("segment_size", segmentSize); } - if (customOverlapSize.prop('checked')) { - taskData.append('overlap_size', overlapSize); + if (customOverlapSize.prop("checked")) { + taskData.append("overlap_size", overlapSize); } - if (customCompressQuality.prop('checked')) { - taskData.append('compress_quality', compressQuality); + if (customCompressQuality.prop("checked")) { + taskData.append("compress_quality", compressQuality); } for (let file of files) { - taskData.append('data', file); + taskData.append("data", file); } - submitCreate.prop('disabled', true); + submitCreate.prop("disabled", true); createTaskRequest(taskData, () => { - taskMessage.css('color', 'green'); - taskMessage.text('Successful request! Creating..'); + taskMessage.css("color", "green"); + taskMessage.text("Successful request! Creating.."); }, () => window.location.reload(), (response) => { - taskMessage.css('color', 'red'); + taskMessage.css("color", "red"); taskMessage.text(response); }, - () => submitCreate.prop('disabled', false), + () => submitCreate.prop("disabled", false), (status) => { - taskMessage.css('color', 'blue'); + taskMessage.css("color", "blue"); taskMessage.text(status); }); }); @@ -311,24 +527,24 @@ function setupTaskCreator() { function updateSelectedFiles() { switch (files.length) { case 0: - filesLabel.text('No Files'); + filesLabel.text("No Files"); break; case 1: - filesLabel.text(typeof(files[0]) == 'string' ? files[0] : files[0].name); + filesLabel.text(typeof(files[0]) === "string" ? files[0] : files[0].name); break; default: - filesLabel.text(files.length + ' files'); + filesLabel.text(files.length + " files"); } } function validateName(name) { - let math = name.match('[a-zA-Z0-9()_ ]+'); + let math = name.match("[a-zA-Z0-9()_ ]+"); return math != null; } function validateLabels(labels) { - let tmp = labels.replace(/\s/g,''); + let tmp = labels.replace(/\s/g,""); return tmp.length > 0; // to do good validator } @@ -341,278 +557,64 @@ function setupTaskCreator() { return (overlapSize >= 0 && overlapSize <= segmentSize - 1); } - cancelCreate.on('click', () => createModal.addClass('hidden')); + cancelCreate.on("click", () => createModal.addClass("hidden")); } function setupTaskUpdater() { - let updateModal = $('#dashboardUpdateModal'); - let oldLabels = $('#dashboardOldLabels'); - let newLabels = $('#dashboardNewLabels'); - let submitUpdate = $('#dashboardSubmitUpdate'); - let cancelUpdate = $('#dashboardCancelUpdate'); + let updateModal = $("#dashboardUpdateModal"); + let oldLabels = $("#dashboardOldLabels"); + let newLabels = $("#dashboardNewLabels"); + let submitUpdate = $("#dashboardSubmitUpdate"); + let cancelUpdate = $("#dashboardCancelUpdate"); updateModal[0].loadCurrentLabels = function() { $.ajax({ - url: '/get/task/' + window.cvat.dashboard.taskID, + url: "/get/task/" + window.cvat.dashboard.taskID, success: function(data) { let labels = new LabelsInfo(data.spec); - oldLabels.attr('value', labels.normalize()); + oldLabels.attr("value", labels.normalize()); }, error: function(response) { - oldLabels.attr('value', 'Bad request'); - let message = 'Bad task request: ' + response.responseText; + oldLabels.attr("value", "Bad request"); + let message = "Bad task request: " + response.responseText; throw Error(message); } }); }; - cancelUpdate.on('click', function() { - $('#dashboardNewLabels').prop('value', ''); - updateModal.addClass('hidden'); + cancelUpdate.on("click", function() { + $("#dashboardNewLabels").prop("value", ""); + updateModal.addClass("hidden"); }); - submitUpdate.on('click', () => UpdateTaskRequest(newLabels.prop('value'))); + submitUpdate.on("click", () => updateTaskRequest(newLabels.prop("value"))); } function setupSearch() { + function getUrlParameter(name) { + let regex = new RegExp("[\\?&]" + name + "=([^&#]*)"); + let results = regex.exec(window.location.search); + return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); + } + let searchInput = $("#dashboardSearchInput"); let searchSubmit = $("#dashboardSearchSubmit"); - let line = getUrlParameter('search') || ""; + let line = getUrlParameter("search") || ""; searchInput.val(line); - searchSubmit.on('click', function() { - let e = $.Event('keypress'); + searchSubmit.on("click", function() { + let e = $.Event("keypress"); e.keyCode = 13; searchInput.trigger(e); }); - searchInput.on('keypress', function(e) { + searchInput.on("keypress", function(e) { if (e.keyCode != 13) return; let filter = e.target.value; if (!filter) window.location.search = ""; else window.location.search = `search=${filter}`; }); - - function getUrlParameter(name) { - let regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); - let results = regex.exec(window.location.search); - return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); - } -} - - -/* Server requests */ -function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, onComplete, onUpdateStatus) { - $.ajax({ - url: '/create/task', - type: 'POST', - data: oData, - contentType: false, - processData: false, - success: function(data) { - onSuccessRequest(); - requestCreatingStatus(data); - }, - error: function(data) { - onComplete(); - onError(data.responseText); - } - }); - - function requestCreatingStatus(data) { - let tid = data.tid; - let request_frequency_ms = 1000; - let done = false; - - let requestInterval = setInterval(function() { - $.ajax({ - url: '/check/task/' + tid, - success: receiveStatus, - error: function(data) { - clearInterval(requestInterval); - onComplete(); - onError(data.responseText); - } - }); - }, request_frequency_ms); - - function receiveStatus(data) { - if (done) return; - if (data['state'] == 'created') { - done = true; - clearInterval(requestInterval); - onComplete(); - onSuccessCreate(); - } - else if (data['state'] == 'error') { - done = true; - clearInterval(requestInterval); - onComplete(); - onError(data.stderr); - } - else if (data['state'] == 'started' && 'status' in data) { - onUpdateStatus(data['status']); - } - } - } -} - - -function UpdateTaskRequest(labels) { - let oData = new FormData(); - oData.append('labels', labels); - - $.ajax({ - url: '/update/task/' + window.cvat.dashboard.taskID, - type: 'POST', - data: oData, - contentType: false, - processData: false, - success: function() { - $('#dashboardNewLabels').prop('value', ''); - showMessage('Task successfully updated.'); - }, - error: function(data) { - showMessage('Task update error. ' + data.responseText); - }, - complete: () => $('#dashboardUpdateModal').addClass('hidden') - }); -} - - -function RemoveTaskRequest() { - confirm('The action can not be undone. Are you sure?', confirmCallback); - - function confirmCallback() { - $.ajax ({ - url: '/delete/task/' + window.cvat.dashboard.taskID, - success: function() { - $(`#dashboardTask_${window.cvat.dashboard.taskID}`).remove(); - showMessage('Task removed.'); - }, - error: function(response) { - let message = 'Abort. Reason: ' + response.responseText; - showMessage(message); - throw Error(message); - } - }); - } -} - - -function uploadAnnotationRequest() { - let input = $('').attr({ - type: 'file', - accept: 'text/xml' - }).on('change', loadXML).click(); - - function loadXML(e) { - input.remove(); - let overlay = showOverlay("File is being uploaded.."); - let file = e.target.files[0]; - let fileReader = new FileReader(); - fileReader.onload = (e) => parseFile(e, overlay); - fileReader.readAsText(file); - } - - function parseFile(e, overlay) { - let xmlText = e.target.result; - overlay.setMessage('Request task data from server..'); - $.ajax({ - url: '/get/task/' + window.cvat.dashboard.taskID, - success: function(data) { - let annotationParser = new AnnotationParser( - { - start: 0, - stop: data.size, - image_meta_data: data.image_meta_data, - flipped: data.flipped - }, - new LabelsInfo(data.spec), - new ConstIdGenerator(-1) - ); - - let asyncParse = function() { - let parsed = null; - try { - parsed = annotationParser.parse(xmlText); - } - catch(error) { - overlay.remove(); - showMessage("Parsing errors was occurred. " + error); - return; - } - - let asyncSave = function() { - $.ajax({ - url: '/delete/annotation/task/' + window.cvat.dashboard.taskID, - type: 'DELETE', - success: function() { - asyncSaveChunk(0); - }, - error: function(response) { - let message = 'Previous annotations cannot be deleted: ' + - response.responseText; - showMessage(message); - overlay.remove(); - }, - }); - }; - - let asyncSaveChunk = function(start) { - const CHUNK_SIZE = 100000; - let end = start + CHUNK_SIZE; - let chunk = {}; - let next = false; - for (let prop in parsed) { - if (parsed.hasOwnProperty(prop)) { - chunk[prop] = parsed[prop].slice(start, end); - next |= chunk[prop].length > 0; - } - } - - if (next) { - let exportData = createExportContainer(); - exportData.create = chunk; - - $.ajax({ - url: '/save/annotation/task/' + window.cvat.dashboard.taskID, - type: 'POST', - data: JSON.stringify(exportData), - contentType: 'application/json', - success: function() { - asyncSaveChunk(end); - }, - error: function(response) { - let message = 'Annotations uploading errors were occurred: ' + - response.responseText; - showMessage(message); - overlay.remove(); - }, - }); - } else { - let message = 'Annotations were uploaded successfully'; - showMessage(message); - overlay.remove(); - } - }; - - overlay.setMessage('Annotation is being saved..'); - setTimeout(asyncSave); - }; - - overlay.setMessage('File is being parsed..'); - setTimeout(asyncParse); - }, - error: function(response) { - overlay.remove(); - let message = 'Bad task request: ' + response.responseText; - showMessage(message); - throw Error(message); - } - }); - } } diff --git a/cvat/apps/dashboard/static/dashboard/stylesheet.css b/cvat/apps/dashboard/static/dashboard/stylesheet.css index a3430e857561..a5a047fc2f6f 100644 --- a/cvat/apps/dashboard/static/dashboard/stylesheet.css +++ b/cvat/apps/dashboard/static/dashboard/stylesheet.css @@ -14,7 +14,7 @@ } .dashboardTaskIntro { - width: 25%; + width: 30%; height: 75%; float: left; margin-left: 20px; @@ -25,7 +25,7 @@ .dashboardButtonsUI { margin-top: 1%; - width: 33%; + width: 35%; height: 75%; float: left; overflow-y: auto; @@ -33,19 +33,19 @@ .dashboardButtonUI { display: block; - width: 70%; - height: 2.5em; + width: 60%; + height: 1.6em; margin: auto; - margin-top: 0.1em; + margin-top: 0.3em; font-size: 1em; } .dashboardJobsUI { - width: 40%; + width: 30%; height: 75%; float: left; text-align: center; - overflow-y: scroll; + overflow-y: auto; } .dashboardJobList { diff --git a/cvat/apps/dashboard/templates/dashboard/dashboard.html b/cvat/apps/dashboard/templates/dashboard/dashboard.html index be700127a325..253fa4e37e32 100644 --- a/cvat/apps/dashboard/templates/dashboard/dashboard.html +++ b/cvat/apps/dashboard/templates/dashboard/dashboard.html @@ -164,13 +164,13 @@
-
- -
+
+ +
diff --git a/cvat/apps/dashboard/templates/dashboard/task.html b/cvat/apps/dashboard/templates/dashboard/task.html index da017006b59c..073fca8fadba 100644 --- a/cvat/apps/dashboard/templates/dashboard/task.html +++ b/cvat/apps/dashboard/templates/dashboard/task.html @@ -12,12 +12,12 @@
- - - - + + + + {%if item.bug_tracker %} - + {% endif %}
diff --git a/cvat/apps/dashboard/tests.py b/cvat/apps/dashboard/tests.py index 53bc3b7adb85..ba48a2a3d51c 100644 --- a/cvat/apps/dashboard/tests.py +++ b/cvat/apps/dashboard/tests.py @@ -3,7 +3,5 @@ # # SPDX-License-Identifier: MIT -from django.test import TestCase - # Create your tests here. diff --git a/cvat/apps/engine/__init__.py b/cvat/apps/engine/__init__.py index d8e62e54b356..b66dde17a5cf 100644 --- a/cvat/apps/engine/__init__.py +++ b/cvat/apps/engine/__init__.py @@ -1,5 +1,3 @@ - # Copyright (C) 2018 Intel Corporation # # SPDX-License-Identifier: MIT - diff --git a/cvat/apps/engine/annotation.py b/cvat/apps/engine/annotation.py index 79f4f126f357..9994c523c28e 100644 --- a/cvat/apps/engine/annotation.py +++ b/cvat/apps/engine/annotation.py @@ -20,6 +20,7 @@ from django.db import transaction from cvat.apps.profiler import silk_profile +from cvat.apps.engine.plugins import plugin_decorator from . import models from .task import get_frame_path, get_image_meta_cache from .log import slogger @@ -34,7 +35,7 @@ def dump(tid, data_format, scheme, host): Dump annotation for the task in specified data format. """ queue = django_rq.get_queue('default') - queue.enqueue_call(func=_dump, args=(tid, data_format, scheme, host), + queue.enqueue_call(func=_dump, args=(tid, data_format, scheme, host, OrderedDict()), job_id="annotation.dump/{}".format(tid)) def check(tid): @@ -72,6 +73,7 @@ def get(jid): return annotation.to_client() @silk_profile(name="Save job") +@plugin_decorator @transaction.atomic def save_job(jid, data): """ @@ -89,8 +91,13 @@ def save_job(jid, data): annotation.save_to_db(data['create']) annotation.update_in_db(data['update']) - db_job.segment.task.updated_date = timezone.now() - db_job.segment.task.save() + updated = sum([ len(data["update"][key]) for key in data["update"] ]) + deleted = sum([ len(data["delete"][key]) for key in data["delete"] ]) + created = sum([ len(data["create"][key]) for key in data["create"] ]) + + if updated or deleted or created: + db_job.segment.task.updated_date = timezone.now() + db_job.segment.task.save() db_job.max_shape_id = max(db_job.max_shape_id, max(client_ids['create']) if client_ids['create'] else -1) db_job.save() @@ -1497,12 +1504,13 @@ def init_from_db(self): self.points = annotation.points self.points_paths = annotation.points_paths +@plugin_decorator @transaction.atomic -def _dump(tid, data_format, scheme, host): +def _dump(tid, data_format, scheme, host, plugin_meta_data): db_task = models.Task.objects.select_for_update().get(id=tid) annotation = _AnnotationForTask(db_task) annotation.init_from_db() - annotation.dump(data_format, scheme, host) + annotation.dump(data_format, scheme, host, plugin_meta_data) def _calc_box_area(box): return (box.xbr - box.xtl) * (box.ybr - box.ytl) @@ -1881,7 +1889,7 @@ def _merge_boxes(self, boxes, start_frame, overlap): # We don't have old boxes on the frame. Let's add all new ones. self.boxes.extend(int_boxes_by_frame[frame]) - def dump(self, data_format, scheme, host): + def dump(self, data_format, scheme, host, plugin_meta_data): def _flip_box(box, im_w, im_h): box.xbr, box.xtl = im_w - box.xtl, im_w - box.xbr box.ybr, box.ytl = im_h - box.ytl, im_h - box.ybr @@ -1945,6 +1953,8 @@ def _flip_shape(shape, im_w, im_h): ("dumped", str(timezone.localtime(timezone.now()))) ]) + meta.update(plugin_meta_data) + if db_task.mode == "interpolation": meta["task"]["original_size"] = OrderedDict([ ("width", str(im_meta_data["original_size"][0]["width"])), diff --git a/cvat/apps/engine/models.py b/cvat/apps/engine/models.py index 1d2729d47252..1d62f02da38d 100644 --- a/cvat/apps/engine/models.py +++ b/cvat/apps/engine/models.py @@ -64,7 +64,7 @@ def get_data_dirname(self): def get_dump_path(self): name = re.sub(r'[\\/*?:"<>|]', '_', self.name) - return os.path.join(self.path, "{}.dump".format(name)) + return os.path.join(self.path, "{}.xml".format(name)) def get_log_path(self): return os.path.join(self.path, "task.log") diff --git a/cvat/apps/engine/plugins.py b/cvat/apps/engine/plugins.py new file mode 100644 index 000000000000..6bf0143f39ce --- /dev/null +++ b/cvat/apps/engine/plugins.py @@ -0,0 +1,70 @@ +# Copyright (C) 2018 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from functools import update_wrapper + +__plugins = {} + + +def add_plugin(name, function, order, exc_ok = False): + if order not in ["before", "after"]: + raise Exception("Order may be 'before' or 'after' only. Got {}.".format(order)) + + if not callable(function): + raise Exception("'function' argument should be a callable element") + + if not isinstance(name, str): + raise Exception("'name' argument should be a string. Got {}.".format(type(name))) + + if name not in __plugins: + __plugins[name] = { + "before": [], + "after": [] + } + + if function in __plugins[name][order]: + raise Exception("plugin has been attached already") + + __plugins[name][order].append(function) + + function.exc_ok = exc_ok + + +def remove_plugin(name, function): + if name in __plugins: + if function in __plugins[name]["before"]: + __plugins[name]["before"].remove(function) + del function.exc_ok + if function in __plugins[name]["after"]: + __plugins[name]["after"].remove(function) + del function.exc_ok + + +def plugin_decorator(function_to_decorate): + name = function_to_decorate.__name__ + + def function_wrapper(*args, **kwargs): + if name in __plugins: + for wrapper in __plugins[name]["before"]: + try: + wrapper(*args, **kwargs) + except Exception as ex: + if not wrapper.exc_ok: + raise ex + + result = function_to_decorate(*args, **kwargs) + + if name in __plugins: + for wrapper in __plugins[name]["after"]: + try: + wrapper(*args, **kwargs) + except Exception as ex: + if not wrapper.exc_ok: + raise ex + + return result + + # Copy meta info about wrapped function to wrapper function + update_wrapper(function_wrapper, function_to_decorate) + return function_wrapper diff --git a/cvat/apps/engine/static/engine/base.css b/cvat/apps/engine/static/engine/base.css index 30b2b12873b6..b694d06efa2c 100644 --- a/cvat/apps/engine/static/engine/base.css +++ b/cvat/apps/engine/static/engine/base.css @@ -75,7 +75,6 @@ html { overflow: auto; background-color: rgb(0,0,0); background-color: rgba(0,0,0,0.4); - overflow: hidden; } .modal-content { diff --git a/cvat/apps/engine/static/engine/js/annotationUI.js b/cvat/apps/engine/static/engine/js/annotationUI.js index 53a0edca03f3..43c54e655df3 100644 --- a/cvat/apps/engine/static/engine/js/annotationUI.js +++ b/cvat/apps/engine/static/engine/js/annotationUI.js @@ -204,7 +204,7 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) { $(window).on('click', function(event) { Logger.updateUserActivityTimer(); - if (['helpWindow', 'settingsWindow'].indexOf(event.target.id) != -1) { + if (event.target.classList.contains('modal')) { event.target.classList.add('hidden'); } }); diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index 786dfb0376a0..c26c9ebd1f08 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -19,6 +19,7 @@ mimetypes.init(files=[_MEDIA_MIMETYPES_FILE]) from cvat.apps.engine.models import StatusChoice +from cvat.apps.engine.plugins import plugin_decorator import django_rq from django.conf import settings @@ -637,12 +638,9 @@ def _save_task_to_db(db_task, task_params): db_task.save() +@plugin_decorator @transaction.atomic def _create_thread(tid, params): - def raise_exception(images, dirs, videos, archives): - raise Exception('Only one archive, one video or many images can be dowloaded simultaneously. \ - {} image(s), {} dir(s), {} video(s), {} archive(s) found'.format(images, dirs, videos, archives)) - slogger.glob.info("create task #{}".format(tid)) job = rq.get_current_job() diff --git a/cvat/apps/engine/templates/engine/base.html b/cvat/apps/engine/templates/engine/base.html index beb3778369a4..352822a8f47f 100644 --- a/cvat/apps/engine/templates/engine/base.html +++ b/cvat/apps/engine/templates/engine/base.html @@ -73,7 +73,7 @@