Auto annotation using models in OpenVINO format (#233)
parent
1300d230b6
commit
4387fdaf46
@ -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,163 @@
|
||||
## Auto annotation
|
||||
|
||||
### Description
|
||||
|
||||
The application will be enabled automatically if OpenVINO™ component 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. Values in `label_map` should be
|
||||
exactly equal to labels for the annotation task, otherwise objects with mismatched
|
||||
labels will be ignored.
|
||||
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)
|
||||
|
||||
__Note__: Model configuration(*.xml) and weights (*.bin) are available in OpenVINO redistributable package.
|
||||
|
||||
__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)
|
||||
|
||||
__Note__: Model configuration (.xml) and weights (.bin) are available in OpenVINO redistributable package.
|
||||
|
||||
__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,8 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from cvat.settings.base import JS_3RDPARTY
|
||||
|
||||
JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['auto_annotation/js/auto_annotation.js']
|
||||
@ -0,0 +1,4 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -0,0 +1,11 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AutoAnnotationConfig(AppConfig):
|
||||
name = "auto_annotation"
|
||||
|
||||
@ -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 imagename, 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,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import json
|
||||
import cv2
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from openvino.inference_engine import IENetwork, IEPlugin
|
||||
|
||||
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 = IEPlugin(device="CPU", plugin_dirs=[IE_PLUGINS_PATH])
|
||||
if (self._check_instruction("avx2")):
|
||||
plugin.add_cpu_extension(os.path.join(IE_PLUGINS_PATH, "libcpu_extension_avx2.so"))
|
||||
elif (self._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.")
|
||||
|
||||
network = IENetwork.from_ir(model=self._model, weights=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()
|
||||
|
||||
@staticmethod
|
||||
def _check_instruction(instruction):
|
||||
return instruction == str.strip(
|
||||
subprocess.check_output(
|
||||
"lscpu | grep -o \"{}\" | head -1".format(instruction), shell=True
|
||||
).decode("utf-8"))
|
||||
|
||||
def load_label_map(labels_path):
|
||||
with open(labels_path, "r") as f:
|
||||
return json.load(f)["label_map"]
|
||||
@ -0,0 +1,4 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
window.cvat = window.cvat || {};
|
||||
window.cvat.dashboard = window.cvat.dashboard || {};
|
||||
window.cvat.dashboard.uiCallbacks = window.cvat.dashboard.uiCallbacks || [];
|
||||
window.cvat.dashboard.uiCallbacks.push(function(newElements) {
|
||||
let tids = [];
|
||||
for (let el of newElements) {
|
||||
tids.push(el.id.split("_")[1]);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/auto_annotation/meta/get",
|
||||
data: JSON.stringify(tids),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
success: (data) => {
|
||||
newElements.each(function() {
|
||||
let elem = $(this);
|
||||
let tid = +elem.attr("id").split("_")[1];
|
||||
|
||||
const autoAnnoButton = $("<button> Run auto annotation </button>").addClass("semiBold dashboardButtonUI dashboardAutoAnno");
|
||||
autoAnnoButton.appendTo(elem.find("div.dashboardButtonsUI")[0]);
|
||||
|
||||
if (tid in data && data[tid].active) {
|
||||
autoAnnoButton.text("Cancel auto annotation");
|
||||
autoAnnoButton.addClass("autoAnnotationProcess");
|
||||
window.cvat.autoAnnotation.checkAutoAnnotationRequest(tid, autoAnnoButton);
|
||||
}
|
||||
|
||||
autoAnnoButton.on("click", () => {
|
||||
if (autoAnnoButton.hasClass("autoAnnotationProcess")) {
|
||||
$.post(`/auto_annotation/cancel/task/${tid}`).fail( (data) => {
|
||||
let message = `Error during cancel auto annotation request. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
|
||||
showMessage(message);
|
||||
throw Error(message);
|
||||
});
|
||||
}
|
||||
else {
|
||||
let dialogWindow = $(`#${window.cvat.autoAnnotation.modalWindowId}`);
|
||||
dialogWindow.attr("current_tid", tid);
|
||||
dialogWindow.removeClass("hidden");
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
error: (data) => {
|
||||
let message = `Can not get auto annotation meta info. Code: ${data.status}. Message: ${data.responseText || data.statusText}`;
|
||||
window.cvat.autoAnnotation.badResponse(message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.cvat.autoAnnotation = {
|
||||
modalWindowId: "autoAnnotationWindow",
|
||||
autoAnnoFromId: "autoAnnotationForm",
|
||||
autoAnnoModelFieldId: "autoAnnotationModelField",
|
||||
autoAnnoWeightsFieldId: "autoAnnotationWeightsField",
|
||||
autoAnnoConfigFieldId: "autoAnnotationConfigField",
|
||||
autoAnnoConvertFieldId: "autoAnnotationConvertField",
|
||||
autoAnnoCloseButtonId: "autoAnnoCloseButton",
|
||||
autoAnnoSubmitButtonId: "autoAnnoSubmitButton",
|
||||
|
||||
checkAutoAnnotationRequest: (tid, autoAnnoButton) => {
|
||||
function timeoutCallback() {
|
||||
$.get(`/auto_annotation/check/task/${tid}`).done((data) => {
|
||||
if (data.status === "started" || data.status === "queued") {
|
||||
let progress = Math.round(data.progress) || 0;
|
||||
autoAnnoButton.text(`Cancel auto annotation (${progress}%)`);
|
||||
setTimeout(timeoutCallback, 1000);
|
||||
}
|
||||
else {
|
||||
autoAnnoButton.text("Run auto annotation");
|
||||
autoAnnoButton.removeClass("autoAnnotationProcess");
|
||||
}
|
||||
}).fail((data) => {
|
||||
let message = "Error was occurred during check annotation status. " +
|
||||
`Code: ${data.status}, text: ${data.responseText || data.statusText}`;
|
||||
window.cvat.autoAnnotation.badResponse(message);
|
||||
});
|
||||
}
|
||||
setTimeout(timeoutCallback, 1000);
|
||||
},
|
||||
|
||||
badResponse(message) {
|
||||
showMessage(message);
|
||||
throw Error(message);
|
||||
},
|
||||
};
|
||||
|
||||
function submitButtonOnClick() {
|
||||
const annoWindow = $(`#${window.cvat.autoAnnotation.modalWindowId}`);
|
||||
const tid = annoWindow.attr("current_tid");
|
||||
const modelInput = $(`#${window.cvat.autoAnnotation.autoAnnoModelFieldId}`);
|
||||
const weightsInput = $(`#${window.cvat.autoAnnotation.autoAnnoWeightsFieldId}`);
|
||||
const configInput = $(`#${window.cvat.autoAnnotation.autoAnnoConfigFieldId}`);
|
||||
const convFileInput = $(`#${window.cvat.autoAnnotation.autoAnnoConvertFieldId}`);
|
||||
|
||||
const modelFile = modelInput.prop("files")[0];
|
||||
const weightsFile = weightsInput.prop("files")[0];
|
||||
const configFile = configInput.prop("files")[0];
|
||||
const convFile = convFileInput.prop("files")[0];
|
||||
|
||||
if (!modelFile || !weightsFile || !configFile || !convFile) {
|
||||
showMessage("All files must be selected");
|
||||
return;
|
||||
}
|
||||
|
||||
let taskData = new FormData();
|
||||
taskData.append("model", modelFile);
|
||||
taskData.append("weights", weightsFile);
|
||||
taskData.append("config", configFile);
|
||||
taskData.append("conv_script", convFile);
|
||||
|
||||
$.ajax({
|
||||
url: `/auto_annotation/create/task/${tid}`,
|
||||
type: "POST",
|
||||
data: taskData,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
}).done(() => {
|
||||
annoWindow.addClass("hidden");
|
||||
const autoAnnoButton = $(`#dashboardTask_${tid} div.dashboardButtonsUI button.dashboardAutoAnno`);
|
||||
autoAnnoButton.addClass("autoAnnotationProcess");
|
||||
window.cvat.autoAnnotation.checkAutoAnnotationRequest(tid, autoAnnoButton);
|
||||
}).fail((data) => {
|
||||
let message = "Error was occurred during run annotation request. " +
|
||||
`Code: ${data.status}, text: ${data.responseText || data.statusText}`;
|
||||
window.cvat.autoAnnotation.badResponse(message);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
$(`<div id="${window.cvat.autoAnnotation.modalWindowId}" class="modal hidden">
|
||||
<form id="${window.cvat.autoAnnotation.autoAnnoFromId}" class="modal-content" autocomplete="on" onsubmit="return false" style="width: 700px;">
|
||||
<center>
|
||||
<label class="semiBold h1"> Auto annotation setup </label>
|
||||
</center>
|
||||
|
||||
<table style="width: 100%; text-align: left;">
|
||||
<tr>
|
||||
<td style="width: 25%"> <label class="regular h2"> Model </label> </td>
|
||||
<td> <input id="${window.cvat.autoAnnotation.autoAnnoModelFieldId}" type="file" name="model" /> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 25%"> <label class="regular h2"> Weights </label> </td>
|
||||
<td> <input id="${window.cvat.autoAnnotation.autoAnnoWeightsFieldId}" type="file" name="weights" /> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 25%"> <label class="regular h2"> Label map </label> </td>
|
||||
<td> <input id="${window.cvat.autoAnnotation.autoAnnoConfigFieldId}" type="file" name="config" accept=".json" /> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 25%"> <label class="regular h2"> Convertation script </label> </td>
|
||||
<td> <input id="${window.cvat.autoAnnotation.autoAnnoConvertFieldId}" type="file" name="convert" /> </td>
|
||||
</tr>
|
||||
</table>
|
||||
<div>
|
||||
<button id="${window.cvat.autoAnnotation.autoAnnoCloseButtonId}" class="regular h2"> Close </button>
|
||||
<button id="${window.cvat.autoAnnotation.autoAnnoSubmitButtonId}" class="regular h2"> Submit </button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>`).appendTo("body");
|
||||
|
||||
const annoWindow = $(`#${window.cvat.autoAnnotation.modalWindowId}`);
|
||||
const closeWindowButton = $(`#${window.cvat.autoAnnotation.autoAnnoCloseButtonId}`);
|
||||
const submitButton = $(`#${window.cvat.autoAnnotation.autoAnnoSubmitButtonId}`);
|
||||
|
||||
|
||||
closeWindowButton.on("click", () => {
|
||||
annoWindow.addClass("hidden");
|
||||
});
|
||||
|
||||
submitButton.on("click", () => {
|
||||
submitButtonOnClick();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,4 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
@ -0,0 +1,14 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("create/task/<int:tid>", views.create),
|
||||
path("check/task/<int:tid>", views.check),
|
||||
path("cancel/task/<int:tid>", views.cancel),
|
||||
path("meta/get", views.get_meta_info),
|
||||
]
|
||||
@ -0,0 +1,401 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
|
||||
from rules.contrib.views import permission_required, objectgetter
|
||||
from cvat.apps.authentication.decorators import login_required
|
||||
from cvat.apps.engine.models import Task as TaskModel
|
||||
from cvat.apps.engine import annotation
|
||||
|
||||
import django_rq
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
import rq
|
||||
|
||||
from cvat.apps.engine.log import slogger
|
||||
|
||||
from .model_loader import ModelLoader, load_label_map
|
||||
from .image_loader import ImageLoader
|
||||
import os.path as osp
|
||||
|
||||
def get_image_data(path_to_data):
|
||||
def get_image_key(item):
|
||||
return int(osp.splitext(osp.basename(item))[0])
|
||||
|
||||
image_list = []
|
||||
for root, _, filenames in os.walk(path_to_data):
|
||||
for filename in fnmatch.filter(filenames, "*.jpg"):
|
||||
image_list.append(osp.join(root, filename))
|
||||
|
||||
image_list.sort(key=get_image_key)
|
||||
return ImageLoader(image_list)
|
||||
|
||||
def create_anno_container():
|
||||
return {
|
||||
"boxes": [],
|
||||
"polygons": [],
|
||||
"polylines": [],
|
||||
"points": [],
|
||||
"box_paths": [],
|
||||
"polygon_paths": [],
|
||||
"polyline_paths": [],
|
||||
"points_paths": [],
|
||||
}
|
||||
|
||||
class Results():
|
||||
def __init__(self):
|
||||
self._results = create_anno_container()
|
||||
|
||||
def add_box(self, xtl, ytl, xbr, ybr, label, frame_number, attributes=None):
|
||||
self.get_boxes().append({
|
||||
"label": label,
|
||||
"frame": frame_number,
|
||||
"xtl": xtl,
|
||||
"ytl": ytl,
|
||||
"xbr": xbr,
|
||||
"ybr": ybr,
|
||||
"attributes": attributes or {},
|
||||
})
|
||||
|
||||
def add_points(self, points, label, frame_number, attributes=None):
|
||||
self.get_points().append(
|
||||
self._create_polyshape(points, label, frame_number, attributes)
|
||||
)
|
||||
|
||||
def add_polygon(self, points, label, frame_number, attributes=None):
|
||||
self.get_polygons().append(
|
||||
self._create_polyshape(points, label, frame_number, attributes)
|
||||
)
|
||||
|
||||
def add_polyline(self, points, label, frame_number, attributes=None):
|
||||
self.get_polylines().append(
|
||||
self._create_polyshape(points, label, frame_number, attributes)
|
||||
)
|
||||
|
||||
def get_boxes(self):
|
||||
return self._results["boxes"]
|
||||
|
||||
def get_polygons(self):
|
||||
return self._results["polygons"]
|
||||
|
||||
def get_polylines(self):
|
||||
return self._results["polylines"]
|
||||
|
||||
def get_points(self):
|
||||
return self._results["points"]
|
||||
|
||||
def get_box_paths(self):
|
||||
return self._results["box_paths"]
|
||||
|
||||
def get_polygon_paths(self):
|
||||
return self._results["polygon_paths"]
|
||||
|
||||
def get_polyline_paths(self):
|
||||
return self._results["polyline_paths"]
|
||||
|
||||
def get_points_paths(self):
|
||||
return self._results["points_paths"]
|
||||
|
||||
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(path_to_data, model_file, weights_file,
|
||||
labels_mapping, attribute_spec, convertation_file, job, update_progress):
|
||||
|
||||
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_polyshapes(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"],
|
||||
"z_order": 0,
|
||||
"group_id": 0,
|
||||
"occluded": False,
|
||||
"attributes": process_attributes(shape["attributes"], attribute_spec[db_label]),
|
||||
})
|
||||
|
||||
def add_boxes(boxes, target_container):
|
||||
for box in boxes:
|
||||
if box["label"] not in labels_mapping:
|
||||
continue
|
||||
|
||||
db_label = labels_mapping[box["label"]]
|
||||
target_container.append({
|
||||
"label_id": db_label,
|
||||
"frame": box["frame"],
|
||||
"xtl": box["xtl"],
|
||||
"ytl": box["ytl"],
|
||||
"xbr": box["xbr"],
|
||||
"ybr": box["ybr"],
|
||||
"z_order": 0,
|
||||
"group_id": 0,
|
||||
"occluded": False,
|
||||
"attributes": process_attributes(box["attributes"], attribute_spec[db_label]),
|
||||
})
|
||||
|
||||
result = {
|
||||
"create": create_anno_container(),
|
||||
"update": create_anno_container(),
|
||||
"delete": create_anno_container(),
|
||||
}
|
||||
|
||||
data = get_image_data(path_to_data)
|
||||
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 not update_progress(job, frame_counter * 100 / data_len):
|
||||
return None
|
||||
|
||||
processed_detections = process_detections(detections, convertation_file)
|
||||
|
||||
add_boxes(processed_detections.get_boxes(), result["create"]["boxes"])
|
||||
add_polyshapes(processed_detections.get_points(), result["create"]["points"])
|
||||
add_polyshapes(processed_detections.get_polygons(), result["create"]["polygons"])
|
||||
add_polyshapes(processed_detections.get_polylines(), result["create"]["polylines"])
|
||||
|
||||
return result
|
||||
|
||||
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
|
||||
|
||||
def create_thread(tid, model_file, weights_file, labels_mapping, attributes, convertation_file):
|
||||
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(
|
||||
path_to_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
|
||||
|
||||
annotation.clear_task(tid)
|
||||
annotation.save_task(tid, result)
|
||||
slogger.glob.info("auto annotation for task {} done".format(tid))
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
@login_required
|
||||
def get_meta_info(request):
|
||||
try:
|
||||
queue = django_rq.get_queue("low")
|
||||
tids = json.loads(request.body.decode("utf-8"))
|
||||
result = {}
|
||||
for tid in tids:
|
||||
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
|
||||
if job is not None:
|
||||
result[tid] = {
|
||||
"active": job.is_queued or job.is_started,
|
||||
"success": not job.is_failed
|
||||
}
|
||||
|
||||
return JsonResponse(result)
|
||||
except Exception as ex:
|
||||
slogger.glob.exception("exception was occurred during auto annotation meta request", exc_info=True)
|
||||
return HttpResponseBadRequest(str(ex))
|
||||
|
||||
@login_required
|
||||
@permission_required(perm=["engine.task.change"],
|
||||
fn=objectgetter(TaskModel, "tid"), raise_exception=True)
|
||||
def create(request, tid):
|
||||
slogger.glob.info("auto annotation create request for task {}".format(tid))
|
||||
|
||||
def write_file(path, file_obj):
|
||||
with open(path, "wb") as upload_file:
|
||||
for chunk in file_obj.chunks():
|
||||
upload_file.write(chunk)
|
||||
|
||||
try:
|
||||
db_task = TaskModel.objects.get(pk=tid)
|
||||
upload_dir = db_task.get_upload_dirname()
|
||||
queue = django_rq.get_queue("low")
|
||||
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
|
||||
if job is not None and (job.is_started or job.is_queued):
|
||||
raise Exception("The process is already running")
|
||||
|
||||
model_file = request.FILES["model"]
|
||||
model_file_path = os.path.join(upload_dir, model_file.name)
|
||||
write_file(model_file_path, model_file)
|
||||
|
||||
weights_file = request.FILES["weights"]
|
||||
weights_file_path = os.path.join(upload_dir, weights_file.name)
|
||||
write_file(weights_file_path, weights_file)
|
||||
|
||||
config_file = request.FILES["config"]
|
||||
config_file_path = os.path.join(upload_dir, config_file.name)
|
||||
write_file(config_file_path, config_file)
|
||||
|
||||
convertation_file = request.FILES["conv_script"]
|
||||
convertation_file_path = os.path.join(upload_dir, convertation_file.name)
|
||||
write_file(convertation_file_path, convertation_file)
|
||||
|
||||
db_labels = db_task.label_set.prefetch_related("attributespec_set").all()
|
||||
db_attributes = {db_label.id:
|
||||
{db_attr.get_name(): db_attr.id for db_attr in db_label.attributespec_set.all()} for db_label in db_labels}
|
||||
db_labels = {db_label.id:db_label.name for db_label in db_labels}
|
||||
|
||||
class_names = load_label_map(config_file_path)
|
||||
|
||||
labels_mapping = {}
|
||||
for db_key, db_label in db_labels.items():
|
||||
for key, label in class_names.items():
|
||||
if label == db_label:
|
||||
labels_mapping[int(key)] = db_key
|
||||
|
||||
if not labels_mapping:
|
||||
raise Exception("No labels found for annotation")
|
||||
|
||||
queue.enqueue_call(func=create_thread,
|
||||
args=(
|
||||
tid,
|
||||
model_file_path,
|
||||
weights_file_path,
|
||||
labels_mapping,
|
||||
db_attributes,
|
||||
convertation_file_path),
|
||||
job_id="auto_annotation.create/{}".format(tid),
|
||||
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 HttpResponse()
|
||||
|
||||
@login_required
|
||||
@permission_required(perm=["engine.task.access"],
|
||||
fn=objectgetter(TaskModel, "tid"), raise_exception=True)
|
||||
def check(request, tid):
|
||||
try:
|
||||
queue = django_rq.get_queue("low")
|
||||
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
|
||||
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"]
|
||||
elif job.is_finished:
|
||||
data["status"] = "finished"
|
||||
job.delete()
|
||||
else:
|
||||
data["status"] = "failed"
|
||||
data["stderr"] = job.exc_info
|
||||
job.delete()
|
||||
|
||||
except Exception:
|
||||
data["status"] = "unknown"
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required(perm=["engine.task.change"],
|
||||
fn=objectgetter(TaskModel, "tid"), raise_exception=True)
|
||||
def cancel(request, tid):
|
||||
try:
|
||||
queue = django_rq.get_queue("low")
|
||||
job = queue.fetch_job("auto_annotation.create/{}".format(tid))
|
||||
if job is None or job.is_finished or job.is_failed:
|
||||
raise Exception("Task is not being annotated currently")
|
||||
elif "cancel" not in job.meta:
|
||||
job.meta["cancel"] = True
|
||||
job.save()
|
||||
|
||||
except Exception as ex:
|
||||
try:
|
||||
slogger.task[tid].exception("cannot cancel auto annotation for task #{}".format(tid), exc_info=True)
|
||||
except Exception as logger_ex:
|
||||
slogger.glob.exception("exception was occured during cancel auto annotation request for task {}: {}".format(tid, str(logger_ex)), exc_info=True)
|
||||
return HttpResponseBadRequest(str(ex))
|
||||
|
||||
return HttpResponse()
|
||||
Loading…
Reference in New Issue