Model manager for DL models to preannotate images (#289)

main
Andrey Zhavoronkov 7 years ago committed by Nikita Manovich
parent 0dcf211d0c
commit 746cffb476

@ -2,8 +2,9 @@
### Description
The application will be enabled automatically if OpenVINO™ component is
installed. It allows to use custom models for auto annotation. Only models in
The application will be enabled automatically if
[OpenVINO™ component](../../../components/openvino)
is installed. It allows to use custom models for auto annotation. Only models in
OpenVINO™ toolkit format are supported. If you would like to annotate a
task with a custom model please convert it to the intermediate representation
(IR) format via the model optimizer tool. See [OpenVINO documentation](https://software.intel.com/en-us/articles/OpenVINO-InferEngine) for details.
@ -14,9 +15,7 @@ To annotate a task with a custom model you need to prepare 4 files:
1. __Model config__ (*.xml) - a text file with network configuration.
1. __Model weights__ (*.bin) - a binary file with trained weights.
1. __Label map__ (*.json) - a simple json file with `label_map` dictionary like
object with string values for label numbers. Values in `label_map` should be
exactly equal to labels for the annotation task, otherwise objects with mismatched
labels will be ignored.
object with string values for label numbers.
Example:
```json
{

@ -3,6 +3,10 @@
#
# SPDX-License-Identifier: MIT
from cvat.settings.base import JS_3RDPARTY
from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY
JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['auto_annotation/js/auto_annotation.js']
default_app_config = 'cvat.apps.auto_annotation.apps.AutoAnnotationConfig'
JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['auto_annotation/js/dashboardPlugin.js']
CSS_3RDPARTY['dashboard'] = CSS_3RDPARTY.get('dashboard', []) + ['auto_annotation/stylesheet.css']

@ -7,5 +7,9 @@ from django.apps import AppConfig
class AutoAnnotationConfig(AppConfig):
name = "auto_annotation"
name = "cvat.apps.auto_annotation"
def ready(self):
from .permissions import setup_permissions
setup_permissions()

@ -0,0 +1,39 @@
# Generated by Django 2.1.3 on 2019-01-24 14:05
import cvat.apps.auto_annotation.models
from django.conf import settings
import django.core.files.storage
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='AnnotationModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', cvat.apps.auto_annotation.models.SafeCharField(max_length=256)),
('created_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now_add=True)),
('model_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)),
('weights_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)),
('labelmap_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)),
('interpretation_file', models.FileField(storage=django.core.files.storage.FileSystemStorage(), upload_to=cvat.apps.auto_annotation.models.upload_path_handler)),
('shared', models.BooleanField(default=False)),
('primary', models.BooleanField(default=False)),
('framework', models.CharField(default=cvat.apps.auto_annotation.models.FrameworkChoice('openvino'), max_length=32)),
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'default_permissions': (),
},
),
]

@ -0,0 +1,143 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
import django_rq
import os
import rq
import shutil
from django.db import transaction
from django.utils import timezone
from django.conf import settings
from .models import AnnotationModel, FrameworkChoice
def _remove_old_file(model_file_field):
if model_file_field and os.path.exists(model_file_field.name):
os.remove(model_file_field.name)
@transaction.atomic
def _update_dl_model_thread(dl_model_id, model_file, weights_file, labelmap_file, interpretation_file, run_tests):
def _get_file_content(filename):
return os.path.basename(filename), open(filename, "rb")
job = rq.get_current_job()
job.meta["progress"] = "Saving data"
job.save_meta()
dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id)
#save files in case of files should be uploaded from share
if model_file:
_remove_old_file(dl_model.model_file)
dl_model.model_file.save(*_get_file_content(model_file))
if weights_file:
_remove_old_file(dl_model.weights_file)
dl_model.weights_file.save(*_get_file_content(weights_file))
if labelmap_file:
_remove_old_file(dl_model.labelmap_file)
dl_model.labelmap_file.save(*_get_file_content(labelmap_file))
if interpretation_file:
_remove_old_file(dl_model.interpretation_file)
dl_model.interpretation_file.save(*_get_file_content(interpretation_file))
if run_tests:
#only for testing
import time
time.sleep(3)
job.meta["progress"] = "Test started"
job.save_meta()
time.sleep(5)
job.meta["progress"] = "Test finished"
@transaction.atomic
def update_model(dl_model_id, name, model_file, weights_file, labelmap_file, interpretation_file, storage, is_shared):
def get_abs_path(share_path):
if not share_path:
return share_path
share_root = settings.SHARE_ROOT
relpath = os.path.normpath(share_path).lstrip('/')
if '..' in relpath.split(os.path.sep):
raise Exception('Permission denied')
abspath = os.path.abspath(os.path.join(share_root, relpath))
if os.path.commonprefix([share_root, abspath]) != share_root:
raise Exception('Bad file path on share: ' + abspath)
return abspath
dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id)
if name:
dl_model.name = name
if is_shared != None:
dl_model.shared = is_shared
run_tests = bool(model_file or weights_file or labelmap_file or interpretation_file)
if storage != "local":
model_file = get_abs_path(model_file)
weights_file = get_abs_path(weights_file)
labelmap_file = get_abs_path(labelmap_file)
interpretation_file = get_abs_path(interpretation_file)
else:
if model_file:
_remove_old_file(dl_model.model_file)
dl_model.model_file = model_file
model_file = None
if weights_file:
_remove_old_file(dl_model.weights_file)
dl_model.weights_file = weights_file
weights_file = None
if labelmap_file:
_remove_old_file(dl_model.labelmap_file)
dl_model.labelmap_file = labelmap_file
labelmap_file = None
if interpretation_file:
_remove_old_file(dl_model.interpretation_file)
dl_model.interpretation_file = interpretation_file
interpretation_file = None
dl_model.updated_date = timezone.now()
dl_model.save()
rq_id = "auto_annotation.create.{}".format(dl_model_id)
queue = django_rq.get_queue('default')
queue.enqueue_call(
func = _update_dl_model_thread,
args = (dl_model_id,
model_file,
weights_file,
labelmap_file,
interpretation_file,
run_tests,
),
job_id = rq_id
)
return rq_id
def create_empty(owner, framework=FrameworkChoice.OPENVINO):
db_model = AnnotationModel(
owner=owner,
)
db_model.save()
model_path = db_model.get_dirname()
if os.path.isdir(model_path):
shutil.rmtree(model_path)
os.mkdir(model_path)
return db_model.id
@transaction.atomic
def delete(dl_model_id):
dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id)
if dl_model:
if dl_model.primary:
raise Exception("Can not delete primary model {}".format(dl_model_id))
dl_model.delete()
shutil.rmtree(dl_model.get_dirname(), ignore_errors=True)
else:
raise Exception("Requested DL model {} doesn't exist".format(dl_model_id))

@ -2,3 +2,54 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from enum import Enum
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.core.files.storage import FileSystemStorage
fs = FileSystemStorage()
def upload_path_handler(instance, filename):
return "{models_root}/{id}/{file}".format(models_root=settings.MODELS_ROOT, id=instance.id, file=filename)
class FrameworkChoice(Enum):
OPENVINO = 'openvino'
TENSORFLOW = 'tensorflow'
PYTORCH = 'pytorch'
def __str__(self):
return self.value
class SafeCharField(models.CharField):
def get_prep_value(self, value):
value = super().get_prep_value(value)
if value:
return value[:self.max_length]
return value
class AnnotationModel(models.Model):
name = SafeCharField(max_length=256)
owner = models.ForeignKey(User, null=True, blank=True,
on_delete=models.SET_NULL)
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now_add=True)
model_file = models.FileField(upload_to=upload_path_handler, storage=fs)
weights_file = models.FileField(upload_to=upload_path_handler, storage=fs)
labelmap_file = models.FileField(upload_to=upload_path_handler, storage=fs)
interpretation_file = models.FileField(upload_to=upload_path_handler, storage=fs)
shared = models.BooleanField(default=False)
primary = models.BooleanField(default=False)
framework = models.CharField(max_length=32, default=FrameworkChoice.OPENVINO)
class Meta:
default_permissions = ()
def get_dirname(self):
return "{models_root}/{id}".format(models_root=settings.MODELS_ROOT, id=self.id)
def __str__(self):
return self.name

@ -0,0 +1,29 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
import rules
from cvat.apps.authentication.auth import has_admin_role, has_user_role
@rules.predicate
def is_model_owner(db_user, db_dl_model):
return db_dl_model.owner == db_user
@rules.predicate
def is_shared_model(_, db_dl_model):
return db_dl_model.shared
@rules.predicate
def is_primary_model(_, db_dl_model):
return db_dl_model.primary
def setup_permissions():
rules.add_perm('auto_annotation.model.create', has_admin_role | has_user_role)
rules.add_perm('auto_annotation.model.update', (has_admin_role | is_model_owner) & ~is_primary_model)
rules.add_perm('auto_annotation.model.delete', (has_admin_role | is_model_owner) & ~is_primary_model)
rules.add_perm('auto_annotation.model.access', has_admin_role | is_model_owner |
is_shared_model | is_primary_model)

@ -1,188 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* global
showMessage:false
*/
"use strict";
window.cvat = window.cvat || {};
window.cvat.dashboard = window.cvat.dashboard || {};
window.cvat.dashboard.uiCallbacks = window.cvat.dashboard.uiCallbacks || [];
window.cvat.dashboard.uiCallbacks.push(function(newElements) {
let tids = [];
for (let el of newElements) {
tids.push(el.id.split("_")[1]);
}
$.ajax({
type: "POST",
url: "/auto_annotation/meta/get",
data: JSON.stringify(tids),
contentType: "application/json; charset=utf-8",
success: (data) => {
newElements.each(function() {
let elem = $(this);
let tid = +elem.attr("id").split("_")[1];
const autoAnnoButton = $("<button> Run auto annotation </button>").addClass("regular dashboardButtonUI dashboardAutoAnno");
autoAnnoButton.appendTo(elem.find("div.dashboardButtonsUI")[0]);
if (tid in data && data[tid].active) {
autoAnnoButton.text("Cancel auto annotation");
autoAnnoButton.addClass("autoAnnotationProcess");
window.cvat.autoAnnotation.checkAutoAnnotationRequest(tid, autoAnnoButton);
}
autoAnnoButton.on("click", () => {
if (autoAnnoButton.hasClass("autoAnnotationProcess")) {
$.post(`/auto_annotation/cancel/task/${tid}`).fail( (data) => {
let message = `Error during cancel auto annotation request. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
showMessage(message);
throw Error(message);
});
}
else {
let dialogWindow = $(`#${window.cvat.autoAnnotation.modalWindowId}`);
dialogWindow.attr("current_tid", tid);
dialogWindow.removeClass("hidden");
}
});
});
},
error: (data) => {
let message = `Can not get auto annotation meta info. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
window.cvat.autoAnnotation.badResponse(message);
}
});
});
window.cvat.autoAnnotation = {
modalWindowId: "autoAnnotationWindow",
autoAnnoFromId: "autoAnnotationForm",
autoAnnoModelFieldId: "autoAnnotationModelField",
autoAnnoWeightsFieldId: "autoAnnotationWeightsField",
autoAnnoConfigFieldId: "autoAnnotationConfigField",
autoAnnoConvertFieldId: "autoAnnotationConvertField",
autoAnnoCloseButtonId: "autoAnnoCloseButton",
autoAnnoSubmitButtonId: "autoAnnoSubmitButton",
checkAutoAnnotationRequest: (tid, autoAnnoButton) => {
function timeoutCallback() {
$.get(`/auto_annotation/check/task/${tid}`).done((data) => {
if (data.status === "started" || data.status === "queued") {
let progress = Math.round(data.progress) || 0;
autoAnnoButton.text(`Cancel auto annotation (${progress}%)`);
setTimeout(timeoutCallback, 1000);
}
else {
autoAnnoButton.text("Run auto annotation");
autoAnnoButton.removeClass("autoAnnotationProcess");
}
}).fail((data) => {
let message = "Error was occurred during check annotation status. " +
`Code: ${data.status}, text: ${data.responseText || data.statusText}`;
window.cvat.autoAnnotation.badResponse(message);
});
}
setTimeout(timeoutCallback, 1000);
},
badResponse(message) {
showMessage(message);
throw Error(message);
},
};
function submitButtonOnClick() {
const annoWindow = $(`#${window.cvat.autoAnnotation.modalWindowId}`);
const tid = annoWindow.attr("current_tid");
const modelInput = $(`#${window.cvat.autoAnnotation.autoAnnoModelFieldId}`);
const weightsInput = $(`#${window.cvat.autoAnnotation.autoAnnoWeightsFieldId}`);
const configInput = $(`#${window.cvat.autoAnnotation.autoAnnoConfigFieldId}`);
const convFileInput = $(`#${window.cvat.autoAnnotation.autoAnnoConvertFieldId}`);
const modelFile = modelInput.prop("files")[0];
const weightsFile = weightsInput.prop("files")[0];
const configFile = configInput.prop("files")[0];
const convFile = convFileInput.prop("files")[0];
if (!modelFile || !weightsFile || !configFile || !convFile) {
showMessage("All files must be selected");
return;
}
let taskData = new FormData();
taskData.append("model", modelFile);
taskData.append("weights", weightsFile);
taskData.append("config", configFile);
taskData.append("conv_script", convFile);
$.ajax({
url: `/auto_annotation/create/task/${tid}`,
type: "POST",
data: taskData,
contentType: false,
processData: false,
}).done(() => {
annoWindow.addClass("hidden");
const autoAnnoButton = $(`#dashboardTask_${tid} div.dashboardButtonsUI button.dashboardAutoAnno`);
autoAnnoButton.addClass("autoAnnotationProcess");
window.cvat.autoAnnotation.checkAutoAnnotationRequest(tid, autoAnnoButton);
}).fail((data) => {
let message = "Error was occurred during run annotation request. " +
`Code: ${data.status}, text: ${data.responseText || data.statusText}`;
window.cvat.autoAnnotation.badResponse(message);
});
}
document.addEventListener("DOMContentLoaded", () => {
$(`<div id="${window.cvat.autoAnnotation.modalWindowId}" class="modal hidden">
<form id="${window.cvat.autoAnnotation.autoAnnoFromId}" class="modal-content" autocomplete="on" onsubmit="return false" style="width: 700px;">
<center>
<label class="semiBold h1"> Auto annotation setup </label>
</center>
<table style="width: 100%; text-align: left;">
<tr>
<td style="width: 25%"> <label class="regular h2"> Model </label> </td>
<td> <input id="${window.cvat.autoAnnotation.autoAnnoModelFieldId}" type="file" name="model" /> </td>
</tr>
<tr>
<td style="width: 25%"> <label class="regular h2"> Weights </label> </td>
<td> <input id="${window.cvat.autoAnnotation.autoAnnoWeightsFieldId}" type="file" name="weights" /> </td>
</tr>
<tr>
<td style="width: 25%"> <label class="regular h2"> Label map </label> </td>
<td> <input id="${window.cvat.autoAnnotation.autoAnnoConfigFieldId}" type="file" name="config" accept=".json" /> </td>
</tr>
<tr>
<td style="width: 25%"> <label class="regular h2"> Convertation script </label> </td>
<td> <input id="${window.cvat.autoAnnotation.autoAnnoConvertFieldId}" type="file" name="convert" /> </td>
</tr>
</table>
<div>
<button id="${window.cvat.autoAnnotation.autoAnnoCloseButtonId}" class="regular h2"> Close </button>
<button id="${window.cvat.autoAnnotation.autoAnnoSubmitButtonId}" class="regular h2"> Submit </button>
</div>
</form>
</div>`).appendTo("body");
const annoWindow = $(`#${window.cvat.autoAnnotation.modalWindowId}`);
const closeWindowButton = $(`#${window.cvat.autoAnnotation.autoAnnoCloseButtonId}`);
const submitButton = $(`#${window.cvat.autoAnnotation.autoAnnoSubmitButtonId}`);
closeWindowButton.on("click", () => {
annoWindow.addClass("hidden");
});
submitButton.on("click", () => {
submitButtonOnClick();
});
});

@ -0,0 +1,792 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* global
showMessage
showOverlay
userConfirm
*/
window.cvat = window.cvat || {};
window.cvat.dashboard = window.cvat.dashboard || {};
window.cvat.dashboard.uiCallbacks = window.cvat.dashboard.uiCallbacks || [];
const AutoAnnotationServer = {
start(modelId, taskId, data, success, error, progress, check) {
$.ajax({
url: `/auto_annotation/start/${modelId}/${taskId}`,
type: 'POST',
data: JSON.stringify(data),
contentType: 'application/json',
success: (responseData) => {
check(responseData.id, success, error, progress);
},
error: (responseData) => {
const message = `Starting request has been failed. Code: ${responseData.status}. Message: ${responseData.responseText || responseData.statusText}`;
error(message);
},
});
},
update(data, success, error, progress, check, modelId) {
let url = '';
if (modelId === null) {
url = '/auto_annotation/create';
} else {
url = `/auto_annotation/update/${modelId}`;
}
$.ajax({
url,
type: 'POST',
data,
contentType: false,
processData: false,
success: (responseData) => {
check(responseData.id, success, error, progress);
},
error: (responseData) => {
const message = `Creating request has been failed. Code: ${responseData.status}. Message: ${responseData.responseText || responseData.statusText}`;
error(message);
},
});
},
delete(modelId, success, error) {
$.ajax({
url: `/auto_annotation/delete/${modelId}`,
type: 'DELETE',
success,
error: (data) => {
const message = `Deleting request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
error(message);
},
});
},
check(workerId, success, error, progress) {
function updateProgress(data) {
if (data.progress && progress) {
progress(data.progress);
}
}
function checkCallback() {
$.ajax({
url: `/auto_annotation/check/${workerId}`,
type: 'GET',
success: (data) => {
updateProgress(data, progress);
switch (data.status) {
case 'failed':
error(`Checking request has returned the "${data.status}" status. Message: ${data.error}`);
break;
case 'unknown':
error(`Checking request has returned the "${data.status}" status.`);
break;
case 'finished':
success();
break;
default:
setTimeout(checkCallback, 1000);
}
},
error: (data) => {
const message = `Checking request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
error(message);
},
});
}
setTimeout(checkCallback, 1000);
},
meta(tids, success, error) {
$.ajax({
url: '/auto_annotation/meta/get',
type: 'POST',
data: JSON.stringify(tids),
contentType: 'application/json',
success,
error: (data) => {
const message = `Getting meta request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
error(message);
},
});
},
cancel(tid, success, error) {
$.ajax({
url: `/auto_annotation/cancel/${tid}`,
type: 'GET',
success,
error: (data) => {
const message = `Getting meta request has been failed. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
error(message);
},
});
},
};
class AutoAnnotationModelManagerView {
constructor() {
const html = `<div class="modal hidden" id="${window.cvat.autoAnnotation.managerWindowId}">
<div class="modal-content" id="${window.cvat.autoAnnotation.managerContentId}">
<div style="float: left; width: 55%; height: 100%;">
<center>
<label class="regular h1"> Uploaded Models </label>
</center>
<div style="overflow: auto; height: 90%; margin-top: 2%;">
<table class="regular modelsTable">
<thead>
<tr>
<th> Name </th>
<th> Upload Date </th>
<th> Actions </th>
</tr>
</thead>
<tbody id="${window.cvat.autoAnnotation.managerUploadedModelsId}"> </tbody>
</table>
</div>
</div>
<div class="regular" id="${window.cvat.autoAnnotation.uploadContentId}">
<center>
<label class="regular h1" id="${window.cvat.autoAnnotation.uploadTitleId}"> Create Model </label>
</center>
<table>
<tr>
<td style="width: 25%"> <label class="regular h3"> Name: </label> </td>
<td> <input type="text" id="${window.cvat.autoAnnotation.uploadNameInputId}" class="regular h3" style="width: 100%"> </td>
</tr>
<tr>
<td> <label class="regular h3"> Source: </label> </td>
<td>
<input id="${window.cvat.autoAnnotation.uploadLocalSourceId}" type="radio" name="modelSourceType" value="local" checked>
<label for="${window.cvat.autoAnnotation.uploadLocalSourceId}" class="regular h3"> Local </label>
<br>
<input id="${window.cvat.autoAnnotation.uploadShareSourceId}" type="radio" name="modelSourceType" value="shared">
<label for="${window.cvat.autoAnnotation.uploadShareSourceId}" class="regular h3"> Share </label>
</td>
</tr>
<tr id="${window.cvat.autoAnnotation.uploadGloballyBlockId}">
<td> <label class="regular h3"> Upload Globally </label> </td>
<td> <input type="checkbox" id="${window.cvat.autoAnnotation.uploadGloballyId}"> </td>
</tr>
</table>
<div style="text-align: left;">
<div>
<button id="${window.cvat.autoAnnotation.selectFilesButtonId}" class="regular h3"> Select Files </button>
<label id="${window.cvat.autoAnnotation.selectedFilesId}" class="regular h3" style="margin-left: 10px"> No Files </label>
<input id="${window.cvat.autoAnnotation.localFileSelectorId}" type="file" accept=".bin,.xml,.json,.py" style="display: none" multiple>
</div>
</div>
<div>
<div style="float: right; width: 50%; height: 50px;">
<button class="regular h3" id="${window.cvat.autoAnnotation.submitUploadButtonId}"> Submit </button>
<button class="regular h3" id="${window.cvat.autoAnnotation.cancelUploadButtonId}"> Cancel </button>
</div>
<div style="float: left; overflow-y: auto; height: 75px; overflow: auto; width: 100%; word-break: break-word;">
<label class="regular h3 selectable" style="float: left;" id="${window.cvat.autoAnnotation.uploadMessageId}"> </label>
</div>
</div>
</div>
</div>`;
this.el = $(html);
this.table = this.el.find(`#${window.cvat.autoAnnotation.managerUploadedModelsId}`);
this.globallyBlock = this.el.find(`#${window.cvat.autoAnnotation.uploadGloballyBlockId}`);
this.uploadTitle = this.el.find(`#${window.cvat.autoAnnotation.uploadTitleId}`);
this.uploadNameInput = this.el.find(`#${window.cvat.autoAnnotation.uploadNameInputId}`);
this.uploadMessage = this.el.find(`#${window.cvat.autoAnnotation.uploadMessageId}`);
this.selectedFilesLabel = this.el.find(`#${window.cvat.autoAnnotation.selectedFilesId}`);
this.modelNameInput = this.el.find(`#${window.cvat.autoAnnotation.uploadNameInputId}`);
this.localSource = this.el.find(`#${window.cvat.autoAnnotation.uploadLocalSourceId}`);
this.shareSource = this.el.find(`#${window.cvat.autoAnnotation.uploadShareSourceId}`);
this.cancelButton = this.el.find(`#${window.cvat.autoAnnotation.cancelUploadButtonId}`);
this.submitButton = this.el.find(`#${window.cvat.autoAnnotation.submitUploadButtonId}`);
this.globallyBox = this.el.find(`#${window.cvat.autoAnnotation.uploadGloballyId}`);
this.selectButton = this.el.find(`#${window.cvat.autoAnnotation.selectFilesButtonId}`);
this.localSelector = this.el.find(`#${window.cvat.autoAnnotation.localFileSelectorId}`);
this.shareSelector = $('#dashboardShareBrowseModal');
this.shareBrowseTree = $('#dashboardShareBrowser');
this.submitShare = $('#dashboardSubmitBrowseServer');
this.id = null;
this.source = this.localSource.prop('checked') ? 'local' : 'shared';
this.files = [];
function filesLabel(source, files) {
const fileLabels = source === 'local' ? [...files].map(el => el.name) : files;
if (fileLabels.length) {
const labelStr = fileLabels.join(', ');
if (labelStr.length > 30) {
return `${labelStr.substr(0, 30)}..`;
}
return labelStr;
}
return 'No Files';
}
function extractFiles(extensions, files, source) {
const extractedFiles = {};
function getExt(file) {
return source === 'local' ? file.name.split('.').pop() : file.split('.').pop();
}
function addFile(file, extention) {
if (extention in files) {
throw Error(`More than one file with the extension .${extention} have been found`);
}
extractedFiles[extention] = file;
}
files.forEach((file) => {
const fileExt = getExt(file);
if (extensions.includes(fileExt)) {
addFile(file, fileExt);
}
});
return extractedFiles;
}
function validateFiles(isUpdate, files, source) {
const extensions = ['xml', 'bin', 'py', 'json'];
const extractedFiles = extractFiles(extensions, files, source);
if (!isUpdate) {
extensions.forEach((extension) => {
if (!(extension in extractedFiles)) {
throw Error(`Please specify a .${extension} file`);
}
});
}
return extractedFiles;
}
this.localSource.on('click', () => {
if (this.source !== 'local') {
this.source = 'local';
this.files = [];
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
}
});
this.shareSource.on('click', () => {
if (this.source !== 'shared') {
this.source = 'shared';
this.files = [];
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
}
});
this.selectButton.on('click', () => {
if (this.source === 'local') {
this.localSelector.click();
} else {
this.shareSelector.appendTo('body');
this.shareBrowseTree.jstree('refresh');
this.shareSelector.removeClass('hidden');
this.shareBrowseTree.jstree({
core: {
data: {
url: 'get_share_nodes',
data: node => ({ id: node.id }),
},
},
plugins: ['checkbox', 'sort'],
});
}
});
this.submitShare.on('click', () => {
if (!this.el.hasClass('hidden')) {
this.shareSelector.addClass('hidden');
this.files = this.shareBrowseTree.jstree(true).get_selected();
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
}
});
this.localSelector.on('change', (e) => {
this.files = Array.from(e.target.files);
this.selectedFilesLabel.text(filesLabel(this.source, this.files));
});
this.cancelButton.on('click', () => this.el.addClass('hidden'));
this.submitButton.on('click', () => {
try {
this.submitButton.prop('disabled', true);
const name = $.trim(this.modelNameInput.prop('value'));
if (!name.length) {
this.uploadMessage.css('color', 'red');
this.uploadMessage.text('Please specify a model name');
return;
}
let validatedFiles = {};
try {
validatedFiles = validateFiles(this.id !== null, this.files, this.source);
} catch (err) {
this.uploadMessage.css('color', 'red');
this.uploadMessage.text(err);
return;
}
const modelData = new FormData();
modelData.append('name', name);
modelData.append('storage', this.source);
modelData.append('shared', this.globallyBox.prop('checked'));
['xml', 'bin', 'json', 'py'].filter(e => e in validatedFiles).forEach((ext) => {
modelData.append(ext, validatedFiles[ext]);
});
this.uploadMessage.text('');
const overlay = showOverlay('Send request to the server..');
window.cvat.autoAnnotation.server.update(modelData, () => {
window.location.reload();
}, (message) => {
overlay.remove();
showMessage(message);
}, (progress) => {
overlay.setMessage(progress);
}, window.cvat.autoAnnotation.server.check, this.id);
} finally {
this.submitButton.prop('disabled', false);
}
});
}
reset() {
const setBlocked = () => {
if (window.cvat.autoAnnotation.data.admin) {
this.globallyBlock.removeClass('hidden');
} else {
this.globallyBlock.addClass('hidden');
}
};
setBlocked();
this.uploadTitle.text('Create Model');
this.uploadNameInput.prop('value', '');
this.uploadMessage.css('color', '');
this.uploadMessage.text('');
this.selectedFilesLabel.text('No Files');
this.localSource.prop('checked', true);
this.globallyBox.prop('checked', false);
this.table.empty();
this.id = null;
this.source = this.localSource.prop('checked') ? 'local' : 'share';
this.files = [];
const updateButtonClickHandler = (event) => {
this.reset();
this.uploadTitle.text('Update Model');
this.uploadNameInput.prop('value', `${event.data.model.name}`);
this.id = event.data.model.id;
};
const deleteButtonClickHandler = (event) => {
userConfirm(`Do you actually want to delete the "${event.data.model.name}" model. Are you sure?`, () => {
window.cvat.autoAnnotation.server.delete(event.data.model.id, () => {
const filtered = window.cvat.autoAnnotation.data.models.filter(
item => item !== event.data.model,
);
window.cvat.autoAnnotation.data.models = filtered;
this.reset();
}, (message) => {
showMessage(message);
});
});
};
const getModelModifyButtons = (model) => {
if (model.primary) {
return '<td> <label class="h1 regular"> Primary Model </label> </td>';
}
const updateButtonHtml = '<button class="regular h3" style="width: 7em;"> Update </button>';
const deleteButtonHtml = '<button class="regular h3" style="width: 7em; margin-top: 5%;"> Delete </button>';
return $('<td> </td>').append(
$(updateButtonHtml).on('click', { model }, updateButtonClickHandler),
$(deleteButtonHtml).on('click', { model }, deleteButtonClickHandler),
);
};
window.cvat.autoAnnotation.data.models.forEach((model) => {
const rowHtml = `<tr>
<td> ${model.name} </td>
<td> ${model.uploadDate} </td>
</tr>`;
this.table.append(
$(rowHtml).append(getModelModifyButtons(model)),
);
});
return this;
}
show() {
this.el.removeClass('hidden');
return this;
}
get element() {
return this.el;
}
}
class AutoAnnotationModelRunnerView {
constructor() {
const html = `<div class="modal hidden" id="${window.cvat.autoAnnotation.runnerWindowId}">
<div class="modal-content" id="${window.cvat.autoAnnotation.runnerContentId}">
<div style="width: 55%; height: 100%; float: left;">
<center style="height: 10%;">
<label class="regular h1"> Uploaded Models </label>
</center>
<div style="height: 70%; overflow: auto; margin-top: 2%;">
<table class="modelsTable" id="${window.cvat.autoAnnotation.runnerUploadedModelsId}"> </table>
</div>
<div>
<input type="checkbox" id="${window.cvat.autoAnnotation.removeCurrentAnnotationId}"/>
<label class="regular h3" for="${window.cvat.autoAnnotation.removeCurrentAnnotationId}"> Remove current annotation </label>
</div>
</div>
<div style="width: 40%; height: 100%; float: left; margin-left: 3%;">
<center style="height: 10%;">
<label class="regular h1"> Annotation Labels </label>
</center>
<div style="height: 70%; overflow: auto; margin-top: 2%;">
<table class="regular" style="text-align: center; word-break: break-all; width: 100%;">
<thead>
<tr style="width: 100%;">
<th style="width: 45%;"> Task Label </th>
<th style="width: 45%;"> DL Model Label </th>
<th style="width: 10%;"> </th>
</tr>
</thead>
<tbody id="${window.cvat.autoAnnotation.annotationLabelsId}">
</tbody>
</table>
</div>
<div style="float:right;">
<button class="regular h3" style="width: 6em;" id="${window.cvat.autoAnnotation.submitAnnotationId}"> Start </button>
<button class="regular h3" style="width: 6em;" id="${window.cvat.autoAnnotation.cancelAnnotationId}"> Cancel </button>
</div>
</div>
</div>
</div>`;
this.el = $(html);
this.id = null;
this.tid = null;
this.initButton = null;
this.modelsTable = this.el.find(`#${window.cvat.autoAnnotation.runnerUploadedModelsId}`);
this.labelsTable = this.el.find(`#${window.cvat.autoAnnotation.annotationLabelsId}`);
this.active = null;
this.el.find(`#${window.cvat.autoAnnotation.cancelAnnotationId}`).on('click', () => {
this.el.addClass('hidden');
});
this.el.find(`#${window.cvat.autoAnnotation.submitAnnotationId}`).on('click', () => {
try {
if (this.id === null) {
throw Error('Please specify a model for an annotation process');
}
const mapping = {};
$('.annotatorMappingRow').each((_, element) => {
const dlModelLabel = $(element).find('.annotatorDlLabelSelector')[0].value;
const taskLabel = $(element).find('.annotatorTaskLabelSelector')[0].value;
if (dlModelLabel in mapping) {
throw Error(`The label "${dlModelLabel}" has been specified twice or more`);
}
mapping[dlModelLabel] = taskLabel;
});
if (!Object.keys(mapping).length) {
throw Error('Labels for an annotation process haven\'t been found');
}
const overlay = showOverlay('Request has been sent');
window.cvat.autoAnnotation.server.start(this.id, this.tid, {
reset: $(`#${window.cvat.autoAnnotation.removeCurrentAnnotationId}`).prop('checked'),
labels: mapping,
}, () => {
overlay.remove();
this.initButton[0].setupRun();
window.cvat.autoAnnotation.runner.hide();
}, (message) => {
overlay.remove();
this.initButton[0].setupRun();
showMessage(message);
}, () => {
window.location.reload();
}, window.cvat.autoAnnotation.server.check);
} catch (error) {
showMessage(error);
}
});
}
reset(data, initButton) {
function labelsSelect(labels, elClass) {
const select = $(`<select class="regular h3 ${elClass}" style="width:100%;"> </select>`);
labels.forEach(label => select.append($(`<option value="${label}"> ${label} </option>`)));
select.prop('value', null);
return select;
}
function makeCreator(dlSelect, taskSelect, callback) {
let dlIsFilled = false;
let taskIsFilled = false;
const creator = $('<tr style="margin-bottom: 5px;"> </tr>').append(
$('<td style="width: 45%;"> </td>').append(taskSelect),
$('<td style="width: 45%;"> </td>').append(dlSelect),
);
const onSelectHandler = () => {
$('<td style="width: 10%; position: relative;"> </td>').append(
$('<a class="close"></a>').css('top', '0px').on('click', (e) => {
$(e.target.parentNode.parentNode).remove();
}),
).appendTo(creator);
creator.addClass('annotatorMappingRow');
callback();
};
dlSelect.on('change', (e) => {
if (e.target.value && taskIsFilled) {
dlSelect.off('change');
taskSelect.off('change');
onSelectHandler();
}
dlIsFilled = Boolean(e.target.value);
});
taskSelect.on('change', (e) => {
if (e.target.value && dlIsFilled) {
dlSelect.off('change');
taskSelect.off('change');
onSelectHandler();
}
taskIsFilled = Boolean(e.target.value);
});
return creator;
}
this.id = null;
this.initButton = initButton;
this.tid = data.taskid;
this.modelsTable.empty();
this.labelsTable.empty();
this.active = null;
const modelItemClickHandler = (event) => {
if (this.active) {
this.active.style.color = '';
}
this.id = event.data.model.id;
this.active = event.target;
this.active.style.color = 'darkblue';
this.labelsTable.empty();
const labels = Object.values(event.data.data.spec.labels);
const intersection = labels.filter(el => event.data.model.labels.indexOf(el) !== -1);
intersection.forEach((label) => {
const dlSelect = labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector');
dlSelect.prop('value', label);
const taskSelect = labelsSelect(labels, 'annotatorTaskLabelSelector');
taskSelect.prop('value', label);
$('<tr class="annotatorMappingRow" style="margin-bottom: 5px;"> </tr>').append(
$('<td style="width: 45%;"> </td>').append(taskSelect),
$('<td style="width: 45%;"> </td>').append(dlSelect),
$('<td style="width: 10%; position: relative;"> </td>').append(
$('<a class="close"></a>').css('top', '0px').on('click', (e) => {
$(e.target.parentNode.parentNode).remove();
}),
),
).appendTo(this.labelsTable);
});
const dlSelect = labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector');
const taskSelect = labelsSelect(labels, 'annotatorTaskLabelSelector');
const callback = () => {
makeCreator(
labelsSelect(event.data.model.labels, 'annotatorDlLabelSelector'),
labelsSelect(labels, 'annotatorTaskLabelSelector'),
callback,
).appendTo(this.labelsTable);
};
makeCreator(dlSelect, taskSelect, callback).appendTo(this.labelsTable);
};
window.cvat.autoAnnotation.data.models.forEach((model) => {
this.modelsTable.append(
$(`<tr> <td> <label class="regular h3"> ${model.name} (${model.uploadDate}) </label> </td> </tr>`).on(
'click', { model, data }, modelItemClickHandler,
),
);
});
return this;
}
show() {
this.el.removeClass('hidden');
return this;
}
hide() {
this.el.addClass('hidden');
return this;
}
get element() {
return this.el;
}
}
window.cvat.autoAnnotation = {
managerWindowId: 'annotatorManagerWindow',
managerContentId: 'annotatorManagerContent',
managerUploadedModelsId: 'annotatorManagerUploadedModels',
uploadContentId: 'annotatorManagerUploadModel',
uploadTitleId: 'annotatorManagerUploadTitle',
uploadNameInputId: 'annotatorManagerUploadNameInput',
uploadLocalSourceId: 'annotatorManagerUploadLocalSource',
uploadShareSourceId: 'annotatorManagerUploadShareSource',
uploadGloballyId: 'annotatorManagerUploadGlobally',
uploadGloballyBlockId: 'annotatorManagerUploadGloballyblock',
selectFilesButtonId: 'annotatorManagerUploadSelector',
selectedFilesId: 'annotatorManagerUploadSelectedFiles',
localFileSelectorId: 'annotatorManagerUploadLocalSelector',
shareFileSelectorId: 'annotatorManagerUploadShareSelector',
submitUploadButtonId: 'annotatorManagerSubmitUploadButton',
cancelUploadButtonId: 'annotatorManagerCancelUploadButton',
uploadMessageId: 'annotatorUploadStatusMessage',
runnerWindowId: 'annotatorRunnerWindow',
runnerContentId: 'annotatorRunnerContent',
runnerUploadedModelsId: 'annotatorRunnerUploadedModels',
removeCurrentAnnotationId: 'annotatorRunnerRemoveCurrentAnnotationBox',
annotationLabelsId: 'annotatorRunnerAnnotationLabels',
submitAnnotationId: 'annotatorRunnerSubmitAnnotationButton',
cancelAnnotationId: 'annotatorRunnerCancelAnnotationButton',
managerButtonId: 'annotatorManagerButton',
};
window.cvat.dashboard.uiCallbacks.push((newElements) => {
window.cvat.autoAnnotation.server = AutoAnnotationServer;
window.cvat.autoAnnotation.manager = new AutoAnnotationModelManagerView();
window.cvat.autoAnnotation.runner = new AutoAnnotationModelRunnerView();
const tids = Array.from(newElements, el => el.id.split('_')[1]);
window.cvat.autoAnnotation.server.meta(tids, (data) => {
window.cvat.autoAnnotation.data = data;
$('body').append(window.cvat.autoAnnotation.manager.element, window.cvat.autoAnnotation.runner.element);
$(`<button id="${window.cvat.autoAnnotation.managerButtonId}" class="regular h1" style=""> Model Manager</button>`)
.on('click', () => {
const overlay = showOverlay('The manager are being setup..');
window.cvat.autoAnnotation.manager.reset().show();
overlay.remove();
}).appendTo('#dashboardManageButtons');
newElements.each((_, element) => {
const elem = $(element);
const tid = +elem.attr('id').split('_')[1];
const button = $('<button> Run Auto Annotation </button>').addClass('regular dashboardButtonUI');
button[0].setupRun = function setupRun() {
const self = $(this);
self.text('Run Auto Annotation').off('click').on('click', () => {
const overlay = showOverlay('Task date are being recieved from the server..');
$.ajax({
url: `/get/task/${tid}`,
dataType: 'json',
success: (responseData) => {
overlay.setMessage('The model runner are being setup..');
window.cvat.autoAnnotation.runner.reset(responseData, self).show();
overlay.remove();
},
error: (responseData) => {
showMessage(`Can't get task data. Code: ${responseData.status}. Message: ${responseData.responseText || responseData.statusText}`);
},
complete: () => {
overlay.remove();
},
});
});
};
button[0].setupCancel = function setupCancel() {
const self = $(this);
self.off('click').text('Cancel Auto Annotation').on('click', () => {
userConfirm('Process will be canceled. Are you sure?', () => {
window.cvat.autoAnnotation.server.cancel(tid, () => {
this.setupRun();
}, (message) => {
showMessage(message);
});
});
});
window.cvat.autoAnnotation.server.check(
window.cvat.autoAnnotation.data.run[tid].rq_id,
() => {
this.setupRun();
},
(error) => {
button[0].setupRun();
button.text('Annotation has failed');
button.title(error);
},
(progress) => {
button.text(`Cancel Auto Annotation (${progress.toString().slice(0, 4)})%`);
},
);
};
const taskStatus = window.cvat.autoAnnotation.data.run[tid];
if (taskStatus && ['queued', 'started'].includes(taskStatus.status)) {
button[0].setupCancel();
} else {
button[0].setupRun();
}
button.appendTo(elem.find('div.dashboardButtonsUI')[0]);
});
}, (error) => {
showMessage(`Cannot get models meta information: ${error}`);
});
});

@ -0,0 +1,83 @@
#annotatorManagerContent, #annotatorRunnerContent {
width: 800px;
height: 300px;
}
#annotatorManagerButton {
padding: 7px;
margin-left: 4px;
}
.modelsTable {
width: 100%;
color:#666;
text-shadow: 1px 1px 0px #fff;
background:#D2D3D4;
border:#ccc 1px solid;
border-radius: 3px;
box-shadow: 0 1px 2px black;
}
.modelsTable th {
border-top: 1px solid #fafafa;
border-bottom: 1px solid #e0e0e0;
background: #ededed;
}
.modelsTable th:first-child {
text-align: left;
padding-left:20px;
}
.modelsTable tr:first-child th:first-child {
border-top-left-radius:3px;
}
.modelsTable tr:first-child th:last-child {
border-top-right-radius:3px;
}
.modelsTable tr {
text-align: center;
padding-left: 20px;
}
.modelsTable td:first-child {
text-align: left;
padding-left: 20px;
border-left: 0;
}
.modelsTable td {
padding: 18px;
border-top: 1px solid #ffffff;
border-bottom:1px solid #e0e0e0;
border-left: 1px solid #e0e0e0;
background: #fafafa;
}
.modelsTable tr.even td {
background: #f6f6f6;
}
.modelsTable tr:last-child td {
border-bottom:0;
}
.modelsTable tr:last-child td:first-child {
border-bottom-left-radius:3px;
}
.modelsTable tr:last-child td:last-child {
border-bottom-right-radius:3px;
}
.modelsTable tr:hover td {
background: #f2f2f2;
}
#annotatorManagerUploadModel {
float: left;
padding-left: 3%;
width: 40%;
}

@ -7,8 +7,13 @@ from django.urls import path
from . import views
urlpatterns = [
path("create/task/<int:tid>", views.create),
path("check/task/<int:tid>", views.check),
path("cancel/task/<int:tid>", views.cancel),
path("create", views.create_model),
path("update/<int:mid>", views.update_model),
path("delete/<int:mid>", views.delete_model),
path("start/<int:mid>/<int:tid>", views.start_annotation),
path("check/<str:rq_id>", views.check),
path("cancel/<int:tid>", views.cancel),
path("meta/get", views.get_meta_info),
]

@ -3,32 +3,35 @@
#
# SPDX-License-Identifier: MIT
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
from rules.contrib.views import permission_required, objectgetter
from cvat.apps.authentication.decorators import login_required
from cvat.apps.engine.models import Task as TaskModel
from cvat.apps.engine import annotation
import django_rq
import fnmatch
import json
import os
import rq
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
from django.db.models import Q
from rules.contrib.views import permission_required, objectgetter
from cvat.apps.authentication.decorators import login_required
from cvat.apps.engine.models import Task as TaskModel
from cvat.apps.engine import annotation
from cvat.apps.authentication.auth import has_admin_role
from cvat.apps.engine.log import slogger
from .model_loader import ModelLoader, load_label_map
from .image_loader import ImageLoader
import os.path as osp
from . import model_manager
from .models import AnnotationModel
def get_image_data(path_to_data):
def get_image_key(item):
return int(osp.splitext(osp.basename(item))[0])
return int(os.path.splitext(os.path.basename(item))[0])
image_list = []
for root, _, filenames in os.walk(path_to_data):
for filename in fnmatch.filter(filenames, "*.jpg"):
image_list.append(osp.join(root, filename))
image_list.append(os.path.join(root, filename))
image_list.sort(key=get_image_key)
return ImageLoader(image_list)
@ -202,7 +205,6 @@ def run_inference_engine_annotation(path_to_data, model_file, weights_file,
frame_counter += 1
if not update_progress(job, frame_counter * 100 / data_len):
return None
processed_detections = process_detections(detections, convertation_file)
add_boxes(processed_detections.get_boxes(), result["create"]["boxes"])
@ -222,7 +224,7 @@ def update_progress(job, progress):
job.save_meta()
return True
def create_thread(tid, model_file, weights_file, labels_mapping, attributes, convertation_file):
def create_thread(tid, model_file, weights_file, labels_mapping, attributes, convertation_file, reset):
try:
job = rq.get_current_job()
job.meta["progress"] = 0
@ -246,85 +248,205 @@ def create_thread(tid, model_file, weights_file, labels_mapping, attributes, con
slogger.glob.info("auto annotation for task {} canceled by user".format(tid))
return
if reset:
annotation.clear_task(tid)
annotation.save_task(tid, result)
slogger.glob.info("auto annotation for task {} done".format(tid))
except Exception:
except Exception as e:
try:
slogger.task[tid].exception("exception was occurred during auto annotation of the task", exc_info=True)
except Exception as ex:
slogger.glob.exception("exception was occurred during auto annotation of the task {}: {}".format(tid, str(ex)), exc_info=True)
raise ex
raise e
@login_required
@permission_required(perm=["engine.task.change"],
fn=objectgetter(TaskModel, "tid"), raise_exception=True)
def cancel(request, tid):
try:
queue = django_rq.get_queue("low")
job = queue.fetch_job("auto_annotation.run.{}".format(tid))
if job is None or job.is_finished or job.is_failed:
raise Exception("Task is not being annotated currently")
elif "cancel" not in job.meta:
job.meta["cancel"] = True
job.save()
except Exception as ex:
try:
slogger.task[tid].exception("cannot cancel auto annotation for task #{}".format(tid), exc_info=True)
except Exception as logger_ex:
slogger.glob.exception("exception was occured during cancel auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True)
return HttpResponseBadRequest(str(ex))
return HttpResponse()
@login_required
@permission_required(perm=["auto_annotation.model.create"], raise_exception=True)
def create_model(request):
if request.method != 'POST':
return HttpResponseBadRequest("Only POST requests are accepted")
try:
params = request.POST
storage = params["storage"]
name = params["name"]
is_shared = params["shared"].lower() == "true"
if is_shared and not has_admin_role(request.user):
raise Exception("Only admin can create shared models")
files = request.FILES if storage == "local" else params
model = files["xml"]
weights = files["bin"]
labelmap = files["json"]
interpretation_script = files["py"]
owner = request.user
dl_model_id = model_manager.create_empty(owner=owner)
rq_id = model_manager.update_model(
dl_model_id=dl_model_id,
name=name,
model_file=model,
weights_file=weights,
labelmap_file=labelmap,
interpretation_file=interpretation_script,
storage=storage,
is_shared=is_shared,
)
return JsonResponse({"id": rq_id})
except Exception as e:
return HttpResponseBadRequest(str(e))
@login_required
@permission_required(perm=["auto_annotation.model.update"],
fn=objectgetter(AnnotationModel, "mid"), raise_exception=True)
def update_model(request, mid):
if request.method != 'POST':
return HttpResponseBadRequest("Only POST requests are accepted")
try:
params = request.POST
storage = params["storage"]
name = params.get("name")
is_shared = params.get("shared")
is_shared = is_shared.lower() == "true" if is_shared else None
if is_shared and not has_admin_role(request.user):
raise Exception("Only admin can create shared models")
files = request.FILES
model = files.get("xml")
weights = files.get("bin")
labelmap = files.get("json")
interpretation_script = files.get("py")
rq_id = model_manager.update_model(
dl_model_id=mid,
name=name,
model_file=model,
weights_file=weights,
labelmap_file=labelmap,
interpretation_file=interpretation_script,
storage=storage,
is_shared=is_shared,
)
return JsonResponse({"id": rq_id})
except Exception as e:
return HttpResponseBadRequest(str(e))
@login_required
@permission_required(perm=["auto_annotation.model.delete"],
fn=objectgetter(AnnotationModel, "mid"), raise_exception=True)
def delete_model(request, mid):
if request.method != 'DELETE':
return HttpResponseBadRequest("Only DELETE requests are accepted")
model_manager.delete(mid)
return HttpResponse()
@login_required
def get_meta_info(request):
try:
tids = json.loads(request.body.decode('utf-8'))
response = {
"admin": has_admin_role(request.user),
"models": [],
"run": {},
}
dl_model_list = list(AnnotationModel.objects.filter(Q(owner=request.user) | Q(primary=True) | Q(shared=True)).order_by('-created_date'))
for dl_model in dl_model_list:
labels = []
if dl_model.labelmap_file and os.path.exists(dl_model.labelmap_file.name):
with dl_model.labelmap_file.open('r') as f:
labels = list(json.load(f)["label_map"].values())
response["models"].append({
"id": dl_model.id,
"name": dl_model.name,
"primary": dl_model.primary,
"uploadDate": dl_model.created_date,
"updateDate": dl_model.updated_date,
"labels": labels,
})
queue = django_rq.get_queue("low")
tids = json.loads(request.body.decode("utf-8"))
result = {}
for tid in tids:
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
rq_id = "auto_annotation.run.{}".format(tid)
job = queue.fetch_job(rq_id)
if job is not None:
result[tid] = {
"active": job.is_queued or job.is_started,
"success": not job.is_failed
response["run"][tid] = {
"status": job.status,
"rq_id": rq_id,
}
return JsonResponse(result)
except Exception as ex:
slogger.glob.exception("exception was occurred during auto annotation meta request", exc_info=True)
return HttpResponseBadRequest(str(ex))
return JsonResponse(response)
except Exception as e:
return HttpResponseBadRequest(str(e))
@login_required
@permission_required(perm=["engine.task.change"],
fn=objectgetter(TaskModel, "tid"), raise_exception=True)
def create(request, tid):
slogger.glob.info("auto annotation create request for task {}".format(tid))
def write_file(path, file_obj):
with open(path, "wb") as upload_file:
for chunk in file_obj.chunks():
upload_file.write(chunk)
@permission_required(perm=["auto_annotation.model.access"],
fn=objectgetter(AnnotationModel, "mid"), raise_exception=True)
def start_annotation(request, mid, tid):
slogger.glob.info("auto annotation create request for task {} via DL model {}".format(tid, mid))
try:
db_task = TaskModel.objects.get(pk=tid)
upload_dir = db_task.get_upload_dirname()
queue = django_rq.get_queue("low")
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
job = queue.fetch_job("auto_annotation.run.{}".format(tid))
if job is not None and (job.is_started or job.is_queued):
raise Exception("The process is already running")
model_file = request.FILES["model"]
model_file_path = os.path.join(upload_dir, model_file.name)
write_file(model_file_path, model_file)
data = json.loads(request.body.decode('utf-8'))
weights_file = request.FILES["weights"]
weights_file_path = os.path.join(upload_dir, weights_file.name)
write_file(weights_file_path, weights_file)
should_reset = data["reset"]
user_defined_labels_mapping = data["labels"]
config_file = request.FILES["config"]
config_file_path = os.path.join(upload_dir, config_file.name)
write_file(config_file_path, config_file)
dl_model = AnnotationModel.objects.get(pk=mid)
convertation_file = request.FILES["conv_script"]
convertation_file_path = os.path.join(upload_dir, convertation_file.name)
write_file(convertation_file_path, convertation_file)
model_file_path = dl_model.model_file.name
weights_file_path = dl_model.weights_file.name
labelmap_file = dl_model.labelmap_file.name
convertation_file_path = dl_model.interpretation_file.name
db_labels = db_task.label_set.prefetch_related("attributespec_set").all()
db_attributes = {db_label.id:
{db_attr.get_name(): db_attr.id for db_attr in db_label.attributespec_set.all()} for db_label in db_labels}
db_labels = {db_label.id:db_label.name for db_label in db_labels}
db_labels = {db_label.name:db_label.id for db_label in db_labels}
class_names = load_label_map(config_file_path)
model_labels = {value: key for key, value in load_label_map(labelmap_file).items()}
labels_mapping = {}
for db_key, db_label in db_labels.items():
for key, label in class_names.items():
if label == db_label:
labels_mapping[int(key)] = db_key
for user_model_label, user_db_label in user_defined_labels_mapping.items():
if user_model_label in model_labels and user_db_label in db_labels:
labels_mapping[int(model_labels[user_model_label])] = db_labels[user_db_label]
if not labels_mapping:
raise Exception("No labels found for annotation")
rq_id="auto_annotation.run.{}".format(tid)
queue.enqueue_call(func=create_thread,
args=(
tid,
@ -332,8 +454,10 @@ def create(request, tid):
weights_file_path,
labels_mapping,
db_attributes,
convertation_file_path),
job_id="auto_annotation.create/{}".format(tid),
convertation_file_path,
should_reset,
),
job_id = rq_id,
timeout=604800) # 7 days
slogger.task[tid].info("auto annotation job enqueued")
@ -345,15 +469,14 @@ def create(request, tid):
slogger.glob.exception("exception was occurred during create auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True)
return HttpResponseBadRequest(str(ex))
return HttpResponse()
return JsonResponse({"id": rq_id})
@login_required
@permission_required(perm=["engine.task.access"],
fn=objectgetter(TaskModel, "tid"), raise_exception=True)
def check(request, tid):
def check(request, rq_id):
try:
queue = django_rq.get_queue("low")
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
target_queue = "low" if "auto_annotation.run" in rq_id else "default"
queue = django_rq.get_queue(target_queue)
job = queue.fetch_job(rq_id)
if job is not None and "cancel" in job.meta:
return JsonResponse({"status": "finished"})
data = {}
@ -363,39 +486,16 @@ def check(request, tid):
data["status"] = "queued"
elif job.is_started:
data["status"] = "started"
data["progress"] = job.meta["progress"]
data["progress"] = job.meta["progress"] if "progress" in job.meta else ""
elif job.is_finished:
data["status"] = "finished"
job.delete()
else:
data["status"] = "failed"
data["stderr"] = job.exc_info
data["error"] = job.exc_info
job.delete()
except Exception:
data["status"] = "unknown"
return JsonResponse(data)
@login_required
@permission_required(perm=["engine.task.change"],
fn=objectgetter(TaskModel, "tid"), raise_exception=True)
def cancel(request, tid):
try:
queue = django_rq.get_queue("low")
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
if job is None or job.is_finished or job.is_failed:
raise Exception("Task is not being annotated currently")
elif "cancel" not in job.meta:
job.meta["cancel"] = True
job.save()
except Exception as ex:
try:
slogger.task[tid].exception("cannot cancel auto annotation for task #{}".format(tid), exc_info=True)
except Exception as logger_ex:
slogger.glob.exception("exception was occured during cancel auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True)
return HttpResponseBadRequest(str(ex))
return HttpResponse()

@ -7,7 +7,7 @@
/* global
AnnotationParser:false
Config:false
confirm:false
userConfirm:false
ConstIdGenerator:false
createExportContainer:false
dumpAnnotationRequest:false
@ -97,7 +97,7 @@ function updateTaskRequest(labels) {
function removeTaskRequest() {
confirm("The action can not be undone. Are you sure?", confirmCallback);
userConfirm("The action can not be undone. Are you sure?", confirmCallback);
function confirmCallback() {
$.ajax ({
@ -266,7 +266,7 @@ window.cvat.dashboard.uiCallbacks.push(function(elements) {
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);
userConfirm("The current annotation will be lost. Are you sure?", uploadAnnotationRequest);
});
updateButton.on("click", function() {
@ -400,9 +400,11 @@ function setupTaskCreator() {
cancelBrowseServer.on("click", () => shareFileSelector.addClass("hidden"));
submitBrowseServer.on("click", function() {
if (!createModal.hasClass("hidden")) {
files = shareBrowseTree.jstree(true).get_selected();
cancelBrowseServer.click();
updateSelectedFiles();
}
});
flipImagesBox.on("click", (e) => {

@ -65,7 +65,7 @@
padding: 10px;
border: 1px solid grey;
float: left;
width: 70%;
width: 76%;
background: #f1f1f1;
outline: 0;
}
@ -75,7 +75,6 @@
width: 20%;
padding: 10px;
background: #0b7dda;
color: white;
border: 1px solid grey;
border-left: none;
cursor: pointer;
@ -90,10 +89,6 @@
background: #0b7fff;
}
#dashboardCreateTaskButton {
font-size: 2em;
}
#dashboardCreateContent {
width: 500px;
display: table;

@ -15,6 +15,9 @@
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/stylesheet.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/jstree/themes/default/style.css' %}">
{% for css_file in css_3rdparty %}
<link rel="stylesheet" type="text/css" href="{% static css_file %}">
{% endfor %}
{% endblock %}
{% block head_js_3rdparty %}
@ -41,19 +44,19 @@
{% block content %}
<div id="content">
<center>
<table style="width: 65%;">
<tr>
<td><button id="dashboardCreateTaskButton" class="regular h1"> Create New Task </button></td>
<td>
<div style="width: 300px; float: right;">
<input type="text" id="dashboardSearchInput" class="regular h1" placeholder="Search.." name="search">
<button id="dashboardSearchSubmit" class="regular h1"> &#x1F50D; </button>
<div style="width: 100%; display: flex;">
<div style="width: 50%; display: flex;"> </div>
<div style="width: 50%; display: flex;">
<div id="dashboardManageButtons" style="display: flex;">
<button id="dashboardCreateTaskButton" class="regular h1" style="padding: 7px;"> Create New Task </button>
</div>
<div style="width: 300px; display: flex; margin-left: 4px;">
<input type="text" id="dashboardSearchInput" class="regular h3" placeholder="Search.." name="search">
<button id="dashboardSearchSubmit" class="regular h3"> &#x1F50D; </button>
</div>
</div>
<div style="width: 50%; display: flex;"> </div>
</div>
</td>
</tr>
</table>
</center>
{% autopaginate data %}
<div style="float: left; width: 100%">

@ -10,7 +10,7 @@ from django.conf import settings
from cvat.apps.authentication.decorators import login_required
from cvat.apps.engine.models import Task as TaskModel, Job as JobModel
from cvat.settings.base import JS_3RDPARTY
from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY
import os
@ -77,4 +77,5 @@ def DashboardView(request):
'base_url': "{0}://{1}/".format(request.scheme, request.get_host()),
'share_path': os.getenv('CVAT_SHARE_URL', default=r'${cvat_root}/share'),
'js_3rdparty': JS_3RDPARTY.get('dashboard', []),
'css_3rdparty': CSS_3RDPARTY.get('dashboard', []),
})

@ -85,6 +85,36 @@ html {
width: 80%;
}
.close {
position: absolute;
right: 5px;
top: 5px;
width: 32px;
height: 32px;
opacity: 0.3;
}
.close:hover {
opacity: 1;
}
.close:before, .close:after {
position: absolute;
left: 15px;
content: ' ';
height: 22px;
width: 2px;
background-color: #333;
}
.close:before {
transform: rotate(45deg);
}
.close:after {
transform: rotate(-45deg);
}
.tab {
overflow: hidden;
border: 1px solid black;

@ -12,7 +12,7 @@
AAMView:false
AnnotationParser:false
Config:false
confirm:false
userConfirm:false
CoordinateTranslator:false
dumpAnnotationRequest:false
HistoryController:false
@ -604,7 +604,7 @@ function setupMenu(job, shapeCollectionModel, annotationParser, aamModel, player
$('#uploadAnnotationButton').on('click', () => {
hide();
confirm('Current annotation will be removed from the client. Continue?',
userConfirm('Current annotation will be removed from the client. Continue?',
() => {
uploadAnnotation(shapeCollectionModel, historyModel, annotationParser, $('#uploadAnnotationButton'));
}
@ -614,7 +614,7 @@ function setupMenu(job, shapeCollectionModel, annotationParser, aamModel, player
$('#removeAnnotationButton').on('click', () => {
if (!window.cvat.mode) {
hide();
confirm('Do you want to remove all annotations? The action cannot be undone!',
userConfirm('Do you want to remove all annotations? The action cannot be undone!',
() => {
historyModel.empty();
shapeCollectionModel.empty();

@ -5,7 +5,7 @@
*/
/* exported
confirm
userConfirm
createExportContainer
dumpAnnotationRequest
ExportType
@ -25,7 +25,7 @@ Math.clamp = function(x, min, max) {
};
function confirm(message, onagree, ondisagree) {
function userConfirm(message, onagree, ondisagree) {
let template = $('#confirmTemplate');
let confirmWindow = $(template.html()).css('display', 'block');

@ -8,7 +8,7 @@
/* global
AREA_TRESHOLD:false
confirm:false
userConfirm:false
Listener:false
Logger:false
Mousetrap:false
@ -313,7 +313,7 @@ class ShapeBufferController {
message += 'Are you sure?';
propagateDialogShowed = true;
confirm(message, () => {
userConfirm(message, () => {
this._model.propagateToFrames();
propagateDialogShowed = false;
}, () => propagateDialogShowed = false);

@ -29,40 +29,6 @@
stroke: grey !important;
}
.close {
position: absolute;
right: 5px;
top: 5px;
width: 32px;
height: 32px;
opacity: 0.3;
}
.close:hover {
opacity: 1;
}
.close:before, .close:after {
position: absolute;
left: 15px;
content: ' ';
height: 22px;
width: 2px;
background-color: #333;
}
.close:before {
transform: rotate(45deg);
}
.close:after {
transform: rotate(-45deg);
}
.graphicButton {
opacity: 0.6;
width: 20px;

@ -9,6 +9,9 @@
{% block head_css %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'engine/stylesheet.css' %}">
{% for css_file in css_3rdparty %}
<link rel="stylesheet" type="text/css" src="{% static css_file %}">
{% endfor %}
{% endblock %}

@ -15,7 +15,7 @@ from django.views.decorators.gzip import gzip_page
from sendfile import sendfile
from . import annotation, task, models
from cvat.settings.base import JS_3RDPARTY
from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY
from cvat.apps.authentication.decorators import login_required
from requests.exceptions import RequestException
import logging
@ -38,6 +38,7 @@ def dispatch_request(request):
"""An entry point to dispatch legacy requests"""
if request.method == 'GET' and 'id' in request.GET:
return render(request, 'engine/annotation.html', {
'css_3rdparty': CSS_3RDPARTY.get('engine', []),
'js_3rdparty': JS_3RDPARTY.get('engine', []),
'status_list': [str(i) for i in StatusChoice]
})

@ -5,7 +5,7 @@
*/
/* global
confirm:false
userConfirm:false
showMessage:false
*/
@ -85,10 +85,10 @@ function onTFAnnotationClick() {
window.cvat.dashboard.taskName = taskName;
if (button.hasClass("tfAnnotationProcess")) {
confirm('The process will be canceled. Continue?', CancelTFAnnotationRequest.bind(button));
userConfirm('The process will be canceled. Continue?', CancelTFAnnotationRequest.bind(button));
}
else {
confirm('The current annotation will be lost. Are you sure?', RunTFAnnotationRequest.bind(button));
userConfirm('The current annotation will be lost. Are you sure?', RunTFAnnotationRequest.bind(button));
}
}

@ -78,6 +78,7 @@ except Exception:
# Application definition
JS_3RDPARTY = {}
CSS_3RDPARTY = {}
INSTALLED_APPS = [
'django.contrib.admin',
@ -308,6 +309,8 @@ DATA_ROOT = os.path.join(BASE_DIR, 'data')
os.makedirs(DATA_ROOT, exist_ok=True)
SHARE_ROOT = os.path.join(BASE_DIR, 'share')
os.makedirs(SHARE_ROOT, exist_ok=True)
MODELS_ROOT=os.path.join(BASE_DIR, 'models')
os.makedirs(MODELS_ROOT, exist_ok=True)
DATA_UPLOAD_MAX_MEMORY_SIZE = 100 * 1024 * 1024 # 100 MB
DATA_UPLOAD_MAX_NUMBER_FIELDS = None # this django check disabled

@ -55,9 +55,11 @@ services:
- cvat_data:/home/django/data
- cvat_keys:/home/django/keys
- cvat_logs:/home/django/logs
- cvat_models:/home/django/models
volumes:
cvat_db:
cvat_data:
cvat_keys:
cvat_logs:
cvat_models:

Loading…
Cancel
Save