Merge pull request #2107 from opencv/release-1.1.0

Release 1.1.0
main
Nikita Manovich 6 years ago committed by GitHub
commit 8eb7c13619
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitattributes vendored

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

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

2
.gitignore vendored

@ -7,12 +7,12 @@
/.env /.env
/keys /keys
/logs /logs
/components/openvino/*.tgz
/profiles /profiles
/ssh/* /ssh/*
!/ssh/README.md !/ssh/README.md
node_modules node_modules
/Mask_RCNN/ /Mask_RCNN/
/letsencrypt-webroot/
# Ignore temporary files # Ignore temporary files
docker-compose.override.yml docker-compose.override.yml

@ -20,7 +20,7 @@ persistent=yes
# List of plugins (as comma separated values of python modules names) to load, # List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers. # usually to register additional checkers.
load-plugins= load-plugins=pylint_django
# Use multiple processes to speed up Pylint. # Use multiple processes to speed up Pylint.
jobs=1 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, W0122,W0124,W0150,W0199,W0221,W0222,W0233,W0404,W0410,W0601,
W0602,W0604,W0611,W0612,W0622,W0623,W0702,W0705,W0711,W1300, W0602,W0604,W0611,W0612,W0622,W0623,W0702,W0705,W0711,W1300,
W1301,W1302,W1303,,W1305,W1306,W1307 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 # Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this # can either give multiple identifiers separated by comma (,) or put this

@ -0,0 +1,20 @@
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4,
"value-keyword-case": null,
"selector-combinator-space-after": null,
"no-descending-specificity": null,
"at-rule-no-unknown": [true, {
"ignoreAtRules": ["extend"]
}],
"selector-type-no-unknown": [true, {
"ignoreTypes": ["first-child"]
}]
},
"ignoreFiles": [
"**/*.js",
"**/*.ts",
"**/*.py"
]
}

@ -5,12 +5,29 @@ language: python
python: python:
- "3.5" - "3.5"
cache:
npm: true
directories:
- ~/.cache
addons:
apt:
packages:
- libgconf-2-4
services: services:
- docker - docker
env: env:
- CONTAINER_COVERAGE_DATA_DIR="/coverage_data" - CONTAINER_COVERAGE_DATA_DIR="/coverage_data"
HOST_COVERAGE_DATA_DIR="${TRAVIS_BUILD_DIR}" 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: before_script:
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml build - 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 # 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 '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' - 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: after_success:
# https://coveralls-python.readthedocs.io/en/latest/usage/multilang.html # https://coveralls-python.readthedocs.io/en/latest/usage/multilang.html

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

@ -4,6 +4,131 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.1.0] - 2020-08-31
### Added
- 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>)
- Ability to change label color in tasks and predefined labels (<https://github.com/opencv/cvat/pull/2014>)
- [Datumaro] Multi-dataset merge (https://github.com/opencv/cvat/pull/1695)
- Ability to configure email verification for new users (<https://github.com/opencv/cvat/pull/1929>)
- Link to django admin page from UI (<https://github.com/opencv/cvat/pull/2068>)
- Notification message when users use wrong browser (<https://github.com/opencv/cvat/pull/2070>)
### Changed
- 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>)
### 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
- Fixed a case in which exported masks could have wrong color order (<https://github.com/opencv/cvat/issues/2032>)
- Fixed error with creating task with labels with the same name (<https://github.com/opencv/cvat/pull/2031>)
- Django RQ dashboard view (<https://github.com/opencv/cvat/pull/2069>)
## [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>)
- Cannot read property 'shapeType' of undefined because of zOrder related issues (<https://github.com/opencv/cvat/pull/1874>)
- Cannot read property 'pinned' of undefined because of zOrder related issues (<https://github.com/opencv/cvat/pull/1874>)
- Do not iterate over hidden objects in aam (which are invisible because of zOrder) (<https://github.com/opencv/cvat/pull/1874>)
- Cursor position is reset after changing a text field (<https://github.com/opencv/cvat/pull/1874>)
- Hidden points and cuboids can be selected to be groupped (<https://github.com/opencv/cvat/pull/1874>)
- `outside` annotations should not be in exported images (<https://github.com/opencv/cvat/issues/1620>)
- `CVAT for video format` import error with interpolation (<https://github.com/opencv/cvat/issues/1893>)
- `Image compression` definition mismatch (<https://github.com/opencv/cvat/issues/1900>)
- 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>)
- 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
- Throttling policy for unauthenticated users (<https://github.com/opencv/cvat/pull/1531>)
- Added default label color table for mask export (<https://github.com/opencv/cvat/pull/1549>)
- Added environment variables for Redis and Postgres hosts for Kubernetes deployment support (<https://github.com/opencv/cvat/pull/1641>)
- Added visual identification for unavailable formats (<https://github.com/opencv/cvat/pull/1567>)
- Shortcut to change color of an activated shape in new UI (Enter) (<https://github.com/opencv/cvat/pull/1683>)
- Shortcut to switch split mode (<https://github.com/opencv/cvat/pull/1683>)
- Built-in search for labels when create an object or change a label (<https://github.com/opencv/cvat/pull/1683>)
- Better validation of labels and attributes in raw viewer (<https://github.com/opencv/cvat/pull/1727>)
- ClamAV antivirus integration (<https://github.com/opencv/cvat/pull/1712>)
- Added canvas background color selector (<https://github.com/opencv/cvat/pull/1705>)
- SCSS files linting with Stylelint tool (<https://github.com/opencv/cvat/pull/1766>)
- Supported import and export or single boxes in MOT format (https://github.com/opencv/cvat/pull/1764)
- [Datumaro] Added `stats` command, which shows some dataset statistics like image mean and std (https://github.com/opencv/cvat/pull/1734)
- Add option to upload annotations upon task creation on CLI
- Polygon and polylines interpolation (<https://github.com/opencv/cvat/pull/1571>)
- Ability to redraw shape from scratch (Shift + N) for an activated shape (<https://github.com/opencv/cvat/pull/1571>)
- Highlights for the first point of a polygon/polyline and direction (<https://github.com/opencv/cvat/pull/1571>)
- Ability to change orientation for poylgons/polylines in context menu (<https://github.com/opencv/cvat/pull/1571>)
- Ability to set the first point for polygons in points context menu (<https://github.com/opencv/cvat/pull/1571>)
- Added new tag annotation workspace (<https://github.com/opencv/cvat/pull/1570>)
- Appearance block in attribute annotation mode (<https://github.com/opencv/cvat/pull/1820>)
- Keyframe navigations and some switchers in attribute annotation mode (<https://github.com/opencv/cvat/pull/1820>)
- [Datumaro] Added `convert` command to convert datasets directly (<https://github.com/opencv/cvat/pull/1837>)
- [Datumaro] Added an option to specify image extension when exporting datasets (<https://github.com/opencv/cvat/pull/1799>)
- [Datumaro] Added image copying when exporting datasets, if possible (<https://github.com/opencv/cvat/pull/1799>)
### Changed
- Removed information about e-mail from the basic user information (<https://github.com/opencv/cvat/pull/1627>)
- Update https install manual. Makes it easier and more robust. Includes automatic renewing of lets encrypt certificates.
- Settings page move to the modal. (<https://github.com/opencv/cvat/pull/1705>)
- Implemented import and export of annotations with relative image paths (<https://github.com/opencv/cvat/pull/1463>)
- Using only single click to start editing or remove a point (<https://github.com/opencv/cvat/pull/1571>)
- Added support for attributes in VOC XML format (https://github.com/opencv/cvat/pull/1792)
- Added annotation attributes in COCO format (https://github.com/opencv/cvat/pull/1782)
- 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>)
### 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>)
- Fixed interpreter crash when trying to import `tensorflow` with no AVX instructions available (<https://github.com/opencv/cvat/pull/1567>)
- Kibana wrong working time calculation with new annotation UI use (<https://github.com/opencv/cvat/pull/1654>)
- Wrong rexex for account name validation (<https://github.com/opencv/cvat/pull/1667>)
- Wrong description on register view for the username field (<https://github.com/opencv/cvat/pull/1667>)
- Wrong resolution for resizing a shape (<https://github.com/opencv/cvat/pull/1667>)
- React warning because of not unique keys in labels viewer (<https://github.com/opencv/cvat/pull/1727>)
- Fixed issue tracker (<https://github.com/opencv/cvat/pull/1705>)
- Fixed canvas fit after sidebar open/close event (<https://github.com/opencv/cvat/pull/1705>)
- A couple of exceptions in AAM related with early object activation (<https://github.com/opencv/cvat/pull/1755>)
- Propagation from the latest frame (<https://github.com/opencv/cvat/pull/1800>)
- Number attribute value validation (didn't work well with floats) (<https://github.com/opencv/cvat/pull/1800>)
- Logout doesn't work (<https://github.com/opencv/cvat/pull/1812>)
- Annotations aren't updated after reopening a task (<https://github.com/opencv/cvat/pull/1753>)
- Labels aren't updated after reopening a task (<https://github.com/opencv/cvat/pull/1753>)
- Canvas isn't fitted after collapsing side panel in attribute annotation mode (<https://github.com/opencv/cvat/pull/1753>)
- Error when interpolating polygons (<https://github.com/opencv/cvat/pull/1878>)
### Security
- SQL injection in Django `CVE-2020-9402` (<https://github.com/opencv/cvat/pull/1657>)
## [1.0.0] - 2020-05-29 ## [1.0.0] - 2020-05-29
### Added ### Added
- cvat-ui: cookie policy drawer for login page (<https://github.com/opencv/cvat/pull/1511>) - cvat-ui: cookie policy drawer for login page (<https://github.com/opencv/cvat/pull/1511>)
@ -54,6 +179,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- A problem with mask to polygons conversion when polygons are too small (<https://github.com/opencv/cvat/pull/1581>) - A problem with mask to polygons conversion when polygons are too small (<https://github.com/opencv/cvat/pull/1581>)
- Unable to upload video with uneven size (<https://github.com/opencv/cvat/pull/1594>) - Unable to upload video with uneven size (<https://github.com/opencv/cvat/pull/1594>)
- Fixed an issue with `z_order` having no effect on segmentations (<https://github.com/opencv/cvat/pull/1589>) - Fixed an issue with `z_order` having no effect on segmentations (<https://github.com/opencv/cvat/pull/1589>)
### Security
- Permission group whitelist check for analytics view (<https://github.com/opencv/cvat/pull/1608>) - Permission group whitelist check for analytics view (<https://github.com/opencv/cvat/pull/1608>)
## [1.0.0-beta.2] - 2020-04-30 ## [1.0.0-beta.2] - 2020-04-30

File diff suppressed because one or more lines are too long

@ -40,7 +40,6 @@ RUN apt-get update && \
libavfilter-dev \ libavfilter-dev \
libavformat-dev \ libavformat-dev \
libavutil-dev \ libavutil-dev \
libldap2-dev \
libswresample-dev \ libswresample-dev \
libswscale-dev \ libswscale-dev \
libldap2-dev \ libldap2-dev \
@ -56,7 +55,7 @@ RUN apt-get update && \
curl && \ curl && \
curl https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \ curl https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \
apt-get --no-install-recommends install -y git-lfs && git lfs install && \ apt-get --no-install-recommends install -y git-lfs && git lfs install && \
python3 -m pip install --no-cache-dir -U pip==20.0.1 setuptools && \ python3 -m pip install --no-cache-dir -U pip==20.0.1 setuptools==49.6.0 wheel==0.35.1 && \
ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime && \ ln -fs /usr/share/zoneinfo/${TZ} /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata && \ dpkg-reconfigure -f noninteractive tzdata && \
add-apt-repository --remove ppa:mc3man/gstffmpeg-keep -y && \ add-apt-repository --remove ppa:mc3man/gstffmpeg-keep -y && \
@ -77,33 +76,6 @@ RUN adduser --shell /bin/bash --disabled-password --gecos "" ${USER} && \
COPY components /tmp/components 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 # Install and initialize CVAT, copy all necessary files
COPY cvat/requirements/ /tmp/requirements/ COPY cvat/requirements/ /tmp/requirements/
COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/
@ -111,22 +83,17 @@ 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 # 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 RUN python3 -m pip install --no-cache-dir pycocotools==2.0.0
ARG CLAM_AV
# CUDA support ENV CLAM_AV=${CLAM_AV}
ARG CUDA_SUPPORT RUN if [ "$CLAM_AV" = "yes" ]; then \
ENV CUDA_SUPPORT=${CUDA_SUPPORT} apt-get update && \
RUN if [ "$CUDA_SUPPORT" = "yes" ]; then \ apt-get --no-install-recommends install -yq \
/tmp/components/cuda/install.sh; \ clamav \
fi libclamunrar9 && \
sed -i 's/ReceiveTimeout 30/ReceiveTimeout 300/g' /etc/clamav/freshclam.conf && \
# TODO: CHANGE URL freshclam && \
ARG WITH_DEXTR chown -R ${USER}:${USER} /var/lib/clamav && \
ENV WITH_DEXTR=${WITH_DEXTR} rm -rf /var/lib/apt/lists/*; \
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 fi
COPY ssh ${HOME}/.ssh COPY ssh ${HOME}/.ssh
@ -139,9 +106,6 @@ COPY datumaro/ ${HOME}/datumaro
RUN python3 -m pip install --no-cache-dir -r ${HOME}/datumaro/requirements.txt 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 chown -R ${USER}:${USER} .
# RUN all commands below as 'django' user # RUN all commands below as 'django' user

@ -1,11 +1,11 @@
FROM cvat FROM cvat/server
ENV DJANGO_CONFIGURATION=testing ENV DJANGO_CONFIGURATION=testing
USER root USER root
RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ 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 && \ 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 && \ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -yq \ DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -yq \
apt-utils \ apt-utils \
@ -23,19 +23,4 @@ RUN gem install coveralls-lcov
COPY .coveragerc . 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 [] ENTRYPOINT []

@ -21,6 +21,8 @@ COPY cvat-canvas/package*.json /tmp/cvat-canvas/
COPY cvat-ui/package*.json /tmp/cvat-ui/ COPY cvat-ui/package*.json /tmp/cvat-ui/
COPY cvat-data/package*.json /tmp/cvat-data/ COPY cvat-data/package*.json /tmp/cvat-data/
RUN npm config set loglevel info
# Install cvat-data dependencies # Install cvat-data dependencies
WORKDIR /tmp/cvat-data/ WORKDIR /tmp/cvat-data/
RUN npm install RUN npm install

@ -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) [![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) [![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) [![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) [![DOI](https://zenodo.org/badge/139156354.svg)](https://zenodo.org/badge/latestdoi/139156354)
CVAT is free, online, interactive video and image annotation CVAT is free, online, interactive video and image annotation
tool for computer vision. It is being used by our team to tool for computer vision. It is being used by our team to
annotate million of objects with different properties. Many UI annotate million of objects with different properties. Many UI
and UX decisions are based on feedbacks from professional data annotation team. and UX decisions are based on feedbacks from professional data
Try it online [cvat.org](https://cvat.org). annotation team. Try it online [cvat.org](https://cvat.org).
![CVAT screenshot](cvat/apps/documentation/static/documentation/images/cvat.jpg) ![CVAT screenshot](cvat/apps/documentation/static/documentation/images/cvat.jpg)
@ -24,17 +23,20 @@ Try it online [cvat.org](https://cvat.org).
- [Command line interface](utils/cli/) - [Command line interface](utils/cli/)
- [XML annotation format](cvat/apps/documentation/xml_format.md) - [XML annotation format](cvat/apps/documentation/xml_format.md)
- [AWS Deployment Guide](cvat/apps/documentation/AWS-Deployment-Guide.md) - [AWS Deployment Guide](cvat/apps/documentation/AWS-Deployment-Guide.md)
- [Frequently asked questions](cvat/apps/documentation/faq.md)
- [Questions](#questions) - [Questions](#questions)
## Screencasts ## Screencasts
- [Introduction](https://youtu.be/L9_IvUIHGwM) - [Introduction](https://youtu.be/JERohTFp-NI)
- [Annotation mode](https://youtu.be/6h7HxGL6Ct4) - [Annotation mode](https://youtu.be/vH_639N67HI)
- [Interpolation mode](https://youtu.be/U3MYDhESHo4) - [Interpolation of bounding boxes](https://youtu.be/Hc3oudNuDsY)
- [Attribute mode](https://youtu.be/UPNfWl8Egd8) - [Interpolation of polygons](https://youtu.be/K4nis9lk92s)
- [Segmentation mode](https://youtu.be/Fh8oKuSUIPs) - [Tag_annotation_video](https://youtu.be/62bI4mF-Xfk)
- [Tutorial for polygons](https://www.youtube.com/watch?v=XTwfXDh4clI) - [Attribute mode](https://youtu.be/iIkJsOkDzVA)
- [Semi-automatic segmentation](https://www.youtube.com/watch?v=vnqXZ-Z-VTQ) - [Segmentation mode](https://youtu.be/9Fe_GzMLo3E)
- [Tutorial for polygons](https://youtu.be/C7-r9lZbjBw)
- [Semi-automatic segmentation](https://youtu.be/9HszWP_qsRQ)
## Supported annotation formats ## Supported annotation formats
@ -56,10 +58,18 @@ via its command line tool and Python library.
| [MOT](https://motchallenge.net/) | X | X | | [MOT](https://motchallenge.net/) | X | X |
| [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X | | [LabelMe 3.0](http://labelme.csail.mit.edu/Release3.0) | X | X |
## Links ## Deep learning models for automatic labeling
- [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) | Name | Type | Framework |
- [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 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) ## Online demo: [cvat.org](https://cvat.org)
@ -69,7 +79,6 @@ are visible to users.
Disabled features: Disabled features:
- [Analytics: management and monitoring of data annotation team](/components/analytics/README.md) - [Analytics: management and monitoring of data annotation team](/components/analytics/README.md)
- [Support for NVIDIA GPUs](/components/cuda/README.md)
Limitations: Limitations:
- No more than 10 tasks per user - No more than 10 tasks per user
@ -103,3 +112,8 @@ If you are not sure or just want to browse other users common questions,
Other ways to ask questions and get our support: Other ways to ask questions and get our support:
* [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow* * [\#cvat](https://stackoverflow.com/search?q=%23cvat) tag on StackOverflow*
* [Forum on Intel Developer Zone](https://software.intel.com/en-us/forums/computer-vision) * [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: cvat_kibana_setup:
container_name: cvat_kibana_setup container_name: cvat_kibana_setup
image: cvat image: cvat/server
volumes: ['./components/analytics/kibana:/home/django/kibana:ro'] volumes: ['./components/analytics/kibana:/home/django/kibana:ro']
depends_on: ['cvat'] depends_on: ['cvat']
working_dir: '/home/django' working_dir: '/home/django'
@ -63,7 +63,7 @@ services:
DJANGO_LOG_SERVER_PORT: 5000 DJANGO_LOG_SERVER_PORT: 5000
DJANGO_LOG_VIEWER_HOST: kibana DJANGO_LOG_VIEWER_HOST: kibana
DJANGO_LOG_VIEWER_PORT: 5601 DJANGO_LOG_VIEWER_PORT: 5601
no_proxy: kibana,logstash,${no_proxy} no_proxy: kibana,logstash,nuclio,${no_proxy}
volumes: volumes:
cvat_events: cvat_events:

@ -99,10 +99,10 @@
"_id": "ec510550-c238-11e8-8e1b-758ef07f6de8", "_id": "ec510550-c238-11e8-8e1b-758ef07f6de8",
"_type": "index-pattern", "_type": "index-pattern",
"_source": { "_source": {
"fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@version\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@version.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"application\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"application.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"box count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"duration\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event\",\"type\":\"string\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"event.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"frame count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"object count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"points count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"polygon count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"polyline count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"task\",\"type\":\"string\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"task.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"track count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"userid\",\"type\":\"string\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"userid.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"working time\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"@version\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@version.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"application\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"application.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"box count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"duration\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event\",\"type\":\"string\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"event.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"frame count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"object count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"points count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"polygon count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"polyline count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"task\",\"type\":\"string\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"task.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"timestamp\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"track count\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"userid\",\"type\":\"string\",\"count\":2,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"userid.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"working_time\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]",
"title": "cvat*", "title": "cvat*",
"timeFieldName": "@timestamp", "timeFieldName": "@timestamp",
"fieldFormatMap": "{\"duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\",\"outputFormat\":\"asSeconds\"}},\"working time\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\",\"outputFormat\":\"asHours\"}}}" "fieldFormatMap": "{\"duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\",\"outputFormat\":\"asSeconds\"}},\"working_time\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\",\"outputFormat\":\"asHours\"}}}"
}, },
"_meta": { "_meta": {
"savedObjectVersion": 2 "savedObjectVersion": 2
@ -147,7 +147,7 @@
"_type": "visualization", "_type": "visualization",
"_source": { "_source": {
"title": "List of users", "title": "List of users",
"visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"2\",\"params\":{\"customLabel\":\"User\",\"field\":\"userid.keyword\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"order\":\"asc\",\"orderBy\":\"_key\",\"otherBucket\":true,\"otherBucketLabel\":\"Other\",\"size\":1000},\"schema\":\"bucket\",\"type\":\"terms\"},{\"enabled\":true,\"id\":\"3\",\"params\":{\"customBucket\":{\"enabled\":true,\"id\":\"3-bucket\",\"params\":{\"customInterval\":\"2h\",\"extended_bounds\":{},\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1},\"schema\":{\"aggFilter\":[],\"deprecate\":false,\"editor\":false,\"group\":\"none\",\"max\":null,\"min\":0,\"name\":\"bucketAgg\",\"params\":[],\"title\":\"Bucket Agg\"},\"type\":\"date_histogram\"},\"customLabel\":\"Activity\",\"customMetric\":{\"enabled\":true,\"id\":\"3-metric\",\"params\":{},\"schema\":{\"aggFilter\":[\"!top_hits\",\"!percentiles\",\"!percentile_ranks\",\"!median\",\"!std_dev\",\"!sum_bucket\",\"!avg_bucket\",\"!min_bucket\",\"!max_bucket\",\"!derivative\",\"!moving_avg\",\"!serial_diff\",\"!cumulative_sum\"],\"deprecate\":false,\"editor\":false,\"group\":\"none\",\"max\":null,\"min\":0,\"name\":\"metricAgg\",\"params\":[],\"title\":\"Metric Agg\"},\"type\":\"count\"}},\"schema\":\"metric\",\"type\":\"sum_bucket\"},{\"enabled\":true,\"id\":\"1\",\"params\":{\"customLabel\":\"Working Time (h)\",\"field\":\"working time\"},\"schema\":\"metric\",\"type\":\"sum\"}],\"params\":{\"perPage\":20,\"showMetricsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\"},\"title\":\"List of users\",\"type\":\"table\"}", "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"2\",\"params\":{\"customLabel\":\"User\",\"field\":\"userid.keyword\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"order\":\"asc\",\"orderBy\":\"_key\",\"otherBucket\":true,\"otherBucketLabel\":\"Other\",\"size\":1000},\"schema\":\"bucket\",\"type\":\"terms\"},{\"enabled\":true,\"id\":\"3\",\"params\":{\"customBucket\":{\"enabled\":true,\"id\":\"3-bucket\",\"params\":{\"customInterval\":\"2h\",\"extended_bounds\":{},\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1},\"schema\":{\"aggFilter\":[],\"deprecate\":false,\"editor\":false,\"group\":\"none\",\"max\":null,\"min\":0,\"name\":\"bucketAgg\",\"params\":[],\"title\":\"Bucket Agg\"},\"type\":\"date_histogram\"},\"customLabel\":\"Activity\",\"customMetric\":{\"enabled\":true,\"id\":\"3-metric\",\"params\":{},\"schema\":{\"aggFilter\":[\"!top_hits\",\"!percentiles\",\"!percentile_ranks\",\"!median\",\"!std_dev\",\"!sum_bucket\",\"!avg_bucket\",\"!min_bucket\",\"!max_bucket\",\"!derivative\",\"!moving_avg\",\"!serial_diff\",\"!cumulative_sum\"],\"deprecate\":false,\"editor\":false,\"group\":\"none\",\"max\":null,\"min\":0,\"name\":\"metricAgg\",\"params\":[],\"title\":\"Metric Agg\"},\"type\":\"count\"}},\"schema\":\"metric\",\"type\":\"sum_bucket\"},{\"enabled\":true,\"id\":\"1\",\"params\":{\"customLabel\":\"Working Time (h)\",\"field\":\"working_time\"},\"schema\":\"metric\",\"type\":\"sum\"}],\"params\":{\"perPage\":20,\"showMetricsAtAllLevels\":false,\"showPartialRows\":false,\"showTotal\":false,\"sort\":{\"columnIndex\":null,\"direction\":null},\"totalFunc\":\"sum\"},\"title\":\"List of users\",\"type\":\"table\"}",
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}", "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":null,\"direction\":null}}}}",
"description": "", "description": "",
"version": 1, "version": 1,
@ -164,7 +164,7 @@
"_type": "visualization", "_type": "visualization",
"_source": { "_source": {
"title": "List of tasks", "title": "List of tasks",
"visState": "{\"title\":\"List of tasks\",\"type\":\"table\",\"params\":{\"perPage\":20,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":2,\"direction\":\"desc\"},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"schema\":\"metric\",\"params\":{\"field\":\"working time\",\"customLabel\":\"Working time (h)\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"task.keyword\",\"size\":1000,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Task\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"userid.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"_key\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"User\"}}]}", "visState": "{\"title\":\"List of tasks\",\"type\":\"table\",\"params\":{\"perPage\":20,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"sort\":{\"columnIndex\":2,\"direction\":\"desc\"},\"showTotal\":false,\"totalFunc\":\"sum\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"schema\":\"metric\",\"params\":{\"field\":\"working_time\",\"customLabel\":\"Working time (h)\"}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"task.keyword\",\"size\":1000,\"order\":\"desc\",\"orderBy\":\"1\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Task\"}},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"bucket\",\"params\":{\"field\":\"userid.keyword\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"_key\",\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"User\"}}]}",
"uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":2,\"direction\":\"desc\"}}}}", "uiStateJSON": "{\"vis\":{\"params\":{\"sort\":{\"columnIndex\":2,\"direction\":\"desc\"}}}}",
"description": "", "description": "",
"version": 1, "version": 1,

@ -65,6 +65,7 @@ filter {
mutate { mutate {
replace => { "type" => "client" } replace => { "type" => "client" }
rename => ["working time", "working_time"]
copy => { copy => {
"job_id" => "task" "job_id" => "task"
"username" => "userid" "username" => "userid"

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

@ -117,6 +117,7 @@ Canvas itself handles:
mode(): Mode; mode(): Mode;
cancel(): void; cancel(): void;
configure(configuration: Configuration): void; configure(configuration: Configuration): void;
isAbleToChangeFrame(): boolean;
} }
``` ```
@ -188,8 +189,7 @@ Standard JS events are used.
| | IDLE | GROUP | SPLIT | DRAW | MERGE | EDIT | DRAG | RESIZE | ZOOM_CANVAS | DRAG_CANVAS | | | IDLE | GROUP | SPLIT | DRAW | MERGE | EDIT | DRAG | RESIZE | ZOOM_CANVAS | DRAG_CANVAS |
|--------------|------|-------|-------|------|-------|------|------|--------|-------------|-------------| |--------------|------|-------|-------|------|-------|------|------|--------|-------------|-------------|
| html() | + | + | + | + | + | + | + | + | + | + | | setup() | + | + | + | +/- | + | +/- | +/- | +/- | + | + |
| setup() | + | + | + | + | + | +/- | +/- | +/- | + | + |
| activate() | + | - | - | - | - | - | - | - | - | - | | activate() | + | - | - | - | - | - | - | - | - | - |
| rotate() | + | + | + | + | + | + | + | + | + | + | | rotate() | + | + | + | + | + | + | + | + | + | + |
| focus() | + | + | + | + | + | + | + | + | + | + | | focus() | + | + | + | + | + | + | + | + | + | + |
@ -208,3 +208,6 @@ Standard JS events are used.
| setZLayer() | + | + | + | + | + | + | + | + | + | + | | setZLayer() | + | + | + | + | + | + | + | + | + | + |
You can call setup() during editing, dragging, and resizing only to update objects, not to change a frame. You can call setup() during editing, dragging, and resizing only to update objects, not to change a frame.
You can change frame during draw only when you do not redraw an existing object
Other methods do not change state and can be used everytime.

@ -1,6 +1,6 @@
{ {
"name": "cvat-canvas", "name": "cvat-canvas",
"version": "1.1.1", "version": "2.0.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -3223,9 +3223,9 @@
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
"version": "6.5.0", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true, "dev": true,
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.4.0",
@ -5949,9 +5949,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"dev": true "dev": true
}, },
"lodash._reinterpolate": { "lodash._reinterpolate": {
@ -10431,9 +10431,9 @@
} }
}, },
"websocket-extensions": { "websocket-extensions": {
"version": "0.1.3", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true "dev": true
}, },
"which": { "which": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-canvas", "name": "cvat-canvas",
"version": "1.1.1", "version": "2.0.2",
"description": "Part of Computer Vision Annotation Tool which presents its canvas library", "description": "Part of Computer Vision Annotation Tool which presents its canvas library",
"main": "src/canvas.ts", "main": "src/canvas.ts",
"scripts": { "scripts": {

@ -42,7 +42,7 @@ polyline.cvat_shape_drawing_opacity {
fill: white; fill: white;
cursor: default; cursor: default;
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif; font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
text-shadow: 0px 0px 4px black; text-shadow: 0 0 4px black;
user-select: none; user-select: none;
pointer-events: none; pointer-events: none;
} }
@ -54,47 +54,55 @@ polyline.cvat_shape_drawing_opacity {
.cvat_canvas_shape_grouping { .cvat_canvas_shape_grouping {
@extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_dasharray;
@extend .cvat_shape_action_opacity; @extend .cvat_shape_action_opacity;
fill: darkmagenta; fill: darkmagenta;
} }
polyline.cvat_canvas_shape_grouping { polyline.cvat_canvas_shape_grouping {
@extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_dasharray;
@extend .cvat_shape_action_opacity; @extend .cvat_shape_action_opacity;
stroke: darkmagenta; stroke: darkmagenta;
} }
.cvat_canvas_shape_merging { .cvat_canvas_shape_merging {
@extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_dasharray;
@extend .cvat_shape_action_opacity; @extend .cvat_shape_action_opacity;
fill: blue; fill: blue;
} }
polyline.cvat_canvas_shape_merging { polyline.cvat_canvas_shape_merging {
@extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_dasharray;
@extend .cvat_shape_action_opacity; @extend .cvat_shape_action_opacity;
stroke: blue; stroke: blue;
} }
polyline.cvat_canvas_shape_splitting { polyline.cvat_canvas_shape_splitting {
@extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_dasharray;
@extend .cvat_shape_action_opacity; @extend .cvat_shape_action_opacity;
stroke: dodgerblue; stroke: dodgerblue;
} }
.cvat_canvas_shape_splitting { .cvat_canvas_shape_splitting {
@extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_dasharray;
@extend .cvat_shape_action_opacity; @extend .cvat_shape_action_opacity;
fill: dodgerblue; fill: dodgerblue;
} }
.cvat_canvas_shape_drawing { .cvat_canvas_shape_drawing {
@extend .cvat_shape_drawing_opacity; @extend .cvat_shape_drawing_opacity;
fill: white; fill: white;
stroke: black; stroke: black;
} }
.cvat_canvas_zoom_selection { .cvat_canvas_zoom_selection {
@extend .cvat_shape_action_dasharray; @extend .cvat_shape_action_dasharray;
stroke: #096dd9; stroke: #096dd9;
fill-opacity: 0; fill-opacity: 0;
} }
@ -103,7 +111,8 @@ polyline.cvat_canvas_shape_splitting {
stroke-dasharray: 5; stroke-dasharray: 5;
} }
.cvat_canvas_shape .svg_select_points, .cvat_canvas_shape .cvat_canvas_cuboid_projections { .cvat_canvas_shape .svg_select_points,
.cvat_canvas_shape .cvat_canvas_cuboid_projections {
stroke-dasharray: none; stroke-dasharray: none;
} }
@ -130,20 +139,24 @@ polyline.cvat_canvas_shape_splitting {
pointer-events: none; pointer-events: none;
} }
.svg_select_points_lb:hover, .svg_select_points_rt:hover { .svg_select_points_lb:hover,
.svg_select_points_rt:hover {
cursor: nesw-resize; cursor: nesw-resize;
} }
.svg_select_points_lt:hover, .svg_select_points_rb:hover { .svg_select_points_lt:hover,
.svg_select_points_rb:hover {
cursor: nwse-resize; cursor: nwse-resize;
} }
.svg_select_points_l:hover, .svg_select_points_r:hover, .svg_select_points_l:hover,
.svg_select_points_r:hover,
.svg_select_points_ew:hover { .svg_select_points_ew:hover {
cursor: ew-resize; cursor: ew-resize;
} }
.svg_select_points_t:hover, .svg_select_points_b:hover { .svg_select_points_t:hover,
.svg_select_points_b:hover {
cursor: ns-resize; cursor: ns-resize;
} }
@ -151,12 +164,31 @@ polyline.cvat_canvas_shape_splitting {
cursor: move; cursor: move;
} }
.cvat_canvas_first_poly_point {
fill: lightgray;
}
.cvat_canvas_poly_direction {
fill: lightgray;
stroke: black;
&:hover {
fill: black;
stroke: lightgray;
}
&:active {
fill: lightgray;
stroke: black;
}
}
#cvat_canvas_wrapper { #cvat_canvas_wrapper {
width: calc(100% - 10px); width: calc(100% - 10px);
height: calc(100% - 10px); height: calc(100% - 10px);
margin: 5px; margin: 5px;
border-radius: 5px; border-radius: 5px;
background-color: white; background-color: inherit;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
@ -183,7 +215,6 @@ polyline.cvat_canvas_shape_splitting {
pointer-events: none; pointer-events: none;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none;
} }
#cvat_canvas_background { #cvat_canvas_background {
@ -192,7 +223,7 @@ polyline.cvat_canvas_shape_splitting {
background-repeat: no-repeat; background-repeat: no-repeat;
width: 100%; width: 100%;
height: 100%; height: 100%;
box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75); box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.75);
} }
#cvat_canvas_bitmap { #cvat_canvas_bitmap {
@ -202,7 +233,7 @@ polyline.cvat_canvas_shape_splitting {
background: black; background: black;
width: 100%; width: 100%;
height: 100%; height: 100%;
box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.75); box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.75);
} }
#cvat_canvas_grid { #cvat_canvas_grid {
@ -211,7 +242,6 @@ polyline.cvat_canvas_shape_splitting {
pointer-events: none; pointer-events: none;
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none;
} }
#cvat_canvas_grid_pattern { #cvat_canvas_grid_pattern {
@ -229,7 +259,18 @@ polyline.cvat_canvas_shape_splitting {
} }
@keyframes loadingAnimation { @keyframes loadingAnimation {
0% {stroke-dashoffset: 1; stroke: #09c;} 0% {
50% {stroke-dashoffset: 100; stroke: #f44;} stroke-dashoffset: 1;
100% {stroke-dashoffset: 300; stroke: #09c;} stroke: #09c;
}
50% {
stroke-dashoffset: 100;
stroke: #f44;
}
100% {
stroke-dashoffset: 300;
stroke: #09c;
}
} }

@ -13,14 +13,14 @@ interface TransformedShape {
} }
export interface AutoborderHandler { export interface AutoborderHandler {
autoborder(enabled: boolean, currentShape?: SVG.Shape, ignoreCurrent?: boolean): void; autoborder(enabled: boolean, currentShape?: SVG.Shape, currentID?: number): void;
transform(geometry: Geometry): void; transform(geometry: Geometry): void;
updateObjects(): void; updateObjects(): void;
} }
export class AutoborderHandlerImpl implements AutoborderHandler { export class AutoborderHandlerImpl implements AutoborderHandler {
private currentShape: SVG.Shape | null; private currentShape: SVG.Shape | null;
private ignoreCurrent: boolean; private currentID?: number;
private frameContent: SVGSVGElement; private frameContent: SVGSVGElement;
private enabled: boolean; private enabled: boolean;
private scale: number; private scale: number;
@ -34,7 +34,7 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
public constructor(frameContent: SVGSVGElement) { public constructor(frameContent: SVGSVGElement) {
this.frameContent = frameContent; this.frameContent = frameContent;
this.ignoreCurrent = false; this.currentID = undefined;
this.currentShape = null; this.currentShape = null;
this.enabled = false; this.enabled = false;
this.scale = 1; this.scale = 1;
@ -231,7 +231,8 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
this.removeMarkers(); this.removeMarkers();
const currentClientID = this.currentShape.node.dataset.originClientId; const currentClientID = this.currentShape.node.dataset.originClientId;
const shapes = Array.from(this.frameContent.getElementsByClassName('cvat_canvas_shape')); const shapes = Array.from(this.frameContent.getElementsByClassName('cvat_canvas_shape'))
.filter((shape: HTMLElement): boolean => +shape.getAttribute('clientID') !== this.currentID);
const transformedShapes = shapes.map((shape: HTMLElement): TransformedShape | null => { const transformedShapes = shapes.map((shape: HTMLElement): TransformedShape | null => {
const color = shape.getAttribute('fill'); const color = shape.getAttribute('fill');
const clientID = shape.getAttribute('clientID'); const clientID = shape.getAttribute('clientID');
@ -277,12 +278,12 @@ export class AutoborderHandlerImpl implements AutoborderHandler {
public autoborder( public autoborder(
enabled: boolean, enabled: boolean,
currentShape?: SVG.Shape, currentShape?: SVG.Shape,
ignoreCurrent: boolean = false, currentID?: number,
): void { ): void {
if (enabled && !this.enabled && currentShape) { if (enabled && !this.enabled && currentShape) {
this.enabled = true; this.enabled = true;
this.currentShape = currentShape; this.currentShape = currentShape;
this.ignoreCurrent = ignoreCurrent; this.currentID = currentID;
this.updateObjects(); this.updateObjects();
} else { } else {
this.release(); this.release();

@ -36,8 +36,7 @@ const CanvasVersion = pjson.version;
interface Canvas { interface Canvas {
html(): HTMLDivElement; html(): HTMLDivElement;
setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[], zLayer?: number): void;
setup(frameData: any, objectStates: any[]): void;
activate(clientID: number | null, attributeID?: number): void; activate(clientID: number | null, attributeID?: number): void;
rotate(rotationAngle: number): void; rotate(rotationAngle: number): void;
focus(clientID: number, padding?: number): void; focus(clientID: number, padding?: number): void;
@ -58,6 +57,7 @@ interface Canvas {
mode(): Mode; mode(): Mode;
cancel(): void; cancel(): void;
configure(configuration: Configuration): void; configure(configuration: Configuration): void;
isAbleToChangeFrame(): boolean;
} }
class CanvasImpl implements Canvas { class CanvasImpl implements Canvas {
@ -75,12 +75,8 @@ class CanvasImpl implements Canvas {
return this.view.html(); return this.view.html();
} }
public setZLayer(zLayer: number | null): void { public setup(frameData: any, objectStates: any[], zLayer = 0): void {
this.model.setZLayer(zLayer); this.model.setup(frameData, objectStates, zLayer);
}
public setup(frameData: any, objectStates: any[]): void {
this.model.setup(frameData, objectStates);
} }
public fitCanvas(): void { public fitCanvas(): void {
@ -153,6 +149,10 @@ class CanvasImpl implements Canvas {
public configure(configuration: Configuration): void { public configure(configuration: Configuration): void {
this.model.configure(configuration); this.model.configure(configuration);
} }
public isAbleToChangeFrame(): boolean {
return this.model.isAbleToChangeFrame();
}
} }
export { export {

@ -66,6 +66,7 @@ export interface DrawData {
numberOfPoints?: number; numberOfPoints?: number;
initialState?: any; initialState?: any;
crosshair?: boolean; crosshair?: boolean;
redraw?: number;
} }
export interface EditData { export interface EditData {
@ -97,7 +98,6 @@ export enum UpdateReasons {
IMAGE_FITTED = 'image_fitted', IMAGE_FITTED = 'image_fitted',
IMAGE_MOVED = 'image_moved', IMAGE_MOVED = 'image_moved',
GRID_UPDATED = 'grid_updated', GRID_UPDATED = 'grid_updated',
SET_Z_LAYER = 'set_z_layer',
OBJECTS_UPDATED = 'objects_updated', OBJECTS_UPDATED = 'objects_updated',
SHAPE_ACTIVATED = 'shape_activated', SHAPE_ACTIVATED = 'shape_activated',
@ -147,11 +147,10 @@ export interface CanvasModel {
geometry: Geometry; geometry: Geometry;
mode: Mode; mode: Mode;
setZLayer(zLayer: number | null): void;
zoom(x: number, y: number, direction: number): void; zoom(x: number, y: number, direction: number): void;
move(topOffset: number, leftOffset: number): void; move(topOffset: number, leftOffset: number): void;
setup(frameData: any, objectStates: any[]): void; setup(frameData: any, objectStates: any[], zLayer: number): void;
activate(clientID: number | null, attributeID: number | null): void; activate(clientID: number | null, attributeID: number | null): void;
rotate(rotationAngle: number): void; rotate(rotationAngle: number): void;
focus(clientID: number, padding: number): void; focus(clientID: number, padding: number): void;
@ -169,6 +168,7 @@ export interface CanvasModel {
dragCanvas(enable: boolean): void; dragCanvas(enable: boolean): void;
zoomCanvas(enable: boolean): void; zoomCanvas(enable: boolean): void;
isAbleToChangeFrame(): boolean;
configure(configuration: Configuration): void; configure(configuration: Configuration): void;
cancel(): void; cancel(): void;
} }
@ -256,11 +256,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
}; };
} }
public setZLayer(zLayer: number | null): void {
this.data.zLayer = zLayer;
this.notify(UpdateReasons.SET_Z_LAYER);
}
public zoom(x: number, y: number, direction: number): void { public zoom(x: number, y: number, direction: number): void {
const oldScale: number = this.data.scale; const oldScale: number = this.data.scale;
const newScale: number = direction > 0 ? oldScale * 6 / 5 : oldScale * 5 / 6; const newScale: number = direction > 0 ? oldScale * 6 / 5 : oldScale * 5 / 6;
@ -335,7 +330,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.notify(UpdateReasons.ZOOM_CANVAS); this.notify(UpdateReasons.ZOOM_CANVAS);
} }
public setup(frameData: any, objectStates: any[]): void { public setup(frameData: any, objectStates: any[], zLayer: number): void {
if (this.data.imageID !== frameData.number) { if (this.data.imageID !== frameData.number) {
if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) { if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`); throw Error(`Canvas is busy. Action: ${this.data.mode}`);
@ -343,6 +338,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
if (frameData.number === this.data.imageID) { if (frameData.number === this.data.imageID) {
this.data.zLayer = zLayer;
this.data.objects = objectStates; this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS_UPDATED); this.notify(UpdateReasons.OBJECTS_UPDATED);
return; return;
@ -367,6 +363,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.data.image = data; this.data.image = data;
this.notify(UpdateReasons.IMAGE_CHANGED); this.notify(UpdateReasons.IMAGE_CHANGED);
this.data.zLayer = zLayer;
this.data.objects = objectStates; this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS_UPDATED); this.notify(UpdateReasons.OBJECTS_UPDATED);
}).catch((exception: any): void => { }).catch((exception: any): void => {
@ -382,10 +379,17 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
if (this.data.mode !== Mode.IDLE && clientID !== null) { if (this.data.mode !== Mode.IDLE && clientID !== null) {
// Exception or just return?
throw Error(`Canvas is busy. Action: ${this.data.mode}`); throw Error(`Canvas is busy. Action: ${this.data.mode}`);
} }
if (typeof (clientID) === 'number') {
const [state] = this.objects
.filter((_state: any): boolean => _state.clientID === clientID);
if (!state || state.objectType === 'tag') {
return;
}
}
this.data.activeElement = { this.data.activeElement = {
clientID, clientID,
attributeID, attributeID,
@ -465,10 +469,24 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
} }
} }
this.data.drawData = { ...drawData }; if (typeof (drawData.redraw) === 'number') {
if (this.data.drawData.initialState) { const clientID = drawData.redraw;
this.data.drawData.shapeType = this.data.drawData.initialState.shapeType; const [state] = this.data.objects
.filter((_state: any): boolean => _state.clientID === clientID);
if (state) {
this.data.drawData = { ...drawData };
this.data.drawData.shapeType = state.shapeType;
} else {
return;
}
} else {
this.data.drawData = { ...drawData };
if (this.data.drawData.initialState) {
this.data.drawData.shapeType = this.data.drawData.initialState.shapeType;
}
} }
this.notify(UpdateReasons.DRAW); this.notify(UpdateReasons.DRAW);
} }
@ -548,6 +566,13 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
this.notify(UpdateReasons.CONFIG_UPDATED); this.notify(UpdateReasons.CONFIG_UPDATED);
} }
public isAbleToChangeFrame(): boolean {
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE].includes(this.data.mode)
|| (this.data.mode === Mode.DRAW && typeof (this.data.drawData.redraw) === 'number');
return !isUnable;
}
public cancel(): void { public cancel(): void {
this.notify(UpdateReasons.CANCEL); this.notify(UpdateReasons.CANCEL);
} }

@ -21,8 +21,11 @@ import consts from './consts';
import { import {
translateToSVG, translateToSVG,
translateFromSVG, translateFromSVG,
pointsToArray, pointsToNumberArray,
parsePoints,
displayShapeSize, displayShapeSize,
scalarProduct,
vectorLength,
ShapeSizeElement, ShapeSizeElement,
DrawnState, DrawnState,
} from './shared'; } from './shared';
@ -71,6 +74,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
private autoborderHandler: AutoborderHandler; private autoborderHandler: AutoborderHandler;
private activeElement: ActiveElement; private activeElement: ActiveElement;
private configuration: Configuration; private configuration: Configuration;
private serviceFlags: {
drawHidden: Record<number, boolean>;
};
private set mode(value: Mode) { private set mode(value: Mode) {
this.controller.mode = value; this.controller.mode = value;
@ -80,8 +86,75 @@ export class CanvasViewImpl implements CanvasView, Listener {
return this.controller.mode; return this.controller.mode;
} }
private isServiceHidden(clientID: number): boolean {
return this.serviceFlags.drawHidden[clientID] || false;
}
private setupServiceHidden(clientID: number, value: boolean): void {
this.serviceFlags.drawHidden[clientID] = value;
const shape = this.svgShapes[clientID];
const text = this.svgTexts[clientID];
const state = this.drawnStates[clientID];
if (value) {
if (shape) {
(state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape)
.addClass('cvat_canvas_hidden');
}
if (text) {
text.addClass('cvat_canvas_hidden');
}
} else {
delete this.serviceFlags.drawHidden[clientID];
if (state) {
if (!state.outside && !state.hidden) {
if (shape) {
(state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape)
.removeClass('cvat_canvas_hidden');
}
if (text) {
text.removeClass('cvat_canvas_hidden');
this.updateTextPosition(
text,
shape,
);
}
}
}
}
}
private onDrawDone(data: object | null, duration: number, continueDraw?: boolean): void { private onDrawDone(data: object | null, duration: number, continueDraw?: boolean): void {
const hiddenBecauseOfDraw = Object.keys(this.serviceFlags.drawHidden)
.map((_clientID): number => +_clientID);
if (hiddenBecauseOfDraw.length) {
for (const hidden of hiddenBecauseOfDraw) {
this.setupServiceHidden(hidden, false);
}
}
if (data) { if (data) {
const { clientID, points } = data as any;
if (typeof (clientID) === 'number') {
const event: CustomEvent = new CustomEvent('canvas.canceled', {
bubbles: false,
cancelable: true,
});
this.canvas.dispatchEvent(event);
const [state] = this.controller.objects
.filter((_state: any): boolean => (
_state.clientID === clientID
));
this.onEditDone(state, points);
return;
}
const { zLayer } = this.controller; const { zLayer } = this.controller;
const event: CustomEvent = new CustomEvent('canvas.drawn', { const event: CustomEvent = new CustomEvent('canvas.drawn', {
bubbles: false, bubbles: false,
@ -323,6 +396,15 @@ export class CanvasViewImpl implements CanvasView, Listener {
); );
} }
for (const element of
window.document.getElementsByClassName('cvat_canvas_poly_direction')) {
const angle = (element as any).instance.data('angle');
(element as any).instance.style({
transform: `scale(${1 / this.geometry.scale}) rotate(${angle}deg)`,
});
}
for (const element of for (const element of
window.document.getElementsByClassName('cvat_canvas_selected_point')) { window.document.getElementsByClassName('cvat_canvas_selected_point')) {
const previousWidth = element.getAttribute('stroke-width') as string; const previousWidth = element.getAttribute('stroke-width') as string;
@ -425,13 +507,88 @@ export class CanvasViewImpl implements CanvasView, Listener {
} }
} }
private hideDirection(shape: SVG.Polygon | SVG.PolyLine): void {
/* eslint class-methods-use-this: 0 */
const handler = shape.remember('_selectHandler');
if (!handler || !handler.nested) return;
const nested = handler.nested as SVG.Parent;
if (nested.children().length) {
nested.children()[0].removeClass('cvat_canvas_first_poly_point');
}
const node = nested.node as SVG.LinkedHTMLElement;
const directions = node.getElementsByClassName('cvat_canvas_poly_direction');
for (const direction of directions) {
const { instance } = (direction as any);
instance.off('click');
instance.remove();
}
}
private showDirection(state: any, shape: SVG.Polygon | SVG.PolyLine): void {
const path = consts.ARROW_PATH;
const points = parsePoints(state.points);
const handler = shape.remember('_selectHandler');
if (!handler || !handler.nested) return;
const firstCircle = handler.nested.children()[0];
const secondCircle = handler.nested.children()[1];
firstCircle.addClass('cvat_canvas_first_poly_point');
const [cx, cy] = [
(secondCircle.cx() + firstCircle.cx()) / 2,
(secondCircle.cy() + firstCircle.cy()) / 2,
];
const [firstPoint, secondPoint] = points.slice(0, 2);
const xAxis = { i: 1, j: 0 };
const baseVector = { i: secondPoint.x - firstPoint.x, j: secondPoint.y - firstPoint.y };
const baseVectorLength = vectorLength(baseVector);
let cosinus = 0;
if (baseVectorLength !== 0) {
// two points have the same coordinates
cosinus = scalarProduct(xAxis, baseVector)
/ (vectorLength(xAxis) * baseVectorLength);
}
const angle = Math.acos(cosinus) * (Math.sign(baseVector.j) || 1) * 180 / Math.PI;
const pathElement = handler.nested.path(path).fill('white')
.stroke({
width: 1,
color: 'black',
}).addClass('cvat_canvas_poly_direction').style({
'transform-origin': `${cx}px ${cy}px`,
transform: `scale(${1 / this.geometry.scale}) rotate(${angle}deg)`,
}).move(cx, cy);
pathElement.on('click', (e: MouseEvent): void => {
if (e.button === 0) {
e.stopPropagation();
if (state.shapeType === 'polygon') {
const reversedPoints = [points[0], ...points.slice(1).reverse()];
this.onEditDone(state, pointsToNumberArray(reversedPoints));
} else {
const reversedPoints = points.reverse();
this.onEditDone(state, pointsToNumberArray(reversedPoints));
}
}
});
pathElement.data('angle', angle);
pathElement.dmove(-pathElement.width() / 2, -pathElement.height() / 2);
}
private selectize(value: boolean, shape: SVG.Element): void { private selectize(value: boolean, shape: SVG.Element): void {
const self = this; const self = this;
const { offset } = this.controller.geometry; const { offset } = this.controller.geometry;
const translate = (points: number[]): number[] => points const translate = (points: number[]): number[] => points
.map((coord: number): number => coord - offset); .map((coord: number): number => coord - offset);
function dblClickHandler(e: MouseEvent): void { function mousedownHandler(e: MouseEvent): void {
if (e.button !== 0) return;
e.preventDefault();
const pointID = Array.prototype.indexOf const pointID = Array.prototype.indexOf
.call(((e.target as HTMLElement).parentElement as HTMLElement).children, e.target); .call(((e.target as HTMLElement).parentElement as HTMLElement).children, e.target);
@ -440,45 +597,52 @@ export class CanvasViewImpl implements CanvasView, Listener {
.filter((_state: any): boolean => ( .filter((_state: any): boolean => (
_state.clientID === self.activeElement.clientID _state.clientID === self.activeElement.clientID
)); ));
if (state.shapeType === 'rectangle') {
e.preventDefault(); if (['polygon', 'polyline', 'points'].includes(state.shapeType)) {
return; if (e.ctrlKey) {
const { points } = state;
self.onEditDone(
state,
points.slice(0, pointID * 2).concat(points.slice(pointID * 2 + 2)),
);
} else if (e.shiftKey) {
self.canvas.dispatchEvent(new CustomEvent('canvas.editstart', {
bubbles: false,
cancelable: true,
}));
self.mode = Mode.EDIT;
self.deactivate();
self.editHandler.edit({
enabled: true,
state,
pointID,
});
}
} }
}
}
function dblClickHandler(e: MouseEvent): void {
e.preventDefault();
if (self.activeElement.clientID !== null) {
const [state] = self.controller.objects
.filter((_state: any): boolean => (
_state.clientID === self.activeElement.clientID
));
if (state.shapeType === 'cuboid') { if (state.shapeType === 'cuboid') {
if (e.shiftKey) { if (e.shiftKey) {
const points = translate(pointsToArray((e.target as any) const points = translate(pointsToNumberArray((e.target as any)
.parentElement.parentElement.instance.attr('points'))); .parentElement.parentElement.instance.attr('points')));
self.onEditDone( self.onEditDone(
state, state,
points, points,
); );
e.preventDefault();
return;
} }
} }
if (e.ctrlKey) {
const { points } = state;
self.onEditDone(
state,
points.slice(0, pointID * 2).concat(points.slice(pointID * 2 + 2)),
);
} else if (e.shiftKey) {
self.canvas.dispatchEvent(new CustomEvent('canvas.editstart', {
bubbles: false,
cancelable: true,
}));
self.mode = Mode.EDIT;
self.deactivate();
self.editHandler.edit({
enabled: true,
state,
pointID,
});
}
} }
e.preventDefault();
} }
function contextMenuHandler(e: MouseEvent): void { function contextMenuHandler(e: MouseEvent): void {
@ -524,6 +688,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}); });
circle.on('dblclick', dblClickHandler); circle.on('dblclick', dblClickHandler);
circle.on('mousedown', mousedownHandler);
circle.on('contextmenu', contextMenuHandler); circle.on('contextmenu', contextMenuHandler);
circle.addClass('cvat_canvas_selected_point'); circle.addClass('cvat_canvas_selected_point');
}); });
@ -534,6 +699,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}); });
circle.off('dblclick', dblClickHandler); circle.off('dblclick', dblClickHandler);
circle.off('mousedown', mousedownHandler);
circle.off('contextmenu', contextMenuHandler); circle.off('contextmenu', contextMenuHandler);
circle.removeClass('cvat_canvas_selected_point'); circle.removeClass('cvat_canvas_selected_point');
}); });
@ -565,6 +731,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
}; };
this.configuration = model.configuration; this.configuration = model.configuration;
this.mode = Mode.IDLE; this.mode = Mode.IDLE;
this.serviceFlags = {
drawHidden: {},
};
// Create HTML elements // Create HTML elements
this.loadingAnimation = window.document this.loadingAnimation = window.document
@ -742,12 +911,32 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (reason === UpdateReasons.CONFIG_UPDATED) { if (reason === UpdateReasons.CONFIG_UPDATED) {
const { activeElement } = this; const { activeElement } = this;
this.deactivate(); this.deactivate();
if (model.configuration.displayAllText && !this.configuration.displayAllText) {
for (const i in this.drawnStates) {
if (!(i in this.svgTexts)) {
this.svgTexts[i] = this.addText(this.drawnStates[i]);
this.updateTextPosition(
this.svgTexts[i],
this.svgShapes[i],
);
}
}
} else if (model.configuration.displayAllText === false
&& this.configuration.displayAllText) {
for (const i in this.drawnStates) {
if (i in this.svgTexts && Number.parseInt(i, 10) !== activeElement.clientID) {
this.svgTexts[i].remove();
delete this.svgTexts[i];
}
}
}
this.configuration = model.configuration; this.configuration = model.configuration;
this.activate(activeElement); this.activate(activeElement);
this.editHandler.configurate(this.configuration); this.editHandler.configurate(this.configuration);
this.drawHandler.configurate(this.configuration); this.drawHandler.configurate(this.configuration);
// todo: setup text, add if doesn't exist and enabled
// remove if exist and not enabled // remove if exist and not enabled
// this.setupObjects([]); // this.setupObjects([]);
// this.setupObjects(model.objects); // this.setupObjects(model.objects);
@ -802,7 +991,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
} }
} else if (reason === UpdateReasons.IMAGE_MOVED) { } else if (reason === UpdateReasons.IMAGE_MOVED) {
this.moveCanvas(); this.moveCanvas();
} else if ([UpdateReasons.OBJECTS_UPDATED, UpdateReasons.SET_Z_LAYER].includes(reason)) { } else if ([UpdateReasons.OBJECTS_UPDATED].includes(reason)) {
if (this.mode === Mode.GROUP) { if (this.mode === Mode.GROUP) {
this.groupHandler.resetSelectedObjects(); this.groupHandler.resetSelectedObjects();
} }
@ -864,6 +1053,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (data.enabled && this.mode === Mode.IDLE) { if (data.enabled && this.mode === Mode.IDLE) {
this.canvas.style.cursor = 'crosshair'; this.canvas.style.cursor = 'crosshair';
this.mode = Mode.DRAW; this.mode = Mode.DRAW;
if (typeof (data.redraw) === 'number') {
this.setupServiceHidden(data.redraw, true);
}
this.drawHandler.draw(data, this.geometry); this.drawHandler.draw(data, this.geometry);
} else { } else {
this.canvas.style.cursor = ''; this.canvas.style.cursor = '';
@ -936,7 +1128,6 @@ export class CanvasViewImpl implements CanvasView, Listener {
if (model.imageBitmap if (model.imageBitmap
&& [UpdateReasons.IMAGE_CHANGED, && [UpdateReasons.IMAGE_CHANGED,
UpdateReasons.OBJECTS_UPDATED, UpdateReasons.OBJECTS_UPDATED,
UpdateReasons.SET_Z_LAYER,
].includes(reason) ].includes(reason)
) { ) {
this.redrawBitmap(); this.redrawBitmap();
@ -1036,6 +1227,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
pinned: state.pinned, pinned: state.pinned,
updated: state.updated, updated: state.updated,
frame: state.frame, frame: state.frame,
label: state.label,
}; };
} }
@ -1045,18 +1237,19 @@ export class CanvasViewImpl implements CanvasView, Listener {
const drawnState = this.drawnStates[clientID]; const drawnState = this.drawnStates[clientID];
const shape = this.svgShapes[state.clientID]; const shape = this.svgShapes[state.clientID];
const text = this.svgTexts[state.clientID]; const text = this.svgTexts[state.clientID];
const isInvisible = state.hidden || state.outside; const isInvisible = state.hidden || state.outside
|| this.isServiceHidden(state.clientID);
if (drawnState.hidden !== state.hidden || drawnState.outside !== state.outside) { if (drawnState.hidden !== state.hidden || drawnState.outside !== state.outside) {
if (isInvisible) { if (isInvisible) {
(state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape) (state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape)
.style('display', 'none'); .addClass('cvat_canvas_hidden');
if (text) { if (text) {
text.addClass('cvat_canvas_hidden'); text.addClass('cvat_canvas_hidden');
} }
} else { } else {
(state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape) (state.shapeType === 'points' ? shape.remember('_selectHandler').nested : shape)
.style('display', ''); .removeClass('cvat_canvas_hidden');
if (text) { if (text) {
text.removeClass('cvat_canvas_hidden'); text.removeClass('cvat_canvas_hidden');
this.updateTextPosition( this.updateTextPosition(
@ -1147,59 +1340,57 @@ export class CanvasViewImpl implements CanvasView, Listener {
const { displayAllText } = this.configuration; const { displayAllText } = this.configuration;
for (const state of states) { for (const state of states) {
if (state.objectType === 'tag') { const points: number[] = (state.points as number[]);
this.addTag(state); const translatedPoints: number[] = translate(points);
// TODO: Use enums after typification cvat-core
if (state.shapeType === 'rectangle') {
this.svgShapes[state.clientID] = this
.addRect(translatedPoints, state);
} else { } else {
const points: number[] = (state.points as number[]); const stringified = translatedPoints.reduce(
const translatedPoints: number[] = translate(points); (acc: string, val: number, idx: number): string => {
if (idx % 2) {
return `${acc}${val} `;
}
// TODO: Use enums after typification cvat-core return `${acc}${val},`;
if (state.shapeType === 'rectangle') { }, '',
);
if (state.shapeType === 'polygon') {
this.svgShapes[state.clientID] = this this.svgShapes[state.clientID] = this
.addRect(translatedPoints, state); .addPolygon(stringified, state);
} else if (state.shapeType === 'polyline') {
this.svgShapes[state.clientID] = this
.addPolyline(stringified, state);
} else if (state.shapeType === 'points') {
this.svgShapes[state.clientID] = this
.addPoints(stringified, state);
} else if (state.shapeType === 'cuboid') {
this.svgShapes[state.clientID] = this
.addCuboid(stringified, state);
} else { } else {
const stringified = translatedPoints.reduce( continue;
(acc: string, val: number, idx: number): string => {
if (idx % 2) {
return `${acc}${val} `;
}
return `${acc}${val},`;
}, '',
);
if (state.shapeType === 'polygon') {
this.svgShapes[state.clientID] = this
.addPolygon(stringified, state);
} else if (state.shapeType === 'polyline') {
this.svgShapes[state.clientID] = this
.addPolyline(stringified, state);
} else if (state.shapeType === 'points') {
this.svgShapes[state.clientID] = this
.addPoints(stringified, state);
} else if (state.shapeType === 'cuboid') {
this.svgShapes[state.clientID] = this
.addCuboid(stringified, state);
}
} }
}
this.svgShapes[state.clientID].on('click.canvas', (): void => { this.svgShapes[state.clientID].on('click.canvas', (): void => {
this.canvas.dispatchEvent(new CustomEvent('canvas.clicked', { this.canvas.dispatchEvent(new CustomEvent('canvas.clicked', {
bubbles: false, bubbles: false,
cancelable: true, cancelable: true,
detail: { detail: {
state, state,
}, },
})); }));
}); });
if (displayAllText) { if (displayAllText) {
this.svgTexts[state.clientID] = this.addText(state); this.svgTexts[state.clientID] = this.addText(state);
this.updateTextPosition( this.updateTextPosition(
this.svgTexts[state.clientID], this.svgTexts[state.clientID],
this.svgShapes[state.clientID], this.svgShapes[state.clientID],
); );
}
} }
this.saveState(state); this.saveState(state);
@ -1327,16 +1518,6 @@ export class CanvasViewImpl implements CanvasView, Listener {
const shape = this.svgShapes[clientID]; const shape = this.svgShapes[clientID];
let text = this.svgTexts[clientID];
if (!text) {
text = this.addText(state);
this.svgTexts[state.clientID] = text;
this.updateTextPosition(
text,
shape,
);
}
if (state.lock) { if (state.lock) {
return; return;
} }
@ -1354,29 +1535,39 @@ export class CanvasViewImpl implements CanvasView, Listener {
(shape as any).attr('projections', true); (shape as any).attr('projections', true);
} }
let text = this.svgTexts[clientID];
if (!text) {
text = this.addText(state);
this.svgTexts[state.clientID] = text;
}
const hideText = (): void => {
if (text) {
text.addClass('cvat_canvas_hidden');
}
};
const showText = (): void => {
if (text) {
text.removeClass('cvat_canvas_hidden');
this.updateTextPosition(text, shape);
}
};
if (!state.pinned) { if (!state.pinned) {
shape.addClass('cvat_canvas_shape_draggable'); shape.addClass('cvat_canvas_shape_draggable');
(shape as any).draggable().on('dragstart', (): void => { (shape as any).draggable().on('dragstart', (): void => {
this.mode = Mode.DRAG; this.mode = Mode.DRAG;
if (text) { hideText();
text.addClass('cvat_canvas_hidden');
}
}).on('dragend', (e: CustomEvent): void => { }).on('dragend', (e: CustomEvent): void => {
if (text) { showText();
text.removeClass('cvat_canvas_hidden');
this.updateTextPosition(
text,
shape,
);
}
this.mode = Mode.IDLE; this.mode = Mode.IDLE;
const p1 = e.detail.handler.startPoints.point; const p1 = e.detail.handler.startPoints.point;
const p2 = e.detail.p; const p2 = e.detail.p;
const delta = 1; const delta = 1;
const { offset } = this.controller.geometry; const { offset } = this.controller.geometry;
if (Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) >= delta) { if (Math.sqrt(((p1.x - p2.x) ** 2) + ((p1.y - p2.y) ** 2)) >= delta) {
const points = pointsToArray( const points = pointsToNumberArray(
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} `
+ `${shape.attr('x') + shape.attr('width')},` + `${shape.attr('x') + shape.attr('width')},`
+ `${shape.attr('y') + shape.attr('height')}`, + `${shape.attr('y') + shape.attr('height')}`,
@ -1399,17 +1590,32 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.selectize(true, shape); this.selectize(true, shape);
} }
const showDirection = (): void => {
if (['polygon', 'polyline'].includes(state.shapeType)) {
this.showDirection(state, shape as SVG.Polygon | SVG.PolyLine);
}
};
const hideDirection = (): void => {
if (['polygon', 'polyline'].includes(state.shapeType)) {
this.hideDirection(shape as SVG.Polygon | SVG.PolyLine);
}
};
showDirection();
let shapeSizeElement: ShapeSizeElement | null = null; let shapeSizeElement: ShapeSizeElement | null = null;
let resized = false; let resized = false;
(shape as any).resize().on('resizestart', (): void => { (shape as any).resize({
snapToGrid: 0.1,
}).on('resizestart', (): void => {
this.mode = Mode.RESIZE; this.mode = Mode.RESIZE;
resized = false;
hideDirection();
hideText();
if (state.shapeType === 'rectangle') { if (state.shapeType === 'rectangle') {
shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText);
} }
resized = false;
if (text) {
text.addClass('cvat_canvas_hidden');
}
}).on('resizing', (): void => { }).on('resizing', (): void => {
resized = true; resized = true;
if (shapeSizeElement) { if (shapeSizeElement) {
@ -1420,20 +1626,15 @@ export class CanvasViewImpl implements CanvasView, Listener {
shapeSizeElement.rm(); shapeSizeElement.rm();
} }
if (text) { showDirection();
text.removeClass('cvat_canvas_hidden'); showText();
this.updateTextPosition(
text,
shape,
);
}
this.mode = Mode.IDLE; this.mode = Mode.IDLE;
if (resized) { if (resized) {
const { offset } = this.controller.geometry; const { offset } = this.controller.geometry;
const points = pointsToArray( const points = pointsToNumberArray(
shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} `
+ `${shape.attr('x') + shape.attr('width')},` + `${shape.attr('x') + shape.attr('width')},`
+ `${shape.attr('y') + shape.attr('height')}`, + `${shape.attr('y') + shape.attr('height')}`,
@ -1451,6 +1652,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
} }
}); });
this.updateTextPosition(text, shape);
this.canvas.dispatchEvent(new CustomEvent('canvas.activated', { this.canvas.dispatchEvent(new CustomEvent('canvas.activated', {
bubbles: false, bubbles: false,
cancelable: true, cancelable: true,
@ -1530,14 +1732,19 @@ export class CanvasViewImpl implements CanvasView, Listener {
private addText(state: any): SVG.Text { private addText(state: any): SVG.Text {
const { undefinedAttrValue } = this.configuration; const { undefinedAttrValue } = this.configuration;
const { label, clientID, attributes } = state; const {
label,
clientID,
attributes,
source,
} = state;
const attrNames = label.attributes.reduce((acc: any, val: any): void => { const attrNames = label.attributes.reduce((acc: any, val: any): void => {
acc[val.id] = val.name; acc[val.id] = val.name;
return acc; return acc;
}, {}); }, {});
return this.adoptedText.text((block): void => { return this.adoptedText.text((block): void => {
block.tspan(`${label.name} ${clientID}`).style('text-transform', 'uppercase'); block.tspan(`${label.name} ${clientID} (${source})`).style('text-transform', 'uppercase');
for (const attrID of Object.keys(attributes)) { for (const attrID of Object.keys(attributes)) {
const value = attributes[attrID] === undefinedAttrValue const value = attributes[attrID] === undefinedAttrValue
? '' : attributes[attrID]; ? '' : attributes[attrID];
@ -1568,8 +1775,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
rect.addClass('cvat_canvas_shape_occluded'); rect.addClass('cvat_canvas_shape_occluded');
} }
if (state.hidden || state.outside) { if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
rect.style('display', 'none'); rect.addClass('cvat_canvas_hidden');
} }
return rect; return rect;
@ -1591,8 +1798,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
polygon.addClass('cvat_canvas_shape_occluded'); polygon.addClass('cvat_canvas_shape_occluded');
} }
if (state.hidden || state.outside) { if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
polygon.style('display', 'none'); polygon.addClass('cvat_canvas_hidden');
} }
return polygon; return polygon;
@ -1614,8 +1821,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
polyline.addClass('cvat_canvas_shape_occluded'); polyline.addClass('cvat_canvas_shape_occluded');
} }
if (state.hidden || state.outside) { if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
polyline.style('display', 'none'); polyline.addClass('cvat_canvas_hidden');
} }
return polyline; return polyline;
@ -1638,8 +1845,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
cube.addClass('cvat_canvas_shape_occluded'); cube.addClass('cvat_canvas_shape_occluded');
} }
if (state.hidden || state.outside) { if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
cube.style('display', 'none'); cube.addClass('cvat_canvas_hidden');
} }
return cube; return cube;
@ -1682,8 +1889,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
const group = this.setupPoints(shape, state); const group = this.setupPoints(shape, state);
if (state.hidden || state.outside) { if (state.hidden || state.outside || this.isServiceHidden(state.clientID)) {
group.style('display', 'none'); group.addClass('cvat_canvas_hidden');
} }
shape.remove = (): SVG.PolyLine => { shape.remove = (): SVG.PolyLine => {
@ -1694,9 +1901,4 @@ export class CanvasViewImpl implements CanvasView, Listener {
return shape; return shape;
} }
/* eslint-disable-next-line */
private addTag(state: any): void {
console.log(state);
}
} }

@ -14,6 +14,8 @@ const MIN_EDGE_LENGTH = 3;
const CUBOID_ACTIVE_EDGE_STROKE_WIDTH = 2.5; const CUBOID_ACTIVE_EDGE_STROKE_WIDTH = 2.5;
const CUBOID_UNACTIVE_EDGE_STROKE_WIDTH = 1.75; const CUBOID_UNACTIVE_EDGE_STROKE_WIDTH = 1.75;
const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__'; const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__';
const ARROW_PATH = 'M13.162 6.284L.682.524a.483.483 0 0 0-.574.134.477.477 0 '
+ '0 0-.012.59L4.2 6.72.096 12.192a.479.479 0 0 0 .585.724l12.48-5.76a.48.48 0 0 0 0-.872z';
export default { export default {
BASE_STROKE_WIDTH, BASE_STROKE_WIDTH,
@ -28,4 +30,5 @@ export default {
CUBOID_ACTIVE_EDGE_STROKE_WIDTH, CUBOID_ACTIVE_EDGE_STROKE_WIDTH,
CUBOID_UNACTIVE_EDGE_STROKE_WIDTH, CUBOID_UNACTIVE_EDGE_STROKE_WIDTH,
UNDEFINED_ATTRIBUTE_VALUE, UNDEFINED_ATTRIBUTE_VALUE,
ARROW_PATH,
}; };

@ -11,8 +11,8 @@ import {
translateToSVG, translateToSVG,
displayShapeSize, displayShapeSize,
ShapeSizeElement, ShapeSizeElement,
pointsToString, stringifyPoints,
pointsToArray, pointsToNumberArray,
BBox, BBox,
Box, Box,
} from './shared'; } from './shared';
@ -264,12 +264,13 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawInstance.on('drawstop', (e: Event): void => { this.drawInstance.on('drawstop', (e: Event): void => {
const bbox = (e.target as SVGRectElement).getBBox(); const bbox = (e.target as SVGRectElement).getBBox();
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
const { shapeType } = this.drawData; const { shapeType, redraw: clientID } = this.drawData;
this.release(); this.release();
if (this.canceled) return; if (this.canceled) return;
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
this.onDrawDone({ this.onDrawDone({
clientID,
shapeType, shapeType,
points: [xtl, ytl, xbr, ybr], points: [xtl, ytl, xbr, ybr],
}, Date.now() - this.startTimestamp); }, Date.now() - this.startTimestamp);
@ -298,12 +299,13 @@ export class DrawHandlerImpl implements DrawHandler {
if (numberOfPoints === 4) { if (numberOfPoints === 4) {
const bbox = (e.target as SVGPolylineElement).getBBox(); const bbox = (e.target as SVGPolylineElement).getBBox();
const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox); const [xtl, ytl, xbr, ybr] = this.getFinalRectCoordinates(bbox);
const { shapeType } = this.drawData; const { shapeType, redraw: clientID } = this.drawData;
this.cancel(); this.cancel();
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
this.onDrawDone({ this.onDrawDone({
shapeType, shapeType,
clientID,
points: [xtl, ytl, xbr, ybr], points: [xtl, ytl, xbr, ybr],
}, Date.now() - this.startTimestamp); }, Date.now() - this.startTimestamp);
} }
@ -356,6 +358,7 @@ export class DrawHandlerImpl implements DrawHandler {
if (lastDrawnPoint.x === null || lastDrawnPoint.y === null) { if (lastDrawnPoint.x === null || lastDrawnPoint.y === null) {
this.drawInstance.draw('point', e); this.drawInstance.draw('point', e);
} else { } else {
this.drawInstance.draw('update', e);
const deltaTreshold = 15; const deltaTreshold = 15;
const delta = Math.sqrt( const delta = Math.sqrt(
((e.clientX - lastDrawnPoint.x) ** 2) ((e.clientX - lastDrawnPoint.x) ** 2)
@ -379,8 +382,8 @@ export class DrawHandlerImpl implements DrawHandler {
}); });
this.drawInstance.on('drawdone', (e: CustomEvent): void => { this.drawInstance.on('drawdone', (e: CustomEvent): void => {
const targetPoints = pointsToArray((e.target as SVGElement).getAttribute('points')); const targetPoints = pointsToNumberArray((e.target as SVGElement).getAttribute('points'));
const { shapeType } = this.drawData; const { shapeType, redraw: clientID } = this.drawData;
const { points, box } = shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints) const { points, box } = shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints)
: this.getFinalPolyshapeCoordinates(targetPoints); : this.getFinalPolyshapeCoordinates(targetPoints);
this.release(); this.release();
@ -390,6 +393,7 @@ export class DrawHandlerImpl implements DrawHandler {
&& ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD) && ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= consts.AREA_THRESHOLD)
&& points.length >= 3 * 2) { && points.length >= 3 * 2) {
this.onDrawDone({ this.onDrawDone({
clientID,
shapeType, shapeType,
points, points,
}, Date.now() - this.startTimestamp); }, Date.now() - this.startTimestamp);
@ -398,12 +402,14 @@ export class DrawHandlerImpl implements DrawHandler {
|| (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD) || (box.ybr - box.ytl) >= consts.SIZE_THRESHOLD)
&& points.length >= 2 * 2) { && points.length >= 2 * 2) {
this.onDrawDone({ this.onDrawDone({
clientID,
shapeType, shapeType,
points, points,
}, Date.now() - this.startTimestamp); }, Date.now() - this.startTimestamp);
} else if (shapeType === 'points' } else if (shapeType === 'points'
&& (e.target as any).getAttribute('points') !== '0,0') { && (e.target as any).getAttribute('points') !== '0,0') {
this.onDrawDone({ this.onDrawDone({
clientID,
shapeType, shapeType,
points, points,
}, Date.now() - this.startTimestamp); }, Date.now() - this.startTimestamp);
@ -411,6 +417,7 @@ export class DrawHandlerImpl implements DrawHandler {
} else if (shapeType === 'cuboid' } else if (shapeType === 'cuboid'
&& points.length === 4 * 2) { && points.length === 4 * 2) {
this.onDrawDone({ this.onDrawDone({
clientID,
shapeType, shapeType,
points: cuboidFrom4Points(points), points: cuboidFrom4Points(points),
}, Date.now() - this.startTimestamp); }, Date.now() - this.startTimestamp);
@ -426,7 +433,7 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawPolyshape(); this.drawPolyshape();
if (this.autobordersEnabled) { if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.drawInstance, false); this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw);
} }
} }
@ -439,7 +446,7 @@ export class DrawHandlerImpl implements DrawHandler {
this.drawPolyshape(); this.drawPolyshape();
if (this.autobordersEnabled) { if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.drawInstance, false); this.autoborderHandler.autoborder(true, this.drawInstance, this.drawData.redraw);
} }
} }
@ -471,7 +478,7 @@ export class DrawHandlerImpl implements DrawHandler {
if (this.canceled) return; if (this.canceled) return;
if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) { if ((xbr - xtl) * (ybr - ytl) >= consts.AREA_THRESHOLD) {
const d = { x: (xbr - xtl) * 0.1, y: (ybr - ytl)*0.1} const d = { x: (xbr - xtl) * 0.1, y: (ybr - ytl) * 0.1 };
this.onDrawDone({ this.onDrawDone({
shapeType, shapeType,
points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]), points: cuboidFrom4Points([xtl, ybr, xbr, ybr, xbr, ytl, xbr + d.x, ytl - d.y]),
@ -673,7 +680,7 @@ export class DrawHandlerImpl implements DrawHandler {
} else { } else {
const points = this.drawData.initialState.points const points = this.drawData.initialState.points
.map((coord: number): number => coord + offset); .map((coord: number): number => coord + offset);
const stringifiedPoints = pointsToString(points); const stringifiedPoints = stringifyPoints(points);
if (this.drawData.shapeType === 'polygon') { if (this.drawData.shapeType === 'polygon') {
this.pastePolygon(stringifiedPoints); this.pastePolygon(stringifiedPoints);
@ -760,7 +767,11 @@ export class DrawHandlerImpl implements DrawHandler {
this.autobordersEnabled = configuration.autoborders; this.autobordersEnabled = configuration.autoborders;
if (this.drawInstance) { if (this.drawInstance) {
if (this.autobordersEnabled) { if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.drawInstance, false); this.autoborderHandler.autoborder(
true,
this.drawInstance,
this.drawData.redraw,
);
} else { } else {
this.autoborderHandler.autoborder(false); this.autoborderHandler.autoborder(false);
} }

@ -6,7 +6,7 @@ import * as SVG from 'svg.js';
import 'svg.select.js'; import 'svg.select.js';
import consts from './consts'; import consts from './consts';
import { translateFromSVG, pointsToArray } from './shared'; import { translateFromSVG, pointsToNumberArray } from './shared';
import { EditData, Geometry, Configuration } from './canvasModel'; import { EditData, Geometry, Configuration } from './canvasModel';
import { AutoborderHandler } from './autoborderHandler'; import { AutoborderHandler } from './autoborderHandler';
@ -28,6 +28,38 @@ export class EditHandlerImpl implements EditHandler {
private clones: SVG.Polygon[]; private clones: SVG.Polygon[];
private autobordersEnabled: boolean; private autobordersEnabled: boolean;
private setupTrailingPoint(circle: SVG.Circle): void {
const head = this.editedShape.attr('points').split(' ').slice(0, this.editData.pointID).join(' ');
circle.on('mouseenter', (): void => {
circle.attr({
'stroke-width': consts.POINTS_SELECTED_STROKE_WIDTH / this.geometry.scale,
});
});
circle.on('mouseleave', (): void => {
circle.attr({
'stroke-width': consts.POINTS_STROKE_WIDTH / this.geometry.scale,
});
});
const minimumPoints = 2;
circle.on('mousedown', (e: MouseEvent): void => {
if (e.button !== 0) return;
const { offset } = this.geometry;
const stringifiedPoints = `${head} ${this.editLine.node.getAttribute('points').slice(0, -2)}`;
const points = pointsToNumberArray(stringifiedPoints).slice(0, -2)
.map((coord: number): number => coord - offset);
if (points.length >= minimumPoints * 2) {
const { state } = this.editData;
this.edit({
enabled: false,
});
this.onEditDone(state, points);
}
});
}
private startEdit(): void { private startEdit(): void {
// get started coordinates // get started coordinates
const [clientX, clientY] = translateFromSVG( const [clientX, clientY] = translateFromSVG(
@ -72,9 +104,18 @@ export class EditHandlerImpl implements EditHandler {
}); });
this.editLine = (this.canvas as any).polyline(); this.editLine = (this.canvas as any).polyline();
if (this.editData.state.shapeType === 'polyline') {
(this.editLine as any).on('drawpoint', (e: CustomEvent): void => {
const circle = (e.target as any).instance.remember('_paintHandler').set.last();
if (circle) this.setupTrailingPoint(circle);
});
}
const strokeColor = this.editedShape.attr('stroke');
(this.editLine as any).addClass('cvat_canvas_shape_drawing').style({ (this.editLine as any).addClass('cvat_canvas_shape_drawing').style({
'pointer-events': 'none', 'pointer-events': 'none',
'fill-opacity': 0, 'fill-opacity': 0,
'stroke': strokeColor,
}).attr({ }).attr({
'data-origin-client-id': this.editData.state.clientID, 'data-origin-client-id': this.editData.state.clientID,
}).on('drawstart drawpoint', (e: CustomEvent): void => { }).on('drawstart drawpoint', (e: CustomEvent): void => {
@ -90,7 +131,7 @@ export class EditHandlerImpl implements EditHandler {
this.setupEditEvents(); this.setupEditEvents();
if (this.autobordersEnabled) { if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.editLine, true); this.autoborderHandler.autoborder(true, this.editLine, this.editData.state.clientID);
} }
} }
@ -110,7 +151,7 @@ export class EditHandlerImpl implements EditHandler {
private selectPolygon(shape: SVG.Polygon): void { private selectPolygon(shape: SVG.Polygon): void {
const { offset } = this.geometry; const { offset } = this.geometry;
const points = pointsToArray(shape.attr('points')) const points = pointsToNumberArray(shape.attr('points'))
.map((coord: number): number => coord - offset); .map((coord: number): number => coord - offset);
const { state } = this.editData; const { state } = this.editData;
@ -140,24 +181,87 @@ export class EditHandlerImpl implements EditHandler {
const [start, stop] = [this.editData.pointID, stopPointID] const [start, stop] = [this.editData.pointID, stopPointID]
.sort((a, b): number => +a - +b); .sort((a, b): number => +a - +b);
if (this.editData.state.shapeType === 'polygon') { if (this.editData.state.shapeType !== 'polygon') {
if (start !== this.editData.pointID) { let points = null;
linePoints.reverse(); const { offset } = this.geometry;
if (this.editData.state.shapeType === 'polyline') {
if (start !== this.editData.pointID) {
linePoints.reverse();
}
points = oldPoints.slice(0, start)
.concat(linePoints)
.concat(oldPoints.slice(stop + 1));
} else {
points = oldPoints.concat(linePoints.slice(0, -1));
} }
const firstPart = oldPoints.slice(0, start) points = pointsToNumberArray(points.join(' '))
.concat(linePoints) .map((coord: number): number => coord - offset);
.concat(oldPoints.slice(stop + 1));
linePoints.reverse(); const { state } = this.editData;
const secondPart = oldPoints.slice(start + 1, stop) this.edit({
.concat(linePoints); enabled: false,
});
this.onEditDone(state, points);
if (firstPart.length < 3 || secondPart.length < 3) { return;
this.cancel(); }
return;
const cutIndexes1 = oldPoints.reduce((acc: string[], _: string, i: number) =>
i >= stop || i <= start ? [...acc, i] : acc, []);
const cutIndexes2 = oldPoints.reduce((acc: string[], _: string, i: number) =>
i <= stop && i >= start ? [...acc, i] : acc, []);
const curveLength = (indexes: number[]) => {
const points = indexes.map((index: number): string => oldPoints[index])
.map((point: string): string[] => point.split(','))
.map((point: string[]): number[] => [+point[0], +point[1]]);
let length = 0;
for (let i = 1; i < points.length; i++) {
length += Math.sqrt(
(points[i][0] - points[i - 1][0]) ** 2
+ (points[i][1] - points[i - 1][1]) ** 2,
);
} }
return length;
}
const pointsCriteria = cutIndexes1.length > cutIndexes2.length;
const lengthCriteria = curveLength(cutIndexes1) > curveLength(cutIndexes2);
if (start !== this.editData.pointID) {
linePoints.reverse();
}
const firstPart = oldPoints.slice(0, start)
.concat(linePoints)
.concat(oldPoints.slice(stop + 1));
const secondPart = oldPoints.slice(start, stop)
.concat(linePoints.slice(1).reverse());
if (firstPart.length < 3 || secondPart.length < 3) {
this.cancel();
return;
}
// We do not need these events any more
this.canvas.off('mousedown.edit');
this.canvas.off('mousemove.edit');
(this.editLine as any).draw('stop');
this.editLine.remove();
this.editLine = null;
if (pointsCriteria && lengthCriteria) {
this.clones.push(this.canvas.polygon(firstPart.join(' ')));
this.selectPolygon(this.clones[0]);
// left indexes1 and
} else if (!pointsCriteria && !lengthCriteria) {
this.clones.push(this.canvas.polygon(secondPart.join(' ')));
this.selectPolygon(this.clones[0]);
} else {
for (const points of [firstPart, secondPart]) { for (const points of [firstPart, secondPart]) {
this.clones.push(this.canvas.polygon(points.join(' ')) this.clones.push(this.canvas.polygon(points.join(' '))
.attr('fill', this.editedShape.attr('fill')) .attr('fill', this.editedShape.attr('fill'))
@ -173,39 +277,9 @@ export class EditHandlerImpl implements EditHandler {
clone.removeClass('cvat_canvas_shape_splitting'); clone.removeClass('cvat_canvas_shape_splitting');
}); });
} }
// We do not need these events any more
this.canvas.off('mousedown.edit');
this.canvas.off('mousemove.edit');
(this.editLine as any).draw('stop');
this.editLine.remove();
this.editLine = null;
return;
}
let points = null;
const { offset } = this.geometry;
if (this.editData.state.shapeType === 'polyline') {
if (start !== this.editData.pointID) {
linePoints.reverse();
}
points = oldPoints.slice(0, start)
.concat(linePoints)
.concat(oldPoints.slice(stop + 1));
} else {
points = oldPoints.concat(linePoints.slice(0, -1));
} }
points = pointsToArray(points.join(' ')) return;
.map((coord: number): number => coord - offset);
const { state } = this.editData;
this.edit({
enabled: false,
});
this.onEditDone(state, points);
} }
private setupPoints(enabled: boolean): void { private setupPoints(enabled: boolean): void {
@ -337,7 +411,11 @@ export class EditHandlerImpl implements EditHandler {
this.autobordersEnabled = configuration.autoborders; this.autobordersEnabled = configuration.autoborders;
if (this.editLine) { if (this.editLine) {
if (this.autobordersEnabled) { if (this.autobordersEnabled) {
this.autoborderHandler.autoborder(true, this.editLine, true); this.autoborderHandler.autoborder(
true,
this.editLine,
this.editData.state.clientID,
);
} else { } else {
this.autoborderHandler.autoborder(false); this.autoborderHandler.autoborder(false);
} }

@ -95,7 +95,9 @@ export class GroupHandlerImpl implements GroupHandler {
this.selectionRect = null; this.selectionRect = null;
const box = this.getSelectionBox(event); const box = this.getSelectionBox(event);
const shapes = (this.canvas.select('.cvat_canvas_shape') as any).members; const shapes = (this.canvas.select('.cvat_canvas_shape') as any).members.filter(
(shape: SVG.Shape): boolean => !shape.hasClass('cvat_canvas_hidden'),
);
for (const shape of shapes) { for (const shape of shapes) {
// TODO: Doesn't work properly for groups // TODO: Doesn't work properly for groups
const bbox = shape.bbox(); const bbox = shape.bbox();

@ -29,6 +29,12 @@ interface Point {
x: number; x: number;
y: number; y: number;
} }
interface Vector2D {
i: number;
j: number;
}
export interface DrawnState { export interface DrawnState {
clientID: number; clientID: number;
outside?: boolean; outside?: boolean;
@ -42,6 +48,7 @@ export interface DrawnState {
pinned?: boolean; pinned?: boolean;
updated: number; updated: number;
frame: number; frame: number;
label: any;
} }
// Translate point array from the canvas coordinate system // Translate point array from the canvas coordinate system
@ -76,21 +83,6 @@ export function translateToSVG(svg: SVGSVGElement, points: number[]): number[] {
return output; return output;
} }
export function pointsToString(points: number[]): string {
return points.reduce((acc, val, idx): string => {
if (idx % 2) {
return `${acc},${val}`;
}
return `${acc} ${val}`.trim();
}, '');
}
export function pointsToArray(points: string): number[] {
return points.trim().split(/[,\s]+/g)
.map((coord: string): number => +coord);
}
export function displayShapeSize( export function displayShapeSize(
shapesContainer: SVG.Container, shapesContainer: SVG.Container,
textContainer: SVG.Container, textContainer: SVG.Container,
@ -120,25 +112,59 @@ export function displayShapeSize(
return shapeSize; return shapeSize;
} }
export function convertToArray(points: Point[]): number[][] { export function pointsToNumberArray(points: string | Point[]): number[] {
const arr: number[][] = []; if (Array.isArray(points)) {
points.forEach((point: Point): void => { return points.reduce((acc: number[], point: Point): number[] => {
arr.push([point.x, point.y]); acc.push(point.x, point.y);
}); return acc;
return arr; }, []);
}
return points.trim().split(/[,\s]+/g)
.map((coord: string): number => +coord);
} }
export function parsePoints(stringified: string): Point[] { export function parsePoints(source: string | number[]): Point[] {
return stringified.trim().split(/\s/).map((point: string): Point => { if (Array.isArray(source)) {
return source.reduce((acc: Point[], _: number, index: number): Point[] => {
if (index % 2) {
acc.push({
x: source[index - 1],
y: source[index],
});
}
return acc;
}, []);
}
return source.trim().split(/\s/).map((point: string): Point => {
const [x, y] = point.split(',').map((coord: string): number => +coord); const [x, y] = point.split(',').map((coord: string): number => +coord);
return { x, y }; return { x, y };
}); });
} }
export function stringifyPoints(points: Point[]): string { export function stringifyPoints(points: (Point | number)[]): string {
if (typeof (points[0]) === 'number') {
return points.reduce((acc: string, val: number, idx: number): string => {
if (idx % 2) {
return `${acc},${val}`;
}
return `${acc} ${val}`.trim();
}, '');
}
return points.map((point: Point): string => `${point.x},${point.y}`).join(' '); return points.map((point: Point): string => `${point.x},${point.y}`).join(' ');
} }
export function clamp(x: number, min: number, max: number): number { export function clamp(x: number, min: number, max: number): number {
return Math.min(Math.max(x, min), max); return Math.min(Math.max(x, min), max);
} }
export function scalarProduct(a: Vector2D, b: Vector2D): number {
return a.i * b.i + a.j * b.j;
}
export function vectorLength(vector: Vector2D): number {
return Math.sqrt((vector.i ** 2) + (vector.j ** 2));
}

@ -17,7 +17,7 @@ import {
Orientation, Orientation,
Edge, Edge,
} from './cuboid'; } from './cuboid';
import { parsePoints, stringifyPoints, clamp } from './shared'; import { parsePoints, clamp } from './shared';
// Update constructor // Update constructor
const originalDraw = SVG.Element.prototype.draw; const originalDraw = SVG.Element.prototype.draw;
@ -174,7 +174,8 @@ SVG.Element.prototype.resize = function constructor(...args: any): any {
originalResize.call(this, ...args); originalResize.call(this, ...args);
handler = this.remember('_resizeHandler'); handler = this.remember('_resizeHandler');
handler.resize = function(e: any) { handler.resize = function(e: any) {
if (e.detail.event.button === 0) { const { event } = e.detail;
if (event.button === 0 && !event.shiftKey && !event.ctrlKey) {
return handler.constructor.prototype.resize.call(this, e); return handler.constructor.prototype.resize.call(this, e);
} }
} }

@ -60,10 +60,12 @@ const webConfig = {
target: 'web', target: 'web',
mode: 'production', mode: 'production',
devtool: 'source-map', devtool: 'source-map',
entry: './src/typescript/canvas.ts', entry: {
'cvat-canvas': './src/typescript/canvas.ts',
},
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'cvat-canvas.js', filename: '[name].[contenthash].js',
library: 'canvas', library: 'canvas',
libraryTarget: 'window', libraryTarget: 'window',
}, },

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

@ -1,7 +1,7 @@
# Module CVAT-CORE # Module CVAT-CORE
## Description ## Description
This CVAT module is a clien-side JavaScipt library to management of objects, frames, logs, etc. This CVAT module is a client-side JavaScipt library to management of objects, frames, logs, etc.
It contains the core logic of the Computer Vision Annotation Tool. It contains the core logic of the Computer Vision Annotation Tool.
## Versioning ## Versioning

Binary file not shown.

File diff suppressed because it is too large Load Diff

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

@ -15,6 +15,7 @@
name: initialData.name, name: initialData.name,
format: initialData.ext, format: initialData.ext,
version: initialData.version, version: initialData.version,
enabled: initialData.enabled,
}; };
Object.defineProperties(this, { Object.defineProperties(this, {
@ -48,6 +49,16 @@
*/ */
get: () => data.version, get: () => data.version,
}, },
enabled: {
/**
* @name enabled
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.enabled,
},
}); });
} }
} }
@ -63,6 +74,7 @@
name: initialData.name, name: initialData.name,
format: initialData.ext, format: initialData.ext,
version: initialData.version, version: initialData.version,
enabled: initialData.enabled,
}; };
Object.defineProperties(this, { Object.defineProperties(this, {
@ -96,6 +108,16 @@
*/ */
get: () => data.version, get: () => data.version,
}, },
enabled: {
/**
* @name enabled
* @type {string}
* @memberof module:API.cvat.classes.Loader
* @readonly
* @instance
*/
get: () => data.enabled,
},
}); });
} }
} }

@ -402,6 +402,7 @@
frame: Math.min.apply(null, Object.keys(keyframes).map((frame) => +frame)), frame: Math.min.apply(null, Object.keys(keyframes).map((frame) => +frame)),
shapes: Object.values(keyframes), shapes: Object.values(keyframes),
group: 0, group: 0,
source: objectStates[0].source,
label_id: label.id, label_id: label.id,
attributes: Object.keys(objectStates[0].attributes) attributes: Object.keys(objectStates[0].attributes)
.reduce((accumulator, attrID) => { .reduce((accumulator, attrID) => {
@ -763,6 +764,7 @@
points: [...state.points], points: [...state.points],
type: state.shapeType, type: state.shapeType,
z_order: state.zOrder, z_order: state.zOrder,
source: state.source,
}); });
} else if (state.objectType === 'track') { } else if (state.objectType === 'track') {
constructed.tracks.push({ constructed.tracks.push({
@ -770,6 +772,7 @@
.filter((attr) => !labelAttributes[attr.spec_id].mutable), .filter((attr) => !labelAttributes[attr.spec_id].mutable),
frame: state.frame, frame: state.frame,
group: 0, group: 0,
source: state.source,
label_id: state.label.id, label_id: state.label.id,
shapes: [{ shapes: [{
attributes: attributes attributes: attributes
@ -797,15 +800,17 @@
.concat(imported.tracks) .concat(imported.tracks)
.concat(imported.shapes); .concat(imported.shapes);
this.history.do(HistoryActions.CREATED_OBJECTS, () => { if (objectStates.length) {
importedArray.forEach((object) => { this.history.do(HistoryActions.CREATED_OBJECTS, () => {
object.removed = true; importedArray.forEach((object) => {
}); object.removed = true;
}, () => { });
importedArray.forEach((object) => { }, () => {
object.removed = false; importedArray.forEach((object) => {
}); object.removed = false;
}, importedArray.map((object) => object.clientID), objectStates[0].frame); });
}, importedArray.map((object) => object.clientID), objectStates[0].frame);
}
return importedArray.map((value) => value.clientID); return importedArray.map((value) => value.clientID);
} }

@ -15,6 +15,7 @@
} = require('./common'); } = require('./common');
const { const {
colors, colors,
Source,
ObjectShape, ObjectShape,
ObjectType, ObjectType,
AttributeType, AttributeType,
@ -156,8 +157,7 @@
if (type === AttributeType.NUMBER) { if (type === AttributeType.NUMBER) {
return +value >= +values[0] return +value >= +values[0]
&& +value <= +values[1] && +value <= +values[1];
&& !((+value - +values[0]) % +values[2]);
} }
if (type === AttributeType.CHECKBOX) { if (type === AttributeType.CHECKBOX) {
@ -184,6 +184,7 @@
this.removed = false; this.removed = false;
this.lock = false; this.lock = false;
this.color = color; this.color = color;
this.source = data.source;
this.updated = Date.now(); this.updated = Date.now();
this.attributes = data.attributes.reduce((attributeAccumulator, attr) => { this.attributes = data.attributes.reduce((attributeAccumulator, attr) => {
attributeAccumulator[attr.spec_id] = attr.value; attributeAccumulator[attr.spec_id] = attr.value;
@ -333,7 +334,9 @@
const { width, height } = this.frameMeta[frame]; const { width, height } = this.frameMeta[frame];
fittedPoints = fitPoints(this.shapeType, data.points, width, height); fittedPoints = fitPoints(this.shapeType, data.points, width, height);
if ((!checkShapeArea(this.shapeType, fittedPoints)) || checkOutside(fittedPoints, width, height)) { if ((!checkShapeArea(this.shapeType, fittedPoints))
|| checkOutside(fittedPoints, width, height)
) {
fittedPoints = []; fittedPoints = [];
} }
} }
@ -491,6 +494,7 @@
frame: this.frame, frame: this.frame,
label_id: this.label.id, label_id: this.label.id,
group: this.group, group: this.group,
source: this.source,
}; };
} }
@ -519,51 +523,67 @@
updated: this.updated, updated: this.updated,
pinned: this.pinned, pinned: this.pinned,
frame, frame,
source: this.source,
}; };
} }
_savePoints(points, frame) { _savePoints(points, frame) {
const undoPoints = this.points; const undoPoints = this.points;
const redoPoints = points; const redoPoints = points;
const undoSource = this.source;
const redoSource = Source.MANUAL;
this.history.do(HistoryActions.CHANGED_POINTS, () => { this.history.do(HistoryActions.CHANGED_POINTS, () => {
this.points = undoPoints; this.points = undoPoints;
this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { }, () => {
this.points = redoPoints; this.points = redoPoints;
this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); }, [this.clientID], frame);
this.source = Source.MANUAL;
this.points = points; this.points = points;
} }
_saveOccluded(occluded, frame) { _saveOccluded(occluded, frame) {
const undoOccluded = this.occluded; const undoOccluded = this.occluded;
const redoOccluded = occluded; const redoOccluded = occluded;
const undoSource = this.source;
const redoSource = Source.MANUAL;
this.history.do(HistoryActions.CHANGED_OCCLUDED, () => { this.history.do(HistoryActions.CHANGED_OCCLUDED, () => {
this.occluded = undoOccluded; this.occluded = undoOccluded;
this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { }, () => {
this.occluded = redoOccluded; this.occluded = redoOccluded;
this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); }, [this.clientID], frame);
this.source = Source.MANUAL;
this.occluded = occluded; this.occluded = occluded;
} }
_saveZOrder(zOrder, frame) { _saveZOrder(zOrder, frame) {
const undoZOrder = this.zOrder; const undoZOrder = this.zOrder;
const redoZOrder = zOrder; const redoZOrder = zOrder;
const undoSource = this.source;
const redoSource = Source.MANUAL;
this.history.do(HistoryActions.CHANGED_ZORDER, () => { this.history.do(HistoryActions.CHANGED_ZORDER, () => {
this.zOrder = undoZOrder; this.zOrder = undoZOrder;
this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { }, () => {
this.zOrder = redoZOrder; this.zOrder = redoZOrder;
this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); }, [this.clientID], frame);
this.source = Source.MANUAL;
this.zOrder = zOrder; this.zOrder = zOrder;
} }
@ -658,6 +678,7 @@
frame: this.frame, frame: this.frame,
label_id: this.label.id, label_id: this.label.id,
group: this.group, group: this.group,
source: this.source,
attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => {
if (!labelAttributes[attrId].mutable) { if (!labelAttributes[attrId].mutable) {
attributeAccumulator.push({ attributeAccumulator.push({
@ -725,6 +746,7 @@
last, last,
}, },
frame, frame,
source: this.source,
}; };
} }
@ -910,13 +932,14 @@
}, [this.clientID], frame); }, [this.clientID], frame);
} }
_appendShapeActionToHistory(actionType, frame, undoShape, redoShape) { _appendShapeActionToHistory(actionType, frame, undoShape, redoShape, undoSource, redoSource) {
this.history.do(actionType, () => { this.history.do(actionType, () => {
if (!undoShape) { if (!undoShape) {
delete this.shapes[frame]; delete this.shapes[frame];
} else { } else {
this.shapes[frame] = undoShape; this.shapes[frame] = undoShape;
} }
this.source = undoSource;
this.updated = Date.now(); this.updated = Date.now();
}, () => { }, () => {
if (!redoShape) { if (!redoShape) {
@ -924,6 +947,7 @@
} else { } else {
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
} }
this.source = redoSource;
this.updated = Date.now(); this.updated = Date.now();
}, [this.clientID], frame); }, [this.clientID], frame);
} }
@ -931,6 +955,8 @@
_savePoints(points, frame) { _savePoints(points, frame) {
const current = this.get(frame); const current = this.get(frame);
const wasKeyframe = frame in this.shapes; const wasKeyframe = frame in this.shapes;
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], points } : { const redoShape = wasKeyframe ? { ...this.shapes[frame], points } : {
frame, frame,
@ -942,17 +968,22 @@
}; };
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
this.source = Source.MANUAL;
this._appendShapeActionToHistory( this._appendShapeActionToHistory(
HistoryActions.CHANGED_POINTS, HistoryActions.CHANGED_POINTS,
frame, frame,
undoShape, undoShape,
redoShape, redoShape,
undoSource,
redoSource,
); );
} }
_saveOutside(frame, outside) { _saveOutside(frame, outside) {
const current = this.get(frame); const current = this.get(frame);
const wasKeyframe = frame in this.shapes; const wasKeyframe = frame in this.shapes;
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], outside } : { const redoShape = wasKeyframe ? { ...this.shapes[frame], outside } : {
frame, frame,
@ -964,17 +995,22 @@
}; };
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
this.source = Source.MANUAL;
this._appendShapeActionToHistory( this._appendShapeActionToHistory(
HistoryActions.CHANGED_OUTSIDE, HistoryActions.CHANGED_OUTSIDE,
frame, frame,
undoShape, undoShape,
redoShape, redoShape,
undoSource,
redoSource,
); );
} }
_saveOccluded(occluded, frame) { _saveOccluded(occluded, frame) {
const current = this.get(frame); const current = this.get(frame);
const wasKeyframe = frame in this.shapes; const wasKeyframe = frame in this.shapes;
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], occluded } : { const redoShape = wasKeyframe ? { ...this.shapes[frame], occluded } : {
frame, frame,
@ -986,17 +1022,22 @@
}; };
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
this.source = Source.MANUAL;
this._appendShapeActionToHistory( this._appendShapeActionToHistory(
HistoryActions.CHANGED_OCCLUDED, HistoryActions.CHANGED_OCCLUDED,
frame, frame,
undoShape, undoShape,
redoShape, redoShape,
undoSource,
redoSource,
); );
} }
_saveZOrder(zOrder, frame) { _saveZOrder(zOrder, frame) {
const current = this.get(frame); const current = this.get(frame);
const wasKeyframe = frame in this.shapes; const wasKeyframe = frame in this.shapes;
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = wasKeyframe ? { ...this.shapes[frame], zOrder } : { const redoShape = wasKeyframe ? { ...this.shapes[frame], zOrder } : {
frame, frame,
@ -1008,11 +1049,14 @@
}; };
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
this.source = Source.MANUAL;
this._appendShapeActionToHistory( this._appendShapeActionToHistory(
HistoryActions.CHANGED_ZORDER, HistoryActions.CHANGED_ZORDER,
frame, frame,
undoShape, undoShape,
redoShape, redoShape,
undoSource,
redoSource,
); );
} }
@ -1025,6 +1069,8 @@
return; return;
} }
const undoSource = this.source;
const redoSource = Source.MANUAL;
const undoShape = wasKeyframe ? this.shapes[frame] : undefined; const undoShape = wasKeyframe ? this.shapes[frame] : undefined;
const redoShape = keyframe ? { const redoShape = keyframe ? {
frame, frame,
@ -1033,8 +1079,10 @@
outside: current.outside, outside: current.outside,
occluded: current.occluded, occluded: current.occluded,
attributes: {}, attributes: {},
source: current.source,
} : undefined; } : undefined;
this.source = Source.MANUAL;
if (redoShape) { if (redoShape) {
this.shapes[frame] = redoShape; this.shapes[frame] = redoShape;
} else { } else {
@ -1046,6 +1094,8 @@
frame, frame,
undoShape, undoShape,
redoShape, redoShape,
undoSource,
redoSource,
); );
} }
@ -1163,6 +1213,7 @@
frame: this.frame, frame: this.frame,
label_id: this.label.id, label_id: this.label.id,
group: this.group, group: this.group,
source: this.source,
attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => { attributes: Object.keys(this.attributes).reduce((attributeAccumulator, attrId) => {
attributeAccumulator.push({ attributeAccumulator.push({
spec_id: attrId, spec_id: attrId,
@ -1193,6 +1244,7 @@
color: this.color, color: this.color,
updated: this.updated, updated: this.updated,
frame, frame,
source: this.source,
}; };
} }
@ -1534,13 +1586,12 @@
} }
interpolatePosition(leftPosition, rightPosition, offset) { interpolatePosition(leftPosition, rightPosition, offset) {
const positionOffset = leftPosition.points.map((point, index) => ( const positionOffset = leftPosition.points.map((point, index) => (
rightPosition.points[index] - point rightPosition.points[index] - point
)) ));
return { return {
points: leftPosition.points.map((point ,index) => ( points: leftPosition.points.map((point, index) => (
point + positionOffset[index] * offset point + positionOffset[index] * offset
)), )),
occluded: leftPosition.occluded, occluded: leftPosition.occluded,
@ -1556,385 +1607,274 @@
} }
interpolatePosition(leftPosition, rightPosition, offset) { interpolatePosition(leftPosition, rightPosition, offset) {
function findBox(points) { if (offset === 0) {
let xmin = Number.MAX_SAFE_INTEGER;
let ymin = Number.MAX_SAFE_INTEGER;
let xmax = Number.MIN_SAFE_INTEGER;
let ymax = Number.MIN_SAFE_INTEGER;
for (let i = 0; i < points.length; i += 2) {
if (points[i] < xmin) xmin = points[i];
if (points[i + 1] < ymin) ymin = points[i + 1];
if (points[i] > xmax) xmax = points[i];
if (points[i + 1] > ymax) ymax = points[i + 1];
}
return { return {
xmin, points: [...leftPosition.points],
ymin, occluded: leftPosition.occluded,
xmax, outside: leftPosition.outside,
ymax, zOrder: leftPosition.zOrder,
}; };
} }
function normalize(points, box) { function toArray(points) {
const normalized = []; return points.reduce((acc, val) => {
const width = box.xmax - box.xmin; acc.push(val.x, val.y);
const height = box.ymax - box.ymin; return acc;
}, []);
}
for (let i = 0; i < points.length; i += 2) { function toPoints(array) {
normalized.push( return array.reduce((acc, _, index) => {
(points[i] - box.xmin) / width, if (index % 2) {
(points[i + 1] - box.ymin) / height, acc.push({
); x: array[index - 1],
} y: array[index],
});
}
return acc;
}, []);
}
return normalized; function curveLength(points) {
return points.slice(1).reduce((acc, _, index) => {
const dx = points[index + 1].x - points[index].x;
const dy = points[index + 1].y - points[index].y;
return acc + Math.sqrt(dx ** 2 + dy ** 2);
}, 0);
} }
function denormalize(points, box) { function curveToOffsetVec(points, length) {
const denormalized = []; const offsetVector = [0]; // with initial value
const width = box.xmax - box.xmin; let accumulatedLength = 0;
const height = box.ymax - box.ymin;
for (let i = 0; i < points.length; i += 2) { points.slice(1).forEach((_, index) => {
denormalized.push( const dx = points[index + 1].x - points[index].x;
points[i] * width + box.xmin, const dy = points[index + 1].y - points[index].y;
points[i + 1] * height + box.ymin, accumulatedLength += Math.sqrt(dx ** 2 + dy ** 2);
); offsetVector.push(accumulatedLength / length);
} });
return denormalized; return offsetVector;
} }
function toPoints(array) { function findNearestPair(value, curve) {
const points = []; let minimum = [0, Math.abs(value - curve[0])];
for (let i = 0; i < array.length; i += 2) { for (let i = 1; i < curve.length; i++) {
points.push({ const distance = Math.abs(value - curve[i]);
x: array[i], if (distance < minimum[1]) {
y: array[i + 1], minimum = [i, distance];
}); }
} }
return points; return minimum[0];
} }
function toArray(points) { function matchLeftRight(leftCurve, rightCurve) {
const array = []; const matching = {};
for (const point of points) { for (let i = 0; i < leftCurve.length; i++) {
array.push(point.x, point.y); matching[i] = [findNearestPair(leftCurve[i], rightCurve)];
} }
return array; return matching;
} }
function computeDistances(source, target) { function matchRightLeft(leftCurve, rightCurve, leftRightMatching) {
const distances = {}; const matchedRightPoints = Object.values(leftRightMatching).flat();
for (let i = 0; i < source.length; i++) { const unmatchedRightPoints = rightCurve.map((_, index) => index)
distances[i] = distances[i] || {}; .filter((index) => !matchedRightPoints.includes(index));
for (let j = 0; j < target.length; j++) { const updatedMatching = { ...leftRightMatching };
const dx = source[i].x - target[j].x;
const dy = source[i].y - target[j].y;
distances[i][j] = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); for (const rightPoint of unmatchedRightPoints) {
} const leftPoint = findNearestPair(rightCurve[rightPoint], leftCurve);
updatedMatching[leftPoint].push(rightPoint);
} }
return distances; for (const key of Object.keys(updatedMatching)) {
const sortedRightIndexes = updatedMatching[key]
.sort((a, b) => a - b);
updatedMatching[key] = sortedRightIndexes;
}
return updatedMatching;
} }
function truncateByThreshold(mapping, threshold) { function reduceInterpolation(interpolatedPoints, matching, leftPoints, rightPoints) {
for (const key of Object.keys(mapping)) { function averagePoint(points) {
if (mapping[key].distance > threshold) { let sumX = 0;
delete mapping[key]; let sumY = 0;
for (const point of points) {
sumX += point.x;
sumY += point.y;
} }
}
}
// https://en.wikipedia.org/wiki/Stable_marriage_problem return {
// TODO: One of important part of the algorithm is to correctly match x: sumX / points.length,
// "corner" points. Thus it is possible for each of such point calculate y: sumY / points.length,
// a descriptor (d) and use (x, y, d) to calculate the distance. One more };
// idea is to be sure that order or matched points is preserved. For example,
// if p1 matches q1 and p2 matches q2 and between p1 and p2 we don't have any
// points thus we should not have points between q1 and q2 as well.
function stableMarriageProblem(men, women, distances) {
const menPreferences = {};
for (const man of men) {
menPreferences[man] = women.concat()
.sort((w1, w2) => distances[man][w1] - distances[man][w2]);
} }
// Start alghoritm with max N^2 complexity function computeDistance(point1, point2) {
const womenMaybe = {}; // id woman:id man,distance return Math.sqrt(
const menBusy = {}; // id man:boolean ((point1.x - point2.x) ** 2) + ((point1.y - point2.y) ** 2),
let prefIndex = 0; );
}
// While there is at least one free man
while (Object.values(menBusy).length !== men.length) {
// Every man makes offer to the best woman
for (const man of men) {
// The man have already found a woman
if (menBusy[man]) {
continue;
}
const woman = menPreferences[man][prefIndex]; function minimizeSegment(baseLength, N, startInterpolated, stopInterpolated) {
const distance = distances[man][woman]; const threshold = baseLength / (2 * N);
const minimized = [interpolatedPoints[startInterpolated]];
let latestPushed = startInterpolated;
for (let i = startInterpolated + 1; i < stopInterpolated; i++) {
const distance = computeDistance(
interpolatedPoints[latestPushed], interpolatedPoints[i],
);
// A women chooses the best offer and says "maybe" if (distance >= threshold) {
if (woman in womenMaybe && womenMaybe[woman].distance > distance) { minimized.push(interpolatedPoints[i]);
// A woman got better offer latestPushed = i;
const prevChoice = womenMaybe[woman].value;
delete womenMaybe[woman];
delete menBusy[prevChoice];
} }
}
if (!(woman in womenMaybe)) { minimized.push(interpolatedPoints[stopInterpolated]);
womenMaybe[woman] = {
value: man, if (minimized.length === 2) {
distance, const distance = computeDistance(
}; interpolatedPoints[startInterpolated],
interpolatedPoints[stopInterpolated],
);
menBusy[man] = true; if (distance < threshold) {
return [averagePoint(minimized)];
} }
} }
prefIndex++; return minimized;
} }
const result = {}; const reduced = [];
for (const woman of Object.keys(womenMaybe)) { const interpolatedIndexes = {};
result[womenMaybe[woman].value] = { let accumulated = 0;
value: woman, for (let i = 0; i < leftPoints.length; i++) {
distance: womenMaybe[woman].distance, // eslint-disable-next-line
}; interpolatedIndexes[i] = matching[i].map(() => accumulated++);
} }
return result; function leftSegment(start, stop) {
} const startInterpolated = interpolatedIndexes[start][0];
const stopInterpolated = interpolatedIndexes[stop][0];
function getMapping(source, target) { if (startInterpolated === stopInterpolated) {
function sumEdges(points) { reduced.push(interpolatedPoints[startInterpolated]);
let result = 0; return;
for (let i = 1; i < points.length; i += 2) {
const distance = Math.sqrt(Math.pow(points[i].x - points[i - 1].x, 2)
+ Math.pow(points[i].y - points[i - 1].y, 2));
result += distance;
} }
// Corner case when work with one point const baseLength = curveLength(leftPoints.slice(start, stop + 1));
// Mapping in this case can't be wrong const N = stop - start + 1;
if (!result) {
return Number.MAX_SAFE_INTEGER;
}
return result; reduced.push(
...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated),
);
} }
function computeDeviation(points, average) { function rightSegment(leftPoint) {
let result = 0; const start = matching[leftPoint][0];
for (let i = 1; i < points.length; i += 2) { const [stop] = matching[leftPoint].slice(-1);
const distance = Math.sqrt(Math.pow(points[i].x - points[i - 1].x, 2) const startInterpolated = interpolatedIndexes[leftPoint][0];
+ Math.pow(points[i].y - points[i - 1].y, 2)); const [stopInterpolated] = interpolatedIndexes[leftPoint].slice(-1);
result += Math.pow(distance - average, 2); const baseLength = curveLength(rightPoints.slice(start, stop + 1));
} const N = stop - start + 1;
return result;
}
const processedSource = []; reduced.push(
const processedTarget = []; ...minimizeSegment(baseLength, N, startInterpolated, stopInterpolated),
);
const distances = computeDistances(source, target);
const mapping = stableMarriageProblem(Array.from(source.keys()),
Array.from(target.keys()), distances);
const average = (sumEdges(target)
+ sumEdges(source)) / (target.length + source.length);
const meanSquareDeviation = Math.sqrt((computeDeviation(source, average)
+ computeDeviation(target, average)) / (source.length + target.length));
const threshold = average + 3 * meanSquareDeviation; // 3 sigma rule
truncateByThreshold(mapping, threshold);
for (const key of Object.keys(mapping)) {
mapping[key] = mapping[key].value;
} }
// const receivingOrder = Object.keys(mapping).map(x => +x).sort((a,b) => a - b); let previousOpened = null;
const receivingOrder = this.appendMapping(mapping, source, target); for (let i = 0; i < leftPoints.length; i++) {
if (matching[i].length === 1) {
// check if left segment is opened
if (previousOpened !== null) {
// check if we should continue the left segment
if (matching[i][0] === matching[previousOpened][0]) {
continue;
} else {
// left segment found
const start = previousOpened;
const stop = i - 1;
leftSegment(start, stop);
// start next left segment
previousOpened = i;
}
} else {
// start next left segment
previousOpened = i;
}
} else {
// check if left segment is opened
if (previousOpened !== null) {
// left segment found
const start = previousOpened;
const stop = i - 1;
leftSegment(start, stop);
previousOpened = null;
}
for (const pointIdx of receivingOrder) { // right segment found
processedSource.push(source[pointIdx]); rightSegment(i);
processedTarget.push(target[mapping[pointIdx]]); }
} }
return [processedSource, processedTarget]; // check if there is an opened segment
} if (previousOpened !== null) {
leftSegment(previousOpened, leftPoints.length - 1);
}
if (offset === 0) { return reduced;
return {
points: [...leftPosition.points],
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
};
} }
let leftBox = findBox(leftPosition.points); // the algorithm below is based on fact that both left and right
let rightBox = findBox(rightPosition.points); // polyshapes have the same start point and the same draw direction
const leftPoints = toPoints(leftPosition.points);
// Sometimes (if shape has one point or shape is line), const rightPoints = toPoints(rightPosition.points);
// We can get box with zero area const leftOffsetVec = curveToOffsetVec(leftPoints, curveLength(leftPoints));
// Next computation will be with NaN in this case const rightOffsetVec = curveToOffsetVec(rightPoints, curveLength(rightPoints));
// We have to prevent it
const delta = 1;
if (leftBox.xmax - leftBox.xmin < delta || rightBox.ymax - rightBox.ymin < delta) {
leftBox = {
xmin: 0,
xmax: 1024, // TODO: Get actual image size
ymin: 0,
ymax: 768,
};
rightBox = leftBox;
}
const leftPoints = toPoints(normalize(leftPosition.points, leftBox)); const matching = matchLeftRight(leftOffsetVec, rightOffsetVec);
const rightPoints = toPoints(normalize(rightPosition.points, rightBox)); const completedMatching = matchRightLeft(
leftOffsetVec, rightOffsetVec, matching,
);
let newLeftPoints = []; const interpolatedPoints = Object.keys(completedMatching)
let newRightPoints = []; .map((leftPointIdx) => +leftPointIdx).sort((a, b) => a - b)
if (leftPoints.length > rightPoints.length) { .reduce((acc, leftPointIdx) => {
const [ const leftPoint = leftPoints[leftPointIdx];
processedRight, for (const rightPointIdx of completedMatching[leftPointIdx]) {
processedLeft, const rightPoint = rightPoints[rightPointIdx];
] = getMapping.call(this, rightPoints, leftPoints); acc.push({
newLeftPoints = processedLeft; x: leftPoint.x + (rightPoint.x - leftPoint.x) * offset,
newRightPoints = processedRight; y: leftPoint.y + (rightPoint.y - leftPoint.y) * offset,
} else { });
const [ }
processedLeft,
processedRight,
] = getMapping.call(this, leftPoints, rightPoints);
newLeftPoints = processedLeft;
newRightPoints = processedRight;
}
const absoluteLeftPoints = denormalize(toArray(newLeftPoints), leftBox); return acc;
const absoluteRightPoints = denormalize(toArray(newRightPoints), rightBox); }, []);
const interpolation = []; const reducedPoints = reduceInterpolation(
for (let i = 0; i < absoluteLeftPoints.length; i++) { interpolatedPoints,
interpolation.push(absoluteLeftPoints[i] + ( completedMatching,
absoluteRightPoints[i] - absoluteLeftPoints[i]) * offset); leftPoints,
} rightPoints,
);
return { return {
points: interpolation, points: toArray(reducedPoints),
occluded: leftPosition.occluded, occluded: leftPosition.occluded,
outside: leftPosition.outside, outside: leftPosition.outside,
zOrder: leftPosition.zOrder, zOrder: leftPosition.zOrder,
}; };
} }
// mapping is predicted order of points sourse_idx:target_idx
// some points from source and target can absent in mapping
// source, target - arrays of points. Target array size >= sourse array size
appendMapping(mapping, source, target) {
const targetMatched = Object.values(mapping).map((x) => +x);
const sourceMatched = Object.keys(mapping).map((x) => +x);
const orderForReceive = [];
function findNeighbors(point) {
let prev = point;
let next = point;
if (!targetMatched.length) {
// Prevent infinity loop
throw new ScriptingError('Interpolation mapping is empty');
}
while (!targetMatched.includes(prev)) {
prev--;
if (prev < 0) {
prev = target.length - 1;
}
}
while (!targetMatched.includes(next)) {
next++;
if (next >= target.length) {
next = 0;
}
}
return [prev, next];
}
function computeOffset(point, prev, next) {
const pathPoints = [];
while (prev !== next) {
pathPoints.push(target[prev]);
prev++;
if (prev >= target.length) {
prev = 0;
}
}
pathPoints.push(target[next]);
let curveLength = 0;
let offset = 0;
let iCrossed = false;
for (let k = 1; k < pathPoints.length; k++) {
const p1 = pathPoints[k];
const p2 = pathPoints[k - 1];
const distance = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
if (!iCrossed) {
offset += distance;
}
curveLength += distance;
if (target[point] === pathPoints[k]) {
iCrossed = true;
}
}
if (!curveLength) {
return 0;
}
return offset / curveLength;
}
for (let i = 0; i < target.length; i++) {
const index = targetMatched.indexOf(i);
if (index === -1) {
// We have to find a neighbours which have been mapped
const [prev, next] = findNeighbors(i);
// Now compute edge offset
const offset = computeOffset(i, prev, next);
// Get point between two neighbors points
const prevPoint = target[prev];
const nextPoint = target[next];
const autoPoint = {
x: prevPoint.x + (nextPoint.x - prevPoint.x) * offset,
y: prevPoint.y + (nextPoint.y - prevPoint.y) * offset,
};
// Put it into matched
source.push(autoPoint);
mapping[source.length - 1] = i;
orderForReceive.push(source.length - 1);
} else {
orderForReceive.push(sourceMatched[index]);
}
}
return orderForReceive;
}
} }
class PolygonTrack extends PolyTrack { class PolygonTrack extends PolyTrack {
@ -1945,6 +1885,26 @@
checkNumberOfPoints(this.shapeType, shape.points); checkNumberOfPoints(this.shapeType, shape.points);
} }
} }
interpolatePosition(leftPosition, rightPosition, offset) {
const copyLeft = {
...leftPosition,
points: [...leftPosition.points, leftPosition.points[0], leftPosition.points[1]],
};
const copyRight = {
...rightPosition,
points: [...rightPosition.points, rightPosition.points[0], rightPosition.points[1]],
};
const result = PolyTrack.prototype.interpolatePosition
.call(this, copyLeft, copyRight, offset);
return {
...result,
points: result.points.slice(0, -2),
};
}
} }
class PolylineTrack extends PolyTrack { class PolylineTrack extends PolyTrack {
@ -1965,6 +1925,27 @@
checkNumberOfPoints(this.shapeType, shape.points); checkNumberOfPoints(this.shapeType, shape.points);
} }
} }
interpolatePosition(leftPosition, rightPosition, offset) {
// interpolate only when one point in both left and right positions
if (leftPosition.points.length === 2 && rightPosition.points.length === 2) {
return {
points: leftPosition.points.map(
(value, index) => value + (rightPosition.points[index] - value) * offset,
),
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
};
}
return {
points: [...leftPosition.points],
occluded: leftPosition.occluded,
outside: leftPosition.outside,
zOrder: leftPosition.zOrder,
};
}
} }
class CuboidTrack extends Track { class CuboidTrack extends Track {
@ -1978,13 +1959,12 @@
} }
interpolatePosition(leftPosition, rightPosition, offset) { interpolatePosition(leftPosition, rightPosition, offset) {
const positionOffset = leftPosition.points.map((point, index) => ( const positionOffset = leftPosition.points.map((point, index) => (
rightPosition.points[index] - point rightPosition.points[index] - point
)) ));
return { return {
points: leftPosition.points.map((point ,index) => ( points: leftPosition.points.map((point, index) => (
point + positionOffset[index] * offset point + positionOffset[index] * offset
)), )),
occluded: leftPosition.occluded, occluded: leftPosition.occluded,

@ -104,7 +104,7 @@
const keys = ['id', 'label_id', 'group', 'frame', const keys = ['id', 'label_id', 'group', 'frame',
'occluded', 'z_order', 'points', 'type', 'shapes', 'occluded', 'z_order', 'points', 'type', 'shapes',
'attributes', 'value', 'spec_id', 'outside']; 'attributes', 'value', 'spec_id', 'source', 'outside'];
// Find created and updated objects // Find created and updated objects
for (const type of Object.keys(exported)) { for (const type of Object.keys(exported)) {

@ -77,6 +77,15 @@
} }
} }
async function closeSession(session) {
const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType);
if (cache.has(session)) {
cache.delete(session);
}
}
async function getAnnotations(session, frame, allTracks, filters) { async function getAnnotations(session, frame, allTracks, filters) {
const sessionType = session instanceof Task ? 'task' : 'job'; const sessionType = session instanceof Task ? 'task' : 'job';
const cache = getCache(sessionType); const cache = getCache(sessionType);
@ -365,5 +374,6 @@
redoActions, redoActions,
clearActions, clearActions,
getActions, getActions,
closeSession,
}; };
})(); })();

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 Intel Corporation * Copyright (C) 2019-2020 Intel Corporation
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
@ -12,6 +12,7 @@
(() => { (() => {
const PluginRegistry = require('./plugins'); const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const lambdaManager = require('./lambda-manager');
const { const {
isBoolean, isBoolean,
isInteger, isInteger,
@ -20,10 +21,7 @@
checkFilter, checkFilter,
} = require('./common'); } = require('./common');
const { const { TaskStatus, TaskMode } = require('./enums');
TaskStatus,
TaskMode,
} = require('./enums');
const User = require('./user'); const User = require('./user');
const { AnnotationFormats } = require('./annotation-formats.js'); const { AnnotationFormats } = require('./annotation-formats.js');
@ -54,6 +52,13 @@
cvat.plugins.list.implementation = PluginRegistry.list; cvat.plugins.list.implementation = PluginRegistry.list;
cvat.plugins.register.implementation = PluginRegistry.register.bind(cvat); 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 () => { cvat.server.about.implementation = async () => {
const result = await serverProxy.server.about(); const result = await serverProxy.server.about();
return result; return result;
@ -76,8 +81,10 @@
cvat.server.register.implementation = async (username, firstName, lastName, cvat.server.register.implementation = async (username, firstName, lastName,
email, password1, password2, userConfirmations) => { email, password1, password2, userConfirmations) => {
await serverProxy.server.register(username, firstName, lastName, email, const user = await serverProxy.server.register(username, firstName,
password1, password2, userConfirmations); lastName, email, password1, password2, userConfirmations);
return new User(user);
}; };
cvat.server.login.implementation = async (username, password) => { cvat.server.login.implementation = async (username, password) => {
@ -88,6 +95,10 @@
await serverProxy.server.logout(); await serverProxy.server.logout();
}; };
cvat.server.changePassword.implementation = async (oldPassword, newPassword1, newPassword2) => {
await serverProxy.server.changePassword(oldPassword, newPassword1, newPassword2);
};
cvat.server.authorized.implementation = async () => { cvat.server.authorized.implementation = async () => {
const result = await serverProxy.server.authorized(); const result = await serverProxy.server.authorized();
return result; return result;

@ -20,6 +20,7 @@ function build() {
const Statistics = require('./statistics'); const Statistics = require('./statistics');
const { Job, Task } = require('./session'); const { Job, Task } = require('./session');
const { Attribute, Label } = require('./labels'); const { Attribute, Label } = require('./labels');
const MLModel = require('./ml-model');
const { const {
ShareFileType, ShareFileType,
@ -30,7 +31,9 @@ function build() {
ObjectShape, ObjectShape,
LogType, LogType,
HistoryActions, HistoryActions,
RQStatus,
colors, colors,
Source,
} = require('./enums'); } = require('./enums');
const { const {
@ -127,10 +130,10 @@ function build() {
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError} * @throws {module:API.cvat.exceptions.ServerError}
*/ */
async userAgreements() { async userAgreements() {
const result = await PluginRegistry const result = await PluginRegistry
.apiWrapper(cvat.server.userAgreements); .apiWrapper(cvat.server.userAgreements);
return result; return result;
}, },
/** /**
@ -145,10 +148,19 @@ function build() {
* @param {string} password1 A password for the new account * @param {string} password1 A password for the new account
* @param {string} password2 The confirmation password for the new account * @param {string} password2 The confirmation password for the new account
* @param {Object} userConfirmations An user confirmations of terms of use if needed * @param {Object} userConfirmations An user confirmations of terms of use if needed
* @returns {Object} response data
* @throws {module:API.cvat.exceptions.PluginError} * @throws {module:API.cvat.exceptions.PluginError}
* @throws {module:API.cvat.exceptions.ServerError} * @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 const result = await PluginRegistry
.apiWrapper(cvat.server.register, username, firstName, .apiWrapper(cvat.server.register, username, firstName,
lastName, email, password1, password2, userConfirmations); lastName, email, password1, password2, userConfirmations);
@ -182,6 +194,19 @@ function build() {
.apiWrapper(cvat.server.logout); .apiWrapper(cvat.server.logout);
return result; 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 allows to know whether you are authorized on the server
* @method authorized * @method authorized
@ -423,6 +448,119 @@ function build() {
return result; 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 to working with logs
* @namespace logger * @namespace logger
@ -530,7 +668,9 @@ function build() {
ObjectShape, ObjectShape,
LogType, LogType,
HistoryActions, HistoryActions,
RQStatus,
colors, colors,
Source,
}, },
/** /**
* Namespace is used for access to exceptions * Namespace is used for access to exceptions
@ -559,6 +699,7 @@ function build() {
Label, Label,
Statistics, Statistics,
ObjectState, ObjectState,
MLModel,
}, },
}; };
@ -567,6 +708,7 @@ function build() {
cvat.jobs = Object.freeze(cvat.jobs); cvat.jobs = Object.freeze(cvat.jobs);
cvat.users = Object.freeze(cvat.users); cvat.users = Object.freeze(cvat.users);
cvat.plugins = Object.freeze(cvat.plugins); cvat.plugins = Object.freeze(cvat.plugins);
cvat.lambda = Object.freeze(cvat.lambda);
cvat.client = Object.freeze(cvat.client); cvat.client = Object.freeze(cvat.client);
cvat.enums = Object.freeze(cvat.enums); cvat.enums = Object.freeze(cvat.enums);

@ -34,6 +34,26 @@
COMPLETED: 'completed', 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 * Task modes
* @enum {string} * @enum {string}
@ -104,6 +124,20 @@
CUBOID: 'cuboid', CUBOID: 'cuboid',
}); });
/**
* Annotation type
* @enum {string}
* @name Source
* @memberof module:API.cvat.enums
* @property {string} MANUAL 'manual'
* @property {string} AUTO 'auto'
* @readonly
*/
const Source = Object.freeze({
MANUAL:'manual',
AUTO:'auto',
});
/** /**
* Logger event types * Logger event types
* @enum {string} * @enum {string}
@ -190,6 +224,7 @@
* @property {string} CHANGED_LOCK Changed lock * @property {string} CHANGED_LOCK Changed lock
* @property {string} CHANGED_COLOR Changed color * @property {string} CHANGED_COLOR Changed color
* @property {string} CHANGED_HIDDEN Changed hidden * @property {string} CHANGED_HIDDEN Changed hidden
* @property {string} CHANGED_SOURCE Changed source
* @property {string} MERGED_OBJECTS Merged objects * @property {string} MERGED_OBJECTS Merged objects
* @property {string} SPLITTED_TRACK Splitted track * @property {string} SPLITTED_TRACK Splitted track
* @property {string} GROUPED_OBJECTS Grouped objects * @property {string} GROUPED_OBJECTS Grouped objects
@ -209,6 +244,7 @@
CHANGED_PINNED: 'Changed pinned', CHANGED_PINNED: 'Changed pinned',
CHANGED_COLOR: 'Changed color', CHANGED_COLOR: 'Changed color',
CHANGED_HIDDEN: 'Changed hidden', CHANGED_HIDDEN: 'Changed hidden',
CHANGED_SOURCE: 'Changed source',
MERGED_OBJECTS: 'Merged objects', MERGED_OBJECTS: 'Merged objects',
SPLITTED_TRACK: 'Splitted track', SPLITTED_TRACK: 'Splitted track',
GROUPED_OBJECTS: 'Grouped objects', GROUPED_OBJECTS: 'Grouped objects',
@ -216,6 +252,18 @@
REMOVED_OBJECT: 'Removed object', 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 * Array of hex colors
* @name colors * @name colors
@ -224,10 +272,11 @@
* @readonly * @readonly
*/ */
const colors = [ const colors = [
'#FF355E', '#E936A7', '#FD5B78', '#FF007C', '#FF00CC', '#66FF66', '#33ddff', '#fa3253', '#34d1b7', '#ff007c', '#ff6037', '#ddff33',
'#50BFE6', '#CCFF00', '#FFFF66', '#FF9966', '#FF6037', '#FFCC33', '#24b353', '#b83df5', '#66ff66', '#32b7fa', '#ffcc33', '#83e070',
'#AAF0D1', '#FF3855', '#FFF700', '#A7F432', '#FF5470', '#FAFA37', '#fafa37', '#5986b3', '#8c78f0', '#ff6a4d', '#f078f0', '#2a7dd1',
'#FF7A00', '#FF9933', '#AFE313', '#00CC99', '#FF5050', '#733380', '#b25050', '#cc3366', '#cc9933', '#aaf0d1', '#ff00cc', '#3df53d',
'#fa32b7', '#fa7dbb', '#ff355e', '#f59331', '#3d3df5', '#733380',
]; ];
module.exports = { module.exports = {
@ -238,7 +287,10 @@
ObjectType, ObjectType,
ObjectShape, ObjectShape,
LogType, LogType,
ModelType,
HistoryActions, HistoryActions,
RQStatus,
colors, colors,
Source,
}; };
})(); })();

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

@ -10,7 +10,6 @@
(() => { (() => {
const { const {
AttributeType, AttributeType,
colors,
} = require('./enums'); } = require('./enums');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
@ -150,9 +149,6 @@
} }
} }
if (typeof (data.id) !== 'undefined') {
data.color = colors[data.id % colors.length];
}
data.attributes = []; data.attributes = [];
if (Object.prototype.hasOwnProperty.call(initialData, 'attributes') if (Object.prototype.hasOwnProperty.call(initialData, 'attributes')
@ -193,10 +189,10 @@
color: { color: {
get: () => data.color, get: () => data.color,
set: (color) => { set: (color) => {
if (colors.includes(color)) { if (typeof color === 'string' && color.match(/^#[0-9a-f]{6}$|^$/)) {
data.color = color; data.color = color;
} else { } else {
throw new ArgumentError('Trying to set unknown color'); throw new ArgumentError('Trying to set wrong color format');
} }
}, },
}, },
@ -217,6 +213,7 @@
const object = { const object = {
name: this.name, name: this.name,
attributes: [...this.attributes.map((el) => el.toJSON())], attributes: [...this.attributes.map((el) => el.toJSON())],
color: this.color,
}; };
if (typeof (this.id) !== 'undefined') { if (typeof (this.id) !== 'undefined') {

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

@ -3,6 +3,8 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
const { Source } = require('./enums');
/* global /* global
require:false require:false
*/ */
@ -22,7 +24,7 @@
* </br> Necessary fields: objectType, shapeType, frame, updated, group * </br> Necessary fields: objectType, shapeType, frame, updated, group
* </br> Optional fields: keyframes, clientID, serverID * </br> Optional fields: keyframes, clientID, serverID
* </br> Optional fields which can be set later: points, zOrder, outside, * </br> Optional fields which can be set later: points, zOrder, outside,
* occluded, hidden, attributes, lock, label, color, keyframe * occluded, hidden, attributes, lock, label, color, keyframe, source
*/ */
constructor(serialized) { constructor(serialized) {
const data = { const data = {
@ -39,6 +41,7 @@
color: null, color: null,
hidden: null, hidden: null,
pinned: null, pinned: null,
source: Source.MANUAL,
keyframes: serialized.keyframes, keyframes: serialized.keyframes,
group: serialized.group, group: serialized.group,
updated: serialized.updated, updated: serialized.updated,
@ -109,6 +112,16 @@
*/ */
get: () => data.shapeType, get: () => data.shapeType,
}, },
source: {
/**
* @name source
* @type {module:API.cvat.enums.Source}
* @memberof module:API.cvat.classes.ObjectState
* @readonly
* @instance
*/
get: () => data.source,
},
clientID: { clientID: {
/** /**
* @name clientID * @name clientID
@ -344,6 +357,9 @@
this.label = serialized.label; this.label = serialized.label;
this.lock = serialized.lock; this.lock = serialized.lock;
if ([Source.MANUAL, Source.AUTO].includes(serialized.source)) {
data.source = serialized.source;
}
if (typeof (serialized.zOrder) === 'number') { if (typeof (serialized.zOrder) === 'number') {
this.zOrder = serialized.zOrder; this.zOrder = serialized.zOrder;
} }

@ -162,7 +162,6 @@
response = await Axios.get(`${backendAPI}/restrictions/user-agreements`, { response = await Axios.get(`${backendAPI}/restrictions/user-agreements`, {
proxy: config.proxy, proxy: config.proxy,
}); });
} catch (errorData) { } catch (errorData) {
throw generateError(errorData); throw generateError(errorData);
} }
@ -170,7 +169,15 @@
return response.data; 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; let response = null;
try { try {
const data = JSON.stringify({ const data = JSON.stringify({
@ -239,6 +246,24 @@
Axios.defaults.headers.common.Authorization = ''; 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() { async function authorized() {
try { try {
await module.exports.users.getSelf(); 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({ Object.defineProperties(this, Object.freeze({
server: { server: {
value: Object.freeze({ value: Object.freeze({
@ -671,6 +786,7 @@
exception, exception,
login, login,
logout, logout,
changePassword,
authorized, authorized,
register, register,
request: serverRequest, request: serverRequest,
@ -731,6 +847,18 @@
}), }),
writable: false, 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 PluginRegistry = require('./plugins');
const loggerStorage = require('./logger-storage'); const loggerStorage = require('./logger-storage');
const serverProxy = require('./server-proxy'); const serverProxy = require('./server-proxy');
const { getFrame, getRanges, getPreview } = require('./frames'); const {
getFrame,
getRanges,
getPreview,
clear: clearFrames,
} = require('./frames');
const { ArgumentError } = require('./exceptions'); const { ArgumentError } = require('./exceptions');
const { TaskStatus } = require('./enums'); const { TaskStatus } = require('./enums');
const { Label } = require('./labels'); const { Label } = require('./labels');
@ -1309,6 +1314,21 @@
}; };
} }
/**
* Method removes all task related data from the client (annotations, history, etc.)
* @method close
* @returns {module:API.cvat.classes.Task}
* @memberof module:API.cvat.classes.Task
* @readonly
* @async
* @instance
* @throws {module:API.cvat.exceptions.PluginError}
*/
async close() {
const result = await PluginRegistry.apiWrapper.call(this, Task.prototype.close);
return result;
}
/** /**
* Method updates data of a created task or creates new task from scratch * Method updates data of a created task or creates new task from scratch
* @method save * @method save
@ -1372,6 +1392,7 @@
redoActions, redoActions,
clearActions, clearActions,
getActions, getActions,
closeSession,
} = require('./annotations'); } = require('./annotations');
buildDublicatedAPI(Job.prototype); buildDublicatedAPI(Job.prototype);
@ -1576,6 +1597,16 @@
return result; return result;
}; };
Task.prototype.close.implementation = function closeTask() {
clearFrames(this.id);
for (const job of this.jobs) {
closeSession(job);
}
closeSession(this);
return this;
};
Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) { Task.prototype.save.implementation = async function saveTaskImplementation(onUpdate) {
// TODO: Add ability to change an owner and an assignee // TODO: Add ability to change an owner and an assignee
if (typeof (this.id) !== 'undefined') { if (typeof (this.id) !== 'undefined') {

@ -23,6 +23,7 @@
is_staff: null, is_staff: null,
is_superuser: null, is_superuser: null,
is_active: null, is_active: null,
email_verification_required: null,
}; };
for (const property in data) { for (const property in data) {
@ -143,6 +144,16 @@
*/ */
get: () => data.is_active, get: () => data.is_active,
}, },
isVerified: {
/**
* @name isVerified
* @type {boolean}
* @memberof module:API.cvat.classes.User
* @readonly
* @instance
*/
get: () => !data.email_verification_required,
},
})); }));
} }
} }

@ -30,10 +30,12 @@ const webConfig = {
target: 'web', target: 'web',
mode: 'production', mode: 'production',
devtool: 'source-map', devtool: 'source-map',
entry: './src/api.js', entry: {
'cvat-core': './src/api.js',
},
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'cvat-core.min.js', filename: '[name].[contenthash].min.js',
library: 'cvat', library: 'cvat',
libraryTarget: 'window', libraryTarget: 'window',
}, },
@ -58,7 +60,7 @@ const webConfig = {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
publicPath: '/static/engine/js/3rdparty/', publicPath: '/static/engine/js/3rdparty/',
name: '[name].js', name: '[name].[contenthash].js',
}, },
}, },
}, { }, {
@ -68,7 +70,7 @@ const webConfig = {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
publicPath: '/static/engine/js/', publicPath: '/static/engine/js/',
name: '[name].js', name: '[name].[contenthash].js',
}, },
}, },
}, },

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

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

@ -295,7 +295,26 @@ class FrameProvider {
worker.terminate(); 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; this._frames[event.data.index] = event.data.data;
if (this._decodingBlocks[`${start}:${end}`].resolveCallback) { if (this._decodingBlocks[`${start}:${end}`].resolveCallback) {

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

@ -9,10 +9,12 @@ const CopyPlugin = require('copy-webpack-plugin');
const cvatData = { const cvatData = {
target: 'web', target: 'web',
mode: 'production', mode: 'production',
entry: './src/js/cvat-data.js', entry: {
'cvat-data': './src/js/cvat-data.js',
},
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'cvat-data.min.js', filename: '[name].[contenthash].min.js',
library: 'cvatData', library: 'cvatData',
libraryTarget: 'window', libraryTarget: 'window',
}, },
@ -39,7 +41,7 @@ const cvatData = {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
publicPath: '/', publicPath: '/',
name: '[name].js', name: '[name].[contenthash].js',
}, },
}, },
}, { }, {
@ -48,7 +50,7 @@ const cvatData = {
loader: 'worker-loader', loader: 'worker-loader',
options: { options: {
publicPath: '/3rdparty/', publicPath: '/3rdparty/',
name: '3rdparty/[name].js', name: '3rdparty/[name].[contenthash].js',
}, },
}, },
}, },

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.2.0", "version": "1.8.4",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -13,14 +13,19 @@
} }
}, },
"@ant-design/create-react-context": { "@ant-design/create-react-context": {
"version": "0.2.4", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/@ant-design/create-react-context/-/create-react-context-0.2.4.tgz", "resolved": "https://registry.npmjs.org/@ant-design/create-react-context/-/create-react-context-0.2.5.tgz",
"integrity": "sha512-8sw+/w6r+aEbd+OJ62ojoSE4zDt/3yfQydmbWFznoftjr8v/opOswGjM+/MU0rSaREbluqzOmZ6xdecHpSaS2w==", "integrity": "sha512-1rMAa4qgP2lfl/QBH9i78+Gjxtj9FTMpMyDGZsEBW5Kih72EuUo9958mV8PgpRkh4uwPSQ7vVZWXeyNZXVAFDg==",
"requires": { "requires": {
"gud": "^1.0.0", "gud": "^1.0.0",
"warning": "^4.0.3" "warning": "^4.0.3"
} }
}, },
"@ant-design/css-animation": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@ant-design/css-animation/-/css-animation-1.7.2.tgz",
"integrity": "sha512-bvVOe7A+r7lws58B7r+fgnQDK90cV45AXuvGx6i5CCSX1W/M3AJnHsNggDANBxEtWdNdFWcDd5LorB+RdSIlBw=="
},
"@ant-design/icons": { "@ant-design/icons": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-2.1.1.tgz",
@ -975,6 +980,11 @@
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==",
"dev": true "dev": true
}, },
"@icons/material": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw=="
},
"@types/eslint-visitor-keys": { "@types/eslint-visitor-keys": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@ -1036,6 +1046,11 @@
"integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==", "integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==",
"dev": true "dev": true
}, },
"@types/platform": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/platform/-/platform-1.3.2.tgz",
"integrity": "sha512-Tn6OuJDAG7bJbyi4R7HqcxXp1w2lmIxVXqyNhPt1Bm0FO2EWIi3CI87JVzF7ncqK0ZMPuUycS3wTMIk85EeF1Q=="
},
"@types/prop-types": { "@types/prop-types": {
"version": "15.7.3", "version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
@ -1056,6 +1071,14 @@
"csstype": "^2.2.0" "csstype": "^2.2.0"
} }
}, },
"@types/react-color": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.2.tgz",
"integrity": "sha512-FhrRy0xEYEpysl1iKL11ynJc79H6ztyYc4xD1pliZyygEChleTlHGohb/bClTYPN8XeSw6yaz45l3YW5SGYftQ==",
"requires": {
"@types/react": "*"
}
},
"@types/react-dom": { "@types/react-dom": {
"version": "16.9.3", "version": "16.9.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.3.tgz",
@ -1506,9 +1529,9 @@
} }
}, },
"antd": { "antd": {
"version": "3.25.2", "version": "3.26.17",
"resolved": "https://registry.npmjs.org/antd/-/antd-3.25.2.tgz", "resolved": "https://registry.npmjs.org/antd/-/antd-3.26.17.tgz",
"integrity": "sha512-+qF1bgU7rUkPIkggIIV0fmm+9pPacl50BBd6NNUR2+kKJOFYjwrnP39ZqJRsYNy5bX9VgR454fz9KEuW7HPjog==", "integrity": "sha512-P9uSK8SZ/1AvhQCC6aaLEkVrQhjbfZyUnqNV+lDnPqtudnZD2Ycy7Og+/EhuOBsQpYQvVT2aPLMgQWFv8tdJkA==",
"requires": { "requires": {
"@ant-design/create-react-context": "^0.2.4", "@ant-design/create-react-context": "^0.2.4",
"@ant-design/icons": "~2.1.1", "@ant-design/icons": "~2.1.1",
@ -1521,18 +1544,19 @@
"css-animation": "^1.5.0", "css-animation": "^1.5.0",
"dom-closest": "^0.2.0", "dom-closest": "^0.2.0",
"enquire.js": "^2.1.6", "enquire.js": "^2.1.6",
"is-mobile": "^2.1.0",
"lodash": "^4.17.13", "lodash": "^4.17.13",
"moment": "^2.24.0", "moment": "^2.24.0",
"omit.js": "^1.0.2", "omit.js": "^1.0.2",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"raf": "^3.4.1", "raf": "^3.4.1",
"rc-animate": "^2.10.2", "rc-animate": "^2.10.2",
"rc-calendar": "~9.15.5", "rc-calendar": "~9.15.7",
"rc-cascader": "~0.17.4", "rc-cascader": "~0.17.4",
"rc-checkbox": "~2.1.6", "rc-checkbox": "~2.1.6",
"rc-collapse": "~1.11.3", "rc-collapse": "~1.11.3",
"rc-dialog": "~7.5.2", "rc-dialog": "~7.6.0",
"rc-drawer": "~3.0.0", "rc-drawer": "~3.1.1",
"rc-dropdown": "~2.4.1", "rc-dropdown": "~2.4.1",
"rc-editor-mention": "^1.1.13", "rc-editor-mention": "^1.1.13",
"rc-form": "^2.4.10", "rc-form": "^2.4.10",
@ -1540,7 +1564,7 @@
"rc-mentions": "~0.4.0", "rc-mentions": "~0.4.0",
"rc-menu": "~7.5.1", "rc-menu": "~7.5.1",
"rc-notification": "~3.3.1", "rc-notification": "~3.3.1",
"rc-pagination": "~1.20.5", "rc-pagination": "~1.20.11",
"rc-progress": "~2.5.0", "rc-progress": "~2.5.0",
"rc-rate": "~2.5.0", "rc-rate": "~2.5.0",
"rc-resize-observer": "^0.1.0", "rc-resize-observer": "^0.1.0",
@ -1548,15 +1572,15 @@
"rc-slider": "~8.7.1", "rc-slider": "~8.7.1",
"rc-steps": "~3.5.0", "rc-steps": "~3.5.0",
"rc-switch": "~1.9.0", "rc-switch": "~1.9.0",
"rc-table": "~6.9.4", "rc-table": "~6.10.5",
"rc-tabs": "~9.6.4", "rc-tabs": "~9.7.0",
"rc-time-picker": "~3.7.1", "rc-time-picker": "~3.7.1",
"rc-tooltip": "~3.7.3", "rc-tooltip": "~3.7.3",
"rc-tree": "~2.1.0", "rc-tree": "~2.1.0",
"rc-tree-select": "~2.9.1", "rc-tree-select": "~2.9.1",
"rc-trigger": "^2.6.2", "rc-trigger": "^2.6.2",
"rc-upload": "~2.9.1", "rc-upload": "~2.9.1",
"rc-util": "^4.10.0", "rc-util": "^4.16.1",
"react-lazy-load": "^3.0.13", "react-lazy-load": "^3.0.13",
"react-lifecycles-compat": "^3.0.4", "react-lifecycles-compat": "^3.0.4",
"react-slick": "~0.25.2", "react-slick": "~0.25.2",
@ -3306,7 +3330,6 @@
"convert-source-map": "^1.1.0", "convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0", "fs-readdir-recursive": "^1.1.0",
"glob": "^7.0.0", "glob": "^7.0.0",
"lodash": "^4.17.13",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"output-file-sync": "^2.0.0", "output-file-sync": "^2.0.0",
"slash": "^2.0.0", "slash": "^2.0.0",
@ -3336,7 +3359,6 @@
"convert-source-map": "^1.1.0", "convert-source-map": "^1.1.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"json5": "^2.1.0", "json5": "^2.1.0",
"lodash": "^4.17.13",
"resolve": "^1.3.2", "resolve": "^1.3.2",
"semver": "^5.4.1", "semver": "^5.4.1",
"source-map": "^0.5.0" "source-map": "^0.5.0"
@ -3372,7 +3394,6 @@
"requires": { "requires": {
"@babel/types": "^7.5.5", "@babel/types": "^7.5.5",
"jsesc": "^2.5.1", "jsesc": "^2.5.1",
"lodash": "^4.17.13",
"source-map": "^0.5.0", "source-map": "^0.5.0",
"trim-right": "^1.0.1" "trim-right": "^1.0.1"
} }
@ -3423,8 +3444,7 @@
"integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==",
"requires": { "requires": {
"@babel/helper-function-name": "^7.1.0", "@babel/helper-function-name": "^7.1.0",
"@babel/types": "^7.5.5", "@babel/types": "^7.5.5"
"lodash": "^4.17.13"
} }
}, },
"@babel/helper-explode-assignable-expression": { "@babel/helper-explode-assignable-expression": {
@ -3487,8 +3507,7 @@
"@babel/helper-simple-access": "^7.1.0", "@babel/helper-simple-access": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.4.4", "@babel/helper-split-export-declaration": "^7.4.4",
"@babel/template": "^7.4.4", "@babel/template": "^7.4.4",
"@babel/types": "^7.5.5", "@babel/types": "^7.5.5"
"lodash": "^4.17.13"
} }
}, },
"@babel/helper-optimise-call-expression": { "@babel/helper-optimise-call-expression": {
@ -3507,10 +3526,7 @@
"@babel/helper-regex": { "@babel/helper-regex": {
"version": "7.5.5", "version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz",
"integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw=="
"requires": {
"lodash": "^4.17.13"
}
}, },
"@babel/helper-remap-async-to-generator": { "@babel/helper-remap-async-to-generator": {
"version": "7.1.0", "version": "7.1.0",
@ -3622,7 +3638,6 @@
"requires": { "requires": {
"@babel/types": "^7.8.7", "@babel/types": "^7.8.7",
"jsesc": "^2.5.1", "jsesc": "^2.5.1",
"lodash": "^4.17.13",
"source-map": "^0.5.0" "source-map": "^0.5.0"
} }
}, },
@ -3734,8 +3749,7 @@
"@babel/parser": "^7.8.6", "@babel/parser": "^7.8.6",
"@babel/types": "^7.8.6", "@babel/types": "^7.8.6",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0", "globals": "^11.1.0"
"lodash": "^4.17.13"
} }
}, },
"@babel/types": { "@babel/types": {
@ -3744,7 +3758,6 @@
"integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==",
"requires": { "requires": {
"esutils": "^2.0.2", "esutils": "^2.0.2",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
@ -3888,8 +3901,7 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz",
"integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==",
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0"
"lodash": "^4.17.13"
} }
}, },
"@babel/plugin-transform-classes": { "@babel/plugin-transform-classes": {
@ -4232,8 +4244,7 @@
"@babel/parser": "^7.5.5", "@babel/parser": "^7.5.5",
"@babel/types": "^7.5.5", "@babel/types": "^7.5.5",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0", "globals": "^11.1.0"
"lodash": "^4.17.13"
}, },
"dependencies": { "dependencies": {
"debug": { "debug": {
@ -4257,7 +4268,6 @@
"integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==",
"requires": { "requires": {
"esutils": "^2.0.2", "esutils": "^2.0.2",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
@ -5134,7 +5144,6 @@
"browserify-rsa": "^4.0.0", "browserify-rsa": "^4.0.0",
"create-hash": "^1.1.0", "create-hash": "^1.1.0",
"create-hmac": "^1.1.2", "create-hmac": "^1.1.2",
"elliptic": "^6.0.0",
"inherits": "^2.0.1", "inherits": "^2.0.1",
"parse-asn1": "^5.0.0" "parse-asn1": "^5.0.0"
} }
@ -5677,8 +5686,7 @@
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
"requires": { "requires": {
"bn.js": "^4.1.0", "bn.js": "^4.1.0"
"elliptic": "^6.0.0"
} }
}, },
"create-error-class": { "create-error-class": {
@ -6167,20 +6175,6 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz",
"integrity": "sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==" "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": { "emoji-regex": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
@ -6292,7 +6286,6 @@
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.3.0", "levn": "^0.3.0",
"lodash": "^4.17.14",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -7215,7 +7208,6 @@
"integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==",
"requires": { "requires": {
"glob": "~7.1.1", "glob": "~7.1.1",
"lodash": "~4.17.12",
"minimatch": "~3.0.2" "minimatch": "~3.0.2"
} }
}, },
@ -7434,7 +7426,6 @@
"requires": { "requires": {
"http-proxy": "^1.17.0", "http-proxy": "^1.17.0",
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10" "micromatch": "^3.1.10"
} }
}, },
@ -7593,7 +7584,6 @@
"cli-width": "^2.0.0", "cli-width": "^2.0.0",
"external-editor": "^3.0.3", "external-editor": "^3.0.3",
"figures": "^2.0.0", "figures": "^2.0.0",
"lodash": "^4.17.12",
"mute-stream": "0.0.7", "mute-stream": "0.0.7",
"run-async": "^2.2.0", "run-async": "^2.2.0",
"rxjs": "^6.4.0", "rxjs": "^6.4.0",
@ -8098,11 +8088,6 @@
"path-exists": "^3.0.0" "path-exists": "^3.0.0"
} }
}, },
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash._reinterpolate": { "lodash._reinterpolate": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
@ -8641,7 +8626,6 @@
"get-stdin": "^4.0.1", "get-stdin": "^4.0.1",
"glob": "^7.0.3", "glob": "^7.0.3",
"in-publish": "^2.0.0", "in-publish": "^2.0.0",
"lodash": "^4.17.15",
"meow": "^3.7.0", "meow": "^3.7.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"nan": "^2.13.2", "nan": "^2.13.2",
@ -10273,7 +10257,6 @@
"integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
"requires": { "requires": {
"glob": "^7.0.0", "glob": "^7.0.0",
"lodash": "^4.0.0",
"scss-tokenizer": "^0.2.3", "scss-tokenizer": "^0.2.3",
"yargs": "^7.0.0" "yargs": "^7.0.0"
}, },
@ -11221,7 +11204,6 @@
"integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==", "integrity": "sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==",
"requires": { "requires": {
"ajv": "^6.10.2", "ajv": "^6.10.2",
"lodash": "^4.17.14",
"slice-ansi": "^2.1.0", "slice-ansi": "^2.1.0",
"string-width": "^3.0.0" "string-width": "^3.0.0"
}, },
@ -12096,15 +12078,9 @@
"integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
"requires": { "requires": {
"http-parser-js": ">=0.4.0 <0.4.11", "http-parser-js": ">=0.4.0 <0.4.11",
"safe-buffer": ">=5.1.0", "safe-buffer": ">=5.1.0"
"websocket-extensions": ">=0.1.1"
} }
}, },
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg=="
},
"which": { "which": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@ -14113,7 +14089,6 @@
"browserify-rsa": "^4.0.0", "browserify-rsa": "^4.0.0",
"create-hash": "^1.1.0", "create-hash": "^1.1.0",
"create-hmac": "^1.1.2", "create-hmac": "^1.1.2",
"elliptic": "^6.0.0",
"inherits": "^2.0.1", "inherits": "^2.0.1",
"parse-asn1": "^5.0.0" "parse-asn1": "^5.0.0"
} }
@ -14530,8 +14505,7 @@
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
"integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
"requires": { "requires": {
"bn.js": "^4.1.0", "bn.js": "^4.1.0"
"elliptic": "^6.0.0"
} }
}, },
"create-hash": { "create-hash": {
@ -14996,20 +14970,6 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.377.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.377.tgz",
"integrity": "sha512-cm2WzMKf/3dW5+hNANKm8GAW6SwIWOqLTJ6GPCD0Bbw1qJ9Wzm9nmx9M+byzSsgw8CdCv5fb/wzLFqVS5h6QrA==" "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": { "emoji-regex": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
@ -18344,9 +18304,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
}, },
"lodash.sortby": { "lodash.sortby": {
"version": "4.7.0", "version": "4.7.0",
@ -21348,9 +21308,9 @@
} }
}, },
"dom-align": { "dom-align": {
"version": "1.10.2", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.10.2.tgz", "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.0.tgz",
"integrity": "sha512-AYZUzLepy05E9bCY4ExoqHrrIlM49PEak9oF93JEFoibqKL0F7w5DLM70/rosLOawerWZ3MlepQcl+EmHskOyw==" "integrity": "sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA=="
}, },
"dom-closest": { "dom-closest": {
"version": "0.2.0", "version": "0.2.0",
@ -21526,9 +21486,9 @@
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
"version": "6.5.2", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true, "dev": true,
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.4.0",
@ -24417,6 +24377,11 @@
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
} }
}, },
"is-mobile": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-2.2.1.tgz",
"integrity": "sha512-6zELsfVFr326eq2CI53yvqq6YBanOxKBybwDT+MbMS2laBnK6Ez8m5XHSuTQQbnKRfpDzCod1CMWW5q3wZYMvA=="
},
"is-number": { "is-number": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
@ -24784,9 +24749,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
}, },
"lodash-es": { "lodash-es": {
"version": "4.17.15", "version": "4.17.15",
@ -24920,6 +24885,11 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -25318,9 +25288,9 @@
"dev": true "dev": true
}, },
"mutationobserver-shim": { "mutationobserver-shim": {
"version": "0.3.3", "version": "0.3.7",
"resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", "resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.7.tgz",
"integrity": "sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==" "integrity": "sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ=="
}, },
"mute-stream": { "mute-stream": {
"version": "0.0.8", "version": "0.0.8",
@ -26225,6 +26195,11 @@
"find-up": "^3.0.0" "find-up": "^3.0.0"
} }
}, },
"platform": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
},
"portfinder": { "portfinder": {
"version": "1.0.25", "version": "1.0.25",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
@ -27028,9 +27003,9 @@
} }
}, },
"rc-animate": { "rc-animate": {
"version": "2.10.2", "version": "2.11.1",
"resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.2.tgz", "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz",
"integrity": "sha512-cE/A7piAzoWFSgUD69NmmMraqCeqVBa51UErod8NS3LUEqWfppSVagHfa0qHAlwPVPiIBg3emRONyny3eiH0Dg==", "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"classnames": "^2.2.6", "classnames": "^2.2.6",
@ -27042,9 +27017,9 @@
} }
}, },
"rc-calendar": { "rc-calendar": {
"version": "9.15.8", "version": "9.15.11",
"resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.8.tgz", "resolved": "https://registry.npmjs.org/rc-calendar/-/rc-calendar-9.15.11.tgz",
"integrity": "sha512-x3zVaZSRX7FkRNKw7nz3tutwrlIrU1aqMn5GtRUmlf84GnXLtd9fuuydxeNkFWfcHry3BPSto7+r9TK2al0h+g==", "integrity": "sha512-qv0VXfAAnysMWJigxaP6se4bJHvr17D9qsLbi8BOpdgEocsS0RkgY1IUiFaOVYKJDy/EyLC447O02sV/y5YYBg==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"classnames": "2.x", "classnames": "2.x",
@ -27081,9 +27056,9 @@
} }
}, },
"rc-collapse": { "rc-collapse": {
"version": "1.11.7", "version": "1.11.8",
"resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-1.11.7.tgz", "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-1.11.8.tgz",
"integrity": "sha512-ge3EEzIFtrDGuPX4bxXdQqwb91JnPIdj3B+FU88yNOUeOroNuA2q9kVK+UatpQ1Eft5hNo/ICTDrVFi8+685ng==", "integrity": "sha512-8EhfPyScTYljkbRuIoHniSwZagD5UPpZ3CToYgoNYWC85L2qCbPYF7+OaC713FOrIkp6NbfNqXsITNxmDAmxog==",
"requires": { "requires": {
"classnames": "2.x", "classnames": "2.x",
"css-animation": "1.x", "css-animation": "1.x",
@ -27095,23 +27070,22 @@
} }
}, },
"rc-dialog": { "rc-dialog": {
"version": "7.5.13", "version": "7.6.1",
"resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.5.13.tgz", "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-7.6.1.tgz",
"integrity": "sha512-tmubIipW/qoCmRlHHV8tpepDaFhuhk+SeSFSyRhNKW4mYgflsEYQmYWilyCJHy6UzKl84bSyFvJskhc1z1Hniw==", "integrity": "sha512-KUKf+2eZ4YL+lnXMG3hR4ZtIhC9glfH27NtTVz3gcoDIPAf3uUvaXVRNoDCiSi+OGKLyIb/b6EoidFh6nQC5Wg==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"rc-animate": "2.x", "rc-animate": "2.x",
"rc-util": "^4.8.1" "rc-util": "^4.16.1"
} }
}, },
"rc-drawer": { "rc-drawer": {
"version": "3.0.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-3.0.2.tgz", "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-3.1.3.tgz",
"integrity": "sha512-oPScGXB/8/ov9gEFLxPH8RBv/9jLTZboZtyF/GgrrnCAvbFwUxXdELH6n6XIowmuDKKvTGIMgZdnao0T46Yv3A==", "integrity": "sha512-2z+RdxmzXyZde/1OhVMfDR1e/GBswFeWSZ7FS3Fdd0qhgVdpV1wSzILzzxRaT481ItB5hOV+e8pZT07vdJE8kg==",
"requires": { "requires": {
"babel-runtime": "^6.26.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"rc-util": "^4.11.2", "rc-util": "^4.16.1",
"react-lifecycles-compat": "^3.0.4" "react-lifecycles-compat": "^3.0.4"
} }
}, },
@ -27157,9 +27131,9 @@
} }
}, },
"rc-form": { "rc-form": {
"version": "2.4.10", "version": "2.4.11",
"resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.10.tgz", "resolved": "https://registry.npmjs.org/rc-form/-/rc-form-2.4.11.tgz",
"integrity": "sha512-h6a5Nvn6fMe3BfLpIWwL2RUkfXs1tvtifblTgGgH0UfzGgiQ5M12jiMJaAXek7TDDBUw90/c5vlZ6wFZjW0IgQ==", "integrity": "sha512-8BL+FNlFLTOY/A5X6tU35GQJLSIpsmqpwn/tFAYQTczXc4dMJ33ggtH248Cum8+LS0jLTsJKG2L4Qp+1CkY+sA==",
"requires": { "requires": {
"async-validator": "~1.11.3", "async-validator": "~1.11.3",
"babel-runtime": "6.x", "babel-runtime": "6.x",
@ -27172,9 +27146,9 @@
} }
}, },
"rc-hammerjs": { "rc-hammerjs": {
"version": "0.6.9", "version": "0.6.10",
"resolved": "https://registry.npmjs.org/rc-hammerjs/-/rc-hammerjs-0.6.9.tgz", "resolved": "https://registry.npmjs.org/rc-hammerjs/-/rc-hammerjs-0.6.10.tgz",
"integrity": "sha512-4llgWO3RgLyVbEqUdGsDfzUDqklRlQW5VEhE3x35IvhV+w//VPRG34SBavK3D2mD/UaLKaohgU41V4agiftC8g==", "integrity": "sha512-Vgh9qIudyN5CHRop4M+v+xUniQBFWXKrsJxQRVtJOi2xgRrCeI52/bkpaL5HWwUhqTK9Ayq0n7lYTItT6ld5rg==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -27182,9 +27156,9 @@
} }
}, },
"rc-input-number": { "rc-input-number": {
"version": "4.5.1", "version": "4.5.7",
"resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-4.5.1.tgz", "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-4.5.7.tgz",
"integrity": "sha512-grO7/Lau7iv3NyHVyCajE1LuGLqGkG1tEAAZSwm9M0esYfrwXVSip4qhb5sF+8g6ACsiI20sOVLIihXuhSoifA==", "integrity": "sha512-99PrQ90sTOKyyj7eu0VzwxY17xQ+bwG1XTQd+bTwFQ+IOUkIw7L4qSAYxt58sVYL+Cw+bu/RAtT2IpT9yC2pCQ==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"classnames": "^2.2.0", "classnames": "^2.2.0",
@ -27194,9 +27168,9 @@
} }
}, },
"rc-mentions": { "rc-mentions": {
"version": "0.4.1", "version": "0.4.2",
"resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-0.4.1.tgz", "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-0.4.2.tgz",
"integrity": "sha512-XSJp6kcEPydUaM0I/gnxpXggiKgA5FjgFPKZCMQBDQJYUjXpQNyg5ogNkOJt1/4B2P7pwbYPZXgxP/30yZVahA==", "integrity": "sha512-DTZurQzacLXOfVuiHydGzqkq7cFMHXF18l2jZ9PhWUn2cqvOSY3W4osN0Pq29AOMOBpcxdZCzgc7Lb0r/bgkDw==",
"requires": { "requires": {
"@ant-design/create-react-context": "^0.2.4", "@ant-design/create-react-context": "^0.2.4",
"classnames": "^2.2.6", "classnames": "^2.2.6",
@ -27207,9 +27181,9 @@
} }
}, },
"rc-menu": { "rc-menu": {
"version": "7.5.3", "version": "7.5.5",
"resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.3.tgz", "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-7.5.5.tgz",
"integrity": "sha512-H/jUyGbJxZI/iuVdC6Iu9KHfz7tucoqK0Vn8ahDnv+ppc1PnKb4SkBbXn5LrmUyaj7thCBiaktBxVnUXSmNE2g==", "integrity": "sha512-4YJXJgrpUGEA1rMftXN7bDhrV5rPB8oBJoHqT+GVXtIWCanfQxEnM3fmhHQhatL59JoAFMZhJaNzhJIk4FUWCQ==",
"requires": { "requires": {
"classnames": "2.x", "classnames": "2.x",
"dom-scroll-into-view": "1.x", "dom-scroll-into-view": "1.x",
@ -27235,9 +27209,9 @@
} }
}, },
"rc-pagination": { "rc-pagination": {
"version": "1.20.11", "version": "1.20.15",
"resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.11.tgz", "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-1.20.15.tgz",
"integrity": "sha512-2wKO5kO+ELx1/zlqTY8TwGBruzofi+1BcZ7Z4xalMlLbDMTuUU4FDljbBBP/n9D2llK+NtgWA619PMBhInozZw==", "integrity": "sha512-/Xr4/3GOa1DtL8iCYl7qRUroEMrRDhZiiuHwcVFfSiwa9LYloMlUWcOJsnr8LN6A7rLPdm3/CHStUNeYd+2pKw==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"classnames": "^2.2.6", "classnames": "^2.2.6",
@ -27246,18 +27220,18 @@
} }
}, },
"rc-progress": { "rc-progress": {
"version": "2.5.2", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.5.2.tgz", "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-2.5.3.tgz",
"integrity": "sha512-ajI+MJkbBz9zYDuE9GQsY5gsyqPF7HFioZEDZ9Fmc+ebNZoiSeSJsTJImPFCg0dW/5WiRGUy2F69SX1aPtSJgA==", "integrity": "sha512-K2fa4CnqGehLZoMrdmBeZ86ONSTVcdk5FlqetbwJ3R/+42XfqhwQVOjWp2MH4P7XSQOMAGcNOy1SFfCP3415sg==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"prop-types": "^15.5.8" "prop-types": "^15.5.8"
} }
}, },
"rc-rate": { "rc-rate": {
"version": "2.5.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.5.0.tgz", "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.5.1.tgz",
"integrity": "sha512-aXX5klRqbVZxvLghcKnLqqo7LvLVCHswEDteWsm5Gb7NBIPa1YKTcAbvb5SZ4Z4i4EeRoZaPwygRAWsQgGtbKw==", "integrity": "sha512-3iJkNJT8xlHklPCdeZtUZmJmRVUbr6AHRlfSsztfYTXVlHrv2TcPn3XkHsH+12j812WVB7gvilS2j3+ffjUHXg==",
"requires": { "requires": {
"classnames": "^2.2.5", "classnames": "^2.2.5",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
@ -27276,9 +27250,9 @@
} }
}, },
"rc-select": { "rc-select": {
"version": "9.2.1", "version": "9.2.3",
"resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.1.tgz", "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-9.2.3.tgz",
"integrity": "sha512-nW/Zr2OCgxN26OX8ff3xcO1wK0e1l5ixnEfyN15Rbdk7TNI/rIPJIjPCQAoihRpk9A2C/GH8pahjlvKV1Vj++g==", "integrity": "sha512-WhswxOMWiNnkXRbxyrj0kiIvyCfo/BaRPaYbsDetSIAU2yEDwKHF798blCP5u86KLOBKBvtxWLFCkSsQw1so5w==",
"requires": { "requires": {
"babel-runtime": "^6.23.0", "babel-runtime": "^6.23.0",
"classnames": "2.x", "classnames": "2.x",
@ -27331,9 +27305,9 @@
} }
}, },
"rc-table": { "rc-table": {
"version": "6.9.5", "version": "6.10.15",
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-6.9.5.tgz", "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-6.10.15.tgz",
"integrity": "sha512-STL6387A/izVh6r9F1WDiIIZ0QeubCdTgIlzMeGTSl/bXhB0VqjAZEikvoijPoauTjJIkIzVuQEIDjOhAWbpkQ==", "integrity": "sha512-LAr0M/gqt+irOjvPNBLApmQ0CUHNOfKsEBhu1uIuB3OlN1ynA9z+sdoTQyNd9+8NSl0MYnQOOfhtLChAY7nU0A==",
"requires": { "requires": {
"classnames": "^2.2.5", "classnames": "^2.2.5",
"component-classes": "^1.2.6", "component-classes": "^1.2.6",
@ -27346,9 +27320,9 @@
} }
}, },
"rc-tabs": { "rc-tabs": {
"version": "9.6.7", "version": "9.7.0",
"resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-9.6.7.tgz", "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-9.7.0.tgz",
"integrity": "sha512-OXbDOgaqv2MGK9QaDi6cdva6bNz3XGw+M9BHQpm1gTGmVQEGx5VcclDClH/3xobIzooxy8hrxg/s0rTlgDnC2w==", "integrity": "sha512-kvmgp8/MfLzFZ06hWHignqomFQ5nF7BqKr5O1FfhE4VKsGrep52YSF/1MvS5oe0NPcI9XGNS2p751C5v6cYDpQ==",
"requires": { "requires": {
"@ant-design/create-react-context": "^0.2.4", "@ant-design/create-react-context": "^0.2.4",
"babel-runtime": "6.x", "babel-runtime": "6.x",
@ -27364,9 +27338,9 @@
} }
}, },
"rc-time-picker": { "rc-time-picker": {
"version": "3.7.2", "version": "3.7.3",
"resolved": "https://registry.npmjs.org/rc-time-picker/-/rc-time-picker-3.7.2.tgz", "resolved": "https://registry.npmjs.org/rc-time-picker/-/rc-time-picker-3.7.3.tgz",
"integrity": "sha512-UVWO9HXGyZoM4I2THlJsEAFcZQz+tYwdcpoHXCEFZsRLz9L2+7vV4EMp9Wa3UrtzMFEt83qSAX/90dCJeKl9sg==", "integrity": "sha512-Lv1Mvzp9fRXhXEnRLO4nW6GLNxUkfAZ3RsiIBsWjGjXXvMNjdr4BX/ayElHAFK0DoJqOhm7c5tjmIYpEOwcUXg==",
"requires": { "requires": {
"classnames": "2.x", "classnames": "2.x",
"moment": "2.x", "moment": "2.x",
@ -27387,9 +27361,9 @@
} }
}, },
"rc-tree": { "rc-tree": {
"version": "2.1.3", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.3.tgz", "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.1.4.tgz",
"integrity": "sha512-COvV65spQ6omrHBUhHRKqKNL5+ddXjlS+qWZchaL9FFuQNvjM5pjp9RnmMWK4fJJ5kBhhpLneh6wh9Vh3kSMXQ==", "integrity": "sha512-Xey794Iavgs8YldFlXcZLOhfcIhlX5Oz/yfKufknBXf2AlZCOkc7aHqSM9uTF7fBPtTGPhPxNEfOqHfY7b7xng==",
"requires": { "requires": {
"@ant-design/create-react-context": "^0.2.4", "@ant-design/create-react-context": "^0.2.4",
"classnames": "2.x", "classnames": "2.x",
@ -27401,51 +27375,27 @@
} }
}, },
"rc-tree-select": { "rc-tree-select": {
"version": "2.9.1", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-2.9.1.tgz", "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-2.9.4.tgz",
"integrity": "sha512-AfJQC1ZzaeH+Onmx84TtVLUL2guBZe7exA8XSfj1RRB1doDbYGTtybzpP3CEw/tuSftSRnz+iPt+iaxRTrgXRw==", "integrity": "sha512-0HQkXAN4XbfBW20CZYh3G+V+VMrjX42XRtDCpyv6PDUm5vikC0Ob682ZBCVS97Ww2a5Hf6Ajmu0ahWEdIEpwhg==",
"requires": { "requires": {
"classnames": "^2.2.1", "classnames": "^2.2.1",
"dom-scroll-into-view": "^1.2.1", "dom-scroll-into-view": "^1.2.1",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
"raf": "^3.4.0", "raf": "^3.4.0",
"rc-animate": "^2.8.2", "rc-animate": "^2.8.2",
"rc-tree": "~2.0.0", "rc-tree": "~2.1.0",
"rc-trigger": "^3.0.0-rc.2", "rc-trigger": "^3.0.0",
"rc-util": "^4.5.0", "rc-util": "^4.5.0",
"react-lifecycles-compat": "^3.0.4", "react-lifecycles-compat": "^3.0.4",
"shallowequal": "^1.0.2", "shallowequal": "^1.0.2",
"warning": "^4.0.1" "warning": "^4.0.1"
}, },
"dependencies": { "dependencies": {
"rc-tree": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-2.0.0.tgz",
"integrity": "sha512-DAT/jsbnFbHqG9Df9OaVG93CAVtTsJVnJiwKX+wqsG8TChpty3s6QX3zJZ+gBgjkq4ikLbu1kuFJtX63EKhSAA==",
"requires": {
"babel-runtime": "^6.23.0",
"classnames": "2.x",
"prop-types": "^15.5.8",
"rc-animate": "^2.6.0",
"rc-util": "^4.5.1",
"react-lifecycles-compat": "^3.0.4",
"warning": "^3.0.0"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "^1.0.0"
}
}
}
},
"rc-trigger": { "rc-trigger": {
"version": "3.0.0-rc.3", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-3.0.0-rc.3.tgz", "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-3.0.0.tgz",
"integrity": "sha512-4vB6cpxcUdm2qO5VtB9q1TZz0MoWm9BzFLvGknulphGrl1qI6uxUsPDCvqnmujdpDdAKGGfjxntFpA7RtAwkFQ==", "integrity": "sha512-hQxbbJpo23E2QnYczfq3Ec5J5tVl2mUDhkqxrEsQAqk16HfADQg+iKNWzEYXyERSncdxfnzYuaBgy764mNRzTA==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"classnames": "^2.2.6", "classnames": "^2.2.6",
@ -27453,25 +27403,37 @@
"raf": "^3.4.0", "raf": "^3.4.0",
"rc-align": "^2.4.1", "rc-align": "^2.4.1",
"rc-animate": "^3.0.0-rc.1", "rc-animate": "^3.0.0-rc.1",
"rc-util": "^4.4.0" "rc-util": "^4.15.7"
}, },
"dependencies": { "dependencies": {
"rc-animate": { "rc-animate": {
"version": "3.0.0-rc.6", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-3.0.0-rc.6.tgz", "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-3.1.0.tgz",
"integrity": "sha512-oBLPpiT6Q4t6YvD/pkLcmofBP1p01TX0Otse8Q4+Mxt8J+VSDflLZGIgf62EwkvRwsQUkLPjZVFBsldnPKLzjg==", "integrity": "sha512-8FsM+3B1H+0AyTyGggY6JyVldHTs1CyYT8CfTmG/nGHHXlecvSLeICJhcKgRLjUiQlctNnRtB1rwz79cvBVmrw==",
"requires": { "requires": {
"babel-runtime": "6.x", "@ant-design/css-animation": "^1.7.2",
"classnames": "^2.2.5", "classnames": "^2.2.6",
"component-classes": "^1.2.6",
"fbjs": "^0.8.16",
"prop-types": "15.x",
"raf": "^3.4.0", "raf": "^3.4.0",
"rc-util": "^4.5.0", "rc-util": "^5.0.1"
"react-lifecycles-compat": "^3.0.4" },
"dependencies": {
"rc-util": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.0.4.tgz",
"integrity": "sha512-cd19RCrE0DJH6UcJ9+V3eaXA/5sNWyVKOKkWl8ZM2OqgNzVb8fv0obf/TkuvSN43tmTsgqY8k7OqpFYHhmef8g==",
"requires": {
"react-is": "^16.12.0",
"shallowequal": "^1.1.0"
}
}
} }
} }
} }
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
} }
} }
}, },
@ -27490,9 +27452,9 @@
} }
}, },
"rc-upload": { "rc-upload": {
"version": "2.9.2", "version": "2.9.4",
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.9.2.tgz", "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-2.9.4.tgz",
"integrity": "sha512-USjuWpTRJl3my32G5woysTaGrAld+S4dvvZ9kW6RX/RkekfmLDjvWc5ho8Mj/+6B6/tDRJnyGyvMxMQNkW7cvw==", "integrity": "sha512-WXt0HGxXyzLrPV6iec/96Rbl/6dyrAW8pKuY6wwD7yFYwfU5bjgKjv7vC8KNMJ6wzitFrZjnoiogNL3dF9dj3Q==",
"requires": { "requires": {
"babel-runtime": "6.x", "babel-runtime": "6.x",
"classnames": "^2.2.5", "classnames": "^2.2.5",
@ -27501,15 +27463,22 @@
} }
}, },
"rc-util": { "rc-util": {
"version": "4.15.6", "version": "4.21.1",
"resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.15.6.tgz", "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz",
"integrity": "sha512-W6HB1gIn+xZLxmQfLkhMnAtaZY9RktcOH2I0Tbam4D4ZDFrkO33f3M7IolN0EPtLMpf4Mv/dEQNclY77/PtBpg==", "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==",
"requires": { "requires": {
"add-dom-event-listener": "^1.1.0", "add-dom-event-listener": "^1.1.0",
"babel-runtime": "6.x",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
"react-is": "^16.12.0",
"react-lifecycles-compat": "^3.0.4", "react-lifecycles-compat": "^3.0.4",
"shallowequal": "^1.1.0" "shallowequal": "^1.1.0"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
} }
}, },
"react": { "react": {
@ -27522,6 +27491,19 @@
"prop-types": "^15.6.2" "prop-types": "^15.6.2"
} }
}, },
"react-color": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.1.tgz",
"integrity": "sha512-X5XpyJS6ncplZs74ak0JJoqPi+33Nzpv5RYWWxn17bslih+X7OlgmfpmGC1fNvdkK7/SGWYf1JJdn7D2n5gSuQ==",
"requires": {
"@icons/material": "^0.2.4",
"lodash": "^4.17.11",
"material-colors": "^1.2.1",
"prop-types": "^15.5.10",
"reactcss": "^1.2.0",
"tinycolor2": "^1.4.1"
}
},
"react-dom": { "react-dom": {
"version": "16.11.0", "version": "16.11.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz",
@ -27654,6 +27636,14 @@
"react-svg-core": "^3.0.3" "react-svg-core": "^3.0.3"
} }
}, },
"reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
"integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
"requires": {
"lodash": "^4.0.1"
}
},
"read-pkg": { "read-pkg": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
@ -28648,9 +28638,9 @@
} }
}, },
"shallow-equal": { "shallow-equal": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.0.tgz", "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
"integrity": "sha512-Z21pVxR4cXsfwpMKMhCEIO1PCi5sp7KEp+CmOpBQ+E8GpHwKOw2sEzk7sgblM3d/j4z4gakoWEoPcjK0VJQogA==" "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
}, },
"shallowequal": { "shallowequal": {
"version": "1.1.0", "version": "1.1.0",
@ -30490,9 +30480,9 @@
} }
}, },
"websocket-extensions": { "websocket-extensions": {
"version": "0.1.3", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true "dev": true
}, },
"whatwg-fetch": { "whatwg-fetch": {

@ -1,6 +1,6 @@
{ {
"name": "cvat-ui", "name": "cvat-ui",
"version": "1.2.0", "version": "1.8.4",
"description": "CVAT single-page application", "description": "CVAT single-page application",
"main": "src/index.tsx", "main": "src/index.tsx",
"scripts": { "scripts": {
@ -47,22 +47,26 @@
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"cvat-core": "file:../cvat-core", "@types/platform": "^1.3.2",
"cvat-canvas": "file:../cvat-canvas",
"@types/react": "^16.9.2", "@types/react": "^16.9.2",
"@types/react-color": "^3.0.2",
"@types/react-dom": "^16.9.0", "@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.2", "@types/react-redux": "^7.1.2",
"@types/react-router": "^5.0.5", "@types/react-router": "^5.0.5",
"@types/react-router-dom": "^5.1.0", "@types/react-router-dom": "^5.1.0",
"@types/react-share": "^3.0.1", "@types/react-share": "^3.0.1",
"@types/redux-logger": "^3.0.7", "@types/redux-logger": "^3.0.7",
"antd": "^3.25.2", "antd": "^3.26.17",
"copy-to-clipboard": "^3.2.0", "copy-to-clipboard": "^3.2.0",
"cvat-canvas": "file:../cvat-canvas",
"cvat-core": "file:../cvat-core",
"dotenv-webpack": "^1.7.0", "dotenv-webpack": "^1.7.0",
"error-stack-parser": "^2.0.6", "error-stack-parser": "^2.0.6",
"moment": "^2.24.0", "moment": "^2.24.0",
"platform": "^1.3.6",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.9.0", "react": "^16.9.0",
"react-color": "^2.18.1",
"react-dom": "^16.9.0", "react-dom": "^16.9.0",
"react-hotkeys": "^2.0.0", "react-hotkeys": "^2.0.0",
"react-redux": "^7.1.1", "react-redux": "^7.1.1",

@ -8,7 +8,7 @@ import {
ActionCreator, ActionCreator,
Store, Store,
} from 'redux'; } from 'redux';
import { ThunkAction } from 'redux-thunk'; import { ThunkAction } from 'utils/redux';
import { import {
CombinedState, CombinedState,
@ -148,8 +148,6 @@ export enum AnnotationActionTypes {
GROUP_ANNOTATIONS_FAILED = 'GROUP_ANNOTATIONS_FAILED', GROUP_ANNOTATIONS_FAILED = 'GROUP_ANNOTATIONS_FAILED',
SPLIT_ANNOTATIONS_SUCCESS = 'SPLIT_ANNOTATIONS_SUCCESS', SPLIT_ANNOTATIONS_SUCCESS = 'SPLIT_ANNOTATIONS_SUCCESS',
SPLIT_ANNOTATIONS_FAILED = 'SPLIT_ANNOTATIONS_FAILED', SPLIT_ANNOTATIONS_FAILED = 'SPLIT_ANNOTATIONS_FAILED',
CHANGE_LABEL_COLOR_SUCCESS = 'CHANGE_LABEL_COLOR_SUCCESS',
CHANGE_LABEL_COLOR_FAILED = 'CHANGE_LABEL_COLOR_FAILED',
UPDATE_TAB_CONTENT_HEIGHT = 'UPDATE_TAB_CONTENT_HEIGHT', UPDATE_TAB_CONTENT_HEIGHT = 'UPDATE_TAB_CONTENT_HEIGHT',
COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR', COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR',
COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE', COLLAPSE_APPEARANCE = 'COLLAPSE_APPEARANCE',
@ -191,8 +189,7 @@ export enum AnnotationActionTypes {
SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED', SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED',
} }
export function saveLogsAsync(): export function saveLogsAsync(): ThunkAction {
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>) => { return async (dispatch: ActionCreator<Dispatch>) => {
try { try {
await logger.save(); await logger.save();
@ -236,7 +233,7 @@ export function switchZLayer(cur: number): AnyAction {
}; };
} }
export function fetchAnnotationsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> { export function fetchAnnotationsAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { const {
@ -308,8 +305,7 @@ export function updateCanvasContextMenu(
}; };
} }
export function removeAnnotationsAsync(sessionInstance: any): export function removeAnnotationsAsync(sessionInstance: any): ThunkAction {
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
await sessionInstance.annotations.clear(); await sessionInstance.annotations.clear();
@ -333,8 +329,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}; };
} }
export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): ThunkAction {
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
@ -404,8 +399,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}; };
} }
export function changeJobStatusAsync(jobInstance: any, status: string): export function changeJobStatusAsync(jobInstance: any, status: string): ThunkAction {
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const oldStatus = jobInstance.status; const oldStatus = jobInstance.status;
try { try {
@ -435,8 +429,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}; };
} }
export function collectStatisticsAsync(sessionInstance: any): export function collectStatisticsAsync(sessionInstance: any): ThunkAction {
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
dispatch({ dispatch({
@ -477,7 +470,7 @@ export function propagateObjectAsync(
objectState: any, objectState: any,
from: number, from: number,
to: number, to: number,
): ThunkAction<Promise<void>, {}, {}, AnyAction> { ): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const copy = { const copy = {
@ -490,6 +483,7 @@ export function propagateObjectAsync(
label: objectState.label, label: objectState.label,
zOrder: objectState.zOrder, zOrder: objectState.zOrder,
frame: from, frame: from,
source: objectState.source,
}; };
await sessionInstance.logger.log( await sessionInstance.logger.log(
@ -542,7 +536,7 @@ export function changePropagateFrames(frames: number): AnyAction {
} }
export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
await sessionInstance.logger.log(LogType.deleteObject, { count: 1 }); await sessionInstance.logger.log(LogType.deleteObject, { count: 1 });
@ -659,7 +653,7 @@ export function switchPlay(playing: boolean): AnyAction {
} }
export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number): export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const { instance: job } = state.annotation.job; const { instance: job } = state.annotation.job;
@ -751,7 +745,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
} }
export function undoActionAsync(sessionInstance: any, frame: number): export function undoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state = getStore().getState(); const state = getStore().getState();
@ -794,7 +788,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
} }
export function redoActionAsync(sessionInstance: any, frame: number): export function redoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state = getStore().getState(); const state = getStore().getState();
@ -906,25 +900,31 @@ export function confirmCanvasReady(): AnyAction {
}; };
} }
export function closeJob(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { jobInstance } = receiveAnnotationsParameters();
if (jobInstance) {
await jobInstance.task.close();
}
dispatch({
type: AnnotationActionTypes.CLOSE_JOB,
});
};
}
export function getJobAsync( export function getJobAsync(
tid: number, tid: number,
jid: number, jid: number,
initialFrame: number, initialFrame: number,
initialFilters: string[], initialFilters: string[],
): ThunkAction<Promise<void>, {}, {}, AnyAction> { ): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const filters = initialFilters; const filters = initialFilters;
const { showAllInterpolationTracks } = state.settings.workspace; const { showAllInterpolationTracks } = state.settings.workspace;
// Check if already loaded job is different from asking one
if (state.annotation.job.instance && state.annotation.job.instance.id !== jid) {
dispatch({
type: AnnotationActionTypes.CLOSE_JOB,
});
}
dispatch({ dispatch({
type: AnnotationActionTypes.GET_JOB, type: AnnotationActionTypes.GET_JOB,
payload: { payload: {
@ -995,7 +995,7 @@ export function getJobAsync(
} }
export function saveAnnotationsAsync(sessionInstance: any): export function saveAnnotationsAsync(sessionInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1112,8 +1112,7 @@ export function splitTrack(enabled: boolean): AnyAction {
}; };
} }
export function updateAnnotationsAsync(statesToUpdate: any[]): export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction {
ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { const {
jobInstance, jobInstance,
@ -1158,7 +1157,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
} }
export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]): export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1186,7 +1185,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
} }
export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]): export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1224,7 +1223,7 @@ export function groupAnnotationsAsync(
sessionInstance: any, sessionInstance: any,
frame: number, frame: number,
statesToGroup: any[], statesToGroup: any[],
): ThunkAction<Promise<void>, {}, {}, AnyAction> { ): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1260,7 +1259,7 @@ export function groupAnnotationsAsync(
} }
export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any): export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> { ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
try { try {
@ -1287,44 +1286,10 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}; };
} }
export function changeLabelColorAsync(
sessionInstance: any,
frameNumber: number,
label: any,
color: string,
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
const updatedLabel = label;
updatedLabel.color = color;
const states = await sessionInstance.annotations
.get(frameNumber, showAllInterpolationTracks, filters);
const history = await sessionInstance.actions.get();
dispatch({
type: AnnotationActionTypes.CHANGE_LABEL_COLOR_SUCCESS,
payload: {
label: updatedLabel,
history,
states,
},
});
} catch (error) {
dispatch({
type: AnnotationActionTypes.CHANGE_LABEL_COLOR_FAILED,
payload: {
error,
},
});
}
};
}
export function changeGroupColorAsync( export function changeGroupColorAsync(
group: number, group: number,
color: string, color: string,
): ThunkAction<Promise<void>, {}, {}, AnyAction> { ): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState(); const state: CombinedState = getStore().getState();
const groupStates = state.annotation.annotations.states const groupStates = state.annotation.annotations.states
@ -1342,7 +1307,7 @@ export function searchAnnotationsAsync(
sessionInstance: any, sessionInstance: any,
frameFrom: number, frameFrom: number,
frameTo: number, frameTo: number,
): ThunkAction<Promise<void>, {}, {}, AnyAction> { ): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try { try {
const { filters } = receiveAnnotationsParameters(); const { filters } = receiveAnnotationsParameters();
@ -1361,7 +1326,7 @@ export function searchAnnotationsAsync(
}; };
} }
export function pasteShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> { export function pasteShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { const {
canvas: { canvas: {
@ -1420,7 +1385,7 @@ export function pasteShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction>
}; };
} }
export function repeatDrawShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> { export function repeatDrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => { return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { const {
canvas: { canvas: {
@ -1483,3 +1448,51 @@ export function repeatDrawShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAc
} }
}; };
} }
export function redrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
annotations: {
activatedStateID,
states,
},
canvas: {
instance: canvasInstance,
},
} = getStore().getState().annotation;
if (activatedStateID !== null) {
const [state] = states
.filter((_state: any): boolean => _state.clientID === activatedStateID);
if (state && state.objectType !== ObjectType.TAG) {
let activeControl = ActiveControl.CURSOR;
if (state.shapeType === ShapeType.RECTANGLE) {
activeControl = ActiveControl.DRAW_RECTANGLE;
} else if (state.shapeType === ShapeType.POINTS) {
activeControl = ActiveControl.DRAW_POINTS;
} else if (state.shapeType === ShapeType.POLYGON) {
activeControl = ActiveControl.DRAW_POLYGON;
} else if (state.shapeType === ShapeType.POLYLINE) {
activeControl = ActiveControl.DRAW_POLYLINE;
} else if (state.shapeType === ShapeType.CUBOID) {
activeControl = ActiveControl.DRAW_CUBOID;
}
dispatch({
type: AnnotationActionTypes.REPEAT_DRAW_SHAPE,
payload: {
activeControl,
},
});
canvasInstance.cancel();
canvasInstance.draw({
enabled: true,
redraw: activatedStateID,
shapeType: state.shapeType,
crosshair: state.shapeType === ShapeType.RECTANGLE,
});
}
}
};
}

@ -5,6 +5,7 @@
import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { UserConfirmation } from 'components/register-page/register-form'; import { UserConfirmation } from 'components/register-page/register-form';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
import isReachable from 'utils/url-checker';
const cvat = getCore(); const cvat = getCore();
@ -20,9 +21,16 @@ export enum AuthActionTypes {
LOGOUT = 'LOGOUT', LOGOUT = 'LOGOUT',
LOGOUT_SUCCESS = 'LOGOUT_SUCCESS', LOGOUT_SUCCESS = 'LOGOUT_SUCCESS',
LOGOUT_FAILED = 'LOGOUT_FAILED', 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 }), authorizeSuccess: (user: any) => createAction(AuthActionTypes.AUTHORIZED_SUCCESS, { user }),
authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }), authorizeFailed: (error: any) => createAction(AuthActionTypes.AUTHORIZED_FAILED, { error }),
login: () => createAction(AuthActionTypes.LOGIN), login: () => createAction(AuthActionTypes.LOGIN),
@ -34,6 +42,21 @@ const authActions = {
logout: () => createAction(AuthActionTypes.LOGOUT), logout: () => createAction(AuthActionTypes.LOGOUT),
logoutSuccess: () => createAction(AuthActionTypes.LOGOUT_SUCCESS), logoutSuccess: () => createAction(AuthActionTypes.LOGOUT_SUCCESS),
logoutFailed: (error: any) => createAction(AuthActionTypes.LOGOUT_FAILED, { error }), 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>; export type AuthActions = ActionUnion<typeof authActions>;
@ -52,10 +75,10 @@ export const registerAsync = (
dispatch(authActions.register()); dispatch(authActions.register());
try { try {
await cvat.server.register(username, firstName, lastName, email, password1, password2, confirmations); const user = await cvat.server.register(username, firstName, lastName, email, password1, password2,
const users = await cvat.users.get({ self: true }); confirmations);
dispatch(authActions.registerSuccess(users[0])); dispatch(authActions.registerSuccess(user));
} catch (error) { } catch (error) {
dispatch(authActions.registerFailed(error)); dispatch(authActions.registerFailed(error));
} }
@ -99,3 +122,30 @@ export const authorizedAsync = (): ThunkAction => async (dispatch) => {
dispatch(authActions.authorizeFailed(error)); 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 // SPDX-License-Identifier: MIT
import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import { import { Model, ActiveInference, RQStatus } from 'reducers/interfaces';
Model,
ModelType,
ModelFiles,
ActiveInference,
CombinedState,
} from 'reducers/interfaces';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
export enum PreinstalledModels {
RCNN = 'RCNN Object Detector',
MaskRCNN = 'Mask RCNN Object Detector',
}
export enum ModelsActionTypes { export enum ModelsActionTypes {
GET_MODELS = 'GET_MODELS', GET_MODELS = 'GET_MODELS',
GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS', GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS',
GET_MODELS_FAILED = 'GET_MODELS_FAILED', GET_MODELS_FAILED = 'GET_MODELS_FAILED',
DELETE_MODEL = 'DELETE_MODEL', DELETE_MODEL = 'DELETE_MODEL',
DELETE_MODEL_SUCCESS = 'DELETE_MODEL_SUCCESS',
DELETE_MODEL_FAILED = 'DELETE_MODEL_FAILED',
CREATE_MODEL = 'CREATE_MODEL', CREATE_MODEL = 'CREATE_MODEL',
CREATE_MODEL_SUCCESS = 'CREATE_MODEL_SUCCESS', CREATE_MODEL_SUCCESS = 'CREATE_MODEL_SUCCESS',
CREATE_MODEL_FAILED = 'CREATE_MODEL_FAILED', CREATE_MODEL_FAILED = 'CREATE_MODEL_FAILED',
@ -50,28 +37,6 @@ export const modelsActions = {
error, 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 }), fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }),
getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => createAction( getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => createAction(
ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, { ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, {
@ -96,7 +61,7 @@ export const modelsActions = {
taskID, taskID,
}, },
), ),
cancelInferenceFaild: (taskID: number, error: any) => createAction( cancelInferenceFailed: (taskID: number, error: any) => createAction(
ModelsActionTypes.CANCEL_INFERENCE_FAILED, { ModelsActionTypes.CANCEL_INFERENCE_FAILED, {
taskID, taskID,
error, error,
@ -113,361 +78,76 @@ export const modelsActions = {
export type ModelsActions = ActionUnion<typeof modelsActions>; export type ModelsActions = ActionUnion<typeof modelsActions>;
const core = getCore(); const core = getCore();
const baseURL = core.config.backendAPI.slice(0, -7);
export function getModelsAsync(): ThunkAction { export function getModelsAsync(): ThunkAction {
return async (dispatch, getState): Promise<void> => { return async (dispatch): 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;
dispatch(modelsActions.getModels()); dispatch(modelsActions.getModels());
const models: Model[] = [];
try { try {
if (OpenVINO) { const models = (await core.lambda.list())
const response = await core.server.request( .filter((model: Model) => ['detector', 'reid'].includes(model.type));
`${baseURL}/auto_annotation/meta/get`, { dispatch(modelsActions.getModelsSuccess(models));
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',
],
});
}
} catch (error) { } catch (error) {
dispatch(modelsActions.getModelsFailed(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 { interface InferenceMeta {
active: boolean;
taskID: number; taskID: number;
requestID: string; requestID: string;
modelType: ModelType;
} }
const timers: any = {}; function listen(
inferenceMeta: InferenceMeta,
async function timeoutCallback(
url: string,
taskID: number,
modelType: ModelType,
dispatch: (action: ModelsActions) => void, dispatch: (action: ModelsActions) => void,
): Promise<void> { ): void {
try { const { taskID, requestID } = inferenceMeta;
delete timers[taskID]; core.lambda.listen(requestID, (status: RQStatus, progress: number, message: string) => {
if (status === RQStatus.failed || status === RQStatus.unknown) {
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') {
dispatch(modelsActions.getInferenceStatusFailed( dispatch(modelsActions.getInferenceStatusFailed(
taskID, taskID,
new Error( new Error(
`Inference status for the task ${taskID} is failed. ${activeInference.error}`, `Inference status for the task ${taskID} is ${status}. ${message}`,
), ),
)); ));
return; return;
} }
if (activeInference.status !== 'finished') { dispatch(modelsActions.getInferenceStatusSuccess(taskID, {
timers[taskID] = setTimeout( status,
timeoutCallback.bind( progress,
null, error: message,
url, id: requestID,
taskID, }));
modelType, }).catch((error: Error) => {
dispatch, dispatch(modelsActions.getInferenceStatusFailed(taskID, {
), 3000, status: 'unknown',
); progress: 0,
} error: error.toString(),
id: requestID,
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,
),
);
}
} }
export function getInferenceStatusAsync(tasks: number[]): ThunkAction { export function getInferenceStatusAsync(): ThunkAction {
return async (dispatch, getState): Promise<void> => { return async (dispatch): 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;
const dispatchCallback = (action: ModelsActions): void => { const dispatchCallback = (action: ModelsActions): void => {
dispatch(action); dispatch(action);
}; };
try { try {
if (OpenVINO) { const requests = await core.lambda.requests();
const response = await core.server.request( requests
`${baseURL}/auto_annotation/meta/get`, { .map((request: any): object => ({
method: 'POST', taskID: +request.function.task,
data: JSON.stringify(tasks), requestID: request.id,
headers: { }))
'Content-Type': 'application/json', .forEach((inferenceMeta: InferenceMeta): void => {
}, listen(inferenceMeta, dispatchCallback);
}, });
);
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);
});
}
} catch (error) { } catch (error) {
dispatch(modelsActions.fetchMetaFailed(error)); dispatch(modelsActions.fetchMetaFailed(error));
} }
@ -477,37 +157,20 @@ export function getInferenceStatusAsync(tasks: number[]): ThunkAction {
export function startInferenceAsync( export function startInferenceAsync(
taskInstance: any, taskInstance: any,
model: Model, model: Model,
mapping: { body: object,
[index: string]: string;
},
cleanOut: boolean,
): ThunkAction { ): ThunkAction {
return async (dispatch): Promise<void> => { return async (dispatch): Promise<void> => {
try { try {
if (model.name === PreinstalledModels.RCNN) { const requestID: string = await core.lambda.run(taskInstance, model, body);
await core.server.request(
`${baseURL}/tensorflow/annotation/create/task/${taskInstance.id}`, const dispatchCallback = (action: ModelsActions): void => {
); dispatch(action);
} 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',
},
},
);
}
dispatch(getInferenceStatusAsync([taskInstance.id])); listen({
taskID: taskInstance.id,
requestID,
}, dispatchCallback);
} catch (error) { } catch (error) {
dispatch(modelsActions.startInferenceFailed(taskInstance.id, error)); dispatch(modelsActions.startInferenceFailed(taskInstance.id, error));
} }
@ -518,30 +181,10 @@ export function cancelInferenceAsync(taskID: number): ThunkAction {
return async (dispatch, getState): Promise<void> => { return async (dispatch, getState): Promise<void> => {
try { try {
const inference = getState().models.inferences[taskID]; const inference = getState().models.inferences[taskID];
if (inference) { await core.lambda.cancel(inference.id);
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];
}
}
dispatch(modelsActions.cancelInferenceSuccess(taskID)); dispatch(modelsActions.cancelInferenceSuccess(taskID));
} catch (error) { } 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 { export enum PluginsActionTypes {
CHECK_PLUGINS = 'CHECK_PLUGINS', CHECK_PLUGINS = 'CHECK_PLUGINS',
CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS' CHECKED_ALL_PLUGINS = 'CHECKED_ALL_PLUGINS',
} }
type PluginObjects = Record<SupportedPlugins, boolean>; type PluginObjects = Record<SupportedPlugins, boolean>;
@ -29,27 +29,20 @@ export function checkPluginsAsync(): ThunkAction {
dispatch(pluginActions.checkPlugins()); dispatch(pluginActions.checkPlugins());
const plugins: PluginObjects = { const plugins: PluginObjects = {
ANALYTICS: false, ANALYTICS: false,
AUTO_ANNOTATION: false,
GIT_INTEGRATION: false, GIT_INTEGRATION: false,
TF_ANNOTATION: false,
TF_SEGMENTATION: false,
REID: false,
DEXTR_SEGMENTATION: false, DEXTR_SEGMENTATION: false,
}; };
const promises: Promise<boolean>[] = [ const promises: Promise<boolean>[] = [
// check must return true/false with no exceptions
PluginChecker.check(SupportedPlugins.ANALYTICS), PluginChecker.check(SupportedPlugins.ANALYTICS),
PluginChecker.check(SupportedPlugins.AUTO_ANNOTATION),
PluginChecker.check(SupportedPlugins.GIT_INTEGRATION), PluginChecker.check(SupportedPlugins.GIT_INTEGRATION),
PluginChecker.check(SupportedPlugins.TF_ANNOTATION),
PluginChecker.check(SupportedPlugins.TF_SEGMENTATION),
PluginChecker.check(SupportedPlugins.DEXTR_SEGMENTATION), PluginChecker.check(SupportedPlugins.DEXTR_SEGMENTATION),
PluginChecker.check(SupportedPlugins.REID),
]; ];
const values = await Promise.all(promises); const values = await Promise.all(promises);
[plugins.ANALYTICS, plugins.AUTO_ANNOTATION, plugins.GIT_INTEGRATION, plugins.TF_ANNOTATION, [plugins.ANALYTICS, plugins.GIT_INTEGRATION,
plugins.TF_SEGMENTATION, plugins.DEXTR_SEGMENTATION, plugins.REID] = values; plugins.DEXTR_SEGMENTATION] = values;
dispatch(pluginActions.checkedAllPlugins(plugins)); dispatch(pluginActions.checkedAllPlugins(plugins));
}; };
} }

@ -32,6 +32,8 @@ export enum SettingsActionTypes {
SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING', SWITCH_AUTOMATIC_BORDERING = 'SWITCH_AUTOMATIC_BORDERING',
SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS',
SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS = 'SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS', SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS = 'SWITCH_SHOWING_OBJECTS_TEXT_ALWAYS',
CHANGE_CANVAS_BACKGROUND_COLOR = 'CHANGE_CANVAS_BACKGROUND_COLOR',
SWITCH_SETTINGS_DIALOG = 'SWITCH_SETTINGS_DIALOG',
} }
export function changeShapesOpacity(opacity: number): AnyAction { export function changeShapesOpacity(opacity: number): AnyAction {
@ -240,3 +242,21 @@ export function switchAutomaticBordering(automaticBordering: boolean): AnyAction
}, },
}; };
} }
export function changeCanvasBackgroundColor(color: string): AnyAction {
return {
type: SettingsActionTypes.CHANGE_CANVAS_BACKGROUND_COLOR,
payload: {
color,
},
};
}
export function switchSettingsDialog(show?: boolean): AnyAction {
return {
type: SettingsActionTypes.SWITCH_SETTINGS_DIALOG,
payload: {
show,
},
};
}

@ -102,13 +102,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
const promises = array const promises = array
.map((task): string => (task as any).frames.preview()); .map((task): string => (task as any).frames.preview());
dispatch( dispatch(getInferenceStatusAsync());
getInferenceStatusAsync(
array.map(
(task: any): number => task.id,
),
),
);
for (const promise of promises) { for (const promise of promises) {
try { try {
@ -350,10 +344,12 @@ function createTask(): AnyAction {
return action; return action;
} }
function createTaskSuccess(): AnyAction { function createTaskSuccess(taskId: number): AnyAction {
const action = { const action = {
type: TasksActionTypes.CREATE_TASK_SUCCESS, type: TasksActionTypes.CREATE_TASK_SUCCESS,
payload: {}, payload: {
taskId,
},
}; };
return action; return action;
@ -439,10 +435,10 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
dispatch(createTask()); dispatch(createTask());
try { try {
await taskInstance.save((status: string): void => { const savedTask = await taskInstance.save((status: string): void => {
dispatch(createTaskUpdateStatus(status)); dispatch(createTaskUpdateStatus(status));
}); });
dispatch(createTaskSuccess()); dispatch(createTaskSuccess(savedTask.id));
} catch (error) { } catch (error) {
dispatch(createTaskFailed(error)); dispatch(createTaskFailed(error));
} }

@ -4,7 +4,7 @@
import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
import getCore from 'cvat-core-wrapper'; import getCore from 'cvat-core-wrapper';
import { UserAgreement } from 'reducers/interfaces' import { UserAgreement } from 'reducers/interfaces';
const core = getCore(); const core = getCore();
@ -16,10 +16,12 @@ export enum UserAgreementsActionTypes {
const userAgreementsActions = { const userAgreementsActions = {
getUserAgreements: () => createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS), getUserAgreements: () => createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS),
getUserAgreementsSuccess: (userAgreements: UserAgreement[]) => getUserAgreementsSuccess: (userAgreements: UserAgreement[]) => (
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements), createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_SUCCESS, userAgreements)
getUserAgreementsFailed: (error: any) => ),
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error }), getUserAgreementsFailed: (error: any) => (
createAction(UserAgreementsActionTypes.GET_USER_AGREEMENTS_FAILED, { error })
),
}; };
export type UserAgreementsActions = ActionUnion<typeof userAgreementsActions>; export type UserAgreementsActions = ActionUnion<typeof userAgreementsActions>;

@ -0,0 +1,3 @@
<svg width="1em" height="1em" viewBox="0 0 14 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M13.7735 2.04722L11.9536 0.227468C11.6503 -0.0758228 11.1603 -0.0758228 10.857 0.227468L8.43052 2.6538L6.92951 1.16845L5.83292 2.26496L6.93729 3.36925L0 10.3061V14H3.69419L10.6315 7.06318L11.7358 8.16748L12.8324 7.07096L11.3392 5.57784L13.7657 3.15151C14.0768 2.84044 14.0768 2.35051 13.7735 2.04722V2.04722ZM3.04867 12.4447L1.55545 10.9515L7.8239 4.68352L9.31712 6.17664L3.04867 12.4447Z"/>
</svg>

After

Width:  |  Height:  |  Size: 514 B

@ -0,0 +1,4 @@
<svg width="1em" height="1em" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M213 32L240 64H220V192H240L212.5 224L185 192H205V64H185L213 32Z" fill="black"/>
<path d="M32 64L33.1578 56.5899L24.5 55.2371V64H32ZM32 192H24.5V200.628L33.0444 199.427L32 192ZM160 174L161.044 181.427L167.5 180.519V174H160ZM160 84H167.5V77.5809L161.158 76.5899L160 84ZM24.5 64V192H39.5V64H24.5ZM33.0444 199.427L161.044 181.427L158.956 166.573L30.9556 184.573L33.0444 199.427ZM161.158 76.5899L33.1578 56.5899L30.8422 71.4101L158.842 91.4101L161.158 76.5899ZM167.5 174V84H152.5V174H167.5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 658 B

@ -2,26 +2,27 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
$header-color: #D8D8D8; $header-color: #d8d8d8;
$text-color: #303030; $text-color: #303030;
$hover-menu-color: rgba(24,144,255,0.05); $hover-menu-color: rgba(24, 144, 255, 0.05);
$completed-progress-color: #61C200; $completed-progress-color: #61c200;
$inprogress-progress-color: #1890FF; $inprogress-progress-color: #1890ff;
$pending-progress-color: #C1C1C1; $pending-progress-color: #c1c1c1;
$border-color-1: #c3c3c3; $border-color-1: #c3c3c3;
$border-color-2: #d9d9d9; $border-color-2: #d9d9d9;
$border-color-3: #242424; $border-color-3: #242424;
$border-color-hover: #40a9ff; $border-color-hover: #40a9ff;
$background-color-1: white; $background-color-1: white;
$background-color-2: #F1F1F1; $background-color-2: #f1f1f1;
$transparent-color: rgba(0, 0, 0, 0); $transparent-color: rgba(0, 0, 0, 0);
$player-slider-color: #979797; $player-slider-color: #979797;
$player-buttons-color: #242424; $player-buttons-color: #242424;
$danger-icon-color: #FF4136; $danger-icon-color: #ff4136;
$info-icon-color: #0074D9; $info-icon-color: #0074d9;
$objects-bar-tabs-color: #BEBEBE; $objects-bar-tabs-color: #bebebe;
$objects-bar-icons-color: #242424; // #6E6E6E $objects-bar-icons-color: #242424; // #6e6e6e
$active-object-item-background-color: #D8ECFF; $active-label-background-color: #d8ecff;
$slider-color: #1890FF; $object-item-border-color: #000;
$slider-color: #1890ff;
$monospaced-fonts-stack: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; $monospaced-fonts-stack: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;

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

@ -15,7 +15,7 @@ function isDefaultFormat(dumperName: string, taskMode: string): boolean {
interface Props { interface Props {
taskMode: string; taskMode: string;
menuKey: string; menuKey: string;
dumpers: string[]; dumpers: any[];
dumpActivities: string[] | null; dumpActivities: string[] | null;
} }
@ -30,21 +30,24 @@ export default function DumpSubmenu(props: Props): JSX.Element {
return ( return (
<Menu.SubMenu key={menuKey} title='Dump annotations'> <Menu.SubMenu key={menuKey} title='Dump annotations'>
{ {
dumpers.map((dumper: string): JSX.Element => { dumpers
const pending = (dumpActivities || []).includes(dumper); .sort((a: any, b: any) => a.name.localeCompare(b.name))
const isDefault = isDefaultFormat(dumper, taskMode); .map((dumper: any): JSX.Element => {
return ( const pending = (dumpActivities || []).includes(dumper.name);
<Menu.Item const disabled = !dumper.enabled || pending;
key={dumper} const isDefault = isDefaultFormat(dumper.name, taskMode);
disabled={pending} return (
className='cvat-menu-dump-submenu-item' <Menu.Item
> key={dumper.name}
<Icon type='download' /> disabled={disabled}
<Text strong={isDefault}>{dumper}</Text> className='cvat-menu-dump-submenu-item'
{pending && <Icon style={{ marginLeft: 10 }} type='loading' />} >
</Menu.Item> <Icon type='download' />
); <Text strong={isDefault} disabled={disabled}>{dumper.name}</Text>
}) {pending && <Icon style={{ marginLeft: 10 }} type='loading' />}
</Menu.Item>
);
})
} }
</Menu.SubMenu> </Menu.SubMenu>
); );

@ -9,7 +9,7 @@ import Text from 'antd/lib/typography/Text';
interface Props { interface Props {
menuKey: string; menuKey: string;
exporters: string[]; exporters: any[];
exportActivities: string[] | null; exportActivities: string[] | null;
} }
@ -23,20 +23,23 @@ export default function ExportSubmenu(props: Props): JSX.Element {
return ( return (
<Menu.SubMenu key={menuKey} title='Export as a dataset'> <Menu.SubMenu key={menuKey} title='Export as a dataset'>
{ {
exporters.map((exporter: string): JSX.Element => { exporters
const pending = (exportActivities || []).includes(exporter); .sort((a: any, b: any) => a.name.localeCompare(b.name))
return ( .map((exporter: any): JSX.Element => {
<Menu.Item const pending = (exportActivities || []).includes(exporter.name);
key={exporter} const disabled = !exporter.enabled || pending;
disabled={pending} return (
className='cvat-menu-export-submenu-item' <Menu.Item
> key={exporter.name}
<Icon type='export' /> disabled={disabled}
<Text>{exporter}</Text> className='cvat-menu-export-submenu-item'
{pending && <Icon style={{ marginLeft: 10 }} type='loading' />} >
</Menu.Item> <Icon type='export' />
); <Text disabled={disabled}>{exporter.name}</Text>
}) {pending && <Icon style={{ marginLeft: 10 }} type='loading' />}
</Menu.Item>
);
})
} }
</Menu.SubMenu> </Menu.SubMenu>
); );

@ -11,7 +11,7 @@ import Text from 'antd/lib/typography/Text';
interface Props { interface Props {
menuKey: string; menuKey: string;
loaders: string[]; loaders: any[];
loadActivity: string | null; loadActivity: string | null;
onFileUpload(file: File): void; onFileUpload(file: File): void;
} }
@ -27,34 +27,40 @@ export default function LoadSubmenu(props: Props): JSX.Element {
return ( return (
<Menu.SubMenu key={menuKey} title='Upload annotations'> <Menu.SubMenu key={menuKey} title='Upload annotations'>
{ {
loaders.map((_loader: string): JSX.Element => { loaders
const [loader, accept] = _loader.split('::'); .sort((a: any, b: any) => a.name.localeCompare(b.name))
const pending = loadActivity === loader; .map((loader: any): JSX.Element => {
return ( const accept = loader.format
<Menu.Item .split(',')
key={loader} .map((x: string) => `.${x.trimStart()}`)
disabled={!!loadActivity} .join(', '); // add '.' to each extension in a list
className='cvat-menu-load-submenu-item' const pending = loadActivity === loader.name;
> const disabled = !loader.enabled || !!loadActivity;
<Upload return (
accept={accept} <Menu.Item
multiple={false} key={loader.name}
showUploadList={false} disabled={disabled}
beforeUpload={(file: File): boolean => { className='cvat-menu-load-submenu-item'
onFileUpload(file);
return false;
}}
> >
<Button block type='link' disabled={!!loadActivity}> <Upload
<Icon type='upload' /> accept={accept}
<Text>{loader}</Text> multiple={false}
{pending && <Icon style={{ marginLeft: 10 }} type='loading' />} showUploadList={false}
</Button> beforeUpload={(file: File): boolean => {
</Upload> onFileUpload(file);
return false;
}}
>
<Button block type='link' disabled={disabled}>
<Icon type='upload' />
<Text>{loader.name}</Text>
{pending && <Icon style={{ marginLeft: 10 }} type='loading' />}
</Button>
</Upload>
</Menu.Item> </Menu.Item>
); );
}) })
} }
</Menu.SubMenu> </Menu.SubMenu>
); );

@ -5,14 +5,14 @@
@import '../../base.scss'; @import '../../base.scss';
.ant-menu.cvat-actions-menu { .ant-menu.cvat-actions-menu {
box-shadow: 0 0 17px rgba(0,0,0,0.2); box-shadow: 0 0 17px rgba(0, 0, 0, 0.2);
> li:hover { > li:hover {
background-color: $hover-menu-color; background-color: $hover-menu-color;
} }
.ant-menu-submenu-title { .ant-menu-submenu-title {
margin: 0px; margin: 0;
width: 13em; width: 13em;
} }
} }
@ -30,10 +30,10 @@
} }
.ant-menu-item.cvat-menu-load-submenu-item { .ant-menu-item.cvat-menu-load-submenu-item {
margin: 0px; margin: 0;
padding: 0px; padding: 0;
> span > .ant-upload { > span > .ant-upload {
width: 100%; width: 100%;
height: 100%; height: 100%;

@ -4,6 +4,7 @@
import './styles.scss'; import './styles.scss';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useHistory } from 'react-router';
import Layout from 'antd/lib/layout'; import Layout from 'antd/lib/layout';
import Spin from 'antd/lib/spin'; import Spin from 'antd/lib/spin';
import Result from 'antd/lib/result'; import Result from 'antd/lib/result';
@ -13,12 +14,14 @@ import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-ba
import StatisticsModalContainer from 'containers/annotation-page/top-bar/statistics-modal'; import StatisticsModalContainer from 'containers/annotation-page/top-bar/statistics-modal';
import StandardWorkspaceComponent from './standard-workspace/standard-workspace'; import StandardWorkspaceComponent from './standard-workspace/standard-workspace';
import AttributeAnnotationWorkspace from './attribute-annotation-workspace/attribute-annotation-workspace'; import AttributeAnnotationWorkspace from './attribute-annotation-workspace/attribute-annotation-workspace';
import TagAnnotationWorkspace from './tag-annotation-workspace/tag-annotation-workspace';
interface Props { interface Props {
job: any | null | undefined; job: any | null | undefined;
fetching: boolean; fetching: boolean;
getJob(): void; getJob(): void;
saveLogs(): void; saveLogs(): void;
closeJob(): void;
workspace: Workspace; workspace: Workspace;
} }
@ -27,10 +30,12 @@ export default function AnnotationPageComponent(props: Props): JSX.Element {
job, job,
fetching, fetching,
getJob, getJob,
closeJob,
saveLogs, saveLogs,
workspace, workspace,
} = props; } = props;
const history = useHistory();
useEffect(() => { useEffect(() => {
saveLogs(); saveLogs();
const root = window.document.getElementById('root'); const root = window.document.getElementById('root');
@ -43,6 +48,10 @@ export default function AnnotationPageComponent(props: Props): JSX.Element {
if (root) { if (root) {
root.style.minHeight = ''; root.style.minHeight = '';
} }
if (!history.location.pathname.includes('/jobs')) {
closeJob();
}
}; };
}, []); }, []);
@ -70,15 +79,21 @@ export default function AnnotationPageComponent(props: Props): JSX.Element {
<Layout.Header className='cvat-annotation-header'> <Layout.Header className='cvat-annotation-header'>
<AnnotationTopBarContainer /> <AnnotationTopBarContainer />
</Layout.Header> </Layout.Header>
{ workspace === Workspace.STANDARD ? ( { workspace === Workspace.STANDARD && (
<Layout.Content> <Layout.Content style={{ height: '100%' }}>
<StandardWorkspaceComponent /> <StandardWorkspaceComponent />
</Layout.Content> </Layout.Content>
) : ( )}
<Layout.Content> { workspace === Workspace.ATTRIBUTE_ANNOTATION && (
<Layout.Content style={{ height: '100%' }}>
<AttributeAnnotationWorkspace /> <AttributeAnnotationWorkspace />
</Layout.Content> </Layout.Content>
)} )}
{ workspace === Workspace.TAG_ANNOTATION && (
<Layout.Content style={{ height: '100%' }}>
<TagAnnotationWorkspace />
</Layout.Content>
)}
<StatisticsModalContainer /> <StatisticsModalContainer />
</Layout> </Layout>
); );

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

@ -0,0 +1,214 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React, { Dispatch } from 'react';
import { AnyAction } from 'redux';
import { connect } from 'react-redux';
import Text from 'antd/lib/typography/Text';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Slider, { SliderValue } from 'antd/lib/slider';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Collapse from 'antd/lib/collapse';
import { ColorBy, CombinedState } from 'reducers/interfaces';
import {
collapseAppearance as collapseAppearanceAction,
updateTabContentHeight as updateTabContentHeightAction,
} from 'actions/annotation-actions';
import {
changeShapesColorBy as changeShapesColorByAction,
changeShapesOpacity as changeShapesOpacityAction,
changeSelectedShapesOpacity as changeSelectedShapesOpacityAction,
changeShapesBlackBorders as changeShapesBlackBordersAction,
changeShowBitmap as changeShowBitmapAction,
changeShowProjections as changeShowProjectionsAction,
} from 'actions/settings-actions';
interface StateToProps {
appearanceCollapsed: boolean;
colorBy: ColorBy;
opacity: number;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
showProjections: boolean;
}
interface DispatchToProps {
collapseAppearance(): void;
changeShapesColorBy(event: RadioChangeEvent): void;
changeShapesOpacity(event: SliderValue): void;
changeSelectedShapesOpacity(event: SliderValue): void;
changeShapesBlackBorders(event: CheckboxChangeEvent): void;
changeShowBitmap(event: CheckboxChangeEvent): void;
changeShowProjections(event: CheckboxChangeEvent): void;
}
export function computeHeight(): number {
const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar');
const [appearance] = window.document.getElementsByClassName('cvat-objects-appearance-collapse');
const [tabs] = Array.from(
window.document.querySelectorAll('.cvat-objects-sidebar-tabs > .ant-tabs-card-bar'),
);
if (sidebar && appearance && tabs) {
const maxHeight = sidebar ? sidebar.clientHeight : 0;
const appearanceHeight = appearance ? appearance.clientHeight : 0;
const tabsHeight = tabs ? tabs.clientHeight : 0;
return maxHeight - appearanceHeight - tabsHeight;
}
return 0;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
appearanceCollapsed,
},
settings: {
shapes: {
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
},
},
} = state;
return {
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
};
}
function mapDispatchToProps(dispatch: Dispatch<AnyAction>): DispatchToProps {
return {
collapseAppearance(): void {
dispatch(collapseAppearanceAction());
const [collapser] = window.document
.getElementsByClassName('cvat-objects-appearance-collapse');
if (collapser) {
const listener = (event: Event): void => {
if ((event as TransitionEvent).propertyName === 'height') {
const height = computeHeight();
dispatch(updateTabContentHeightAction(height));
collapser.removeEventListener('transitionend', listener);
}
};
collapser.addEventListener('transitionend', listener);
}
},
changeShapesColorBy(event: RadioChangeEvent): void {
dispatch(changeShapesColorByAction(event.target.value));
},
changeShapesOpacity(value: SliderValue): void {
dispatch(changeShapesOpacityAction(value as number));
},
changeSelectedShapesOpacity(value: SliderValue): void {
dispatch(changeSelectedShapesOpacityAction(value as number));
},
changeShapesBlackBorders(event: CheckboxChangeEvent): void {
dispatch(changeShapesBlackBordersAction(event.target.checked));
},
changeShowBitmap(event: CheckboxChangeEvent): void {
dispatch(changeShowBitmapAction(event.target.checked));
},
changeShowProjections(event: CheckboxChangeEvent): void {
dispatch(changeShowProjectionsAction(event.target.checked));
},
};
}
type Props = StateToProps & DispatchToProps;
function AppearanceBlock(props: Props): JSX.Element {
const {
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
collapseAppearance,
changeShapesColorBy,
changeShapesOpacity,
changeSelectedShapesOpacity,
changeShapesBlackBorders,
changeShowBitmap,
changeShowProjections,
} = props;
return (
<Collapse
onChange={collapseAppearance}
activeKey={appearanceCollapsed ? [] : ['appearance']}
className='cvat-objects-appearance-collapse'
>
<Collapse.Panel
header={
<Text strong>Appearance</Text>
}
key='appearance'
>
<div className='cvat-objects-appearance-content'>
<Text type='secondary'>Color by</Text>
<Radio.Group value={colorBy} onChange={changeShapesColorBy}>
<Radio.Button value={ColorBy.LABEL}>{ColorBy.LABEL}</Radio.Button>
<Radio.Button value={ColorBy.INSTANCE}>{ColorBy.INSTANCE}</Radio.Button>
<Radio.Button value={ColorBy.GROUP}>{ColorBy.GROUP}</Radio.Button>
</Radio.Group>
<Text type='secondary'>Opacity</Text>
<Slider
onChange={changeShapesOpacity}
value={opacity}
min={0}
max={100}
/>
<Text type='secondary'>Selected opacity</Text>
<Slider
onChange={changeSelectedShapesOpacity}
value={selectedOpacity}
min={0}
max={100}
/>
<Checkbox
onChange={changeShapesBlackBorders}
checked={blackBorders}
>
Black borders
</Checkbox>
<Checkbox
onChange={changeShowBitmap}
checked={showBitmap}
>
Show bitmap
</Checkbox>
<Checkbox
onChange={changeShowProjections}
checked={showProjections}
>
Show projections
</Checkbox>
</div>
</Collapse.Panel>
</Collapse>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(React.memo(AppearanceBlock));

@ -5,22 +5,25 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys'; import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import Layout, { SiderProps } from 'antd/lib/layout'; import Layout, { SiderProps } from 'antd/lib/layout';
import { SelectValue } from 'antd/lib/select'; import { SelectValue } from 'antd/lib/select';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { Row, Col } from 'antd/lib/grid'; import { Row, Col } from 'antd/lib/grid';
import Text from 'antd/lib/typography/Text'; import Text from 'antd/lib/typography/Text';
import Icon from 'antd/lib/icon'; import Icon from 'antd/lib/icon';
import { ThunkDispatch } from 'utils/redux';
import { Canvas } from 'cvat-canvas-wrapper';
import { LogType } from 'cvat-logger'; import { LogType } from 'cvat-logger';
import { import {
activateObject as activateObjectAction, activateObject as activateObjectAction,
updateAnnotationsAsync, updateAnnotationsAsync,
changeFrameAsync,
} from 'actions/annotation-actions'; } from 'actions/annotation-actions';
import { CombinedState } from 'reducers/interfaces'; import { CombinedState, ObjectType } from 'reducers/interfaces';
import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input'; import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input';
import AppearanceBlock from 'components/annotation-page/appearance-block';
import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons';
import ObjectSwitcher from './object-switcher'; import ObjectSwitcher from './object-switcher';
import AttributeSwitcher from './attribute-switcher'; import AttributeSwitcher from './attribute-switcher';
import ObjectBasicsEditor from './object-basics-edtior'; import ObjectBasicsEditor from './object-basics-edtior';
@ -35,11 +38,15 @@ interface StateToProps {
jobInstance: any; jobInstance: any;
keyMap: Record<string, ExtendedKeyMapOptions>; keyMap: Record<string, ExtendedKeyMapOptions>;
normalizedKeyMap: Record<string, string>; normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas;
canvasIsReady: boolean;
curZLayer: number;
} }
interface DispatchToProps { interface DispatchToProps {
activateObject(clientID: number | null, attrID: number | null): void; activateObject(clientID: number | null, attrID: number | null): void;
updateAnnotations(statesToUpdate: any[]): void; updateAnnotations(statesToUpdate: any[]): void;
changeFrame(frame: number): void;
} }
interface LabelAttrMap { interface LabelAttrMap {
@ -53,11 +60,18 @@ function mapStateToProps(state: CombinedState): StateToProps {
activatedStateID, activatedStateID,
activatedAttributeID, activatedAttributeID,
states, states,
zLayer: {
cur,
},
}, },
job: { job: {
instance: jobInstance, instance: jobInstance,
labels, labels,
}, },
canvas: {
instance: canvasInstance,
ready: canvasIsReady,
},
}, },
shortcuts: { shortcuts: {
keyMap, keyMap,
@ -73,10 +87,13 @@ function mapStateToProps(state: CombinedState): StateToProps {
states, states,
keyMap, keyMap,
normalizedKeyMap, normalizedKeyMap,
canvasInstance,
canvasIsReady,
curZLayer: cur,
}; };
} }
function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>): DispatchToProps { function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
return { return {
activateObject(clientID: number, attrID: number): void { activateObject(clientID: number, attrID: number): void {
dispatch(activateObjectAction(clientID, attrID)); dispatch(activateObjectAction(clientID, attrID));
@ -84,6 +101,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>):
updateAnnotations(states): void { updateAnnotations(states): void {
dispatch(updateAnnotationsAsync(states)); dispatch(updateAnnotationsAsync(states));
}, },
changeFrame(frame: number): void {
dispatch(changeFrameAsync(frame));
},
}; };
} }
@ -95,11 +115,18 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
activatedAttributeID, activatedAttributeID,
jobInstance, jobInstance,
updateAnnotations, updateAnnotations,
changeFrame,
activateObject, activateObject,
keyMap, keyMap,
normalizedKeyMap, normalizedKeyMap,
canvasInstance,
canvasIsReady,
curZLayer,
} = props; } = props;
const filteredStates = states.filter((state) => !state.outside
&& !state.hidden
&& state.zOrder <= curZLayer);
const [labelAttrMap, setLabelAttrMap] = useState( const [labelAttrMap, setLabelAttrMap] = useState(
labels.reduce((acc, label): LabelAttrMap => { labels.reduce((acc, label): LabelAttrMap => {
acc[label.id] = label.attributes.length ? label.attributes[0] : null; acc[label.id] = label.attributes.length ? label.attributes[0] : null;
@ -109,36 +136,52 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [activeObjectState] = activatedStateID === null const collapse = (): void => {
? [null] : states.filter((objectState: any): boolean => ( const [collapser] = window.document
objectState.clientID === activatedStateID .getElementsByClassName('attribute-annotation-sidebar');
));
if (collapser) {
collapser.addEventListener('transitionend', () => {
canvasInstance.fitCanvas();
}, { once: true });
}
setSidebarCollapsed(!sidebarCollapsed);
};
const indexes = filteredStates.map((state) => state.clientID);
const activatedIndex = indexes.indexOf(activatedStateID);
const activeObjectState = activatedStateID === null || activatedIndex === -1
? null : filteredStates[activatedIndex];
const activeAttribute = activeObjectState const activeAttribute = activeObjectState
? labelAttrMap[activeObjectState.label.id] ? labelAttrMap[activeObjectState.label.id]
: null; : null;
if (activeObjectState) { if (canvasIsReady) {
const attribute = labelAttrMap[activeObjectState.label.id]; if (activeObjectState) {
if (attribute && attribute.id !== activatedAttributeID) { const attribute = labelAttrMap[activeObjectState.label.id];
activateObject(activatedStateID, attribute ? attribute.id : null); if (attribute && attribute.id !== activatedAttributeID) {
activateObject(activatedStateID, attribute ? attribute.id : null);
}
} else if (filteredStates.length) {
const attribute = labelAttrMap[filteredStates[0].label.id];
activateObject(filteredStates[0].clientID, attribute ? attribute.id : null);
} }
} else if (states.length) {
const attribute = labelAttrMap[states[0].label.id];
activateObject(states[0].clientID, attribute ? attribute.id : null);
} }
const nextObject = (step: number): void => { const nextObject = (step: number): void => {
if (states.length) { if (filteredStates.length) {
const index = states.indexOf(activeObjectState); const index = filteredStates.indexOf(activeObjectState);
let nextIndex = index + step; let nextIndex = index + step;
if (nextIndex > states.length - 1) { if (nextIndex > filteredStates.length - 1) {
nextIndex = 0; nextIndex = 0;
} else if (nextIndex < 0) { } else if (nextIndex < 0) {
nextIndex = states.length - 1; nextIndex = filteredStates.length - 1;
} }
if (nextIndex !== index) { if (nextIndex !== index) {
const attribute = labelAttrMap[states[nextIndex].label.id]; const attribute = labelAttrMap[filteredStates[nextIndex].label.id];
activateObject(states[nextIndex].clientID, attribute ? attribute.id : null); activateObject(filteredStates[nextIndex].clientID, attribute ? attribute.id : null);
} }
} }
}; };
@ -181,42 +224,74 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
collapsed: sidebarCollapsed, collapsed: sidebarCollapsed,
}; };
const preventDefault = (event: KeyboardEvent | undefined): void => {
if (event) {
event.preventDefault();
}
};
const subKeyMap = { const subKeyMap = {
NEXT_ATTRIBUTE: keyMap.NEXT_ATTRIBUTE, NEXT_ATTRIBUTE: keyMap.NEXT_ATTRIBUTE,
PREVIOUS_ATTRIBUTE: keyMap.PREVIOUS_ATTRIBUTE, PREVIOUS_ATTRIBUTE: keyMap.PREVIOUS_ATTRIBUTE,
NEXT_OBJECT: keyMap.NEXT_OBJECT, NEXT_OBJECT: keyMap.NEXT_OBJECT,
PREVIOUS_OBJECT: keyMap.PREVIOUS_OBJECT, PREVIOUS_OBJECT: keyMap.PREVIOUS_OBJECT,
SWITCH_LOCK: keyMap.SWITCH_LOCK,
SWITCH_OCCLUDED: keyMap.SWITCH_OCCLUDED,
NEXT_KEY_FRAME: keyMap.NEXT_KEY_FRAME,
PREV_KEY_FRAME: keyMap.PREV_KEY_FRAME,
}; };
const handlers = { const handlers = {
NEXT_ATTRIBUTE: (event: KeyboardEvent | undefined) => { NEXT_ATTRIBUTE: (event: KeyboardEvent | undefined) => {
if (event) { preventDefault(event);
event.preventDefault();
}
nextAttribute(1); nextAttribute(1);
}, },
PREVIOUS_ATTRIBUTE: (event: KeyboardEvent | undefined) => { PREVIOUS_ATTRIBUTE: (event: KeyboardEvent | undefined) => {
if (event) { preventDefault(event);
event.preventDefault();
}
nextAttribute(-1); nextAttribute(-1);
}, },
NEXT_OBJECT: (event: KeyboardEvent | undefined) => { NEXT_OBJECT: (event: KeyboardEvent | undefined) => {
if (event) { preventDefault(event);
event.preventDefault();
}
nextObject(1); nextObject(1);
}, },
PREVIOUS_OBJECT: (event: KeyboardEvent | undefined) => { PREVIOUS_OBJECT: (event: KeyboardEvent | undefined) => {
if (event) { preventDefault(event);
event.preventDefault();
}
nextObject(-1); nextObject(-1);
}, },
SWITCH_LOCK: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState) {
activeObjectState.lock = !activeObjectState.lock;
updateAnnotations([activeObjectState]);
}
},
SWITCH_OCCLUDED: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState && activeObjectState.objectType !== ObjectType.TAG) {
activeObjectState.occluded = !activeObjectState.occluded;
updateAnnotations([activeObjectState]);
}
},
NEXT_KEY_FRAME: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) {
const frame = typeof (activeObjectState.keyframes.next) === 'number'
? activeObjectState.keyframes.next : null;
if (frame !== null && canvasInstance.isAbleToChangeFrame()) {
changeFrame(frame);
}
}
},
PREV_KEY_FRAME: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) {
const frame = typeof (activeObjectState.keyframes.prev) === 'number'
? activeObjectState.keyframes.prev : null;
if (frame !== null && canvasInstance.isAbleToChangeFrame()) {
changeFrame(frame);
}
}
},
}; };
if (activeObjectState) { if (activeObjectState) {
@ -227,7 +302,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
className={`cvat-objects-sidebar-sider className={`cvat-objects-sidebar-sider
ant-layout-sider-zero-width-trigger ant-layout-sider-zero-width-trigger
ant-layout-sider-zero-width-trigger-left`} ant-layout-sider-zero-width-trigger-left`}
onClick={() => setSidebarCollapsed(!sidebarCollapsed)} onClick={collapse}
> >
{sidebarCollapsed ? <Icon type='menu-fold' title='Show' /> {sidebarCollapsed ? <Icon type='menu-fold' title='Show' />
: <Icon type='menu-unfold' title='Hide' />} : <Icon type='menu-unfold' title='Hide' />}
@ -242,15 +317,14 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
currentLabel={activeObjectState.label.name} currentLabel={activeObjectState.label.name}
clientID={activeObjectState.clientID} clientID={activeObjectState.clientID}
occluded={activeObjectState.occluded} occluded={activeObjectState.occluded}
objectsCount={states.length} objectsCount={filteredStates.length}
currentIndex={states.indexOf(activeObjectState)} currentIndex={filteredStates.indexOf(activeObjectState)}
normalizedKeyMap={normalizedKeyMap} normalizedKeyMap={normalizedKeyMap}
nextObject={nextObject} nextObject={nextObject}
/> />
<ObjectBasicsEditor <ObjectBasicsEditor
currentLabel={activeObjectState.label.name} currentLabel={activeObjectState.label.name}
labels={labels} labels={labels}
occluded={activeObjectState.occluded}
changeLabel={(value: SelectValue): void => { changeLabel={(value: SelectValue): void => {
const labelName = value as string; const labelName = value as string;
const [newLabel] = labels const [newLabel] = labels
@ -258,10 +332,12 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
activeObjectState.label = newLabel; activeObjectState.label = newLabel;
updateAnnotations([activeObjectState]); updateAnnotations([activeObjectState]);
}} }}
setOccluded={(event: CheckboxChangeEvent): void => { />
activeObjectState.occluded = event.target.checked; <ObjectButtonsContainer
updateAnnotations([activeObjectState]); clientID={activeObjectState.clientID}
}} outsideDisabled
hiddenDisabled
keyframeDisabled
/> />
{ {
activeAttribute activeAttribute
@ -276,6 +352,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
nextAttribute={nextAttribute} nextAttribute={nextAttribute}
/> />
<AttributeEditor <AttributeEditor
clientID={activeObjectState.clientID}
attribute={activeAttribute} attribute={activeAttribute}
currentValue={activeObjectState.attributes[activeAttribute.id]} currentValue={activeObjectState.attributes[activeAttribute.id]}
onChange={(value: string) => { onChange={(value: string) => {
@ -300,12 +377,29 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
</div> </div>
) )
} }
{ !sidebarCollapsed && <AppearanceBlock /> }
</Layout.Sider> </Layout.Sider>
); );
} }
return ( return (
<Layout.Sider {...siderProps}> <Layout.Sider {...siderProps}>
{/* eslint-disable-next-line */}
<span
className={`cvat-objects-sidebar-sider
ant-layout-sider-zero-width-trigger
ant-layout-sider-zero-width-trigger-left`}
onClick={collapse}
>
{sidebarCollapsed ? <Icon type='menu-fold' title='Show' />
: <Icon type='menu-unfold' title='Hide' />}
</span>
<Row className='cvat-objects-sidebar-filter-input'>
<Col>
<AnnotationsFiltersInput />
</Col>
</Row>
<div className='attribute-annotations-sidebar-not-found-wrapper'> <div className='attribute-annotations-sidebar-not-found-wrapper'>
<Text strong>No objects found</Text> <Text strong>No objects found</Text>
</div> </div>

@ -13,6 +13,7 @@ import Input from 'antd/lib/input';
import consts from 'consts'; import consts from 'consts';
interface InputElementParameters { interface InputElementParameters {
clientID: number;
attrID: number; attrID: number;
inputType: string; inputType: string;
values: string[]; values: string[];
@ -24,6 +25,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
const { const {
inputType, inputType,
attrID, attrID,
clientID,
values, values,
currentValue, currentValue,
onChange, onChange,
@ -103,7 +105,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
<div className='attribute-annotation-sidebar-attr-elem-wrapper'> <div className='attribute-annotation-sidebar-attr-elem-wrapper'>
<Input <Input
autoFocus autoFocus
key={attrID} key={`${clientID}:${attrID}`}
defaultValue={currentValue} defaultValue={currentValue}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target; const { value } = event.target;
@ -257,13 +259,19 @@ function renderList(parameters: ListParameters): JSX.Element | null {
} }
interface Props { interface Props {
clientID: number;
attribute: any; attribute: any;
currentValue: string; currentValue: string;
onChange(value: string): void; onChange(value: string): void;
} }
function AttributeEditor(props: Props): JSX.Element { function AttributeEditor(props: Props): JSX.Element {
const { attribute, currentValue, onChange } = props; const {
attribute,
currentValue,
onChange,
clientID,
} = props;
const { inputType, values, id: attrID } = attribute; const { inputType, values, id: attrID } = attribute;
return ( return (
@ -271,6 +279,7 @@ function AttributeEditor(props: Props): JSX.Element {
{renderList({ values, inputType, onChange })} {renderList({ values, inputType, onChange })}
<hr /> <hr />
{renderInputElement({ {renderInputElement({
clientID,
attrID, attrID,
inputType, inputType,
currentValue, currentValue,

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

@ -4,24 +4,15 @@
import React from 'react'; import React from 'react';
import Select, { SelectValue } from 'antd/lib/select'; import Select, { SelectValue } from 'antd/lib/select';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
interface Props { interface Props {
currentLabel: string; currentLabel: string;
labels: any[]; labels: any[];
occluded: boolean;
setOccluded(event: CheckboxChangeEvent): void;
changeLabel(value: SelectValue): void; changeLabel(value: SelectValue): void;
} }
function ObjectBasicsEditor(props: Props): JSX.Element { function ObjectBasicsEditor(props: Props): JSX.Element {
const { const { currentLabel, labels, changeLabel } = props;
currentLabel,
occluded,
labels,
setOccluded,
changeLabel,
} = props;
return ( return (
<div className='attribute-annotation-sidebar-basics-editor'> <div className='attribute-annotation-sidebar-basics-editor'>
@ -35,7 +26,6 @@ function ObjectBasicsEditor(props: Props): JSX.Element {
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
<Checkbox checked={occluded} onChange={setOccluded}>Occluded</Checkbox>
</div> </div>
); );
} }

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

@ -13,7 +13,8 @@
padding: 5px; padding: 5px;
} }
.attribute-annotation-sidebar-switcher { .attribute-annotation-sidebar-object-switcher,
.attribute-annotation-sidebar-attribute-switcher {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
@ -36,7 +37,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
font-size: 18px; font-size: 18px;
margin: 10px 0px; margin: 10px 0;
} }
.attribute-annotations-sidebar-not-found-wrapper { .attribute-annotations-sidebar-not-found-wrapper {
@ -45,7 +46,7 @@
} }
.attribute-annotation-sidebar-attr-list-wrapper { .attribute-annotation-sidebar-attr-list-wrapper {
margin: 10px 0px 10px 10px; margin: 10px 0 10px 10px;
} }
.attribute-annotation-sidebar-attr-elem-wrapper { .attribute-annotation-sidebar-attr-elem-wrapper {

@ -28,7 +28,7 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null {
return ReactDOM.createPortal( return ReactDOM.createPortal(
<div className='cvat-canvas-context-menu' style={{ top, left }}> <div className='cvat-canvas-context-menu' style={{ top, left }}>
<ObjectItemContainer clientID={activatedStateID} /> <ObjectItemContainer key={activatedStateID} clientID={activatedStateID} />
</div>, </div>,
window.document.body, window.document.body,
); );

@ -2,40 +2,131 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import React from 'react'; import React, { useState } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import Button from 'antd/lib/button'; import Button from 'antd/lib/button';
import Tooltip from 'antd/lib/tooltip'; import Tooltip from 'antd/lib/tooltip';
import { connect } from 'react-redux';
interface Props { import { CombinedState, ContextMenuType } from 'reducers/interfaces';
activatedStateID: number | null; import { updateAnnotationsAsync, updateCanvasContextMenu } from 'actions/annotation-actions';
interface StateToProps {
activatedState: any | null;
selectedPoint: number | null;
visible: boolean; visible: boolean;
left: number;
top: number; top: number;
onPointDelete(): void; left: number;
type: ContextMenuType;
} }
export default function CanvasPointContextMenu(props: Props): JSX.Element | null { function mapStateToProps(state: CombinedState): StateToProps {
const { const {
onPointDelete, annotation: {
activatedStateID, annotations: {
states,
activatedStateID,
},
canvas: {
contextMenu: {
visible,
top,
left,
type,
pointID: selectedPoint,
},
},
},
} = state;
return {
activatedState: activatedStateID === null
? null : states.filter((_state) => _state.clientID === activatedStateID)[0] || null,
selectedPoint,
visible, visible,
left, left,
top, top,
type,
};
}
interface DispatchToProps {
onUpdateAnnotations(states: any[]): void;
onCloseContextMenu(): void;
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
onUpdateAnnotations(states: any[]): void {
dispatch(updateAnnotationsAsync(states));
},
onCloseContextMenu(): void {
dispatch(updateCanvasContextMenu(false, 0, 0));
},
};
}
type Props = StateToProps & DispatchToProps;
function CanvasPointContextMenu(props: Props): React.ReactPortal | null {
const {
onCloseContextMenu,
onUpdateAnnotations,
activatedState,
visible,
type,
top,
left,
} = props; } = props;
if (!visible || activatedStateID === null) { const [contextMenuFor, setContextMenuFor] = useState(activatedState);
return null;
if (activatedState !== contextMenuFor) {
setContextMenuFor(activatedState);
if (visible && type === ContextMenuType.CANVAS_SHAPE_POINT) {
onCloseContextMenu();
}
} }
return ReactDOM.createPortal( const onPointDelete = (): void => {
<div className='cvat-canvas-point-context-menu' style={{ top, left }}> const { selectedPoint } = props;
<Tooltip title='Delete point [Ctrl + dblclick]'> if (contextMenuFor && selectedPoint !== null) {
<Button type='link' icon='delete' onClick={onPointDelete}> contextMenuFor.points = contextMenuFor.points.slice(0, selectedPoint * 2)
Delete point .concat(contextMenuFor.points.slice(selectedPoint * 2 + 2));
</Button> onUpdateAnnotations([contextMenuFor]);
</Tooltip> onCloseContextMenu();
</div>, }
window.document.body, };
);
const onSetStartPoint = (): void => {
const { selectedPoint } = props;
if (contextMenuFor && selectedPoint !== null && contextMenuFor.shapeType === 'polygon') {
contextMenuFor.points = contextMenuFor.points.slice(selectedPoint * 2)
.concat(contextMenuFor.points.slice(0, selectedPoint * 2));
onUpdateAnnotations([contextMenuFor]);
onCloseContextMenu();
}
};
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]' mouseLeaveDelay={0}>
<Button type='link' icon='delete' onClick={onPointDelete}>
Delete point
</Button>
</Tooltip>
{contextMenuFor && contextMenuFor.shapeType === 'polygon' && (
<Button type='link' icon='environment' onClick={onSetStartPoint}>
Set start point
</Button>
)}
</div>,
window.document.body,
)) : null;
} }
export default connect(
mapStateToProps,
mapDispatchToProps,
)(CanvasPointContextMenu);

@ -58,13 +58,13 @@ interface Props {
contrastLevel: number; contrastLevel: number;
saturationLevel: number; saturationLevel: number;
resetZoom: boolean; resetZoom: boolean;
contextVisible: boolean;
contextType: ContextMenuType;
aamZoomMargin: number; aamZoomMargin: number;
showObjectsTextAlways: boolean; showObjectsTextAlways: boolean;
showAllInterpolationTracks: boolean;
workspace: Workspace; workspace: Workspace;
automaticBordering: boolean; automaticBordering: boolean;
keyMap: Record<string, ExtendedKeyMapOptions>; keyMap: Record<string, ExtendedKeyMapOptions>;
canvasBackgroundColor: string;
switchableAutomaticBordering: boolean; switchableAutomaticBordering: boolean;
onSetupCanvas: () => void; onSetupCanvas: () => void;
onDragCanvas: (enabled: boolean) => void; onDragCanvas: (enabled: boolean) => void;
@ -93,6 +93,7 @@ interface Props {
onChangeGridColor(color: GridColor): void; onChangeGridColor(color: GridColor): void;
onSwitchGrid(enabled: boolean): void; onSwitchGrid(enabled: boolean): void;
onSwitchAutomaticBordering(enabled: boolean): void; onSwitchAutomaticBordering(enabled: boolean): void;
onFetchAnnotation(): void;
} }
export default class CanvasWrapperComponent extends React.PureComponent<Props> { export default class CanvasWrapperComponent extends React.PureComponent<Props> {
@ -101,7 +102,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
automaticBordering, automaticBordering,
showObjectsTextAlways, showObjectsTextAlways,
canvasInstance, canvasInstance,
curZLayer,
} = this.props; } = this.props;
// It's awful approach from the point of view React // It's awful approach from the point of view React
@ -115,7 +115,6 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE,
displayAllText: showObjectsTextAlways, displayAllText: showObjectsTextAlways,
}); });
canvasInstance.setZLayer(curZLayer);
this.initialSetup(); this.initialSetup();
this.updateCanvas(); this.updateCanvas();
@ -137,6 +136,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
curZLayer, curZLayer,
resetZoom, resetZoom,
grid, grid,
gridSize,
gridOpacity, gridOpacity,
gridColor, gridColor,
brightnessLevel, brightnessLevel,
@ -145,8 +145,11 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
workspace, workspace,
frameFetching, frameFetching,
showObjectsTextAlways, showObjectsTextAlways,
showAllInterpolationTracks,
automaticBordering, automaticBordering,
showProjections, showProjections,
canvasBackgroundColor,
onFetchAnnotation,
} = this.props; } = this.props;
if (prevProps.showObjectsTextAlways !== showObjectsTextAlways if (prevProps.showObjectsTextAlways !== showObjectsTextAlways
@ -161,6 +164,10 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
}); });
} }
if (prevProps.showAllInterpolationTracks !== showAllInterpolationTracks) {
onFetchAnnotation();
}
if (prevProps.sidebarCollapsed !== sidebarCollapsed) { if (prevProps.sidebarCollapsed !== sidebarCollapsed) {
const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar'); const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar');
if (sidebar) { if (sidebar) {
@ -179,6 +186,10 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
} }
if (gridSize !== prevProps.gridSize) {
canvasInstance.grid(gridSize, gridSize);
}
if (gridOpacity !== prevProps.gridOpacity if (gridOpacity !== prevProps.gridOpacity
|| gridColor !== prevProps.gridColor || gridColor !== prevProps.gridColor
|| grid !== prevProps.grid) { || grid !== prevProps.grid) {
@ -204,17 +215,15 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
} }
if (prevProps.curZLayer !== curZLayer) { if (prevProps.annotations !== annotations
canvasInstance.setZLayer(curZLayer); || prevProps.frameData !== frameData
} || prevProps.curZLayer !== curZLayer) {
if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) {
this.updateCanvas(); this.updateCanvas();
} }
if (prevProps.frame !== frameData.number if (prevProps.frame !== frameData.number
&& resetZoom && ((resetZoom && workspace !== Workspace.ATTRIBUTE_ANNOTATION)
&& workspace !== Workspace.ATTRIBUTE_ANNOTATION || workspace === Workspace.TAG_ANNOTATION)
) { ) {
canvasInstance.html().addEventListener('canvas.setup', () => { canvasInstance.html().addEventListener('canvas.setup', () => {
canvasInstance.fit(); canvasInstance.fit();
@ -222,7 +231,8 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
if (prevProps.opacity !== opacity || prevProps.blackBorders !== blackBorders if (prevProps.opacity !== opacity || prevProps.blackBorders !== blackBorders
|| prevProps.selectedOpacity !== selectedOpacity || prevProps.colorBy !== colorBy) { || prevProps.selectedOpacity !== selectedOpacity || prevProps.colorBy !== colorBy
) {
this.updateShapesView(); this.updateShapesView();
} }
@ -243,6 +253,13 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
} }
} }
if (prevProps.canvasBackgroundColor !== canvasBackgroundColor) {
const canvasWrapperElement = window.document.getElementsByClassName('cvat-canvas-container').item(0) as HTMLElement | null;
if (canvasWrapperElement) {
canvasWrapperElement.style.backgroundColor = canvasBackgroundColor;
}
}
this.activateOnCanvas(); this.activateOnCanvas();
} }
@ -382,10 +399,9 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
const { const {
activatedStateID, activatedStateID,
onUpdateContextMenu, onUpdateContextMenu,
contextType,
} = this.props; } = this.props;
if (contextType !== ContextMenuType.CANVAS_SHAPE_POINT) { if (e.target && !(e.target as HTMLElement).classList.contains('svg_select_points')) {
onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY, onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY,
ContextMenuType.CANVAS_SHAPE); ContextMenuType.CANVAS_SHAPE);
} }
@ -442,7 +458,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
onActivateObject, onActivateObject,
} = this.props; } = this.props;
if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { if (workspace !== Workspace.STANDARD) {
return; return;
} }
@ -571,7 +587,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
canvasInstance.fit(); canvasInstance.fit();
} }
} }
if (activatedState.objectType !== ObjectType.TAG) { if (activatedState && activatedState.objectType !== ObjectType.TAG) {
canvasInstance.activate(activatedStateID, activatedAttributeID); canvasInstance.activate(activatedStateID, activatedAttributeID);
} }
const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`);
@ -616,16 +632,15 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
private updateCanvas(): void { private updateCanvas(): void {
const { const {
curZLayer,
annotations, annotations,
frameData, frameData,
frameAngle,
canvasInstance, canvasInstance,
} = this.props; } = this.props;
if (frameData !== null) { if (frameData !== null) {
canvasInstance.setup(frameData, annotations canvasInstance.setup(frameData, annotations
.filter((e) => e.objectType !== ObjectType.TAG)); .filter((e) => e.objectType !== ObjectType.TAG), curZLayer);
canvasInstance.rotate(frameAngle);
} }
} }
@ -639,6 +654,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
brightnessLevel, brightnessLevel,
contrastLevel, contrastLevel,
saturationLevel, saturationLevel,
canvasBackgroundColor,
} = this.props; } = this.props;
// Size // Size
@ -665,6 +681,11 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
+ `saturate(${saturationLevel / 100})`; + `saturate(${saturationLevel / 100})`;
} }
const canvasWrapperElement = window.document.getElementsByClassName('cvat-canvas-container').item(0) as HTMLElement | null;
if (canvasWrapperElement) {
canvasWrapperElement.style.backgroundColor = canvasBackgroundColor;
}
// Events // Events
canvasInstance.html().addEventListener('canvas.setup', () => { canvasInstance.html().addEventListener('canvas.setup', () => {
const { activatedStateID, activatedAttributeID } = this.props; const { activatedStateID, activatedAttributeID } = this.props;
@ -855,7 +876,7 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
defaultValue={0} defaultValue={0}
onChange={(value: SliderValue): void => onSwitchZLayer(value as number)} 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} /> <Icon type='plus-circle' onClick={onAddZLayer} />
</Tooltip> </Tooltip>
</div> </div>

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

Loading…
Cancel
Save