Release 0.4.0
@ -0,0 +1,8 @@
|
|||||||
|
[bandit]
|
||||||
|
# B101 : assert_used
|
||||||
|
# B102 : exec_used
|
||||||
|
# B320 : xml_bad_etree
|
||||||
|
# B404 : import_subprocess
|
||||||
|
# B406 : import_xml_sax
|
||||||
|
# B410 : import_lxml
|
||||||
|
skips: B101,B102,B320,B404,B406,B410
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
exclude_paths:
|
||||||
|
- '**/3rdparty/**'
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"env": {
|
||||||
|
"node": false,
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"jquery": true,
|
||||||
|
"qunit": true,
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "script",
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"security",
|
||||||
|
"no-unsanitized",
|
||||||
|
"no-unsafe-innerhtml",
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:security/recommended",
|
||||||
|
"plugin:no-unsanitized/DOM",
|
||||||
|
"airbnb",
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-new": [0],
|
||||||
|
"class-methods-use-this": [0],
|
||||||
|
"no-restricted-properties": [0, {
|
||||||
|
"object": "Math",
|
||||||
|
"property": "pow",
|
||||||
|
}],
|
||||||
|
"no-param-reassign": [0],
|
||||||
|
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
|
||||||
|
"no-restricted-syntax": [0, {"selector": "ForOfStatement"}],
|
||||||
|
"no-continue": [0],
|
||||||
|
"no-unsafe-innerhtml/no-unsafe-innerhtml": 1,
|
||||||
|
// This rule actual for user input data on the node.js environment mainly.
|
||||||
|
"security/detect-object-injection": 0,
|
||||||
|
"indent": ["warn", 4],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
exports.settings = {bullet: '*', paddedTable: false}
|
||||||
|
|
||||||
|
exports.plugins = [
|
||||||
|
'remark-preset-lint-recommended',
|
||||||
|
'remark-preset-lint-consistent',
|
||||||
|
['remark-preset-lint-markdown-style-guide', 'mixed'],
|
||||||
|
['remark-lint-no-dead-urls', { skipOffline: true }],
|
||||||
|
['remark-lint-maximum-line-length', 120],
|
||||||
|
['remark-lint-maximum-heading-length', 120],
|
||||||
|
['remark-lint-strong-marker', "*"],
|
||||||
|
['remark-lint-emphasis-marker', "_"],
|
||||||
|
['remark-lint-unordered-list-marker-style', "-"],
|
||||||
|
['remark-lint-ordered-list-marker-style', "."],
|
||||||
|
['remark-lint-no-file-name-irregular-characters', false],
|
||||||
|
['remark-lint-list-item-spacing', false],
|
||||||
|
]
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
sudo: required
|
||||||
|
|
||||||
|
language: python
|
||||||
|
|
||||||
|
python:
|
||||||
|
- "3.5"
|
||||||
|
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml up --build -d
|
||||||
|
|
||||||
|
script:
|
||||||
|
- docker exec -it cvat /bin/bash -c 'tests/node_modules/.bin/karma start tests/karma.conf.js'
|
||||||
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
### There are some additional components for CVAT
|
|
||||||
|
|
||||||
* [NVIDIA CUDA](cuda/README.md)
|
|
||||||
* [OpenVINO](openvino/README.md)
|
|
||||||
* [Tensorflow Object Detector](tf_annotation/README.md)
|
|
||||||
* [Analytics](analytics/README.md)
|
|
||||||
@ -0,0 +1,165 @@
|
|||||||
|
## Auto annotation
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
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.
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"label_map": {
|
||||||
|
"0": "background",
|
||||||
|
"1": "aeroplane",
|
||||||
|
"2": "bicycle",
|
||||||
|
"3": "bird",
|
||||||
|
"4": "boat",
|
||||||
|
"5": "bottle",
|
||||||
|
"6": "bus",
|
||||||
|
"7": "car",
|
||||||
|
"8": "cat",
|
||||||
|
"9": "chair",
|
||||||
|
"10": "cow",
|
||||||
|
"11": "diningtable",
|
||||||
|
"12": "dog",
|
||||||
|
"13": "horse",
|
||||||
|
"14": "motorbike",
|
||||||
|
"15": "person",
|
||||||
|
"16": "pottedplant",
|
||||||
|
"17": "sheep",
|
||||||
|
"18": "sofa",
|
||||||
|
"19": "train",
|
||||||
|
"20": "tvmonitor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
1. __Interpretation script__ (*.py) - a file used to convert net output layer
|
||||||
|
to a predefined structure which can be processed by CVAT. This code will be run
|
||||||
|
inside a restricted python's environment, but it's possible to use some
|
||||||
|
builtin functions like __str, int, float, max, min, range__.
|
||||||
|
|
||||||
|
Also two variables are available in the scope:
|
||||||
|
|
||||||
|
- __detections__ - a list of dictionaries with detections for each frame:
|
||||||
|
* __frame_id__ - frame number
|
||||||
|
* __frame_height__ - frame height
|
||||||
|
* __frame_width__ - frame width
|
||||||
|
* __detections__ - output np.ndarray (See [ExecutableNetwork.infer](https://software.intel.com/en-us/articles/OpenVINO-InferEngine#inpage-nav-11-6-3) for details).
|
||||||
|
|
||||||
|
- __results__ - an instance of python class with converted results.
|
||||||
|
Following methods should be used to add shapes:
|
||||||
|
```python
|
||||||
|
# xtl, ytl, xbr, ybr - expected values are float or int
|
||||||
|
# label - expected value is int
|
||||||
|
# frame_number - expected value is int
|
||||||
|
# attributes - dictionary of attribute_name: attribute_value pairs, for example {"confidence": "0.83"}
|
||||||
|
add_box(self, xtl, ytl, xbr, ybr, label, frame_number, attributes=None)
|
||||||
|
|
||||||
|
# points - list of (x, y) pairs of float or int, for example [(57.3, 100), (67, 102.7)]
|
||||||
|
# label - expected value is int
|
||||||
|
# frame_number - expected value is int
|
||||||
|
# attributes - dictionary of attribute_name: attribute_value pairs, for example {"confidence": "0.83"}
|
||||||
|
add_points(self, points, label, frame_number, attributes=None)
|
||||||
|
add_polygon(self, points, label, frame_number, attributes=None)
|
||||||
|
add_polyline(self, points, label, frame_number, attributes=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
#### [Person-vehicle-bike-detection-crossroad-0078](https://github.com/opencv/open_model_zoo/blob/2018/intel_models/person-vehicle-bike-detection-crossroad-0078/description/person-vehicle-bike-detection-crossroad-0078.md) (OpenVINO toolkit)
|
||||||
|
|
||||||
|
__Links__
|
||||||
|
- [person-vehicle-bike-detection-crossroad-0078.xml](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-vehicle-bike-detection-crossroad-0078/FP32/person-vehicle-bike-detection-crossroad-0078.xml)
|
||||||
|
- [person-vehicle-bike-detection-crossroad-0078.bin](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-vehicle-bike-detection-crossroad-0078/FP32/person-vehicle-bike-detection-crossroad-0078.bin)
|
||||||
|
|
||||||
|
__Task labels__: person vehicle non-vehicle
|
||||||
|
|
||||||
|
__label_map.json__:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"label_map": {
|
||||||
|
"1": "person",
|
||||||
|
"2": "vehicle",
|
||||||
|
"3": "non-vehicle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
__Interpretation script for SSD based networks__:
|
||||||
|
```python
|
||||||
|
def clip(value):
|
||||||
|
return max(min(1.0, value), 0.0)
|
||||||
|
|
||||||
|
for frame_results in detections:
|
||||||
|
frame_height = frame_results["frame_height"]
|
||||||
|
frame_width = frame_results["frame_width"]
|
||||||
|
frame_number = frame_results["frame_id"]
|
||||||
|
|
||||||
|
for i in range(frame_results["detections"].shape[2]):
|
||||||
|
confidence = frame_results["detections"][0, 0, i, 2]
|
||||||
|
if confidence < 0.5:
|
||||||
|
continue
|
||||||
|
|
||||||
|
results.add_box(
|
||||||
|
xtl=clip(frame_results["detections"][0, 0, i, 3]) * frame_width,
|
||||||
|
ytl=clip(frame_results["detections"][0, 0, i, 4]) * frame_height,
|
||||||
|
xbr=clip(frame_results["detections"][0, 0, i, 5]) * frame_width,
|
||||||
|
ybr=clip(frame_results["detections"][0, 0, i, 6]) * frame_height,
|
||||||
|
label=int(frame_results["detections"][0, 0, i, 1]),
|
||||||
|
frame_number=frame_number,
|
||||||
|
attributes={
|
||||||
|
"confidence": "{:.2f}".format(confidence),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [Landmarks-regression-retail-0009](https://github.com/opencv/open_model_zoo/blob/2018/intel_models/landmarks-regression-retail-0009/description/landmarks-regression-retail-0009.md) (OpenVINO toolkit)
|
||||||
|
|
||||||
|
__Links__
|
||||||
|
- [landmarks-regression-retail-0009.xml](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/landmarks-regression-retail-0009/FP32/landmarks-regression-retail-0009.xml)
|
||||||
|
- [landmarks-regression-retail-0009.bin](https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/landmarks-regression-retail-0009/FP32/landmarks-regression-retail-0009.bin)
|
||||||
|
|
||||||
|
__Task labels__: left_eye right_eye tip_of_nose left_lip_corner right_lip_corner
|
||||||
|
|
||||||
|
__label_map.json__:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"label_map": {
|
||||||
|
"0": "left_eye",
|
||||||
|
"1": "right_eye",
|
||||||
|
"2": "tip_of_nose",
|
||||||
|
"3": "left_lip_corner",
|
||||||
|
"4": "right_lip_corner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
__Interpretation script__:
|
||||||
|
```python
|
||||||
|
def clip(value):
|
||||||
|
return max(min(1.0, value), 0.0)
|
||||||
|
|
||||||
|
for frame_results in detections:
|
||||||
|
frame_height = frame_results["frame_height"]
|
||||||
|
frame_width = frame_results["frame_width"]
|
||||||
|
frame_number = frame_results["frame_id"]
|
||||||
|
|
||||||
|
for i in range(0, frame_results["detections"].shape[1], 2):
|
||||||
|
x = frame_results["detections"][0, i, 0, 0]
|
||||||
|
y = frame_results["detections"][0, i + 1, 0, 0]
|
||||||
|
|
||||||
|
results.add_points(
|
||||||
|
points=[(clip(x) * frame_width, clip(y) * frame_height)],
|
||||||
|
label=i // 2, # see label map and model output specification,
|
||||||
|
frame_number=frame_number,
|
||||||
|
)
|
||||||
|
```
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY
|
||||||
|
|
||||||
|
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']
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AutoAnnotationConfig(AppConfig):
|
||||||
|
name = "cvat.apps.auto_annotation"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from .permissions import setup_permissions
|
||||||
|
|
||||||
|
setup_permissions()
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
|
||||||
|
class ImageLoader():
|
||||||
|
def __init__(self, image_list):
|
||||||
|
self.image_list = image_list
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
return self.image_list[i]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for imagename in self.image_list:
|
||||||
|
yield self._load_image(imagename)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.image_list)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_image(path_to_image):
|
||||||
|
return cv2.imread(path_to_image)
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from openvino.inference_engine import IENetwork, IEPlugin
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_instruction(instruction):
|
||||||
|
return instruction == str.strip(
|
||||||
|
subprocess.check_output(
|
||||||
|
'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True
|
||||||
|
).decode('utf-8')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_plugin():
|
||||||
|
if _IE_PLUGINS_PATH is None:
|
||||||
|
raise OSError('Inference engine plugin path env not found in the system.')
|
||||||
|
|
||||||
|
plugin = IEPlugin(device='CPU', plugin_dirs=[_IE_PLUGINS_PATH])
|
||||||
|
if (_check_instruction('avx2')):
|
||||||
|
plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_avx2.so'))
|
||||||
|
elif (_check_instruction('sse4')):
|
||||||
|
plugin.add_cpu_extension(os.path.join(_IE_PLUGINS_PATH, 'libcpu_extension_sse4.so'))
|
||||||
|
else:
|
||||||
|
raise Exception('Inference engine requires a support of avx2 or sse4.')
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
|
||||||
|
|
||||||
|
def make_network(model, weights):
|
||||||
|
return IENetwork.from_ir(model = model, weights = weights)
|
||||||
@ -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,47 @@
|
|||||||
|
|
||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import json
|
||||||
|
import cv2
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from cvat.apps.auto_annotation.inference_engine import make_plugin, make_network
|
||||||
|
|
||||||
|
class ModelLoader():
|
||||||
|
def __init__(self, model, weights):
|
||||||
|
self._model = model
|
||||||
|
self._weights = weights
|
||||||
|
|
||||||
|
IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH")
|
||||||
|
if not IE_PLUGINS_PATH:
|
||||||
|
raise OSError("Inference engine plugin path env not found in the system.")
|
||||||
|
|
||||||
|
plugin = make_plugin()
|
||||||
|
network = make_network(self._model, self._weights)
|
||||||
|
|
||||||
|
supported_layers = plugin.get_supported_layers(network)
|
||||||
|
not_supported_layers = [l for l in network.layers.keys() if l not in supported_layers]
|
||||||
|
if len(not_supported_layers) != 0:
|
||||||
|
raise Exception("Following layers are not supported by the plugin for specified device {}:\n {}".
|
||||||
|
format(plugin.device, ", ".join(not_supported_layers)))
|
||||||
|
|
||||||
|
self._input_blob_name = next(iter(network.inputs))
|
||||||
|
self._output_blob_name = next(iter(network.outputs))
|
||||||
|
|
||||||
|
self._net = plugin.load(network=network, num_requests=2)
|
||||||
|
input_type = network.inputs[self._input_blob_name]
|
||||||
|
self._input_layout = input_type if isinstance(input_type, list) else input_type.shape
|
||||||
|
|
||||||
|
def infer(self, image):
|
||||||
|
_, _, h, w = self._input_layout
|
||||||
|
in_frame = image if image.shape[:-1] == (h, w) else cv2.resize(image, (w, h))
|
||||||
|
in_frame = in_frame.transpose((2, 0, 1)) # Change data layout from HWC to CHW
|
||||||
|
return self._net.infer(inputs={self._input_blob_name: in_frame})[self._output_blob_name].copy()
|
||||||
|
|
||||||
|
|
||||||
|
def load_label_map(labels_path):
|
||||||
|
with open(labels_path, "r") as f:
|
||||||
|
return json.load(f)["label_map"]
|
||||||
@ -0,0 +1,390 @@
|
|||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import django_rq
|
||||||
|
import fnmatch
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
import rq
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from cvat.apps.engine.log import slogger
|
||||||
|
from cvat.apps.engine.models import Task as TaskModel
|
||||||
|
from cvat.apps.engine.serializers import LabeledDataSerializer
|
||||||
|
from cvat.apps.engine.annotation import put_task_data, patch_task_data
|
||||||
|
|
||||||
|
from .models import AnnotationModel, FrameworkChoice
|
||||||
|
from .model_loader import ModelLoader
|
||||||
|
from .image_loader import ImageLoader
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def _update_dl_model_thread(dl_model_id, name, is_shared, model_file, weights_file, labelmap_file,
|
||||||
|
interpretation_file, run_tests, is_local_storage, delete_if_test_fails):
|
||||||
|
def _get_file_content(filename):
|
||||||
|
return os.path.basename(filename), open(filename, "rb")
|
||||||
|
|
||||||
|
def _delete_source_files():
|
||||||
|
for f in [model_file, weights_file, labelmap_file, interpretation_file]:
|
||||||
|
if f:
|
||||||
|
os.remove(f)
|
||||||
|
|
||||||
|
def _run_test(model_file, weights_file, labelmap_file, interpretation_file):
|
||||||
|
test_image = np.ones((1024, 1980, 3), np.uint8) * 255
|
||||||
|
try:
|
||||||
|
_run_inference_engine_annotation(
|
||||||
|
data=[test_image,],
|
||||||
|
model_file=model_file,
|
||||||
|
weights_file=weights_file,
|
||||||
|
labels_mapping=labelmap_file,
|
||||||
|
attribute_spec={},
|
||||||
|
convertation_file=interpretation_file,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
job = rq.get_current_job()
|
||||||
|
job.meta["progress"] = "Saving data"
|
||||||
|
job.save_meta()
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
dl_model = AnnotationModel.objects.select_for_update().get(pk=dl_model_id)
|
||||||
|
|
||||||
|
test_res = True
|
||||||
|
message = ""
|
||||||
|
if run_tests:
|
||||||
|
job.meta["progress"] = "Test started"
|
||||||
|
job.save_meta()
|
||||||
|
|
||||||
|
test_res, message = _run_test(
|
||||||
|
model_file=model_file or dl_model.model_file.name,
|
||||||
|
weights_file=weights_file or dl_model.weights_file.name,
|
||||||
|
labelmap_file=labelmap_file or dl_model.labelmap_file.name,
|
||||||
|
interpretation_file=interpretation_file or dl_model.interpretation_file.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not test_res:
|
||||||
|
job.meta["progress"] = "Test failed"
|
||||||
|
if delete_if_test_fails:
|
||||||
|
shutil.rmtree(dl_model.get_dirname(), ignore_errors=True)
|
||||||
|
dl_model.delete()
|
||||||
|
else:
|
||||||
|
job.meta["progress"] = "Test passed"
|
||||||
|
job.save_meta()
|
||||||
|
|
||||||
|
# update DL model
|
||||||
|
if test_res:
|
||||||
|
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 name:
|
||||||
|
dl_model.name = name
|
||||||
|
|
||||||
|
if is_shared != None:
|
||||||
|
dl_model.shared = is_shared
|
||||||
|
|
||||||
|
dl_model.updated_date = timezone.now()
|
||||||
|
dl_model.save()
|
||||||
|
|
||||||
|
if is_local_storage:
|
||||||
|
_delete_source_files()
|
||||||
|
|
||||||
|
if not test_res:
|
||||||
|
raise Exception("Model was not properly created/updated. Test failed: {}".format(message))
|
||||||
|
|
||||||
|
def create_or_update(dl_model_id, name, model_file, weights_file, labelmap_file, interpretation_file, owner, 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
|
||||||
|
|
||||||
|
def save_file_as_tmp(data):
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
fd, filename = tempfile.mkstemp()
|
||||||
|
with open(filename, 'wb') as tmp_file:
|
||||||
|
for chunk in data.chunks():
|
||||||
|
tmp_file.write(chunk)
|
||||||
|
os.close(fd)
|
||||||
|
return filename
|
||||||
|
is_create_request = dl_model_id is None
|
||||||
|
if is_create_request:
|
||||||
|
dl_model_id = create_empty(owner=owner)
|
||||||
|
|
||||||
|
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:
|
||||||
|
model_file = save_file_as_tmp(model_file)
|
||||||
|
weights_file = save_file_as_tmp(weights_file)
|
||||||
|
labelmap_file = save_file_as_tmp(labelmap_file)
|
||||||
|
interpretation_file = save_file_as_tmp(interpretation_file)
|
||||||
|
|
||||||
|
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,
|
||||||
|
name,
|
||||||
|
is_shared,
|
||||||
|
model_file,
|
||||||
|
weights_file,
|
||||||
|
labelmap_file,
|
||||||
|
interpretation_file,
|
||||||
|
run_tests,
|
||||||
|
storage == "local",
|
||||||
|
is_create_request,
|
||||||
|
),
|
||||||
|
job_id=rq_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return rq_id
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
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))
|
||||||
|
|
||||||
|
shutil.rmtree(dl_model.get_dirname(), ignore_errors=True)
|
||||||
|
dl_model.delete()
|
||||||
|
else:
|
||||||
|
raise Exception("Requested DL model {} doesn't exist".format(dl_model_id))
|
||||||
|
|
||||||
|
def get_image_data(path_to_data):
|
||||||
|
def get_image_key(item):
|
||||||
|
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(os.path.join(root, filename))
|
||||||
|
|
||||||
|
image_list.sort(key=get_image_key)
|
||||||
|
return ImageLoader(image_list)
|
||||||
|
|
||||||
|
class Results():
|
||||||
|
def __init__(self):
|
||||||
|
self._results = {
|
||||||
|
"shapes": [],
|
||||||
|
"tracks": []
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_box(self, xtl, ytl, xbr, ybr, label, frame_number, attributes=None):
|
||||||
|
self.get_shapes().append({
|
||||||
|
"label": label,
|
||||||
|
"frame": frame_number,
|
||||||
|
"points": [xtl, ytl, xbr, ybr],
|
||||||
|
"type": "rectangle",
|
||||||
|
"attributes": attributes or {},
|
||||||
|
})
|
||||||
|
|
||||||
|
def add_points(self, points, label, frame_number, attributes=None):
|
||||||
|
points = self._create_polyshape(points, label, frame_number, attributes)
|
||||||
|
points["type"] = "points"
|
||||||
|
self.get_shapes().append(points)
|
||||||
|
|
||||||
|
def add_polygon(self, points, label, frame_number, attributes=None):
|
||||||
|
polygon = self._create_polyshape(points, label, frame_number, attributes)
|
||||||
|
polygon["type"] = "polygon"
|
||||||
|
self.get_shapes().append(polygon)
|
||||||
|
|
||||||
|
def add_polyline(self, points, label, frame_number, attributes=None):
|
||||||
|
polyline = self._create_polyshape(points, label, frame_number, attributes)
|
||||||
|
polyline["type"] = "polyline"
|
||||||
|
self.get_shapes().append(polyline)
|
||||||
|
|
||||||
|
def get_shapes(self):
|
||||||
|
return self._results["shapes"]
|
||||||
|
|
||||||
|
def get_tracks(self):
|
||||||
|
return self._results["tracks"]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_polyshape(self, points, label, frame_number, attributes=None):
|
||||||
|
return {
|
||||||
|
"label": label,
|
||||||
|
"frame": frame_number,
|
||||||
|
"points": " ".join("{},{}".format(pair[0], pair[1]) for pair in points),
|
||||||
|
"attributes": attributes or {},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _process_detections(detections, path_to_conv_script):
|
||||||
|
results = Results()
|
||||||
|
global_vars = {
|
||||||
|
"__builtins__": {
|
||||||
|
"str": str,
|
||||||
|
"int": int,
|
||||||
|
"float": float,
|
||||||
|
"max": max,
|
||||||
|
"min": min,
|
||||||
|
"range": range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
local_vars = {
|
||||||
|
"detections": detections,
|
||||||
|
"results": results,
|
||||||
|
}
|
||||||
|
exec (open(path_to_conv_script).read(), global_vars, local_vars)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _run_inference_engine_annotation(data, model_file, weights_file,
|
||||||
|
labels_mapping, attribute_spec, convertation_file, job=None, update_progress=None):
|
||||||
|
def process_attributes(shape_attributes, label_attr_spec):
|
||||||
|
attributes = []
|
||||||
|
for attr_text, attr_value in shape_attributes.items():
|
||||||
|
if attr_text in label_attr_spec:
|
||||||
|
attributes.append({
|
||||||
|
"id": label_attr_spec[attr_text],
|
||||||
|
"value": attr_value,
|
||||||
|
})
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
|
||||||
|
def add_shapes(shapes, target_container):
|
||||||
|
for shape in shapes:
|
||||||
|
if shape["label"] not in labels_mapping:
|
||||||
|
continue
|
||||||
|
db_label = labels_mapping[shape["label"]]
|
||||||
|
|
||||||
|
target_container.append({
|
||||||
|
"label_id": db_label,
|
||||||
|
"frame": shape["frame"],
|
||||||
|
"points": shape["points"],
|
||||||
|
"type": shape["type"],
|
||||||
|
"z_order": 0,
|
||||||
|
"group": None,
|
||||||
|
"occluded": False,
|
||||||
|
"attributes": process_attributes(shape["attributes"], attribute_spec[db_label]),
|
||||||
|
})
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"shapes": [],
|
||||||
|
"tracks": [],
|
||||||
|
"tags": [],
|
||||||
|
"version": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
data_len = len(data)
|
||||||
|
model = ModelLoader(model=model_file, weights=weights_file)
|
||||||
|
|
||||||
|
frame_counter = 0
|
||||||
|
|
||||||
|
detections = []
|
||||||
|
for frame in data:
|
||||||
|
orig_rows, orig_cols = frame.shape[:2]
|
||||||
|
|
||||||
|
detections.append({
|
||||||
|
"frame_id": frame_counter,
|
||||||
|
"frame_height": orig_rows,
|
||||||
|
"frame_width": orig_cols,
|
||||||
|
"detections": model.infer(frame),
|
||||||
|
})
|
||||||
|
|
||||||
|
frame_counter += 1
|
||||||
|
if job and update_progress and not update_progress(job, frame_counter * 100 / data_len):
|
||||||
|
return None
|
||||||
|
|
||||||
|
processed_detections = _process_detections(detections, convertation_file)
|
||||||
|
|
||||||
|
add_shapes(processed_detections.get_shapes(), result["shapes"])
|
||||||
|
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def run_inference_thread(tid, model_file, weights_file, labels_mapping, attributes, convertation_file, reset, user):
|
||||||
|
def update_progress(job, progress):
|
||||||
|
job.refresh()
|
||||||
|
if "cancel" in job.meta:
|
||||||
|
del job.meta["cancel"]
|
||||||
|
job.save()
|
||||||
|
return False
|
||||||
|
job.meta["progress"] = progress
|
||||||
|
job.save_meta()
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
job = rq.get_current_job()
|
||||||
|
job.meta["progress"] = 0
|
||||||
|
job.save_meta()
|
||||||
|
db_task = TaskModel.objects.get(pk=tid)
|
||||||
|
|
||||||
|
result = None
|
||||||
|
slogger.glob.info("auto annotation with openvino toolkit for task {}".format(tid))
|
||||||
|
result = _run_inference_engine_annotation(
|
||||||
|
data=get_image_data(db_task.get_data_dirname()),
|
||||||
|
model_file=model_file,
|
||||||
|
weights_file=weights_file,
|
||||||
|
labels_mapping=labels_mapping,
|
||||||
|
attribute_spec=attributes,
|
||||||
|
convertation_file= convertation_file,
|
||||||
|
job=job,
|
||||||
|
update_progress=update_progress,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
slogger.glob.info("auto annotation for task {} canceled by user".format(tid))
|
||||||
|
return
|
||||||
|
|
||||||
|
serializer = LabeledDataSerializer(data = result)
|
||||||
|
if serializer.is_valid(raise_exception=True):
|
||||||
|
if reset:
|
||||||
|
put_task_data(tid, user, result)
|
||||||
|
else:
|
||||||
|
patch_task_data(tid, user, result, "create")
|
||||||
|
|
||||||
|
slogger.glob.info("auto annotation for task {} done".format(tid))
|
||||||
|
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
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
# 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)
|
||||||
@ -0,0 +1,780 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global
|
||||||
|
showMessage
|
||||||
|
showOverlay
|
||||||
|
userConfirm
|
||||||
|
*/
|
||||||
|
|
||||||
|
window.cvat = window.cvat || {};
|
||||||
|
|
||||||
|
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.id;
|
||||||
|
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 = event.data.data.labels.map(x => x.name);
|
||||||
|
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.addEventListener('DOMContentLoaded', () => {
|
||||||
|
window.cvat.autoAnnotation.server = AutoAnnotationServer;
|
||||||
|
window.cvat.autoAnnotation.manager = new AutoAnnotationModelManagerView();
|
||||||
|
window.cvat.autoAnnotation.runner = new AutoAnnotationModelRunnerView();
|
||||||
|
|
||||||
|
$('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');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('dashboardReady', (event) => {
|
||||||
|
const elements = $('.dashboardItem');
|
||||||
|
const tids = Array.from(elements, el => +el.getAttribute('tid'));
|
||||||
|
|
||||||
|
window.cvat.autoAnnotation.server.meta(tids, (data) => {
|
||||||
|
window.cvat.autoAnnotation.data = data;
|
||||||
|
|
||||||
|
elements.each(function setupDashboardItem() {
|
||||||
|
const elem = $(this);
|
||||||
|
const tid = +elem.attr('tid');
|
||||||
|
|
||||||
|
const button = $('<button> Run Auto Annotation </button>').addClass('regular dashboardButtonUI');
|
||||||
|
button[0].setupRun = function setupRun() {
|
||||||
|
const self = $(this);
|
||||||
|
const taskInfo = event.detail.filter(task => task.id === tid)[0];
|
||||||
|
self.text('Run Auto Annotation').off('click').on('click', () => {
|
||||||
|
window.cvat.autoAnnotation.runner.reset(taskInfo, self).show();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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%;
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
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),
|
||||||
|
]
|
||||||
@ -0,0 +1,260 @@
|
|||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import django_rq
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
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.authentication.auth import has_admin_role
|
||||||
|
from cvat.apps.engine.log import slogger
|
||||||
|
|
||||||
|
from .model_loader import load_label_map
|
||||||
|
from . import model_manager
|
||||||
|
from .models import AnnotationModel
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
rq_id = model_manager.create_or_update(
|
||||||
|
dl_model_id=None,
|
||||||
|
name=name,
|
||||||
|
model_file=model,
|
||||||
|
weights_file=weights,
|
||||||
|
labelmap_file=labelmap,
|
||||||
|
interpretation_file=interpretation_script,
|
||||||
|
owner=owner,
|
||||||
|
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.create_or_update(
|
||||||
|
dl_model_id=mid,
|
||||||
|
name=name,
|
||||||
|
model_file=model,
|
||||||
|
weights_file=weights,
|
||||||
|
labelmap_file=labelmap,
|
||||||
|
interpretation_file=interpretation_script,
|
||||||
|
owner=None,
|
||||||
|
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")
|
||||||
|
for tid in tids:
|
||||||
|
rq_id = "auto_annotation.run.{}".format(tid)
|
||||||
|
job = queue.fetch_job(rq_id)
|
||||||
|
if job is not None:
|
||||||
|
response["run"][tid] = {
|
||||||
|
"status": job.get_status(),
|
||||||
|
"rq_id": rq_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
@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)
|
||||||
|
queue = django_rq.get_queue("low")
|
||||||
|
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")
|
||||||
|
|
||||||
|
data = json.loads(request.body.decode('utf-8'))
|
||||||
|
|
||||||
|
should_reset = data["reset"]
|
||||||
|
user_defined_labels_mapping = data["labels"]
|
||||||
|
|
||||||
|
dl_model = AnnotationModel.objects.get(pk=mid)
|
||||||
|
|
||||||
|
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.name: db_attr.id for db_attr in db_label.attributespec_set.all()} for db_label in db_labels}
|
||||||
|
db_labels = {db_label.name:db_label.id for db_label in db_labels}
|
||||||
|
|
||||||
|
model_labels = {value: key for key, value in load_label_map(labelmap_file).items()}
|
||||||
|
|
||||||
|
labels_mapping = {}
|
||||||
|
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=model_manager.run_inference_thread,
|
||||||
|
args=(
|
||||||
|
tid,
|
||||||
|
model_file_path,
|
||||||
|
weights_file_path,
|
||||||
|
labels_mapping,
|
||||||
|
db_attributes,
|
||||||
|
convertation_file_path,
|
||||||
|
should_reset,
|
||||||
|
request.user,
|
||||||
|
),
|
||||||
|
job_id = rq_id,
|
||||||
|
timeout=604800) # 7 days
|
||||||
|
|
||||||
|
slogger.task[tid].info("auto annotation job enqueued")
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
try:
|
||||||
|
slogger.task[tid].exception("exception was occurred during annotation request", exc_info=True)
|
||||||
|
except Exception as logger_ex:
|
||||||
|
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 JsonResponse({"id": rq_id})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def check(request, rq_id):
|
||||||
|
try:
|
||||||
|
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 = {}
|
||||||
|
if job is None:
|
||||||
|
data["status"] = "unknown"
|
||||||
|
elif job.is_queued:
|
||||||
|
data["status"] = "queued"
|
||||||
|
elif job.is_started:
|
||||||
|
data["status"] = "started"
|
||||||
|
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["error"] = job.exc_info
|
||||||
|
job.delete()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
data["status"] = "unknown"
|
||||||
|
|
||||||
|
return JsonResponse(data)
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,38 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (C) 2018 Intel Corporation
|
|
||||||
|
|
||||||
SPDX-License-Identifier: MIT
|
|
||||||
-->
|
|
||||||
<div class="dashboardTaskUI" id="dashboardTask_{{item.id}}">
|
|
||||||
<center class="dashboardTitleWrapper">
|
|
||||||
<label class="semiBold h1 dashboardTaskNameLabel selectable"> {{ item.name }} </label>
|
|
||||||
</center>
|
|
||||||
<center class="dashboardTitleWrapper">
|
|
||||||
<label class="regular dashboardStatusLabel"> {{ item.status }} </label>
|
|
||||||
</center>
|
|
||||||
<div class="dashboardTaskIntro" style='background-image: url("/get/task/{{item.id}}/frame/0")'> </div>
|
|
||||||
<div class="dashboardButtonsUI">
|
|
||||||
<button class="dashboardDumpAnnotation semiBold dashboardButtonUI"> Dump Annotation </button>
|
|
||||||
<button class="dashboardUploadAnnotation semiBold dashboardButtonUI"> Upload Annotation </button>
|
|
||||||
<button class="dashboardUpdateTask semiBold dashboardButtonUI"> Update Task </button>
|
|
||||||
<button class="dashboardDeleteTask semiBold dashboardButtonUI"> Delete Task </button>
|
|
||||||
{%if item.bug_tracker %}
|
|
||||||
<button class="dashboardOpenTrackerButton semiBold dashboardButtonUI"> Open Bug Tracker </button>
|
|
||||||
<a class="dashboardBugTrackerLink" href='{{item.bug_tracker}}' style="display: none;"> </a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="dashboardJobsUI">
|
|
||||||
<center class="dashboardTitleWrapper">
|
|
||||||
<label class="regular h1"> Jobs </label>
|
|
||||||
</center>
|
|
||||||
<table class="dashboardJobList regular">
|
|
||||||
{% for segm in item.segment_set.all %}
|
|
||||||
{% for job in segm.job_set.all %}
|
|
||||||
<tr>
|
|
||||||
<td> <a href="{{base_url}}?id={{job.id}}"> {{base_url}}?id={{job.id}} </a> </td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
# Semi-Automatic Segmentation with [Deep Extreme Cut](http://www.vision.ee.ethz.ch/~cvlsegmentation/dextr/)
|
||||||
|
|
||||||
|
## About the application
|
||||||
|
|
||||||
|
The application allows to use deep learning models for semi-automatic semantic and instance segmentation.
|
||||||
|
You can get a segmentation polygon from four (or more) extreme points of an object.
|
||||||
|
This application uses the pre-trained DEXTR model which has been converted to Inference Engine format.
|
||||||
|
|
||||||
|
We are grateful to K.K. Maninis, S. Caelles, J. Pont-Tuset, and L. Van Gool who permitted using their models in our tool
|
||||||
|
|
||||||
|
## Build docker image
|
||||||
|
```bash
|
||||||
|
# OpenVINO component is also needed
|
||||||
|
docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml -f cvat/apps/dextr_segmentation/docker-compose.dextr.yml build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run docker container
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml -f cvat/apps/dextr_segmentation/docker-compose.dextr.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using
|
||||||
|
|
||||||
|
1. Open a job
|
||||||
|
2. Select "Auto Segmentation" in the list of shapes
|
||||||
|
3. Run the draw mode as usually (by press the "Create Shape" button or by "N" shortcut)
|
||||||
|
4. Click four-six (or more if it's need) extreme points of an object
|
||||||
|
5. Close the draw mode as usually (by shortcut or pressing the button "Stop Creation")
|
||||||
|
6. Wait a moment and you will get a class agnostic annotation polygon
|
||||||
|
7. You can close an annotation request if it is too long
|
||||||
|
(in case if it is queued to rq worker and all workers are busy)
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from cvat.settings.base import JS_3RDPARTY
|
||||||
|
|
||||||
|
JS_3RDPARTY['engine'] = JS_3RDPARTY.get('engine', []) + ['dextr_segmentation/js/enginePlugin.js']
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
class DextrSegmentationConfig(AppConfig):
|
||||||
|
name = 'dextr_segmentation'
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
|
||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from cvat.apps.auto_annotation.inference_engine import make_plugin, make_network
|
||||||
|
|
||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import PIL
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
_IE_CPU_EXTENSION = os.getenv("IE_CPU_EXTENSION", "libcpu_extension_avx2.so")
|
||||||
|
_IE_PLUGINS_PATH = os.getenv("IE_PLUGINS_PATH", None)
|
||||||
|
|
||||||
|
_DEXTR_MODEL_DIR = os.getenv("DEXTR_MODEL_DIR", None)
|
||||||
|
_DEXTR_PADDING = 50
|
||||||
|
_DEXTR_TRESHOLD = 0.9
|
||||||
|
_DEXTR_SIZE = 512
|
||||||
|
|
||||||
|
class DEXTR_HANDLER:
|
||||||
|
def __init__(self):
|
||||||
|
self._plugin = None
|
||||||
|
self._network = None
|
||||||
|
self._exec_network = None
|
||||||
|
self._input_blob = None
|
||||||
|
self._output_blob = None
|
||||||
|
if not _DEXTR_MODEL_DIR:
|
||||||
|
raise Exception("DEXTR_MODEL_DIR is not defined")
|
||||||
|
|
||||||
|
|
||||||
|
def handle(self, im_path, points):
|
||||||
|
# Lazy initialization
|
||||||
|
if not self._plugin:
|
||||||
|
self._plugin = make_plugin()
|
||||||
|
self._network = make_network(os.path.join(_DEXTR_MODEL_DIR, 'dextr.xml'),
|
||||||
|
os.path.join(_DEXTR_MODEL_DIR, 'dextr.bin'))
|
||||||
|
self._input_blob = next(iter(self._network.inputs))
|
||||||
|
self._output_blob = next(iter(self._network.outputs))
|
||||||
|
self._exec_network = self._plugin.load(network=self._network)
|
||||||
|
|
||||||
|
image = PIL.Image.open(im_path)
|
||||||
|
numpy_image = np.array(image)
|
||||||
|
points = np.asarray([[int(p["x"]), int(p["y"])] for p in points], dtype=int)
|
||||||
|
bounding_box = (
|
||||||
|
max(min(points[:, 0]) - _DEXTR_PADDING, 0),
|
||||||
|
max(min(points[:, 1]) - _DEXTR_PADDING, 0),
|
||||||
|
min(max(points[:, 0]) + _DEXTR_PADDING, numpy_image.shape[1] - 1),
|
||||||
|
min(max(points[:, 1]) + _DEXTR_PADDING, numpy_image.shape[0] - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prepare an image
|
||||||
|
numpy_cropped = np.array(image.crop(bounding_box))
|
||||||
|
resized = cv2.resize(numpy_cropped, (_DEXTR_SIZE, _DEXTR_SIZE),
|
||||||
|
interpolation = cv2.INTER_CUBIC).astype(np.float32)
|
||||||
|
|
||||||
|
# Make a heatmap
|
||||||
|
points = points - [min(points[:, 0]), min(points[:, 1])] + [_DEXTR_PADDING, _DEXTR_PADDING]
|
||||||
|
points = (points * [_DEXTR_SIZE / numpy_cropped.shape[1], _DEXTR_SIZE / numpy_cropped.shape[0]]).astype(int)
|
||||||
|
heatmap = np.zeros(shape=resized.shape[:2], dtype=np.float64)
|
||||||
|
for point in points:
|
||||||
|
gaussian_x_axis = np.arange(0, _DEXTR_SIZE, 1, float) - point[0]
|
||||||
|
gaussian_y_axis = np.arange(0, _DEXTR_SIZE, 1, float)[:, np.newaxis] - point[1]
|
||||||
|
gaussian = np.exp(-4 * np.log(2) * ((gaussian_x_axis ** 2 + gaussian_y_axis ** 2) / 100)).astype(np.float64)
|
||||||
|
heatmap = np.maximum(heatmap, gaussian)
|
||||||
|
cv2.normalize(heatmap, heatmap, 0, 255, cv2.NORM_MINMAX)
|
||||||
|
|
||||||
|
# Concat an image and a heatmap
|
||||||
|
input_dextr = np.concatenate((resized, heatmap[:, :, np.newaxis].astype(resized.dtype)), axis=2)
|
||||||
|
input_dextr = input_dextr.transpose((2,0,1))
|
||||||
|
|
||||||
|
pred = self._exec_network.infer(inputs={self._input_blob: input_dextr[np.newaxis, ...]})[self._output_blob][0, 0, :, :]
|
||||||
|
pred = cv2.resize(pred, tuple(reversed(numpy_cropped.shape[:2])), interpolation = cv2.INTER_CUBIC)
|
||||||
|
result = np.zeros(numpy_image.shape[:2])
|
||||||
|
result[bounding_box[1]:bounding_box[1] + pred.shape[0], bounding_box[0]:bounding_box[0] + pred.shape[1]] = pred > _DEXTR_TRESHOLD
|
||||||
|
|
||||||
|
# Convert a mask to a polygon
|
||||||
|
result = np.array(result, dtype=np.uint8)
|
||||||
|
cv2.normalize(result,result,0,255,cv2.NORM_MINMAX)
|
||||||
|
contours = None
|
||||||
|
if int(cv2.__version__.split('.')[0]) > 3:
|
||||||
|
contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[0]
|
||||||
|
else:
|
||||||
|
contours = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_KCOS)[1]
|
||||||
|
|
||||||
|
contours = max(contours, key=lambda arr: arr.size)
|
||||||
|
if contours.shape.count(1):
|
||||||
|
contours = np.squeeze(contours)
|
||||||
|
if contours.size < 3 * 2:
|
||||||
|
raise Exception('Less then three point have been detected. Can not build a polygon.')
|
||||||
|
|
||||||
|
result = ""
|
||||||
|
for point in contours:
|
||||||
|
result += "{},{} ".format(int(point[0]), int(point[1]))
|
||||||
|
result = result[:-1]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if self._exec_network:
|
||||||
|
del self._exec_network
|
||||||
|
if self._network:
|
||||||
|
del self._network
|
||||||
|
if self._plugin:
|
||||||
|
del self._plugin
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
|
||||||
|
version: "2.3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
cvat:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
WITH_DEXTR: "yes"
|
||||||
@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global
|
||||||
|
AREA_TRESHOLD:false
|
||||||
|
PolyShapeModel:false
|
||||||
|
ShapeCreatorModel:true
|
||||||
|
ShapeCreatorView:true
|
||||||
|
showMessage:false
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint no-underscore-dangle: 0 */
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
$('<option value="auto_segmentation" class="regular"> Auto Segmentation </option>').appendTo('#shapeTypeSelector');
|
||||||
|
|
||||||
|
const dextrCancelButtonId = 'dextrCancelButton';
|
||||||
|
const dextrOverlay = $(`
|
||||||
|
<div class="modal hidden force-modal">
|
||||||
|
<div class="modal-content" style="width: 300px; height: 70px;">
|
||||||
|
<center> <label class="regular h2"> Segmentation request is being processed </label></center>
|
||||||
|
<center style="margin-top: 5px;">
|
||||||
|
<button id="${dextrCancelButtonId}" class="regular h2" style="width: 250px;"> Cancel </button>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>`).appendTo('body');
|
||||||
|
|
||||||
|
const dextrCancelButton = $(`#${dextrCancelButtonId}`);
|
||||||
|
dextrCancelButton.on('click', () => {
|
||||||
|
dextrCancelButton.prop('disabled', true);
|
||||||
|
$.ajax({
|
||||||
|
url: `/dextr/cancel/${window.cvat.job.id}`,
|
||||||
|
type: 'GET',
|
||||||
|
error: (errorData) => {
|
||||||
|
const message = `Can not cancel segmentation. Code: ${errorData.status}.
|
||||||
|
Message: ${errorData.responseText || errorData.statusText}`;
|
||||||
|
showMessage(message);
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
dextrCancelButton.prop('disabled', false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function ShapeCreatorModelWrapper(OriginalClass) {
|
||||||
|
// Constructor will patch some properties for a created instance
|
||||||
|
function constructorDecorator(...args) {
|
||||||
|
const instance = new OriginalClass(...args);
|
||||||
|
|
||||||
|
// Decorator for the defaultType property
|
||||||
|
Object.defineProperty(instance, 'defaultType', {
|
||||||
|
get: () => instance._defaultType,
|
||||||
|
set: (type) => {
|
||||||
|
if (!['box', 'points', 'polygon', 'polyline', 'auto_segmentation'].includes(type)) {
|
||||||
|
throw Error(`Unknown shape type found ${type}`);
|
||||||
|
}
|
||||||
|
instance._defaultType = type;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decorator for finish method.
|
||||||
|
const decoratedFinish = instance.finish;
|
||||||
|
instance.finish = (result) => {
|
||||||
|
if (instance._defaultType === 'auto_segmentation') {
|
||||||
|
try {
|
||||||
|
instance._defaultType = 'polygon';
|
||||||
|
decoratedFinish.call(instance, result);
|
||||||
|
} finally {
|
||||||
|
instance._defaultType = 'auto_segmentation';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
decoratedFinish.call(instance, result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructorDecorator.prototype = OriginalClass.prototype;
|
||||||
|
constructorDecorator.prototype.constructor = constructorDecorator;
|
||||||
|
return constructorDecorator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ShapeCreatorViewWrapper(OriginalClass) {
|
||||||
|
// Constructor will patch some properties for each instance
|
||||||
|
function constructorDecorator(...args) {
|
||||||
|
const instance = new OriginalClass(...args);
|
||||||
|
|
||||||
|
// Decorator for the _create() method.
|
||||||
|
// We save the decorated _create() and we will use it if type != 'auto_segmentation'
|
||||||
|
const decoratedCreate = instance._create;
|
||||||
|
instance._create = () => {
|
||||||
|
if (instance._type !== 'auto_segmentation') {
|
||||||
|
decoratedCreate.call(instance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance._drawInstance = instance._frameContent.polyline().draw({ snapToGrid: 0.1 }).addClass('shapeCreation').attr({
|
||||||
|
'stroke-width': 0,
|
||||||
|
z_order: Number.MAX_SAFE_INTEGER,
|
||||||
|
});
|
||||||
|
instance._createPolyEvents();
|
||||||
|
|
||||||
|
/* the _createPolyEvents method have added "drawdone"
|
||||||
|
* event handler which invalid for this case
|
||||||
|
* because of that reason we remove the handler and
|
||||||
|
* create the valid handler instead
|
||||||
|
*/
|
||||||
|
instance._drawInstance.off('drawdone').on('drawdone', (e) => {
|
||||||
|
let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute('points'));
|
||||||
|
actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
|
||||||
|
|
||||||
|
if (actualPoints.length < 4) {
|
||||||
|
showMessage('It is need to specify minimum four extreme points for an object');
|
||||||
|
instance._controller.switchCreateMode(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { frameWidth } = window.cvat.player.geometry;
|
||||||
|
const { frameHeight } = window.cvat.player.geometry;
|
||||||
|
for (let idx = 0; idx < actualPoints.length; idx += 1) {
|
||||||
|
const point = actualPoints[idx];
|
||||||
|
point.x = Math.clamp(point.x, 0, frameWidth);
|
||||||
|
point.y = Math.clamp(point.y, 0, frameHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.target.setAttribute('points',
|
||||||
|
window.cvat.translate.points.actualToCanvas(
|
||||||
|
PolyShapeModel.convertNumberArrayToString(actualPoints),
|
||||||
|
));
|
||||||
|
|
||||||
|
const polybox = e.target.getBBox();
|
||||||
|
const area = polybox.width * polybox.height;
|
||||||
|
|
||||||
|
if (area > AREA_TRESHOLD) {
|
||||||
|
$.ajax({
|
||||||
|
url: `/dextr/create/${window.cvat.job.id}`,
|
||||||
|
type: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
frame: window.cvat.player.frames.current,
|
||||||
|
points: actualPoints,
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: () => {
|
||||||
|
function intervalCallback() {
|
||||||
|
$.ajax({
|
||||||
|
url: `/dextr/check/${window.cvat.job.id}`,
|
||||||
|
type: 'GET',
|
||||||
|
success: (jobData) => {
|
||||||
|
if (['queued', 'started'].includes(jobData.status)) {
|
||||||
|
if (jobData.status === 'queued') {
|
||||||
|
dextrCancelButton.prop('disabled', false);
|
||||||
|
}
|
||||||
|
setTimeout(intervalCallback, 1000);
|
||||||
|
} else {
|
||||||
|
dextrOverlay.addClass('hidden');
|
||||||
|
if (jobData.status === 'finished') {
|
||||||
|
if (jobData.result) {
|
||||||
|
instance._controller.finish({ points: jobData.result }, 'polygon');
|
||||||
|
}
|
||||||
|
} else if (jobData.status === 'failed') {
|
||||||
|
const message = `Segmentation has fallen. Error: '${jobData.stderr}'`;
|
||||||
|
showMessage(message);
|
||||||
|
} else {
|
||||||
|
let message = `Check segmentation request returned "${jobData.status}" status.`;
|
||||||
|
if (jobData.stderr) {
|
||||||
|
message += ` Error: ${jobData.stderr}`;
|
||||||
|
}
|
||||||
|
showMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (errorData) => {
|
||||||
|
dextrOverlay.addClass('hidden');
|
||||||
|
const message = `Can not check segmentation. Code: ${errorData.status}.`
|
||||||
|
+ ` Message: ${errorData.responseText || errorData.statusText}`;
|
||||||
|
showMessage(message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dextrCancelButton.prop('disabled', true);
|
||||||
|
dextrOverlay.removeClass('hidden');
|
||||||
|
setTimeout(intervalCallback, 1000);
|
||||||
|
},
|
||||||
|
error: (errorData) => {
|
||||||
|
const message = `Can not cancel ReID process. Code: ${errorData.status}.`
|
||||||
|
+ ` Message: ${errorData.responseText || errorData.statusText}`;
|
||||||
|
showMessage(message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
instance._controller.switchCreateMode(true);
|
||||||
|
}); // end of "drawdone" handler
|
||||||
|
}; // end of _create() method
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
} // end of constructorDecorator()
|
||||||
|
|
||||||
|
constructorDecorator.prototype = OriginalClass.prototype;
|
||||||
|
constructorDecorator.prototype.constructor = constructorDecorator;
|
||||||
|
return constructorDecorator;
|
||||||
|
} // end of ShapeCreatorViewWrapper
|
||||||
|
|
||||||
|
// Apply patch for classes
|
||||||
|
ShapeCreatorModel = ShapeCreatorModelWrapper(ShapeCreatorModel);
|
||||||
|
ShapeCreatorView = ShapeCreatorViewWrapper(ShapeCreatorView);
|
||||||
|
});
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('create/<int:jid>', views.create),
|
||||||
|
path('cancel/<int:jid>', views.cancel),
|
||||||
|
path('check/<int:jid>', views.check)
|
||||||
|
]
|
||||||
@ -0,0 +1,127 @@
|
|||||||
|
# Copyright (C) 2018 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
|
||||||
|
from cvat.apps.authentication.decorators import login_required
|
||||||
|
from rules.contrib.views import permission_required, objectgetter
|
||||||
|
|
||||||
|
from cvat.apps.engine.models import Job
|
||||||
|
from cvat.apps.engine.log import slogger
|
||||||
|
from cvat.apps.dextr_segmentation.dextr import DEXTR_HANDLER
|
||||||
|
|
||||||
|
import django_rq
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import rq
|
||||||
|
|
||||||
|
__RQ_QUEUE_NAME = "default"
|
||||||
|
__DEXTR_HANDLER = DEXTR_HANDLER()
|
||||||
|
|
||||||
|
def _dextr_thread(im_path, points):
|
||||||
|
job = rq.get_current_job()
|
||||||
|
job.meta["result"] = __DEXTR_HANDLER.handle(im_path, points)
|
||||||
|
job.save_meta()
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required(perm=["engine.job.change"],
|
||||||
|
fn=objectgetter(Job, "jid"), raise_exception=True)
|
||||||
|
def create(request, jid):
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body.decode("utf-8"))
|
||||||
|
|
||||||
|
points = data["points"]
|
||||||
|
frame = int(data["frame"])
|
||||||
|
username = request.user.username
|
||||||
|
|
||||||
|
slogger.job[jid].info("create dextr request for the JOB: {} ".format(jid)
|
||||||
|
+ "by the USER: {} on the FRAME: {}".format(username, frame))
|
||||||
|
|
||||||
|
db_task = Job.objects.select_related("segment__task").get(id=jid).segment.task
|
||||||
|
im_path = os.path.realpath(db_task.get_frame_path(frame))
|
||||||
|
|
||||||
|
queue = django_rq.get_queue(__RQ_QUEUE_NAME)
|
||||||
|
rq_id = "dextr.create/{}/{}".format(jid, username)
|
||||||
|
job = queue.fetch_job(rq_id)
|
||||||
|
|
||||||
|
if job is not None and (job.is_started or job.is_queued):
|
||||||
|
if "cancel" not in job.meta:
|
||||||
|
raise Exception("Segmentation process has been already run for the " +
|
||||||
|
"JOB: {} and the USER: {}".format(jid, username))
|
||||||
|
else:
|
||||||
|
job.delete()
|
||||||
|
|
||||||
|
queue.enqueue_call(func=_dextr_thread,
|
||||||
|
args=(im_path, points),
|
||||||
|
job_id=rq_id,
|
||||||
|
timeout=15,
|
||||||
|
ttl=30)
|
||||||
|
|
||||||
|
return HttpResponse()
|
||||||
|
except Exception as ex:
|
||||||
|
slogger.job[jid].error("can't create a dextr request for the job {}".format(jid), exc_info=True)
|
||||||
|
return HttpResponseBadRequest(str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required(perm=["engine.job.change"],
|
||||||
|
fn=objectgetter(Job, "jid"), raise_exception=True)
|
||||||
|
def cancel(request, jid):
|
||||||
|
try:
|
||||||
|
username = request.user.username
|
||||||
|
slogger.job[jid].info("cancel dextr request for the JOB: {} ".format(jid)
|
||||||
|
+ "by the USER: {}".format(username))
|
||||||
|
|
||||||
|
queue = django_rq.get_queue(__RQ_QUEUE_NAME)
|
||||||
|
rq_id = "dextr.create/{}/{}".format(jid, username)
|
||||||
|
job = queue.fetch_job(rq_id)
|
||||||
|
|
||||||
|
if job is None or job.is_finished or job.is_failed:
|
||||||
|
raise Exception("Segmentation isn't running now")
|
||||||
|
elif "cancel" not in job.meta:
|
||||||
|
job.meta["cancel"] = True
|
||||||
|
job.save_meta()
|
||||||
|
|
||||||
|
return HttpResponse()
|
||||||
|
except Exception as ex:
|
||||||
|
slogger.job[jid].error("can't cancel a dextr request for the job {}".format(jid), exc_info=True)
|
||||||
|
return HttpResponseBadRequest(str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@permission_required(perm=["engine.job.change"],
|
||||||
|
fn=objectgetter(Job, "jid"), raise_exception=True)
|
||||||
|
def check(request, jid):
|
||||||
|
try:
|
||||||
|
username = request.user.username
|
||||||
|
slogger.job[jid].info("check dextr request for the JOB: {} ".format(jid)
|
||||||
|
+ "by the USER: {}".format(username))
|
||||||
|
|
||||||
|
queue = django_rq.get_queue(__RQ_QUEUE_NAME)
|
||||||
|
rq_id = "dextr.create/{}/{}".format(jid, username)
|
||||||
|
job = queue.fetch_job(rq_id)
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
if job is None:
|
||||||
|
data["status"] = "unknown"
|
||||||
|
else:
|
||||||
|
if "cancel" in job.meta:
|
||||||
|
data["status"] = "finished"
|
||||||
|
elif job.is_queued:
|
||||||
|
data["status"] = "queued"
|
||||||
|
elif job.is_started:
|
||||||
|
data["status"] = "started"
|
||||||
|
elif job.is_finished:
|
||||||
|
data["status"] = "finished"
|
||||||
|
data["result"] = job.meta["result"]
|
||||||
|
job.delete()
|
||||||
|
else:
|
||||||
|
data["status"] = "failed"
|
||||||
|
data["stderr"] = job.exc_info
|
||||||
|
job.delete()
|
||||||
|
|
||||||
|
return JsonResponse(data)
|
||||||
|
except Exception as ex:
|
||||||
|
slogger.job[jid].error("can't check a dextr request for the job {}".format(jid), exc_info=True)
|
||||||
|
return HttpResponseBadRequest(str(ex))
|
||||||
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 678 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 718 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 485 KiB |
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 30 KiB |