Merge branch 'develop' of https://github.com/opencv/cvat into upstream/develop

main
Maya 6 years ago
commit 3236546907

1
.gitattributes vendored

@ -15,7 +15,6 @@ LICENSE text
*.conf text
*.mimetypes text
*.sh text eol=lf
components/openvino/eula.cfg text eol=lf
*.avi binary
*.bmp binary

@ -23,7 +23,7 @@
/datumaro/ @zhiltsov-max
/cvat/apps/dataset_manager/ @zhiltsov-max
# Advanced components (e.g. OpenVINO)
# Advanced components (e.g. analytics)
/components/ @azhavoro
# Infrastructure

1
.gitignore vendored

@ -7,7 +7,6 @@
/.env
/keys
/logs
/components/openvino/*.tgz
/profiles
/ssh/*
!/ssh/README.md

@ -20,7 +20,7 @@ persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
load-plugins=pylint_django
# Use multiple processes to speed up Pylint.
jobs=1
@ -66,8 +66,8 @@ enable= E0001,E0100,E0101,E0102,E0103,E0104,E0105,E0106,E0107,E0110,
W0122,W0124,W0150,W0199,W0221,W0222,W0233,W0404,W0410,W0601,
W0602,W0604,W0611,W0612,W0622,W0623,W0702,W0705,W0711,W1300,
W1301,W1302,W1303,,W1305,W1306,W1307
R0102,R0201,R0202,R0203
R0102,R0202,R0203
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this

@ -11,6 +11,10 @@
"selector-type-no-unknown": [true, {
"ignoreTypes": ["first-child"]
}]
}
},
"ignoreFiles": [
"**/*.js",
"**/*.ts",
"**/*.py"
]
}

@ -5,12 +5,29 @@ language: python
python:
- "3.5"
cache:
npm: true
directories:
- ~/.cache
addons:
apt:
packages:
- libgconf-2-4
services:
- docker
env:
- CONTAINER_COVERAGE_DATA_DIR="/coverage_data"
HOST_COVERAGE_DATA_DIR="${TRAVIS_BUILD_DIR}"
DJANGO_SU_NAME="admin"
DJANGO_SU_EMAIL="admin@localhost.company"
DJANGO_SU_PASSWORD="12qwaszx"
NODE_VERSION="12"
before_install:
- nvm install ${NODE_VERSION}
before_script:
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml build
@ -20,6 +37,13 @@ script:
# FIXME: Git package and application name conflict in PATH and try to leave only one python test execution
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps && coverage run -a manage.py test --pattern="_test*.py" cvat/apps/dataset_manager/tests cvat/apps/engine/tests utils/cli && coverage run -a manage.py test datumaro/ && mv .coverage ${CONTAINER_COVERAGE_DATA_DIR}'
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm install && cd ../cvat-core && npm install && npm run test && coveralls-lcov -v -n ./reports/coverage/lcov.info > ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json'
# Up all containers
- docker-compose up -d
# Create superuser
- docker exec -it cvat bash -ic "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
# Install Cypress and run tests
- cd ./tests && npm install
- $(npm bin)/cypress run --headless --browser chrome && cd ..
after_success:
# https://coveralls-python.readthedocs.io/en/latest/usage/multilang.html

@ -27,7 +27,7 @@
"request": "launch",
"stopOnEntry": false,
"justMyCode": false,
"pythonPath": "${config:python.pythonPath}",
"pythonPath": "${command:python.interpreterPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"runserver",
@ -58,7 +58,7 @@
"request": "launch",
"stopOnEntry": false,
"justMyCode": false,
"pythonPath": "${config:python.pythonPath}",
"pythonPath": "${command:python.interpreterPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"rqworker",
@ -77,7 +77,7 @@
"request": "launch",
"stopOnEntry": false,
"justMyCode": false,
"pythonPath": "${config:python.pythonPath}",
"pythonPath": "${command:python.interpreterPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"rqscheduler",
@ -93,7 +93,7 @@
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"pythonPath":"${command:python.interpreterPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"rqworker",
@ -112,7 +112,7 @@
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"pythonPath": "${command:python.interpreterPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"update_git_states"
@ -128,7 +128,7 @@
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"pythonPath": "${command:python.interpreterPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"migrate"
@ -144,7 +144,7 @@
"request": "launch",
"justMyCode": false,
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"pythonPath": "${command:python.interpreterPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"test",

@ -4,14 +4,16 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0-beta] - Unreleased
## [1.1.0] - Unreleased
### Added
- Source type support for tags, shapes and tracks (<https://github.com/opencv/cvat/pull/1192>)
- Source type support for CVAT Dumper/Loader (<https://github.com/opencv/cvat/pull/1192>)
- Intelligent polygon editing (<https://github.com/opencv/cvat/pull/1921>)
- Siammask tracker as DL serverless function (<https://github.com/opencv/cvat/pull/1988>)
- [Datumaro] Added model info and source info commands (<https://github.com/opencv/cvat/pull/1973>)
- [Datumaro] Dataset statistics (<https://github.com/opencv/cvat/pull/1668>)
- [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695)
### Changed
- Smaller object details (<https://github.com/opencv/cvat/pull/1877>)
- Shape coordinates are rounded to 2 digits in dumped annotations (<https://github.com/opencv/cvat/pull/1970>)
- COCO format does not produce polygon points for bbox annotations (<https://github.com/opencv/cvat/pull/1953>)
### Deprecated
-
@ -19,6 +21,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
-
### Fixed
- Issue loading openvino models for semi-automatic and automatic annotation (<https://github.com/opencv/cvat/pull/1996>)
- Basic functions of CVAT works without activated nuclio dashboard
### Security
-
## [1.1.0-beta] - 2020-08-03
### Added
- DL models as serverless functions (<https://github.com/opencv/cvat/pull/1767>)
- Source type support for tags, shapes and tracks (<https://github.com/opencv/cvat/pull/1192>)
- Source type support for CVAT Dumper/Loader (<https://github.com/opencv/cvat/pull/1192>)
- Intelligent polygon editing (<https://github.com/opencv/cvat/pull/1921>)
- Support creating multiple jobs for each task through python cli (https://github.com/opencv/cvat/pull/1950)
- python cli over https (<https://github.com/opencv/cvat/pull/1942>)
- Error message when plugins weren't able to initialize instead of infinite loading (<https://github.com/opencv/cvat/pull/1966>)
- Ability to change user password (<https://github.com/opencv/cvat/pull/1954>)
### Changed
- Smaller object details (<https://github.com/opencv/cvat/pull/1877>)
- `COCO` format does not convert bboxes to polygons on export (<https://github.com/opencv/cvat/pull/1953>)
- It is impossible to submit a DL model in OpenVINO format using UI. Now you can deploy new models on the server using serverless functions (<https://github.com/opencv/cvat/pull/1767>)
- Files and folders under share path are now alphabetically sorted
### Removed
- Removed OpenVINO and CUDA components because they are not necessary anymore (<https://github.com/opencv/cvat/pull/1767>)
- Removed the old UI code (<https://github.com/opencv/cvat/pull/1964>)
### Fixed
- Some objects aren't shown on canvas sometimes. For example after propagation on of objects is invisible (<https://github.com/opencv/cvat/pull/1834>)
- CVAT doesn't offer to restore state after an error (<https://github.com/opencv/cvat/pull/1874>)
@ -33,10 +63,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Points are dublicated during polygon interpolation sometimes (<https://github.com/opencv/cvat/pull/1892>)
- When redraw a shape with activated autobordering, previous points are visible (<https://github.com/opencv/cvat/pull/1892>)
- No mapping between side object element and context menu in some attributes (<https://github.com/opencv/cvat/pull/1923>)
- Interpolated shapes exported as `keyframe = True` (https://github.com/opencv/cvat/pull/1937)
### Security
-
- Interpolated shapes exported as `keyframe = True` (<https://github.com/opencv/cvat/pull/1937>)
- Stylelint filetype scans (<https://github.com/opencv/cvat/pull/1952>)
- Fixed toolip closing issue (<https://github.com/opencv/cvat/pull/1955>)
- Clearing frame cache when close a task (<https://github.com/opencv/cvat/pull/1966>)
- Increase rate of throttling policy for unauthenticated users (<https://github.com/opencv/cvat/pull/1969>)
## [1.1.0-alpha] - 2020-06-30
### Added
@ -77,12 +108,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Colorized object items in the side panel (<https://github.com/opencv/cvat/pull/1753>)
- [Datumaro] Annotation-less files are not generated anymore in COCO format, unless tasks explicitly requested (<https://github.com/opencv/cvat/pull/1799>)
### Deprecated
-
### Removed
-
### Fixed
- Problem with exported frame stepped image task (<https://github.com/opencv/cvat/issues/1613>)
- Fixed dataset filter item representation for imageless dataset items (<https://github.com/opencv/cvat/pull/1593>)

File diff suppressed because one or more lines are too long

@ -76,33 +76,6 @@ RUN adduser --shell /bin/bash --disabled-password --gecos "" ${USER} && \
COPY components /tmp/components
# OpenVINO toolkit support
ARG OPENVINO_TOOLKIT
ENV OPENVINO_TOOLKIT=${OPENVINO_TOOLKIT}
ENV REID_MODEL_DIR=${HOME}/reid
RUN if [ "$OPENVINO_TOOLKIT" = "yes" ]; then \
/tmp/components/openvino/install.sh && \
mkdir ${REID_MODEL_DIR} && \
curl https://download.01.org/openvinotoolkit/2018_R5/open_model_zoo/person-reidentification-retail-0079/FP32/person-reidentification-retail-0079.xml -o reid/reid.xml && \
curl 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
# Tensorflow annotation support
ARG TF_ANNOTATION
ENV TF_ANNOTATION=${TF_ANNOTATION}
ENV TF_ANNOTATION_MODEL_PATH=${HOME}/rcnn/inference_graph
RUN if [ "$TF_ANNOTATION" = "yes" ]; then \
bash -i /tmp/components/tf_annotation/install.sh; \
fi
# Auto segmentation support. by Mohammad
ARG AUTO_SEGMENTATION
ENV AUTO_SEGMENTATION=${AUTO_SEGMENTATION}
ENV AUTO_SEGMENTATION_PATH=${HOME}/Mask_RCNN
RUN if [ "$AUTO_SEGMENTATION" = "yes" ]; then \
bash -i /tmp/components/auto_segmentation/install.sh; \
fi
# Install and initialize CVAT, copy all necessary files
COPY cvat/requirements/ /tmp/requirements/
COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/
@ -110,24 +83,6 @@ RUN python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGUR
# pycocotools package is impossible to install with its dependencies by one pip install command
RUN python3 -m pip install --no-cache-dir pycocotools==2.0.0
# CUDA support
ARG CUDA_SUPPORT
ENV CUDA_SUPPORT=${CUDA_SUPPORT}
RUN if [ "$CUDA_SUPPORT" = "yes" ]; then \
/tmp/components/cuda/install.sh; \
fi
# TODO: CHANGE URL
ARG WITH_DEXTR
ENV WITH_DEXTR=${WITH_DEXTR}
ENV DEXTR_MODEL_DIR=${HOME}/dextr
RUN if [ "$WITH_DEXTR" = "yes" ]; then \
mkdir ${DEXTR_MODEL_DIR} -p && \
curl https://download.01.org/openvinotoolkit/models_contrib/cvat/dextr_model_v1.zip -o ${DEXTR_MODEL_DIR}/dextr.zip && \
7z e ${DEXTR_MODEL_DIR}/dextr.zip -o${DEXTR_MODEL_DIR} && rm ${DEXTR_MODEL_DIR}/dextr.zip; \
fi
ARG CLAM_AV
ENV CLAM_AV=${CLAM_AV}
RUN if [ "$CLAM_AV" = "yes" ]; then \
@ -151,9 +106,6 @@ COPY datumaro/ ${HOME}/datumaro
RUN python3 -m pip install --no-cache-dir -r ${HOME}/datumaro/requirements.txt
# Binary option is necessary to correctly apply the patch on Windows platform.
# https://unix.stackexchange.com/questions/239364/how-to-fix-hunk-1-failed-at-1-different-line-endings-message
RUN patch --binary -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch
RUN chown -R ${USER}:${USER} .
# RUN all commands below as 'django' user

@ -1,11 +1,11 @@
FROM cvat
FROM cvat/server
ENV DJANGO_CONFIGURATION=testing
USER root
RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \
curl https://deb.nodesource.com/setup_9.x | bash - && \
curl https://deb.nodesource.com/setup_12.x | bash - && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -yq \
apt-utils \
@ -23,19 +23,4 @@ RUN gem install coveralls-lcov
COPY .coveragerc .
# RUN all commands below as 'django' user
USER ${USER}
RUN mkdir -p tests && cd tests && npm install \
eslint \
eslint-detailed-reporter \
karma \
karma-chrome-launcher \
karma-coveralls \
karma-coverage \
karma-junit-reporter \
karma-qunit \
qunit; \
echo "export PATH=~/tests/node_modules/.bin:${PATH}" >> ~/.bashrc;
ENTRYPOINT []

@ -4,14 +4,13 @@
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/840351da141e4eaeac6476fd19ec0a33)](https://app.codacy.com/app/cvat/cvat?utm_source=github.com&utm_medium=referral&utm_content=opencv/cvat&utm_campaign=Badge_Grade_Dashboard)
[![Gitter chat](https://badges.gitter.im/opencv-cvat/gitter.png)](https://gitter.im/opencv-cvat)
[![Coverage Status](https://coveralls.io/repos/github/opencv/cvat/badge.svg?branch=)](https://coveralls.io/github/opencv/cvat?branch=develop)
[![codebeat badge](https://codebeat.co/badges/53cd0d16-fddc-46f8-903c-f43ed9abb6dd)](https://codebeat.co/projects/github-com-opencv-cvat-develop)
[![DOI](https://zenodo.org/badge/139156354.svg)](https://zenodo.org/badge/latestdoi/139156354)
CVAT 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.
Try it online [cvat.org](https://cvat.org).
and UX decisions are based on feedbacks from professional data
annotation team. Try it online [cvat.org](https://cvat.org).
![CVAT screenshot](cvat/apps/documentation/static/documentation/images/cvat.jpg)
@ -58,10 +57,18 @@ via its command line tool and Python library.
| [MOT](https://motchallenge.net/) | X | X |
| [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X |
## 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/)
## Deep learning models for automatic labeling
| Name | Type | Framework |
| ------------------------------------------------------------------------------------------------------- | ---------- | ---------- |
| [Deep Extreme Cut](/serverless/openvino/dextr/nuclio) | interactor | OpenVINO |
| [Faster RCNN](/serverless/tensorflow/faster_rcnn_inception_v2_coco/nuclio) | detector | TensorFlow |
| [Mask RCNN](/serverless/openvino/omz/public/mask_rcnn_inception_resnet_v2_atrous_coco/nuclio) | detector | OpenVINO |
| [YOLO v3](/serverless/openvino/omz/public/yolo-v3-tf/nuclio) | detector | OpenVINO |
| [Text detection v4](/serverless/openvino/omz/intel/text-detection-0004/nuclio) | detector | OpenVINO |
| [Semantic segmentation for ADAS](/serverless/openvino/omz/intel/semantic-segmentation-adas-0001/nuclio) | detector | OpenVINO |
| [Mask RCNN](/serverless/tensorflow/matterport/mask_rcnn/nuclio) | detector | TensorFlow |
| [Object reidentification](/serverless/openvino/omz/intel/person-reidentification-retail-300/nuclio) | reid | OpenVINO |
## Online demo: [cvat.org](https://cvat.org)
@ -71,7 +78,6 @@ are visible to users.
Disabled features:
- [Analytics: management and monitoring of data annotation team](/components/analytics/README.md)
- [Support for NVIDIA GPUs](/components/cuda/README.md)
Limitations:
- No more than 10 tasks per user
@ -105,3 +111,8 @@ If you are not sure or just want to browse other users common questions,
Other ways to ask questions and get our support:
* [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow*
* [Forum on Intel Developer Zone](https://software.intel.com/en-us/forums/computer-vision)
## 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/)

@ -31,7 +31,7 @@ services:
cvat_kibana_setup:
container_name: cvat_kibana_setup
image: cvat
image: cvat/server
volumes: ['./components/analytics/kibana:/home/django/kibana:ro']
depends_on: ['cvat']
working_dir: '/home/django'
@ -63,7 +63,7 @@ services:
DJANGO_LOG_SERVER_PORT: 5000
DJANGO_LOG_VIEWER_HOST: kibana
DJANGO_LOG_VIEWER_PORT: 5601
no_proxy: kibana,logstash,${no_proxy}
no_proxy: kibana,logstash,nuclio,${no_proxy}
volumes:
cvat_events:

@ -1,38 +0,0 @@
## [Keras+Tensorflow Mask R-CNN Segmentation](https://github.com/matterport/Mask_RCNN)
### What is it?
- This application allows you automatically to segment many various objects on images.
- It's based on Feature Pyramid Network (FPN) and a ResNet101 backbone.
- It uses a pre-trained model on MS COCO dataset
- It supports next classes (use them in "labels" row):
```python
'BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
'bus', 'train', 'truck', 'boat', 'traffic light',
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird',
'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear',
'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie',
'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
'kite', 'baseball bat', 'baseball glove', 'skateboard',
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',
'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed',
'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
'keyboard', 'cell phone', 'microwave', 'oven', 'toaster',
'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
'teddy bear', 'hair drier', 'toothbrush'.
```
- Component adds "Run Auto Segmentation" button into dashboard.
### Build docker image
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/auto_segmentation/docker-compose.auto_segmentation.yml build
```
### Run docker container
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/auto_segmentation/docker-compose.auto_segmentation.yml up -d
```

@ -1,13 +0,0 @@
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
version: "2.3"
services:
cvat:
build:
context: .
args:
AUTO_SEGMENTATION: "yes"

@ -1,13 +0,0 @@
#!/bin/bash
#
set -e
MASK_RCNN_URL=https://github.com/matterport/Mask_RCNN
cd ${HOME} && \
git clone ${MASK_RCNN_URL}.git && \
curl -L ${MASK_RCNN_URL}/releases/download/v2.0/mask_rcnn_coco.h5 -o Mask_RCNN/mask_rcnn_coco.h5
# TODO remove useless files
# tensorflow and Keras are installed globally

@ -1,41 +0,0 @@
## [NVIDIA CUDA Toolkit](https://developer.nvidia.com/cuda-toolkit)
### Requirements
* NVIDIA GPU with a compute capability [3.0 - 7.2]
* Latest GPU driver
### Installation
#### Install the latest driver for your graphics card
```bash
sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt-get update
sudo apt-cache search nvidia-* # find latest nvidia driver
sudo apt-get --no-install-recommends install nvidia-* # install the nvidia driver
sudo apt-get --no-install-recommends install mesa-common-dev
sudo apt-get --no-install-recommends install freeglut3-dev
sudo apt-get --no-install-recommends install nvidia-modprobe
```
#### Reboot your PC and verify installation by `nvidia-smi` command.
#### Install [Nvidia-Docker](https://github.com/NVIDIA/nvidia-docker)
Please be sure that installation was successful.
```bash
docker info | grep 'Runtimes' # output should contains 'nvidia'
```
### Build docker image
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml build
```
### Run docker container
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/cuda/docker-compose.cuda.yml up -d
```

@ -1,23 +0,0 @@
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
version: "2.3"
services:
cvat:
build:
context: .
args:
CUDA_SUPPORT: "yes"
runtime: "nvidia"
environment:
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: compute,utility
# That environment variable is used by the Nvidia Container Runtime.
# The Nvidia Container Runtime parses this as:
# :space:: logical OR
# ,: Logical AND
# https://gitlab.com/nvidia/container-images/cuda/issues/31#note_149432780
NVIDIA_REQUIRE_CUDA: "cuda>=10.0 brand=tesla,driver>=384,driver<385 brand=tesla,driver>=410,driver<411"

@ -1,38 +0,0 @@
#!/bin/bash
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
set -e
NVIDIA_GPGKEY_SUM=d1be581509378368edeec8c1eb2958702feedf3bc3d17011adbf24efacce4ab5 && \
NVIDIA_GPGKEY_FPR=ae09fe4bbd223a84b2ccfce3f60f4b3d7fa2af80 && \
apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub && \
apt-key adv --export --no-emit-version -a $NVIDIA_GPGKEY_FPR | tail -n +5 > cudasign.pub && \
echo "$NVIDIA_GPGKEY_SUM cudasign.pub" | sha256sum -c --strict - && rm cudasign.pub && \
echo "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/cuda.list && \
echo "deb http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64 /" > /etc/apt/sources.list.d/nvidia-ml.list
CUDA_VERSION=10.0.130
NCCL_VERSION=2.5.6
CUDNN_VERSION=7.6.5.32
CUDA_PKG_VERSION="10-0=$CUDA_VERSION-1"
echo 'export PATH=/usr/local/nvidia/bin:/usr/local/cuda/bin:${PATH}' >> ${HOME}/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64:${LD_LIBRARY_PATH}' >> ${HOME}/.bashrc
apt-get update && apt-get install -y --no-install-recommends --allow-unauthenticated \
cuda-cudart-$CUDA_PKG_VERSION \
cuda-compat-10-0 \
cuda-libraries-$CUDA_PKG_VERSION \
cuda-nvtx-$CUDA_PKG_VERSION \
libnccl2=$NCCL_VERSION-1+cuda10.0 \
libcudnn7=$CUDNN_VERSION-1+cuda10.0 && \
ln -s cuda-10.0 /usr/local/cuda && \
apt-mark hold libnccl2 libcudnn7 && \
rm -rf /var/lib/apt/lists/* \
/etc/apt/sources.list.d/nvidia-ml.list /etc/apt/sources.list.d/cuda.list
python3 -m pip uninstall -y tensorflow
python3 -m pip install --no-cache-dir tensorflow-gpu==1.15.2

@ -1,45 +0,0 @@
## [Intel OpenVINO toolkit](https://software.intel.com/en-us/openvino-toolkit)
### Requirements
* Intel Core with 6th generation and higher or Intel Xeon CPUs.
### Preparation
- Download the latest [OpenVINO toolkit](https://software.intel.com/en-us/openvino-toolkit) .tgz installer
(offline or online) for Ubuntu platforms. Note that OpenVINO does not maintain forward compatability between
Intermediate Representations (IRs), so the version of OpenVINO in CVAT and the version used to translate the
models needs to be the same.
- Put downloaded file into ```cvat/components/openvino```.
- Accept EULA in the `cvat/components/openvino/eula.cfg` file.
### Build docker image
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml build
```
### Run docker container
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/openvino/docker-compose.openvino.yml up -d
```
You should be able to login and see the web interface for CVAT now, complete with the new "Model Manager" button.
### OpenVINO Models
Clone the [Open Model Zoo](https://github.com/opencv/open_model_zoo). `$ git clone https://github.com/opencv/open_model_zoo.git`
Install the appropriate libraries. Currently that command would be `$ pip install -r open_model_zoo/tools/downloader/requirements.in`
Download the models using `downloader.py` file in `open_model_zoo/tools/downloader/`.
The `--name` command can be used to specify specific models.
The `--print_all` command can print all the available models.
Specific models that are already integrated into Cvat can be found [here](https://github.com/opencv/cvat/tree/develop/utils/open_model_zoo).
From the web user interface in CVAT, upload the models using the model manager.
You'll need to include the xml and bin file from the model downloader.
You'll need to include the python and JSON files from scratch or by using the ones in the CVAT libary.
See [here](https://github.com/opencv/cvat/tree/develop/cvat/apps/auto_annotation) for instructions for creating custom
python and JSON files.

@ -1,13 +0,0 @@
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
version: "2.3"
services:
cvat:
build:
context: .
args:
OPENVINO_TOOLKIT: "yes"

@ -1,3 +0,0 @@
# Accept actual EULA from openvino installation archive. Valid values are: {accept, decline}
ACCEPT_EULA=accept

@ -1,41 +0,0 @@
#!/bin/bash
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
set -e
if [[ `lscpu | grep -o "GenuineIntel"` != "GenuineIntel" ]]; then
echo "OpenVINO supports only Intel CPUs"
exit 1
fi
if [[ `lscpu | grep -o "sse4" | head -1` != "sse4" ]] && [[ `lscpu | grep -o "avx2" | head -1` != "avx2" ]]; then
echo "OpenVINO expects your CPU to support SSE4 or AVX2 instructions"
exit 1
fi
cd /tmp/components/openvino
tar -xzf `ls | grep "openvino_toolkit"`
cd `ls -d */ | grep "openvino_toolkit"`
apt-get update && apt-get --no-install-recommends install -y sudo cpio && \
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
./install.sh -s silent.cfg
cd /tmp/components && rm openvino -r
if [ -f "/opt/intel/computer_vision_sdk/bin/setupvars.sh" ]; then
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

@ -1,41 +0,0 @@
## [Tensorflow Object Detector](https://github.com/tensorflow/models/tree/master/research/object_detection)
### What is it?
* This application allows you automatically to annotate many various objects on images.
* It uses [Faster RCNN Inception Resnet v2 Atrous Coco Model](http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz) from [tensorflow detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md)
* It can work on CPU (with Tensorflow or OpenVINO) or GPU (with Tensorflow GPU).
* It supports next classes (just specify them in "labels" row):
```
'surfboard', 'car', 'skateboard', 'boat', 'clock',
'cat', 'cow', 'knife', 'apple', 'cup', 'tv',
'baseball_bat', 'book', 'suitcase', 'tennis_racket',
'stop_sign', 'couch', 'cell_phone', 'keyboard',
'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant',
'snowboard', 'bed', 'vase', 'teddy_bear',
'toaster', 'wine_glass', 'traffic_light',
'broccoli', 'backpack', 'carrot', 'potted_plant',
'donut', 'umbrella', 'parking_meter', 'bottle',
'sandwich', 'motorcycle', 'bear', 'banana',
'person', 'scissors', 'elephant', 'dining_table',
'toothbrush', 'toilet', 'skis', 'bowl', 'sheep',
'refrigerator', 'oven', 'microwave', 'train',
'orange', 'mouse', 'laptop', 'bench', 'bicycle',
'fork', 'kite', 'zebra', 'baseball_glove', 'bus',
'spoon', 'horse', 'handbag', 'pizza', 'sports_ball',
'airplane', 'hair_drier', 'hot_dog', 'remote',
'sink', 'dog', 'bird', 'giraffe', 'chair'.
```
* Component adds "Run TF Annotation" button into dashboard.
### Build docker image
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/tf_annotation/docker-compose.tf_annotation.yml build
```
### Run docker container
```bash
# From project root directory
docker-compose -f docker-compose.yml -f components/tf_annotation/docker-compose.tf_annotation.yml up -d
```

@ -1,13 +0,0 @@
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
version: "2.3"
services:
cvat:
build:
context: .
args:
TF_ANNOTATION: "yes"

@ -1,15 +0,0 @@
#!/bin/bash
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
set -e
cd ${HOME} && \
curl http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28.tar.gz -o model.tar.gz && \
tar -xzf model.tar.gz && rm model.tar.gz && \
mv faster_rcnn_inception_resnet_v2_atrous_coco_2018_01_28 ${HOME}/rcnn && cd ${HOME} && \
mv rcnn/frozen_inference_graph.pb rcnn/inference_graph.pb
# tensorflow is installed globally

@ -3223,9 +3223,9 @@
"dev": true
},
"elliptic": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz",
"integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==",
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true,
"requires": {
"bn.js": "^4.4.0",

@ -189,7 +189,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
private onEditDone(state: any, points: number[]): void {
if (state && points) {
if (state && points) {
const event: CustomEvent = new CustomEvent('canvas.edited', {
bubbles: false,
cancelable: true,
@ -1732,7 +1732,12 @@ export class CanvasViewImpl implements CanvasView, Listener {
private addText(state: any): SVG.Text {
const { undefinedAttrValue } = this.configuration;
const { label, clientID, attributes, source } = state;
const {
label,
clientID,
attributes,
source,
} = state;
const attrNames = label.attributes.reduce((acc: any, val: any): void => {
acc[val.id] = val.name;
return acc;

@ -1,6 +1,5 @@
docs
node_modules
reports
package-lock.json
yarn.lock
dist

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "cvat-core",
"version": "3.1.2",
"version": "3.4.0",
"description": "Part of Computer Vision Tool which presents an interface for client-side integration",
"main": "babel.config.js",
"scripts": {
@ -27,7 +27,7 @@
"eslint-plugin-security": "^1.4.0",
"jest": "^24.8.0",
"jest-junit": "^6.4.0",
"jsdoc": "^3.6.2",
"jsdoc": "^3.6.4",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2"
},

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Intel Corporation
* Copyright (C) 2019-2020 Intel Corporation
* SPDX-License-Identifier: MIT
*/
@ -12,6 +12,7 @@
(() => {
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const lambdaManager = require('./lambda-manager');
const {
isBoolean,
isInteger,
@ -20,10 +21,7 @@
checkFilter,
} = require('./common');
const {
TaskStatus,
TaskMode,
} = require('./enums');
const { TaskStatus, TaskMode } = require('./enums');
const User = require('./user');
const { AnnotationFormats } = require('./annotation-formats.js');
@ -54,6 +52,13 @@
cvat.plugins.list.implementation = PluginRegistry.list;
cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat);
cvat.lambda.list.implementation = lambdaManager.list.bind(lambdaManager);
cvat.lambda.run.implementation = lambdaManager.run.bind(lambdaManager);
cvat.lambda.call.implementation = lambdaManager.call.bind(lambdaManager);
cvat.lambda.cancel.implementation = lambdaManager.cancel.bind(lambdaManager);
cvat.lambda.listen.implementation = lambdaManager.listen.bind(lambdaManager);
cvat.lambda.requests.implementation = lambdaManager.requests.bind(lambdaManager);
cvat.server.about.implementation = async () => {
const result = await serverProxy.server.about();
return result;
@ -88,6 +93,10 @@
await serverProxy.server.logout();
};
cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
};
cvat.server.authorized.implementation = async () => {
const result = await serverProxy.server.authorized();
return result;

@ -20,6 +20,7 @@ function build() {
const Statistics = require('./statistics');
const { Job, Task } = require('./session');
const { Attribute, Label } = require('./labels');
const MLModel = require('./ml-model');
const {
ShareFileType,
@ -30,6 +31,7 @@ function build() {
ObjectShape,
LogType,
HistoryActions,
RQStatus,
colors,
Source,
} = require('./enums');
@ -128,10 +130,10 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async userAgreements() {
const result = await PluginRegistry
.apiWrapper(cvat.server.userAgreements);
return result;
async userAgreements() {
const result = await PluginRegistry
.apiWrapper(cvat.server.userAgreements);
return result;
},
/**
@ -149,7 +151,15 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async register(username, firstName, lastName, email, password1, password2, userConfirmations) {
async register(
username,
firstName,
lastName,
email,
password1,
password2,
userConfirmations,
) {
const result = await PluginRegistry
.apiWrapper(cvat.server.register, username, firstName,
lastName, email, password1, password2, userConfirmations);
@ -183,6 +193,19 @@ function build() {
.apiWrapper(cvat.server.logout);
return result;
},
/**
* Method allows to change user password
* @method changePassword
* @async
* @memberof module:API.cvat.server
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError}
*/
async changePassword(oldPassword, newPassword1, newPassword2) {
const result = await PluginRegistry
.apiWrapper(cvat.server.changePassword, oldPassword, newPassword1, newPassword2);
return result;
},
/**
* Method allows to know whether you are authorized on the server
* @method authorized
@ -424,6 +447,119 @@ function build() {
return result;
},
},
/**
* Namespace is used for serverless functions management (mainly related with DL models)
* @namespace lambda
* @memberof module:API.cvat
*/
lambda: {
/**
* Method returns list of available serverless models
* @method list
* @async
* @memberof module:API.cvat.lambda
* @returns {module:API.cvat.classes.MLModel[]}
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async list() {
const result = await PluginRegistry
.apiWrapper(cvat.lambda.list);
return result;
},
/**
* Run long-time request for a function on a specific task
* @method run
* @async
* @memberof module:API.cvat.lambda
* @param {module:API.cvat.classes.Task} task task to be annotated
* @param {module:API.cvat.classes.MLModel} model model used to get annotation
* @param {object} [args] extra arguments
* @returns {string} requestID
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
async run(task, model, args) {
const result = await PluginRegistry
.apiWrapper(cvat.lambda.run, task, model, args);
return result;
},
/**
* Run short-time request for a function on a specific task
* @method call
* @async
* @memberof module:API.cvat.lambda
* @param {module:API.cvat.classes.Task} task task to be annotated
* @param {module:API.cvat.classes.MLModel} model model used to get annotation
* @param {object} [args] extra arguments
* @returns {string} requestID
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
async call(task, model, args) {
const result = await PluginRegistry
.apiWrapper(cvat.lambda.call, task, model, args);
return result;
},
/**
* Cancel running of a serverless function for a specific task
* @method cancel
* @async
* @memberof module:API.cvat.lambda
* @param {string} requestID
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ArgumentError}
*/
async cancel(requestID) {
const result = await PluginRegistry
.apiWrapper(cvat.lambda.cancel, requestID);
return result;
},
/**
* @callback onRequestStatusChange
* @param {string} status
* @param {number} progress
* @param {string} [message]
* @global
*/
/**
* Listen for a specific request
* @method listen
* @async
* @memberof module:API.cvat.lambda
* @param {string} requestID
* @param {onRequestStatusChange} onChange
* @throws {module:API.cvat.exceptions.ArgumentError}
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async listen(requestID, onChange) {
const result = await PluginRegistry
.apiWrapper(cvat.lambda.listen, requestID, onChange);
return result;
},
/**
* Get active lambda requests
* @method requests
* @async
* @memberof module:API.cvat.lambda
* @throws {module:API.cvat.exceptions.ServerError}
* @throws {module:API.cvat.exceptions.PluginError}
*/
async requests() {
const result = await PluginRegistry
.apiWrapper(cvat.lambda.requests);
return result;
},
},
/**
* Namespace to working with logs
* @namespace logger
@ -531,6 +667,7 @@ function build() {
ObjectShape,
LogType,
HistoryActions,
RQStatus,
colors,
Source,
},
@ -561,6 +698,7 @@ function build() {
Label,
Statistics,
ObjectState,
MLModel,
},
};
@ -569,6 +707,7 @@ function build() {
cvat.jobs = Object.freeze(cvat.jobs);
cvat.users = Object.freeze(cvat.users);
cvat.plugins = Object.freeze(cvat.plugins);
cvat.lambda = Object.freeze(cvat.lambda);
cvat.client = Object.freeze(cvat.client);
cvat.enums = Object.freeze(cvat.enums);

@ -34,6 +34,26 @@
COMPLETED: 'completed',
});
/**
* List of RQ statuses
* @enum {string}
* @name RQStatus
* @memberof module:API.cvat.enums
* @property {string} QUEUED 'queued'
* @property {string} STARTED 'started'
* @property {string} FINISHED 'finished'
* @property {string} FAILED 'failed'
* @property {string} UNKNOWN 'unknown'
* @readonly
*/
const RQStatus = Object.freeze({
QUEUED: 'queued',
STARTED: 'started',
FINISHED: 'finished',
FAILED: 'failed',
UNKNOWN: 'unknown',
});
/**
* Task modes
* @enum {string}
@ -232,6 +252,18 @@
REMOVED_OBJECT: 'Removed object',
});
/**
* Enum string values.
* @name ModelType
* @memberof module:API.cvat.enums
* @enum {string}
*/
const ModelType = {
DETECTOR: 'detector',
INTERACTOR: 'interactor',
TRACKER: 'tracker',
};
/**
* Array of hex colors
* @name colors
@ -255,7 +287,9 @@
ObjectType,
ObjectShape,
LogType,
ModelType,
HistoryActions,
RQStatus,
colors,
Source,
};

@ -605,10 +605,18 @@
};
}
function clear(taskID) {
if (taskID in frameDataCache) {
frameDataCache[taskID].frameBuffer.clear();
delete frameDataCache[taskID];
}
}
module.exports = {
FrameData,
getFrame,
getRanges,
getPreview,
clear,
};
})();

@ -0,0 +1,126 @@
/*
* Copyright (C) 2020 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/* global
require:false
*/
const serverProxy = require('./server-proxy');
const { ArgumentError } = require('./exceptions');
const { Task } = require('./session');
const MLModel = require('./ml-model');
const { RQStatus } = require('./enums');
class LambdaManager {
constructor() {
this.listening = {};
this.cachedList = null;
}
async list() {
if (Array.isArray(this.cachedList)) {
return [...this.cachedList];
}
const result = await serverProxy.lambda.list();
const models = [];
for (const model of result) {
models.push(new MLModel({
id: model.id,
name: model.name,
description: model.description,
framework: model.framework,
labels: [...model.labels],
type: model.kind,
}));
}
this.cachedList = models;
return models;
}
async run(task, model, args) {
if (!(task instanceof Task)) {
throw new ArgumentError(
`Argument task is expected to be an instance of Task class, but got ${typeof (task)}`,
);
}
if (!(model instanceof MLModel)) {
throw new ArgumentError(
`Argument model is expected to be an instance of MLModel class, but got ${typeof (model)}`,
);
}
if (args && typeof (args) !== 'object') {
throw new ArgumentError(
`Argument args is expected to be an object, but got ${typeof (model)}`,
);
}
const body = args;
body.task = task.id;
body.function = model.id;
const result = await serverProxy.lambda.run(body);
return result.id;
}
async call(task, model, args) {
const body = args;
body.task = task.id;
const result = await serverProxy.lambda.call(model.id, body);
return result;
}
async requests() {
const result = await serverProxy.lambda.requests();
return result.filter((request) => ['queued', 'started'].includes(request.status));
}
async cancel(requestID) {
if (typeof (requestID) !== 'string') {
throw new ArgumentError(`Request id argument is required to be a string. But got ${requestID}`);
}
if (this.listening[requestID]) {
clearTimeout(this.listening[requestID].timeout);
delete this.listening[requestID];
}
await serverProxy.lambda.cancel(requestID);
}
async listen(requestID, onUpdate) {
const timeoutCallback = async () => {
try {
this.listening[requestID].timeout = null;
const response = await serverProxy.lambda.status(requestID);
if (response.status === RQStatus.QUEUED || response.status === RQStatus.STARTED) {
onUpdate(response.status, response.progress || 0);
this.listening[requestID].timeout = setTimeout(timeoutCallback, 2000);
} else {
if (response.status === RQStatus.FINISHED) {
onUpdate(response.status, response.progress || 100);
} else {
onUpdate(response.status, response.progress || 0, response.exc_info || '');
}
delete this.listening[requestID];
}
} catch (error) {
onUpdate(RQStatus.UNKNOWN, 0, `Could not get a status of the request ${requestID}. ${error.toString()}`);
}
};
this.listening[requestID] = {
onUpdate,
timeout: setTimeout(timeoutCallback, 2000),
};
}
}
module.exports = new LambdaManager();

@ -0,0 +1,73 @@
/*
* Copyright (C) 2019-2020 Intel Corporation
* SPDX-License-Identifier: MIT
*/
/**
* Class representing a machine learning model
* @memberof module:API.cvat.classes
*/
class MLModel {
constructor(data) {
this._id = data.id;
this._name = data.name;
this._labels = data.labels;
this._framework = data.framework;
this._description = data.description;
this._type = data.type;
}
/**
* @returns {string}
* @readonly
*/
get id() {
return this._id;
}
/**
* @returns {string}
* @readonly
*/
get name() {
return this._name;
}
/**
* @returns {string[]}
* @readonly
*/
get labels() {
if (Array.isArray(this._labels)) {
return [...this._labels];
}
return [];
}
/**
* @returns {string}
* @readonly
*/
get framework() {
return this._framework;
}
/**
* @returns {string}
* @readonly
*/
get description() {
return this._description;
}
/**
* @returns {module:API.cvat.enums.ModelType}
* @readonly
*/
get type() {
return this._type;
}
}
module.exports = MLModel;

@ -162,7 +162,6 @@
response = await Axios.get(`${backendAPI}/restrictions/user-agreements`, {
proxy: config.proxy,
});
} catch (errorData) {
throw generateError(errorData);
}
@ -170,7 +169,15 @@
return response.data;
}
async function register(username, firstName, lastName, email, password1, password2, confirmations) {
async function register(
username,
firstName,
lastName,
email,
password1,
password2,
confirmations,
) {
let response = null;
try {
const data = JSON.stringify({
@ -239,6 +246,24 @@
Axios.defaults.headers.common.Authorization = '';
}
async function changePassword(oldPassword, newPassword1, newPassword2) {
try {
const data = JSON.stringify({
old_password: oldPassword,
new_password1: newPassword1,
new_password2:newPassword2,
});
await Axios.post(`${config.backendAPI}/auth/password/change`, data, {
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
} catch (errorData) {
throw generateError(errorData);
}
}
async function authorized() {
try {
await module.exports.users.getSelf();
@ -662,6 +687,96 @@
}
}
async function getLambdaFunctions() {
const { backendAPI } = config;
try {
const response = await Axios.get(`${backendAPI}/lambda/functions`, {
proxy: config.proxy,
});
return response.data;
} catch (errorData) {
throw generateError(errorData);
}
}
async function runLambdaRequest(body) {
const { backendAPI } = config;
try {
const response = await Axios.post(`${backendAPI}/lambda/requests`,
JSON.stringify(body), {
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
} catch (errorData) {
throw generateError(errorData);
}
}
async function callLambdaFunction(funId, body) {
const { backendAPI } = config;
try {
const response = await Axios.post(`${backendAPI}/lambda/functions/${funId}`,
JSON.stringify(body), {
proxy: config.proxy,
headers: {
'Content-Type': 'application/json',
},
});
return response.data;
} catch (errorData) {
throw generateError(errorData);
}
}
async function getLambdaRequests() {
const { backendAPI } = config;
try {
const response = await Axios.get(`${backendAPI}/lambda/requests`, {
proxy: config.proxy,
});
return response.data;
} catch (errorData) {
throw generateError(errorData);
}
}
async function getRequestStatus(requestID) {
const { backendAPI } = config;
try {
const response = await Axios.get(`${backendAPI}/lambda/requests/${requestID}`, {
proxy: config.proxy,
});
return response.data;
} catch (errorData) {
throw generateError(errorData);
}
}
async function cancelLambdaRequest(requestId) {
const { backendAPI } = config;
try {
await Axios.delete(
`${backendAPI}/lambda/requests/${requestId}`, {
method: 'DELETE',
},
);
} catch (errorData) {
throw generateError(errorData);
}
}
Object.defineProperties(this, Object.freeze({
server: {
value: Object.freeze({
@ -671,6 +786,7 @@
exception,
login,
logout,
changePassword,
authorized,
register,
request: serverRequest,
@ -731,6 +847,18 @@
}),
writable: false,
},
lambda: {
value: Object.freeze({
list: getLambdaFunctions,
status: getRequestStatus,
requests: getLambdaRequests,
run: runLambdaRequest,
call: callLambdaFunction,
cancel: cancelLambdaRequest,
}),
writable: false,
},
}));
}
}

@ -11,7 +11,12 @@
const PluginRegistry = require('./plugins');
const loggerStorage = require('./logger-storage');
const serverProxy = require('./server-proxy');
const { getFrame, getRanges, getPreview } = require('./frames');
const {
getFrame,
getRanges,
getPreview,
clear: clearFrames,
} = require('./frames');
const { ArgumentError } = require('./exceptions');
const { TaskStatus } = require('./enums');
const { Label } = require('./labels');
@ -1612,6 +1617,7 @@
};
Task.prototype.close.implementation = function closeTask() {
clearFrames(this.id);
for (const job of this.jobs) {
closeSession(job);
}

@ -1,6 +1,6 @@
{
"name": "cvat-data",
"version": "1.0.0",
"version": "1.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "cvat-data",
"version": "1.0.0",
"version": "1.0.1",
"description": "",
"main": "src/js/cvat-data.js",
"devDependencies": {

@ -295,7 +295,26 @@ class FrameProvider {
worker.terminate();
};
worker.onmessage = (event) => {
worker.onmessage = async (event) => {
if (event.data.isRaw) {
// safary doesn't support createImageBitmap
// there is a way to polyfill it with using document.createElement
// but document.createElement doesn't work in worker
// so, we get raw data and decode it here, no other way
const createImageBitmap = async function(blob) {
return new Promise((resolve,reject) => {
let img = document.createElement('img');
img.addEventListener('load', function() {
resolve(this);
});
img.src = URL.createObjectURL(blob);
});
};
event.data.data = await createImageBitmap(event.data.data);
}
this._frames[event.data.index] = event.data.data;
if (this._decodingBlocks[`${start}:${end}`].resolveCallback) {

@ -20,13 +20,22 @@ onmessage = (e) => {
const fileIndex = index++;
if (fileIndex <= end) {
_zip.file(relativePath).async('blob').then((fileData) => {
createImageBitmap(fileData).then((img) => {
if (self.createImageBitmap) {
createImageBitmap(fileData).then((img) => {
postMessage({
fileName: relativePath,
index: fileIndex,
data: img,
});
});
} else {
postMessage({
fileName: relativePath,
index: fileIndex,
data: img,
data: fileData,
isRaw: true,
});
});
}
});
}
});

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.6.4",
"version": "1.7.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -5139,7 +5139,6 @@
"browserify-rsa": "^4.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.2",
"elliptic": "^6.0.0",
"inherits": "^2.0.1",
"parse-asn1": "^5.0.0"
}
@ -5682,8 +5681,7 @@
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
"requires": {
"bn.js": "^4.1.0",
"elliptic": "^6.0.0"
"bn.js": "^4.1.0"
}
},
"create-error-class": {
@ -6172,20 +6170,6 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz",
"integrity": "sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA=="
},
"elliptic": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz",
"integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
@ -14089,7 +14073,6 @@
"browserify-rsa": "^4.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.2",
"elliptic": "^6.0.0",
"inherits": "^2.0.1",
"parse-asn1": "^5.0.0"
}
@ -14503,8 +14486,7 @@
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
"requires": {
"bn.js": "^4.1.0",
"elliptic": "^6.0.0"
"bn.js": "^4.1.0"
}
},
"create-hash": {
@ -14969,20 +14951,6 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.377.tgz",
"integrity": "sha512-cm2WzMKf/3dW5+hNANKm8GAW6SwIWOqLTJ6GPCD0Bbw1qJ9Wzm9nmx9M+byzSsgw8CdCv5fb/wzLFqVS5h6QrA=="
},
"elliptic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
@ -21482,9 +21450,9 @@
"dev": true
},
"elliptic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true,
"requires": {
"bn.js": "^4.4.0",

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.6.4",
"version": "1.7.1",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -5,6 +5,7 @@
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { UserConfirmation } from 'components/register-page/register-form';
import getCore from 'cvat-core-wrapper';
import isReachable from 'utils/url-checker';
const cvat = getCore();
@ -20,9 +21,16 @@ export enum AuthActionTypes {
LOGOUT = 'LOGOUT',
LOGOUT_SUCCESS = 'LOGOUT_SUCCESS',
LOGOUT_FAILED = 'LOGOUT_FAILED',
CHANGE_PASSWORD = 'CHANGE_PASSWORD',
CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS',
CHANGE_PASSWORD_FAILED = 'CHANGE_PASSWORD_FAILED',
SWITCH_CHANGE_PASSWORD_DIALOG = 'SWITCH_CHANGE_PASSWORD_DIALOG',
LOAD_AUTH_ACTIONS = 'LOAD_AUTH_ACTIONS',
LOAD_AUTH_ACTIONS_SUCCESS = 'LOAD_AUTH_ACTIONS_SUCCESS',
LOAD_AUTH_ACTIONS_FAILED = 'LOAD_AUTH_ACTIONS_FAILED',
}
const authActions = {
export const authActions = {
authorizeSuccess: (user: any) => createAction(AuthActionTypes.AUTHORIZED_SUCCESS, { user }),
authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }),
login: () => createAction(AuthActionTypes.LOGIN),
@ -34,6 +42,21 @@ const authActions = {
logout: () => createAction(AuthActionTypes.LOGOUT),
logoutSuccess: () => createAction(AuthActionTypes.LOGOUT_SUCCESS),
logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }),
changePassword: () => createAction(AuthActionTypes.CHANGE_PASSWORD),
changePasswordSuccess: () => createAction(AuthActionTypes.CHANGE_PASSWORD_SUCCESS),
changePasswordFailed: (error: any) => (
createAction(AuthActionTypes.CHANGE_PASSWORD_FAILED, { error })
),
switchChangePasswordDialog: (showChangePasswordDialog: boolean) => (
createAction(AuthActionTypes.SWITCH_CHANGE_PASSWORD_DIALOG, { showChangePasswordDialog })
),
loadServerAuthActions: () => createAction(AuthActionTypes.LOAD_AUTH_ACTIONS),
loadServerAuthActionsSuccess: (allowChangePassword: boolean) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_SUCCESS, { allowChangePassword })
),
loadServerAuthActionsFailed: (error: any) => (
createAction(AuthActionTypes.LOAD_AUTH_ACTIONS_FAILED, { error })
),
};
export type AuthActions = ActionUnion<typeof authActions>;
@ -100,3 +123,30 @@ export const authorizedAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.authorizeFailed(error));
}
};
export const changePasswordAsync = (oldPassword: string,
newPassword1: string, newPassword2: string): ThunkAction => async (dispatch) => {
dispatch(authActions.changePassword());
try {
await cvat.server.changePassword(oldPassword, newPassword1, newPassword2);
dispatch(authActions.changePasswordSuccess());
} catch (error) {
dispatch(authActions.changePasswordFailed(error));
}
};
export const loadAuthActionsAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.loadServerAuthActions());
try {
const promises: Promise<boolean>[] = [
isReachable(`${cvat.config.backendAPI}/auth/password/change`, 'OPTIONS'),
];
const [allowChangePassword] = await Promise.all(promises);
dispatch(authActions.loadServerAuthActionsSuccess(allowChangePassword));
} catch (error) {
dispatch(authActions.loadServerAuthActionsFailed(error));
}
};

@ -3,27 +3,14 @@
// SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import {
Model,
ModelType,
ModelFiles,
ActiveInference,
CombinedState,
} from 'reducers/interfaces';
import { Model, ActiveInference, RQStatus } from 'reducers/interfaces';
import getCore from 'cvat-core-wrapper';
export enum PreinstalledModels {
RCNN = 'RCNN Object Detector',
MaskRCNN = 'Mask RCNN Object Detector',
}
export enum ModelsActionTypes {
GET_MODELS = 'GET_MODELS',
GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS',
GET_MODELS_FAILED = 'GET_MODELS_FAILED',
DELETE_MODEL = 'DELETE_MODEL',
DELETE_MODEL_SUCCESS = 'DELETE_MODEL_SUCCESS',
DELETE_MODEL_FAILED = 'DELETE_MODEL_FAILED',
CREATE_MODEL = 'CREATE_MODEL',
CREATE_MODEL_SUCCESS = 'CREATE_MODEL_SUCCESS',
CREATE_MODEL_FAILED = 'CREATE_MODEL_FAILED',
@ -50,28 +37,6 @@ export const modelsActions = {
error,
},
),
deleteModelSuccess: (id: number) => createAction(
ModelsActionTypes.DELETE_MODEL_SUCCESS, {
id,
},
),
deleteModelFailed: (id: number, error: any) => createAction(
ModelsActionTypes.DELETE_MODEL_FAILED, {
error, id,
},
),
createModel: () => createAction(ModelsActionTypes.CREATE_MODEL),
createModelSuccess: () => createAction(ModelsActionTypes.CREATE_MODEL_SUCCESS),
createModelFailed: (error: any) => createAction(
ModelsActionTypes.CREATE_MODEL_FAILED, {
error,
},
),
createModelUpdateStatus: (status: string) => createAction(
ModelsActionTypes.CREATE_MODEL_STATUS_UPDATED, {
status,
},
),
fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }),
getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => createAction(
ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, {
@ -96,7 +61,7 @@ export const modelsActions = {
taskID,
},
),
cancelInferenceFaild: (taskID: number, error: any) => createAction(
cancelInferenceFailed: (taskID: number, error: any) => createAction(
ModelsActionTypes.CANCEL_INFERENCE_FAILED, {
taskID,
error,
@ -113,361 +78,76 @@ export const modelsActions = {
export type ModelsActions = ActionUnion<typeof modelsActions>;
const core = getCore();
const baseURL = core.config.backendAPI.slice(0, -7);
export function getModelsAsync(): ThunkAction {
return async (dispatch, getState): Promise<void> => {
const state: CombinedState = getState();
const OpenVINO = state.plugins.list.AUTO_ANNOTATION;
const RCNN = state.plugins.list.TF_ANNOTATION;
const MaskRCNN = state.plugins.list.TF_SEGMENTATION;
return async (dispatch): Promise<void> => {
dispatch(modelsActions.getModels());
const models: Model[] = [];
try {
if (OpenVINO) {
const response = await core.server.request(
`${baseURL}/auto_annotation/meta/get`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: JSON.stringify([]),
},
);
for (const model of response.models) {
models.push({
id: model.id,
ownerID: model.owner,
primary: model.primary,
name: model.name,
uploadDate: model.uploadDate,
updateDate: model.updateDate,
labels: [...model.labels],
});
}
}
if (RCNN) {
models.push({
id: null,
ownerID: null,
primary: true,
name: PreinstalledModels.RCNN,
uploadDate: '',
updateDate: '',
labels: ['surfboard', 'car', 'skateboard', 'boat', 'clock',
'cat', 'cow', 'knife', 'apple', 'cup', 'tv',
'baseball_bat', 'book', 'suitcase', 'tennis_racket',
'stop_sign', 'couch', 'cell_phone', 'keyboard',
'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant',
'snowboard', 'bed', 'vase', 'teddy_bear',
'toaster', 'wine_glass', 'traffic_light',
'broccoli', 'backpack', 'carrot', 'potted_plant',
'donut', 'umbrella', 'parking_meter', 'bottle',
'sandwich', 'motorcycle', 'bear', 'banana',
'person', 'scissors', 'elephant', 'dining_table',
'toothbrush', 'toilet', 'skis', 'bowl', 'sheep',
'refrigerator', 'oven', 'microwave', 'train',
'orange', 'mouse', 'laptop', 'bench', 'bicycle',
'fork', 'kite', 'zebra', 'baseball_glove', 'bus',
'spoon', 'horse', 'handbag', 'pizza', 'sports_ball',
'airplane', 'hair_drier', 'hot_dog', 'remote',
'sink', 'dog', 'bird', 'giraffe', 'chair',
],
});
}
if (MaskRCNN) {
models.push({
id: null,
ownerID: null,
primary: true,
name: PreinstalledModels.MaskRCNN,
uploadDate: '',
updateDate: '',
labels: ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
'bus', 'train', 'truck', 'boat', 'traffic light',
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird',
'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear',
'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie',
'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
'kite', 'baseball bat', 'baseball glove', 'skateboard',
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',
'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed',
'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
'keyboard', 'cell phone', 'microwave', 'oven', 'toaster',
'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
'teddy bear', 'hair drier', 'toothbrush',
],
});
}
const models = (await core.lambda.list())
.filter((model: Model) => ['detector', 'reid'].includes(model.type));
dispatch(modelsActions.getModelsSuccess(models));
} catch (error) {
dispatch(modelsActions.getModelsFailed(error));
return;
}
dispatch(modelsActions.getModelsSuccess(models));
};
}
export function deleteModelAsync(id: number): ThunkAction {
return async (dispatch): Promise<void> => {
try {
await core.server.request(`${baseURL}/auto_annotation/delete/${id}`, {
method: 'DELETE',
});
} catch (error) {
dispatch(modelsActions.deleteModelFailed(id, error));
return;
}
dispatch(modelsActions.deleteModelSuccess(id));
};
}
export function createModelAsync(name: string, files: ModelFiles, global: boolean): ThunkAction {
return async (dispatch): Promise<void> => {
async function checkCallback(id: string): Promise<void> {
try {
const data = await core.server.request(
`${baseURL}/auto_annotation/check/${id}`, {
method: 'GET',
},
);
switch (data.status) {
case 'failed':
dispatch(modelsActions.createModelFailed(
`Checking request has returned the "${data.status}" status. Message: ${data.error}`,
));
break;
case 'unknown':
dispatch(modelsActions.createModelFailed(
`Checking request has returned the "${data.status}" status.`,
));
break;
case 'finished':
dispatch(modelsActions.createModelSuccess());
break;
default:
if ('progress' in data) {
modelsActions.createModelUpdateStatus(data.progress);
}
setTimeout(checkCallback.bind(null, id), 1000);
}
} catch (error) {
dispatch(modelsActions.createModelFailed(error));
}
}
dispatch(modelsActions.createModel());
const data = new FormData();
data.append('name', name);
data.append('storage', typeof files.bin === 'string' ? 'shared' : 'local');
data.append('shared', global.toString());
Object.keys(files).reduce((acc, key: string): FormData => {
acc.append(key, files[key]);
return acc;
}, data);
try {
dispatch(modelsActions.createModelUpdateStatus('Request is beign sent..'));
const response = await core.server.request(
`${baseURL}/auto_annotation/create`, {
method: 'POST',
data,
contentType: false,
processData: false,
},
);
dispatch(modelsActions.createModelUpdateStatus('Request is being processed..'));
setTimeout(checkCallback.bind(null, response.id), 1000);
} catch (error) {
dispatch(modelsActions.createModelFailed(error));
}
};
}
interface InferenceMeta {
active: boolean;
taskID: number;
requestID: string;
modelType: ModelType;
}
const timers: any = {};
async function timeoutCallback(
url: string,
taskID: number,
modelType: ModelType,
function listen(
inferenceMeta: InferenceMeta,
dispatch: (action: ModelsActions) => void,
): Promise<void> {
try {
delete timers[taskID];
const response = await core.server.request(url, {
method: 'GET',
});
const activeInference: ActiveInference = {
status: response.status,
progress: +response.progress || 0,
error: response.error || response.stderr || '',
modelType,
};
if (activeInference.status === 'unknown') {
dispatch(modelsActions.getInferenceStatusFailed(
taskID,
new Error(
`Inference status for the task ${taskID} is unknown.`,
),
));
return;
}
if (activeInference.status === 'failed') {
): void {
const { taskID, requestID } = inferenceMeta;
core.lambda.listen(requestID, (status: RQStatus, progress: number, message: string) => {
if (status === RQStatus.failed || status === RQStatus.unknown) {
dispatch(modelsActions.getInferenceStatusFailed(
taskID,
new Error(
`Inference status for the task ${taskID} is failed. ${activeInference.error}`,
`Inference status for the task ${taskID} is ${status}. ${message}`,
),
));
return;
}
if (activeInference.status !== 'finished') {
timers[taskID] = setTimeout(
timeoutCallback.bind(
null,
url,
taskID,
modelType,
dispatch,
), 3000,
);
}
dispatch(modelsActions.getInferenceStatusSuccess(taskID, activeInference));
} catch (error) {
dispatch(modelsActions.getInferenceStatusFailed(taskID, new Error(
`Server request for the task ${taskID} was failed`,
)));
}
}
function subscribe(
inferenceMeta: InferenceMeta,
dispatch: (action: ModelsActions) => void,
): void {
if (!(inferenceMeta.taskID in timers)) {
let requestURL = `${baseURL}`;
if (inferenceMeta.modelType === ModelType.OPENVINO) {
requestURL = `${requestURL}/auto_annotation/check`;
} else if (inferenceMeta.modelType === ModelType.RCNN) {
requestURL = `${requestURL}/tensorflow/annotation/check/task`;
} else if (inferenceMeta.modelType === ModelType.MASK_RCNN) {
requestURL = `${requestURL}/tensorflow/segmentation/check/task`;
}
requestURL = `${requestURL}/${inferenceMeta.requestID}`;
timers[inferenceMeta.taskID] = setTimeout(
timeoutCallback.bind(
null,
requestURL,
inferenceMeta.taskID,
inferenceMeta.modelType,
dispatch,
),
);
}
dispatch(modelsActions.getInferenceStatusSuccess(taskID, {
status,
progress,
error: message,
id: requestID,
}));
}).catch((error: Error) => {
dispatch(modelsActions.getInferenceStatusFailed(taskID, {
status: 'unknown',
progress: 0,
error: error.toString(),
id: requestID,
}));
});
}
export function getInferenceStatusAsync(tasks: number[]): ThunkAction {
return async (dispatch, getState): Promise<void> => {
function parse(response: any, modelType: ModelType): InferenceMeta[] {
return Object.keys(response).map((key: string): InferenceMeta => ({
taskID: +key,
requestID: response[key].rq_id || key,
active: typeof (response[key].active) === 'undefined' ? ['queued', 'started']
.includes(response[key].status.toLowerCase()) : response[key].active,
modelType,
}));
}
const state: CombinedState = getState();
const OpenVINO = state.plugins.list.AUTO_ANNOTATION;
const RCNN = state.plugins.list.TF_ANNOTATION;
const MaskRCNN = state.plugins.list.TF_SEGMENTATION;
export function getInferenceStatusAsync(): ThunkAction {
return async (dispatch): Promise<void> => {
const dispatchCallback = (action: ModelsActions): void => {
dispatch(action);
};
try {
if (OpenVINO) {
const response = await core.server.request(
`${baseURL}/auto_annotation/meta/get`, {
method: 'POST',
data: JSON.stringify(tasks),
headers: {
'Content-Type': 'application/json',
},
},
);
parse(response.run, ModelType.OPENVINO)
.filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
.forEach((inferenceMeta: InferenceMeta): void => {
subscribe(inferenceMeta, dispatchCallback);
});
}
if (RCNN) {
const response = await core.server.request(
`${baseURL}/tensorflow/annotation/meta/get`, {
method: 'POST',
data: JSON.stringify(tasks),
headers: {
'Content-Type': 'application/json',
},
},
);
parse(response, ModelType.RCNN)
.filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
.forEach((inferenceMeta: InferenceMeta): void => {
subscribe(inferenceMeta, dispatchCallback);
});
}
if (MaskRCNN) {
const response = await core.server.request(
`${baseURL}/tensorflow/segmentation/meta/get`, {
method: 'POST',
data: JSON.stringify(tasks),
headers: {
'Content-Type': 'application/json',
},
},
);
parse(response, ModelType.MASK_RCNN)
.filter((inferenceMeta: InferenceMeta): boolean => inferenceMeta.active)
.forEach((inferenceMeta: InferenceMeta): void => {
subscribe(inferenceMeta, dispatchCallback);
});
}
const requests = await core.lambda.requests();
requests
.map((request: any): object => ({
taskID: +request.function.task,
requestID: request.id,
}))
.forEach((inferenceMeta: InferenceMeta): void => {
listen(inferenceMeta, dispatchCallback);
});
} catch (error) {
dispatch(modelsActions.fetchMetaFailed(error));
}
@ -477,37 +157,20 @@ export function getInferenceStatusAsync(tasks: number[]): ThunkAction {
export function startInferenceAsync(
taskInstance: any,
model: Model,
mapping: {
[index: string]: string;
},
cleanOut: boolean,
body: object,
): ThunkAction {
return async (dispatch): Promise<void> => {
try {
if (model.name === PreinstalledModels.RCNN) {
await core.server.request(
`${baseURL}/tensorflow/annotation/create/task/${taskInstance.id}`,
);
} else if (model.name === PreinstalledModels.MaskRCNN) {
await core.server.request(
`${baseURL}/tensorflow/segmentation/create/task/${taskInstance.id}`,
);
} else {
await core.server.request(
`${baseURL}/auto_annotation/start/${model.id}/${taskInstance.id}`, {
method: 'POST',
data: JSON.stringify({
reset: cleanOut,
labels: mapping,
}),
headers: {
'Content-Type': 'application/json',
},
},
);
}
const requestID: string = await core.lambda.run(taskInstance, model, body);
const dispatchCallback = (action: ModelsActions): void => {
dispatch(action);
};
dispatch(getInferenceStatusAsync([taskInstance.id]));
listen({
taskID: taskInstance.id,
requestID,
}, dispatchCallback);
} catch (error) {
dispatch(modelsActions.startInferenceFailed(taskInstance.id, error));
}
@ -518,30 +181,10 @@ export function cancelInferenceAsync(taskID: number): ThunkAction {
return async (dispatch, getState): Promise<void> => {
try {
const inference = getState().models.inferences[taskID];
if (inference) {
if (inference.modelType === ModelType.OPENVINO) {
await core.server.request(
`${baseURL}/auto_annotation/cancel/${taskID}`,
);
} else if (inference.modelType === ModelType.RCNN) {
await core.server.request(
`${baseURL}/tensorflow/annotation/cancel/task/${taskID}`,
);
} else if (inference.modelType === ModelType.MASK_RCNN) {
await core.server.request(
`${baseURL}/tensorflow/segmentation/cancel/task/${taskID}`,
);
}
if (timers[taskID]) {
clearTimeout(timers[taskID]);
delete timers[taskID];
}
}
await core.lambda.cancel(inference.id);
dispatch(modelsActions.cancelInferenceSuccess(taskID));
} catch (error) {
dispatch(modelsActions.cancelInferenceFaild(taskID, error));
dispatch(modelsActions.cancelInferenceFailed(taskID, error));
}
};
}

@ -8,7 +8,7 @@ import PluginChecker from 'utils/plugin-checker';
export enum PluginsActionTypes {
CHECK_PLUGINS = 'CHECK_PLUGINS',
CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS'
CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS',
}
type PluginObjects = Record<SupportedPlugins, boolean>;
@ -29,27 +29,20 @@ export function checkPluginsAsync(): ThunkAction {
dispatch(pluginActions.checkPlugins());
const plugins: PluginObjects = {
ANALYTICS: false,
AUTO_ANNOTATION: false,
GIT_INTEGRATION: false,
TF_ANNOTATION: false,
TF_SEGMENTATION: false,
REID: false,
DEXTR_SEGMENTATION: false,
};
const promises: Promise<boolean>[] = [
// check must return true/false with no exceptions
PluginChecker.check(SupportedPlugins.ANALYTICS),
PluginChecker.check(SupportedPlugins.AUTO_ANNOTATION),
PluginChecker.check(SupportedPlugins.GIT_INTEGRATION),
PluginChecker.check(SupportedPlugins.TF_ANNOTATION),
PluginChecker.check(SupportedPlugins.TF_SEGMENTATION),
PluginChecker.check(SupportedPlugins.DEXTR_SEGMENTATION),
PluginChecker.check(SupportedPlugins.REID),
];
const values = await Promise.all(promises);
[plugins.ANALYTICS, plugins.AUTO_ANNOTATION, plugins.GIT_INTEGRATION, plugins.TF_ANNOTATION,
plugins.TF_SEGMENTATION, plugins.DEXTR_SEGMENTATION, plugins.REID] = values;
[plugins.ANALYTICS, plugins.GIT_INTEGRATION,
plugins.DEXTR_SEGMENTATION] = values;
dispatch(pluginActions.checkedAllPlugins(plugins));
};
}

@ -102,13 +102,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
const promises = array
.map((task): string => (task as any).frames.preview());
dispatch(
getInferenceStatusAsync(
array.map(
(task: any): number => task.id,
),
),
);
dispatch(getInferenceStatusAsync());
for (const promise of promises) {
try {

@ -15,16 +15,11 @@ interface Props {
taskID: number;
taskMode: string;
bugTracker: string;
loaders: any[];
dumpers: any[];
loadActivity: string | null;
dumpActivities: string[] | null;
exportActivities: string[] | null;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
installedAutoAnnotation: boolean;
inferenceIsActive: boolean;
onClickMenu: (params: ClickParam, file?: File) => void;
@ -44,12 +39,7 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
taskID,
taskMode,
bugTracker,
installedAutoAnnotation,
installedTFAnnotation,
installedTFSegmentation,
inferenceIsActive,
dumpers,
loaders,
onClickMenu,
@ -58,9 +48,6 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
loadActivity,
} = props;
const renderModelRunner = installedAutoAnnotation
|| installedTFAnnotation || installedTFSegmentation;
let latestParams: ClickParam | null = null;
function onClickMenuWrapper(params: ClickParam | null, file?: File): void {
const copyParams = params || latestParams;
@ -137,17 +124,12 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
})
}
{!!bugTracker && <Menu.Item key={Actions.OPEN_BUG_TRACKER}>Open bug tracker</Menu.Item>}
{
renderModelRunner
&& (
<Menu.Item
disabled={inferenceIsActive}
key={Actions.RUN_AUTO_ANNOTATION}
>
Automatic annotation
</Menu.Item>
)
}
<Menu.Item
disabled={inferenceIsActive}
key={Actions.RUN_AUTO_ANNOTATION}
>
Automatic annotation
</Menu.Item>
<hr />
<Menu.Item key={Actions.DELETE_TASK}>Delete</Menu.Item>
</Menu>

@ -80,17 +80,17 @@ export default function AnnotationPageComponent(props: Props): JSX.Element {
<AnnotationTopBarContainer />
</Layout.Header>
{ workspace === Workspace.STANDARD && (
<Layout.Content>
<Layout.Content style={{ height: '100%' }}>
<StandardWorkspaceComponent />
</Layout.Content>
)}
{ workspace === Workspace.ATTRIBUTE_ANNOTATION && (
<Layout.Content>
<Layout.Content style={{ height: '100%' }}>
<AttributeAnnotationWorkspace />
</Layout.Content>
)}
{ workspace === Workspace.TAG_ANNOTATION && (
<Layout.Content>
<Layout.Content style={{ height: '100%' }}>
<TagAnnotationWorkspace />
</Layout.Content>
)}

@ -147,7 +147,7 @@ function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Ele
placeholder={
underCursor ? (
<>
<Tooltip title='Click to open help'>
<Tooltip title='Click to open help' mouseLeaveDelay={0}>
<Icon
type='filter'
onClick={(e: React.MouseEvent) => {

@ -27,17 +27,17 @@ function AttributeSwitcher(props: Props): JSX.Element {
const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`;
return (
<div className='attribute-annotation-sidebar-switcher'>
<Tooltip title={`Previous attribute ${normalizedKeyMap.PREVIOUS_ATTRIBUTE}`}>
<div className='attribute-annotation-sidebar-attribute-switcher'>
<Tooltip title={`Previous attribute ${normalizedKeyMap.PREVIOUS_ATTRIBUTE}`} mouseLeaveDelay={0}>
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(-1)}>
<Icon type='left' />
</Button>
</Tooltip>
<Tooltip title={title}>
<Tooltip title={title} mouseLeaveDelay={0}>
<Text className='cvat-text'>{currentAttribute}</Text>
<Text strong>{` [${currentIndex + 1}/${attributesCount}]`}</Text>
</Tooltip>
<Tooltip title={`Next attribute ${normalizedKeyMap.NEXT_ATTRIBUTE}`}>
<Tooltip title={`Next attribute ${normalizedKeyMap.NEXT_ATTRIBUTE}`} mouseLeaveDelay={0}>
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(1)}>
<Icon type='right' />
</Button>

@ -31,18 +31,18 @@ function ObjectSwitcher(props: Props): JSX.Element {
const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`;
return (
<div className='attribute-annotation-sidebar-switcher'>
<Tooltip title={`Previous object ${normalizedKeyMap.PREVIOUS_OBJECT}`}>
<div className='attribute-annotation-sidebar-object-switcher'>
<Tooltip title={`Previous object ${normalizedKeyMap.PREVIOUS_OBJECT}`} mouseLeaveDelay={0}>
<Button disabled={objectsCount <= 1} onClick={() => nextObject(-1)}>
<Icon type='left' />
</Button>
</Tooltip>
<Tooltip title={title}>
<Tooltip title={title} mouseLeaveDelay={0}>
<Text className='cvat-text'>{currentLabel}</Text>
<Text className='cvat-text'>{` ${clientID} `}</Text>
<Text strong>{`[${currentIndex + 1}/${objectsCount}]`}</Text>
</Tooltip>
<Tooltip title={`Next object ${normalizedKeyMap.NEXT_OBJECT}`}>
<Tooltip title={`Next object ${normalizedKeyMap.NEXT_OBJECT}`} mouseLeaveDelay={0}>
<Button disabled={objectsCount <= 1} onClick={() => nextObject(1)}>
<Icon type='right' />
</Button>

@ -13,7 +13,8 @@
padding: 5px;
}
.attribute-annotation-sidebar-switcher {
.attribute-annotation-sidebar-object-switcher,
.attribute-annotation-sidebar-attribute-switcher {
display: flex;
align-items: center;
justify-content: space-between;

@ -111,7 +111,7 @@ function CanvasPointContextMenu(props: Props): React.ReactPortal | null {
return visible && contextMenuFor && type === ContextMenuType.CANVAS_SHAPE_POINT
? (ReactDOM.createPortal(
<div className='cvat-canvas-point-context-menu' style={{ top, left }}>
<Tooltip title='Delete point [Ctrl + dblclick]'>
<Tooltip title='Delete point [Ctrl + dblclick]' mouseLeaveDelay={0}>
<Button type='link' icon='delete' onClick={onPointDelete}>
Delete point
</Button>

@ -876,7 +876,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
defaultValue={0}
onChange={(value: SliderValue): void => onSwitchZLayer(value as number)}
/>
<Tooltip title={`Add new layer ${maxZLayer + 1} and switch to it`}>
<Tooltip title={`Add new layer ${maxZLayer + 1} and switch to it`} mouseLeaveDelay={0}>
<Icon type='plus-circle' onClick={onAddZLayer} />
</Tooltip>
</div>

@ -24,7 +24,7 @@ function CursorControl(props: Props): JSX.Element {
} = props;
return (
<Tooltip title={`Cursor ${cursorShortkey}`} placement='right'>
<Tooltip title={`Cursor ${cursorShortkey}`} placement='right' mouseLeaveDelay={0}>
<Icon
component={CursorIcon}
className={activeControl === ActiveControl.CURSOR

@ -62,7 +62,7 @@ function DEXTRPlugin(props: StateToProps & DispatchToProps): JSX.Element | null
return (
pluginEnabled ? (
<Tooltip title='Make AI polygon from at least 4 extreme points using deep extreme cut'>
<Tooltip title='Make AI polygon from at least 4 extreme points using deep extreme cut' mouseLeaveDelay={0}>
<Checkbox
style={{ marginTop: 5 }}
checked={pluginActivated}

@ -47,6 +47,7 @@ function DrawPolygonControl(props: Props): JSX.Element {
)}
>
<Icon
className='cvat-draw-cuboid-control'
{...dynamicIconProps}
component={CubeIcon}
/>

@ -43,6 +43,7 @@ function DrawPointsControl(props: Props): JSX.Element {
)}
>
<Icon
className='cvat-draw-points-control'
{...dynamicIconProps}
component={PointIcon}
/>

@ -43,6 +43,7 @@ function DrawPolygonControl(props: Props): JSX.Element {
)}
>
<Icon
className='cvat-draw-polygon-control'
{...dynamicIconProps}
component={PolygonIcon}
/>

@ -43,6 +43,7 @@ function DrawPolylineControl(props: Props): JSX.Element {
)}
>
<Icon
className='cvat-draw-polyline-control'
{...dynamicIconProps}
component={PolylineIcon}
/>

@ -45,6 +45,7 @@ function DrawRectangleControl(props: Props): JSX.Element {
)}
>
<Icon
className='cvat-draw-rectangle-control'
{...dynamicIconProps}
component={RectangleIcon}
/>

@ -186,14 +186,14 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
}
<Row type='flex' justify='space-around'>
<Col span={12}>
<Tooltip title={`Press ${repeatShapeShortcut} to draw again`}>
<Tooltip title={`Press ${repeatShapeShortcut} to draw again`} mouseLeaveDelay={0}>
<Button onClick={onDrawShape}>
Shape
</Button>
</Tooltip>
</Col>
<Col span={12}>
<Tooltip title={`Press ${repeatShapeShortcut} to draw again`}>
<Tooltip title={`Press ${repeatShapeShortcut} to draw again`} mouseLeaveDelay={0}>
<Button onClick={onDrawTrack}>
Track
</Button>

@ -19,7 +19,7 @@ function FitControl(props: Props): JSX.Element {
} = props;
return (
<Tooltip title='Fit the image [Double Click]' placement='right'>
<Tooltip title='Fit the image [Double Click]' placement='right' mouseLeaveDelay={0}>
<Icon component={FitIcon} onClick={(): void => canvasInstance.fit()} />
</Tooltip>
);

@ -45,7 +45,7 @@ function GroupControl(props: Props): JSX.Element {
const title = `Group shapes/tracks ${switchGroupShortcut}.`
+ ` Select and press ${resetGroupShortcut} to reset a group`;
return (
<Tooltip title={title} placement='right'>
<Tooltip title={title} placement='right' mouseLeaveDelay={0}>
<Icon {...dynamicIconProps} component={GroupIcon} />
</Tooltip>
);

@ -41,7 +41,7 @@ function MergeControl(props: Props): JSX.Element {
};
return (
<Tooltip title={`Merge shapes/tracks ${switchMergeShortcut}`} placement='right'>
<Tooltip title={`Merge shapes/tracks ${switchMergeShortcut}`} placement='right' mouseLeaveDelay={0}>
<Icon {...dynamicIconProps} component={MergeIcon} />
</Tooltip>
);

@ -19,7 +19,7 @@ function MoveControl(props: Props): JSX.Element {
const { canvasInstance, activeControl } = props;
return (
<Tooltip title='Move the image' placement='right'>
<Tooltip title='Move the image' placement='right' mouseLeaveDelay={0}>
<Icon
component={MoveIcon}
className={activeControl === ActiveControl.DRAG_CANVAS

@ -22,7 +22,7 @@ function ResizeControl(props: Props): JSX.Element {
} = props;
return (
<Tooltip title='Select a region of interest' placement='right'>
<Tooltip title='Select a region of interest' placement='right' mouseLeaveDelay={0}>
<Icon
component={ZoomIcon}
className={activeControl === ActiveControl.ZOOM_CANVAS

@ -29,14 +29,14 @@ function RotateControl(props: Props): JSX.Element {
placement='right'
content={(
<>
<Tooltip title={`Rotate the image anticlockwise ${anticlockwiseShortcut}`} placement='topRight'>
<Tooltip title={`Rotate the image anticlockwise ${anticlockwiseShortcut}`} placement='topRight' mouseLeaveDelay={0}>
<Icon
className='cvat-rotate-canvas-controls-left'
onClick={(): void => rotateFrame(Rotation.ANTICLOCKWISE90)}
component={RotateIcon}
/>
</Tooltip>
<Tooltip title={`Rotate the image clockwise ${clockwiseShortcut}`} placement='topRight'>
<Tooltip title={`Rotate the image clockwise ${clockwiseShortcut}`} placement='topRight' mouseLeaveDelay={0}>
<Icon
className='cvat-rotate-canvas-controls-right'
onClick={(): void => rotateFrame(Rotation.CLOCKWISE90)}

@ -61,7 +61,7 @@ function SetupTagPopover(props: Props): JSX.Element {
</Row>
<Row type='flex' justify='space-around'>
<Col span={24}>
<Tooltip title={`Press ${repeatShapeShortcut} to add a tag again`}>
<Tooltip title={`Press ${repeatShapeShortcut} to add a tag again`} mouseLeaveDelay={0}>
<Button onClick={() => onSetup(selectedLabeID)}>
Tag
</Button>

@ -41,7 +41,7 @@ function SplitControl(props: Props): JSX.Element {
};
return (
<Tooltip title={`Split a track ${switchSplitShortcut}`} placement='right'>
<Tooltip title={`Split a track ${switchSplitShortcut}`} placement='right' mouseLeaveDelay={0}>
<Icon {...dynamicIconProps} component={SplitIcon} />
</Tooltip>
);

@ -74,7 +74,7 @@ function ItemTopComponent(props: Props): JSX.Element {
<Text type='secondary' style={{ fontSize: 10 }}>{type}</Text>
</Col>
<Col span={12}>
<Tooltip title='Change current label'>
<Tooltip title='Change current label' mouseLeaveDelay={0}>
<Select
size='small'
value={`${labelID}`}

@ -113,7 +113,7 @@ function ItemButtonsComponent(props: Props): JSX.Element {
<Col>
{ navigatePrevKeyframe
? (
<Tooltip title={`Go to previous keyframe ${prevKeyFrameShortcut}`}>
<Tooltip title={`Go to previous keyframe ${prevKeyFrameShortcut}`} mouseLeaveDelay={0}>
<Icon
component={PreviousIcon}
onClick={navigatePrevKeyframe}
@ -125,7 +125,7 @@ function ItemButtonsComponent(props: Props): JSX.Element {
<Col>
{ navigateNextKeyframe
? (
<Tooltip title={`Go to next keyframe ${nextKeyFrameShortcut}`}>
<Tooltip title={`Go to next keyframe ${nextKeyFrameShortcut}`} mouseLeaveDelay={0}>
<Icon
component={NextIcon}
onClick={navigateNextKeyframe}
@ -142,7 +142,7 @@ function ItemButtonsComponent(props: Props): JSX.Element {
</Row>
<Row type='flex' justify='space-around'>
<Col>
<Tooltip title={`Switch outside property ${switchOutsideShortcut}`}>
<Tooltip title={`Switch outside property ${switchOutsideShortcut}`} mouseLeaveDelay={0}>
{ outside
? (
<Icon
@ -155,28 +155,28 @@ function ItemButtonsComponent(props: Props): JSX.Element {
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
<Tooltip title={`Switch lock property ${switchLockShortcut}`} mouseLeaveDelay={0}>
{ locked
? <Icon type='lock' theme='filled' onClick={unlock} />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`} mouseLeaveDelay={0}>
{ occluded
? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`} mouseLeaveDelay={0}>
{ hidden
? <Icon type='eye-invisible' theme='filled' onClick={show} style={hiddenStyle} />
: <Icon type='eye' onClick={hide} style={hiddenStyle} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch keyframe property ${switchKeyFrameShortcut}`}>
<Tooltip title={`Switch keyframe property ${switchKeyFrameShortcut}`} mouseLeaveDelay={0}>
{ keyframe
? <Icon type='star' theme='filled' onClick={unsetKeyframe} style={keyframeStyle} />
: <Icon type='star' onClick={setKeyframe} style={keyframeStyle} />}
@ -185,7 +185,7 @@ function ItemButtonsComponent(props: Props): JSX.Element {
{
shapeType !== ShapeType.POINTS && (
<Col>
<Tooltip title='Switch pinned property'>
<Tooltip title='Switch pinned property' mouseLeaveDelay={0}>
{ pinned
? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}
@ -205,7 +205,7 @@ function ItemButtonsComponent(props: Props): JSX.Element {
<Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'>
<Col>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
<Tooltip title={`Switch lock property ${switchLockShortcut}`} mouseLeaveDelay={0}>
{ locked
? <Icon type='lock' onClick={unlock} theme='filled' />
: <Icon type='unlock' onClick={lock} />}
@ -222,21 +222,21 @@ function ItemButtonsComponent(props: Props): JSX.Element {
<Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'>
<Col>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
<Tooltip title={`Switch lock property ${switchLockShortcut}`} mouseLeaveDelay={0}>
{ locked
? <Icon type='lock' onClick={unlock} theme='filled' />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`} mouseLeaveDelay={0}>
{ occluded
? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`} mouseLeaveDelay={0}>
{ hidden
? <Icon type='eye-invisible' onClick={show} />
: <Icon type='eye' onClick={hide} />}
@ -245,7 +245,7 @@ function ItemButtonsComponent(props: Props): JSX.Element {
{
shapeType !== ShapeType.POINTS && (
<Col>
<Tooltip title='Switch pinned property'>
<Tooltip title='Switch pinned property' mouseLeaveDelay={0}>
{ pinned
? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}

@ -63,14 +63,14 @@ export default function ItemMenu(props: Props): JSX.Element {
</Button>
</Menu.Item>
<Menu.Item>
<Tooltip title={`${copyShortcut} and ${pasteShortcut}`}>
<Tooltip title={`${copyShortcut} and ${pasteShortcut}`} mouseLeaveDelay={0}>
<Button type='link' icon='copy' onClick={copy}>
Make a copy
</Button>
</Tooltip>
</Menu.Item>
<Menu.Item>
<Tooltip title={`${propagateShortcut}`}>
<Tooltip title={`${propagateShortcut}`} mouseLeaveDelay={0}>
<Button type='link' icon='block' onClick={propagate}>
Propagate
</Button>
@ -93,7 +93,7 @@ export default function ItemMenu(props: Props): JSX.Element {
)}
{objectType !== ObjectType.TAG && (
<Menu.Item>
<Tooltip title={`${toBackgroundShortcut}`}>
<Tooltip title={`${toBackgroundShortcut}`} mouseLeaveDelay={0}>
<Button type='link' onClick={toBackground}>
<Icon component={BackgroundIcon} />
To background
@ -103,7 +103,7 @@ export default function ItemMenu(props: Props): JSX.Element {
)}
{objectType !== ObjectType.TAG && (
<Menu.Item>
<Tooltip title={`${toForegroundShortcut}`}>
<Tooltip title={`${toForegroundShortcut}`} mouseLeaveDelay={0}>
<Button type='link' onClick={toForeground}>
<Icon component={ForegroundIcon} />
To foreground
@ -112,7 +112,7 @@ export default function ItemMenu(props: Props): JSX.Element {
</Menu.Item>
)}
<Menu.Item>
<Tooltip title={`${removeShortcut}`}>
<Tooltip title={`${removeShortcut}`} mouseLeaveDelay={0}>
<Button
type='link'
icon='delete'

@ -95,21 +95,21 @@ function ObjectListHeader(props: Props): JSX.Element {
</Row>
<Row type='flex' justify='space-between' align='middle'>
<Col span={2}>
<Tooltip title={`Switch lock property for all ${switchLockAllShortcut}`}>
<Tooltip title={`Switch lock property for all ${switchLockAllShortcut}`} mouseLeaveDelay={0}>
{ statesLocked
? <Icon type='lock' onClick={unlockAllStates} theme='filled' />
: <Icon type='unlock' onClick={lockAllStates} />}
</Tooltip>
</Col>
<Col span={2}>
<Tooltip title={`Switch hidden property for all ${switchHiddenAllShortcut}`}>
<Tooltip title={`Switch hidden property for all ${switchHiddenAllShortcut}`} mouseLeaveDelay={0}>
{ statesHidden
? <Icon type='eye-invisible' onClick={showAllStates} />
: <Icon type='eye' onClick={hideAllStates} />}
</Tooltip>
</Col>
<Col span={2}>
<Tooltip title='Expand/collapse all'>
<Tooltip title='Expand/collapse all' mouseLeaveDelay={0}>
{ statesCollapsed
? <Icon type='caret-down' onClick={expandAllStates} />
: <Icon type='caret-up' onClick={collapseAllStates} />}

@ -98,7 +98,7 @@
padding: 10px;
border-radius: 5px;
background: $background-color-2;
width: 250px;
width: 270px;
> div {
margin-top: 5px;

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT
import React, { useState, useEffect, Fragment } from 'react';
import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
import { Row, Col } from 'antd/lib/grid';
@ -86,38 +86,41 @@ const ShortcutsSelect = (props: Props): JSX.Element => {
<Text strong>Shortcuts for labels:</Text>
</Col>
</Row>
{shift(Object.keys(shortcutLabelMap), 1).slice(0, Math.min(labels.length, 10)).map((id) => (
<Row key={id}>
<Col>
<Text strong>{`Key ${id}:`}</Text>
<Select
value={`${shortcutLabelMap[Number.parseInt(id, 10)]}`}
onChange={(value: string) => {
onChangeShortcutLabel(value, Number.parseInt(id, 10));
}}
size='default'
style={{ width: 200 }}
className='cvat-tag-annotation-label-select'
>
<Select.Option value=''>
<Text type='secondary'>
None
</Text>
</Select.Option>
{
(labels as any[]).map((label: any) => (
<Select.Option
key={label.id}
value={`${label.id}`}
>
{label.name}
{
shift(Object.keys(shortcutLabelMap), 1).slice(0, Math.min(labels.length, 10))
.map((id) => (
<Row key={id}>
<Col>
<Text strong>{`Key ${id}:`}</Text>
<Select
value={`${shortcutLabelMap[Number.parseInt(id, 10)]}`}
onChange={(value: string) => {
onChangeShortcutLabel(value, Number.parseInt(id, 10));
}}
size='default'
style={{ width: 200 }}
className='cvat-tag-annotation-label-select'
>
<Select.Option value=''>
<Text type='secondary'>
None
</Text>
</Select.Option>
))
}
</Select>
</Col>
</Row>
))}
{
(labels as any[]).map((label: any) => (
<Select.Option
key={label.id}
value={`${label.id}`}
>
{label.name}
</Select.Option>
))
}
</Select>
</Col>
</Row>
))
}
</div>
);
};

@ -9,7 +9,6 @@ import Modal from 'antd/lib/modal';
import DumpSubmenu from 'components/actions-menu/dump-submenu';
import LoadSubmenu from 'components/actions-menu/load-submenu';
import ExportSubmenu from 'components/actions-menu/export-submenu';
import ReIDPlugin from './reid-plugin';
interface Props {
taskMode: string;
@ -18,7 +17,6 @@ interface Props {
loadActivity: string | null;
dumpActivities: string[] | null;
exportActivities: string[] | null;
installedReID: boolean;
taskID: number;
onClickMenu(params: ClickParam, file?: File): void;
}
@ -40,7 +38,6 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
loadActivity,
dumpActivities,
exportActivities,
installedReID,
taskID,
} = props;
@ -125,7 +122,6 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
Open the task
</a>
</Menu.Item>
{ installedReID && <ReIDPlugin /> }
</Menu>
);
}

@ -54,42 +54,44 @@ function PlayerButtons(props: Props): JSX.Element {
return (
<Col className='cvat-player-buttons'>
<Tooltip title='Go to the first frame'>
<Icon component={FirstIcon} onClick={onFirstFrame} />
<Tooltip title='Go to the first frame' mouseLeaveDelay={0}>
<Icon className='cvat-player-first-button' component={FirstIcon} onClick={onFirstFrame} />
</Tooltip>
<Tooltip title={`Go back with a step ${backwardShortcut}`}>
<Icon component={BackJumpIcon} onClick={onBackward} />
<Tooltip title={`Go back with a step ${backwardShortcut}`} mouseLeaveDelay={0}>
<Icon className='cvat-player-backward-button' component={BackJumpIcon} onClick={onBackward} />
</Tooltip>
<Tooltip title={`Go back ${previousFrameShortcut}`}>
<Icon component={PreviousIcon} onClick={onPrevFrame} />
<Tooltip title={`Go back ${previousFrameShortcut}`} mouseLeaveDelay={0}>
<Icon className='cvat-player-previous-button' component={PreviousIcon} onClick={onPrevFrame} />
</Tooltip>
{!playing
? (
<Tooltip title={`Play ${playPauseShortcut}`}>
<Tooltip title={`Play ${playPauseShortcut}`} mouseLeaveDelay={0}>
<Icon
className='cvat-player-play-button'
component={PlayIcon}
onClick={onSwitchPlay}
/>
</Tooltip>
)
: (
<Tooltip title={`Pause ${playPauseShortcut}`}>
<Tooltip title={`Pause ${playPauseShortcut}`} mouseLeaveDelay={0}>
<Icon
className='cvat-player-pause-button'
component={PauseIcon}
onClick={onSwitchPlay}
/>
</Tooltip>
)}
<Tooltip title={`Go next ${nextFrameShortcut}`}>
<Icon component={NextIcon} onClick={onNextFrame} />
<Tooltip title={`Go next ${nextFrameShortcut}`} mouseLeaveDelay={0}>
<Icon className='cvat-player-next-button' component={NextIcon} onClick={onNextFrame} />
</Tooltip>
<Tooltip title={`Go next with a step ${forwardShortcut}`}>
<Icon component={ForwardJumpIcon} onClick={onForward} />
<Tooltip title={`Go next with a step ${forwardShortcut}`} mouseLeaveDelay={0}>
<Icon className='cvat-player-forward-button' component={ForwardJumpIcon} onClick={onForward} />
</Tooltip>
<Tooltip title='Go to the last frame'>
<Icon component={LastIcon} onClick={onLastFrame} />
<Tooltip title='Go to the last frame' mouseLeaveDelay={0}>
<Icon className='cvat-player-last-button' component={LastIcon} onClick={onLastFrame} />
</Tooltip>
</Col>
);

@ -62,19 +62,19 @@ function PlayerNavigation(props: Props): JSX.Element {
</Row>
<Row type='flex' justify='center'>
<Col className='cvat-player-filename-wrapper'>
<Tooltip title={frameFilename}>
<Tooltip title={frameFilename} mouseLeaveDelay={0}>
<Text type='secondary'>{frameFilename}</Text>
</Tooltip>
</Col>
<Col offset={1}>
<Tooltip title='Create frame URL'>
<Tooltip title='Create frame URL' mouseLeaveDelay={0}>
<Icon className='cvat-player-frame-url-icon' type='link' onClick={onURLIconClick} />
</Tooltip>
</Col>
</Row>
</Col>
<Col>
<Tooltip title={`Press ${focusFrameInputShortcut} to focus here`}>
<Tooltip title={`Press ${focusFrameInputShortcut} to focus here`} mouseLeaveDelay={0}>
<InputNumber
className='cvat-player-frame-selector'
type='number'

@ -1,227 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import ReactDOM from 'react-dom';
import React, { useState, useEffect } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Modal from 'antd/lib/modal';
import Menu from 'antd/lib/menu';
import Text from 'antd/lib/typography/Text';
import InputNumber from 'antd/lib/input-number';
import Tooltip from 'antd/lib/tooltip';
import { clamp } from 'utils/math';
import { run, cancel } from 'utils/reid-utils';
import { connect } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';
import { fetchAnnotationsAsync } from 'actions/annotation-actions';
interface InputModalProps {
visible: boolean;
onCancel(): void;
onSubmit(threshold: number, distance: number): void;
}
function InputModal(props: InputModalProps): JSX.Element {
const { visible, onCancel, onSubmit } = props;
const [threshold, setThreshold] = useState(0.5);
const [distance, setDistance] = useState(50);
const [thresholdMin, thresholdMax] = [0.05, 0.95];
const [distanceMin, distanceMax] = [1, 1000];
return (
<Modal
closable={false}
width={300}
visible={visible}
onCancel={onCancel}
onOk={() => onSubmit(threshold, distance)}
okText='Merge'
>
<Row type='flex'>
<Col span={10}>
<Tooltip title='Similarity of objects on neighbour frames is calculated using AI model'>
<Text>Similarity threshold: </Text>
</Tooltip>
</Col>
<Col span={12}>
<InputNumber
style={{ width: '100%' }}
min={thresholdMin}
max={thresholdMax}
step={0.05}
value={threshold}
onChange={(value: number | undefined) => {
if (typeof (value) === 'number') {
setThreshold(clamp(value, thresholdMin, thresholdMax));
}
}}
/>
</Col>
</Row>
<Row type='flex'>
<Col span={10}>
<Tooltip title='The value defines max distance to merge (between centers of two objects on neighbour frames)'>
<Text>Max pixel distance: </Text>
</Tooltip>
</Col>
<Col span={12}>
<InputNumber
style={{ width: '100%' }}
min={distanceMin}
max={distanceMax}
step={5}
value={distance}
onChange={(value: number | undefined) => {
if (typeof (value) === 'number') {
setDistance(clamp(value, distanceMin, distanceMax));
}
}}
/>
</Col>
</Row>
</Modal>
);
}
interface InProgressDialogProps {
visible: boolean;
progress: number;
onCancel(): void;
}
function InProgressDialog(props: InProgressDialogProps): JSX.Element {
const { visible, onCancel, progress } = props;
return (
<Modal
closable={false}
width={300}
visible={visible}
okText='Cancel'
okButtonProps={{
type: 'danger',
}}
onOk={onCancel}
cancelButtonProps={{
style: {
display: 'none',
},
}}
>
<Text>{`Merging is in progress ${progress}%`}</Text>
</Modal>
);
}
const reidContainer = window.document.createElement('div');
reidContainer.setAttribute('id', 'cvat-reid-wrapper');
window.document.body.appendChild(reidContainer);
interface StateToProps {
jobInstance: any | null;
}
interface DispatchToProps {
updateAnnotations(): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
job: {
instance: jobInstance,
},
},
} = state;
return {
jobInstance,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
updateAnnotations(): void {
dispatch(fetchAnnotationsAsync());
},
};
}
function ReIDPlugin(props: StateToProps & DispatchToProps): JSX.Element {
const { jobInstance, updateAnnotations, ...rest } = props;
const [showInputDialog, setShowInputDialog] = useState(false);
const [showInProgressDialog, setShowInProgressDialog] = useState(false);
const [progress, setProgress] = useState(0);
useEffect(() => {
ReactDOM.render((
<>
<InProgressDialog
visible={showInProgressDialog}
progress={progress}
onCancel={() => {
cancel(jobInstance.id);
}}
/>
<InputModal
visible={showInputDialog}
onCancel={() => setShowInputDialog(false)}
onSubmit={async (threshold: number, distance: number) => {
setProgress(0);
setShowInputDialog(false);
setShowInProgressDialog(true);
const onUpdatePercentage = (percent: number): void => {
setProgress(percent);
};
try {
const annotations = await jobInstance.annotations.export();
const merged = await run({
threshold,
distance,
onUpdatePercentage,
jobID: jobInstance.id,
annotations,
});
await jobInstance.annotations.clear();
updateAnnotations(); // one more call to do not confuse canvas
await jobInstance.annotations.import(merged);
updateAnnotations();
} catch (error) {
Modal.error({
title: 'Could not merge annotations',
content: error.toString(),
});
} finally {
setShowInProgressDialog(false);
}
}}
/>
</>
), reidContainer);
});
return (
<Menu.Item
{...rest}
key='run_reid'
title='Run algorithm that merges separated bounding boxes automatically'
onClick={() => {
if (jobInstance) {
setShowInputDialog(true);
}
}}
>
Run ReID merge
</Menu.Item>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ReIDPlugin);

@ -49,7 +49,7 @@ function RightGroup(props: Props): JSX.Element {
value={workspace}
>
{Object.values(Workspace).map((ws) => (
<Select.Option key={ws} value={ws} >
<Select.Option key={ws} value={ws}>
{ws}
</Select.Option>
))}

@ -89,7 +89,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
});
const makeShapesTracksTitle = (title: string): JSX.Element => (
<Tooltip title='Shapes / Tracks'>
<Tooltip title='Shapes / Tracks' mouseLeaveDelay={0}>
<Text strong style={{ marginRight: 5 }}>{title}</Text>
<Icon className='cvat-info-circle-icon' type='question-circle' />
</Tooltip>

@ -0,0 +1,166 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import patterns from 'utils/validation-patterns';
export interface ChangePasswordData {
oldPassword: string;
newPassword1: string;
newPassword2: string;
}
type ChangePasswordFormProps = {
fetching: boolean;
onSubmit(loginData: ChangePasswordData): void;
} & FormComponentProps;
class ChangePasswordFormComponent extends React.PureComponent<ChangePasswordFormProps> {
private validateConfirmation = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (value && value !== form.getFieldValue('newPassword1')) {
callback('Two passwords that you enter is inconsistent!');
} else {
callback();
}
};
private validatePassword = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (!patterns.validatePasswordLength.pattern.test(value)) {
callback(patterns.validatePasswordLength.message);
}
if (!patterns.passwordContainsNumericCharacters.pattern.test(value)) {
callback(patterns.passwordContainsNumericCharacters.message);
}
if (!patterns.passwordContainsUpperCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsUpperCaseCharacter.message);
}
if (!patterns.passwordContainsLowerCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsLowerCaseCharacter.message);
}
if (value) {
form.validateFields(['newPassword2'], { force: true });
}
callback();
};
private handleSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const {
form,
onSubmit,
} = this.props;
form.validateFields((error, values): void => {
if (!error) {
const validatedFields = {
...values,
confirmations: [],
};
onSubmit(validatedFields);
}
});
};
private renderOldPasswordField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('oldPassword', {
rules: [{
required: true,
message: 'Please input your current password!',
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Current password'
/>)}
</Form.Item>
);
}
private renderNewPasswordField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword1', {
rules: [{
required: true,
message: 'Please input new password!',
}, {
validator: this.validatePassword,
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='New password'
/>)}
</Form.Item>
);
}
private renderNewPasswordConfirmationField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword2', {
rules: [{
required: true,
message: 'Please confirm your new password!',
}, {
validator: this.validateConfirmation,
}],
})(<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Confirm new password'
/>)}
</Form.Item>
);
}
public render(): JSX.Element {
const { fetching } = this.props;
return (
<Form
onSubmit={this.handleSubmit}
className='change-password-form'
>
{this.renderOldPasswordField()}
{this.renderNewPasswordField()}
{this.renderNewPasswordConfirmationField()}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className='change-password-form-button'
loading={fetching}
disabled={fetching}
>
Submit
</Button>
</Form.Item>
</Form>
);
}
}
export default Form.create<ChangePasswordFormProps>()(ChangePasswordFormComponent);

@ -0,0 +1,84 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import Modal from 'antd/lib/modal';
import Title from 'antd/lib/typography/Title';
import { changePasswordAsync } from 'actions/auth-actions';
import { CombinedState } from 'reducers/interfaces';
import ChangePasswordForm, { ChangePasswordData } from './change-password-form';
interface StateToProps {
fetching: boolean;
visible: boolean;
}
interface DispatchToProps {
onChangePassword(
oldPassword: string,
newPassword1: string,
newPassword2: string): void;
}
interface ChangePasswordPageComponentProps {
fetching: boolean;
visible: boolean;
onChangePassword: (oldPassword: string, newPassword1: string, newPassword2: string) => void;
onClose(): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
return {
fetching: state.auth.fetching,
visible: state.auth.showChangePasswordDialog,
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return ({
onChangePassword(oldPassword: string, newPassword1: string, newPassword2: string): void {
dispatch(changePasswordAsync(oldPassword, newPassword1, newPassword2));
},
});
}
function ChangePasswordComponent(props: ChangePasswordPageComponentProps): JSX.Element {
const {
fetching,
onChangePassword,
visible,
onClose,
} = props;
return (
<Modal
title={<Title level={3}>Change password</Title>}
okType='primary'
okText='Submit'
footer={null}
visible={visible}
destroyOnClose
onCancel={onClose}
>
<ChangePasswordForm
onSubmit={(changePasswordData: ChangePasswordData): void => {
onChangePassword(
changePasswordData.oldPassword,
changePasswordData.newPassword1,
changePasswordData.newPassword2,
);
}}
fetching={fetching}
/>
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ChangePasswordComponent);

@ -1,160 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Alert from 'antd/lib/alert';
import Button from 'antd/lib/button';
import Tooltip from 'antd/lib/tooltip';
import message from 'antd/lib/message';
import notification from 'antd/lib/notification';
import Text from 'antd/lib/typography/Text';
import consts from 'consts';
import ConnectedFileManager, {
FileManagerContainer,
} from 'containers/file-manager/file-manager';
import { ModelFiles } from 'reducers/interfaces';
import CreateModelForm, {
CreateModelForm as WrappedCreateModelForm,
} from './create-model-form';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
modelCreatingStatus: string;
}
export default class CreateModelContent extends React.PureComponent<Props> {
private modelForm: WrappedCreateModelForm;
private fileManagerContainer: FileManagerContainer;
public constructor(props: Props) {
super(props);
this.modelForm = null as any as WrappedCreateModelForm;
this.fileManagerContainer = null as any as FileManagerContainer;
}
public componentDidUpdate(prevProps: Props): void {
const { modelCreatingStatus } = this.props;
if (prevProps.modelCreatingStatus !== 'CREATED'
&& modelCreatingStatus === 'CREATED') {
message.success('The model has been uploaded');
this.modelForm.resetFields();
this.fileManagerContainer.reset();
}
}
private handleSubmitClick = (): void => {
const { createModel } = this.props;
this.modelForm.submit()
.then((data) => {
const {
local,
share,
} = this.fileManagerContainer.getFiles();
const files = local.length ? local : share;
const grouppedFiles: ModelFiles = {
xml: '',
bin: '',
py: '',
json: '',
};
(files as any).reduce((acc: ModelFiles, value: File | string): ModelFiles => {
const name = typeof value === 'string' ? value : value.name;
const [extension] = name.split('.').reverse();
if (extension in acc) {
acc[extension] = value;
}
return acc;
}, grouppedFiles);
if (Object.keys(grouppedFiles)
.map((key: string) => grouppedFiles[key])
.filter((val) => !!val).length !== 4) {
notification.error({
message: 'Could not upload a model',
description: 'Please, specify correct files',
});
} else {
createModel(data.name, grouppedFiles, data.global);
}
}).catch(() => {
notification.error({
message: 'Could not upload a model',
description: 'Please, check input fields',
});
});
};
public render(): JSX.Element {
const {
modelCreatingStatus,
} = this.props;
const loading = !!modelCreatingStatus
&& modelCreatingStatus !== 'CREATED';
const status = modelCreatingStatus
&& modelCreatingStatus !== 'CREATED' ? modelCreatingStatus : '';
const { AUTO_ANNOTATION_GUIDE_URL } = consts;
return (
<Row type='flex' justify='start' align='middle' className='cvat-create-model-content'>
<Col span={24}>
<Tooltip title='Click to open guide'>
<Icon
onClick={(): void => {
// false positive
// eslint-disable-next-line
window.open(AUTO_ANNOTATION_GUIDE_URL, '_blank');
}}
type='question-circle'
/>
</Tooltip>
</Col>
<Col span={24}>
<CreateModelForm
wrappedComponentRef={
(ref: WrappedCreateModelForm): void => {
this.modelForm = ref;
}
}
/>
</Col>
<Col span={24}>
<Text type='danger'>* </Text>
<Text className='cvat-text-color'>Select files:</Text>
</Col>
<Col span={24}>
<ConnectedFileManager
ref={
(container: FileManagerContainer): void => {
this.fileManagerContainer = container;
}
}
withRemote={false}
/>
</Col>
<Col span={18}>
{status && <Alert message={`${status}`} />}
</Col>
<Col span={6}>
<Button
type='primary'
disabled={loading}
loading={loading}
onClick={this.handleSubmitClick}
>
Submit
</Button>
</Col>
</Row>
);
}
}

@ -1,80 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Input from 'antd/lib/input';
import Tooltip from 'antd/lib/tooltip';
import Checkbox from 'antd/lib/checkbox';
import Text from 'antd/lib/typography/Text';
type Props = FormComponentProps;
export class CreateModelForm extends React.PureComponent<Props> {
public submit(): Promise<{name: string; global: boolean}> {
const { form } = this.props;
return new Promise((resolve, reject) => {
form.validateFields((errors, values): void => {
if (!errors) {
resolve({
name: values.name,
global: values.global,
});
} else {
reject(errors);
}
});
});
}
public resetFields(): void {
const { form } = this.props;
form.resetFields();
}
public render(): JSX.Element {
const { form } = this.props;
const { getFieldDecorator } = form;
return (
<Form onSubmit={(e: React.FormEvent): void => e.preventDefault()}>
<Row>
<Col span={24}>
<Text type='danger'>* </Text>
<Text className='cvat-text-color'>Name:</Text>
</Col>
<Col span={14}>
<Form.Item hasFeedback>
{ getFieldDecorator('name', {
rules: [{
required: true,
message: 'Please, specify a model name',
}],
})(<Input placeholder='Model name' />)}
</Form.Item>
</Col>
<Col span={8} offset={2}>
<Form.Item>
<Tooltip title='Will this model be availabe for everyone?'>
{ getFieldDecorator('global', {
initialValue: false,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-text-color'>
Load globally
</Text>
</Checkbox>,
)}
</Tooltip>
</Form.Item>
</Col>
</Row>
</Form>
);
}
}
export default Form.create()(CreateModelForm);

@ -1,38 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Text from 'antd/lib/typography/Text';
import { ModelFiles } from 'reducers/interfaces';
import CreateModelContent from './create-model-content';
interface Props {
createModel(name: string, files: ModelFiles, global: boolean): void;
isAdmin: boolean;
modelCreatingStatus: string;
}
export default function CreateModelPageComponent(props: Props): JSX.Element {
const {
isAdmin,
modelCreatingStatus,
createModel,
} = props;
return (
<Row type='flex' justify='center' align='top' className='cvat-create-model-form-wrapper'>
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'>Upload a new model</Text>
<CreateModelContent
isAdmin={isAdmin}
modelCreatingStatus={modelCreatingStatus}
createModel={createModel}
/>
</Col>
</Row>
);
}

@ -1,43 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
@import '../../base.scss';
.cvat-create-model-form-wrapper {
text-align: center;
margin-top: 40px;
overflow-y: auto;
height: 90%;
> div > span {
font-size: 36px;
}
.cvat-create-model-content {
margin-top: 20px;
width: 100%;
height: auto;
border: 1px solid $border-color-1;
border-radius: 3px;
padding: 20px;
background: $background-color-1;
text-align: initial;
> div:nth-child(1) > i {
float: right;
font-size: 20px;
color: $danger-icon-color;
}
> div:nth-child(4) {
margin-top: 10px;
}
> div:nth-child(6) > button {
margin-top: 10px;
float: right;
width: 120px;
}
}
}

@ -143,7 +143,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<Form.Item label={<span>Image quality</span>}>
<Tooltip title='Defines image quality level'>
<Tooltip title='Defines image quality level' mouseLeaveDelay={0}>
{form.getFieldDecorator('imageQuality', {
initialValue: 70,
rules: [{
@ -169,7 +169,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<Form.Item label={<span>Overlap size</span>}>
<Tooltip title='Defines a number of intersected frames between different segments'>
<Tooltip title='Defines a number of intersected frames between different segments' mouseLeaveDelay={0}>
{form.getFieldDecorator('overlapSize', {
rules: [{
validator: isNonNegativeInteger,
@ -187,7 +187,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<Form.Item label={<span>Segment size</span>}>
<Tooltip title='Defines a number of frames in a segment'>
<Tooltip title='Defines a number of frames in a segment' mouseLeaveDelay={0}>
{form.getFieldDecorator('segmentSize', {
rules: [{
validator: isPositiveInteger,
@ -422,6 +422,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
More: 1 - 4
</>
)}
mouseLeaveDelay={0}
>
{form.getFieldDecorator('dataChunkSize', {
rules: [{

@ -10,7 +10,7 @@ import Collapse from 'antd/lib/collapse';
import notification from 'antd/lib/notification';
import Text from 'antd/lib/typography/Text';
import FileManagerContainer from 'containers/file-manager/file-manager';
import ConnectedFileManager from 'containers/file-manager/file-manager';
import BasicConfigurationForm, { BaseConfiguration } from './basic-configuration-form';
import AdvancedConfigurationForm, { AdvancedConfiguration } from './advanced-configuration-form';
import LabelsEditor from '../labels-editor/labels-editor';
@ -185,7 +185,7 @@ export default class CreateTaskContent extends React.PureComponent<Props, State>
<Col span={24}>
<Text type='danger'>* </Text>
<Text className='cvat-text-color'>Select files:</Text>
<FileManagerContainer
<ConnectedFileManager
ref={
(container: any): void => { this.fileManagerContainer = container; }
}

@ -8,8 +8,9 @@
text-align: center;
padding-top: 40px;
overflow-y: auto;
height: 100%;
padding-bottom: 40px;
height: 90%;
position: fixed;
width: 100%;
> div > span {
font-size: 36px;

@ -18,7 +18,6 @@ import TasksPageContainer from 'containers/tasks-page/tasks-page';
import CreateTaskPageContainer from 'containers/create-task-page/create-task-page';
import TaskPageContainer from 'containers/task-page/task-page';
import ModelsPageContainer from 'containers/models-page/models-page';
import CreateModelPageContainer from 'containers/create-model-page/create-model-page';
import AnnotationPageContainer from 'containers/annotation-page/annotation-page';
import LoginPageContainer from 'containers/login-page/login-page';
import RegisterPageContainer from 'containers/register-page/register-page';
@ -35,26 +34,30 @@ interface CVATAppProps {
verifyAuthorized: () => void;
loadUserAgreements: () => void;
initPlugins: () => void;
initModels: () => void;
resetErrors: () => void;
resetMessages: () => void;
switchShortcutsDialog: () => void;
switchSettingsDialog: () => void;
loadAuthActions: () => void;
keyMap: Record<string, ExtendedKeyMapOptions>;
userInitialized: boolean;
userFetching: boolean;
pluginsInitialized: boolean;
pluginsFetching: boolean;
modelsInitialized: boolean;
modelsFetching: boolean;
formatsInitialized: boolean;
formatsFetching: boolean;
usersInitialized: boolean;
usersFetching: boolean;
aboutInitialized: boolean;
aboutFetching: boolean;
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
userAgreementsFetching: boolean;
userAgreementsInitialized: boolean;
authActionsFetching: boolean;
authActionsInitialized: boolean;
allowChangePassword: boolean;
notifications: NotificationsState;
user: any;
}
@ -88,6 +91,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
loadAbout,
loadUserAgreements,
initPlugins,
initModels,
loadAuthActions,
userInitialized,
userFetching,
formatsInitialized,
@ -98,9 +103,13 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
aboutFetching,
pluginsInitialized,
pluginsFetching,
modelsInitialized,
modelsFetching,
user,
userAgreementsFetching,
userAgreementsInitialized,
authActionsFetching,
authActionsInitialized,
} = this.props;
this.showErrors();
@ -120,6 +129,10 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
return;
}
if (!authActionsInitialized && !authActionsFetching) {
loadAuthActions();
}
if (!formatsInitialized && !formatsFetching) {
loadFormats();
}
@ -132,6 +145,10 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
loadAbout();
}
if (!modelsInitialized && !modelsFetching) {
initModels();
}
if (!pluginsInitialized && !pluginsFetching) {
initPlugins();
}
@ -159,8 +176,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
let shown = false;
for (const where of Object.keys(notifications.messages)) {
for (const what of Object.keys(notifications.messages[where])) {
const message = notifications.messages[where][what];
for (const what of Object.keys((notifications as any).messages[where])) {
const message = (notifications as any).messages[where][what];
shown = shown || !!message;
if (message) {
showMessage(message);
@ -189,6 +206,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
description: error.length > 200 ? 'Open the Browser Console to get details' : error,
});
// eslint-disable-next-line no-console
console.error(error);
}
@ -199,8 +217,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
let shown = false;
for (const where of Object.keys(notifications.errors)) {
for (const what of Object.keys(notifications.errors[where])) {
const error = notifications.errors[where][what];
for (const what of Object.keys((notifications as any).errors[where])) {
const error = (notifications as any).errors[where][what];
shown = shown || !!error;
if (error) {
showError(error.message, error.reason);
@ -221,9 +239,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
aboutInitialized,
pluginsInitialized,
formatsInitialized,
installedAutoAnnotation,
installedTFSegmentation,
installedTFAnnotation,
switchShortcutsDialog,
switchSettingsDialog,
user,
@ -234,9 +249,6 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
|| (userInitialized && formatsInitialized
&& pluginsInitialized && usersInitialized && aboutInitialized);
const withModels = installedAutoAnnotation
|| installedTFAnnotation || installedTFSegmentation;
const subKeyMap = {
SWITCH_SHORTCUTS: keyMap.SWITCH_SHORTCUTS,
SWITCH_SETTINGS: keyMap.SWITCH_SETTINGS,
@ -261,7 +273,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<GlobalErrorBoundary>
<Layout>
<HeaderContainer> </HeaderContainer>
<Layout.Content>
<Layout.Content style={{ height: '100%' }}>
<ShorcutsDialog />
<GlobalHotKeys keyMap={subKeyMap} handlers={handlers}>
<Switch>
@ -269,10 +281,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
<Route exact path='/tasks/create' component={CreateTaskPageContainer} />
<Route exact path='/tasks/:id' component={TaskPageContainer} />
<Route exact path='/tasks/:tid/jobs/:jid' component={AnnotationPageContainer} />
{withModels
&& <Route exact path='/models' component={ModelsPageContainer} />}
{installedAutoAnnotation
&& <Route exact path='/models/create' component={CreateModelPageContainer} />}
<Route exact path='/models' component={ModelsPageContainer} />
<Redirect push to='/tasks' />
</Switch>
</GlobalHotKeys>

@ -3,10 +3,11 @@
// SPDX-License-Identifier: MIT
.cvat-feedback-button {
position: absolute;
position: fixed;
bottom: 20px;
right: 20px;
padding: 0;
height: auto;
> i {
font-size: 40px;

@ -129,6 +129,8 @@ export default class FileManager extends React.PureComponent<Props, State> {
private renderShareSelector(): JSX.Element {
function renderTreeNodes(data: TreeNodeNormal[]): JSX.Element[] {
// sort alphabetically
data.sort((a: TreeNodeNormal, b: TreeNodeNormal): number => a.key.localeCompare(b.key));
return data.map((item: TreeNodeNormal) => {
if (item.children) {
return (

@ -153,7 +153,7 @@ class GlobalErrorBoundary extends React.PureComponent<Props, State> {
</Paragraph>
<ul>
<li>
<Tooltip title='Copied!' trigger='click'>
<Tooltip title='Copied!' trigger='click' mouseLeaveDelay={0}>
{/* eslint-disable-next-line */}
<a onClick={() => {copy(message)}}> Copy </a>
</Tooltip>

@ -17,16 +17,16 @@ import Text from 'antd/lib/typography/Text';
import { CVATLogo, AccountIcon } from 'icons';
import consts from 'consts';
import ChangePasswordDialog from 'components/change-password-modal/change-password-modal';
import SettingsModal from './settings-modal/settings-modal';
interface HeaderContainerProps {
onLogout: () => void;
switchSettingsDialog: (show: boolean) => void;
switchChangePasswordDialog: (show: boolean) => void;
logoutFetching: boolean;
changePasswordFetching: boolean;
installedAnalytics: boolean;
installedAutoAnnotation: boolean;
installedTFAnnotation: boolean;
installedTFSegmentation: boolean;
serverHost: string;
username: string;
toolName: string;
@ -37,15 +37,14 @@ interface HeaderContainerProps {
uiVersion: string;
switchSettingsShortcut: string;
settingsDialogShown: boolean;
changePasswordDialogShown: boolean;
renderChangePasswordItem: boolean;
}
type Props = HeaderContainerProps & RouteComponentProps;
function HeaderContainer(props: Props): JSX.Element {
const {
installedTFSegmentation,
installedAutoAnnotation,
installedTFAnnotation,
installedAnalytics,
username,
toolName,
@ -57,15 +56,14 @@ function HeaderContainer(props: Props): JSX.Element {
uiVersion,
onLogout,
logoutFetching,
changePasswordFetching,
settingsDialogShown,
switchSettingsShortcut,
switchSettingsDialog,
switchChangePasswordDialog,
renderChangePasswordItem,
} = props;
const renderModels = installedAutoAnnotation
|| installedTFAnnotation
|| installedTFSegmentation;
const {
CHANGELOG_URL,
LICENSE_URL,
@ -146,6 +144,16 @@ function HeaderContainer(props: Props): JSX.Element {
<Icon type='info-circle' />
About
</Menu.Item>
{renderChangePasswordItem && (
<Menu.Item
onClick={(): void => switchChangePasswordDialog(true)}
disabled={changePasswordFetching}
>
{changePasswordFetching ? <Icon type='loading' /> : <Icon type='edit' />}
Change password
</Menu.Item>
)}
<Menu.Item
onClick={onLogout}
disabled={logoutFetching}
@ -172,19 +180,16 @@ function HeaderContainer(props: Props): JSX.Element {
>
Tasks
</Button>
{ renderModels
&& (
<Button
className='cvat-header-button'
type='link'
value='models'
onClick={
(): void => props.history.push('/models')
}
>
Models
</Button>
)}
<Button
className='cvat-header-button'
type='link'
value='models'
onClick={
(): void => props.history.push('/models')
}
>
Models
</Button>
{ installedAnalytics
&& (
<Button
@ -245,6 +250,13 @@ function HeaderContainer(props: Props): JSX.Element {
visible={settingsDialogShown}
onClose={() => switchSettingsDialog(false)}
/>
{ renderChangePasswordItem
&& (
<ChangePasswordDialog
onClose={() => switchChangePasswordDialog(false)}
/>
)}
</Layout.Header>
);
}

@ -27,7 +27,7 @@ export default function ConstructorViewerItem(props: ConstructorViewerItemProps)
return (
<div style={{ background: color }} className='cvat-constructor-viewer-item'>
<Text>{label.name}</Text>
<Tooltip title='Update attributes'>
<Tooltip title='Update attributes' mouseLeaveDelay={0}>
<span
role='button'
tabIndex={0}
@ -39,7 +39,7 @@ export default function ConstructorViewerItem(props: ConstructorViewerItemProps)
</Tooltip>
{ label.id < 0
&& (
<Tooltip title='Delete label'>
<Tooltip title='Delete label' mouseLeaveDelay={0}>
<span
role='button'
tabIndex={0}

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

Loading…
Cancel
Save