Release 0.4.0

main
Nikita Manovich 7 years ago
commit 7d6dcd1c04

@ -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],
},
};

4
.gitignore vendored

@ -8,6 +8,10 @@
/logs /logs
/components/openvino/*.tgz /components/openvino/*.tgz
/profiles /profiles
/ssh/*
!/ssh/README.md
node_modules
# Ignore temporary files # Ignore temporary files
docker-compose.override.yml docker-compose.override.yml

@ -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'

@ -5,29 +5,24 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "CVAT Server", "name": "server",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"stopOnEntry": false, "stopOnEntry": false,
"debugStdLib": true, "justMyCode": false,
"pythonPath": "${config:python.pythonPath}", "pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py", "program": "${workspaceRoot}/manage.py",
"args": [ "args": [
"runserver", "runserver",
"--noreload", "--noreload",
"--nothreading",
"--insecure", "--insecure",
"127.0.0.1:7000" "127.0.0.1:7000"
], ],
"debugOptions": [ "django": true,
"RedirectOutput", "cwd": "${workspaceFolder}"
"DjangoDebugging"
],
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/.env",
}, },
{ {
"name": "CVAT Client", "name": "client",
"type": "chrome", "type": "chrome",
"request": "launch", "request": "launch",
"url": "http://localhost:7000/", "url": "http://localhost:7000/",
@ -40,11 +35,11 @@
} }
}, },
{ {
"name": "CVAT RQ - default", "name": "RQ - default",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"stopOnEntry": false, "stopOnEntry": false,
"debugStdLib": true, "justMyCode": false,
"pythonPath": "${config:python.pythonPath}", "pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py", "program": "${workspaceRoot}/manage.py",
"args": [ "args": [
@ -53,20 +48,15 @@
"--worker-class", "--worker-class",
"cvat.simpleworker.SimpleWorker", "cvat.simpleworker.SimpleWorker",
], ],
"debugOptions": [ "django": true,
"RedirectOutput",
"DjangoDebugging"
],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"env": {}, "env": {}
"envFile": "${workspaceFolder}/.env",
}, },
{ {
"name": "CVAT RQ - low", "name": "RQ - low",
"type": "python", "type": "python",
"request": "launch", "request": "launch",
"debugStdLib": true, "justMyCode": false,
"stopOnEntry": false, "stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}", "pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py", "program": "${workspaceRoot}/manage.py",
@ -76,23 +66,68 @@
"--worker-class", "--worker-class",
"cvat.simpleworker.SimpleWorker", "cvat.simpleworker.SimpleWorker",
], ],
"debugOptions": [ "django": true,
"RedirectOutput", "cwd": "${workspaceFolder}",
"DjangoDebugging" "env": {}
},
{
"name": "git",
"type": "python",
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"update_git_states"
],
"django": true,
"cwd": "${workspaceFolder}",
"env": {}
},
{
"name": "migrate",
"type": "python",
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"migrate"
],
"django": true,
"cwd": "${workspaceFolder}",
"env": {}
},
{
"name": "tests",
"type": "python",
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"test",
"--settings",
"cvat.settings.testing",
"cvat/apps/engine",
], ],
"django": true,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"env": {}, "env": {}
"envFile": "${workspaceFolder}/.env",
}, },
], ],
"compounds": [ "compounds": [
{ {
"name": "CVAT Debugging", "name": "debugging",
"configurations": [ "configurations": [
"CVAT Client", "client",
"CVAT Server", "server",
"CVAT RQ - default", "RQ - default",
"CVAT RQ - low", "RQ - low",
"git",
] ]
} }
] ]

@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- OpenVINO auto annotation: it is possible to upload a custom model and annotate images automatically.
- Ability to rotate images/video in the client part (Ctrl+R, Shift+Ctrl+R shortcuts) (#305)
- The ReID application for automatic bounding box merging has been added (#299)
- Keyboard shortcuts to switch next/previous default shape type (box, polygon etc) [Alt + <, Alt + >] (#316)
- Converter for VOC now supports interpolation tracks
- REST API (/api/v1/*, /api/docs)
- Semi-automatic semantic segmentation with the [Deep Extreme Cut](http://www.vision.ee.ethz.ch/~cvlsegmentation/dextr/) work
### Changed
- Propagation setup has been moved from settings to bottom player panel
- Additional events like "Debug Info" or "Fit Image" have been added for analitics
- Optional using LFS for git annotation storages (#314)
### Deprecated
- "Flip images" flag in the create task dialog will be removed. Rotation functionality in client part have been added instead.
### Removed
-
### Fixed
- Django 2.1.5 (security fix, https://nvd.nist.gov/vuln/detail/CVE-2019-3498)
- Several scenarious which cause code 400 after undo/redo/save have been fixed (#315)
### Security
-
## [0.3.0] - 2018-12-29 ## [0.3.0] - 2018-12-29
### Added ### Added
- Ability to copy Object URL and Frame URL via object context menu and player context menu respectively. - Ability to copy Object URL and Frame URL via object context menu and player context menu respectively.

@ -57,6 +57,11 @@ $ code .
You have done! Now it is possible to insert breakpoints and debug server and client of the tool. You have done! Now it is possible to insert breakpoints and debug server and client of the tool.
## JavaScript coding style
We use the [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) for JavaScript code with a
litle exception - we prefere 4 spaces for indentation of nested blocks and statements.
## Branching model ## Branching model
The project uses [a successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model). The project uses [a successful Git branching model](https://nvie.com/posts/a-successful-git-branching-model).

@ -28,3 +28,11 @@
- **[Sebastián Yonekura](https://github.com/syonekura)** - **[Sebastián Yonekura](https://github.com/syonekura)**
* [convert_to_voc.py](utils/voc) - an utility for converting CVAT XML to PASCAL VOC data annotation format. * [convert_to_voc.py](utils/voc) - an utility for converting CVAT XML to PASCAL VOC data annotation format.
- **[ITLab Team](https://github.com/itlab-vision/cvat):**
**[Vasily Danilin](https://github.com/DanVev)**,
**[Eugene Shashkin](https://github.com/EvgenyShashkin)**,
**[Dmitry Silenko](https://github.com/DimaSilenko)**,
**[Alina Bykovskaya](https://github.com/alinaut)**,
**[Yanina Koltushkina](https://github.com/YaniKolt)**
* Integrating CI tools as Travis CI, Codacy and Coveralls.io

@ -3,11 +3,13 @@ FROM ubuntu:16.04
ARG http_proxy ARG http_proxy
ARG https_proxy ARG https_proxy
ARG no_proxy ARG no_proxy
ARG socks_proxy
ENV TERM=xterm \ ENV TERM=xterm \
http_proxy=${http_proxy} \ http_proxy=${http_proxy} \
https_proxy=${https_proxy} \ https_proxy=${https_proxy} \
no_proxy=${no_proxy} no_proxy=${no_proxy} \
socks_proxy=${socks_proxy}
ENV LANG='C.UTF-8' \ ENV LANG='C.UTF-8' \
LC_ALL='C.UTF-8' LC_ALL='C.UTF-8'
@ -89,6 +91,7 @@ RUN if [ "$WITH_TESTS" = "yes" ]; then \
eslint-detailed-reporter \ eslint-detailed-reporter \
karma \ karma \
karma-chrome-launcher \ karma-chrome-launcher \
karma-coveralls \
karma-coverage \ karma-coverage \
karma-junit-reporter \ karma-junit-reporter \
karma-qunit \ karma-qunit \
@ -100,6 +103,39 @@ RUN if [ "$WITH_TESTS" = "yes" ]; then \
COPY cvat/requirements/ /tmp/requirements/ COPY cvat/requirements/ /tmp/requirements/
COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/
RUN pip3 install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt RUN pip3 install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt
# Install git application dependencies
RUN apt-get update && \
apt-get install -y ssh netcat-openbsd git curl zip && \
wget -qO /dev/stdout https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \
apt-get install -y git-lfs && \
git lfs install && \
rm -rf /var/lib/apt/lists/* && \
if [ -z ${socks_proxy} ]; then \
echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30\"" >> ${HOME}/.bashrc; \
else \
echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30 -o ProxyCommand='nc -X 5 -x ${socks_proxy} %h %p'\"" >> ${HOME}/.bashrc; \
fi
# Download model for re-identification app
ENV REID_MODEL_DIR=${HOME}/reid
RUN if [ "$OPENVINO_TOOLKIT" = "yes" ]; then \
mkdir ${HOME}/reid && \
wget https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -O reid/reid.xml && \
wget https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.bin -O reid/reid.bin; \
fi
# TODO: CHANGE URL
ARG WITH_DEXTR
ENV WITH_DEXTR=${WITH_DEXTR}
ENV DEXTR_MODEL_DIR=${HOME}/models/dextr
RUN if [ "$WITH_DEXTR" = "yes" ]; then \
mkdir ${DEXTR_MODEL_DIR} -p && \
wget https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -O ${DEXTR_MODEL_DIR}/dextr.zip && \
unzip ${DEXTR_MODEL_DIR}/dextr.zip -d ${DEXTR_MODEL_DIR} && rm ${DEXTR_MODEL_DIR}/dextr.zip; \
fi
COPY ssh ${HOME}/.ssh
COPY cvat/ ${HOME}/cvat COPY cvat/ ${HOME}/cvat
COPY tests ${HOME}/tests COPY tests ${HOME}/tests
RUN patch -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch RUN patch -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch

@ -1,5 +1,7 @@
# Computer Vision Annotation Tool (CVAT) # Computer Vision Annotation Tool (CVAT)
[![Build Status](https://travis-ci.org/opencv/cvat.svg?branch=develop)](https://travis-ci.org/opencv/cvat)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/840351da141e4eaeac6476fd19ec0a33)](https://app.codacy.com/app/nmanovic/cvat?utm_source=github.com&utm_medium=referral&utm_content=opencv/cvat&utm_campaign=Badge_Grade_Settings)
[![Gitter chat](https://badges.gitter.im/opencv-cvat/gitter.png)](https://gitter.im/opencv-cvat) [![Gitter chat](https://badges.gitter.im/opencv-cvat/gitter.png)](https://gitter.im/opencv-cvat)
CVAT is completely re-designed and re-implemented version of [Video Annotation Tool from Irvine, California](http://carlvondrick.com/vatic/) tool. It is free, online, interactive video and image annotation tool for computer vision. It is being used by our team to annotate million of objects with different properties. Many UI and UX decisions are based on feedbacks from professional data annotation team. CVAT is completely re-designed and re-implemented version of [Video Annotation Tool from Irvine, California](http://carlvondrick.com/vatic/) tool. It is free, online, interactive video and image annotation tool for computer vision. It is being used by our team to annotate million of objects with different properties. Many UI and UX decisions are based on feedbacks from professional data annotation team.
@ -15,11 +17,18 @@ CVAT is completely re-designed and re-implemented version of [Video Annotation T
## Screencasts ## Screencasts
- [Introduction](https://youtu.be/L9_IvUIHGwM)
- [Annotation mode](https://youtu.be/6h7HxGL6Ct4) - [Annotation mode](https://youtu.be/6h7HxGL6Ct4)
- [Interpolation mode](https://youtu.be/U3MYDhESHo4) - [Interpolation mode](https://youtu.be/U3MYDhESHo4)
- [Attribute mode](https://youtu.be/UPNfWl8Egd8) - [Attribute mode](https://youtu.be/UPNfWl8Egd8)
- [Segmentation mode](https://youtu.be/6IJ0QN7PBKo) - [Segmentation mode](https://youtu.be/Fh8oKuSUIPs)
- [Tutorial for polygons](https://www.youtube.com/watch?v=XTwfXDh4clI) - [Tutorial for polygons](https://www.youtube.com/watch?v=XTwfXDh4clI)
- [Semi-automatic segmentation](https://www.youtube.com/watch?v=vnqXZ-Z-VTQ)
## Links
- [Intel AI blog: New Computer Vision Tool Accelerates Annotation of Digital Images and Video](https://www.intel.ai/introducing-cvat)
- [Intel Software: Computer Vision Annotation Tool: A Universal Approach to Data Annotation](https://software.intel.com/en-us/articles/computer-vision-annotation-tool-a-universal-approach-to-data-annotation)
- [VentureBeat: Intel open-sources CVAT, a toolkit for data labeling](https://venturebeat.com/2019/03/05/intel-open-sources-cvat-a-toolkit-for-data-labeling/)
## Online Demo ## Online Demo
@ -68,7 +77,13 @@ docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml
docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml -f components/openvino/docker-compose.openvino.yml up -d docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml -f components/openvino/docker-compose.openvino.yml up -d
``` ```
For details please see [components section](components/README.md). ### Additional optional components
- [Auto annotation using DL models in OpenVINO toolkit format](cvat/apps/auto_annotation/README.md)
- [Analytics: management and monitoring of data annotation team](components/analytics/README.md)
- [TF Object Detection API: auto annotation](components/tf_annotation/README.md)
- [Support for NVIDIA GPUs](components/cuda/README.md)
- [Semi-automatic segmentation with Deep Extreme Cut](cvat/apps/dextr_segmentation/README.md)
### Create superuser account ### Create superuser account

@ -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)

@ -1,5 +1,7 @@
## Analytics for Computer Vision Annotation Tool (CVAT) ## Analytics for Computer Vision Annotation Tool (CVAT)
![](/cvat/apps/documentation/static/documentation/images/image097.jpg)
It is possible to proxy annotation logs from client to ELK. To do that run the following command below: It is possible to proxy annotation logs from client to ELK. To do that run the following command below:
### Build docker image ### Build docker image

@ -10,34 +10,48 @@ filter {
# 1. Decode the event from json in 'message' field # 1. Decode the event from json in 'message' field
# 2. Remove unnecessary field from it # 2. Remove unnecessary field from it
# 3. Type it as client # 3. Type it as client
mutate {
rename => { "message" => "source_message" }
}
json { json {
source => "message" source => "source_message"
} }
date { date {
match => ["timestamp", "UNIX", "UNIX_MS"] match => ["time", "ISO8601"]
remove_field => "timestamp" remove_field => "time"
}
if [payload] {
ruby {
code => "
event.get('payload').each { |key, value|
event.set(key, value)
}
"
}
} }
if [event] == "Send exception" { if [name] == "Send exception" {
aggregate { aggregate {
task_id => "%{userid}_%{application}_%{message}_%{filename}_%{line}" task_id => "%{username}_%{message}_%{filename}_%{line}"
code => " code => "
require 'time' require 'time'
map['userid'] ||= event.get('userid'); map['username'] ||= event.get('username');
map['application'] ||= event.get('application');
map['error'] ||= event.get('message'); map['error'] ||= event.get('message');
map['filename'] ||= event.get('filename'); map['filename'] ||= event.get('filename');
map['line'] ||= event.get('line'); map['line'] ||= event.get('line');
map['task'] ||= event.get('task'); map['task_id'] ||= event.get('task_id');
map['job_id'] ||= event.get('job_id');
map['error_count'] ||= 0; map['error_count'] ||= 0;
map['error_count'] += 1; map['error_count'] += 1;
map['aggregated_stack'] ||= ''; map['aggregated_stack'] ||= '';
map['aggregated_stack'] += event.get('stack') + '\n\n\n';" map['aggregated_stack'] += event.get('stack') + '\n\n\n';
"
timeout => 3600 timeout => 3600
timeout_tags => ['aggregated_exception'] timeout_tags => ['aggregated_exception']
push_map_as_event_on_timeout => true push_map_as_event_on_timeout => true
@ -45,12 +59,17 @@ filter {
} }
prune { prune {
blacklist_names => ["level", "host", "logger_name", "message", "path", blacklist_names => ["level", "host", "logger_name", "path",
"port", "stack_info"] "port", "stack_info", "payload", "source_message"]
} }
mutate { mutate {
replace => { "type" => "client" } replace => { "type" => "client" }
copy => {
"job_id" => "task"
"username" => "userid"
"name" => "event"
}
} }
} else if [logger_name] =~ /cvat.server/ { } else if [logger_name] =~ /cvat.server/ {
# 1. Remove 'logger_name' field and create 'task' field # 1. Remove 'logger_name' field and create 'task' field
@ -58,14 +77,14 @@ filter {
# 3. Type it as server # 3. Type it as server
if [logger_name] =~ /cvat\.server\.task_[0-9]+/ { if [logger_name] =~ /cvat\.server\.task_[0-9]+/ {
mutate { mutate {
rename => { "logger_name" => "task" } rename => { "logger_name" => "task_id" }
gsub => [ "task", "cvat.server.task_", "" ] gsub => [ "task_id", "cvat.server.task_", "" ]
} }
# Need to split the mutate because otherwise the conversion # Need to split the mutate because otherwise the conversion
# doesn't work. # doesn't work.
mutate { mutate {
convert => { "task" => "integer" } convert => { "task_id" => "integer" }
} }
} }

@ -6,7 +6,7 @@
### Preparation ### Preparation
* Download latest [OpenVINO toolkit](https://software.intel.com/en-us/openvino-toolkit) installer (offline or online) for Linux platform. It should be .tgz archive. Minimum required version is 2018 R3. * Download [OpenVINO toolkit 2018R5](https://software.intel.com/en-us/openvino-toolkit) .tgz installer (offline or online) for Ubuntu platforms.
* Put downloaded file into ```components/openvino```. * Put downloaded file into ```components/openvino```.
* Accept EULA in the eula.cfg file. * Accept EULA in the eula.cfg file.

@ -12,7 +12,7 @@ if [[ `lscpu | grep -o "GenuineIntel"` != "GenuineIntel" ]]; then
fi fi
if [[ `lscpu | grep -o "sse4" | head -1` != "sse4" ]] && [[ `lscpu | grep -o "avx2" | head -1` != "avx2" ]]; then if [[ `lscpu | grep -o "sse4" | head -1` != "sse4" ]] && [[ `lscpu | grep -o "avx2" | head -1` != "avx2" ]]; then
echo "You Intel CPU should support sse4 or avx2 instruction if you want use OpenVINO" echo "OpenVINO expects your CPU to support SSE4 or AVX2 instructions"
exit 1 exit 1
fi fi
@ -23,12 +23,18 @@ tar -xzf `ls | grep "openvino_toolkit"`
cd `ls -d */ | grep "openvino_toolkit"` cd `ls -d */ | grep "openvino_toolkit"`
apt-get update && apt-get install -y sudo cpio && \ apt-get update && apt-get install -y sudo cpio && \
./install_cv_sdk_dependencies.sh && SUDO_FORCE_REMOVE=yes apt-get remove -y sudo if [ -f "install_cv_sdk_dependencies.sh" ]; then ./install_cv_sdk_dependencies.sh; \
else ./install_openvino_dependencies.sh; fi && SUDO_FORCE_REMOVE=yes apt-get remove -y sudo
cat ../eula.cfg >> silent.cfg cat ../eula.cfg >> silent.cfg
./install.sh -s silent.cfg ./install.sh -s silent.cfg
cd /tmp/components && rm openvino -r cd /tmp/components && rm openvino -r
echo "source /opt/intel/computer_vision_sdk/bin/setupvars.sh" >> ${HOME}/.bashrc if [ -f "/opt/intel/computer_vision_sdk/bin/setupvars.sh" ]; then
echo -e '\nexport IE_PLUGINS_PATH=${IE_PLUGINS_PATH}' >> /opt/intel/computer_vision_sdk/bin/setupvars.sh echo "source /opt/intel/computer_vision_sdk/bin/setupvars.sh" >> ${HOME}/.bashrc;
echo -e '\nexport IE_PLUGINS_PATH=${IE_PLUGINS_PATH}' >> /opt/intel/computer_vision_sdk/bin/setupvars.sh;
else
echo "source /opt/intel/openvino/bin/setupvars.sh" >> ${HOME}/.bashrc;
echo -e '\nexport IE_PLUGINS_PATH=${IE_PLUGINS_PATH}' >> /opt/intel/openvino/bin/setupvars.sh;
fi

@ -16,17 +16,5 @@ if [[ "$CUDA_SUPPORT" = "yes" ]]
then then
pip3 install --no-cache-dir tensorflow-gpu==1.7.0 pip3 install --no-cache-dir tensorflow-gpu==1.7.0
else else
if [[ "$OPENVINO_TOOLKIT" = "yes" ]]
then
pip3 install -r ${INTEL_CVSDK_DIR}/deployment_tools/model_optimizer/requirements.txt && \
cd ${HOME}/rcnn/ && \
${INTEL_CVSDK_DIR}/deployment_tools/model_optimizer/mo.py --framework tf \
--data_type FP32 --input_shape [1,600,600,3] \
--input image_tensor --output detection_scores,detection_boxes,num_detections \
--tensorflow_use_custom_operations_config ${INTEL_CVSDK_DIR}/deployment_tools/model_optimizer/extensions/front/tf/faster_rcnn_support.json \
--tensorflow_object_detection_api_pipeline_config pipeline.config --input_model inference_graph.pb && \
rm inference_graph.pb
else
pip3 install --no-cache-dir tensorflow==1.7.0 pip3 install --no-cache-dir tensorflow==1.7.0
fi
fi fi

@ -3,3 +3,8 @@
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
from cvat.utils.version import get_version
VERSION = (0, 4, 0, 'final')
__version__ = get_version(VERSION)

@ -4,8 +4,10 @@
import os import os
from django.conf import settings from django.conf import settings
from django.db.models import Q
import rules import rules
from . import AUTH_ROLE from . import AUTH_ROLE
from rest_framework.permissions import BasePermission
def register_signals(): def register_signals():
from django.db.models.signals import post_migrate, post_save from django.db.models.signals import post_migrate, post_save
@ -67,6 +69,11 @@ def is_job_annotator(db_user, db_job):
return has_rights return has_rights
# AUTH PERMISSIONS RULES # AUTH PERMISSIONS RULES
rules.add_perm('engine.role.user', has_user_role)
rules.add_perm('engine.role.admin', has_admin_role)
rules.add_perm('engine.role.annotator', has_annotator_role)
rules.add_perm('engine.role.observer', has_observer_role)
rules.add_perm('engine.task.create', has_admin_role | has_user_role) rules.add_perm('engine.task.create', has_admin_role | has_user_role)
rules.add_perm('engine.task.access', has_admin_role | has_observer_role | rules.add_perm('engine.task.access', has_admin_role | has_observer_role |
is_task_owner | is_task_annotator) is_task_owner | is_task_annotator)
@ -78,3 +85,64 @@ rules.add_perm('engine.job.access', has_admin_role | has_observer_role |
is_job_owner | is_job_annotator) is_job_owner | is_job_annotator)
rules.add_perm('engine.job.change', has_admin_role | is_job_owner | rules.add_perm('engine.job.change', has_admin_role | is_job_owner |
is_job_annotator) is_job_annotator)
class AdminRolePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
return request.user.has_perm("engine.role.admin")
class UserRolePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
return request.user.has_perm("engine.role.user")
class AnnotatorRolePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
return request.user.has_perm("engine.role.annotator")
class ObserverRolePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
return request.user.has_perm("engine.role.observer")
class TaskCreatePermission(BasePermission):
# pylint: disable=no-self-use
def has_permission(self, request, view):
return request.user.has_perm("engine.task.create")
class TaskAccessPermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.task.access", obj)
class TaskGetQuerySetMixin(object):
def get_queryset(self):
queryset = super().get_queryset()
user = self.request.user
# Don't filter queryset for admin, observer and detail methods
if has_admin_role(user) or has_observer_role(user) or self.detail:
return queryset
else:
return queryset.filter(Q(owner=user) | Q(assignee=user) |
Q(segment__job__assignee=user) | Q(assignee=None)).distinct()
class TaskChangePermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.task.change", obj)
class TaskDeletePermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.task.delete", obj)
class JobAccessPermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.job.access", obj)
class JobChangePermission(BasePermission):
# pylint: disable=no-self-use
def has_object_permission(self, request, view, obj):
return request.user.has_perm("engine.job.change", obj)

@ -0,0 +1,165 @@
## Auto annotation
### Description
The application will be enabled automatically if
[OpenVINO&trade; component](../../../components/openvino)
is installed. It allows to use custom models for auto annotation. Only models in
OpenVINO&trade; 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']

@ -2,8 +2,3 @@
# Copyright (C) 2018 Intel Corporation # Copyright (C) 2018 Intel Corporation
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
from django.contrib import admin
# Register your models here.

@ -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': (),
},
),
]

@ -3,7 +3,3 @@
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
from django.db import models
# Create your models here.

@ -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%;
}

@ -2,8 +2,3 @@
# Copyright (C) 2018 Intel Corporation # Copyright (C) 2018 Intel Corporation
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
from django.test import TestCase
# Create your tests here.

@ -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)

@ -5,7 +5,9 @@
from django.apps import AppConfig from django.apps import AppConfig
class DashboardConfig(AppConfig): class DashboardConfig(AppConfig):
name = 'dashboard' name = 'cvat.apps.dashboard'
def ready(self):
# plugin registration
pass

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -4,12 +4,9 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
"use strict";
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
$(`<button class="menuButton semiBold h2"> Open Task </button>`).on('click', () => { $('<button class="menuButton semiBold h2"> Open Task </button>').on('click', () => {
let win = window.open(`${window.location.origin }/dashboard/?jid=${window.cvat.job.id}`, '_blank'); const win = window.open(`${window.location.origin}/dashboard/?id=${window.cvat.job.task_id}`, '_blank');
win.focus(); win.focus();
}).prependTo('#engineMenuButtons'); }).prependTo('#engineMenuButtons');
}); });

@ -4,7 +4,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
.dashboardTaskUI { .dashboardItem {
margin: 5px auto; margin: 5px auto;
width: 1200px; width: 1200px;
height: 335px; height: 335px;
@ -14,7 +14,7 @@
} }
.dashboardTaskIntro { .dashboardTaskIntro {
width: 25%; width: 30%;
height: 75%; height: 75%;
float: left; float: left;
margin-left: 20px; margin-left: 20px;
@ -25,7 +25,7 @@
.dashboardButtonsUI { .dashboardButtonsUI {
margin-top: 1%; margin-top: 1%;
width: 33%; width: 35%;
height: 75%; height: 75%;
float: left; float: left;
overflow-y: auto; overflow-y: auto;
@ -33,19 +33,19 @@
.dashboardButtonUI { .dashboardButtonUI {
display: block; display: block;
width: 70%; width: 60%;
height: 2.5em; height: 1.6em;
margin: auto; margin: auto;
margin-top: 0.1em; margin-top: 0.3em;
font-size: 1em; font-size: 1em;
} }
.dashboardJobsUI { .dashboardJobsUI {
width: 40%; width: 30%;
height: 75%; height: 75%;
float: left; float: left;
text-align: center; text-align: center;
overflow-y: scroll; overflow-y: auto;
} }
.dashboardJobList { .dashboardJobList {
@ -65,7 +65,7 @@
padding: 10px; padding: 10px;
border: 1px solid grey; border: 1px solid grey;
float: left; float: left;
width: 70%; width: 76%;
background: #f1f1f1; background: #f1f1f1;
outline: 0; outline: 0;
} }
@ -75,7 +75,6 @@
width: 20%; width: 20%;
padding: 10px; padding: 10px;
background: #0b7dda; background: #0b7dda;
color: white;
border: 1px solid grey; border: 1px solid grey;
border-left: none; border-left: none;
cursor: pointer; cursor: pointer;
@ -90,10 +89,6 @@
background: #0b7fff; background: #0b7fff;
} }
#dashboardCreateTaskButton {
font-size: 2em;
}
#dashboardCreateContent { #dashboardCreateContent {
width: 500px; width: 500px;
display: table; display: table;

@ -3,6 +3,7 @@
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
--> -->
{% extends 'engine/base.html' %} {% extends 'engine/base.html' %}
{% load static %} {% load static %}
{% load pagination_tags %} {% load pagination_tags %}
@ -14,12 +15,17 @@
{% block head_css %} {% block head_css %}
{{ block.super }} {{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/stylesheet.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'dashboard/stylesheet.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/jstree/themes/default/style.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/jstree/themes/default/style.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dashboard/js/3rdparty/pagination/pagination.css' %}">
{% for css_file in css_3rdparty %}
<link rel="stylesheet" type="text/css" href="{% static css_file %}">
{% endfor %}
{% endblock %} {% endblock %}
{% block head_js_3rdparty %} {% block head_js_3rdparty %}
{{ block.super }} {{ block.super }}
<script type="text/javascript" src="{% static 'dashboard/js/3rdparty/jstree/jstree.js' %}"></script> <script type="text/javascript" src="{% static 'dashboard/js/3rdparty/jstree/jstree.min.js' %}"></script>
<script type="text/javascript" src="{% static 'dashboard/js/3rdparty/pagination/pagination.min.js' %}"></script>
{% for js_file in js_3rdparty %} {% for js_file in js_3rdparty %}
<script type="text/javascript" src="{% static js_file %}" defer></script> <script type="text/javascript" src="{% static js_file %}" defer></script>
{% endfor %} {% endfor %}
@ -33,35 +39,27 @@
<script type="text/javascript" src="{% static 'engine/js/shapes.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/shapes.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script> <script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script>
<script>
window.maxUploadSize = {{ max_upload_size }};
window.maxUploadCount = {{ max_upload_count }};
</script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="content"> <div id="content">
<center> <div style="width: 100%; display: flex;">
<table style="width: 65%;"> <div style="width: 50%; display: flex;"> </div>
<tr> <div style="width: 100%; display: flex;">
<td><button id="dashboardCreateTaskButton" class="regular h1"> Create New Task </button></td> <div id="dashboardManageButtons" style="display: flex;">
<td> <button id="dashboardCreateTaskButton" class="regular h1" style="padding: 7px;"> Create New Task </button>
<div style="width: 300px; float: right;"> </div>
<input type="text" id="dashboardSearchInput" class="regular h1" placeholder="Search.." name="search"> <div style="width: 300px; display: flex; margin-left: 4px;">
<button id="dashboardSearchSubmit" class="regular h1"> &#x1F50D; </button> <input type="text" id="dashboardSearchInput" class="regular h3" placeholder="Search.." name="search">
<button id="dashboardSearchSubmit" class="regular h3"> &#x1F50D; </button>
</div>
</div>
<div style="width: 50%; display: flex;"> </div>
</div> </div>
</td>
</tr>
</table>
</center>
{% autopaginate data %} <div id="dashboardPagination">
<div style="float: left; width: 100%"> <div id="dashboardList" style="float: left; width: 100%"> </div>
{% for item in data %}
{% include "dashboard/task.html" %}
{% endfor %}
</div> </div>
<center>{% paginate %}</center>
</div> </div>
<div id="dashboardCreateModal" class="modal hidden"> <div id="dashboardCreateModal" class="modal hidden">
@ -110,14 +108,6 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
<br> <input id="dashboardShareSource" type="radio" name="sourceType" value="share"/> <label for="dashboardShareSource" class="regular h2" for="shareSource"> Share </label> <br> <input id="dashboardShareSource" type="radio" name="sourceType" value="share"/> <label for="dashboardShareSource" class="regular h2" for="shareSource"> Share </label>
</td> </td>
</tr> </tr>
<tr>
<td>
<label class="regular h2"> Flip images </label>
</td>
<td>
<input type="checkbox" id="dashboardFlipImages"/>
</td>
</tr>
<tr> <tr>
<td> <td>
<label class="regular h2"> Z-Order </label> <label class="regular h2"> Z-Order </label>
@ -164,20 +154,20 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
</div> </div>
</div> </div>
<div style="width: 100%; height: 14%; padding-top: 10px;"> <div style="width: 100%; height: 14%; padding-top: 10px;">
<div style="float: left; height: 50px; overflow: auto; width: 63%; height: auto;">
<label id="dashboardCreateTaskMessage" class="regular h2 selectable" style="float:left;"> </label>
</div>
<div style="float: right; width: 35%; height: 50px;"> <div style="float: right; width: 35%; height: 50px;">
<button id="dashboardCancelTask" class="regular h2"> Cancel </button> <button id="dashboardCancelTask" class="regular h2"> Cancel </button>
<button id="dashboardSubmitTask" class="regular h2"> Submit </button> <button id="dashboardSubmitTask" class="regular h2"> Submit </button>
</div> </div>
<div style="float: left; height: 50px; overflow: auto; width: 100%; height: auto; word-break: break-word;">
<label id="dashboardCreateTaskMessage" class="regular h2 selectable" style="float:left;"> </label>
</div>
</div> </div>
</form> </form>
</div> </div>
<div id="dashboardShareBrowseModal" class="modal hidden"> <div id="dashboardShareBrowseModal" class="modal hidden">
<div style="width: 600px; height: 400px;" class="modal-content noSelect"> <div style="width: 600px; height: 400px;" class="modal-content noSelect">
<center> <label class="regular h1"> {{ share_path }} </label> </center> <center> <label id="dashboardShareBasePath" class="regular h1"> </label> </center>
<div id="dashboardShareBrowser"> </div> <div id="dashboardShareBrowser"> </div>
<center> <center>
<button id="dashboardCancelBrowseServer" class="regular h2" style="margin: 0px 10px"> Cancel </button> <button id="dashboardCancelBrowseServer" class="regular h2" style="margin: 0px 10px"> Cancel </button>
@ -186,14 +176,16 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
</div> </div>
</div> </div>
<div id="dashboardUpdateModal" class="modal hidden"> <template id="dashboardUpdateTemplate">
<div id="dashboardUpdateModal" class="modal">
<div id="dashboardUpdateContent" class="modal-content"> <div id="dashboardUpdateContent" class="modal-content">
<input id="dashboardOldLabels" type="text" readonly=true placeholder="Please Wait.." class="regular h2"> <input id="dashboardOldLabels" type="text" readonly=true class="regular h2">
<input id="dashboardNewLabels" type="text" placeholder="New Labels" class="regular h2"> <input id="dashboardNewLabels" type="text" placeholder="expand the specification here" class="regular h2">
<center> <center>
<button id="dashboardCancelUpdate" class="regular h2"> Cancel </button> <button id="dashboardCancelUpdate" class="regular h2"> Cancel </button>
<button id="dashboardSubmitUpdate" class="regular h2"> Update </button> <button id="dashboardSubmitUpdate" class="regular h2"> Update </button>
</center> </center>
</div> </div>
</div> </div>
</template>
{% endblock %} {% endblock %}

@ -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>

@ -7,7 +7,7 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('get_share_nodes', views.JsTreeView),
path('', views.DashboardView), path('', views.DashboardView),
path('meta', views.DashboardMeta),
] ]

@ -9,72 +9,22 @@ from django.shortcuts import render
from django.conf import settings from django.conf import settings
from cvat.apps.authentication.decorators import login_required from cvat.apps.authentication.decorators import login_required
from cvat.apps.engine.models import Task as TaskModel, Job as JobModel from cvat.settings.base import JS_3RDPARTY, CSS_3RDPARTY
from cvat.settings.base import JS_3RDPARTY
import os import os
def ScanNode(directory):
if '..' in directory.split(os.path.sep):
return HttpResponseBadRequest('Permission Denied')
act_dir = os.path.normpath(settings.SHARE_ROOT + directory)
result = []
nodes = os.listdir(act_dir)
files = filter(os.path.isfile, map(lambda f: os.path.join(act_dir, f), nodes))
dirs = filter(os.path.isdir, map(lambda d: os.path.join(act_dir, d), nodes))
for d in dirs:
name = os.path.basename(d)
children = len(os.listdir(d)) > 0
node = {'id': directory + name + '/', 'text': name, 'children': children}
result.append(node)
for f in files:
name = os.path.basename(f)
node = {'id': directory + name, 'text': name, "icon" : "jstree-file"}
result.append(node)
return result
@login_required
def JsTreeView(request):
node_id = None
if 'id' in request.GET:
node_id = request.GET['id']
if node_id is None or node_id == '#':
node_id = '/'
response = [{"id": node_id, "text": node_id, "children": ScanNode(node_id)}]
else:
response = ScanNode(node_id)
return JsonResponse(response, safe=False,
json_dumps_params=dict(ensure_ascii=False))
@login_required @login_required
def DashboardView(request): def DashboardView(request):
query_name = request.GET['search'] if 'search' in request.GET else None
query_job = int(request.GET['jid']) if 'jid' in request.GET and request.GET['jid'].isdigit() else None
task_list = None
if query_job is not None and JobModel.objects.filter(pk = query_job).exists():
task_list = [JobModel.objects.select_related('segment__task').get(pk = query_job).segment.task]
else:
task_list = list(TaskModel.objects.prefetch_related('segment_set__job_set').order_by('-created_date').all())
if query_name is not None:
task_list = list(filter(lambda x: query_name.lower() in x.name.lower(), task_list))
task_list = list(filter(lambda task: request.user.has_perm(
'engine.task.access', task), task_list))
return render(request, 'dashboard/dashboard.html', { return render(request, 'dashboard/dashboard.html', {
'data': task_list, 'js_3rdparty': JS_3RDPARTY.get('dashboard', []),
'css_3rdparty': CSS_3RDPARTY.get('dashboard', []),
})
@login_required
def DashboardMeta(request):
return JsonResponse({
'max_upload_size': settings.LOCAL_LOAD_MAX_FILES_SIZE, 'max_upload_size': settings.LOCAL_LOAD_MAX_FILES_SIZE,
'max_upload_count': settings.LOCAL_LOAD_MAX_FILES_COUNT, 'max_upload_count': settings.LOCAL_LOAD_MAX_FILES_COUNT,
'base_url': "{0}://{1}/".format(request.scheme, request.get_host()), 'base_url': "{0}://{1}/".format(request.scheme, request.get_host()),
'share_path': os.getenv('CVAT_SHARE_URL', default=r'${cvat_root}/share'), 'share_path': os.getenv('CVAT_SHARE_URL', default=r'${cvat_root}/share'),
'js_3rdparty': JS_3RDPARTY.get('dashboard', []),
}) })

@ -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))

@ -5,5 +5,4 @@
from cvat.settings.base import JS_3RDPARTY from cvat.settings.base import JS_3RDPARTY
JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['documentation/js/shortcuts.js'] JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['documentation/js/dashboardPlugin.js']

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 678 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 718 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save